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.services.h" |
7 | #include "proto/userfs-manager.pb.h" |
8 | #include "proto/userfs-manager.services.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(fname, 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 | |