Skip to content

Commit

Permalink
mount: umount2 support
Browse files Browse the repository at this point in the history
Add umount2() support. Also umount support for tmpfs and ext2, and loose
fixes for dcache rename and unlink.

Signed-off-by: Pedro Falcato <[email protected]>
  • Loading branch information
heatd committed Aug 5, 2024
1 parent 6a40762 commit 35c14ce
Show file tree
Hide file tree
Showing 14 changed files with 285 additions and 61 deletions.
17 changes: 17 additions & 0 deletions kernel/arch/riscv64/syscall_table.json
Original file line number Diff line number Diff line change
Expand Up @@ -2736,5 +2736,22 @@
],
"return_type": "int",
"abi": "c"
},
{
"name": "umount2",
"nr": 156,
"nr_args": 2,
"args": [
[
"const char *",
"target"
],
[
"int",
"flags"
]
],
"return_type": "int",
"abi": "c"
}
]
18 changes: 18 additions & 0 deletions kernel/arch/x86_64/syscall_table.json
Original file line number Diff line number Diff line change
Expand Up @@ -2780,5 +2780,23 @@
],
"return_type": "int",
"abi": "c"
},
{
"name": "umount2",
"nr": 156,
"nr_args": 2,
"args": [
[
"const char *",
"target"
],
[
"int",
"flags"
]
],
"return_type": "int",
"abi": "c"
}

]
10 changes: 10 additions & 0 deletions kernel/include/onyx/dentry.h
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,16 @@ struct dcache_shrink_result
enum lru_walk_ret scan_dcache_lru_one(struct lru_list *lru, struct list_head *object, void *data);
enum lru_walk_ret shrink_dcache_lru_one(struct lru_list *lru, struct list_head *object, void *data);

void dentry_shrink_subtree(struct dentry *dentry);

/**
* @brief Do the final unref on a whole subtree
* Should _only_ be used by in-memory filesystems that use the dcache as their directories.
*
* @param dentry Root dentry
*/
void dentry_unref_subtree(struct dentry *dentry);

__END_CDECLS

#ifdef __cplusplus
Expand Down
14 changes: 8 additions & 6 deletions kernel/include/onyx/rcupdate.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,15 +27,17 @@ void synchronize_rcu();
void __kfree_rcu(struct rcu_head *head, unsigned long off);

#ifdef __cplusplus
#define _Static_assert(x) static_assert(x)
#define _Static_assert(x, m) static_assert(x, m)
#endif

