1// SPDX-License-Identifier: GPL-3.0-or-later
2
3#include "mos/filesystem/dentry.h"
4#include "mos/filesystem/inode.h"
5#include "mos/filesystem/mount.h"
6#include "mos/filesystem/vfs.h"
7#include "mos/filesystem/vfs_types.h"
8#include "mos/syslog/printk.h"
9
10#include <mos_stdio.h>
11#include <mos_stdlib.h>
12#include <mos_string.h>
13
14dentry_t *dentry_ref(dentry_t *dentry)
15{
16 MOS_ASSERT(dentry);
17 MOS_ASSERT(dentry->inode); // one cannot refcount a dentry without an inode
18 dentry->refcount++;
19 inode_ref(inode: dentry->inode);
20 pr_dinfo2(dcache_ref, "dentry %p '%s' increased refcount to %zu", (void *) dentry, dentry_name(dentry), dentry->refcount);
21 return dentry;
22}
23
24dentry_t *dentry_ref_up_to(dentry_t *dentry, dentry_t *root)
25{
26 pr_dinfo2(dcache_ref, "dentry_ref_up_to(%p '%s', %p '%s')", (void *) dentry, dentry_name(dentry), (void *) root, dentry_name(root));
27 for (dentry_t *cur = dentry; cur != root; cur = dentry_parent(dentry: cur))
28 {
29 dentry_ref(dentry: cur);
30 if (cur->name == NULL)
31 {
32 cur = dentry_root_get_mountpoint(dentry: cur);
33 dentry_ref(dentry: cur);
34 }
35 }
36
37 dentry_ref(dentry: root); // it wasn't refcounted in the loop
38
39 pr_dinfo2(dcache_ref, "...done");
40 return dentry;
41}
42
43/**
44 * @brief Decrease the refcount of ONE SINGLE dentry, including (if it's a mountpoint) the mountpoint dentry
45 *
46 * @param dentry The dentry to decrease the refcount of
47 * @return true if the refcount was decreased, false if the refcount was already 0
48 */
49__nodiscard bool dentry_unref_one_norelease(dentry_t *dentry)
50{
51 if (dentry == NULL)
52 return false;
53
54 if (dentry->refcount == 0)
55 {
56 mos_warn("dentry refcount is already 0");
57 return false;
58 }
59
60 dentry->refcount--;
61
62 if (dentry->inode)
63 {
64 if (inode_unref(inode: dentry->inode))
65 {
66 pr_dinfo2(vfs, "inode %p has no more references, releasing", (void *) dentry->inode);
67 dentry->inode = NULL;
68 }
69 }
70
71 pr_dinfo2(dcache_ref, "dentry %p '%s' decreased refcount to %zu", (void *) dentry, dentry_name(dentry), dentry->refcount);
72
73 if (dentry->name == NULL && dentry != root_dentry)
74 {
75 dentry_t *mountpoint = dentry_root_get_mountpoint(dentry);
76 if (!mountpoint)
77 goto done;
78 mountpoint->refcount--;
79 pr_dinfo2(dcache_ref, " mountpoint %p '%s' decreased mountpoint refcount to %zu", (void *) mountpoint, dentry_name(mountpoint), mountpoint->refcount);
80 }
81done:
82 return true;
83}
84
85void dentry_dump_refstat(const dentry_t *dentry, dump_refstat_receiver_t *receiver, void *receiver_data)
86{
87 if (dentry == NULL)
88 return;
89 static int depth = 0;
90
91 receiver(depth, dentry, false, receiver_data);
92
93 if (dentry->is_mountpoint)
94 {
95 dentry = dentry_get_mount(dentry)->root;
96 receiver(depth, dentry, true, receiver_data);
97 }
98
99 depth++;
100 tree_foreach_child(dentry_t, child, dentry)
101 {
102 dentry_dump_refstat(dentry: child, receiver, receiver_data);
103 }
104 depth--;
105}
106
107void dentry_check_refstat(const dentry_t *dentry)
108{
109 size_t expected_refcount = 0;
110
111 if (dentry != root_dentry)
112 {
113 if (dentry->is_mountpoint)
114 expected_refcount++; // the mountpoint itself
115
116 if (dentry->name == NULL)
117 expected_refcount++; // the mounted root dentry
118 }
119 else
120 {
121 expected_refcount++; // the root dentry should only has one reference
122 }
123
124 tree_foreach_child(dentry_t, child, dentry)
125 {
126 expected_refcount += child->refcount;
127 }
128
129 if (dentry->refcount < expected_refcount)
130 {
131 mos_warn("dentry %p refcount %zu is less than expected refcount %zu", (void *) dentry, dentry->refcount, expected_refcount);
132 tree_foreach_child(dentry_t, child, dentry)
133 {
134 pr_warn(" child %p '%s' has %zu references", (void *) child, dentry_name(child), child->refcount);
135 }
136 mos_panic("don't know how to handle this");
137 }
138 else if (dentry->refcount - expected_refcount)
139 {
140 pr_dinfo2(dcache_ref, " dentry %p '%s' has %zu direct references", (void *) dentry, dentry_name(dentry), dentry->refcount - expected_refcount);
141 }
142}
143
144void dentry_try_release(dentry_t *dentry)
145{
146 MOS_ASSERT(dentry->refcount == 0);
147
148 const bool can_release = dentry->inode == NULL && list_is_empty(head: &tree_node(dentry)->children);
149 if (can_release)
150 {
151 list_remove(&dentry->tree_node);
152 if (dentry->name)
153 kfree(ptr: dentry->name);
154 kfree(ptr: dentry);
155 }
156}
157
158void dentry_unref(dentry_t *dentry)
159{
160 if (!dentry_unref_one_norelease(dentry))
161 return;
162
163 dentry_check_refstat(dentry);
164 dentry_unref(dentry: dentry_parent(dentry));
165
166 if (dentry->refcount == 0)
167 dentry_try_release(dentry);
168}
169
170ssize_t dentry_path(dentry_t *dentry, dentry_t *root, char *buf, size_t size)
171{
172 if (dentry == NULL)
173 return 0;
174
175 if (size < 2)
176 return -1;
177
178 if (dentry == root)
179 {
180 buf[0] = '/';
181 buf[1] = '\0';
182 return 1;
183 }
184
185 if (dentry->name == NULL)
186 dentry = dentry_root_get_mountpoint(dentry);
187
188 char *path = strdup(src: dentry->name);
189
190 for (dentry_t *current = dentry_parent(dentry); current != root; current = dentry_parent(dentry: current))
191 {
192 if (current->name == NULL)
193 current = dentry_root_get_mountpoint(dentry: current);
194
195 if (current == NULL)
196 {
197 // root for other fs trees
198 char *newpath = kmalloc(strlen(path) + 3);
199 strcpy(dest: newpath, src: ":/");
200 strcat(dest: newpath, src: path);
201 kfree(ptr: path);
202 path = newpath;
203
204 if (strlen(str: path) + 1 > size)
205 return -1;
206
207 const size_t real_size = snprintf(str: buf, size, format: "%s", path);
208 kfree(ptr: path);
209 return real_size;
210 }
211 else
212 {
213 char *newpath = kmalloc(strlen(current->name) + 1 + strlen(path) + 1);
214 strcpy(dest: newpath, src: current->name);
215 strcat(dest: newpath, src: "/");
216 strcat(dest: newpath, src: path);
217 kfree(ptr: path);
218 path = newpath;
219 }
220 }
221
222 if (strlen(str: path) + 1 > size)
223 return -1;
224
225 const size_t real_size = snprintf(str: buf, size, format: "/%s", path);
226 kfree(ptr: path);
227
228 return real_size;
229}
230