1// SPDX-License-Identifier: GPL-3.0-or-later
2
3#include "mos/filesystem/vfs_types.hpp"
4#include "mos/filesystem/vfs_utils.hpp"
5#include "mos/mm/mm.hpp"
6#include "mos/mm/physical/pmm.hpp"
7#include "mos/platform/platform.hpp"
8
9#include <algorithm>
10#include <mos/allocator.hpp>
11#include <mos/filesystem/dentry.hpp>
12#include <mos/filesystem/fs_types.h>
13#include <mos/filesystem/vfs.hpp>
14#include <mos/lib/structures/list.hpp>
15#include <mos/lib/structures/tree.hpp>
16#include <mos/misc/setup.hpp>
17#include <mos/mos_global.h>
18#include <mos/syslog/printk.hpp>
19#include <mos_stdlib.hpp>
20#include <mos_string.hpp>
21
22#define CPIO_MODE_FILE_TYPE 0170000 // This masks the file type bits.
23#define CPIO_MODE_SOCKET 0140000 // File type value for sockets.
24#define CPIO_MODE_SYMLINK 0120000 // File type value for symbolic links. For symbolic links, the link body is stored as file data.
25#define CPIO_MODE_FILE 0100000 // File type value for regular files.
26#define CPIO_MODE_BLOCKDEV 0060000 // File type value for block special devices.
27#define CPIO_MODE_DIR 0040000 // File type value for directories.
28#define CPIO_MODE_CHARDEV 0020000 // File type value for character special devices.
29#define CPIO_MODE_FIFO 0010000 // File type value for named pipes or FIFOs.
30#define CPIO_MODE_SUID 0004000 // SUID bit.
31#define CPIO_MODE_SGID 0002000 // SGID bit.
32#define CPIO_MODE_STICKY 0001000 // Sticky bit.
33
34typedef struct
35{
36 char magic[6];
37 char ino[8];
38 char mode[8];
39 char uid[8];
40 char gid[8];
41 char nlink[8];
42 char mtime[8];
43
44 char filesize[8];
45 char devmajor[8];
46 char devminor[8];
47 char rdevmajor[8];
48 char rdevminor[8];
49
50 char namesize[8];
51 char check[8];
52} cpio_newc_header_t;
53
54MOS_STATIC_ASSERT(sizeof(cpio_newc_header_t) == 110, "cpio_newc_header has wrong size");
55
56struct cpio_inode_t : mos::NamedType<"CPIO.Inode">
57{
58 inode_t inode;
59 size_t header_offset;
60 size_t name_offset, name_length;
61 size_t data_offset;
62 cpio_newc_header_t header;
63};
64
65extern const inode_ops_t cpio_dir_inode_ops;
66extern const inode_ops_t cpio_file_inode_ops;
67extern const file_ops_t cpio_file_ops;
68extern const inode_cache_ops_t cpio_icache_ops;
69extern const superblock_ops_t cpio_sb_ops;
70extern const file_ops_t cpio_noop_file_ops = { .open: 0 };
71
72static size_t initrd_read(void *buf, size_t size, size_t offset)
73{
74 if (unlikely(offset + size > platform_info->initrd_npages * MOS_PAGE_SIZE))
75 mos_panic("initrd_read: out of bounds");
76 memcpy(dest: buf, src: (void *) (pfn_va(platform_info->initrd_pfn) + offset), n: size);
77 return size;
78}
79
80static file_type_t cpio_modebits_to_filetype(u32 modebits)
81{
82 file_type_t type = FILE_TYPE_UNKNOWN;
83 switch (modebits & CPIO_MODE_FILE_TYPE)
84 {
85 case CPIO_MODE_FILE: type = FILE_TYPE_REGULAR; break;
86 case CPIO_MODE_DIR: type = FILE_TYPE_DIRECTORY; break;
87 case CPIO_MODE_SYMLINK: type = FILE_TYPE_SYMLINK; break;
88 case CPIO_MODE_CHARDEV: type = FILE_TYPE_CHAR_DEVICE; break;
89 case CPIO_MODE_BLOCKDEV: type = FILE_TYPE_BLOCK_DEVICE; break;
90 case CPIO_MODE_FIFO: type = FILE_TYPE_NAMED_PIPE; break;
91 case CPIO_MODE_SOCKET: type = FILE_TYPE_SOCKET; break;
92 default: mos_warn("invalid cpio file mode"); break;
93 }
94
95 return type;
96}
97
98static bool cpio_read_metadata(const char *target, cpio_newc_header_t *header, size_t *header_offset, size_t *name_offset, size_t *name_length, size_t *data_offset,
99 size_t *data_length)
100{
101 if (unlikely(strcmp(target, "TRAILER!!!") == 0))
102 mos_panic("what the heck are you doing?");
103
104 size_t offset = 0;
105
106 while (true)
107 {
108 initrd_read(buf: header, size: sizeof(cpio_newc_header_t), offset);
109
110 if (strncmp(str1: header->magic, str2: "07070", n: 5) != 0 || (header->magic[5] != '1' && header->magic[5] != '2'))
111 {
112 mos_warn("invalid cpio header magic, possibly corrupt archive");
113 return false;
114 }
115
116 offset += sizeof(cpio_newc_header_t);
117
118 const size_t filename_len = strntoll(str: header->namesize, NULL, base: 16, n: sizeof(header->namesize) / sizeof(char));
119
120 char filename[filename_len + 1]; // +1 for null terminator
121 initrd_read(buf: filename, size: filename_len, offset);
122 filename[filename_len] = '\0';
123
124 bool found = strncmp(str1: filename, str2: target, n: filename_len) == 0;
125
126 if (found)
127 {
128 *header_offset = offset - sizeof(cpio_newc_header_t);
129 *name_offset = offset;
130 *name_length = filename_len;
131 }
132
133 if (unlikely(strcmp(filename, "TRAILER!!!") == 0))
134 return false;
135
136 offset += filename_len;
137 offset = ((offset + 3) & ~0x03); // align to 4 bytes
138
139 size_t data_len = strntoll(str: header->filesize, NULL, base: 16, n: sizeof(header->filesize) / sizeof(char));
140 if (found)
141 {
142 *data_offset = offset;
143 *data_length = data_len;
144 }
145
146 offset += data_len;
147 offset = ((offset + 3) & ~0x03); // align to 4 bytes (again)
148
149 if (found)
150 return true;
151 }
152
153 MOS_UNREACHABLE();
154}
155
156should_inline cpio_inode_t *CPIO_INODE(inode_t *inode)
157{
158 return container_of(inode, cpio_inode_t, inode);
159}
160
161// ============================================================================================================
162
163static cpio_inode_t *cpio_inode_trycreate(const char *path, superblock_t *sb)
164{
165 cpio_newc_header_t header = { 0 };
166 size_t header_offset = 0;
167 size_t name_offset = 0, name_length = 0;
168 size_t data_offset = 0, data_length = 0;
169 const bool found = cpio_read_metadata(target: path, header: &header, header_offset: &header_offset, name_offset: &name_offset, name_length: &name_length, data_offset: &data_offset, data_length: &data_length);
170 if (!found)
171 return NULL;
172
173 cpio_inode_t *cpio_inode = mos::create<cpio_inode_t>();
174 cpio_inode->header = header;
175 cpio_inode->header_offset = header_offset;
176 cpio_inode->name_offset = name_offset;
177 cpio_inode->name_length = name_length;
178 cpio_inode->data_offset = data_offset;
179
180 const u32 modebits = strntoll(str: cpio_inode->header.mode, NULL, base: 16, n: sizeof(cpio_inode->header.mode) / sizeof(char));
181 const u64 ino = strntoll(str: cpio_inode->header.ino, NULL, base: 16, n: sizeof(cpio_inode->header.ino) / sizeof(char));
182 const file_type_t file_type = cpio_modebits_to_filetype(modebits: modebits & CPIO_MODE_FILE_TYPE);
183
184 inode_t *const inode = &cpio_inode->inode;
185 inode_init(inode, sb, ino, type: file_type);
186
187 // 0000777 - The lower 9 bits specify read/write/execute permissions for world, group, and user following standard POSIX conventions.
188 inode->perm = modebits & PERM_MASK;
189 inode->size = data_length;
190 inode->uid = strntoll(str: cpio_inode->header.uid, NULL, base: 16, n: sizeof(cpio_inode->header.uid) / sizeof(char));
191 inode->gid = strntoll(str: cpio_inode->header.gid, NULL, base: 16, n: sizeof(cpio_inode->header.gid) / sizeof(char));
192 inode->sticky = modebits & CPIO_MODE_STICKY;
193 inode->suid = modebits & CPIO_MODE_SUID;
194 inode->sgid = modebits & CPIO_MODE_SGID;
195 inode->nlinks = strntoll(str: cpio_inode->header.nlink, NULL, base: 16, n: sizeof(cpio_inode->header.nlink) / sizeof(char));
196 inode->ops = file_type == FILE_TYPE_DIRECTORY ? &cpio_dir_inode_ops : &cpio_file_inode_ops;
197 inode->file_ops = file_type == FILE_TYPE_DIRECTORY ? &cpio_noop_file_ops : &cpio_file_ops;
198 inode->cache.ops = &cpio_icache_ops;
199
200 return cpio_inode;
201}
202
203// ============================================================================================================
204
205static PtrResult<dentry_t> cpio_mount(filesystem_t *fs, const char *dev_name, const char *mount_options)
206{
207 if (unlikely(mount_options) && strlen(str: mount_options) > 0)
208 mos_warn("cpio: mount options are not supported");
209
210 if (dev_name && strcmp(str1: dev_name, str2: "none") != 0)
211 pr_warn("cpio: mount: dev_name is not supported");
212
213 superblock_t *sb = mos::create<superblock_t>();
214 sb->ops = &cpio_sb_ops;
215
216 cpio_inode_t *i = cpio_inode_trycreate(path: ".", sb);
217 if (!i)
218 {
219 delete sb;
220 return -ENOENT; // not found
221 }
222
223 pr_dinfo2(cpio, "cpio header: %.6s", i->header.magic);
224 sb->fs = fs;
225 sb->root = dentry_get_from_parent(sb, NULL);
226 dentry_attach(d: sb->root, inode: &i->inode);
227 sb->root->superblock = i->inode.superblock = sb;
228 return sb->root;
229}
230
231static bool cpio_i_lookup(inode_t *parent_dir, dentry_t *dentry)
232{
233 // keep prepending the path with the parent path, until we reach the root
234 char pathbuf[MOS_PATH_MAX_LENGTH] = { 0 };
235 dentry_path(dentry, root: parent_dir->superblock->root, buf: pathbuf, size: sizeof(pathbuf));
236 const char *path = pathbuf + 1; // skip the first slash
237
238 cpio_inode_t *inode = cpio_inode_trycreate(path, sb: parent_dir->superblock);
239 if (!inode)
240 return false; // not found
241
242 dentry_attach(d: dentry, inode: &inode->inode);
243 return true;
244}
245
246static void cpio_i_iterate_dir(dentry_t *dentry, vfs_listdir_state_t *state, dentry_iterator_op add_record)
247{
248 dentry_t *d_parent = dentry_parent(dentry: *dentry);
249 if (d_parent == NULL)
250 d_parent = root_dentry;
251
252 MOS_ASSERT(d_parent->inode != NULL);
253 MOS_ASSERT(dentry->inode);
254
255 add_record(state, dentry->inode->ino, ".", FILE_TYPE_DIRECTORY);
256 add_record(state, d_parent->inode->ino, "..", FILE_TYPE_DIRECTORY);
257
258 cpio_inode_t *inode = CPIO_INODE(inode: dentry->inode);
259
260 char path_prefix[inode->name_length + 1]; // +1 for null terminator
261 initrd_read(buf: path_prefix, size: inode->name_length, offset: inode->name_offset);
262 path_prefix[inode->name_length] = '\0';
263 size_t prefix_len = strlen(str: path_prefix); // +1 for the slash
264
265 if (strcmp(str1: path_prefix, str2: ".") == 0)
266 path_prefix[0] = '\0', prefix_len = 0; // root directory
267
268 // find all children of this directory, that starts with 'path' and doesn't have any more slashes
269 cpio_newc_header_t header;
270 size_t offset = 0;
271
272 while (true)
273 {
274 initrd_read(buf: &header, size: sizeof(cpio_newc_header_t), offset);
275 offset += sizeof(cpio_newc_header_t);
276
277 if (strncmp(str1: header.magic, str2: "07070", n: 5) != 0 || (header.magic[5] != '1' && header.magic[5] != '2'))
278 mos_panic("invalid cpio header magic, possibly corrupt archive");
279
280 const size_t filename_len = strntoll(str: header.namesize, NULL, base: 16, n: sizeof(header.namesize) / sizeof(char));
281
282 char filename[filename_len + 1]; // +1 for null terminator
283 initrd_read(buf: filename, size: filename_len, offset);
284 filename[filename_len] = '\0';
285
286 const bool found = strncmp(str1: path_prefix, str2: filename, n: prefix_len) == 0 // make sure the path starts with the parent path
287 && (prefix_len == 0 || filename[prefix_len] == '/') // make sure it's a child, not a sibling (/path/to vs /path/toooo)
288 && strchr(s: filename + prefix_len + 1, c: '/') == NULL; // make sure it's only one level deep
289
290 const bool is_TRAILER = strcmp(str1: filename, str2: "TRAILER!!!") == 0;
291 const bool is_root_dot = strcmp(str1: filename, str2: ".") == 0;
292
293 if (found && !is_TRAILER && !is_root_dot)
294 {
295 pr_dinfo2(cpio, "prefix '%s' filename '%s'", path_prefix, filename);
296
297 const u32 modebits = strntoll(str: header.mode, NULL, base: 16, n: sizeof(header.mode) / sizeof(char));
298 const file_type_t type = cpio_modebits_to_filetype(modebits: modebits & CPIO_MODE_FILE_TYPE);
299 const s64 ino = strntoll(str: header.ino, NULL, base: 16, n: sizeof(header.ino) / sizeof(char));
300
301 const char *name = filename + prefix_len + (prefix_len == 0 ? 0 : 1); // +1 for the slash if it's not the root
302 const size_t name_len = filename_len - prefix_len - (prefix_len == 0 ? 0 : 1); // -1 for the slash if it's not the root
303
304 add_record(state, ino, mos::string(name, name_len), type);
305 }
306
307 if (unlikely(is_TRAILER))
308 break;
309
310 offset += filename_len;
311 offset = ALIGN_UP(offset, 4); // align to 4 bytes
312
313 const size_t data_len = strntoll(str: header.filesize, NULL, base: 16, n: sizeof(header.filesize) / sizeof(char));
314 offset += data_len;
315 offset = ALIGN_UP(offset, 4); // align to 4 bytes (again)
316 }
317}
318
319static size_t cpio_i_readlink(dentry_t *dentry, char *buffer, size_t buflen)
320{
321 cpio_inode_t *inode = CPIO_INODE(inode: dentry->inode);
322 return initrd_read(buf: buffer, size: std::min(a: buflen, b: inode->inode.size), offset: inode->data_offset);
323}
324
325static bool cpio_sb_drop_inode(inode_t *inode)
326{
327 delete CPIO_INODE(inode);
328 return true;
329}
330
331const superblock_ops_t cpio_sb_ops = {
332 .drop_inode = cpio_sb_drop_inode,
333};
334
335const inode_ops_t cpio_dir_inode_ops = {
336 .iterate_dir = cpio_i_iterate_dir,
337 .lookup = cpio_i_lookup,
338};
339
340const inode_ops_t cpio_file_inode_ops = {
341 .readlink = cpio_i_readlink,
342};
343
344const file_ops_t cpio_file_ops = {
345 .read = vfs_generic_read,
346};
347
348PtrResult<phyframe_t> cpio_fill_cache(inode_cache_t *cache, uint64_t pgoff)
349{
350 inode_t *i = cache->owner;
351 cpio_inode_t *cpio_i = CPIO_INODE(inode: i);
352
353 phyframe_t *page = mm_get_free_page();
354 if (!page)
355 return -ENOMEM;
356
357 pmm_ref_one(page);
358
359 if ((size_t) pgoff * MOS_PAGE_SIZE >= i->size)
360 return page; // EOF, no need to read anything
361
362 const size_t bytes_to_read = std::min(a: (size_t) MOS_PAGE_SIZE, b: i->size - pgoff * MOS_PAGE_SIZE);
363 const size_t read = initrd_read(buf: (char *) phyframe_va(page), size: bytes_to_read, offset: cpio_i->data_offset + pgoff * MOS_PAGE_SIZE);
364 MOS_ASSERT(read == bytes_to_read);
365 return page;
366}
367
368const inode_cache_ops_t cpio_icache_ops = {
369 .fill_cache = cpio_fill_cache,
370};
371
372static FILESYSTEM_DEFINE(fs_cpiofs, "cpiofs", cpio_mount, NULL);
373FILESYSTEM_AUTOREGISTER(fs_cpiofs);
374