1#include <compiler/core.h>
2#include <fs/ext2.h>
3#include <fs/vfs.h>
4#include <mem/alloc.h>
5#include <stdbool.h>
6#include <stdint.h>
7#include <string.h>
8#include <test/export.h>
9#include <time/time.h>
10
11// TODO: With all VFS impls, make sure to check these are on the same disk and
12// filesystem
13
14// TODO: open/close atomicity or whatever
15
16// TODO: check if files already exist, update times, check if file types are
17// correct
18
19enum err ext2_vfs_stat(struct vfs_node *v, struct vfs_stat *out);
20enum err ext2_vfs_rename(struct vfs_node *old_parent, const char *old_name,
21 struct vfs_node *new_parent, const char *new_name);
22enum err ext2_vfs_chmod(struct vfs_node *n, mode_t mode);
23
24enum err ext2_vfs_chown(struct vfs_node *n, uid_t uid, gid_t gid);
25enum err ext2_vfs_utime(struct vfs_node *n, uint64_t atime, uint64_t mtime);
26
27enum err ext2_vfs_read(struct vfs_node *n, void *buf, uint64_t size,
28 uint64_t offset);
29
30enum err ext2_vfs_write(struct vfs_node *n, const void *buf, uint64_t size,
31 uint64_t offset);
32
33enum err ext2_vfs_truncate(struct vfs_node *n, uint64_t length);
34
35enum err ext2_vfs_link(struct vfs_node *parent, struct vfs_node *target,
36 const char *name);
37
38enum err ext2_vfs_create(struct vfs_node *n, const char *name, mode_t mode);
39
40enum err ext2_vfs_unlink(struct vfs_node *n, const char *name);
41
42enum err ext2_vfs_symlink(struct vfs_node *parent, const char *target,
43 const char *link_name);
44enum err ext2_vfs_readlink(struct vfs_node *n, char *out_buf, uint64_t size);
45
46enum err ext2_vfs_finddir(struct vfs_node *node, const char *fname,
47 struct vfs_dirent *out);
48enum err ext2_vfs_mkdir(struct vfs_node *n, const char *name, mode_t mode);
49enum err ext2_vfs_rmdir(struct vfs_node *n, const char *name);
50enum err ext2_vfs_readdir(struct vfs_node *n, struct vfs_dirent *out,
51 uint64_t index);
52
53static enum err vfs_dummy_open(struct vfs_node *a, uint32_t b) {
54 cc_ignore(a, b);
55 return ERR_OK;
56}
57
58static enum err vfs_dummy_close(struct vfs_node *a) {
59 cc_ignore(a);
60 return ERR_OK;
61}
62
63static const struct vfs_ops ext2_vfs_ops = {
64 .open = vfs_dummy_open,
65 .close = vfs_dummy_close,
66 .stat = ext2_vfs_stat,
67 .rename = ext2_vfs_rename,
68 .chmod = ext2_vfs_chmod,
69 .chown = ext2_vfs_chown,
70 .utime = ext2_vfs_utime,
71 .read = ext2_vfs_read,
72 .write = ext2_vfs_write,
73 .truncate = ext2_vfs_truncate,
74 .link = ext2_vfs_link,
75 .create = ext2_vfs_create,
76 .unlink = ext2_vfs_unlink,
77 .symlink = ext2_vfs_symlink,
78 .readlink = ext2_vfs_readlink,
79 .finddir = ext2_vfs_finddir,
80 .readdir = ext2_vfs_readdir,
81 .mkdir = ext2_vfs_mkdir,
82 .rmdir = ext2_vfs_rmdir,
83 .mount = vfs_mount,
84 .unmount = vfs_unmount,
85};
86
87//
88//
89// Utility functions and things - converting between ext2/vfs versions of stuff
90//
91//
92
93static uint32_t ext2_to_vfs_flags(uint32_t ext2_flags) {
94 uint32_t vfs_flags = 0;
95
96 if (ext2_flags & EXT2_APPEND_FL)
97 vfs_flags |= VFS_NODE_APPENDONLY;
98
99 if (ext2_flags & EXT2_IMMUTABLE_FL)
100 vfs_flags |= VFS_NODE_IMMUTABLE;
101
102 if (ext2_flags & EXT2_NOATIME_FL)
103 vfs_flags |= VFS_NODE_NOATIME;
104
105 if (ext2_flags & EXT2_SYNC_FL)
106 vfs_flags |= VFS_NODE_SYNC;
107
108 if (ext2_flags & EXT2_DIRSYNC_FL)
109 vfs_flags |= VFS_NODE_DIRSYNC;
110
111 return vfs_flags;
112}
113TEST_EXPORT(ext2_to_vfs_flags);
114
115static uint32_t vfs_to_ext2_flags(uint32_t vfs_flags) {
116 uint32_t ext2_flags = 0;
117
118 if (vfs_flags & VFS_NODE_APPENDONLY)
119 ext2_flags |= EXT2_APPEND_FL;
120
121 if (vfs_flags & VFS_NODE_IMMUTABLE)
122 ext2_flags |= EXT2_IMMUTABLE_FL;
123
124 if (vfs_flags & VFS_NODE_NOATIME)
125 ext2_flags |= EXT2_NOATIME_FL;
126
127 if (vfs_flags & VFS_NODE_SYNC)
128 ext2_flags |= EXT2_SYNC_FL;
129
130 if (vfs_flags & VFS_NODE_DIRSYNC)
131 ext2_flags |= EXT2_DIRSYNC_FL;
132
133 return ext2_flags;
134}
135TEST_EXPORT(vfs_to_ext2_flags);
136
137static uint16_t ext2_to_vfs_mode(uint16_t ext2_mode) {
138 uint16_t vfs_mode = 0;
139
140 // File type
141 switch (ext2_mode & EXT2_S_IFMT) {
142 case EXT2_S_IFREG: vfs_mode |= VFS_MODE_FILE; break;
143 case EXT2_S_IFDIR: vfs_mode |= VFS_MODE_DIR; break;
144 case EXT2_S_IFLNK: vfs_mode |= VFS_MODE_SYMLINK; break;
145 case EXT2_S_IFCHR: vfs_mode |= VFS_MODE_CHARDEV; break;
146 case EXT2_S_IFBLK: vfs_mode |= VFS_MODE_BLOCKDEV; break;
147 case EXT2_S_IFIFO: vfs_mode |= VFS_MODE_PIPE; break;
148 case EXT2_S_IFSOCK: vfs_mode |= VFS_MODE_SOCKET; break;
149 default: vfs_mode |= VFS_MODE_FILE; break;
150 }
151
152 // Owner permissions
153 if (ext2_mode & EXT2_S_IRUSR)
154 vfs_mode |= VFS_MODE_O_READ;
155
156 if (ext2_mode & EXT2_S_IWUSR)
157 vfs_mode |= VFS_MODE_O_WRITE;
158
159 if (ext2_mode & EXT2_S_IXUSR)
160 vfs_mode |= VFS_MODE_O_EXEC;
161
162 // Group permissions
163 if (ext2_mode & EXT2_S_IRGRP)
164 vfs_mode |= VFS_MODE_G_READ;
165
166 if (ext2_mode & EXT2_S_IWGRP)
167 vfs_mode |= VFS_MODE_G_WRITE;
168
169 if (ext2_mode & EXT2_S_IXGRP)
170 vfs_mode |= VFS_MODE_G_EXEC;
171
172 // Other permissions
173 if (ext2_mode & EXT2_S_IROTH)
174 vfs_mode |= VFS_MODE_R_READ;
175
176 if (ext2_mode & EXT2_S_IWOTH)
177 vfs_mode |= VFS_MODE_R_WRITE;
178
179 if (ext2_mode & EXT2_S_IXOTH)
180 vfs_mode |= VFS_MODE_R_EXEC;
181
182 return vfs_mode;
183}
184TEST_EXPORT(ext2_to_vfs_mode);
185
186static uint16_t vfs_to_ext2_mode(uint16_t vfs_mode) {
187 uint16_t ext2_mode = 0;
188
189 // File type
190 switch (vfs_mode & VFS_MODE_TYPE_MASK) {
191 case VFS_MODE_DIR: ext2_mode |= EXT2_S_IFDIR; break;
192 case VFS_MODE_SYMLINK: ext2_mode |= EXT2_S_IFLNK; break;
193 case VFS_MODE_CHARDEV: ext2_mode |= EXT2_S_IFCHR; break;
194 case VFS_MODE_BLOCKDEV: ext2_mode |= EXT2_S_IFBLK; break;
195 case VFS_MODE_PIPE: ext2_mode |= EXT2_S_IFIFO; break;
196 case VFS_MODE_SOCKET: ext2_mode |= EXT2_S_IFSOCK; break;
197 case VFS_MODE_FILE:
198 default: ext2_mode |= EXT2_S_IFREG; break;
199 }
200
201 // Owner permissions
202 if (vfs_mode & VFS_MODE_O_READ)
203 ext2_mode |= EXT2_S_IRUSR;
204
205 if (vfs_mode & VFS_MODE_O_WRITE)
206 ext2_mode |= EXT2_S_IWUSR;
207
208 if (vfs_mode & VFS_MODE_O_EXEC)
209 ext2_mode |= EXT2_S_IXUSR;
210
211 // Group permissions
212 if (vfs_mode & VFS_MODE_G_READ)
213 ext2_mode |= EXT2_S_IRGRP;
214
215 if (vfs_mode & VFS_MODE_G_WRITE)
216 ext2_mode |= EXT2_S_IWGRP;
217
218 if (vfs_mode & VFS_MODE_G_EXEC)
219 ext2_mode |= EXT2_S_IXGRP;
220
221 // Other permissions
222 if (vfs_mode & VFS_MODE_R_READ)
223 ext2_mode |= EXT2_S_IROTH;
224
225 if (vfs_mode & VFS_MODE_R_WRITE)
226 ext2_mode |= EXT2_S_IWOTH;
227
228 if (vfs_mode & VFS_MODE_R_EXEC)
229 ext2_mode |= EXT2_S_IXOTH;
230
231 return ext2_mode;
232}
233TEST_EXPORT(vfs_to_ext2_mode);
234
235static enum err ext2_to_vfs_stat(struct ext2_full_inode *node,
236 struct vfs_stat *out) {
237 if (!out || !node)
238 return ERR_INVAL;
239
240 struct ext2_inode *inode = &node->node;
241
242 out->inode = node->inode_num;
243 out->mode = ext2_to_vfs_mode(ext2_mode: inode->mode);
244 out->nlink = inode->links_count;
245 out->atime = inode->atime;
246 out->mtime = inode->mtime;
247 out->size = inode->size;
248 out->ctime = inode->ctime;
249 out->present_mask = 0xffff;
250
251 return ERR_OK;
252}
253
254static struct vfs_node *make_vfs_node(struct ext2_fs *fs,
255 struct ext2_full_inode *node,
256 const char *fname) {
257 if (!node || !fname || !fs)
258 return NULL;
259
260 struct vfs_node *ret = vnew(ret, ALLOC_ZERO);
261 if (!ret)
262 return NULL;
263
264 if (*fname == '.')
265 ret->flags |= VFS_NODE_HIDDEN;
266
267 ret->flags = ext2_to_vfs_flags(ext2_flags: node->node.flags);
268
269 ret->unique_id = node->inode_num;
270 ret->fs_data = fs;
271 ret->fs_node_data = node;
272 ret->size = node->node.size;
273 ret->mode = ext2_to_vfs_mode(ext2_mode: node->node.mode);
274 ret->ops = &ext2_vfs_ops;
275 ret->fs_type = FS_EXT2;
276
277 return ret;
278}
279
280static struct vfs_dirent *ext2_to_vfs_dirent(struct ext2_dir_entry *ext2) {
281 struct vfs_dirent *dirent = vnew(dirent, ALLOC_ZERO);
282 if (!dirent)
283 return NULL;
284
285 dirent->mode = ext2_to_vfs_mode(ext2_mode: ext2->file_type);
286 dirent->dirent_data = ext2;
287 memcpy(dirent->name, ext2->name, ext2->name_len);
288 return dirent;
289}
290
291struct rename_ctx {
292 const char *old_name;
293 const char *new_name;
294 bool success;
295};
296
297static bool dir_entry_rename_callback(struct ext2_fs *fs,
298 struct ext2_dir_entry *entry,
299 void *ctx_ptr, uint32_t b, uint32_t e_num,
300 uint32_t c) {
301 cc_ignore(c, fs, b, e_num);
302 struct rename_ctx *ctx = (struct rename_ctx *) ctx_ptr;
303
304 if (!ext2_dirent_valid(entry))
305 return false;
306
307 if (entry->inode != 0 &&
308 memcmp(entry->name, ctx->old_name, entry->name_len) == 0 &&
309 ctx->old_name[entry->name_len] == '\0') {
310
311 memcpy(entry->name, ctx->new_name, strlen(ctx->new_name));
312 entry->name_len = strlen(str: ctx->new_name);
313 ctx->success = true;
314 return true;
315 }
316
317 return false;
318}
319
320static enum err dir_entry_rename(struct ext2_fs *fs,
321 struct ext2_full_inode *node, const char *old,
322 const char *new) {
323 if (!ext2_dir_contains_file(fs, dir_inode: node, fname: old))
324 return ERR_NO_ENT;
325
326 struct rename_ctx ctx = {
327 .old_name = old, .new_name = new, .success = false};
328
329 if (!ext2_walk_dir(fs, dir: node, cb: dir_entry_rename_callback, ctx: &ctx))
330 return ERR_IO;
331
332 return ERR_OK;
333}
334
335//
336//
337//
338// Actual implementations are below this
339//
340//
341//
342
343/* no locking necessary in mounting, duh */
344enum err ext2_mount(struct partition *p, struct ext2_fs *fs,
345 struct ext2_sblock *sblock, struct vfs_node *out_node) {
346 if (!fs || !sblock)
347 return ERR_INVAL;
348
349 sblock->mtime = time_get_unix();
350 sblock->wtime = time_get_unix();
351 fs->drive = p->disk;
352 fs->partition = p;
353 fs->inodes_count = sblock->inodes_count;
354 fs->inodes_per_group = sblock->inodes_per_group;
355 fs->inode_size = sblock->inode_size;
356 fs->block_size = KIB(1) << sblock->log_block_size;
357 fs->blocks_per_group = sblock->blocks_per_group;
358
359 spinlock_init(&fs->lock);
360
361 fs->sectors_per_block = fs->block_size / p->disk->sector_size;
362
363 fs->num_groups = DIV_ROUND_UP(fs->inodes_count, fs->inodes_per_group);
364
365 uint32_t superblock_block = KIB(1) / fs->block_size;
366 uint32_t gdt_block = (fs->block_size == KIB(1)) ? 2 : 1;
367
368 fs->sblock =
369 (void *) ext2_block_read(fs, block_num: superblock_block, out: &fs->sbcache_ent);
370
371 fs->group_desc =
372 (void *) ext2_block_read(fs, block_num: gdt_block, out: &fs->gdesc_cache_ent);
373
374 struct ext2_full_inode *f = vnew(f, ALLOC_ZERO);
375 if (!f)
376 return ERR_NO_MEM;
377
378 struct bcache_entry *root_ent = NULL;
379 struct ext2_inode *root = ext2_inode_read(fs, EXT2_ROOT_INODE, out_ent: &root_ent);
380 if (!root) {
381 vfree(f);
382 return ERR_IO;
383 }
384
385 if (!out_node) {
386 vfree(f);
387 bcache_ent_release(ent: root_ent);
388 return ERR_OK;
389 }
390
391 memcpy(&f->node, root, sizeof(struct ext2_inode));
392
393 f->inode_num = EXT2_ROOT_INODE;
394
395 out_node->open_handles += 1;
396 out_node->flags = ext2_to_vfs_flags(ext2_flags: f->node.flags);
397 out_node->mode = ext2_to_vfs_mode(ext2_mode: f->node.mode);
398 out_node->size = f->node.size;
399 out_node->fs_data = fs;
400 out_node->fs_node_data = f;
401 out_node->fs_type = FS_EXT2;
402 out_node->ops = &ext2_vfs_ops;
403
404 bcache_ent_release(ent: fs->gdesc_cache_ent);
405 bcache_ent_release(ent: fs->sbcache_ent);
406 bcache_ent_release(ent: root_ent);
407 return ERR_OK;
408}
409
410enum err ext2_vfs_finddir(struct vfs_node *node, const char *fname,
411 struct vfs_dirent *out) {
412 if (!node || !fname || !out)
413 return ERR_INVAL;
414
415 struct ext2_full_inode *full_inode = node->fs_node_data;
416
417 struct ext2_fs *fs = node->fs_data;
418
419 struct ext2_full_inode *found =
420 ext2_find_file_in_dir(fs, dir_inode: full_inode, fname, NULL);
421
422 if (!found)
423 return ERR_NO_ENT;
424
425 struct vfs_node *n = make_vfs_node(fs, node: found, fname);
426
427 struct vfs_dirent ent;
428 ent.mode = n->mode;
429 ent.node = n;
430 ent.dirent_data = fs;
431
432 memcpy(&ent.name, fname, strlen(fname));
433 memcpy(out, &ent, sizeof(struct vfs_dirent));
434
435 return ERR_OK;
436}
437
438enum err ext2_vfs_readdir(struct vfs_node *node, struct vfs_dirent *out,
439 uint64_t index) {
440 if (!node || !out)
441 return ERR_INVAL;
442
443 struct ext2_full_inode *full_inode = node->fs_node_data;
444
445 struct ext2_fs *fs = node->fs_data;
446
447 struct ext2_dir_entry *ext2_out = vnew(ext2_out, ALLOC_ZERO);
448 if (cc_unlikely(!ext2_out))
449 return ERR_NO_MEM;
450
451 enum err e = ext2_readdir(fs, dir_inode: full_inode, out: ext2_out, entry_offset: index);
452
453 if (ERR_IS_FATAL(e))
454 return e;
455
456 struct vfs_dirent *dout = ext2_to_vfs_dirent(ext2: ext2_out);
457
458 memcpy(out, dout, sizeof(struct vfs_dirent));
459
460 return e;
461}
462
463enum err ext2_vfs_rename(struct vfs_node *old_parent, const char *old_name,
464 struct vfs_node *new_parent, const char *new_name) {
465 if (!old_parent || !old_name || !new_name || !new_name)
466 return ERR_INVAL;
467
468 struct ext2_fs *fs = old_parent->fs_data;
469 struct ext2_full_inode *old_node = old_parent->fs_node_data;
470 struct ext2_full_inode *new_node = new_parent->fs_node_data;
471
472 if (old_node == new_node) {
473 return dir_entry_rename(fs, node: old_node, old: old_name, new: new_name);
474 }
475
476 uint8_t ftype = 0;
477
478 struct ext2_full_inode *this_inode =
479 ext2_find_file_in_dir(fs, dir_inode: old_node, fname: old_name, type_out: &ftype);
480
481 if (!this_inode)
482 return ERR_NO_ENT;
483
484 ext2_unlink_file(fs, dir_inode: old_node, name: old_name, false, false);
485
486 return ext2_link_file(fs, dir_inode: new_node, inode: this_inode, name: new_name, type: ftype, true);
487}
488
489enum err ext2_vfs_stat(struct vfs_node *v, struct vfs_stat *out) {
490 struct ext2_full_inode *node = v->fs_node_data;
491 return ext2_to_vfs_stat(node, out);
492}
493
494enum err ext2_vfs_link(struct vfs_node *parent, struct vfs_node *target,
495 const char *name) {
496 if (!parent || !target || !name)
497 return ERR_INVAL;
498
499 if (target->mode & VFS_MODE_DIR)
500 return ERR_IS_DIR;
501
502 // TODO: check if this is a directory
503 struct ext2_full_inode *dir = parent->fs_node_data;
504 struct ext2_full_inode *child = target->fs_node_data;
505 struct ext2_fs *fs = parent->fs_data;
506
507 return ext2_link_file(fs, dir_inode: dir, inode: child, name, type: vfs_to_ext2_mode(vfs_mode: target->mode),
508 true);
509}
510
511enum err ext2_vfs_symlink(struct vfs_node *parent, const char *target,
512 const char *link_name) {
513 if (!parent || !target || !link_name)
514 return ERR_INVAL;
515
516 struct ext2_full_inode *dir = parent->fs_node_data;
517 struct ext2_fs *fs = parent->fs_data;
518
519 return ext2_symlink_file(fs, dir_inode: dir, name: link_name, target);
520}
521
522enum err ext2_vfs_readlink(struct vfs_node *n, char *out_buf, uint64_t size) {
523 if (!n || !out_buf || !size)
524 return ERR_INVAL;
525
526 struct ext2_full_inode *node = n->fs_node_data;
527 struct ext2_fs *fs = n->fs_data;
528
529 return ext2_readlink(fs, node, buf: out_buf, size);
530}
531
532enum err ext2_vfs_chmod(struct vfs_node *n, mode_t mode) {
533 if (!n)
534 return ERR_INVAL;
535
536 struct ext2_fs *fs = n->fs_data;
537 struct ext2_full_inode *node = n->fs_node_data;
538 uint16_t new_mode = vfs_to_ext2_mode(vfs_mode: mode);
539 enum err e = ext2_chmod(fs, node, new_mode);
540 if (e != ERR_OK)
541 return e;
542
543 n->mode = mode;
544 return ERR_OK;
545}
546
547enum err ext2_vfs_chown(struct vfs_node *n, uid_t uid, gid_t gid) {
548 if (!n)
549 return ERR_INVAL;
550
551 struct ext2_fs *fs = n->fs_data;
552 struct ext2_full_inode *node = n->fs_node_data;
553 enum err e = ext2_chown(fs, node, new_uid: uid, new_gid: gid);
554 if (e != ERR_OK)
555 return e;
556
557 n->uid = uid;
558 n->gid = gid;
559 return ERR_OK;
560}
561
562enum err ext2_vfs_read(struct vfs_node *n, void *buf, uint64_t size,
563 uint64_t offset) {
564 if (!n || !buf)
565 return ERR_INVAL;
566
567 struct ext2_fs *fs = n->fs_data;
568 struct ext2_full_inode *node = n->fs_node_data;
569 return ext2_read_file(fs, inode: node, offset, buffer: buf, length: size);
570}
571
572enum err ext2_vfs_write(struct vfs_node *n, const void *buf, uint64_t size,
573 uint64_t offset) {
574 if (!n || !buf)
575 return ERR_INVAL;
576
577 struct ext2_fs *fs = n->fs_data;
578 struct ext2_full_inode *node = n->fs_node_data;
579 enum err e = ext2_write_file(fs, inode: node, offset, src: buf, size);
580
581 if (e != ERR_OK)
582 return e;
583
584 n->size = node->node.size;
585 return ERR_OK;
586}
587
588enum err ext2_vfs_utime(struct vfs_node *n, uint64_t atime, uint64_t mtime) {
589 if (!n)
590 return ERR_INVAL;
591
592 struct ext2_fs *fs = n->fs_data;
593 struct ext2_full_inode *node = n->fs_node_data;
594 struct ext2_inode *inode = &node->node;
595 inode->atime = atime;
596 inode->mtime = mtime;
597 if (!ext2_inode_write(fs, inode_num: node->inode_num, inode))
598 return ERR_IO;
599
600 n->atime = atime;
601 n->mtime = mtime;
602 return ERR_OK;
603}
604
605enum err ext2_vfs_truncate(struct vfs_node *n, uint64_t length) {
606 if (!n)
607 return ERR_INVAL;
608
609 struct ext2_fs *fs = n->fs_data;
610 struct ext2_full_inode *node = n->fs_node_data;
611 return ext2_truncate_file(fs, inode: node, new_size: length);
612}
613
614enum err ext2_vfs_unlink(struct vfs_node *n, const char *name) {
615 if (!n || !name)
616 return ERR_INVAL;
617
618 struct ext2_fs *fs = n->fs_data;
619 struct ext2_full_inode *node = n->fs_node_data;
620 return ext2_unlink_file(fs, dir_inode: node, name, true, false);
621}
622
623enum err ext2_vfs_create(struct vfs_node *n, const char *name, mode_t mode) {
624 if (!n || !name)
625 return ERR_INVAL;
626
627 uint16_t new_mode = vfs_to_ext2_mode(vfs_mode: mode);
628 struct ext2_fs *fs = n->fs_data;
629 struct ext2_full_inode *node = n->fs_node_data;
630 return ext2_create_file(fs, parent_dir: node, name, mode: new_mode, false);
631}
632
633enum err ext2_vfs_mkdir(struct vfs_node *n, const char *name, mode_t mode) {
634 if (!n || !name)
635 return ERR_INVAL;
636
637 uint16_t new_mode = vfs_to_ext2_mode(vfs_mode: mode);
638 struct ext2_fs *fs = n->fs_data;
639 struct ext2_full_inode *node = n->fs_node_data;
640 return ext2_mkdir(fs, parent_dir: node, name, mode: new_mode);
641}
642
643enum err ext2_vfs_rmdir(struct vfs_node *n, const char *name) {
644 if (!n || !name)
645 return ERR_INVAL;
646
647 struct ext2_fs *fs = n->fs_data;
648 struct ext2_full_inode *node = n->fs_node_data;
649 return ext2_rmdir(fs, parent_dir: node, name);
650}
651