#define is_kfree_rcu_off(off) ((off) < 4096)
#define kfree_rcu(ptr, head) \
({ \
unsigned long off = offsetof(__typeof__(*(ptr)), head); \
_Static_assert(is_kfree_rcu_off(offsetof(__typeof__(*(ptr)), head))); \
__kfree_rcu(&(ptr)->head, off); \
#define kfree_rcu(ptr, head) \
({ \
unsigned long off = offsetof(__typeof__(*(ptr)), head); \
_Static_assert( \
is_kfree_rcu_off(offsetof(__typeof__(*(ptr)), head)), \
"kfree_rcu's rcu_head needs to be within 4096 bytes off the start of the struct"); \
__kfree_rcu(&(ptr)->head, off); \
})

/**
Expand Down
11 changes: 10 additions & 1 deletion kernel/include/onyx/superblock.h
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,14 @@
#include <onyx/lru.h>
#include <onyx/mm/shrinker.h>
#include <onyx/mutex.h>
#include <onyx/rcupdate.h>
#include <onyx/spinlock.h>
#include <onyx/types.h>

struct file;
struct bio_req;
struct blockdev;
struct mount;

#define SB_FLAG_NODIRTY (1 << 0)
#define SB_FLAG_IN_MEMORY (1 << 1)
Expand All @@ -34,13 +36,18 @@ struct superblock
int (*flush_inode)(struct inode *inode, bool in_sync);
int (*kill_inode)(struct inode *inode);
int (*statfs)(struct statfs *buf, struct superblock *sb);
int (*umount)(struct mount *mnt);
int (*shutdown)(struct superblock *sb);
unsigned int s_block_size;
struct blockdev *s_bdev;
dev_t s_devnr;
unsigned long s_flags;
struct mutex s_rename_lock;
struct lru_list s_dcache_lru;
struct shrinker s_shrinker;
union {
struct shrinker s_shrinker;
struct rcu_head s_rcu;
};
};

__BEGIN_CDECLS
Expand All @@ -51,6 +58,8 @@ void superblock_add_inode_unlocked(struct superblock *sb, struct inode *inode);
void superblock_add_inode(struct superblock *sb, struct inode *inode);
void superblock_remove_inode(struct superblock *sb, struct inode *inode);
void superblock_kill(struct superblock *sb);
void sb_shutdown(struct superblock *sb);
int sb_generic_shutdown(struct superblock *sb);

struct page_iov;

Expand Down
5 changes: 1 addition & 4 deletions kernel/include/onyx/tmpfs.h
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,12 @@ class tmpfs_superblock : public superblock

dev_t fs_minor;

list_head_cpp<tmpfs_superblock> fs_list_node;

const file_ops *tmpfs_ops_;
atomic<size_t> nblocks;
atomic<size_t> ino_nr;

tmpfs_superblock()
: superblock{}, curr_inode{}, fs_minor{++curr_minor_number}, fs_list_node{this},
tmpfs_ops_{&tmpfs_fops}
: superblock{}, curr_inode{}, fs_minor{++curr_minor_number}, tmpfs_ops_{&tmpfs_fops}
{
superblock_init(this);
s_block_size = PAGE_SIZE;
Expand Down
49 changes: 48 additions & 1 deletion kernel/kernel/fs/dentry.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -785,6 +785,8 @@ void dentry_do_unlink(dentry *entry)
auto parent = entry->d_parent;
DCHECK(spin_lock_held(&parent->d_lock));
dput_locked(parent);
if ((entry->d_flags & (DENTRY_FLAG_LRU | DENTRY_FLAG_SHRINK)) == DENTRY_FLAG_LRU)
d_remove_lru(entry);
entry->d_parent = nullptr;

if (!d_is_negative(entry))
Expand All @@ -795,7 +797,6 @@ void dentry_do_unlink(dentry *entry)
{
inode_dec_nlink(parent->d_inode);
inode_dec_nlink(entry->d_inode);
dentry_shrink_subtree(entry);
}
}

Expand Down Expand Up @@ -865,6 +866,8 @@ void dentry_do_rename_unlink(dentry *entry)
auto parent = entry->d_parent;
DCHECK(spin_lock_held(&parent->d_lock));
WARN_ON(dput_locked(parent) == 0);
if ((entry->d_flags & (DENTRY_FLAG_LRU | DENTRY_FLAG_SHRINK)) == DENTRY_FLAG_LRU)
d_remove_lru(entry);
entry->d_parent = nullptr;

/* The dcache buckets are already locked, so we don't grab the lock again. Just open-code the
Expand Down Expand Up @@ -920,7 +923,10 @@ void dentry_rename(dentry *dent, const char *name, dentry *parent,
spin_lock(&dentry_ht_locks[oldi]);
}

spin_lock(&parent->d_lock);
dentry_do_rename_unlink(dst);
spin_unlock(&parent->d_lock);

spin_lock(&dent->d_lock);

DCHECK(dentry_is_in_chain(dent, oldi));
Expand Down Expand Up @@ -1263,6 +1269,47 @@ void dentry_shrink_subtree(struct dentry *dentry)
}
}

static d_walk_ret find_unref(void *data, struct dentry *dentry)
{
struct shrink_data *s = (struct shrink_data *) data;
if (!(dentry->d_flags & DENTRY_FLAG_SHRINK))
{
if (dentry->d_flags & DENTRY_FLAG_LRU)
d_remove_lru(dentry);

list_add_tail(&dentry->d_lru, &s->shrink_list);
dentry->d_flags |= DENTRY_FLAG_SHRINK | DENTRY_FLAG_LRU;
}

return D_WALK_CONTINUE;
}

static void unref_list(struct shrink_data *s)
{
list_for_every_safe (&s->shrink_list)
{
struct dentry *dentry = container_of(l, struct dentry, d_lru);
list_remove(&dentry->d_lru);
dentry->d_flags &= ~(DENTRY_FLAG_SHRINK | DENTRY_FLAG_LRU);
dput(dentry);
}
}

/**
* @brief Do the final unref on a whole subtree
* Should _only_ be used by in-memory filesystems that use the dcache as their directories.
*
* @param dentry Root dentry
*/
void dentry_unref_subtree(struct dentry *dentry)
{
struct shrink_data data;
INIT_LIST_HEAD(&data.shrink_list);
d_walk(dentry, &data, find_unref);
if (!list_is_empty(&data.shrink_list))
unref_list(&data);
}

enum lru_walk_ret scan_dcache_lru_one(struct lru_list *lru, struct list_head *object, void *data)
{
struct dentry *dentry = container_of(object, struct dentry, d_lru);
Expand Down
11 changes: 11 additions & 0 deletions kernel/kernel/fs/ext2/ext2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,16 @@ int ext2_statfs(struct statfs *buf, superblock *sb)
return ((ext2_superblock *) sb)->stat_fs(buf);
}

static int ext2_shutdown_sb(struct superblock *sb_)
{
ext2_superblock *sb = (ext2_superblock *) sb_;
/* Shutdown the sb generically first, then tear down the ext2_superblock. This is required for
* e.g sync purposes. */
sb_generic_shutdown(sb);
sb->~ext2_superblock();
return 0;
}

struct superblock *ext2_mount_partition(struct vfs_mount_info *info)
{
struct blockdev *dev = info->bdev;
Expand Down Expand Up @@ -782,6 +792,7 @@ struct superblock *ext2_mount_partition(struct vfs_mount_info *info)
sb->flush_inode = ext2_flush_inode;
sb->kill_inode = ext2_kill_inode;
sb->statfs = ext2_statfs;
sb->shutdown = ext2_shutdown_sb;

sb->sb->s_mtime = clock_get_posix_time();
sb->sb->s_mnt_count++;
Expand Down
97 changes: 96 additions & 1 deletion kernel/kernel/fs/mount.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
*/

#include <stdio.h>
#include <sys/mount.h>

#include <onyx/compiler.h>
#include <onyx/cred.h>
Expand Down Expand Up @@ -208,7 +209,6 @@ static int mnt_commit(struct mount *mnt, const char *target)
__atomic_or_fetch(&mnt->mnt_point->d_flags, DENTRY_FLAG_MOUNTPOINT, __ATOMIC_RELEASE);

write_sequnlock(&mount_lock);
pr_info("mounted %p on %s\n", mnt, target);

return 0;
}
Expand Down Expand Up @@ -316,6 +316,101 @@ int sys_mount(const char *usource, const char *utarget, const char *ufilesystemt
return ret;
}

/* HACK */
#define LOOKUP_NOFOLLOW (1 << 0)
#define LOOKUP_FAIL_IF_LINK (1 << 1)
#define LOOKUP_MUST_BE_DIR (1 << 2)
#define LOOKUP_INTERNAL_TRAILING_SLASH (1 << 3)
#define LOOKUP_EMPTY_PATH (1 << 4)
#define LOOKUP_DONT_DO_LAST_NAME (1 << 5)
#define LOOKUP_INTERNAL_SAW_LAST_NAME (1U << 31)

static bool attempt_disconnect(struct mount *mount)
{
bool ok = false;
write_seqlock(&mount_lock);
/* No one can grab a reference to a mount while we hold mount_lock. As such, checking the refs
* here is mostly safe. Note that we can spuriouly see a ref-up here, but that's not _really_ a
* problem. We expect a mnt_count of 1 for the struct path we hold. */
if (mount->mnt_count == 1)
{
struct dentry *mp = mount->mnt_point;
list_remove(&mount->mnt_mp_node);
list_remove(&mount->mnt_node);
ok = true;

/* Check if we have nothing mounted at mp anymore. If so, unset DENTRY_FLAG_MOUNTPOINT.
* There's no race because MOUNTPOINT is only set while holding mount_lock in write mode. */
if (!mnt_find_by_mp(mp))
__atomic_and_fetch(&mp->d_flags, ~DENTRY_FLAG_MOUNTPOINT, __ATOMIC_RELEASE);
}

write_sequnlock(&mount_lock);
return ok;
}

static int do_umount_path(struct path *path, int flags)
{
int err = -EINVAL;
struct mount *mount = path->mount;

/* Check if the path given is actually a mountpoint */
if (path->mount->mnt_root != path->dentry)
goto out_put_path;

err = -EBUSY;
if (!attempt_disconnect(mount))
goto out_put_path;

/* Mount was disconnected. No one should hold a reference to one of this mount's dentries after
* this. */
path_put(path);

if (mount->mnt_sb->umount)
mount->mnt_sb->umount(mount);

dentry_shrink_subtree(mount->mnt_root);
dput(mount->mnt_point);

WARN_ON(mount->mnt_root->d_ref != 1);

/* Undo our fake d_parent... */
mount->mnt_root->d_parent = NULL;
/* Finally, put our root */
dput(mount->mnt_root);

/* Now shutdown the superblock */
sb_shutdown(mount->mnt_sb);
kfree_rcu(mount, mnt_rcu);
return 0;
out_put_path:
path_put(path);
return err;
}

int sys_umount2(const char *utarget, int flags)
{
if (!is_root_user())
return -EPERM;
const char *target = strcpy_from_user(utarget);
if (!target)
return -errno;
if (flags & ~UMOUNT_NOFOLLOW)
return -EINVAL;

struct path path;
int err =
path_openat(AT_FDCWD, target,
LOOKUP_MUST_BE_DIR | (flags & UMOUNT_NOFOLLOW ? LOOKUP_NOFOLLOW : 0), &path);
if (err < 0)
goto out;

err = do_umount_path(&path, flags);
out:
free((void *) target);
return err;
}

static __init void mount_init(void)
{
for (int i = 0; i < MT_HASH_SIZE; i++)
Expand Down
2 changes: 2 additions & 0 deletions kernel/kernel/fs/namei.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1079,6 +1079,8 @@ int unlink_vfs(const char *path, int flags, int dirfd)
spin_lock(&dentry->d_lock);
dentry_do_unlink(child);
spin_unlock(&dentry->d_lock);
if (dentry_is_dir(child))
dentry_shrink_subtree(child);
}

out2:
Expand Down
Loading

0 comments on commit 35c14ce

Please sign in to comment.