1// SPDX-License-Identifier: GPL-3.0-or-later
2
3#include "ext4fs.hpp"
4
5#include "blockdev.h"
6#include "ext4.h"
7#include "ext4_blockdev.h"
8#include "ext4_dir.h"
9#include "ext4_fs.h"
10#include "ext4_inode.h"
11#include "ext4_types.h"
12#include "proto/blockdev.pb.h"
13#include "proto/filesystem.pb.h"
14
15#include <cassert>
16#include <iostream>
17#include <librpc/rpc.h>
18#include <optional>
19#include <string>
20#include <sys/stat.h>
21
22using namespace mosrpc::blockdev;
23
24// clang-format off
25u64 inode_index_from_data(mosrpc_fs_inode_ref &ref) { return ref.data; }
26
27mosrpc_fs_inode_ref make_inode_ref(ext4_inode_ref &ref) { return { .data = ref.index }; }
28
29mosrpc_fs_inode_ref make_inode_ref(u64 index) { return { .data = index }; }
30// clang-format on
31
32static std::optional<mosrpc_blockdev_blockdev> open_blockdev(const std::string &name)
33{
34 open_device::request req{ .device_name = strdup(string: name.c_str()) };
35 open_device::response resp;
36 const auto result = blockdev_manager->open_device(request: &req, response: &resp);
37
38 if (result != RPC_RESULT_OK || !resp.result.success)
39 {
40 std::cerr << "Failed to open block device" << std::endl;
41 if (resp.result.error)
42 std::cerr << "Error: " << resp.result.error << std::endl;
43 return std::nullopt;
44 }
45
46 auto dev = resp.device;
47 pb_release(fields: &mosrpc_blockdev_open_device_request_msg, dest_struct: &req);
48 pb_release(fields: &mosrpc_blockdev_open_device_response_msg, dest_struct: &resp);
49
50 std::cout << "Block Device '" << name << "' opened" << std::endl;
51 return dev;
52}
53
54static size_t blockdev_size(std::string_view name)
55{
56 struct stat st;
57 if (stat(("/dev/block/" + std::string(name)).c_str(), &st) != 0)
58 {
59 std::cerr << "Failed to get block device size" << std::endl;
60 return 0;
61 }
62
63 return st.st_size;
64}
65
66static int no_op(struct ext4_blockdev *)
67{
68 return 0;
69}
70
71static int blockdev_bread(struct ext4_blockdev *bdev, void *buf, uint64_t blk_id, uint32_t blk_cnt)
72{
73 const auto state = static_cast<ext4_context_state *>(bdev->bdif->p_user);
74
75 read_block::request req{ .device = state->blockdev, .n_boffset = blk_id, .n_blocks = blk_cnt };
76 read_block::response resp;
77
78 const auto result = blockdev_manager->read_block(request: &req, response: &resp);
79
80 if (result != RPC_RESULT_OK || !resp.result.success)
81 {
82 std::cerr << "Failed to read block" << std::endl;
83 if (resp.result.error)
84 std::cerr << "Error: " << resp.result.error << std::endl;
85 return EIO;
86 }
87
88 assert(resp.data->size == 512 * blk_cnt); // 512 bytes per block (hardcoded)
89
90 memcpy(dest: buf, src: resp.data->bytes, size: resp.data->size);
91
92 pb_release(fields: &mosrpc_blockdev_read_block_request_msg, dest_struct: &req);
93 pb_release(fields: &mosrpc_blockdev_read_block_response_msg, dest_struct: &resp);
94
95 return EOK;
96}
97
98static int blockdev_bwrite(struct ext4_blockdev *bdev, const void *buf, uint64_t blk_id, uint32_t blk_cnt)
99{
100 const auto state = static_cast<ext4_context_state *>(bdev->bdif->p_user);
101
102 const auto data_size = 512 * blk_cnt; // 512 bytes per block (hardcoded)
103
104 write_block::request req{
105 .device = state->blockdev,
106 .data = (pb_bytes_array_t *) malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(data_size)),
107 .n_boffset = blk_id,
108 .n_blocks = blk_cnt,
109 };
110
111 // std::cout << "Writing " << blk_cnt << " blocks at offset " << blk_id << std::endl;
112 // std::cout << " buffer: " << buf << std::endl;
113 // std::cout << " to: " << req.data << std::endl;
114 // std::cout << std::flush;
115
116 req.data->size = data_size;
117 memcpy(dest: req.data->bytes, src: buf, size: data_size);
118
119 write_block::response resp;
120
121 const auto result = blockdev_manager->write_block(request: &req, response: &resp);
122
123 if (result != RPC_RESULT_OK || !resp.result.success)
124 {
125 std::cerr << "Failed to write block" << std::endl;
126 if (resp.result.error)
127 std::cerr << "Error: " << resp.result.error << std::endl;
128
129 pb_release(fields: &mosrpc_blockdev_write_block_request_msg, dest_struct: &req);
130 pb_release(fields: &mosrpc_blockdev_write_block_response_msg, dest_struct: &resp);
131 return EIO;
132 }
133
134 pb_release(fields: &mosrpc_blockdev_write_block_request_msg, dest_struct: &req);
135 pb_release(fields: &mosrpc_blockdev_write_block_response_msg, dest_struct: &resp);
136
137 // std::cout << " ok" << std::endl;
138 return EOK;
139}
140
141Ext4UserFS::Ext4UserFS(const std::string &name) : IUserFSService(name)
142{
143}
144
145void Ext4UserFS::on_connect(rpc_context_t *ctx)
146{
147 set_data(context: ctx, data: new ext4_context_state());
148}
149
150void Ext4UserFS::on_disconnect(rpc_context_t *ctx)
151{
152 delete get_data<ext4_context_state>(context: ctx);
153}
154
155void Ext4UserFS::populate_mosrpc_fs_inode_info(mosrpc_fs_inode_info &info, ext4_sblock *sb, ext4_inode *inode, int ino)
156{
157 info.ino = ino;
158 info.perm = ext4_inode_get_mode(sb, inode);
159 info.uid = ext4_inode_get_uid(inode);
160 info.gid = ext4_inode_get_gid(inode);
161 info.size = ext4_inode_get_size(sb, inode);
162 info.accessed = ext4_inode_get_access_time(inode);
163 info.modified = ext4_inode_get_modif_time(inode);
164 info.created = ext4_inode_get_change_inode_time(inode);
165 info.nlinks = ext4_inode_get_links_cnt(inode);
166 info.type = ext4_get_file_type(sb, dentry_or_inode: inode);
167
168 // TODO: Implement the following fields
169 info.sticky = false;
170 info.suid = false;
171 info.sgid = false;
172};
173
174void Ext4UserFS::save_inode_info(ext4_sblock *sb, ext4_inode *inode, const mosrpc_fs_inode_info &info)
175{
176 ext4_inode_set_size(inode, size: info.size);
177 ext4_inode_set_mode(sb, inode, mode: info.perm);
178 ext4_inode_set_uid(inode, uid: info.uid);
179 ext4_inode_set_gid(inode, gid: info.gid);
180 ext4_inode_set_access_time(inode, time: info.accessed);
181 ext4_inode_set_modif_time(inode, time: info.modified);
182 ext4_inode_set_change_inode_time(inode, time: info.created);
183 ext4_inode_set_links_cnt(inode, cnt: info.nlinks);
184}
185
186rpc_result_code_t Ext4UserFS::mount(rpc_context_t *ctx, mosrpc_fs_mount_request *req, mosrpc_fs_mount_response *resp)
187{
188 if (req->fs_name != "userfs.ext4"s)
189 {
190 resp->result.success = false;
191 resp->result.error = strdup(string: "Invalid filesystem name");
192 return RPC_RESULT_OK;
193 }
194
195 auto state = get_data<ext4_context_state>(context: ctx);
196
197 const auto dev = open_blockdev(name: req->device);
198 if (!dev)
199 {
200 resp->result.success = false;
201 resp->result.error = strdup(string: "Failed to open block device");
202 return RPC_RESULT_OK;
203 }
204
205 state->blockdev = dev.value();
206
207 const auto devsize = blockdev_size(name: req->device);
208
209 state->ext4_dev_iface.open = no_op;
210 state->ext4_dev_iface.close = no_op;
211 state->ext4_dev_iface.lock = no_op;
212 state->ext4_dev_iface.unlock = no_op;
213 state->ext4_dev_iface.bread = blockdev_bread;
214 state->ext4_dev_iface.bwrite = blockdev_bwrite;
215 state->ext4_dev_iface.ph_bsize = 512;
216 state->ext4_dev_iface.ph_bcnt = devsize / 512;
217 state->ext4_dev_iface.ph_bbuf = state->ext4_buf;
218 state->ext4_dev_iface.p_user = state;
219
220 state->ext4_dev.bdif = &state->ext4_dev_iface;
221 state->ext4_dev.part_offset = 0;
222 state->ext4_dev.part_size = devsize;
223
224 ext4_device_register(bd: &state->ext4_dev, dev_name: "dev");
225
226 if (int retval = ext4_mount(dev_name: "dev", mount_point: "/", read_only: false); retval != EOK)
227 {
228 resp->result.success = false;
229 resp->result.error = strdup(string: strerror(errnum: retval));
230 return RPC_RESULT_OK;
231 }
232
233 state->fs = state->ext4_dev.fs;
234
235 if (state->fs->read_only)
236 {
237 resp->result.success = false;
238 resp->result.error = strdup(string: "Filesystem is read-only");
239 return RPC_RESULT_OK;
240 }
241
242 state->mp = ext4_get_mount(path: "/");
243
244 ext4_inode_ref root;
245 ext4_fs_get_inode_ref(fs: state->fs, EXT4_ROOT_INO, ref: &root);
246 populate_mosrpc_fs_inode_info(info&: resp->root_info, sb: &state->fs->sb, inode: root.inode, EXT4_ROOT_INO);
247 ext4_fs_put_inode_ref(ref: &root);
248 resp->root_ref = make_inode_ref(ref&: root);
249
250 resp->result.success = true;
251 resp->result.error = nullptr;
252 return RPC_RESULT_OK;
253}
254
255rpc_result_code_t Ext4UserFS::readdir(rpc_context_t *ctx, mosrpc_fs_readdir_request *req, mosrpc_fs_readdir_response *resp)
256{
257 auto state = get_data<ext4_context_state>(context: ctx);
258
259 ext4_inode_ref dir;
260 if (ext4_fs_get_inode_ref(fs: state->fs, index: inode_index_from_data(ref&: req->i_ref), ref: &dir) != EOK)
261 {
262 resp->result.success = false;
263 resp->result.error = strdup(string: "Failed to get inode reference");
264 return RPC_RESULT_OK;
265 }
266
267 ext4_dir_iter iter;
268 if (ext4_dir_iterator_init(it: &iter, inode_ref: &dir, pos: 0) != EOK)
269 {
270 resp->result.success = false;
271 resp->result.error = strdup(string: "Failed to initialize directory iterator");
272 ext4_fs_put_inode_ref(ref: &dir);
273 return RPC_RESULT_OK;
274 }
275
276 while (iter.curr != NULL)
277 {
278 if (ext4_dir_en_get_inode(de: iter.curr) != 0)
279 {
280 // Found a non-empty directory entry
281 const auto de = iter.curr;
282
283 mosrpc_fs_pb_dirent dirent;
284 dirent.ino = de->inode;
285 dirent.name = strndup((char *) de->name, de->name_len);
286 dirent.type = ext4_get_file_type(sb: &state->fs->sb, dentry_or_inode: de);
287
288 resp->entries_count++;
289 resp->entries = (mosrpc_fs_pb_dirent *) realloc(pointer: resp->entries, size: resp->entries_count * sizeof(mosrpc_fs_pb_dirent));
290 resp->entries[resp->entries_count - 1] = dirent;
291 }
292
293 if (ext4_dir_iterator_next(it: &iter) != EOK)
294 break; // got some error
295 }
296
297 if (ext4_dir_iterator_fini(it: &iter) != EOK)
298 {
299 resp->result.success = false;
300 resp->result.error = strdup(string: "Failed to finalize directory iterator");
301 ext4_fs_put_inode_ref(ref: &dir);
302 return RPC_RESULT_OK;
303 }
304
305 ext4_fs_put_inode_ref(ref: &dir);
306
307 resp->result.success = true;
308 resp->result.error = nullptr;
309 return RPC_RESULT_OK;
310}
311
312rpc_result_code_t Ext4UserFS::lookup(rpc_context_t *ctx, mosrpc_fs_lookup_request *req, mosrpc_fs_lookup_response *resp)
313{
314 auto state = get_data<ext4_context_state>(context: ctx);
315
316 ext4_inode_ref parent_inode_ref;
317 if (ext4_fs_get_inode_ref(fs: state->fs, index: inode_index_from_data(ref&: req->i_ref), ref: &parent_inode_ref) != EOK)
318 {
319 resp->result.success = false;
320 resp->result.error = strdup(string: "Failed to get inode reference");
321 return RPC_RESULT_OK;
322 }
323
324 ext4_dir_search_result result;
325 if (ext4_dir_find_entry(result: &result, parent: &parent_inode_ref, name: req->name, name_len: strlen(s: req->name)) != EOK)
326 {
327 resp->result.success = false;
328 resp->result.error = strdup(string: "Failed to find directory entry");
329 return RPC_RESULT_OK;
330 }
331
332 ext4_inode_ref sub_inode;
333 if (ext4_fs_get_inode_ref(fs: state->fs, index: result.dentry->inode, ref: &sub_inode) != EOK)
334 {
335 ext4_dir_destroy_result(parent: &parent_inode_ref, result: &result);
336 resp->result.success = false;
337 resp->result.error = strdup(string: "Failed to get inode reference");
338 return RPC_RESULT_OK;
339 }
340
341 resp->i_ref = make_inode_ref(index: result.dentry->inode);
342 populate_mosrpc_fs_inode_info(info&: resp->i_info, sb: &state->fs->sb, inode: sub_inode.inode, ino: result.dentry->inode);
343 ext4_dir_destroy_result(parent: &parent_inode_ref, result: &result);
344 ext4_fs_put_inode_ref(ref: &sub_inode);
345 ext4_fs_put_inode_ref(ref: &parent_inode_ref);
346
347 resp->result.success = true;
348 resp->result.error = nullptr;
349 return RPC_RESULT_OK;
350}
351
352rpc_result_code_t Ext4UserFS::readlink(rpc_context_t *ctx, mosrpc_fs_readlink_request *req, mosrpc_fs_readlink_response *resp)
353{
354 auto state = get_data<ext4_context_state>(context: ctx);
355
356 ext4_inode_ref inode_ref;
357 if (ext4_fs_get_inode_ref(fs: state->fs, index: inode_index_from_data(ref&: req->i_ref), ref: &inode_ref) != EOK)
358 {
359 resp->result.success = false;
360 resp->result.error = strdup(string: "Failed to get inode reference");
361 return RPC_RESULT_OK;
362 }
363
364 size_t file_size = ext4_inode_get_size(sb: &state->fs->sb, inode: inode_ref.inode);
365 if (file_size == 0)
366 {
367 resp->result.success = false;
368 resp->result.error = strdup(string: "File is empty");
369 ext4_fs_put_inode_ref(ref: &inode_ref);
370 return RPC_RESULT_OK;
371 }
372
373 ext4_file file = {
374 .mp = state->mp,
375 .inode = inode_ref.index,
376 .flags = O_RDONLY,
377 .fsize = file_size,
378 .fpos = 0,
379 };
380
381 char buf[MOS_PATH_MAX_LENGTH] = { 0 };
382 size_t read_cnt;
383 if (ext4_fread(file: &file, buf, MOS_PATH_MAX_LENGTH, rcnt: &read_cnt) != EOK)
384 {
385 resp->result.success = false;
386 resp->result.error = strdup(string: "Failed to read file");
387 ext4_fs_put_inode_ref(ref: &inode_ref);
388 return RPC_RESULT_OK;
389 }
390
391 resp->target = strdup(string: buf);
392
393 resp->result.success = true;
394 resp->result.error = nullptr;
395
396 ext4_fs_put_inode_ref(ref: &inode_ref);
397 return RPC_RESULT_OK;
398}
399
400rpc_result_code_t Ext4UserFS::get_page(rpc_context_t *ctx, mosrpc_fs_getpage_request *req, mosrpc_fs_getpage_response *resp)
401{
402 auto state = get_data<ext4_context_state>(context: ctx);
403 ext4_inode_ref inode_ref;
404 if (ext4_fs_get_inode_ref(fs: state->fs, index: inode_index_from_data(ref&: req->i_ref), ref: &inode_ref) != EOK)
405 {
406 resp->result.success = false;
407 resp->result.error = strdup(string: "Failed to get inode reference");
408 return RPC_RESULT_OK;
409 }
410
411 ext4_inode *inode = inode_ref.inode;
412 const size_t file_size = ext4_inode_get_size(sb: &state->fs->sb, inode);
413
414 ext4_file file = {
415 .mp = state->mp,
416 .inode = inode_ref.index,
417 .flags = O_RDONLY,
418 .fsize = file_size,
419 .fpos = req->pgoff * MOS_PAGE_SIZE,
420 };
421
422 const size_t read_size = std::min(a: (size_t) MOS_PAGE_SIZE, b: file_size - file.fpos);
423
424 resp->data = (pb_bytes_array_t *) malloc(PB_BYTES_ARRAY_T_ALLOCSIZE(read_size));
425 size_t read_cnt = 0; // should zero-initialize if read_size is zero
426 if (ext4_fread(file: &file, buf: resp->data->bytes, size: read_size, rcnt: &read_cnt) != EOK)
427 {
428 resp->result.success = false;
429 resp->result.error = strdup(string: "Failed to read file");
430 ext4_fs_put_inode_ref(ref: &inode_ref);
431 return RPC_RESULT_OK;
432 }
433
434 assert(read_cnt <= MOS_PAGE_SIZE);
435 resp->data->size = read_cnt;
436
437 resp->result.success = true;
438 resp->result.error = nullptr;
439 ext4_fs_put_inode_ref(ref: &inode_ref);
440 return RPC_RESULT_OK;
441}
442
443rpc_result_code_t Ext4UserFS::create_file(rpc_context_t *ctx, mosrpc_fs_create_file_request *req, mosrpc_fs_create_file_response *resp)
444{
445 auto state = get_data<ext4_context_state>(context: ctx);
446 ext4_inode_ref inode_ref;
447 if (ext4_fs_get_inode_ref(fs: state->fs, index: inode_index_from_data(ref&: req->i_ref), ref: &inode_ref) != EOK)
448 {
449 resp->result.success = false;
450 resp->result.error = strdup(string: "Failed to get inode reference");
451 return RPC_RESULT_OK;
452 }
453
454 const auto ext4_ftype = [&]()
455 {
456 switch ((file_type_t) req->type)
457 {
458 case FILE_TYPE_REGULAR: return EXT4_DE_REG_FILE;
459 case FILE_TYPE_DIRECTORY: return EXT4_DE_DIR;
460 case FILE_TYPE_SYMLINK: return EXT4_DE_SYMLINK;
461 case FILE_TYPE_CHAR_DEVICE: return EXT4_DE_CHRDEV;
462 case FILE_TYPE_BLOCK_DEVICE: return EXT4_DE_BLKDEV;
463 case FILE_TYPE_NAMED_PIPE: return EXT4_DE_FIFO;
464 case FILE_TYPE_SOCKET: return EXT4_DE_SOCK;
465 case FILE_TYPE_UNKNOWN: return EXT4_DE_UNKNOWN;
466 default: return EXT4_DE_UNKNOWN;
467 }
468 }();
469
470 ext4_inode_ref child_ref;
471 int ret = ext4_fs_alloc_inode(fs: state->fs, inode_ref: &child_ref, filetype: (int) ext4_ftype);
472 if (ret != EOK)
473 {
474 resp->result.success = false;
475 resp->result.error = strdup(string: "Failed to allocate inode");
476 ext4_fs_put_inode_ref(ref: &inode_ref);
477 return RPC_RESULT_OK;
478 }
479
480 ext4_fs_inode_blocks_init(fs: state->fs, inode_ref: &child_ref);
481
482 ret = ext4_link(mp: state->mp, parent: &inode_ref, ch: &child_ref, n: req->name, len: strlen(s: req->name), rename: false);
483 if (ret != EOK)
484 {
485 /*Fail. Free new inode.*/
486 ext4_fs_free_inode(inode_ref: &child_ref);
487 /*We do not want to write new inode. But block has to be released.*/
488 child_ref.dirty = false;
489 ext4_fs_put_inode_ref(ref: &child_ref);
490 ext4_fs_put_inode_ref(ref: &inode_ref);
491 resp->result.success = false;
492 resp->result.error = strdup(string: "Failed to link new inode");
493 return RPC_RESULT_OK;
494 }
495
496 resp->i_ref = make_inode_ref(ref&: child_ref);
497 populate_mosrpc_fs_inode_info(info&: resp->i_info, sb: &state->fs->sb, inode: child_ref.inode, ino: child_ref.index);
498 ext4_fs_put_inode_ref(ref: &child_ref);
499 ext4_block_cache_flush(bdev: state->mp->bc.bdev);
500 ext4_fs_put_inode_ref(ref: &inode_ref);
501
502 resp->result.success = true;
503 resp->result.error = nullptr;
504 return RPC_RESULT_OK;
505}
506
507rpc_result_code_t Ext4UserFS::put_page(rpc_context_t *ctx, mosrpc_fs_putpage_request *req, mosrpc_fs_putpage_response *resp)
508{
509 auto state = get_data<ext4_context_state>(context: ctx);
510 ext4_inode_ref inode_ref;
511 if (ext4_fs_get_inode_ref(fs: state->fs, index: inode_index_from_data(ref&: req->i_ref), ref: &inode_ref) != EOK)
512 {
513 resp->result.success = false;
514 resp->result.error = strdup(string: "Failed to get inode reference");
515 return RPC_RESULT_OK;
516 }
517
518 ext4_inode *inode = inode_ref.inode;
519 ext4_file file = {
520 .mp = state->mp,
521 .inode = inode_ref.index,
522 .flags = O_WRONLY,
523 .fsize = ext4_inode_get_size(sb: &state->fs->sb, inode),
524 .fpos = 0,
525 };
526
527 ext4_fseek(file: &file, offset: req->pgoff * MOS_PAGE_SIZE, SEEK_SET);
528
529 const auto write_pos = req->pgoff * MOS_PAGE_SIZE;
530
531 // if pos is beyond the file size, we need to extend the file
532 if (write_pos > file.fsize)
533 {
534 size_t pad_size = write_pos - file.fsize;
535 while (pad_size > 0)
536 {
537 char pad[512] = { 0 };
538 size_t written = 0;
539 if (int err = ext4_fwrite(file: &file, buf: pad, size: std::min(a: pad_size, b: sizeof(pad)), wcnt: &written); err != EOK)
540 {
541 resp->result.success = false;
542 resp->result.error = strdup(string: (std::string("Failed to pad file: ") + strerror(errnum: err)).data());
543 ext4_fs_put_inode_ref(ref: &inode_ref);
544 return RPC_RESULT_OK;
545 }
546
547 pad_size -= written;
548 }
549 }
550
551 size_t written = 0;
552
553 if (int err = ext4_fwrite(file: &file, buf: req->data->bytes, size: req->data->size, wcnt: &written); err != EOK)
554 {
555 resp->result.success = false;
556 resp->result.error = strdup(string: (std::string("Failed to write file: ") + strerror(errnum: err)).data());
557 ext4_fs_put_inode_ref(ref: &inode_ref);
558 return RPC_RESULT_OK;
559 }
560
561 if (written != req->data->size)
562 {
563 resp->result.success = false;
564 resp->result.error = strdup(string: "Failed to write all data");
565 ext4_fs_put_inode_ref(ref: &inode_ref);
566 return RPC_RESULT_OK;
567 }
568
569 ext4_fs_put_inode_ref(ref: &inode_ref);
570 resp->result.success = true;
571 resp->result.error = nullptr;
572 return RPC_RESULT_OK;
573}
574
575rpc_result_code_t Ext4UserFS::sync_inode(rpc_context_t *ctx, mosrpc_fs_sync_inode_request *req, mosrpc_fs_sync_inode_response *resp)
576{
577 auto state = get_data<ext4_context_state>(context: ctx);
578
579 ext4_inode_ref inode_ref;
580 if (ext4_fs_get_inode_ref(fs: state->fs, index: req->i_info.ino, ref: &inode_ref) != EOK)
581 {
582 resp->result.success = false;
583 resp->result.error = strdup(string: "Failed to get inode reference");
584 return RPC_RESULT_OK;
585 }
586
587 save_inode_info(sb: &state->fs->sb, inode: inode_ref.inode, info: req->i_info);
588 inode_ref.dirty = true;
589 ext4_fs_put_inode_ref(ref: &inode_ref);
590 resp->result.success = true;
591 resp->result.error = nullptr;
592 return RPC_RESULT_OK;
593}
594
595rpc_result_code_t Ext4UserFS::unlink(rpc_context_t *ctx, mosrpc_fs_unlink_request *req, mosrpc_fs_unlink_response *resp)
596{
597 auto state = get_data<ext4_context_state>(context: ctx);
598
599 ext4_inode_ref dir;
600 if (ext4_fs_get_inode_ref(fs: state->fs, index: inode_index_from_data(ref&: req->i_ref), ref: &dir) != EOK)
601 {
602 resp->result.success = false;
603 resp->result.error = strdup(string: "Failed to get directory inode reference");
604 return RPC_RESULT_OK;
605 }
606
607 ext4_inode_ref child;
608 if (ext4_fs_get_inode_ref(fs: state->fs, index: req->dentry.inode_id, ref: &child) != EOK)
609 {
610 resp->result.success = false;
611 resp->result.error = strdup(string: "Failed to get child inode reference");
612 return RPC_RESULT_OK;
613 }
614
615 /*Link count will be zero, the inode should be freed. */
616 if (ext4_inode_get_links_cnt(inode: child.inode) == 1)
617 {
618 ext4_block_cache_write_back(bdev: state->fs->bdev, on_off: 1);
619 const auto r = ext4_fs_truncate_inode(inode_ref: &child, new_size: 0);
620 if (r != EOK)
621 {
622 ext4_fs_put_inode_ref(ref: &dir);
623 ext4_fs_put_inode_ref(ref: &child);
624
625 resp->result.success = false;
626 resp->result.error = strdup(string: "Failed to truncate inode");
627 return RPC_RESULT_OK;
628 }
629
630 ext4_block_cache_write_back(bdev: state->fs->bdev, on_off: 0);
631 }
632
633 if (ext4_unlink(mp: state->mp, parent: &dir, child: &child, name: req->dentry.name, name_len: strlen(s: req->dentry.name)) != EOK)
634 {
635 resp->result.success = false;
636 resp->result.error = strdup(string: "Failed to unlink child");
637 return RPC_RESULT_OK;
638 }
639
640 // Link count is zero, the inode should be freed.
641 if (!ext4_inode_get_links_cnt(inode: child.inode))
642 {
643 ext4_inode_set_del_time(inode: child.inode, time: -1L);
644
645 const auto r = ext4_fs_free_inode(inode_ref: &child);
646 if (r != EOK)
647 {
648 resp->result.success = false;
649 resp->result.error = strdup(string: "Failed to free inode");
650 return RPC_RESULT_OK;
651 }
652 }
653
654 ext4_fs_put_inode_ref(ref: &child);
655 ext4_fs_put_inode_ref(ref: &dir);
656
657 resp->result.success = true;
658 resp->result.error = nullptr;
659 return RPC_RESULT_OK;
660}
661
662rpc_result_code_t Ext4UserFS::make_dir(rpc_context_t *ctx, mosrpc_fs_make_dir_request *req, mosrpc_fs_make_dir_response *resp)
663{
664 mosrpc_fs_create_file_request create_req{
665 .i_ref = req->i_ref,
666 .name = req->name,
667 .type = FILE_TYPE_DIRECTORY,
668 .perm = req->perm,
669 };
670
671 mosrpc_fs_create_file_response create_resp;
672 if (create_file(ctx, req: &create_req, resp: &create_resp) != RPC_RESULT_OK)
673 {
674 resp->result.success = false;
675 resp->result.error = create_resp.result.error;
676 return RPC_RESULT_OK;
677 }
678
679 resp->result.success = true;
680 resp->result.error = nullptr;
681 resp->i_info = create_resp.i_info;
682 resp->i_ref = create_resp.i_ref;
683 return RPC_RESULT_OK;
684}
685