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