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
30MOS_RPC_USERFS_MANAGER_CLIENT(fs_manager)
31MOS_RPC_USERFS_SERVER(cpiofs)
32
33static rpc_server_t *cpiofs = NULL;
34static rpc_server_stub_t *fs_manager = NULL;
35
36typedef struct
37{
38 mosrpc_fs_inode_info pb_i;
39 size_t header_offset;
40 size_t name_offset, name_length;
41 size_t data_offset;
42 cpio_header_t header;
43} cpio_inode_t;
44
45static 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
63static cpio_inode_t *cpio_trycreate_i(const char *path)
64{
65 cpio_header_t header;
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
99static 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
122static 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 header;
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
203static 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
243static 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
255static 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
287static 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
294static 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
301static 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
308static 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
315static 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
322void 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
363cleanup:
364 if (cpiofs)
365 rpc_server_destroy(server: cpiofs);
366 if (fs_manager)
367 rpc_client_destroy(server: fs_manager);
368
369 return;
370
371bad:
372 if (write(fd: notifier, buffer: "x", size: 1) != 1)
373 puts(string: "cpiofs: failed to notify init");
374
375 goto cleanup;
376}
377