| 1 | // SPDX-License-Identifier: GPL-3.0-or-later |
| 2 | |
| 3 | #include "bootstrapper.h" |
| 4 | #include "cpiofs.h" |
| 5 | #include "proto/filesystem.pb.h" |
| 6 | #include "proto/filesystem.service.h" |
| 7 | #include "proto/userfs-manager.pb.h" |
| 8 | #include "proto/userfs-manager.service.h" |
| 9 | |
| 10 | #include <bits/posix/pthread_t.h> |
| 11 | #include <librpc/macro_magic.h> |
| 12 | #include <librpc/rpc.h> |
| 13 | #include <librpc/rpc_client.h> |
| 14 | #include <librpc/rpc_server.h> |
| 15 | #include <mos/filesystem/fs_types.h> |
| 16 | #include <mos/mos_global.h> |
| 17 | #include <mos/proto/fs_server.h> |
| 18 | #include <mos/syscall/usermode.h> |
| 19 | #include <pb.h> |
| 20 | #include <pb_decode.h> |
| 21 | #include <pb_encode.h> |
| 22 | #include <pthread.h> |
| 23 | #include <stdio.h> |
| 24 | #include <sys/param.h> |
| 25 | #include <unistd.h> |
| 26 | |
| 27 | #define CPIOFS_NAME "cpiofs" |
| 28 | #define CPIOFS_RPC_SERVER_NAME "fs.cpiofs" |
| 29 | |
| 30 | MOS_RPC_USERFS_MANAGER_CLIENT(fs_manager) |
| 31 | MOS_RPC_USERFS_SERVER(cpiofs) |
| 32 | |
| 33 | static rpc_server_t *cpiofs = NULL; |
| 34 | static rpc_server_stub_t *fs_manager = NULL; |
| 35 | |
| 36 | typedef struct |
| 37 | { |
| 38 | mosrpc_fs_inode_info pb_i; |
| 39 | size_t ; |
| 40 | size_t name_offset, name_length; |
| 41 | size_t data_offset; |
| 42 | cpio_header_t ; |
| 43 | } cpio_inode_t; |
| 44 | |
| 45 | static file_type_t cpio_modebits_to_filetype(u32 modebits) |
| 46 | { |
| 47 | file_type_t type = FILE_TYPE_UNKNOWN; |
| 48 | switch (modebits & CPIO_MODE_FILE_TYPE) |
| 49 | { |
| 50 | case CPIO_MODE_FILE: type = FILE_TYPE_REGULAR; break; |
| 51 | case CPIO_MODE_DIR: type = FILE_TYPE_DIRECTORY; break; |
| 52 | case CPIO_MODE_SYMLINK: type = FILE_TYPE_SYMLINK; break; |
| 53 | case CPIO_MODE_CHARDEV: type = FILE_TYPE_CHAR_DEVICE; break; |
| 54 | case CPIO_MODE_BLOCKDEV: type = FILE_TYPE_BLOCK_DEVICE; break; |
| 55 | case CPIO_MODE_FIFO: type = FILE_TYPE_NAMED_PIPE; break; |
| 56 | case CPIO_MODE_SOCKET: type = FILE_TYPE_SOCKET; break; |
| 57 | default: puts(string: "invalid cpio file mode" ); break; |
| 58 | } |
| 59 | |
| 60 | return type; |
| 61 | } |
| 62 | |
| 63 | static cpio_inode_t *cpio_trycreate_i(const char *path) |
| 64 | { |
| 65 | cpio_header_t ; |
| 66 | cpio_metadata_t metadata; |
| 67 | const bool found = cpio_read_metadata(target: path, header: &header, metadata: &metadata); |
| 68 | if (!found) |
| 69 | return NULL; |
| 70 | |
| 71 | cpio_inode_t *cpio_inode = malloc(size: sizeof(cpio_inode_t)); |
| 72 | cpio_inode->header = header; |
| 73 | cpio_inode->header_offset = metadata.header_offset; |
| 74 | cpio_inode->name_offset = metadata.name_offset; |
| 75 | cpio_inode->name_length = metadata.name_length; |
| 76 | cpio_inode->data_offset = metadata.data_offset; |
| 77 | |
| 78 | const u32 modebits = strntoll(str: cpio_inode->header.mode, NULL, base: 16, n: sizeof(cpio_inode->header.mode) / sizeof(char)); |
| 79 | const u64 ino = strntoll(str: cpio_inode->header.ino, NULL, base: 16, n: sizeof(cpio_inode->header.ino) / sizeof(char)); |
| 80 | const file_type_t file_type = cpio_modebits_to_filetype(modebits: modebits & CPIO_MODE_FILE_TYPE); |
| 81 | |
| 82 | mosrpc_fs_inode_info *const i = &cpio_inode->pb_i; |
| 83 | |
| 84 | i->type = file_type; |
| 85 | i->ino = ino; |
| 86 | |
| 87 | // 0000777 - The lower 9 bits specify read/write/execute permissions for world, group, and user following standard POSIX conventions. |
| 88 | i->perm = modebits & 0777; |
| 89 | i->size = metadata.data_length; |
| 90 | i->uid = strntoll(str: cpio_inode->header.uid, NULL, base: 16, n: sizeof(cpio_inode->header.uid) / sizeof(char)); |
| 91 | i->gid = strntoll(str: cpio_inode->header.gid, NULL, base: 16, n: sizeof(cpio_inode->header.gid) / sizeof(char)); |
| 92 | i->sticky = modebits & CPIO_MODE_STICKY; |
| 93 | i->suid = modebits & CPIO_MODE_SUID; |
| 94 | i->sgid = modebits & CPIO_MODE_SGID; |
| 95 | i->nlinks = strntoll(str: cpio_inode->header.nlink, NULL, base: 16, n: sizeof(cpio_inode->header.nlink) / sizeof(char)); |
| 96 | return cpio_inode; |
| 97 | } |
| 98 | |
| 99 | static rpc_result_code_t cpiofs_mount(rpc_context_t *, mosrpc_fs_mount_request *req, mosrpc_fs_mount_response *resp) |
| 100 | { |
| 101 | if (req->options && strlen(s: req->options) > 0 && strcmp(a: req->options, b: "defaults" ) != 0) |
| 102 | printf(format: "cpio: mount option '%s' is not supported\n" , req->options); |
| 103 | |
| 104 | if (req->device && strlen(s: req->device) > 0 && strcmp(a: req->device, b: "none" ) != 0) |
| 105 | printf(format: "cpio: mount: device name '%s' is not supported\n" , req->device); |
| 106 | |
| 107 | cpio_inode_t *cpio_i = cpio_trycreate_i(path: "." ); |
| 108 | if (!cpio_i) |
| 109 | { |
| 110 | puts(string: "cpio: failed to mount" ); |
| 111 | resp->result.error = strdup(string: "unable to find root inode" ); |
| 112 | resp->result.success = false; |
| 113 | return RPC_RESULT_OK; |
| 114 | } |
| 115 | |
| 116 | resp->result.success = true; |
| 117 | resp->root_info = cpio_i->pb_i; |
| 118 | resp->root_ref.data = (ptr_t) cpio_i; |
| 119 | return RPC_RESULT_OK; |
| 120 | } |
| 121 | |
| 122 | static rpc_result_code_t cpiofs_readdir(rpc_context_t *, mosrpc_fs_readdir_request *req, mosrpc_fs_readdir_response *resp) |
| 123 | { |
| 124 | cpio_inode_t *inode = (cpio_inode_t *) req->i_ref.data; |
| 125 | |
| 126 | char path_prefix[inode->name_length + 1]; // +1 for null terminator |
| 127 | read_initrd(buf: path_prefix, size: inode->name_length, offset: inode->name_offset); |
| 128 | path_prefix[inode->name_length] = '\0'; |
| 129 | |
| 130 | const size_t prefix_len = statement_expr(size_t, { |
| 131 | retval = strlen(path_prefix); |
| 132 | if (strcmp(path_prefix, "." ) == 0) |
| 133 | path_prefix[0] = '\0', retval = 0; // root directory |
| 134 | }); |
| 135 | |
| 136 | // find all children of this directory, that starts with 'path' and doesn't have any more slashes |
| 137 | |
| 138 | size_t n_written = 0; |
| 139 | resp->entries_count = 1; |
| 140 | resp->entries = malloc(size: sizeof(mosrpc_fs_pb_dirent) * resp->entries_count); |
| 141 | |
| 142 | size_t offset = 0; |
| 143 | |
| 144 | while (true) |
| 145 | { |
| 146 | cpio_header_t ; |
| 147 | read_initrd(buf: &header, size: sizeof(cpio_header_t), offset); |
| 148 | offset += sizeof(cpio_header_t); |
| 149 | |
| 150 | if (strncmp(a: header.magic, b: "07070" , max_size: 5) != 0 || (header.magic[5] != '1' && header.magic[5] != '2')) |
| 151 | puts(string: "invalid cpio header magic, possibly corrupt archive" ); |
| 152 | |
| 153 | const size_t fpath_len = strntoll(str: header.namesize, NULL, base: 16, n: sizeof(header.namesize) / sizeof(char)); |
| 154 | |
| 155 | char fpath[fpath_len + 1]; // +1 for null terminator |
| 156 | read_initrd(buf: fpath, size: fpath_len, offset); |
| 157 | fpath[fpath_len] = '\0'; |
| 158 | |
| 159 | const bool found = strncmp(a: path_prefix, b: fpath, max_size: prefix_len) == 0 && // make sure the path starts with the parent path |
| 160 | (prefix_len == 0 || fpath[prefix_len] == '/') && // make sure it's a child, not a sibling (/path/to vs /path/toooo) |
| 161 | strchr(s: fpath + prefix_len + 1, c: '/') == NULL; // make sure it's a direct child, not a grandchild (/path/to vs /path/to/ooo) |
| 162 | |
| 163 | const bool is_TRAILER = strcmp(a: fpath, b: "TRAILER!!!" ) == 0; |
| 164 | const bool is_root_dot = strcmp(a: fpath, b: "." ) == 0; |
| 165 | |
| 166 | if (found && !is_TRAILER && !is_root_dot) |
| 167 | { |
| 168 | const s64 ino = strntoll(str: header.ino, NULL, base: 16, n: sizeof(header.ino) / sizeof(char)); |
| 169 | const u32 modebits = strntoll(str: header.mode, NULL, base: 16, n: sizeof(header.mode) / sizeof(char)); |
| 170 | const file_type_t type = cpio_modebits_to_filetype(modebits: modebits & CPIO_MODE_FILE_TYPE); |
| 171 | |
| 172 | const char *fname = fpath + prefix_len + (prefix_len != 0); // +1 for the slash if it's not the root |
| 173 | const size_t fname_len = fpath_len - prefix_len - (prefix_len != 0); // -1 for the slash if it's not the root |
| 174 | |
| 175 | // write ino, name, name_len, type |
| 176 | if (n_written >= resp->entries_count) |
| 177 | { |
| 178 | resp->entries_count *= 2; |
| 179 | resp->entries = realloc(pointer: resp->entries, size: sizeof(mosrpc_fs_pb_dirent) * resp->entries_count); |
| 180 | } |
| 181 | |
| 182 | mosrpc_fs_pb_dirent *const de = &resp->entries[n_written++]; |
| 183 | de->ino = ino; |
| 184 | de->name = strndup(string: fname, max_size: fname_len); |
| 185 | de->type = type; |
| 186 | } |
| 187 | |
| 188 | if (unlikely(is_TRAILER)) |
| 189 | break; // coming to the end of the archive |
| 190 | |
| 191 | offset += fpath_len; |
| 192 | offset = ALIGN_UP(offset, 4); |
| 193 | |
| 194 | const size_t data_len = strntoll(str: header.filesize, NULL, base: 16, n: sizeof(header.filesize) / sizeof(char)); |
| 195 | offset += data_len; |
| 196 | offset = ALIGN_UP(offset, 4); |
| 197 | } |
| 198 | |
| 199 | resp->entries_count = n_written; // not the same as resp->entries_count, because we might have allocated more than we needed |
| 200 | return RPC_RESULT_OK; |
| 201 | } |
| 202 | |
| 203 | static rpc_result_code_t cpiofs_lookup(rpc_context_t *, mosrpc_fs_lookup_request *req, mosrpc_fs_lookup_response *resp) |
| 204 | { |
| 205 | char pathbuf[PATH_MAX] = { 0 }; |
| 206 | cpio_inode_t *parent_diri = (cpio_inode_t *) req->i_ref.data; |
| 207 | read_initrd(buf: pathbuf, size: parent_diri->name_length, offset: parent_diri->name_offset); |
| 208 | |
| 209 | // append the filename (req->name) |
| 210 | const size_t pathbuf_len = strlen(s: pathbuf); |
| 211 | const size_t name_len = strlen(s: req->name); |
| 212 | |
| 213 | if (unlikely(pathbuf_len + name_len + 1 >= MOS_PATH_MAX_LENGTH)) |
| 214 | { |
| 215 | puts(string: "cpiofs_lookup: path too long" ); |
| 216 | return false; |
| 217 | } |
| 218 | |
| 219 | pathbuf[pathbuf_len] = '/'; |
| 220 | memcpy(dest: pathbuf + pathbuf_len + 1, src: req->name, size: name_len); |
| 221 | pathbuf[pathbuf_len + 1 + name_len] = '\0'; |
| 222 | |
| 223 | const char *path = statement_expr(char *, { |
| 224 | retval = pathbuf; |
| 225 | if (pathbuf[0] == '.' && pathbuf[1] == '/') |
| 226 | retval += 2; // skip the leading ./ (relative path) |
| 227 | }); |
| 228 | |
| 229 | cpio_inode_t *const cpio_i = cpio_trycreate_i(path); |
| 230 | if (!cpio_i) |
| 231 | { |
| 232 | resp->result.success = false; |
| 233 | resp->result.error = strdup(string: "unable to find inode" ); |
| 234 | return RPC_RESULT_OK; |
| 235 | } |
| 236 | |
| 237 | resp->result.success = true; |
| 238 | resp->i_info = cpio_i->pb_i; |
| 239 | resp->i_ref.data = (ptr_t) cpio_i; |
| 240 | return RPC_RESULT_OK; |
| 241 | } |
| 242 | |
| 243 | static rpc_result_code_t cpiofs_readlink(rpc_context_t *, mosrpc_fs_readlink_request *req, mosrpc_fs_readlink_response *resp) |
| 244 | { |
| 245 | cpio_inode_t *cpio_i = (cpio_inode_t *) req->i_ref.data; |
| 246 | char path[cpio_i->data_offset + cpio_i->pb_i.size + 1]; |
| 247 | read_initrd(buf: path, size: cpio_i->pb_i.size, offset: cpio_i->data_offset); |
| 248 | path[cpio_i->pb_i.size] = '\0'; |
| 249 | |
| 250 | resp->result.success = true; |
| 251 | resp->target = strdup(string: path); |
| 252 | return RPC_RESULT_OK; |
| 253 | } |
| 254 | |
| 255 | static rpc_result_code_t cpiofs_get_page(rpc_context_t *, mosrpc_fs_getpage_request *req, mosrpc_fs_getpage_response *resp) |
| 256 | { |
| 257 | cpio_inode_t *cpio_i = (cpio_inode_t *) req->i_ref.data; |
| 258 | |
| 259 | if (req->pgoff * MOS_PAGE_SIZE >= cpio_i->pb_i.size) |
| 260 | { |
| 261 | resp->data = malloc(size: sizeof(pb_bytes_array_t)); |
| 262 | resp->data->size = 0; |
| 263 | resp->result.success = true; |
| 264 | return RPC_RESULT_OK; |
| 265 | } |
| 266 | |
| 267 | const size_t bytes_to_read = MIN((size_t) MOS_PAGE_SIZE, cpio_i->pb_i.size - req->pgoff * MOS_PAGE_SIZE); |
| 268 | |
| 269 | resp->data = malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(bytes_to_read)); |
| 270 | resp->data->size = bytes_to_read; |
| 271 | |
| 272 | const size_t read = read_initrd(buf: resp->data->bytes, size: bytes_to_read, offset: cpio_i->data_offset + req->pgoff * MOS_PAGE_SIZE); |
| 273 | if (read != bytes_to_read) |
| 274 | { |
| 275 | puts(string: "cpiofs_getpage: failed to read page" ); |
| 276 | resp->result.success = false; |
| 277 | resp->result.error = strdup(string: "failed to read page" ); |
| 278 | |
| 279 | free(pointer: resp->data); |
| 280 | return RPC_RESULT_OK; |
| 281 | } |
| 282 | |
| 283 | resp->result.success = true; |
| 284 | return RPC_RESULT_OK; |
| 285 | } |
| 286 | |
| 287 | static rpc_result_code_t cpiofs_create_file(rpc_context_t *, mosrpc_fs_create_file_request *, mosrpc_fs_create_file_response *resp) |
| 288 | { |
| 289 | resp->result.success = false; |
| 290 | resp->result.error = strdup(string: "cpiofs: cannot create files" ); |
| 291 | return RPC_RESULT_OK; |
| 292 | } |
| 293 | |
| 294 | static rpc_result_code_t cpiofs_put_page(rpc_context_t *, mosrpc_fs_putpage_request *, mosrpc_fs_putpage_response *resp) |
| 295 | { |
| 296 | resp->result.success = false; |
| 297 | resp->result.error = strdup(string: "cpiofs: cannot write to cpiofs" ); |
| 298 | return RPC_RESULT_OK; |
| 299 | } |
| 300 | |
| 301 | static rpc_result_code_t cpiofs_sync_inode(rpc_context_t *, mosrpc_fs_sync_inode_request *, mosrpc_fs_sync_inode_response *resp) |
| 302 | { |
| 303 | resp->result.success = false; |
| 304 | resp->result.error = strdup(string: "cpiofs: cannot sync cpiofs" ); |
| 305 | return RPC_RESULT_OK; |
| 306 | } |
| 307 | |
| 308 | static rpc_result_code_t cpiofs_unlink(rpc_context_t *, mosrpc_fs_unlink_request *, mosrpc_fs_unlink_response *resp) |
| 309 | { |
| 310 | resp->result.success = false; |
| 311 | resp->result.error = strdup(string: "cpiofs: cannot unlink from cpiofs" ); |
| 312 | return RPC_RESULT_OK; |
| 313 | } |
| 314 | |
| 315 | static rpc_result_code_t cpiofs_make_dir(rpc_context_t *, mosrpc_fs_make_dir_request *, mosrpc_fs_make_dir_response *resp) |
| 316 | { |
| 317 | resp->result.success = false; |
| 318 | resp->result.error = strdup(string: "cpiofs: cannot create directories" ); |
| 319 | return RPC_RESULT_OK; |
| 320 | } |
| 321 | |
| 322 | void init_start_cpiofs_server(fd_t notifier) |
| 323 | { |
| 324 | cpiofs = rpc_server_create(CPIOFS_RPC_SERVER_NAME, NULL); |
| 325 | if (!cpiofs) |
| 326 | { |
| 327 | puts(string: "cpiofs: failed to create cpiofs server" ); |
| 328 | goto bad; |
| 329 | } |
| 330 | |
| 331 | rpc_server_register_functions(server: cpiofs, functions: cpiofs_functions, MOS_ARRAY_SIZE(cpiofs_functions)); |
| 332 | |
| 333 | fs_manager = rpc_client_create(USERFS_SERVER_RPC_NAME); |
| 334 | if (!fs_manager) |
| 335 | { |
| 336 | puts(string: "cpiofs: failed to connect to the userfs manager" ); |
| 337 | goto bad; |
| 338 | } |
| 339 | |
| 340 | mosrpc_userfs_register_request req = mosrpc_userfs_register_request_init_zero; |
| 341 | req.fs.name = CPIOFS_NAME; |
| 342 | req.rpc_server_name = CPIOFS_RPC_SERVER_NAME; |
| 343 | |
| 344 | mosrpc_userfs_register_response resp = mosrpc_userfs_register_response_init_zero; |
| 345 | const rpc_result_code_t result = fs_manager_register_filesystem(server_stub: fs_manager, request: &req, response: &resp); |
| 346 | if (result != RPC_RESULT_OK || !resp.result.success) |
| 347 | { |
| 348 | puts(string: "cpiofs: failed to register cpiofs with filesystem server" ); |
| 349 | goto bad; |
| 350 | } |
| 351 | |
| 352 | pb_release(mosrpc_userfs_register_response_fields, dest_struct: &resp); |
| 353 | |
| 354 | if (write(fd: notifier, buffer: "v" , size: 1) != 1) |
| 355 | { |
| 356 | puts(string: "cpiofs: failed to notify init" ); |
| 357 | goto cleanup; |
| 358 | } |
| 359 | |
| 360 | rpc_server_exec(server: cpiofs); |
| 361 | rpc_server_destroy(server: cpiofs); |
| 362 | |
| 363 | cleanup: |
| 364 | if (cpiofs) |
| 365 | rpc_server_destroy(server: cpiofs); |
| 366 | if (fs_manager) |
| 367 | rpc_client_destroy(server: fs_manager); |
| 368 | |
| 369 | return; |
| 370 | |
| 371 | bad: |
| 372 | if (write(fd: notifier, buffer: "x" , size: 1) != 1) |
| 373 | puts(string: "cpiofs: failed to notify init" ); |
| 374 | |
| 375 | goto cleanup; |
| 376 | } |
| 377 | |