1// SPDX-License-Identifier: GPL-3.0-or-later
2// userspace filesystems
3
4#include "mos/filesystem/userfs/userfs.hpp"
5
6#include "mos/filesystem/dentry.hpp"
7#include "mos/filesystem/vfs_types.hpp"
8#include "mos/filesystem/vfs_utils.hpp"
9#include "mos/misc/profiling.hpp"
10#include "mos/syslog/printk.hpp"
11#include "proto/filesystem.pb.h"
12#include "proto/filesystem.services.h"
13
14#include <algorithm>
15#include <librpc/macro_magic.h>
16#include <librpc/rpc.h>
17#include <librpc/rpc_client.h>
18#include <librpc/rpc_server.h>
19#include <mos/filesystem/fs_types.h>
20#include <mos_stdio.hpp>
21#include <mos_stdlib.hpp>
22#include <mos_string.hpp>
23#include <pb.h>
24#include <pb_decode.h>
25#include <pb_encode.h>
26
27MOS_RPC_USERFS_CLIENT(fs_client)
28
29extern const inode_ops_t userfs_iops;
30extern const file_ops_t userfs_fops;
31extern const inode_cache_ops_t userfs_inode_cache_ops;
32extern const superblock_ops_t userfs_sb_ops;
33
34#define userfs_get(_fs, _fmt, ...) \
35 statement_expr(userfs_t *, { \
36 retval = container_of(_fs, userfs_t, fs); \
37 userfs_ensure_connected(retval); \
38 pr_dinfo2(userfs, "calling '%s' (rpc_server '%s'): " _fmt, _fs->name.c_str(), retval->rpc_server_name.c_str() __VA_OPT__(, __VA_ARGS__)); \
39 })
40
41struct AutoCleanup
42{
43 const pb_msgdesc_t *const fields;
44 void *data;
45
46 void skip()
47 {
48 data = nullptr;
49 }
50
51 explicit AutoCleanup(const pb_msgdesc_t *fields, void *data) : fields(fields), data(data) {};
52 ~AutoCleanup()
53 {
54 if (fields && data)
55 pb_release(fields, dest_struct: data);
56 }
57};
58
59inode_t *i_from_pbfull(const mosrpc_fs_inode_info *stat, superblock_t *sb, void *private_data)
60{
61 // enum pb_file_type_t -> enum file_type_t is safe here because they have the same values
62 inode_t *i = inode_create(sb, ino: stat->ino, type: (file_type_t) stat->type);
63 i->created = stat->created;
64 i->modified = stat->modified;
65 i->accessed = stat->accessed;
66 i->size = stat->size;
67 i->uid = stat->uid;
68 i->gid = stat->gid;
69 i->perm = stat->perm;
70 i->nlinks = stat->nlinks;
71 i->suid = stat->suid;
72 i->sgid = stat->sgid;
73 i->sticky = stat->sticky;
74 i->private_data = private_data;
75 i->ops = &userfs_iops;
76 i->file_ops = &userfs_fops;
77 return i;
78}
79
80mosrpc_fs_inode_info *i_to_pb_full(const inode_t *i, mosrpc_fs_inode_info *pbi)
81{
82 pbi->ino = i->ino;
83 pbi->type = i->type;
84 pbi->created = i->created;
85 pbi->modified = i->modified;
86 pbi->accessed = i->accessed;
87 pbi->size = i->size;
88 pbi->uid = i->uid;
89 pbi->gid = i->gid;
90 pbi->perm = i->perm;
91 pbi->nlinks = i->nlinks;
92 pbi->suid = i->suid;
93 pbi->sgid = i->sgid;
94 pbi->sticky = i->sticky;
95 // pbi->private_data = (ptr_t) i->private;
96 return pbi;
97}
98
99mosrpc_fs_inode_ref i_to_pb_ref(const inode_t *i)
100{
101 mosrpc_fs_inode_ref ref = { .data = (ptr_t) i->private_data }; // for userfs, private_data is the inode reference used by the server
102 return ref;
103}
104
105void userfs_ensure_connected(userfs_t *userfs)
106{
107 if (likely(userfs->rpc_server))
108 return;
109
110 userfs->rpc_server = rpc_client_create(server_name: userfs->rpc_server_name.c_str());
111 if (unlikely(!userfs->rpc_server))
112 {
113 pr_warn("userfs_ensure_connected: failed to connect to %s", userfs->rpc_server_name.c_str());
114 return;
115 }
116}
117
118static bool userfs_iop_hardlink(dentry_t *d, inode_t *i, dentry_t *new_d)
119{
120 MOS_UNUSED(i);
121 MOS_UNUSED(new_d);
122
123 const auto name = dentry_name(dentry: d);
124 userfs_t *userfs = userfs_get(d->superblock->fs, "hardlink: %s", name.c_str());
125 MOS_UNUSED(userfs);
126
127 return false;
128}
129
130static void userfs_iop_iterate_dir(dentry_t *dentry, vfs_listdir_state_t *state, dentry_iterator_op add_record)
131{
132 const auto name = dentry_name(dentry);
133 userfs_t *userfs = userfs_get(dentry->superblock->fs, "iterate_dir: %s", name.c_str());
134
135 mosrpc_fs_readdir_request req = { .i_ref = i_to_pb_ref(i: dentry->inode) };
136 mosrpc_fs_readdir_response resp = {};
137
138 const pf_point_t ev = profile_enter();
139 const int result = fs_client_readdir(server_stub: userfs->rpc_server, request: &req, response: &resp);
140 profile_leave(ev, "userfs.'%s'.readdir", userfs->rpc_server_name);
141
142 AutoCleanup cleanup(mosrpc_fs_readdir_response_fields, &resp);
143
144 if (result != RPC_RESULT_OK)
145 {
146 pr_warn("userfs_iop_iterate_dir: failed to readdir %s: %d", name.c_str(), result);
147 return;
148 }
149
150 if (!resp.entries_count)
151 {
152 pr_dwarn(userfs, "userfs_iop_iterate_dir: failed to readdir %s: %s", name.c_str(), resp.result.error);
153 return;
154 }
155
156 for (size_t i = 0; i < resp.entries_count; i++)
157 {
158 const mosrpc_fs_pb_dirent *pbde = &resp.entries[i];
159 MOS_ASSERT(pbde->name);
160 add_record(state, pbde->ino, pbde->name, (file_type_t) pbde->type);
161 }
162}
163
164static bool userfs_iop_lookup(inode_t *dir, dentry_t *dentry)
165{
166 const auto name = dentry_name(dentry);
167 userfs_t *userfs = userfs_get(dir->superblock->fs, "lookup: %s", name.c_str());
168
169 mosrpc_fs_lookup_request req = {
170 .i_ref = i_to_pb_ref(i: dir),
171 .name = (char *) name.c_str(),
172 };
173
174 mosrpc_fs_lookup_response resp = {};
175 const pf_point_t ev = profile_enter();
176 const int result = fs_client_lookup(server_stub: userfs->rpc_server, request: &req, response: &resp);
177 profile_leave(ev, "userfs.'%s'.lookup", userfs->rpc_server_name);
178
179 AutoCleanup cleanup(mosrpc_fs_lookup_response_fields, &resp);
180
181 if (result != RPC_RESULT_OK)
182 {
183 pr_warn("userfs_iop_lookup: failed to lookup %s: %d", name.c_str(), result);
184 return false;
185 }
186
187 if (!resp.result.success)
188 {
189 return false; // ENOENT is not a big deal
190 }
191
192 inode_t *i = i_from_pbfull(stat: &resp.i_info, sb: dir->superblock, private_data: (void *) resp.i_ref.data);
193 dentry_attach(d: dentry, inode: i);
194 dentry->superblock = i->superblock = dir->superblock;
195 i->ops = &userfs_iops;
196 i->cache.ops = &userfs_inode_cache_ops;
197 i->file_ops = &userfs_fops;
198 return true;
199}
200
201static bool userfs_iop_mkdir(inode_t *dir, dentry_t *dentry, file_perm_t perm)
202{
203 const auto name = dentry_name(dentry);
204 userfs_t *userfs = userfs_get(dir->superblock->fs, "mkdir: %s", name.c_str());
205
206 mosrpc_fs_make_dir_request req = {
207 .i_ref = i_to_pb_ref(i: dir),
208 .name = (char *) name.c_str(),
209 .perm = perm,
210 };
211 mosrpc_fs_make_dir_response resp = {};
212
213 const pf_point_t pp = profile_enter();
214 const int result = fs_client_make_dir(server_stub: userfs->rpc_server, request: &req, response: &resp);
215 profile_leave(pp, "userfs.'%s'.make_dir", userfs->rpc_server_name);
216
217 AutoCleanup cleanup(mosrpc_fs_make_dir_response_fields, &resp);
218
219 if (result != RPC_RESULT_OK)
220 {
221 pr_warn("userfs_iop_mkdir: failed to mkdir %s: %d", name.c_str(), result);
222 return false;
223 }
224
225 if (!resp.result.success)
226 {
227 pr_dwarn(userfs, "userfs_iop_mkdir: failed to mkdir %s: %s", name.c_str(), resp.result.error);
228 return false;
229 }
230
231 inode_t *i = i_from_pbfull(stat: &resp.i_info, sb: dir->superblock, private_data: (void *) resp.i_ref.data);
232 dentry_attach(d: dentry, inode: i);
233 dentry->superblock = i->superblock = dir->superblock;
234 i->ops = &userfs_iops;
235 i->cache.ops = &userfs_inode_cache_ops;
236 i->file_ops = &userfs_fops;
237 return true;
238}
239
240static bool userfs_iop_mknode(inode_t *dir, dentry_t *dentry, file_type_t type, file_perm_t perm, dev_t dev)
241{
242 MOS_UNUSED(dir);
243 MOS_UNUSED(dentry);
244 MOS_UNUSED(type);
245 MOS_UNUSED(perm);
246 MOS_UNUSED(dev);
247
248 const auto name = dentry_name(dentry);
249 userfs_t *userfs = userfs_get(dir->superblock->fs, "mknode: %s", name.c_str());
250 MOS_UNUSED(userfs);
251
252 return false;
253}
254
255static bool userfs_iop_newfile(inode_t *dir, dentry_t *dentry, file_type_t type, file_perm_t perm)
256{
257 const auto name = dentry_name(dentry);
258 userfs_t *userfs = userfs_get(dir->superblock->fs, "newfile: %s", name.c_str());
259
260 const mosrpc_fs_create_file_request req{
261 .i_ref = i_to_pb_ref(i: dir),
262 .name = (char *) name.c_str(),
263 .type = type,
264 .perm = perm,
265 };
266 mosrpc_fs_create_file_response resp = {};
267
268 const pf_point_t pp = profile_enter();
269 const int result = fs_client_create_file(server_stub: userfs->rpc_server, request: &req, response: &resp);
270 profile_leave(pp, "userfs.'%s'.create_file", userfs->rpc_server_name);
271
272 AutoCleanup cleanup(mosrpc_fs_create_file_response_fields, &resp);
273
274 if (result != RPC_RESULT_OK)
275 {
276 pr_warn("userfs_iop_newfile: failed to create file %s: %d", name.c_str(), result);
277 return false;
278 }
279
280 if (!resp.result.success)
281 {
282 pr_dwarn(userfs, "userfs_iop_newfile: failed to create file %s: %s", name.c_str(), resp.result.error);
283 return false;
284 }
285
286 inode_t *i = i_from_pbfull(stat: &resp.i_info, sb: dir->superblock, private_data: (void *) resp.i_ref.data);
287 dentry_attach(d: dentry, inode: i);
288 dentry->superblock = i->superblock = dir->superblock;
289 i->ops = &userfs_iops;
290 i->cache.ops = &userfs_inode_cache_ops;
291 i->file_ops = &userfs_fops;
292 return true;
293}
294
295static size_t userfs_iop_readlink(dentry_t *dentry, char *buffer, size_t buflen)
296{
297 const auto name = dentry_name(dentry);
298 userfs_t *userfs = userfs_get(dentry->superblock->fs, "readlink: %s", name.c_str());
299
300 const mosrpc_fs_readlink_request req = {
301 .i_ref = i_to_pb_ref(i: dentry->inode),
302 };
303 mosrpc_fs_readlink_response resp = {};
304
305 const pf_point_t pp = profile_enter();
306 const int result = fs_client_readlink(server_stub: userfs->rpc_server, request: &req, response: &resp);
307 profile_leave(pp, "userfs.'%s'.readlink", userfs->rpc_server_name);
308
309 AutoCleanup cleanup(mosrpc_fs_readlink_response_fields, &resp);
310
311 if (result != RPC_RESULT_OK)
312 {
313 pr_warn("userfs_iop_readlink: failed to readlink %s: %d", name.c_str(), result);
314 return -EIO;
315 }
316
317 if (!resp.result.success)
318 {
319 pr_dwarn(userfs, "userfs_iop_readlink: failed to readlink %s: %s", name.c_str(), resp.result.error);
320 return -EIO;
321 }
322
323 size_t len = strlen(str: resp.target);
324 if (len > buflen)
325 len = buflen;
326
327 memcpy(dest: buffer, src: resp.target, n: len);
328 return len;
329}
330
331static bool userfs_iop_rename(inode_t *old_dir, dentry_t *old_dentry, inode_t *new_dir, dentry_t *new_dentry)
332{
333 MOS_UNUSED(old_dir);
334 MOS_UNUSED(old_dentry);
335 MOS_UNUSED(new_dir);
336 MOS_UNUSED(new_dentry);
337
338 const auto old_name = dentry_name(dentry: old_dentry);
339 const auto new_name = dentry_name(dentry: new_dentry);
340 userfs_t *userfs = userfs_get(old_dir->superblock->fs, "rename: %s -> %s", old_name.c_str(), new_name.c_str());
341 MOS_UNUSED(userfs);
342
343 return false;
344}
345
346static bool userfs_iop_rmdir(inode_t *dir, dentry_t *dentry)
347{
348 MOS_UNUSED(dir);
349 MOS_UNUSED(dentry);
350
351 const auto name = dentry_name(dentry);
352 userfs_t *userfs = userfs_get(dir->superblock->fs, "rmdir: %s", name.c_str());
353 MOS_UNUSED(userfs);
354
355 return false;
356}
357
358static bool userfs_iop_symlink(inode_t *dir, dentry_t *dentry, const char *symname)
359{
360 MOS_UNUSED(dir);
361 MOS_UNUSED(dentry);
362 MOS_UNUSED(symname);
363
364 const auto name = dentry_name(dentry);
365 userfs_t *userfs = userfs_get(dir->superblock->fs, "symlink: %s", name.c_str());
366 MOS_UNUSED(userfs);
367
368 return false;
369}
370
371static bool userfs_iop_unlink(inode_t *dir, dentry_t *dentry)
372{
373 const auto name = dentry_name(dentry);
374 userfs_t *userfs = userfs_get(dir->superblock->fs, "unlink: %s", name.c_str());
375
376 const mosrpc_fs_unlink_request req = {
377 .i_ref = i_to_pb_ref(i: dir),
378 .dentry = { .inode_id = dentry->inode->ino, .name = (char *) name.c_str() },
379 };
380 mosrpc_fs_unlink_response resp = {};
381
382 const pf_point_t pp = profile_enter();
383 const int result = fs_client_unlink(server_stub: userfs->rpc_server, request: &req, response: &resp);
384 profile_leave(pp, "userfs.'%s'.unlink", userfs->rpc_server_name);
385
386 AutoCleanup cleanup(mosrpc_fs_unlink_response_fields, &resp);
387
388 if (result != RPC_RESULT_OK)
389 {
390 pr_warn("userfs_iop_unlink: failed to unlink %s: %d", name.c_str(), result);
391 return false;
392 }
393
394 if (!resp.result.success)
395 {
396 pr_dwarn(userfs, "userfs_iop_unlink: failed to unlink %s: %s", name.c_str(), resp.result.error);
397 return false;
398 }
399
400 return true;
401}
402
403const inode_ops_t userfs_iops = {
404 .hardlink = userfs_iop_hardlink,
405 .iterate_dir = userfs_iop_iterate_dir,
406 .lookup = userfs_iop_lookup,
407 .mkdir = userfs_iop_mkdir,
408 .mknode = userfs_iop_mknode,
409 .newfile = userfs_iop_newfile,
410 .readlink = userfs_iop_readlink,
411 .rename = userfs_iop_rename,
412 .rmdir = userfs_iop_rmdir,
413 .symlink = userfs_iop_symlink,
414 .unlink = userfs_iop_unlink,
415};
416
417static bool userfs_fop_open(inode_t *inode, file_t *file, bool created)
418{
419 MOS_UNUSED(inode);
420 MOS_UNUSED(file);
421 MOS_UNUSED(created);
422
423 return true;
424}
425
426const file_ops_t userfs_fops = {
427 .open = userfs_fop_open,
428 .read = vfs_generic_read,
429 .write = vfs_generic_write,
430 .release = NULL,
431 .seek = NULL,
432 .mmap = NULL,
433 .munmap = NULL,
434};
435
436static PtrResult<phyframe_t> userfs_inode_cache_fill_cache(inode_cache_t *cache, uint64_t pgoff)
437{
438 userfs_t *userfs = userfs_get(cache->owner->superblock->fs, "fill_cache", );
439
440 const mosrpc_fs_getpage_request req = {
441 .i_ref = i_to_pb_ref(i: cache->owner),
442 .pgoff = pgoff,
443 };
444
445 mosrpc_fs_getpage_response resp = {};
446
447 const pf_point_t pp = profile_enter();
448 const int result = fs_client_get_page(server_stub: userfs->rpc_server, request: &req, response: &resp);
449 profile_leave(pp, "userfs.'%s'.getpage", userfs->rpc_server_name);
450
451 AutoCleanup cleanup(mosrpc_fs_getpage_response_fields, &resp);
452
453 if (result != RPC_RESULT_OK)
454 {
455 pr_warn("userfs_inode_cache_fill_cache: failed to getpage: %d", result);
456 return -EIO;
457 }
458
459 if (!resp.result.success)
460 {
461 pr_dwarn(userfs, "userfs_inode_cache_fill_cache: failed to getpage: %s", resp.result.error);
462 return -EIO;
463 }
464
465 // allocate a page
466 phyframe_t *page = pmm_ref_one(mm_get_free_page());
467 if (!page)
468 {
469 pr_warn("userfs_inode_cache_fill_cache: failed to allocate page");
470 return -ENOMEM;
471 }
472
473 // copy the data from the server
474 memcpy(dest: (void *) phyframe_va(page), src: resp.data->bytes, n: std::min(a: resp.data->size, b: (pb_size_t) MOS_PAGE_SIZE));
475 return page;
476}
477
478long userfs_inode_cache_flush_page(inode_cache_t *cache, uint64_t pgoff, phyframe_t *page)
479{
480 userfs_t *userfs = userfs_get(cache->owner->superblock->fs, "flush_page", );
481
482 mosrpc_fs_putpage_request req = {
483 .i_ref = i_to_pb_ref(i: cache->owner),
484 .pgoff = pgoff,
485 .data = nullptr,
486 };
487 req.data = (pb_bytes_array_t *) kcalloc<char>(PB_BYTES_ARRAY_T_ALLOCSIZE(MOS_PAGE_SIZE));
488 req.data->size = MOS_PAGE_SIZE;
489 memcpy(dest: req.data->bytes, src: (void *) phyframe_va(page), MOS_PAGE_SIZE);
490
491 mosrpc_fs_putpage_response resp = {};
492
493 const pf_point_t pp = profile_enter();
494 const int result = fs_client_put_page(server_stub: userfs->rpc_server, request: &req, response: &resp);
495 profile_leave(pp, "userfs.'%s'.putpage", userfs->rpc_server_name);
496
497 AutoCleanup cleanup(mosrpc_fs_putpage_request_fields, &req);
498 AutoCleanup cleanup2(mosrpc_fs_putpage_response_fields, &resp);
499
500 if (result != RPC_RESULT_OK)
501 {
502 pr_warn("userfs_inode_cache_flush_page: failed to putpage: %d", result);
503 return -EIO;
504 }
505
506 if (!resp.result.success)
507 {
508 pr_dwarn(userfs, "userfs_inode_cache_flush_page: failed to putpage: %s", resp.result.error);
509 return -EIO;
510 }
511
512 return 0;
513}
514
515const inode_cache_ops_t userfs_inode_cache_ops = {
516 .fill_cache = userfs_inode_cache_fill_cache,
517 .page_write_begin = simple_page_write_begin,
518 .page_write_end = simple_page_write_end,
519 .flush_page = userfs_inode_cache_flush_page,
520};
521
522long userfs_sync_inode(inode_t *inode)
523{
524 userfs_t *userfs = userfs_get(inode->superblock->fs, "sync_inode: %llu", inode->ino);
525
526 mosrpc_fs_sync_inode_request req = {};
527 req.i_ref = i_to_pb_ref(i: inode);
528 req.i_info = *i_to_pb_full(i: inode, pbi: &req.i_info);
529
530 mosrpc_fs_sync_inode_response resp = {};
531 const pf_point_t pp = profile_enter();
532 const int result = fs_client_sync_inode(server_stub: userfs->rpc_server, request: &req, response: &resp);
533 profile_leave(pp, "userfs.'%s'.sync_inode", userfs->rpc_server_name);
534
535 if (result != RPC_RESULT_OK)
536 {
537 pr_warn("userfs_sync_inode: failed to sync inode %llu: %d", inode->ino, result);
538 return -EIO;
539 }
540
541 if (!resp.result.success)
542 {
543 pr_dwarn(userfs, "userfs_sync_inode: failed to sync inode %llu: %s", inode->ino, resp.result.error);
544 return -EIO;
545 }
546
547 return 0;
548}
549
550const superblock_ops_t userfs_sb_ops = {
551 .drop_inode = NULL,
552 .sync_inode = userfs_sync_inode,
553};
554
555PtrResult<dentry_t> userfs_fsop_mount(filesystem_t *fs, const char *device, const char *options)
556{
557 userfs_t *userfs = userfs_get(fs, "mount: %s", fs->name.c_str());
558
559 const mosrpc_fs_mount_request req = {
560 .fs_name = (char *) fs->name.c_str(),
561 .device = (char *) device,
562 .options = (char *) options,
563 };
564
565 mosrpc_fs_mount_response resp = {};
566
567 const pf_point_t pp = profile_enter();
568 const int result = fs_client_mount(server_stub: userfs->rpc_server, request: &req, response: &resp);
569 profile_leave(pp, "userfs.'%s'.mount", userfs->rpc_server_name);
570
571 AutoCleanup cleanup(mosrpc_fs_mount_response_fields, &resp);
572
573 if (result != RPC_RESULT_OK)
574 {
575 pr_warn("userfs_fsop_mount: failed to mount %s: %d", fs->name.c_str(), result);
576 return -EIO;
577 }
578
579 if (!resp.result.success)
580 {
581 pr_warn("userfs_fsop_mount: failed to mount %s: %s", fs->name.c_str(), resp.result.error);
582 return -EIO;
583 }
584
585 superblock_t *sb = mos::create<superblock_t>();
586 sb->ops = &userfs_sb_ops;
587
588 inode_t *i = i_from_pbfull(stat: &resp.root_info, sb, private_data: (void *) resp.root_ref.data);
589
590 sb->fs = fs;
591 sb->root = dentry_get_from_parent(sb, NULL);
592 sb->root->superblock = i->superblock = sb;
593 dentry_attach(d: sb->root, inode: i);
594 return sb->root;
595}
596