1#include <block/bcache.h>
2#include <errno.h>
3#include <fs/ext2.h>
4#include <stdbool.h>
5#include <stdint.h>
6#include <string.h>
7#include <time/time.h>
8
9struct unlink_ctx {
10 const char *name;
11 bool found;
12 inode_t inode_num;
13 uint32_t block_num;
14 uint32_t entry_offset;
15 uint32_t prev_offset;
16};
17
18void free_block_visitor(struct ext2_fs *fs, struct ext2_inode *inode,
19 uint32_t depth, uint32_t *block_ptr, void *user_data) {
20 (void) inode, (void) user_data, (void) depth;
21 if (*block_ptr) {
22 ext2_free_block(fs, block_num: *block_ptr);
23 *block_ptr = 0;
24 }
25}
26
27bool unlink_callback(struct ext2_fs *fs, struct ext2_dir_entry *entry,
28 void *arg, uint32_t block_num, uint32_t e,
29 uint32_t entry_offset) {
30 (void) e;
31 (void) fs;
32 struct unlink_ctx *ctx = (struct unlink_ctx *) arg;
33
34 if (ctx->found)
35 return false;
36
37 if (entry->name_len == strlen(str: ctx->name) &&
38 strncmp(s1: entry->name, s2: ctx->name, n: entry->name_len) == 0) {
39 ctx->found = true;
40 ctx->inode_num = entry->inode;
41 ctx->block_num = block_num;
42 ctx->entry_offset = entry_offset;
43 entry->inode = 0;
44 entry->name_len = 0;
45 memset(entry->name, 0, EXT2_NAME_LEN);
46 return true;
47 }
48
49 ctx->prev_offset = entry_offset;
50 return false;
51}
52
53static void unlink_adjust_neighbors(struct ext2_fs *fs, uint8_t *block,
54 uint32_t offset, uint32_t prev_offset) {
55 struct ext2_dir_entry *entry = (struct ext2_dir_entry *) (block + offset);
56
57 if (offset == 0) {
58 struct ext2_dir_entry *next =
59 (struct ext2_dir_entry *) ((uint8_t *) entry + entry->rec_len);
60 if ((uint8_t *) next < block + fs->block_size && next->inode != 0) {
61 next->rec_len += entry->rec_len;
62 }
63 } else {
64 struct ext2_dir_entry *prev =
65 (struct ext2_dir_entry *) (block + prev_offset);
66 prev->rec_len += entry->rec_len;
67 }
68}
69
70static inline void unlink_target_update(struct ext2_inode *target_inode) {
71 target_inode->dtime = time_get_unix();
72 target_inode->links_count--;
73}
74
75static inline void unlink_free_blocks(struct ext2_fs *fs,
76 struct ext2_inode *target_inode,
77 inode_t inode_num) {
78 ext2_traverse_inode_blocks(fs, inode: target_inode, visitor: free_block_visitor, NULL,
79 false);
80 ext2_free_inode(fs, inode_num);
81}
82
83enum errno ext2_unlink_file(struct ext2_fs *fs,
84 struct ext2_full_inode *dir_inode, const char *name,
85 bool free_blocks, bool decrement_links) {
86 if (!ext2_dir_contains_file(fs, dir_inode, fname: name))
87 return ERR_NO_ENT;
88
89 struct unlink_ctx ctx = {name, false, 0, 0, 0, 0};
90 if (!ext2_walk_dir(fs, dir: dir_inode, cb: unlink_callback, ctx: &ctx))
91 return ERR_FS_INTERNAL;
92
93 struct bcache_entry *ent;
94 uint8_t *block = ext2_block_read(fs, block_num: ctx.block_num, out: &ent);
95 if (!block)
96 return ERR_IO;
97
98 unlink_adjust_neighbors(fs, block, offset: ctx.entry_offset, prev_offset: ctx.prev_offset);
99 bcache_ent_release(ent);
100
101 if (!ext2_block_write(fs, ent, EXT2_PRIO_DIRENT))
102 return ERR_IO;
103
104 struct ext2_inode *target_inode = NULL;
105 target_inode = ext2_inode_read(fs, inode_idx: ctx.inode_num, out_ent: &ent);
106
107 if (!ent || !target_inode)
108 return ERR_IO;
109
110 if (target_inode->links_count == 0) {
111 bcache_ent_release(ent);
112 return ERR_FS_NO_INODE;
113 }
114
115 unlink_target_update(target_inode);
116 bcache_ent_release(ent);
117
118 /* This will re-acquire our locks */
119 if (target_inode->links_count == 0 && free_blocks)
120 unlink_free_blocks(fs, target_inode, inode_num: ctx.inode_num);
121
122 if (!ext2_inode_write(fs, inode_num: ctx.inode_num, inode: target_inode))
123 return ERR_IO;
124
125 if (decrement_links)
126 dir_inode->node.links_count--;
127
128 if (!ext2_inode_write(fs, inode_num: dir_inode->inode_num, inode: &dir_inode->node))
129 return ERR_IO;
130
131 return ERR_OK;
132}
133