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