| 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 | |
| 34 | struct |
| 35 | { |
| 36 | char [6] = { 0 }; |
| 37 | char [8] = { 0 }; |
| 38 | char [8] = { 0 }; |
| 39 | char [8] = { 0 }; |
| 40 | char [8] = { 0 }; |
| 41 | char [8] = { 0 }; |
| 42 | char [8] = { 0 }; |
| 43 | |
| 44 | char [8] = { 0 }; |
| 45 | char [8] = { 0 }; |
| 46 | char [8] = { 0 }; |
| 47 | char [8] = { 0 }; |
| 48 | char [8] = { 0 }; |
| 49 | |
| 50 | char [8] = { 0 }; |
| 51 | char [8] = { 0 }; |
| 52 | }; |
| 53 | |
| 54 | MOS_STATIC_ASSERT(sizeof(cpio_newc_header_t) == 110, "cpio_newc_header has wrong size" ); |
| 55 | |
| 56 | struct cpio_inode_t : mos::NamedType<"CPIO.Inode" > |
| 57 | { |
| 58 | inode_t inode; |
| 59 | size_t ; |
| 60 | size_t name_offset, name_length; |
| 61 | size_t data_offset; |
| 62 | cpio_newc_header_t ; |
| 63 | }; |
| 64 | |
| 65 | extern const inode_ops_t cpio_dir_inode_ops; |
| 66 | extern const inode_ops_t cpio_file_inode_ops; |
| 67 | extern const file_ops_t cpio_file_ops; |
| 68 | extern const inode_cache_ops_t cpio_icache_ops; |
| 69 | extern const superblock_ops_t cpio_sb_ops; |
| 70 | extern const file_ops_t cpio_noop_file_ops = { .open: 0 }; |
| 71 | |
| 72 | static 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 | |
| 80 | static 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 | |
| 98 | static bool (const char *target, cpio_newc_header_t *, size_t *, 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 | |
| 156 | should_inline cpio_inode_t *CPIO_INODE(inode_t *inode) |
| 157 | { |
| 158 | return container_of(inode, cpio_inode_t, inode); |
| 159 | } |
| 160 | |
| 161 | // ============================================================================================================ |
| 162 | |
| 163 | static cpio_inode_t *cpio_inode_trycreate(const char *path, superblock_t *sb) |
| 164 | { |
| 165 | cpio_newc_header_t = {}; |
| 166 | size_t = 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 | |
| 205 | static 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 | |
| 231 | static 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 | const auto path_str = dentry_path(dentry, root: parent_dir->superblock->root); |
| 235 | const char *path = path_str->c_str() + 1; // skip the first slash |
| 236 | |
| 237 | cpio_inode_t *inode = cpio_inode_trycreate(path, sb: parent_dir->superblock); |
| 238 | if (!inode) |
| 239 | return false; // not found |
| 240 | |
| 241 | dentry_attach(d: dentry, inode: &inode->inode); |
| 242 | return true; |
| 243 | } |
| 244 | |
| 245 | static void cpio_i_iterate_dir(dentry_t *dentry, vfs_listdir_state_t *state, dentry_iterator_op add_record) |
| 246 | { |
| 247 | dentry_t *d_parent = dentry_parent(dentry: *dentry); |
| 248 | if (d_parent == NULL) |
| 249 | d_parent = root_dentry; |
| 250 | |
| 251 | MOS_ASSERT(d_parent->inode != NULL); |
| 252 | MOS_ASSERT(dentry->inode); |
| 253 | |
| 254 | add_record(state, dentry->inode->ino, "." , FILE_TYPE_DIRECTORY); |
| 255 | add_record(state, d_parent->inode->ino, ".." , FILE_TYPE_DIRECTORY); |
| 256 | |
| 257 | cpio_inode_t *inode = CPIO_INODE(inode: dentry->inode); |
| 258 | |
| 259 | char path_prefix[inode->name_length + 1]; // +1 for null terminator |
| 260 | initrd_read(buf: path_prefix, size: inode->name_length, offset: inode->name_offset); |
| 261 | path_prefix[inode->name_length] = '\0'; |
| 262 | size_t prefix_len = strlen(str: path_prefix); // +1 for the slash |
| 263 | |
| 264 | if (strcmp(str1: path_prefix, str2: "." ) == 0) |
| 265 | path_prefix[0] = '\0', prefix_len = 0; // root directory |
| 266 | |
| 267 | // find all children of this directory, that starts with 'path' and doesn't have any more slashes |
| 268 | cpio_newc_header_t ; |
| 269 | size_t offset = 0; |
| 270 | |
| 271 | while (true) |
| 272 | { |
| 273 | initrd_read(buf: &header, size: sizeof(cpio_newc_header_t), offset); |
| 274 | offset += sizeof(cpio_newc_header_t); |
| 275 | |
| 276 | if (strncmp(str1: header.magic, str2: "07070" , n: 5) != 0 || (header.magic[5] != '1' && header.magic[5] != '2')) |
| 277 | mos_panic("invalid cpio header magic, possibly corrupt archive" ); |
| 278 | |
| 279 | const size_t filename_len = strntoll(str: header.namesize, NULL, base: 16, n: sizeof(header.namesize) / sizeof(char)); |
| 280 | |
| 281 | char filename[filename_len + 1]; // +1 for null terminator |
| 282 | initrd_read(buf: filename, size: filename_len, offset); |
| 283 | filename[filename_len] = '\0'; |
| 284 | |
| 285 | const bool found = strncmp(str1: path_prefix, str2: filename, n: prefix_len) == 0 // make sure the path starts with the parent path |
| 286 | && (prefix_len == 0 || filename[prefix_len] == '/') // make sure it's a child, not a sibling (/path/to vs /path/toooo) |
| 287 | && strchr(s: filename + prefix_len + 1, c: '/') == NULL; // make sure it's only one level deep |
| 288 | |
| 289 | const bool is_TRAILER = strcmp(str1: filename, str2: "TRAILER!!!" ) == 0; |
| 290 | const bool is_root_dot = strcmp(str1: filename, str2: "." ) == 0; |
| 291 | |
| 292 | if (found && !is_TRAILER && !is_root_dot) |
| 293 | { |
| 294 | pr_dinfo2(cpio, "prefix '%s' filename '%s'" , path_prefix, filename); |
| 295 | |
| 296 | const u32 modebits = strntoll(str: header.mode, NULL, base: 16, n: sizeof(header.mode) / sizeof(char)); |
| 297 | const file_type_t type = cpio_modebits_to_filetype(modebits: modebits & CPIO_MODE_FILE_TYPE); |
| 298 | const s64 ino = strntoll(str: header.ino, NULL, base: 16, n: sizeof(header.ino) / sizeof(char)); |
| 299 | |
| 300 | const char *name = filename + prefix_len + (prefix_len == 0 ? 0 : 1); // +1 for the slash if it's not the root |
| 301 | const size_t name_len = filename_len - prefix_len - (prefix_len == 0 ? 0 : 1); // -1 for the slash if it's not the root |
| 302 | |
| 303 | add_record(state, ino, mos::string(name, name_len), type); |
| 304 | } |
| 305 | |
| 306 | if (unlikely(is_TRAILER)) |
| 307 | break; |
| 308 | |
| 309 | offset += filename_len; |
| 310 | offset = ALIGN_UP(offset, 4); // align to 4 bytes |
| 311 | |
| 312 | const size_t data_len = strntoll(str: header.filesize, NULL, base: 16, n: sizeof(header.filesize) / sizeof(char)); |
| 313 | offset += data_len; |
| 314 | offset = ALIGN_UP(offset, 4); // align to 4 bytes (again) |
| 315 | } |
| 316 | } |
| 317 | |
| 318 | static size_t cpio_i_readlink(dentry_t *dentry, char *buffer, size_t buflen) |
| 319 | { |
| 320 | cpio_inode_t *inode = CPIO_INODE(inode: dentry->inode); |
| 321 | return initrd_read(buf: buffer, size: std::min(a: buflen, b: inode->inode.size), offset: inode->data_offset); |
| 322 | } |
| 323 | |
| 324 | static bool cpio_sb_drop_inode(inode_t *inode) |
| 325 | { |
| 326 | delete CPIO_INODE(inode); |
| 327 | return true; |
| 328 | } |
| 329 | |
| 330 | const superblock_ops_t cpio_sb_ops = { |
| 331 | .drop_inode = cpio_sb_drop_inode, |
| 332 | }; |
| 333 | |
| 334 | const inode_ops_t cpio_dir_inode_ops = { |
| 335 | .iterate_dir = cpio_i_iterate_dir, |
| 336 | .lookup = cpio_i_lookup, |
| 337 | }; |
| 338 | |
| 339 | const inode_ops_t cpio_file_inode_ops = { |
| 340 | .readlink = cpio_i_readlink, |
| 341 | }; |
| 342 | |
| 343 | const file_ops_t cpio_file_ops = { |
| 344 | .read = vfs_generic_read, |
| 345 | }; |
| 346 | |
| 347 | PtrResult<phyframe_t> cpio_fill_cache(inode_cache_t *cache, uint64_t pgoff) |
| 348 | { |
| 349 | inode_t *i = cache->owner; |
| 350 | cpio_inode_t *cpio_i = CPIO_INODE(inode: i); |
| 351 | |
| 352 | phyframe_t *page = mm_get_free_page(); |
| 353 | if (!page) |
| 354 | return -ENOMEM; |
| 355 | |
| 356 | pmm_ref_one(page); |
| 357 | |
| 358 | if ((size_t) pgoff * MOS_PAGE_SIZE >= i->size) |
| 359 | return page; // EOF, no need to read anything |
| 360 | |
| 361 | const size_t bytes_to_read = std::min(a: (size_t) MOS_PAGE_SIZE, b: i->size - pgoff * MOS_PAGE_SIZE); |
| 362 | const size_t read = initrd_read(buf: (char *) phyframe_va(page), size: bytes_to_read, offset: cpio_i->data_offset + pgoff * MOS_PAGE_SIZE); |
| 363 | MOS_ASSERT(read == bytes_to_read); |
| 364 | return page; |
| 365 | } |
| 366 | |
| 367 | const inode_cache_ops_t cpio_icache_ops = { |
| 368 | .fill_cache = cpio_fill_cache, |
| 369 | }; |
| 370 | |
| 371 | static FILESYSTEM_DEFINE(fs_cpiofs, "cpiofs" , cpio_mount, NULL); |
| 372 | FILESYSTEM_AUTOREGISTER(fs_cpiofs); |
| 373 | |