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 | |
24 | using namespace std::chrono; |
25 | |
26 | #define BLOCKDEVFS_NAME "blockdevfs" |
27 | #define BLOCKDEVFS_RPC_SERVER_NAME "fs.blockdevfs" |
28 | |
29 | static std::unique_ptr<IUserFSService> blockdevfs; |
30 | |
31 | struct blockdevfs_inode |
32 | { |
33 | std::string blockdev_name; |
34 | }; |
35 | |
36 | static blockdevfs_inode *root = NULL; |
37 | |
38 | rpc_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 | |
75 | rpc_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 | |
102 | rpc_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 | |
138 | static 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 | |
148 | bool 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 | |