510 lines
14 KiB
C
510 lines
14 KiB
C
// SPDX-License-Identifier: ISC
|
|
|
|
#include <zinnia/alloc.h>
|
|
#include <zinnia/assert.h>
|
|
#include <zinnia/atomic.h>
|
|
#include <zinnia/clock.h>
|
|
#include <zinnia/initgraph.h>
|
|
#include <zinnia/vfs.h>
|
|
#include <zinnia/vma.h>
|
|
#include <zinnia/vmo.h>
|
|
#include <arch/mem.h>
|
|
#include <string.h>
|
|
|
|
struct tmpfs_super {
|
|
struct vfs_super_block base;
|
|
_Atomic(ino_t) inode_counter;
|
|
};
|
|
|
|
struct tmpfs_symlink {
|
|
char* target;
|
|
size_t len;
|
|
};
|
|
|
|
struct tmpfs_file {
|
|
struct vmo vmo;
|
|
_Atomic(uint64_t) size;
|
|
};
|
|
|
|
static errno_t tmpfs_file_get_pages(
|
|
struct vmo* vmo,
|
|
uint64_t index,
|
|
struct page** pages,
|
|
size_t count,
|
|
size_t* out_valid
|
|
) {
|
|
struct tmpfs_file* file = containerof(vmo, struct tmpfs_file, vmo);
|
|
const size_t page_size = arch_mem_page_size();
|
|
const uint64_t size = atomic_load(&file->size, ATOMIC_ACQUIRE);
|
|
const uint64_t limit = size / page_size + (size % page_size != 0);
|
|
|
|
const size_t valid = index >= limit ? 0 : MIN(count, limit - index);
|
|
for (size_t i = 0; i < valid; i++)
|
|
memset(HHDM_PTR(vm_page_addr(pages[i])), 0, page_size);
|
|
*out_valid = valid;
|
|
return 0;
|
|
}
|
|
|
|
static void tmpfs_file_destroy(struct vmo* vmo) {
|
|
mem_free(containerof(vmo, struct tmpfs_file, vmo));
|
|
}
|
|
|
|
static const struct vmo_pager_ops tmpfs_file_pager = {
|
|
.get_pages = tmpfs_file_get_pages,
|
|
.destroy = tmpfs_file_destroy,
|
|
};
|
|
|
|
static const struct vfs_node_ops tmpfs_dir_ops;
|
|
static const struct vfs_node_ops tmpfs_regular_ops;
|
|
static const struct vfs_node_ops tmpfs_symlink_ops;
|
|
|
|
static errno_t tmpfs_create_inode(
|
|
struct vfs_super_block* sb,
|
|
enum vfs_node_type type,
|
|
const struct vfs_node_ops* ops,
|
|
mode_t mode,
|
|
const struct ident* identity,
|
|
struct vfs_node** out
|
|
) {
|
|
struct tmpfs_super* super = containerof(sb, struct tmpfs_super, base);
|
|
|
|
struct vfs_node* node;
|
|
errno_t status = vfs_node_new(sb, type, ops, mode, &node);
|
|
if (status)
|
|
return status;
|
|
|
|
node->id = atomic_fetch_add(&super->inode_counter, 1, ATOMIC_RELAXED);
|
|
node->uid = identity->euid;
|
|
node->gid = identity->egid;
|
|
|
|
*out = node;
|
|
return 0;
|
|
}
|
|
|
|
// Super block
|
|
|
|
static errno_t tmpfs_super_sync(struct vfs_super_block* sb) {
|
|
// This is a no-op.
|
|
return 0;
|
|
}
|
|
|
|
static errno_t tmpfs_super_statvfs(struct vfs_super_block* sb, struct __statvfs* out) {
|
|
memset(out, 0, sizeof(*out));
|
|
out->f_namemax = __NAME_MAX;
|
|
return 0;
|
|
}
|
|
|
|
static void tmpfs_super_destroy(struct vfs_super_block* sb) {
|
|
mem_free(containerof(sb, struct tmpfs_super, base));
|
|
}
|
|
|
|
static const struct vfs_super_block_ops tmpfs_super_ops = {
|
|
.sync = tmpfs_super_sync,
|
|
.statvfs = tmpfs_super_statvfs,
|
|
.destroy = tmpfs_super_destroy,
|
|
};
|
|
|
|
// Directories
|
|
|
|
static errno_t tmpfs_dir_lookup(struct vfs_node* node, struct vfs_path* child) {
|
|
// tmpfs directories only live in memory, so we cannot look up entries that do not exist.
|
|
return ENOENT;
|
|
}
|
|
|
|
static errno_t tmpfs_dir_open(
|
|
struct vfs_node* node,
|
|
struct vfs_path* path,
|
|
enum open_flags flags,
|
|
const struct ident* identity,
|
|
struct vfs_file** out_file
|
|
) {
|
|
return vfs_file_new(node, path, flags, out_file);
|
|
}
|
|
|
|
static errno_t tmpfs_dir_create(
|
|
struct vfs_node* node,
|
|
struct vfs_entry* entry,
|
|
mode_t mode,
|
|
const struct ident* identity
|
|
) {
|
|
struct tmpfs_file* data = mem_alloc(sizeof(struct tmpfs_file), 0);
|
|
if (!data)
|
|
return ENOMEM;
|
|
vmo_init(&data->vmo, &tmpfs_file_pager);
|
|
struct vmo* cache = &data->vmo;
|
|
errno_t status;
|
|
|
|
struct vfs_node* file;
|
|
status = tmpfs_create_inode(node->sb, VNODE_REGULAR, &tmpfs_regular_ops, mode, identity, &file);
|
|
if (status) {
|
|
vmo_unref(cache);
|
|
return status;
|
|
}
|
|
|
|
// Attach the cache before the node becomes visible through the entry.
|
|
file->priv = cache;
|
|
vfs_entry_set_inode(entry, file);
|
|
vfs_node_unref(file);
|
|
return 0;
|
|
}
|
|
|
|
static errno_t tmpfs_dir_mkdir(
|
|
struct vfs_node* node,
|
|
struct vfs_path* path,
|
|
mode_t mode,
|
|
const struct ident* identity
|
|
) {
|
|
struct vfs_node* dir;
|
|
errno_t status = tmpfs_create_inode(node->sb, VNODE_DIRECTORY, &tmpfs_dir_ops, mode, identity, &dir);
|
|
if (status)
|
|
return status;
|
|
|
|
vfs_entry_set_inode(path->entry, dir);
|
|
vfs_node_unref(dir);
|
|
return 0;
|
|
}
|
|
|
|
static errno_t tmpfs_dir_symlink(
|
|
struct vfs_node* node,
|
|
struct vfs_path* path,
|
|
const char* target,
|
|
size_t target_len,
|
|
const struct ident* identity
|
|
) {
|
|
struct tmpfs_symlink* link = mem_alloc(sizeof(*link), 0);
|
|
if (!link)
|
|
return ENOMEM;
|
|
link->target = mem_alloc(target_len + 1, ALLOC_NOZERO);
|
|
if (!link->target) {
|
|
mem_free(link);
|
|
return ENOMEM;
|
|
}
|
|
memcpy(link->target, target, target_len);
|
|
link->target[target_len] = '\0';
|
|
link->len = target_len;
|
|
|
|
struct vfs_node* sym;
|
|
errno_t status = tmpfs_create_inode(node->sb, VNODE_SYMLINK, &tmpfs_symlink_ops, 0777, identity, &sym);
|
|
if (status) {
|
|
mem_free(link->target);
|
|
mem_free(link);
|
|
return status;
|
|
}
|
|
|
|
sym->priv = link;
|
|
sym->size = target_len;
|
|
vfs_entry_set_inode(path->entry, sym);
|
|
vfs_node_unref(sym);
|
|
return 0;
|
|
}
|
|
|
|
static void adjust_links(struct vfs_node* node, int delta) {
|
|
mutex_lock(&node->lock);
|
|
node->nlink += delta;
|
|
node->ctime = time_from_nano(clock_get_realtime_ns());
|
|
mutex_unlock(&node->lock);
|
|
}
|
|
|
|
static errno_t tmpfs_dir_link(
|
|
struct vfs_node* node,
|
|
struct vfs_path* path,
|
|
struct vfs_node* target,
|
|
const struct ident* identity
|
|
) {
|
|
adjust_links(target, 1);
|
|
vfs_entry_set_inode(path->entry, target);
|
|
return 0;
|
|
}
|
|
|
|
// Removes `child` from the cached children of `parent` and drops the list's reference.
|
|
static void detach_child(struct vfs_entry* parent, struct vfs_entry* child) {
|
|
bool found = false;
|
|
|
|
mutex_lock(&parent->lock);
|
|
for (struct vfs_entry* it = tailq_first(&parent->children); it; it = tailq_next(it, child_link)) {
|
|
if (it == child) {
|
|
tailq_remove(&parent->children, child, child_link);
|
|
found = true;
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&parent->lock);
|
|
|
|
if (found)
|
|
vfs_entry_unref(child);
|
|
}
|
|
|
|
static errno_t tmpfs_dir_unlink(struct vfs_node* node, struct vfs_path* path, const struct ident* identity) {
|
|
struct vfs_path parent;
|
|
errno_t status = vfs_path_lookup_parent(*path, &parent);
|
|
if (status)
|
|
return status;
|
|
|
|
struct vfs_node* victim = vfs_entry_get_inode(path->entry);
|
|
if (victim) {
|
|
adjust_links(victim, -1);
|
|
vfs_node_unref(victim);
|
|
}
|
|
vfs_entry_set_negative(path->entry);
|
|
detach_child(parent.entry, path->entry);
|
|
vfs_path_drop(parent);
|
|
return 0;
|
|
}
|
|
|
|
static errno_t tmpfs_dir_rename(
|
|
struct vfs_node* node,
|
|
struct vfs_path* path,
|
|
struct vfs_node* target,
|
|
struct vfs_path* target_path,
|
|
const struct ident* identity
|
|
) {
|
|
struct vfs_node* moved = vfs_entry_get_inode(path->entry);
|
|
if (!moved)
|
|
return ENOENT;
|
|
|
|
struct vfs_path parent;
|
|
errno_t status = vfs_path_lookup_parent(*path, &parent);
|
|
if (status) {
|
|
vfs_node_unref(moved);
|
|
return status;
|
|
}
|
|
|
|
struct vfs_node* replaced = vfs_entry_get_inode(target_path->entry);
|
|
if (replaced) {
|
|
adjust_links(replaced, -1);
|
|
vfs_node_unref(replaced);
|
|
}
|
|
vfs_entry_set_inode(target_path->entry, moved);
|
|
vfs_node_unref(moved);
|
|
|
|
// Transfer children for directory renames.
|
|
struct vfs_entry* src = path->entry;
|
|
vfs_entry_replace_children(target_path->entry, src);
|
|
|
|
vfs_entry_set_negative(src);
|
|
detach_child(parent.entry, src);
|
|
vfs_path_drop(parent);
|
|
return 0;
|
|
}
|
|
|
|
static errno_t tmpfs_dir_mknod(
|
|
struct vfs_node* node,
|
|
struct vfs_path* path,
|
|
mode_t mode,
|
|
struct vfs_mknod_target* target,
|
|
const struct ident* identity
|
|
) {
|
|
if (!target)
|
|
return ENODEV;
|
|
|
|
struct vfs_node* new_node;
|
|
errno_t status = tmpfs_create_inode(node->sb, target->type, target->ops, mode, identity, &new_node);
|
|
if (status)
|
|
return status;
|
|
|
|
new_node->priv = target->priv;
|
|
new_node->rdev = target->rdev;
|
|
vfs_entry_set_inode(path->entry, new_node);
|
|
vfs_node_unref(new_node);
|
|
return 0;
|
|
}
|
|
|
|
static const struct vfs_file_ops tmpfs_dir_file_ops = {};
|
|
|
|
static const struct vfs_node_ops tmpfs_dir_ops = {
|
|
.file_ops = &tmpfs_dir_file_ops,
|
|
.dir = {
|
|
.lookup = tmpfs_dir_lookup,
|
|
.open = tmpfs_dir_open,
|
|
.create = tmpfs_dir_create,
|
|
.mkdir = tmpfs_dir_mkdir,
|
|
.symlink = tmpfs_dir_symlink,
|
|
.link = tmpfs_dir_link,
|
|
.unlink = tmpfs_dir_unlink,
|
|
.rmdir = tmpfs_dir_unlink,
|
|
.rename = tmpfs_dir_rename,
|
|
.mknod = tmpfs_dir_mknod,
|
|
.get_dir_entries = vfs_dir_get_cached_entries,
|
|
},
|
|
};
|
|
|
|
// Regular files
|
|
|
|
static struct vmo* regular_cache(struct vfs_node* node) {
|
|
return node->priv;
|
|
}
|
|
|
|
static void set_size(struct vfs_node* node, uint64_t size) {
|
|
node->size = size;
|
|
atomic_store(&containerof(regular_cache(node), struct tmpfs_file, vmo)->size, size, ATOMIC_RELEASE);
|
|
}
|
|
|
|
static errno_t tmpfs_regular_read(struct vfs_file* file, iovec_iter_t* buffer, uint64_t offset, ssize_t* out_read) {
|
|
struct vfs_node* node = file->vnode;
|
|
|
|
mutex_lock(&node->lock);
|
|
size_t size = node->size;
|
|
mutex_unlock(&node->lock);
|
|
|
|
*out_read = 0;
|
|
if (offset >= size)
|
|
return 0;
|
|
|
|
size_t progress;
|
|
errno_t status =
|
|
vmo_read_iter(regular_cache(node), offset, buffer, MIN(iovec_iter_len(buffer), size - offset), &progress);
|
|
*out_read = progress;
|
|
return progress > 0 ? 0 : status;
|
|
}
|
|
|
|
static errno_t tmpfs_regular_write(struct vfs_file* file, iovec_iter_t* buffer, uint64_t offset, ssize_t* out_written) {
|
|
struct vfs_node* node = file->vnode;
|
|
const size_t total = iovec_iter_len(buffer);
|
|
|
|
mutex_lock(&node->lock);
|
|
const uint64_t old_size = node->size;
|
|
if (offset + total > old_size)
|
|
set_size(node, offset + total);
|
|
size_t progress;
|
|
errno_t status = vmo_write_iter(regular_cache(node), offset, buffer, total, &progress);
|
|
if (progress == 0 && total > 0 && !status)
|
|
status = ENOMEM;
|
|
if (offset + total > old_size)
|
|
set_size(node, MAX(old_size, offset + progress));
|
|
mutex_unlock(&node->lock);
|
|
|
|
*out_written = progress;
|
|
return progress > 0 ? 0 : status;
|
|
}
|
|
|
|
static errno_t tmpfs_regular_mmap(
|
|
struct vfs_file* file,
|
|
struct vma* vma,
|
|
uintptr_t addr,
|
|
size_t len,
|
|
enum prot_flags prot,
|
|
enum map_flags flags,
|
|
off_t offset,
|
|
uintptr_t* out_addr
|
|
) {
|
|
enum vma_flags vflags = (flags & MAP_SHARED) ? VMA_SHARED : 0;
|
|
errno_t status = vma_map(vma, regular_cache(file->vnode), addr, len, prot, vflags, offset);
|
|
if (status)
|
|
return status;
|
|
|
|
*out_addr = addr;
|
|
return 0;
|
|
}
|
|
|
|
static errno_t tmpfs_regular_truncate(struct vfs_node* node, uint64_t new_length) {
|
|
mutex_lock(&node->lock);
|
|
const uint64_t old_size = node->size;
|
|
set_size(node, new_length);
|
|
if (new_length < old_size)
|
|
vmo_truncate(regular_cache(node), new_length);
|
|
mutex_unlock(&node->lock);
|
|
return 0;
|
|
}
|
|
|
|
static errno_t tmpfs_regular_get_object(struct vfs_node* node, struct vmo** out) {
|
|
struct vmo* obj = regular_cache(node);
|
|
vmo_ref(obj);
|
|
*out = obj;
|
|
return 0;
|
|
}
|
|
|
|
static void tmpfs_regular_destroy(struct vfs_node* node) {
|
|
if (node->priv)
|
|
vmo_unref(regular_cache(node));
|
|
}
|
|
|
|
static const struct vfs_file_ops tmpfs_regular_file_ops = {
|
|
.read = tmpfs_regular_read,
|
|
.write = tmpfs_regular_write,
|
|
.mmap = tmpfs_regular_mmap,
|
|
};
|
|
|
|
static const struct vfs_node_ops tmpfs_regular_ops = {
|
|
.destroy = tmpfs_regular_destroy,
|
|
.file_ops = &tmpfs_regular_file_ops,
|
|
.regular = {
|
|
.truncate = tmpfs_regular_truncate,
|
|
.get_vmo = tmpfs_regular_get_object,
|
|
},
|
|
};
|
|
|
|
// Symbolic links
|
|
|
|
static errno_t tmpfs_symlink_read_link(struct vfs_node* node, iovec_iter_t* iter, size_t* out_read) {
|
|
struct tmpfs_symlink* link = node->priv;
|
|
return iovec_iter_copy_to(iter, link->target, link->len, out_read);
|
|
}
|
|
|
|
static void tmpfs_symlink_destroy(struct vfs_node* node) {
|
|
struct tmpfs_symlink* link = node->priv;
|
|
if (link) {
|
|
mem_free(link->target);
|
|
mem_free(link);
|
|
}
|
|
}
|
|
|
|
static const struct vfs_file_ops tmpfs_symlink_file_ops = {};
|
|
|
|
static const struct vfs_node_ops tmpfs_symlink_ops = {
|
|
.destroy = tmpfs_symlink_destroy,
|
|
.file_ops = &tmpfs_symlink_file_ops,
|
|
.symlink = {
|
|
.read_link = tmpfs_symlink_read_link,
|
|
},
|
|
};
|
|
|
|
// File system
|
|
|
|
static errno_t tmpfs_mount(const struct vfs_filesystem* fs, enum mount_flags flags, void* arg, struct vfs_mount** out) {
|
|
struct tmpfs_super* super = mem_alloc(sizeof(*super), 0);
|
|
if (!super)
|
|
return ENOMEM;
|
|
refcount_init(&super->base.rc);
|
|
super->base.ops = &tmpfs_super_ops;
|
|
super->base.flags = flags;
|
|
atomic_init(&super->inode_counter, 1);
|
|
|
|
struct vfs_node* root_node = nullptr;
|
|
struct vfs_entry* root_entry = nullptr;
|
|
|
|
errno_t status =
|
|
tmpfs_create_inode(&super->base, VNODE_DIRECTORY, &tmpfs_dir_ops, 0755, ident_kernel(), &root_node);
|
|
if (status)
|
|
goto out;
|
|
|
|
status = vfs_entry_new("", 0, nullptr, &root_entry);
|
|
if (status)
|
|
goto out;
|
|
vfs_entry_set_inode(root_entry, root_node);
|
|
|
|
status = vfs_mount_new(flags, root_entry, out);
|
|
|
|
out:
|
|
if (root_entry)
|
|
vfs_entry_unref(root_entry);
|
|
if (root_node)
|
|
vfs_node_unref(root_node);
|
|
// The root inode keeps the super block alive now.
|
|
vfs_super_block_unref(&super->base);
|
|
return status;
|
|
}
|
|
|
|
static const struct vfs_filesystem tmpfs = {
|
|
.name = "tmpfs",
|
|
.mount = tmpfs_mount,
|
|
};
|
|
|
|
static void tmpfs_stage_init(struct boot_info* info) {
|
|
errno_t status = vfs_register_filesystem(&tmpfs);
|
|
assert(status == 0, "Unable to register tmpfs (%i)", status);
|
|
}
|
|
|
|
INIT_TASK(tmpfs_stage, "fs.tmpfs", tmpfs_stage_init);
|
|
INIT_DEPENDS(tmpfs_stage, mem_stage);
|
|
INIT_ENTAILS(tmpfs_stage, vfs_stage);
|