MOS Source Code
Loading...
Searching...
No Matches
dentry.cpp
Go to the documentation of this file.
1// SPDX-License-Identifier: GPL-3.0-or-later
2
4
5#include "mos/assert.hpp"
11#include "mos/io/io.hpp"
13#include "mos/misc/kutils.hpp"
14#include "mos/tasks/process.hpp"
16
17#include <atomic>
20#include <mos_stdio.hpp>
21#include <mos_stdlib.hpp>
22#include <mos_string.hpp>
23
24// A path in its string form is composed of "segments" separated
25// by a slash "/", a path may:
26//
27// - begin with a slash, indicating that's an absolute path
28// - begin without a slash, indicating that's a relative path
29// (relative to the current working directory (AT_FDCWD))
30//
31// A path may end with a slash, indicating that the caller expects
32// the path to be a directory
33
34// The two functions below have circular dependencies, so we need to forward declare them
35// Both of them return a referenced dentry, no need to refcount them again
36static PtrResult<dentry_t> dentry_resolve_lastseg(dentry_t *parent, mos::string leaf, const LastSegmentResolveFlags flags, bool *is_symlink);
37static PtrResult<dentry_t> dentry_resolve_follow_symlink(dentry_t *dentry, LastSegmentResolveFlags flags);
38
48static std::pair<PtrResult<dentry_t>, std::optional<mos::string>> dentry_resolve_to_parent(dentry_t *base_dir, dentry_t *root_dir, mos::string_view path)
49{
50 dInfo2<dcache> << "lookup parent of '" << path << "'";
51 MOS_ASSERT_X(base_dir && root_dir, "Invalid VFS lookup parameters");
52
53 dentry_t *parent_ref = [&]()
54 {
55 dentry_t *tmp = path_is_absolute(path) ? root_dir : base_dir;
56 if (tmp->is_mountpoint)
57 tmp = dentry_get_mount(tmp)->root; // if it's a mountpoint, jump to mounted filesystem
58 return dentry_ref_up_to(tmp, root_dir);
59 }();
60
61 const auto parts = split_string(path, PATH_DELIM);
62 if (unlikely(parts.empty()))
63 {
64 // this only happens if the path is empty, or contains only slashes
65 // in which case we return the base directory
66 return { parent_ref, std::nullopt };
67 }
68
69 for (size_t i = 0; i < parts.size(); i++)
70 {
71 const bool is_last = i == parts.size() - 1;
72 const auto current_seg = parts[i];
73
74 dInfo2<dcache> << "lookup parent: current segment '" << current_seg << "'" << (is_last ? " (last)" : "");
75
76 if (is_last)
77 {
78 const bool ends_with_slash = path.ends_with(PATH_DELIM);
79 return { parent_ref, current_seg + (ends_with_slash ? PATH_DELIM_STR : "") };
80 }
81
82 if (current_seg == "." || current_seg == "./")
83 continue;
84
85 if (current_seg == ".." || current_seg == "../")
86 {
87 // we can't go above the root directory
88 if (parent_ref != root_dir)
89 {
90 dentry_t *const parent = dentry_parent(*parent_ref);
91
92 // don't recurse up to the root
94 parent_ref = parent;
95
96 // if the parent is a mountpoint, we need to jump to the mountpoint's parent
97 // and then jump to the mountpoint's parent's parent
98 // already referenced when we jumped to the mountpoint
99 if (parent_ref->is_mountpoint)
100 parent_ref = dentry_root_get_mountpoint(parent);
101 }
102 }
103 else
104 {
105 auto child_ref = dentry_lookup_child(parent_ref, current_seg);
106 if (child_ref->inode == NULL)
107 {
108 // kfree(path);
109 dentry_try_release(child_ref.get());
110 dentry_unref(parent_ref);
111 return { -ENOENT, std::nullopt };
112 }
113
114 if (child_ref->is_mountpoint)
115 {
116 dInfo2<dcache> << "jumping to mountpoint " << child_ref->name;
117 parent_ref = dentry_get_mount(child_ref.get())->root; // if it's a mountpoint, jump to the tree of mounted filesystem instead
118
119 // refcount the mounted filesystem root
120 dentry_ref(parent_ref);
121 }
122 else
123 {
124 parent_ref = child_ref.get();
125 }
126 }
127
128 if (parent_ref->inode->type == FILE_TYPE_SYMLINK)
129 {
130 // go to the real interesting dir (if it's a symlink)
131 auto parent_real_ref = dentry_resolve_follow_symlink(parent_ref, RESOLVE_EXPECT_EXIST | RESOLVE_EXPECT_DIR);
132 dentry_unref(parent_ref);
133 if (parent_real_ref.isErr())
134 return { -ENOENT, std::nullopt }; // the symlink target does not exist
135 parent_ref = parent_real_ref.get();
136 }
137 }
138
140}
141
142static PtrResult<dentry_t> dentry_resolve_follow_symlink(dentry_t *d, LastSegmentResolveFlags flags)
143{
144 MOS_ASSERT_X(d != NULL && d->inode != NULL, "check before calling this function!");
145 MOS_ASSERT_X(d->inode->type == FILE_TYPE_SYMLINK, "check before calling this function!");
146
147 if (!d->inode->ops || !d->inode->ops->readlink)
148 mos_panic("inode does not support readlink (symlink) operation, but it's a symlink!");
149
150 const auto target = (char *) kcalloc<char>(MOS_PATH_MAX_LENGTH);
151 const size_t read = d->inode->ops->readlink(d, target, MOS_PATH_MAX_LENGTH);
152 if (read == 0)
153 {
154 mos_warn("symlink is empty");
155 return -ENOENT; // symlink is empty
156 }
157
158 if (read == MOS_PATH_MAX_LENGTH)
159 {
160 mos_warn("symlink is too long");
161 return -ENAMETOOLONG; // symlink is too long
162 }
163
164 target[read] = '\0'; // ensure null termination
165
166 dInfo2<dcache> << "symlink target: " << target;
167
168 auto [parent_ref, last_segment] = dentry_resolve_to_parent(dentry_parent(*d), root_dentry, target);
169 kfree(target);
170 if (parent_ref.isErr())
171 return parent_ref; // the symlink target does not exist
172
173 // it's possibly that the symlink target is also a symlink, this will be handled recursively
174 bool is_symlink = false;
175 const auto child_ref = dentry_resolve_lastseg(parent_ref.get(), *last_segment, flags, &is_symlink);
176
177 // if symlink is true, we need to unref the parent_ref dentry as it's irrelevant now
178 if (child_ref.isErr() || is_symlink)
179 dentry_unref(parent_ref.get());
180
181 return child_ref; // the real dentry, or an error code
182}
183
184static PtrResult<dentry_t> dentry_resolve_lastseg(dentry_t *parent, mos::string leaf, const LastSegmentResolveFlags flags, bool *is_symlink)
185{
186 MOS_ASSERT(parent != NULL);
187 *is_symlink = false;
188
189 dInfo2<dcache> << "resolving last segment: '" << leaf << "'";
190 const bool ends_with_slash = leaf.ends_with(PATH_DELIM);
191 if (ends_with_slash)
192 leaf.resize(leaf.size() - 1); // remove the trailing slash
193
194 if (unlikely(ends_with_slash && !flags.test(RESOLVE_EXPECT_DIR)))
195 {
196 mos_warn("RESOLVE_EXPECT_DIR isn't set, but the provided path ends with a slash");
197 return -EINVAL;
198 }
199
200 if (leaf == "." || leaf == "./")
201 return parent;
202 else if (leaf == ".." || leaf == "../")
203 {
204 if (parent == root_dentry)
205 return parent;
206
207 dentry_t *const parent_parent = dentry_parent(*parent);
208 MOS_ASSERT(dentry_unref_one_norelease(parent)); // don't recursively unref all the way to the root
209
210 // if the parent is a mountpoint, we need to jump to the mountpoint's parent
211 if (parent_parent->is_mountpoint)
212 return dentry_root_get_mountpoint(parent_parent);
213
214 return parent_parent;
215 }
216
217 auto child_ref = dentry_lookup_child(parent, leaf); // now we have a reference to the child
218
219 if (unlikely(child_ref->inode == NULL))
220 {
221 if (flags.test(RESOLVE_EXPECT_NONEXIST))
222 {
223 // do not use dentry_ref, because it checks for an inode
224 child_ref->refcount++;
225 return child_ref;
226 }
227
228 dInfo2<dcache> << "file does not exist";
229 dentry_try_release(child_ref.get()); // child has no ref, we should release it directly
230 return -ENOENT;
231 }
232
233 MOS_ASSERT(child_ref->refcount > 0); // dentry_get_child may return a negative dentry, which is handled above, otherwise we should have a reference on it
234
235 if (flags.test(RESOLVE_EXPECT_NONEXIST) && !flags.test(RESOLVE_EXPECT_EXIST))
236 {
237 dentry_unref(child_ref.get());
238 return -EEXIST;
239 }
240
241 if (child_ref->inode->type == FILE_TYPE_SYMLINK)
242 {
243 if (!flags.test(RESOLVE_SYMLINK_NOFOLLOW))
244 {
245 dInfo2<dcache> << "resolving symlink for '" << leaf << "'";
246 const auto symlink_target_ref = dentry_resolve_follow_symlink(child_ref.get(), flags);
247 // we don't need the symlink node anymore
248 MOS_ASSERT(dentry_unref_one_norelease(child_ref.get()));
249 *is_symlink = symlink_target_ref != nullptr;
250 return symlink_target_ref;
251 }
252
253 dInfo2<dcache> << "not following symlink";
254 }
255 else if (child_ref->inode->type == FILE_TYPE_DIRECTORY)
256 {
257 if (!flags.test(RESOLVE_EXPECT_DIR))
258 {
259 MOS_ASSERT(dentry_unref_one_norelease(child_ref.get())); // it's the caller's responsibility to unref the parent and grandparents
260 return -EISDIR;
261 }
262
263 // if the child is a mountpoint, we need to jump to the mounted filesystem's root
264 if (child_ref->is_mountpoint)
265 return dentry_ref(dentry_get_mount(child_ref.get())->root);
266 }
267 else
268 {
269 if (!flags.test(RESOLVE_EXPECT_FILE))
270 {
271 MOS_ASSERT(dentry_unref_one_norelease(child_ref.get())); // it's the caller's responsibility to unref the parent and grandparents
272 return -ENOTDIR;
273 }
274 }
275
276 return child_ref;
277}
278
280{
281 MOS_ASSERT_X(d->inode == NULL, "reattaching an inode to a dentry");
282 MOS_ASSERT(inode != NULL);
283 // MOS_ASSERT_X(d->refcount == 1, "dentry %p refcount %zu is not 1", (void *) d, d->refcount);
284
285 for (std::atomic_size_t i = 0; i < d->refcount; i++)
286 inode_ref(inode); // refcount the inode for each reference to the dentry
287
288 inode_ref(inode); // refcount the inode for each reference to the dentry
289 d->inode = inode;
290}
291
293{
294 if (d->inode == NULL)
295 return;
296
297 // the caller should have the only reference to the dentry
298 // MOS_ASSERT(d->refcount == 1); // !! TODO: this assertion fails in vfs_unlinkat
299
300 (void) inode_unref(d->inode); // we don't care if the inode is freed or not
301 d->inode = NULL;
302}
303
305{
306 if (fd == AT_FDCWD)
307 {
308 if (current_thread)
309 return current_process->working_directory;
310 else
311 return root_dentry; // no current process, so cwd is always root
312 }
313
314 // sanity check: fd != AT_FDCWD, no current process
316
318 if (io == NULL)
319 return -EBADF;
320
321 if (io->io_type != IO_FILE && io->io_type != IO_DIR)
322 return -EBADF;
323
324 FsBaseFile *file = static_cast<FsBaseFile *>(io);
325 return file->dentry;
326}
327
329{
330 if (unlikely(parent == nullptr))
331 return nullptr;
332
333 dInfo2<dcache> << "looking for dentry '" << name.data() << "' in '" << dentry_name(parent) << "'";
334
335 // firstly check if it's in the cache
336 dentry_t *dentry = dentry_get_from_parent(parent->superblock, parent, name);
337 MOS_ASSERT(dentry);
338
339 spinlock_acquire(&dentry->lock);
340
341 if (dentry->inode)
342 {
343 dInfo2<dcache> << "dentry '" << name.data() << "' found in the cache";
344 spinlock_release(&dentry->lock);
345 return dentry_ref(dentry);
346 }
347
348 // not in the cache, try to find it in the filesystem
349 if (parent->inode == NULL || parent->inode->ops == NULL || parent->inode->ops->lookup == NULL)
350 {
351 dInfo2<dcache> << "filesystem doesn't support lookup";
352 spinlock_release(&dentry->lock);
353 return dentry;
354 }
355
356 const bool lookup_result = parent->inode->ops->lookup(parent->inode, dentry);
357 spinlock_release(&dentry->lock);
358
359 if (lookup_result)
360 {
361 dInfo2<dcache> << "dentry '" << name.data() << "' found in the filesystem";
362 return dentry_ref(dentry);
363 }
364 else
365 {
366 dInfo2<dcache> << "dentry '" << name.data() << "' not found in the filesystem";
367 return dentry; // do not reference a negative dentry
368 }
369}
370
371PtrResult<dentry_t> dentry_resolve(dentry_t *starting_dir, dentry_t *root_dir, mos::string_view path, LastSegmentResolveFlags flags)
372{
373 if (!root_dir)
374 return -ENOENT; // no root directory
375
376 dInfo2<dcache> << "resolving path '" << path << "'";
377 const auto [parent_ref, last_segment] = dentry_resolve_to_parent(starting_dir, root_dir, path);
378 if (parent_ref.isErr())
379 {
380 dInfo2<dcache> << "failed to resolve parent of '" << path << "', file not found";
381 return parent_ref;
382 }
383
384 if (!last_segment)
385 {
386 // path is a single "/", last_segment is empty
387 dInfo2<dcache> << "path '" << path << "' is a single '/' or is empty";
388 MOS_ASSERT(parent_ref == starting_dir);
389 if (!flags.test(RESOLVE_EXPECT_DIR))
390 {
391 dentry_unref(parent_ref.get());
392 return -EISDIR;
393 }
394
395 return parent_ref;
396 }
397
398 bool symlink = false;
399 auto child_ref = dentry_resolve_lastseg(parent_ref.get(), *last_segment, flags, &symlink);
400 if (child_ref.isErr() || symlink)
401 dentry_unref(parent_ref.get()); // the lookup failed, or child_ref is irrelevant with the parent_ref
402 return child_ref;
403}
404
406{
409 entry->ino = ino;
410 entry->name = name;
411 entry->type = type;
412 list_node_append(&state->entries, list_node(entry));
413 state->n_count++;
414}
415
417{
418 // this call may not write all the entries, because the buffer may not be big enough
419 if (dir->inode->ops && dir->inode->ops->iterate_dir)
420 dir->inode->ops->iterate_dir(dir, state, dirter_add);
421 else
423}
#define MOS_ASSERT_X(cond, msg,...)
Definition assert.hpp:12
#define MOS_ASSERT(cond)
Definition assert.hpp:19
#define MOS_UNREACHABLE()
Definition assert.hpp:10
#define mos_warn(fmt,...)
Definition assert.hpp:22
#define MOS_PATH_MAX_LENGTH
Definition autoconf.h:27
bool ends_with(CharT c) const
size_t size() const
Definition string.hpp:274
void resize(size_t new_length)
Definition string.hpp:163
bool ends_with(Char c) const
Definition string.hpp:188
static PtrResult< dentry_t > dentry_resolve_follow_symlink(dentry_t *dentry, LastSegmentResolveFlags flags)
Definition dentry.cpp:142
static PtrResult< dentry_t > dentry_resolve_lastseg(dentry_t *parent, mos::string leaf, const LastSegmentResolveFlags flags, bool *is_symlink)
Definition dentry.cpp:184
static void dirter_add(vfs_listdir_state_t *state, u64 ino, mos::string_view name, file_type_t type)
Definition dentry.cpp:405
static std::pair< PtrResult< dentry_t >, std::optional< mos::string > > dentry_resolve_to_parent(dentry_t *base_dir, dentry_t *root_dir, mos::string_view path)
Lookup the parent directory of a given path, and return the last segment of the path in last_seg_out.
Definition dentry.cpp:48
#define PATH_DELIM
Definition fs_types.h:10
#define PATH_DELIM_STR
Definition fs_types.h:11
file_type_t
Definition fs_types.h:14
@ FILE_TYPE_SYMLINK
Definition fs_types.h:17
@ FILE_TYPE_DIRECTORY
Definition fs_types.h:16
void dentry_attach(dentry_t *d, inode_t *inode)
Attach an inode to a dentry.
Definition dentry.cpp:279
PtrResult< dentry_t > dentry_lookup_child(dentry_t *parent, mos::string_view name)
Get a child dentry from a parent dentry.
Definition dentry.cpp:328
dentry_t * dentry_ref_up_to(dentry_t *dentry, dentry_t *root)
Increment the reference count of a dentry up to a given dentry.
PtrResult< dentry_t > dentry_from_fd(fd_t fd)
Get the dentry from a file descriptor.
Definition dentry.cpp:304
void dentry_detach(dentry_t *d)
Detach the inode from a dentry.
Definition dentry.cpp:292
PtrResult< dentry_t > dentry_resolve(dentry_t *starting_dir, dentry_t *root_dir, mos::string_view path, LastSegmentResolveFlags flags)
Lookup a path in the filesystem.
Definition dentry.cpp:371
void dentry_try_release(dentry_t *dentry)
should_inline dentry_t * dentry_parent(const dentry_t &dentry)
Definition dentry.hpp:67
void vfs_populate_listdir_buf(dentry_t *dir, vfs_listdir_state_t *state)
List the contents of a directory.
Definition dentry.cpp:416
dentry_t * dentry_ref(dentry_t *dentry)
Increment the reference count of a dentry.
should_inline bool path_is_absolute(mos::string_view path)
Check if a path is absolute.
Definition dentry.hpp:62
void dentry_unref(dentry_t *dentry)
Decrement the reference count of a dentry.
__nodiscard bool dentry_unref_one_norelease(dentry_t *dentry)
Decrease the refcount of ONE SINGLE dentry, including (if it's a mountpoint) the mountpoint dentry.
@ RESOLVE_EXPECT_EXIST
Definition dentry.hpp:49
@ RESOLVE_SYMLINK_NOFOLLOW
Definition dentry.hpp:46
@ RESOLVE_EXPECT_DIR
Definition dentry.hpp:41
@ RESOLVE_EXPECT_NONEXIST
Definition dentry.hpp:50
@ RESOLVE_EXPECT_FILE
Definition dentry.hpp:40
MOSAPI void(1, 2) fatal_abort(const char *fmt
MOSAPI void linked_list_init(list_node_t *head_node)
Initialise a circular double linked list.
Definition list.cpp:15
MOSAPI void list_node_append(list_node_t *head, list_node_t *item)
Definition list.cpp:68
#define list_node(element)
Get the ‘list_node’ of a list element. This is exactly the reverse of ‘list_entry’ above.
Definition list.hpp:74
dentry_t * root_dentry
Definition vfs.cpp:35
void inode_ref(inode_t *inode)
Definition inode.cpp:66
bool inode_unref(inode_t *inode)
Definition inode.cpp:72
@ IO_FILE
Definition io.hpp:20
@ IO_DIR
Definition io.hpp:21
mos::vector< mos::string > split_string(mos::string_view str, char delim)
Definition kutils.cpp:47
#define unlikely(x)
Definition mos_global.h:40
dentry_t * dentry_root_get_mountpoint(const dentry_t *dentry)
Given a mounted root dentry, return the mountpoint dentry that points to it.
Definition mount.cpp:22
ptr< mount_t > dentry_get_mount(const dentry_t *dentry)
Definition mount.cpp:50
basic_string_view< char > string_view
T * create(Args &&...args)
Definition allocator.hpp:12
mos::basic_string< char > string
Definition string.hpp:395
#define mos_panic(fmt,...)
Definition panic.hpp:51
#define NULL
Definition pb_syshdr.h:46
#define current_thread
Definition platform.hpp:33
#define current_process
Definition platform.hpp:34
IO * process_get_fd(Process *process, fd_t fd)
Definition process.cpp:198
const char * name
Definition slab.cpp:33
#define spinlock_acquire(lock)
Definition spinlock.hpp:64
#define spinlock_release(lock)
Definition spinlock.hpp:65
dentry_t *const dentry
Definition io.hpp:39
const io_type_t io_type
Definition io.hpp:41
superblock_t * superblock
bool is_mountpoint
spinlock_t lock
atomic_t refcount
inode_t * inode
size_t(* readlink)(dentry_t *dentry, char *buffer, size_t buflen)
read the contents of a symbolic link
Definition vfs_types.hpp:81
bool(* lookup)(inode_t *dir, dentry_t *dentry)
lookup a file in a directory, if it's unset for a directory, the VFS will use the default lookup
Definition vfs_types.hpp:73
void(* iterate_dir)(dentry_t *dentry, vfs_listdir_state_t *iterator_state, dentry_iterator_op op)
iterate over the contents of a directory
Definition vfs_types.hpp:71
const inode_ops_t * ops
file_type_t type
Definition vfs_types.hpp:50
mos::string name
Definition vfs_types.hpp:53
file_type_t type
Definition vfs_types.hpp:54
ino_t ino
Definition vfs_types.hpp:52
size_t n_count
number of entries in the list
Definition vfs_types.hpp:60
constexpr auto dInfo2
Definition syslog.hpp:151
s32 fd_t
Definition types.h:77
unsigned long long u64
Definition types.h:19
mos::string dentry_name(const dentry_t *dentry)
void vfs_generic_iterate_dir(const dentry_t *dir, vfs_listdir_state_t *state, dentry_iterator_op add_record)
dentry_t * dentry_get_from_parent(superblock_t *sb, dentry_t *parent, mos::string_view name)
Create a new dentry with the given name and parent.
Definition vfs_utils.cpp:37