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