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