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