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/syslog/printk.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, char *leaf, lastseg_resolve_flags_t flags, bool *symlink_resolved);
38
48static PtrResult<dentry_t> dentry_resolve_to_parent(dentry_t *base_dir, dentry_t *root_dir, const char *original_path, char **last_seg_out)
49{
50 pr_dinfo2(dcache, "lookup parent of '%s'", original_path);
51 MOS_ASSERT_X(base_dir && root_dir && original_path, "Invalid VFS lookup parameters");
52 if (last_seg_out != NULL)
53 *last_seg_out = NULL;
54
55 dentry_t *parent_ref = [&]()
56 {
57 dentry_t *tmp = path_is_absolute(original_path) ? root_dir : base_dir;
58 if (tmp->is_mountpoint)
59 tmp = dentry_get_mount(tmp)->root; // if it's a mountpoint, jump to mounted filesystem
60 return dentry_ref_up_to(tmp, root_dir);
61 }();
62
63 char *saveptr = NULL;
64 char *path = strdup(original_path);
65 const char *current_seg = strtok_r(path, PATH_DELIM_STR, &saveptr);
66 if (unlikely(current_seg == NULL))
67 {
68 // this only happens if the path is empty, or contains only slashes
69 // in which case we return the base directory
70 kfree(path);
71 if (last_seg_out != NULL)
72 *last_seg_out = NULL;
73 return parent_ref;
74 }
75
76 while (true)
77 {
78 pr_dinfo2(dcache, "lookup parent: current segment '%s'", current_seg);
79 const char *const next = strtok_r(NULL, PATH_DELIM_STR, &saveptr);
80 if (parent_ref->inode->type == FILE_TYPE_SYMLINK)
81 {
82 // this is the real interesting dir
83 auto parent_real_ref = dentry_resolve_follow_symlink(parent_ref, RESOLVE_EXPECT_EXIST | RESOLVE_EXPECT_DIR);
84 dentry_unref(parent_ref);
85 if (parent_real_ref.isErr())
86 return -ENOENT; // the symlink target does not exist
87 parent_ref = parent_real_ref.get();
88 }
89
90 if (next == NULL)
91 {
92 // "current_seg" is the last segment of the path
93 if (last_seg_out != NULL)
94 {
95 const bool ends_with_slash = original_path[strlen(original_path) - 1] == PATH_DELIM;
96 char *tmp = kcalloc<char>(strlen(current_seg) + 2); // +2 for the null terminator and the slash
97 strcpy(tmp, current_seg);
98 if (ends_with_slash)
100 *last_seg_out = tmp;
101 }
102
103 kfree(path);
104 return parent_ref;
105 }
106
107 if (strncmp(current_seg, ".", 2) == 0 || strcmp(current_seg, "./") == 0)
108 {
109 current_seg = next;
110 continue;
111 }
112
113 if (strncmp(current_seg, "..", 3) == 0 || strcmp(current_seg, "../") == 0)
114 {
115 if (parent_ref == root_dir)
116 {
117 // we can't go above the root directory
118 current_seg = next;
119 continue;
120 }
121
122 dentry_t *parent = dentry_parent(*parent_ref);
123
124 // don't recurse up to the root
126 parent_ref = parent;
127
128 // if the parent is a mountpoint, we need to jump to the mountpoint's parent
129 // and then jump to the mountpoint's parent's parent
130 // already referenced when we jumped to the mountpoint
131 if (parent_ref->is_mountpoint)
132 parent_ref = dentry_root_get_mountpoint(parent);
133
134 current_seg = next;
135 continue;
136 }
137
138 auto child_ref = dentry_lookup_child(parent_ref, current_seg);
139 if (child_ref->inode == NULL)
140 {
141 *last_seg_out = NULL;
142 kfree(path);
143 dentry_try_release(child_ref.get());
144 dentry_unref(parent_ref);
145 return -ENOENT;
146 }
147
148 if (child_ref->is_mountpoint)
149 {
150 pr_dinfo2(dcache, "jumping to mountpoint %s", child_ref->name.c_str());
151 parent_ref = dentry_get_mount(child_ref.get())->root; // if it's a mountpoint, jump to the tree of mounted filesystem instead
152
153 // refcount the mounted filesystem root
154 dentry_ref(parent_ref);
155 }
156 else
157 {
158 parent_ref = child_ref.get();
159 }
160
161 current_seg = next;
162 }
163
165}
166
168{
169 MOS_ASSERT_X(d != NULL && d->inode != NULL, "check before calling this function!");
170 MOS_ASSERT_X(d->inode->type == FILE_TYPE_SYMLINK, "check before calling this function!");
171
172 if (!d->inode->ops || !d->inode->ops->readlink)
173 mos_panic("inode does not support readlink (symlink) operation, but it's a symlink!");
174
175 const auto target = (char *) kcalloc<char>(MOS_PATH_MAX_LENGTH);
176 const size_t read = d->inode->ops->readlink(d, target, MOS_PATH_MAX_LENGTH);
177 if (read == 0)
178 {
179 mos_warn("symlink is empty");
180 return -ENOENT; // symlink is empty
181 }
182
183 if (read == MOS_PATH_MAX_LENGTH)
184 {
185 mos_warn("symlink is too long");
186 return -ENAMETOOLONG; // symlink is too long
187 }
188
189 target[read] = '\0'; // ensure null termination
190
191 pr_dinfo2(dcache, "symlink target: %s", target);
192
193 char *last_segment = NULL;
194 auto parent_ref = dentry_resolve_to_parent(dentry_parent(*d), root_dentry, target, &last_segment);
195 kfree(target);
196 if (parent_ref.isErr())
197 return parent_ref; // the symlink target does not exist
198
199 // it's possibly that the symlink target is also a symlink, this will be handled recursively
200 bool is_symlink = false;
201 const auto child_ref = dentry_resolve_lastseg(parent_ref.get(), last_segment, flags, &is_symlink);
202 kfree(last_segment);
203
204 // if symlink is true, we need to unref the parent_ref dentry as it's irrelevant now
205 if (child_ref.isErr() || is_symlink)
206 dentry_unref(parent_ref.get());
207
208 return child_ref; // the real dentry, or an error code
209}
210
211static PtrResult<dentry_t> dentry_resolve_lastseg(dentry_t *parent, char *leaf, lastseg_resolve_flags_t flags, bool *is_symlink)
212{
213 MOS_ASSERT(parent != NULL && leaf != NULL);
214 *is_symlink = false;
215
216 pr_dinfo2(dcache, "resolving last segment: '%s'", leaf);
217 const bool ends_with_slash = leaf[strlen(leaf) - 1] == PATH_DELIM;
218 if (ends_with_slash)
219 leaf[strlen(leaf) - 1] = '\0'; // remove the trailing slash
220
221 if (unlikely(ends_with_slash && !(flags & RESOLVE_EXPECT_DIR)))
222 {
223 mos_warn("RESOLVE_EXPECT_DIR isn't set, but the provided path ends with a slash");
224 return -EINVAL;
225 }
226
227 if (strncmp(leaf, ".", 2) == 0 || strcmp(leaf, "./") == 0)
228 return parent;
229 else if (strncmp(leaf, "..", 3) == 0 || strcmp(leaf, "../") == 0)
230 {
231 if (parent == root_dentry)
232 return parent;
233
234 dentry_t *const parent_parent = dentry_parent(*parent);
235 MOS_ASSERT(dentry_unref_one_norelease(parent)); // don't recursively unref all the way to the root
236
237 // if the parent is a mountpoint, we need to jump to the mountpoint's parent
238 if (parent_parent->is_mountpoint)
239 return dentry_root_get_mountpoint(parent_parent);
240
241 return parent_parent;
242 }
243
244 auto child_ref = dentry_lookup_child(parent, leaf); // now we have a reference to the child
245
246 if (unlikely(child_ref->inode == NULL))
247 {
248 if (flags & RESOLVE_EXPECT_NONEXIST)
249 {
250 // do not use dentry_ref, because it checks for an inode
251 child_ref->refcount++;
252 return child_ref;
253 }
254
255 pr_dinfo2(dcache, "file does not exist");
256 dentry_try_release(child_ref.get()); // child has no ref, we should release it directly
257 return -ENOENT;
258 }
259
260 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
261
262 if (flags & RESOLVE_EXPECT_NONEXIST && !(flags & RESOLVE_EXPECT_EXIST))
263 {
264 dentry_unref(child_ref.get());
265 return -EEXIST;
266 }
267
268 if (child_ref->inode->type == FILE_TYPE_SYMLINK)
269 {
270 if (!(flags & RESOLVE_SYMLINK_NOFOLLOW))
271 {
272 pr_dinfo2(dcache, "resolving symlink for '%s'", leaf);
273 const auto symlink_target_ref = dentry_resolve_follow_symlink(child_ref.get(), flags);
274 // we don't need the symlink node anymore
275 MOS_ASSERT(dentry_unref_one_norelease(child_ref.get()));
276 *is_symlink = symlink_target_ref != nullptr;
277 return symlink_target_ref;
278 }
279
280 pr_dinfo2(dcache, "not following symlink");
281 }
282 else if (child_ref->inode->type == FILE_TYPE_DIRECTORY)
283 {
284 if (!(flags & RESOLVE_EXPECT_DIR))
285 {
286 MOS_ASSERT(dentry_unref_one_norelease(child_ref.get())); // it's the caller's responsibility to unref the parent and grandparents
287 return -EISDIR;
288 }
289
290 // if the child is a mountpoint, we need to jump to the mounted filesystem's root
291 if (child_ref->is_mountpoint)
292 return dentry_ref(dentry_get_mount(child_ref.get())->root);
293 }
294 else
295 {
296 if (!(flags & RESOLVE_EXPECT_FILE))
297 {
298 MOS_ASSERT(dentry_unref_one_norelease(child_ref.get())); // it's the caller's responsibility to unref the parent and grandparents
299 return -ENOTDIR;
300 }
301 }
302
303 return child_ref;
304}
305
307{
308 MOS_ASSERT_X(d->inode == NULL, "reattaching an inode to a dentry");
309 MOS_ASSERT(inode != NULL);
310 // MOS_ASSERT_X(d->refcount == 1, "dentry %p refcount %zu is not 1", (void *) d, d->refcount);
311
312 for (std::atomic_size_t i = 0; i < d->refcount; i++)
313 inode_ref(inode); // refcount the inode for each reference to the dentry
314
315 inode_ref(inode); // refcount the inode for each reference to the dentry
316 d->inode = inode;
317}
318
320{
321 if (d->inode == NULL)
322 return;
323
324 // the caller should have the only reference to the dentry
325 // MOS_ASSERT(d->refcount == 1); // !! TODO: this assertion fails in vfs_unlinkat
326
327 (void) inode_unref(d->inode); // we don't care if the inode is freed or not
328 d->inode = NULL;
329}
330
332{
333 if (fd == AT_FDCWD)
334 {
335 if (current_thread)
336 return current_process->working_directory;
337 else
338 return root_dentry; // no current process, so cwd is always root
339 }
340
341 // sanity check: fd != AT_FDCWD, no current process
343
345 if (io == NULL)
346 return -EBADF;
347
348 if (io->type != IO_FILE && io->type != IO_DIR)
349 return -EBADF;
350
351 file_t *file = container_of(io, file_t, io);
352 return file->dentry;
353}
354
356{
357 if (unlikely(parent == nullptr))
358 return nullptr;
359
360 pr_dinfo2(dcache, "looking for dentry '%s' in '%s'", name, dentry_name(parent).c_str());
361
362 // firstly check if it's in the cache
363 dentry_t *dentry = dentry_get_from_parent(parent->superblock, parent, name);
364 MOS_ASSERT(dentry);
365
366 spinlock_acquire(&dentry->lock);
367
368 if (dentry->inode)
369 {
370 pr_dinfo2(dcache, "dentry '%s' found in the cache", name);
371 spinlock_release(&dentry->lock);
372 return dentry_ref(dentry);
373 }
374
375 // not in the cache, try to find it in the filesystem
376 if (parent->inode == NULL || parent->inode->ops == NULL || parent->inode->ops->lookup == NULL)
377 {
378 pr_dinfo2(dcache, "filesystem doesn't support lookup");
379 spinlock_release(&dentry->lock);
380 return dentry;
381 }
382
383 const bool lookup_result = parent->inode->ops->lookup(parent->inode, dentry);
384 spinlock_release(&dentry->lock);
385
386 if (lookup_result)
387 {
388 pr_dinfo2(dcache, "dentry '%s' found in the filesystem", name);
389 return dentry_ref(dentry);
390 }
391 else
392 {
393 pr_dinfo2(dcache, "dentry '%s' not found in the filesystem", name);
394 return dentry; // do not reference a negative dentry
395 }
396}
397
398PtrResult<dentry_t> dentry_resolve(dentry_t *starting_dir, dentry_t *root_dir, const char *path, lastseg_resolve_flags_t flags)
399{
400 if (!root_dir)
401 return -ENOENT; // no root directory
402
403 char *last_segment;
404 pr_dinfo2(dcache, "resolving path '%s'", path);
405 auto parent_ref = dentry_resolve_to_parent(starting_dir, root_dir, path, &last_segment);
406 if (parent_ref.isErr())
407 {
408 pr_dinfo2(dcache, "failed to resolve parent of '%s', file not found", path);
409 return parent_ref;
410 }
411
412 if (last_segment == NULL)
413 {
414 // path is a single "/"
415 pr_dinfo2(dcache, "path '%s' is a single '/' or is empty", path);
416 MOS_ASSERT(parent_ref == starting_dir);
417 if (!(flags & RESOLVE_EXPECT_DIR))
418 {
419 dentry_unref(parent_ref.get());
420 return -EISDIR;
421 }
422
423 return parent_ref;
424 }
425
426 bool symlink = false;
427 auto child_ref = dentry_resolve_lastseg(parent_ref.get(), last_segment, flags, &symlink);
428 kfree(last_segment);
429 if (child_ref.isErr() || symlink)
430 dentry_unref(parent_ref.get()); // the lookup failed, or child_ref is irrelevant with the parent_ref
431
432 return child_ref;
433}
434
436{
439 entry->ino = ino;
440 entry->name = name;
441 entry->type = type;
442 list_node_append(&state->entries, list_node(entry));
443 state->n_count++;
444}
445
447{
448 // this call may not write all the entries, because the buffer may not be big enough
449 if (dir->inode->ops && dir->inode->ops->iterate_dir)
450 dir->inode->ops->iterate_dir(dir, state, dirter_add);
451 else
453}
#define MOS_ASSERT_X(cond, msg,...)
Definition assert.hpp:15
#define MOS_ASSERT(cond)
Definition assert.hpp:14
#define MOS_UNREACHABLE()
Definition assert.hpp:11
#define mos_warn(fmt,...)
Definition assert.hpp:23
#define MOS_PATH_MAX_LENGTH
Definition autoconf.h:27
static PtrResult< dentry_t > dentry_resolve_to_parent(dentry_t *base_dir, dentry_t *root_dir, const char *original_path, char **last_seg_out)
Lookup the parent directory of a given path, and return the last segment of the path in last_seg_out.
Definition dentry.cpp:48
static PtrResult< dentry_t > dentry_resolve_follow_symlink(dentry_t *dentry, lastseg_resolve_flags_t flags)
Definition dentry.cpp:167
static PtrResult< dentry_t > dentry_resolve_lastseg(dentry_t *parent, char *leaf, lastseg_resolve_flags_t flags, bool *symlink_resolved)
Definition dentry.cpp:211
static void dirter_add(vfs_listdir_state_t *state, u64 ino, mos::string_view name, file_type_t type)
Definition dentry.cpp:435
#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:306
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:331
should_inline bool path_is_absolute(const char *path)
Check if a path is absolute.
Definition dentry.hpp:62
PtrResult< dentry_t > dentry_lookup_child(dentry_t *parent, const char *name)
Get a child dentry from a parent dentry.
Definition dentry.cpp:355
void dentry_detach(dentry_t *d)
Detach the inode from a dentry.
Definition dentry.cpp:319
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:446
dentry_t * dentry_ref(dentry_t *dentry)
Increment the reference count of a dentry.
lastseg_resolve_flags_t
Definition dentry.hpp:38
PtrResult< dentry_t > dentry_resolve(dentry_t *starting_dir, dentry_t *root_dir, const char *path, lastseg_resolve_flags_t flags)
Lookup a path in the filesystem.
Definition dentry.cpp:398
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 char * strcat(char *__restrict dest, const char *__restrict src)
MOSAPI char * strcpy(char *__restrict dest, const char *__restrict src)
MOSAPI char * strdup(const char *src)
MOSAPI s32 strncmp(const char *str1, const char *str2, size_t n)
MOSAPI char * strtok_r(char *str, const char *delim, char **saveptr)
MOSAPI s32 strcmp(const char *str1, const char *str2)
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:17
@ IO_DIR
Definition io.hpp:18
#define unlikely(x)
Definition mos_global.h:40
dentry_t * dentry_root_get_mountpoint(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:10
#define mos_panic(fmt,...)
Definition panic.hpp:51
#define NULL
Definition pb_syshdr.h:46
static size_t strlen(const char *s)
Definition pb_syshdr.h:80
#define current_thread
Definition platform.hpp:32
#define current_process
Definition platform.hpp:33
#define pr_dinfo2(feat, fmt,...)
Definition printk.hpp:27
io_t * process_get_fd(Process *process, fd_t fd)
Definition process.cpp:193
const char * name
Definition slab.cpp:35
#define spinlock_acquire(lock)
Definition spinlock.hpp:64
#define spinlock_release(lock)
Definition spinlock.hpp:65
superblock_t * superblock
bool is_mountpoint
spinlock_t lock
atomic_t refcount
inode_t * inode
dentry_t * dentry
size_t(* readlink)(dentry_t *dentry, char *buffer, size_t buflen)
read the contents of a symbolic link
Definition vfs_types.hpp:78
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:70
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:68
const inode_ops_t * ops
file_type_t type
Definition io.hpp:48
io_type_t type
Definition io.hpp:52
Definition vfs_types.hpp:47
mos::string name
Definition vfs_types.hpp:50
file_type_t type
Definition vfs_types.hpp:51
ino_t ino
Definition vfs_types.hpp:49
size_t n_count
number of entries in the list
Definition vfs_types.hpp:57
s32 fd_t
Definition types.h:77
unsigned long long u64
Definition types.h:19
#define container_of(ptr, type, member)
Definition types.hpp:31
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