1// SPDX-License-Identifier: GPL-3.0-or-later
2
3#include "blockdevfs.hpp"
4
5#include "blockdev_manager.hpp"
6#include "proto/filesystem.pb.h"
7#include "proto/userfs-manager.pb.h"
8
9#include <assert.h>
10#include <chrono>
11#include <iostream>
12#include <librpc/macro_magic.h>
13#include <librpc/rpc.h>
14#include <librpc/rpc_client.h>
15#include <librpc/rpc_server.h>
16#include <memory>
17#include <mos/filesystem/fs_types.h>
18#include <mos/proto/fs_server.h>
19#include <mos/syscall/usermode.h>
20#include <ostream>
21#include <pb_decode.h>
22#include <sys/stat.h>
23
24using namespace std::chrono;
25
26#define BLOCKDEVFS_NAME "blockdevfs"
27#define BLOCKDEVFS_RPC_SERVER_NAME "fs.blockdevfs"
28
29static std::unique_ptr<IUserFSService> blockdevfs;
30
31struct blockdevfs_inode
32{
33 std::string blockdev_name;
34};
35
36static blockdevfs_inode *root = NULL;
37
38rpc_result_code_t BlockdevFSServer::mount(rpc_context_t *, mosrpc_fs_mount_request *req, mosrpc_fs_mount_response *resp)
39{
40 if (req->options && strlen(s: req->options) > 0 && strcmp(a: req->options, b: "defaults") != 0)
41 printf(format: "blockdevfs: mount option '%s' is not supported\n", req->options);
42
43 if (req->device && strlen(s: req->device) > 0 && strcmp(a: req->device, b: "none") != 0)
44 printf(format: "blockdevfs: mount: device name '%s' is not supported\n", req->device);
45
46 if (root)
47 {
48 resp->result.success = false;
49 resp->result.error = strdup(string: "blockdevfs: already mounted");
50 return RPC_RESULT_OK;
51 }
52
53 root = new blockdevfs_inode();
54
55 mosrpc_fs_inode_info *const i = &resp->root_info;
56 i->ino = 1;
57 i->type = FILE_TYPE_DIRECTORY;
58 i->perm = 0755;
59 i->uid = i->gid = 0;
60 i->size = 0;
61 i->accessed = i->modified = i->created = duration_cast<seconds>(d: system_clock::now().time_since_epoch()).count();
62 i->nlinks = 1; // 1 for the directory itself
63 i->sticky = false;
64 i->suid = false;
65 i->sgid = false;
66
67 resp->root_ref.data = (ptr_t) root;
68
69 resp->result.success = true;
70 resp->result.error = NULL;
71
72 return RPC_RESULT_OK;
73}
74
75rpc_result_code_t BlockdevFSServer::readdir(rpc_context_t *, mosrpc_fs_readdir_request *req, mosrpc_fs_readdir_response *resp)
76{
77 if (req->i_ref.data != (ptr_t) root)
78 {
79 resp->result.success = false;
80 resp->result.error = strdup(string: "blockdevfs: invalid inode");
81 return RPC_RESULT_OK;
82 }
83
84 const size_t count = devices.size();
85 resp->entries_count = count;
86 resp->entries = (mosrpc_fs_pb_dirent *) malloc(size: count * sizeof(mosrpc_fs_pb_dirent));
87
88 int i = 0;
89 for (const auto &[name, info] : devices)
90 {
91 mosrpc_fs_pb_dirent *e = &resp->entries[i++];
92 e->name = strdup(string: name.c_str());
93 e->ino = info.ino;
94 e->type = FILE_TYPE_BLOCK_DEVICE;
95 }
96
97 resp->result.success = true;
98 resp->result.error = NULL;
99 return RPC_RESULT_OK;
100}
101
102rpc_result_code_t BlockdevFSServer::lookup(rpc_context_t *, mosrpc_fs_lookup_request *req, mosrpc_fs_lookup_response *resp)
103{
104 if (req->i_ref.data != (ptr_t) root)
105 {
106 resp->result.success = false;
107 resp->result.error = strdup(string: "blockdevfs: invalid inode");
108 return RPC_RESULT_OK;
109 }
110
111 if (!devices.contains(x: req->name))
112 {
113 resp->result.success = false;
114 resp->result.error = strdup(string: "blockdevfs: no such block device");
115 return RPC_RESULT_OK;
116 }
117
118 const auto &info = devices[req->name];
119
120 mosrpc_fs_inode_info *i = &resp->i_info;
121 i->ino = info.ino;
122 i->type = FILE_TYPE_BLOCK_DEVICE;
123 i->perm = 0660;
124 i->uid = 0;
125 i->gid = 0;
126 i->size = info.n_blocks * info.block_size;
127 i->accessed = i->modified = i->created = std::chrono::duration_cast<std::chrono::seconds>(d: std::chrono::system_clock::now().time_since_epoch()).count();
128 i->nlinks = 1;
129 i->sticky = false;
130 i->suid = false;
131 i->sgid = false;
132
133 resp->result.success = true;
134 resp->result.error = NULL;
135 return RPC_RESULT_OK;
136}
137
138static void *blockdevfs_worker(void *data)
139{
140 MOS_UNUSED(data);
141 pthread_setname_np(pthread_self(), "blockdevfs.worker");
142 assert(blockdevfs != nullptr);
143 blockdevfs->run();
144 std::cout << "blockdevfs: worker thread exiting" << std::endl;
145 return NULL;
146}
147
148bool register_blockdevfs()
149{
150 blockdevfs = std::make_unique<BlockdevFSServer>(BLOCKDEVFS_RPC_SERVER_NAME);
151
152 UserFSManagerStub userfs_manager{ USERFS_SERVER_RPC_NAME };
153 mosrpc_userfs_register_request req = { .fs = { .name = strdup(BLOCKDEVFS_NAME) }, .rpc_server_name = strdup(BLOCKDEVFS_RPC_SERVER_NAME) };
154 mosrpc_userfs_register_response resp;
155
156 const rpc_result_code_t result = userfs_manager.register_filesystem(request: &req, response: &resp);
157 if (result != RPC_RESULT_OK || !resp.result.success)
158 {
159 std::cout << "blockdevfs: failed to register blockdevfs with filesystem server" << std::endl;
160 std::cout << "blockdevfs: " << resp.result.error << std::endl;
161 return false;
162 }
163
164 pb_release(mosrpc_userfs_register_request_fields, dest_struct: &req);
165 pb_release(mosrpc_userfs_register_response_fields, dest_struct: &resp);
166
167 pthread_t worker;
168 if (pthread_create(&worker, NULL, blockdevfs_worker, NULL) != 0)
169 {
170 std::cout << "blockdevfs: failed to create worker thread" << std::endl;
171 return false;
172 }
173
174 mkdir("/dev/block", 0755);
175 long ok = syscall_vfs_mount(device: "none", mount_point: "/dev/block", fs_type: "userfs.blockdevfs", options: "defaults"); // a blocked syscall
176 if (IS_ERR_VALUE(ok))
177 {
178 std::cerr << "Failed to mount blockdevfs" << std::endl;
179 return 1;
180 }
181
182 return true;
183}
184