fs: Initial VFS port
This commit is contained in:
@@ -5,6 +5,7 @@
|
|||||||
#include <zinnia/compiler.h>
|
#include <zinnia/compiler.h>
|
||||||
#include <zinnia/console.h>
|
#include <zinnia/console.h>
|
||||||
#include <zinnia/elf.h>
|
#include <zinnia/elf.h>
|
||||||
|
#include <zinnia/exec.h>
|
||||||
#include <zinnia/init.h>
|
#include <zinnia/init.h>
|
||||||
#include <zinnia/initgraph.h>
|
#include <zinnia/initgraph.h>
|
||||||
#include <zinnia/irq.h>
|
#include <zinnia/irq.h>
|
||||||
|
|||||||
@@ -0,0 +1,40 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
// anonfs is a virtual file system. It backs files that don't live in any other
|
||||||
|
// file system, like pipes or unbound sockets.
|
||||||
|
|
||||||
|
#include <zinnia/atomic.h>
|
||||||
|
#include <zinnia/vfs.h>
|
||||||
|
|
||||||
|
static const struct vfs_super_block_ops anon_super_ops = {};
|
||||||
|
|
||||||
|
static struct vfs_super_block anon_super = {
|
||||||
|
.rc = {.count = 1}, // There's always just this exact super block.
|
||||||
|
.ops = &anon_super_ops,
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct vfs_node_ops anon_node_ops = {};
|
||||||
|
|
||||||
|
static _Atomic(ino_t) anon_inode_counter = 1;
|
||||||
|
|
||||||
|
errno_t vfs_create_anon_inode(
|
||||||
|
const struct vfs_file_ops* file_ops,
|
||||||
|
enum vfs_node_type type,
|
||||||
|
mode_t mode,
|
||||||
|
const struct ident* identity,
|
||||||
|
struct vfs_node** out
|
||||||
|
) {
|
||||||
|
struct vfs_node* node;
|
||||||
|
errno_t status = vfs_node_new(&anon_super, type, &anon_node_ops, mode, &node);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
node->file_ops = file_ops;
|
||||||
|
// Every anon node gets its own inode counter.
|
||||||
|
node->id = atomic_fetch_add(&anon_inode_counter, 1, ATOMIC_RELAXED);
|
||||||
|
node->uid = identity->euid;
|
||||||
|
node->gid = identity->egid;
|
||||||
|
|
||||||
|
*out = node;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
+411
@@ -0,0 +1,411 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#include <zinnia/alloc.h>
|
||||||
|
#include <zinnia/vfs.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
constexpr size_t MAX_SYMLINK_DEPTH = 40;
|
||||||
|
|
||||||
|
// If `path` is a mount point, crosses into the root of the topmost mounted file system.
|
||||||
|
// Returns a new reference.
|
||||||
|
static struct vfs_path cross_mounts(struct vfs_path path) {
|
||||||
|
struct vfs_mount* mount = path.mount;
|
||||||
|
struct vfs_entry* entry = path.entry;
|
||||||
|
|
||||||
|
again:
|
||||||
|
mutex_lock(&entry->lock);
|
||||||
|
vec_foreach(&entry->mounts, i) {
|
||||||
|
struct vfs_mount* child = vec_elem_get(&entry->mounts, i);
|
||||||
|
if (child != mount) {
|
||||||
|
mutex_unlock(&entry->lock);
|
||||||
|
mount = child;
|
||||||
|
entry = child->root;
|
||||||
|
goto again;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex_unlock(&entry->lock);
|
||||||
|
|
||||||
|
return vfs_path_clone((struct vfs_path){.mount = mount, .entry = entry});
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_path_lookup_parent(struct vfs_path path, struct vfs_path* out) {
|
||||||
|
struct vfs_mount* mount = path.mount;
|
||||||
|
struct vfs_entry* entry = path.entry;
|
||||||
|
|
||||||
|
// Get the top mount point.
|
||||||
|
while (mount->has_mount_point && entry == mount->root) {
|
||||||
|
entry = mount->mount_point.entry;
|
||||||
|
mount = mount->mount_point.mount;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!entry->parent)
|
||||||
|
return ENOENT;
|
||||||
|
|
||||||
|
*out = vfs_path_clone((struct vfs_path){.mount = mount, .entry = entry->parent});
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_path_lookup_child(
|
||||||
|
struct vfs_path path,
|
||||||
|
struct vfs_path* root,
|
||||||
|
const char* name,
|
||||||
|
size_t name_len,
|
||||||
|
struct vfs_path* out
|
||||||
|
) {
|
||||||
|
errno_t status = 0;
|
||||||
|
|
||||||
|
// Traverse the mounts on the current entry.
|
||||||
|
struct vfs_path crossed = cross_mounts(path);
|
||||||
|
|
||||||
|
// A lookup only makes sense on directories.
|
||||||
|
struct vfs_node* parent = vfs_entry_get_inode(crossed.entry);
|
||||||
|
if (!parent) {
|
||||||
|
status = ENOENT;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
if (parent->type != VNODE_DIRECTORY) {
|
||||||
|
status = ENOTDIR;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name_len == 1 && name[0] == '.') {
|
||||||
|
vfs_node_unref(parent);
|
||||||
|
*out = crossed;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name_len == 2 && name[0] == '.' && name[1] == '.') {
|
||||||
|
vfs_node_unref(parent);
|
||||||
|
|
||||||
|
struct vfs_path crossed_root = cross_mounts(*root);
|
||||||
|
bool at_root = vfs_path_equal(crossed, crossed_root);
|
||||||
|
vfs_path_drop(crossed_root);
|
||||||
|
if (at_root) {
|
||||||
|
*out = crossed;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = vfs_path_lookup_parent(crossed, out);
|
||||||
|
if (status == ENOENT) {
|
||||||
|
*out = crossed;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
vfs_path_drop(crossed);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock(&crossed.entry->lock);
|
||||||
|
|
||||||
|
// If this entry has already been looked up before, return that.
|
||||||
|
struct vfs_entry* child = nullptr;
|
||||||
|
for (struct vfs_entry* it = tailq_first(&crossed.entry->children); it; it = tailq_next(it, child_link)) {
|
||||||
|
if (it->name_len == name_len && !memcmp(it->name, name, name_len)) {
|
||||||
|
child = it;
|
||||||
|
vfs_entry_ref(child);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!child) {
|
||||||
|
status = vfs_entry_new(name, name_len, crossed.entry, &child);
|
||||||
|
if (status) {
|
||||||
|
mutex_unlock(&crossed.entry->lock);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct vfs_path child_path = {.mount = crossed.mount, .entry = child};
|
||||||
|
status = parent->ops->dir.lookup ? parent->ops->dir.lookup(parent, &child_path) : ENOENT;
|
||||||
|
if (status && status != ENOENT) {
|
||||||
|
mutex_unlock(&crossed.entry->lock);
|
||||||
|
vfs_entry_unref(child);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Allow lookup failures so we can cache it as a negative entry.
|
||||||
|
if (status == ENOENT)
|
||||||
|
vfs_entry_set_negative(child);
|
||||||
|
status = 0;
|
||||||
|
|
||||||
|
// The children list takes over the initial reference.
|
||||||
|
tailq_insert_tail(&crossed.entry->children, child, child_link);
|
||||||
|
vfs_entry_ref(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_unlock(&crossed.entry->lock);
|
||||||
|
|
||||||
|
*out = cross_mounts((struct vfs_path){.mount = crossed.mount, .entry = child});
|
||||||
|
vfs_entry_unref(child);
|
||||||
|
vfs_node_unref(parent);
|
||||||
|
vfs_path_drop(crossed);
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
if (parent)
|
||||||
|
vfs_node_unref(parent);
|
||||||
|
vfs_path_drop(crossed);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static errno_t lookup_inner(
|
||||||
|
struct vfs_path root,
|
||||||
|
struct vfs_path start,
|
||||||
|
const char* path,
|
||||||
|
const struct ident* identity,
|
||||||
|
enum vfs_lookup_flags flags,
|
||||||
|
size_t* symlink_depth,
|
||||||
|
struct vfs_path* out
|
||||||
|
);
|
||||||
|
|
||||||
|
// Resolves `current` in place until it no longer points to a symbolic link.
|
||||||
|
static errno_t resolve_symlink(
|
||||||
|
struct vfs_path root,
|
||||||
|
struct vfs_path* current,
|
||||||
|
const struct ident* identity,
|
||||||
|
enum vfs_lookup_flags flags,
|
||||||
|
size_t* symlink_depth
|
||||||
|
) {
|
||||||
|
errno_t status = 0;
|
||||||
|
char* link_buf = nullptr;
|
||||||
|
|
||||||
|
while (true) {
|
||||||
|
struct vfs_node* node = vfs_entry_get_inode(current->entry);
|
||||||
|
if (!node)
|
||||||
|
break;
|
||||||
|
if (node->type != VNODE_SYMLINK) {
|
||||||
|
vfs_node_unref(node);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (*symlink_depth >= MAX_SYMLINK_DEPTH || !node->ops->symlink.read_link) {
|
||||||
|
vfs_node_unref(node);
|
||||||
|
status = ELOOP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
(*symlink_depth)++;
|
||||||
|
|
||||||
|
if (!link_buf) {
|
||||||
|
link_buf = mem_alloc(__PATH_MAX + 1, ALLOC_NOZERO);
|
||||||
|
if (!link_buf) {
|
||||||
|
vfs_node_unref(node);
|
||||||
|
status = ENOMEM;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
iovec_t vec = {.base = (uintptr_t)link_buf, .len = __PATH_MAX};
|
||||||
|
iovec_iter_t iter = iovec_iter_kernel(&vec, 1);
|
||||||
|
size_t link_len = 0;
|
||||||
|
status = node->ops->symlink.read_link(node, &iter, &link_len);
|
||||||
|
vfs_node_unref(node);
|
||||||
|
if (status)
|
||||||
|
break;
|
||||||
|
link_buf[link_len] = '\0';
|
||||||
|
|
||||||
|
struct vfs_path parent;
|
||||||
|
status = vfs_path_lookup_parent(*current, &parent);
|
||||||
|
if (status)
|
||||||
|
break;
|
||||||
|
|
||||||
|
struct vfs_path result;
|
||||||
|
status = lookup_inner(
|
||||||
|
root,
|
||||||
|
parent,
|
||||||
|
link_buf,
|
||||||
|
identity,
|
||||||
|
flags & ~(VFS_LOOKUP_MUST_EXIST | VFS_LOOKUP_MUST_NOT_EXIST),
|
||||||
|
symlink_depth,
|
||||||
|
&result
|
||||||
|
);
|
||||||
|
vfs_path_drop(parent);
|
||||||
|
if (status)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (vfs_path_equal(result, *current)) {
|
||||||
|
vfs_path_drop(result);
|
||||||
|
status = ELOOP;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
vfs_path_drop(*current);
|
||||||
|
*current = result;
|
||||||
|
}
|
||||||
|
|
||||||
|
mem_free(link_buf);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static errno_t lookup_inner(
|
||||||
|
struct vfs_path root,
|
||||||
|
struct vfs_path start,
|
||||||
|
const char* path,
|
||||||
|
const struct ident* identity,
|
||||||
|
enum vfs_lookup_flags flags,
|
||||||
|
size_t* symlink_depth,
|
||||||
|
struct vfs_path* out
|
||||||
|
) {
|
||||||
|
errno_t status = 0;
|
||||||
|
|
||||||
|
if (path[0] == '\0')
|
||||||
|
return ENOENT;
|
||||||
|
|
||||||
|
// If a path starts with '/', it's an absolute path.
|
||||||
|
// In that case, skip the first character and use the current root as a starting point.
|
||||||
|
struct vfs_path current;
|
||||||
|
if (path[0] == '/') {
|
||||||
|
current = vfs_path_clone(root);
|
||||||
|
path++;
|
||||||
|
} else {
|
||||||
|
current = vfs_path_clone(start);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse each component.
|
||||||
|
while (true) {
|
||||||
|
while (*path == '/')
|
||||||
|
path++;
|
||||||
|
if (*path == '\0')
|
||||||
|
break;
|
||||||
|
|
||||||
|
const char* component = path;
|
||||||
|
size_t component_len = 0;
|
||||||
|
while (path[component_len] != '/' && path[component_len] != '\0')
|
||||||
|
component_len++;
|
||||||
|
path += component_len;
|
||||||
|
|
||||||
|
if (component_len > __NAME_MAX) {
|
||||||
|
status = ENAMETOOLONG;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = resolve_symlink(root, ¤t, identity, flags, symlink_depth);
|
||||||
|
if (status)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
struct vfs_node* node = vfs_entry_get_inode(current.entry);
|
||||||
|
if (!node) {
|
||||||
|
status = ENOENT;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
if (node->type != VNODE_DIRECTORY) {
|
||||||
|
vfs_node_unref(node);
|
||||||
|
status = ENOTDIR;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = vfs_node_try_access(node, identity, X_OK, flags & VFS_LOOKUP_REAL_ID);
|
||||||
|
vfs_node_unref(node);
|
||||||
|
if (status)
|
||||||
|
goto fail;
|
||||||
|
|
||||||
|
struct vfs_path next;
|
||||||
|
status = vfs_path_lookup_child(current, &root, component, component_len, &next);
|
||||||
|
if (status)
|
||||||
|
goto fail;
|
||||||
|
vfs_path_drop(current);
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (flags & VFS_LOOKUP_FOLLOW_SYMLINKS) {
|
||||||
|
status = resolve_symlink(root, ¤t, identity, flags, symlink_depth);
|
||||||
|
if (status)
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct vfs_node* node = vfs_entry_get_inode(current.entry);
|
||||||
|
if (node)
|
||||||
|
vfs_node_unref(node);
|
||||||
|
|
||||||
|
if ((flags & VFS_LOOKUP_MUST_EXIST) && !node) {
|
||||||
|
status = ENOENT;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
if ((flags & VFS_LOOKUP_MUST_NOT_EXIST) && node) {
|
||||||
|
status = EEXIST;
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = current;
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
vfs_path_drop(current);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_path_lookup(
|
||||||
|
struct vfs_path root,
|
||||||
|
struct vfs_path cwd,
|
||||||
|
const char* path,
|
||||||
|
const struct ident* identity,
|
||||||
|
enum vfs_lookup_flags flags,
|
||||||
|
struct vfs_path* out
|
||||||
|
) {
|
||||||
|
size_t symlink_depth = 0;
|
||||||
|
return lookup_inner(root, cwd, path, identity, flags, &symlink_depth, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_path_absolute(struct vfs_path path, struct vfs_path* root, char* buf, size_t len, size_t* out_len) {
|
||||||
|
if (len == 0)
|
||||||
|
return ENAMETOOLONG;
|
||||||
|
|
||||||
|
struct vfs_path global_root;
|
||||||
|
if (!root) {
|
||||||
|
global_root = vfs_get_root();
|
||||||
|
root = &global_root;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t status = 0;
|
||||||
|
struct vfs_mount* mount = path.mount;
|
||||||
|
struct vfs_entry* entry = path.entry;
|
||||||
|
|
||||||
|
// Build the path back to front, leaving room for the NUL terminator.
|
||||||
|
size_t cursor = len - 1;
|
||||||
|
buf[cursor] = '\0';
|
||||||
|
|
||||||
|
while (entry != root->entry || mount != root->mount) {
|
||||||
|
// At the root of a mounted file system we must step into the parent file system.
|
||||||
|
if (entry == mount->root) {
|
||||||
|
if (!mount->has_mount_point) {
|
||||||
|
// Global root with no parent.
|
||||||
|
status = ENOENT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
entry = mount->mount_point.entry;
|
||||||
|
mount = mount->mount_point.mount;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (entry->name_len + 1 > cursor) {
|
||||||
|
status = ENAMETOOLONG;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
cursor -= entry->name_len;
|
||||||
|
memcpy(buf + cursor, entry->name, entry->name_len);
|
||||||
|
buf[--cursor] = '/';
|
||||||
|
|
||||||
|
if (!entry->parent) {
|
||||||
|
status = ENOENT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
entry = entry->parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The root itself resolves to "/".
|
||||||
|
if (cursor == len - 1) {
|
||||||
|
if (cursor == 0) {
|
||||||
|
status = ENAMETOOLONG;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
buf[--cursor] = '/';
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t result_len = len - 1 - cursor;
|
||||||
|
memmove(buf, buf + cursor, result_len + 1);
|
||||||
|
if (out_len)
|
||||||
|
*out_len = result_len;
|
||||||
|
|
||||||
|
out:
|
||||||
|
if (root == &global_root)
|
||||||
|
vfs_path_drop(global_root);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include <zinnia/alloc.h>
|
#include <zinnia/alloc.h>
|
||||||
#include <zinnia/assert.h>
|
#include <zinnia/assert.h>
|
||||||
#include <zinnia/elf.h>
|
#include <zinnia/elf.h>
|
||||||
|
#include <zinnia/exec.h>
|
||||||
#include <zinnia/errno.h>
|
#include <zinnia/errno.h>
|
||||||
#include <zinnia/mman.h>
|
#include <zinnia/mman.h>
|
||||||
#include <zinnia/percpu.h>
|
#include <zinnia/percpu.h>
|
||||||
|
|||||||
@@ -0,0 +1,628 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#include <zinnia/alloc.h>
|
||||||
|
#include <zinnia/assert.h>
|
||||||
|
#include <zinnia/atomic.h>
|
||||||
|
#include <zinnia/print.h>
|
||||||
|
#include <zinnia/vfs.h>
|
||||||
|
#include <stdckdint.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
errno_t vfs_file_new(struct vfs_node* node, const struct vfs_path* path, enum open_flags flags, struct vfs_file** out) {
|
||||||
|
assert(node != nullptr, "Every open file must be backed by an inode");
|
||||||
|
|
||||||
|
struct vfs_file* file = mem_alloc(sizeof(*file), 0);
|
||||||
|
if (!file)
|
||||||
|
return ENOMEM;
|
||||||
|
|
||||||
|
refcount_init(&file->rc);
|
||||||
|
file->ops = node->file_ops;
|
||||||
|
file->vnode = node;
|
||||||
|
vfs_node_ref(node);
|
||||||
|
atomic_init(&file->flags, flags);
|
||||||
|
mutex_init(&file->lock);
|
||||||
|
|
||||||
|
switch (node->type) {
|
||||||
|
case VNODE_REGULAR:
|
||||||
|
case VNODE_DIRECTORY:
|
||||||
|
file->position_kind = VFS_POSITION_ATOMIC;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
file->position_kind = VFS_POSITION_POSITION;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (path) {
|
||||||
|
file->path = vfs_path_clone(*path);
|
||||||
|
file->has_path = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = file;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_stream_open(struct vfs_file* file) {
|
||||||
|
file->position_kind = VFS_POSITION_STREAM;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolves the absolute path of `path` as seen from `root` into a fitting allocation.
|
||||||
|
static char* make_absolute_path(struct vfs_path path, struct vfs_path* root) {
|
||||||
|
char* buf = mem_alloc(__PATH_MAX, ALLOC_NOZERO);
|
||||||
|
if (!buf)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
size_t len;
|
||||||
|
char* result = nullptr;
|
||||||
|
if (vfs_path_absolute(path, root, buf, __PATH_MAX, &len) == 0) {
|
||||||
|
result = mem_alloc(len + 1, ALLOC_NOZERO);
|
||||||
|
if (result)
|
||||||
|
memcpy(result, buf, len + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
mem_free(buf);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
static errno_t open_special(
|
||||||
|
struct vfs_path* root,
|
||||||
|
struct vfs_path* file_path,
|
||||||
|
struct vfs_node* node,
|
||||||
|
enum open_flags flags,
|
||||||
|
struct vfs_file** out
|
||||||
|
) {
|
||||||
|
if ((node->type == VNODE_CHARDEV || node->type == VNODE_BLOCKDEV) && (!node->ops || !node->ops->device.open))
|
||||||
|
return ENXIO;
|
||||||
|
|
||||||
|
struct vfs_file* file;
|
||||||
|
errno_t status = vfs_file_new(node, file_path, flags, &file);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
file->absolute_path = make_absolute_path(*file_path, root);
|
||||||
|
|
||||||
|
status = node->type == VNODE_FIFO ? vfs_fifo_open(node, file) : node->ops->device.open(node, file);
|
||||||
|
if (status) {
|
||||||
|
file->ops = nullptr;
|
||||||
|
vfs_file_unref(file);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = file;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum access_flags open_access(enum open_flags flags) {
|
||||||
|
if (flags & O_PATH)
|
||||||
|
return F_OK;
|
||||||
|
|
||||||
|
enum access_flags access = F_OK;
|
||||||
|
if (flags & O_RDONLY)
|
||||||
|
access |= R_OK;
|
||||||
|
if (flags & (O_WRONLY | O_TRUNC))
|
||||||
|
access |= W_OK;
|
||||||
|
return access;
|
||||||
|
}
|
||||||
|
|
||||||
|
static errno_t do_open_inode(
|
||||||
|
struct vfs_path* root,
|
||||||
|
struct vfs_path* file_path,
|
||||||
|
struct vfs_node* node,
|
||||||
|
enum open_flags flags,
|
||||||
|
bool created,
|
||||||
|
const struct ident* identity,
|
||||||
|
struct vfs_file** out
|
||||||
|
) {
|
||||||
|
// If we want to open as a directory, make sure this is actually a directory.
|
||||||
|
if ((flags & O_DIRECTORY) && node->type != VNODE_DIRECTORY)
|
||||||
|
return ENOTDIR;
|
||||||
|
|
||||||
|
if (node->type == VNODE_DIRECTORY && (flags & (O_WRONLY | O_CREAT | O_TRUNC)))
|
||||||
|
return EISDIR;
|
||||||
|
|
||||||
|
errno_t status = created ? 0 : vfs_node_try_access(node, identity, open_access(flags), false);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
switch (node->type) {
|
||||||
|
case VNODE_REGULAR: {
|
||||||
|
if (flags & O_TRUNC) {
|
||||||
|
if (!(flags & O_WRONLY))
|
||||||
|
return EINVAL;
|
||||||
|
if (!node->ops->regular.truncate)
|
||||||
|
return EPERM;
|
||||||
|
status = node->ops->regular.truncate(node, 0);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct vfs_file* file;
|
||||||
|
status = vfs_file_new(node, file_path, flags, &file);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
file->absolute_path = make_absolute_path(*file_path, root);
|
||||||
|
*out = file;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
case VNODE_DIRECTORY:
|
||||||
|
if (node->ops->dir.open)
|
||||||
|
return node->ops->dir.open(node, file_path, flags, identity, out);
|
||||||
|
return vfs_file_new(node, file_path, flags, out);
|
||||||
|
case VNODE_SYMLINK:
|
||||||
|
return ELOOP;
|
||||||
|
case VNODE_CHARDEV:
|
||||||
|
case VNODE_BLOCKDEV:
|
||||||
|
case VNODE_FIFO:
|
||||||
|
return open_special(root, file_path, node, flags, out);
|
||||||
|
case VNODE_SOCKET:
|
||||||
|
return ENXIO;
|
||||||
|
default:
|
||||||
|
return ENOTSUP;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_file_open(
|
||||||
|
struct vfs_path root,
|
||||||
|
struct vfs_path cwd,
|
||||||
|
const char* path,
|
||||||
|
enum open_flags flags,
|
||||||
|
mode_t mode,
|
||||||
|
const struct ident* identity,
|
||||||
|
struct vfs_file** out
|
||||||
|
) {
|
||||||
|
if ((flags & O_DIRECTORY) && (flags & (O_CREAT | O_TMPFILE)))
|
||||||
|
return EINVAL;
|
||||||
|
|
||||||
|
const bool exclusive = (flags & (O_CREAT | O_EXCL)) == (O_CREAT | O_EXCL);
|
||||||
|
|
||||||
|
enum vfs_lookup_flags lookup_flags = 0;
|
||||||
|
// If we don't want to create a file when opening, the lookup must succeed.
|
||||||
|
// If we want to create a temporary unnamed file, `path` is the directory of the owning FS.
|
||||||
|
// In that case the path also has to exist.
|
||||||
|
if (!(flags & O_CREAT) || (flags & O_TMPFILE))
|
||||||
|
lookup_flags |= VFS_LOOKUP_MUST_EXIST;
|
||||||
|
if (exclusive)
|
||||||
|
lookup_flags |= VFS_LOOKUP_MUST_NOT_EXIST;
|
||||||
|
if (!(flags & O_NOFOLLOW) && !exclusive)
|
||||||
|
lookup_flags |= VFS_LOOKUP_FOLLOW_SYMLINKS;
|
||||||
|
|
||||||
|
struct vfs_path file_path;
|
||||||
|
errno_t status = vfs_path_lookup(root, cwd, path, identity, lookup_flags, &file_path);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
bool created = false;
|
||||||
|
struct vfs_node* node = vfs_entry_get_inode(file_path.entry);
|
||||||
|
if (!node) {
|
||||||
|
// If the lookup was successful, we expect that the entry is positive.
|
||||||
|
if (!(flags & O_CREAT)) {
|
||||||
|
log_warn("vfs: Tried opening a file without O_CREAT and backing inode, this is a bug!\n");
|
||||||
|
status = ENOENT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct vfs_path parent_path;
|
||||||
|
status = vfs_path_lookup_parent(file_path, &parent_path);
|
||||||
|
if (status)
|
||||||
|
goto out;
|
||||||
|
struct vfs_node* parent = vfs_entry_get_inode(parent_path.entry);
|
||||||
|
vfs_path_drop(parent_path);
|
||||||
|
if (!parent) {
|
||||||
|
status = ENOENT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = parent->type == VNODE_DIRECTORY ? 0 : ENOTDIR;
|
||||||
|
if (!status)
|
||||||
|
status = vfs_node_try_access(parent, identity, W_OK | X_OK, false);
|
||||||
|
if (!status)
|
||||||
|
status = parent->ops->dir.create ? parent->ops->dir.create(parent, file_path.entry, mode, identity) : EPERM;
|
||||||
|
vfs_node_unref(parent);
|
||||||
|
if (status)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
node = vfs_entry_get_inode(file_path.entry);
|
||||||
|
if (!node) {
|
||||||
|
status = EIO;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
created = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = do_open_inode(&root, &file_path, node, flags, created, identity, out);
|
||||||
|
vfs_node_unref(node);
|
||||||
|
|
||||||
|
out:
|
||||||
|
vfs_path_drop(file_path);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static errno_t advance_offset(off_t* offset, ssize_t amount) {
|
||||||
|
if (amount > 0 && ckd_add(offset, *offset, amount))
|
||||||
|
return EOVERFLOW;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_file_read(struct vfs_file* file, iovec_iter_t* buffer, ssize_t* out_read) {
|
||||||
|
*out_read = 0;
|
||||||
|
if (iovec_iter_len(buffer) == 0)
|
||||||
|
return 0;
|
||||||
|
if (!file->ops || !file->ops->read)
|
||||||
|
return EINVAL;
|
||||||
|
|
||||||
|
errno_t status;
|
||||||
|
ssize_t read = 0;
|
||||||
|
switch (file->position_kind) {
|
||||||
|
case VFS_POSITION_STREAM:
|
||||||
|
status = file->ops->read(file, buffer, 0, &read);
|
||||||
|
break;
|
||||||
|
case VFS_POSITION_ATOMIC:
|
||||||
|
mutex_lock(&file->lock);
|
||||||
|
status = file->ops->read(file, buffer, file->position, &read);
|
||||||
|
if (!status)
|
||||||
|
status = advance_offset(&file->position, read);
|
||||||
|
mutex_unlock(&file->lock);
|
||||||
|
break;
|
||||||
|
case VFS_POSITION_POSITION: {
|
||||||
|
mutex_lock(&file->lock);
|
||||||
|
off_t pos = file->position;
|
||||||
|
mutex_unlock(&file->lock);
|
||||||
|
|
||||||
|
status = file->ops->read(file, buffer, pos, &read);
|
||||||
|
if (!status)
|
||||||
|
status = advance_offset(&pos, read);
|
||||||
|
if (!status) {
|
||||||
|
mutex_lock(&file->lock);
|
||||||
|
file->position = pos;
|
||||||
|
mutex_unlock(&file->lock);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
*out_read = read;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_file_pread(struct vfs_file* file, iovec_iter_t* buffer, uint64_t offset, ssize_t* out_read) {
|
||||||
|
*out_read = 0;
|
||||||
|
if (iovec_iter_len(buffer) == 0)
|
||||||
|
return 0;
|
||||||
|
if (file->position_kind == VFS_POSITION_STREAM)
|
||||||
|
return ESPIPE;
|
||||||
|
if (!file->ops || !file->ops->read)
|
||||||
|
return EINVAL;
|
||||||
|
|
||||||
|
return file->ops->read(file, buffer, offset, out_read);
|
||||||
|
}
|
||||||
|
|
||||||
|
static errno_t check_write_progress(ssize_t written) {
|
||||||
|
if (written == 0) {
|
||||||
|
log_warn("vfs: write reported zero progress for a non-empty buffer\n");
|
||||||
|
return EIO;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static errno_t sync_after_write(struct vfs_file* file, ssize_t written) {
|
||||||
|
if (written <= 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
if ((atomic_load(&file->flags, ATOMIC_RELAXED) & O_SYNC) == O_SYNC)
|
||||||
|
return vfs_file_sync(file, false);
|
||||||
|
if ((atomic_load(&file->flags, ATOMIC_RELAXED) & O_DSYNC) == O_DSYNC)
|
||||||
|
return vfs_file_sync(file, true);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_file_write(struct vfs_file* file, iovec_iter_t* buffer, ssize_t* out_written) {
|
||||||
|
*out_written = 0;
|
||||||
|
if (iovec_iter_len(buffer) == 0)
|
||||||
|
return 0;
|
||||||
|
if (!file->ops || !file->ops->write)
|
||||||
|
return EINVAL;
|
||||||
|
|
||||||
|
// TODO: Appending writes through different files of the same inode aren't serialized.
|
||||||
|
const bool append = atomic_load(&file->flags, ATOMIC_RELAXED) & O_APPEND;
|
||||||
|
|
||||||
|
errno_t status;
|
||||||
|
ssize_t written = 0;
|
||||||
|
switch (file->position_kind) {
|
||||||
|
case VFS_POSITION_STREAM:
|
||||||
|
status = file->ops->write(file, buffer, 0, &written);
|
||||||
|
break;
|
||||||
|
case VFS_POSITION_ATOMIC:
|
||||||
|
mutex_lock(&file->lock);
|
||||||
|
if (append)
|
||||||
|
file->position = file->vnode->size;
|
||||||
|
status = file->ops->write(file, buffer, file->position, &written);
|
||||||
|
if (!status)
|
||||||
|
status = advance_offset(&file->position, written);
|
||||||
|
mutex_unlock(&file->lock);
|
||||||
|
break;
|
||||||
|
case VFS_POSITION_POSITION: {
|
||||||
|
mutex_lock(&file->lock);
|
||||||
|
off_t pos = append ? (off_t)file->vnode->size : file->position;
|
||||||
|
mutex_unlock(&file->lock);
|
||||||
|
|
||||||
|
status = file->ops->write(file, buffer, pos, &written);
|
||||||
|
if (!status)
|
||||||
|
status = advance_offset(&pos, written);
|
||||||
|
if (!status) {
|
||||||
|
mutex_lock(&file->lock);
|
||||||
|
file->position = pos;
|
||||||
|
mutex_unlock(&file->lock);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!status)
|
||||||
|
status = check_write_progress(written);
|
||||||
|
if (!status)
|
||||||
|
status = sync_after_write(file, written);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
*out_written = written;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_file_pwrite(struct vfs_file* file, iovec_iter_t* buffer, uint64_t offset, ssize_t* out_written) {
|
||||||
|
*out_written = 0;
|
||||||
|
if (iovec_iter_len(buffer) == 0)
|
||||||
|
return 0;
|
||||||
|
if (file->position_kind == VFS_POSITION_STREAM)
|
||||||
|
return ESPIPE;
|
||||||
|
if (!file->ops || !file->ops->write)
|
||||||
|
return EINVAL;
|
||||||
|
|
||||||
|
ssize_t written = 0;
|
||||||
|
errno_t status = file->ops->write(file, buffer, offset, &written);
|
||||||
|
if (!status)
|
||||||
|
status = check_write_progress(written);
|
||||||
|
if (!status)
|
||||||
|
status = sync_after_write(file, written);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
*out_written = written;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_file_ioctl(struct vfs_file* file, uintptr_t request, uintptr_t arg, uintptr_t* out_result) {
|
||||||
|
if (!file->ops || !file->ops->ioctl)
|
||||||
|
return ENOTTY;
|
||||||
|
return file->ops->ioctl(file, request, arg, out_result);
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_file_sync(struct vfs_file* file, bool data_only) {
|
||||||
|
if (!file->ops || !file->ops->sync)
|
||||||
|
return 0;
|
||||||
|
return file->ops->sync(file, data_only);
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_file_seek(struct vfs_file* file, enum vfs_seek_anchor anchor, off_t offset, off_t* out_position) {
|
||||||
|
if (file->position_kind == VFS_POSITION_STREAM)
|
||||||
|
return ESPIPE;
|
||||||
|
|
||||||
|
errno_t status = 0;
|
||||||
|
mutex_lock(&file->lock);
|
||||||
|
|
||||||
|
off_t base;
|
||||||
|
switch (anchor) {
|
||||||
|
case VFS_SEEK_START:
|
||||||
|
base = 0;
|
||||||
|
break;
|
||||||
|
case VFS_SEEK_CURRENT:
|
||||||
|
base = file->position;
|
||||||
|
break;
|
||||||
|
case VFS_SEEK_END:
|
||||||
|
base = file->vnode->size;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
status = EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t new_position;
|
||||||
|
if (ckd_add(&new_position, base, offset)) {
|
||||||
|
status = EOVERFLOW;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (new_position < 0) {
|
||||||
|
status = EINVAL;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
|
||||||
|
file->position = new_position;
|
||||||
|
if (out_position)
|
||||||
|
*out_position = new_position;
|
||||||
|
|
||||||
|
out:
|
||||||
|
mutex_unlock(&file->lock);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
off_t vfs_file_tell(struct vfs_file* file) {
|
||||||
|
mutex_lock(&file->lock);
|
||||||
|
off_t position = file->position;
|
||||||
|
mutex_unlock(&file->lock);
|
||||||
|
return position;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_file_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
|
||||||
|
) {
|
||||||
|
if (!file->ops || !file->ops->mmap)
|
||||||
|
return ENODEV;
|
||||||
|
return file->ops->mmap(file, vma, addr, len, prot, flags, offset, out_addr);
|
||||||
|
}
|
||||||
|
|
||||||
|
enum poll_flags vfs_poll(struct vfs_file* file, enum poll_flags events, struct poll_table* table) {
|
||||||
|
const enum poll_flags always = POLLERR | POLLHUP | POLLNVAL;
|
||||||
|
if (!file->ops || !file->ops->poll)
|
||||||
|
return events & (POLLIN | POLLOUT | POLLRDNORM | POLLWRNORM);
|
||||||
|
return file->ops->poll(file, events | POLLERR | POLLHUP, table) & (events | always);
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_file_get_object(struct vfs_file* file, struct vmo** out) {
|
||||||
|
struct vfs_node* node = file->vnode;
|
||||||
|
if (node->type != VNODE_REGULAR || !node->ops || !node->ops->regular.get_vmo)
|
||||||
|
return ENODEV;
|
||||||
|
return node->ops->regular.get_vmo(node, out);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint8_t dirent_type(enum vfs_node_type type) {
|
||||||
|
switch (type) {
|
||||||
|
case VNODE_REGULAR:
|
||||||
|
return __DT_REG;
|
||||||
|
case VNODE_DIRECTORY:
|
||||||
|
return __DT_DIR;
|
||||||
|
case VNODE_SYMLINK:
|
||||||
|
return __DT_LNK;
|
||||||
|
case VNODE_FIFO:
|
||||||
|
return __DT_FIFO;
|
||||||
|
case VNODE_BLOCKDEV:
|
||||||
|
return __DT_BLK;
|
||||||
|
case VNODE_CHARDEV:
|
||||||
|
return __DT_CHR;
|
||||||
|
case VNODE_SOCKET:
|
||||||
|
return __DT_SOCK;
|
||||||
|
default:
|
||||||
|
// Should be unreachable, but just as a failsafe in case someone messes up.
|
||||||
|
return __DT_UNKNOWN;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_dir_get_cached_entries(
|
||||||
|
struct vfs_node* node,
|
||||||
|
struct vfs_entry* entry,
|
||||||
|
off_t offset,
|
||||||
|
struct __dirent* buffer,
|
||||||
|
size_t buffer_len,
|
||||||
|
const struct ident* identity,
|
||||||
|
size_t* out_count
|
||||||
|
) {
|
||||||
|
size_t read = 0;
|
||||||
|
off_t current = 0;
|
||||||
|
|
||||||
|
mutex_lock(&entry->lock);
|
||||||
|
for (struct vfs_entry* child = tailq_first(&entry->children); child && read < buffer_len;
|
||||||
|
child = tailq_next(child, child_link)) {
|
||||||
|
struct vfs_node* child_node = vfs_entry_get_inode(child);
|
||||||
|
if (!child_node)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if (current < offset) {
|
||||||
|
current++;
|
||||||
|
vfs_node_unref(child_node);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct __dirent* d = &buffer[read];
|
||||||
|
d->d_ino = child_node->id;
|
||||||
|
d->d_off = current;
|
||||||
|
d->d_reclen = sizeof(*d);
|
||||||
|
d->d_type = dirent_type(child_node->type);
|
||||||
|
memcpy(d->d_name, child->name, child->name_len);
|
||||||
|
d->d_name[child->name_len] = '\0';
|
||||||
|
vfs_node_unref(child_node);
|
||||||
|
|
||||||
|
read++;
|
||||||
|
current++;
|
||||||
|
}
|
||||||
|
mutex_unlock(&entry->lock);
|
||||||
|
|
||||||
|
*out_count = read;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fill_dot_entry(struct __dirent* d, ino_t ino, off_t off, const char* name) {
|
||||||
|
d->d_ino = ino;
|
||||||
|
d->d_off = off;
|
||||||
|
d->d_reclen = sizeof(*d);
|
||||||
|
d->d_type = __DT_DIR;
|
||||||
|
memset(d->d_name, 0, sizeof(d->d_name));
|
||||||
|
memcpy(d->d_name, name, strlen(name));
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_get_dir_entries(
|
||||||
|
struct vfs_file* file,
|
||||||
|
struct __dirent* buffer,
|
||||||
|
size_t buffer_len,
|
||||||
|
const struct ident* identity,
|
||||||
|
size_t* out_count
|
||||||
|
) {
|
||||||
|
*out_count = 0;
|
||||||
|
if (buffer_len == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
struct vfs_node* node = file->vnode;
|
||||||
|
if (!file->has_path || node->type != VNODE_DIRECTORY)
|
||||||
|
return ENOTDIR;
|
||||||
|
if (file->position_kind == VFS_POSITION_STREAM)
|
||||||
|
return EBADF;
|
||||||
|
|
||||||
|
ino_t parent_ino = node->id;
|
||||||
|
struct vfs_path parent;
|
||||||
|
if (vfs_path_lookup_parent(file->path, &parent) == 0) {
|
||||||
|
struct vfs_node* parent_node = vfs_entry_get_inode(parent.entry);
|
||||||
|
if (parent_node) {
|
||||||
|
parent_ino = parent_node->id;
|
||||||
|
vfs_node_unref(parent_node);
|
||||||
|
}
|
||||||
|
vfs_path_drop(parent);
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t status = 0;
|
||||||
|
mutex_lock(&file->lock);
|
||||||
|
|
||||||
|
const off_t start_offset = file->position;
|
||||||
|
size_t num_read = 0;
|
||||||
|
|
||||||
|
// Read `.` and `..` entries.
|
||||||
|
if (start_offset == 0)
|
||||||
|
fill_dot_entry(&buffer[num_read++], node->id, 0, ".");
|
||||||
|
if ((start_offset == 1 || num_read == 1) && num_read < buffer_len)
|
||||||
|
fill_dot_entry(&buffer[num_read++], parent_ino, 1, "..");
|
||||||
|
|
||||||
|
// Read normal dir entries.
|
||||||
|
if (num_read < buffer_len && node->ops->dir.get_dir_entries) {
|
||||||
|
size_t count = 0;
|
||||||
|
status = node->ops->dir.get_dir_entries(
|
||||||
|
node,
|
||||||
|
file->path.entry,
|
||||||
|
start_offset > 2 ? start_offset - 2 : 0,
|
||||||
|
buffer + num_read,
|
||||||
|
buffer_len - num_read,
|
||||||
|
identity,
|
||||||
|
&count
|
||||||
|
);
|
||||||
|
if (status)
|
||||||
|
goto out;
|
||||||
|
num_read += count;
|
||||||
|
}
|
||||||
|
|
||||||
|
file->position += num_read;
|
||||||
|
for (size_t i = 0; i < num_read; i++)
|
||||||
|
buffer[i].d_off = start_offset + i + 1;
|
||||||
|
|
||||||
|
*out_count = num_read;
|
||||||
|
|
||||||
|
out:
|
||||||
|
mutex_unlock(&file->lock);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
@@ -15,3 +15,7 @@ bool ident_in_group(const struct ident* identity, gid_t gid, bool use_real) {
|
|||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool ident_is_privileged(const struct ident* identity, bool use_real) {
|
||||||
|
return (use_real ? identity->uid : identity->euid) == 0;
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,21 @@
|
|||||||
#include <zinnia/vfs.h>
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
void vfs_init() {}
|
#include <zinnia/alloc.h>
|
||||||
|
#include <zinnia/assert.h>
|
||||||
|
#include <zinnia/initgraph.h>
|
||||||
|
#include <zinnia/vfs.h>
|
||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
static void vfs_stage_init(struct boot_info* info) {
|
||||||
|
// Mount a tmpfs as root.
|
||||||
|
struct vfs_mount* tmpfs;
|
||||||
|
errno_t status = vfs_mount("tmpfs", 0, nullptr, &tmpfs);
|
||||||
|
assert(status == 0, "Unable to mount the root tmpfs (%i)", status);
|
||||||
|
|
||||||
|
struct vfs_path root = {.mount = tmpfs, .entry = tmpfs->root};
|
||||||
|
vfs_set_root(root);
|
||||||
|
vfs_mount_unref(tmpfs);
|
||||||
|
}
|
||||||
|
|
||||||
|
INIT_TASK(vfs_stage, "fs.vfs", vfs_stage_init);
|
||||||
|
INIT_DEPENDS(vfs_stage, mem_stage);
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <zinnia/vfs.h>
|
||||||
|
|
||||||
|
// Sets the global root of the VFS. Takes a new reference to `root`.
|
||||||
|
void vfs_set_root(struct vfs_path root);
|
||||||
|
|
||||||
|
// Returns true if both paths reference the same entry on the same mount.
|
||||||
|
static inline bool vfs_path_equal(struct vfs_path a, struct vfs_path b) {
|
||||||
|
return a.mount == b.mount && a.entry == b.entry;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pipe_destroy(struct pipe* pipe);
|
||||||
|
errno_t vfs_fifo_open(struct vfs_node* node, struct vfs_file* file);
|
||||||
+10
-2
@@ -1,7 +1,15 @@
|
|||||||
subdir('exec')
|
|
||||||
|
|
||||||
kernel_sources += files(
|
kernel_sources += files(
|
||||||
|
'anon.c',
|
||||||
|
'cache.c',
|
||||||
|
'file.c',
|
||||||
'ident.c',
|
'ident.c',
|
||||||
'init.c',
|
'init.c',
|
||||||
|
'mount.c',
|
||||||
|
'ops.c',
|
||||||
|
'pipe.c',
|
||||||
'poll.c',
|
'poll.c',
|
||||||
|
'refs.c',
|
||||||
|
'tmpfs.c',
|
||||||
)
|
)
|
||||||
|
|
||||||
|
subdir('exec')
|
||||||
|
|||||||
+226
@@ -0,0 +1,226 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#include <zinnia/alloc.h>
|
||||||
|
#include <zinnia/assert.h>
|
||||||
|
#include <zinnia/print.h>
|
||||||
|
#include <zinnia/spin.h>
|
||||||
|
#include <zinnia/vfs.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
// All known and registered file systems.
|
||||||
|
static struct spinlock fs_table_lock;
|
||||||
|
static vec(const struct vfs_filesystem*) fs_table;
|
||||||
|
|
||||||
|
// Super blocks of all mounted file systems. Each holds a reference.
|
||||||
|
static struct spinlock supers_lock;
|
||||||
|
static vec(struct vfs_super_block*) mounted_supers;
|
||||||
|
|
||||||
|
// The global root.
|
||||||
|
static struct vfs_path vfs_root;
|
||||||
|
static bool vfs_root_set;
|
||||||
|
|
||||||
|
void vfs_set_root(struct vfs_path root) {
|
||||||
|
assert(!vfs_root_set, "The VFS root can only be set once");
|
||||||
|
vfs_root = vfs_path_clone(root);
|
||||||
|
vfs_root_set = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct vfs_path vfs_get_root() {
|
||||||
|
assert(vfs_root_set, "The VFS root hasn't been set yet");
|
||||||
|
return vfs_path_clone(vfs_root);
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_register_filesystem(const struct vfs_filesystem* fs) {
|
||||||
|
errno_t status = 0;
|
||||||
|
|
||||||
|
spin_lock(&fs_table_lock);
|
||||||
|
vec_foreach(&fs_table, i) {
|
||||||
|
if (!strcmp(vec_elem_get(&fs_table, i)->name, fs->name)) {
|
||||||
|
status = EEXIST;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
status = vec_push(&fs_table, fs);
|
||||||
|
|
||||||
|
out:
|
||||||
|
spin_unlock(&fs_table_lock);
|
||||||
|
if (!status)
|
||||||
|
log("vfs: Registered new file system \"%s\"\n", fs->name);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns a new reference to the super block of `mount`, if it has one.
|
||||||
|
static struct vfs_super_block* mount_get_super(struct vfs_mount* mount) {
|
||||||
|
struct vfs_node* root = vfs_entry_get_inode(mount->root);
|
||||||
|
if (!root)
|
||||||
|
return nullptr;
|
||||||
|
|
||||||
|
struct vfs_super_block* sb = root->sb;
|
||||||
|
if (sb)
|
||||||
|
vfs_super_block_ref(sb);
|
||||||
|
vfs_node_unref(root);
|
||||||
|
return sb;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_mount(const char* fs_name, enum mount_flags flags, void* arg, struct vfs_mount** out) {
|
||||||
|
const struct vfs_filesystem* fs = nullptr;
|
||||||
|
|
||||||
|
spin_lock(&fs_table_lock);
|
||||||
|
vec_foreach(&fs_table, i) {
|
||||||
|
if (!strcmp(vec_elem_get(&fs_table, i)->name, fs_name)) {
|
||||||
|
fs = vec_elem_get(&fs_table, i);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spin_unlock(&fs_table_lock);
|
||||||
|
|
||||||
|
if (!fs) {
|
||||||
|
log_warn("Attempted to mount unknown file system type \"%s\"\n", fs_name);
|
||||||
|
return ENODEV;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct vfs_mount* mount;
|
||||||
|
errno_t status = fs->mount(fs, flags, arg, &mount);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
struct vfs_super_block* sb = mount_get_super(mount);
|
||||||
|
if (sb) {
|
||||||
|
bool known = false;
|
||||||
|
spin_lock(&supers_lock);
|
||||||
|
vec_foreach(&mounted_supers, i) {
|
||||||
|
if (vec_elem_get(&mounted_supers, i) == sb) {
|
||||||
|
known = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// The list takes over our reference.
|
||||||
|
if (!known && vec_push(&mounted_supers, sb) == 0)
|
||||||
|
sb = nullptr;
|
||||||
|
spin_unlock(&supers_lock);
|
||||||
|
|
||||||
|
if (sb)
|
||||||
|
vfs_super_block_unref(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = mount;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_unmount(struct vfs_mount* mount) {
|
||||||
|
struct vfs_super_block* sb = mount_get_super(mount);
|
||||||
|
if (!sb)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
spin_lock(&supers_lock);
|
||||||
|
vec_foreach(&mounted_supers, i) {
|
||||||
|
if (vec_elem_get(&mounted_supers, i) == sb) {
|
||||||
|
vec_swap_remove(&mounted_supers, i);
|
||||||
|
vfs_super_block_unref(sb);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spin_unlock(&supers_lock);
|
||||||
|
|
||||||
|
errno_t status = sb->ops && sb->ops->sync ? sb->ops->sync(sb) : 0;
|
||||||
|
vfs_super_block_unref(sb);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_sync_all() {
|
||||||
|
// Take a snapshot so we don't call into file systems while holding a spinlock.
|
||||||
|
spin_lock(&supers_lock);
|
||||||
|
size_t count = vec_length(&mounted_supers);
|
||||||
|
struct vfs_super_block** supers = count ? mem_alloc(count * sizeof(*supers), ALLOC_NOZERO) : nullptr;
|
||||||
|
if (count && !supers) {
|
||||||
|
spin_unlock(&supers_lock);
|
||||||
|
return ENOMEM;
|
||||||
|
}
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
supers[i] = vec_elem_get(&mounted_supers, i);
|
||||||
|
vfs_super_block_ref(supers[i]);
|
||||||
|
}
|
||||||
|
spin_unlock(&supers_lock);
|
||||||
|
|
||||||
|
errno_t result = 0;
|
||||||
|
for (size_t i = 0; i < count; i++) {
|
||||||
|
struct vfs_super_block* sb = supers[i];
|
||||||
|
if (sb->ops && sb->ops->sync) {
|
||||||
|
errno_t status = sb->ops->sync(sb);
|
||||||
|
if (status)
|
||||||
|
result = status;
|
||||||
|
}
|
||||||
|
vfs_super_block_unref(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
mem_free(supers);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_mount_new(enum mount_flags flags, struct vfs_entry* root, struct vfs_mount** out) {
|
||||||
|
struct vfs_mount* mount = mem_alloc(sizeof(*mount), 0);
|
||||||
|
if (!mount)
|
||||||
|
return ENOMEM;
|
||||||
|
|
||||||
|
refcount_init(&mount->rc);
|
||||||
|
mount->flags = flags;
|
||||||
|
mount->root = root;
|
||||||
|
vfs_entry_ref(root);
|
||||||
|
mount->has_mount_point = false;
|
||||||
|
|
||||||
|
*out = mount;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_path_mount(struct vfs_path at, struct vfs_mount* mount) {
|
||||||
|
if (mount->has_mount_point)
|
||||||
|
return EBUSY;
|
||||||
|
|
||||||
|
mutex_lock(&at.entry->lock);
|
||||||
|
errno_t status = vec_push(&at.entry->mounts, mount);
|
||||||
|
mutex_unlock(&at.entry->lock);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
vfs_mount_ref(mount);
|
||||||
|
mount->mount_point = vfs_path_clone(at);
|
||||||
|
mount->has_mount_point = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_path_unmount(struct vfs_path path) {
|
||||||
|
struct vfs_mount* mount = path.mount;
|
||||||
|
if (path.entry != mount->root || !mount->has_mount_point)
|
||||||
|
return EINVAL;
|
||||||
|
|
||||||
|
vfs_entry_prune(mount->root);
|
||||||
|
mutex_lock(&mount->root->lock);
|
||||||
|
const bool busy = !tailq_empty(&mount->root->children);
|
||||||
|
mutex_unlock(&mount->root->lock);
|
||||||
|
if (busy)
|
||||||
|
return EBUSY;
|
||||||
|
|
||||||
|
struct vfs_entry* point = mount->mount_point.entry;
|
||||||
|
bool found = false;
|
||||||
|
mutex_lock(&point->lock);
|
||||||
|
vec_foreach(&point->mounts, i) {
|
||||||
|
if (vec_elem_get(&point->mounts, i) == mount) {
|
||||||
|
vec_remove(&point->mounts, i);
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex_unlock(&point->lock);
|
||||||
|
if (!found)
|
||||||
|
return EINVAL;
|
||||||
|
|
||||||
|
struct vfs_path mount_point = mount->mount_point;
|
||||||
|
mount->mount_point = (struct vfs_path){};
|
||||||
|
mount->has_mount_point = false;
|
||||||
|
vfs_path_drop(mount_point);
|
||||||
|
|
||||||
|
errno_t status = vfs_unmount(mount);
|
||||||
|
vfs_mount_unref(mount);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
@@ -0,0 +1,656 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#include <zinnia/clock.h>
|
||||||
|
#include <zinnia/vfs.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
static bool is_read_only(struct vfs_node* node) {
|
||||||
|
return node->sb && (node->sb->flags & MNT_RDONLY);
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_node_try_access(
|
||||||
|
struct vfs_node* node,
|
||||||
|
const struct ident* identity,
|
||||||
|
enum access_flags access,
|
||||||
|
bool use_real
|
||||||
|
) {
|
||||||
|
mutex_lock(&node->lock);
|
||||||
|
const mode_t mode = node->mode;
|
||||||
|
const uid_t owner = node->uid;
|
||||||
|
const gid_t group = node->gid;
|
||||||
|
mutex_unlock(&node->lock);
|
||||||
|
|
||||||
|
if ((access & W_OK) && is_read_only(node) &&
|
||||||
|
(node->type == VNODE_REGULAR || node->type == VNODE_DIRECTORY || node->type == VNODE_SYMLINK))
|
||||||
|
return EROFS;
|
||||||
|
if ((access & X_OK) && node->type == VNODE_REGULAR && node->sb && (node->sb->flags & MNT_NOEXEC))
|
||||||
|
return EACCES;
|
||||||
|
|
||||||
|
if (ident_is_privileged(identity, use_real)) {
|
||||||
|
if ((access & X_OK) && node->type != VNODE_DIRECTORY && !(mode & (__S_IXUSR | __S_IXGRP | __S_IXOTH)))
|
||||||
|
return EACCES;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
mode_t granted;
|
||||||
|
if ((use_real ? identity->uid : identity->euid) == owner)
|
||||||
|
granted = (mode >> 6) & 7;
|
||||||
|
else if (ident_in_group(identity, group, use_real))
|
||||||
|
granted = (mode >> 3) & 7;
|
||||||
|
else
|
||||||
|
granted = mode & 7;
|
||||||
|
|
||||||
|
return (access & (R_OK | W_OK | X_OK) & ~granted) ? EACCES : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static errno_t sync_node(struct vfs_node* node) {
|
||||||
|
return node->ops && node->ops->sync ? node->ops->sync(node) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static timespec_t now() {
|
||||||
|
return time_from_nano(clock_get_realtime_ns());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Looks up the parent directory of `path` and checks that `identity` may write to it.
|
||||||
|
// Returns a new reference to the parent inode in `out`.
|
||||||
|
static errno_t get_writable_parent(struct vfs_path path, const struct ident* identity, struct vfs_node** out) {
|
||||||
|
struct vfs_path parent;
|
||||||
|
errno_t status = vfs_path_lookup_parent(path, &parent);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
struct vfs_node* node = vfs_entry_get_inode(parent.entry);
|
||||||
|
vfs_path_drop(parent);
|
||||||
|
if (!node)
|
||||||
|
return ENOENT;
|
||||||
|
|
||||||
|
if (node->type != VNODE_DIRECTORY) {
|
||||||
|
vfs_node_unref(node);
|
||||||
|
return ENOTDIR;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = vfs_node_try_access(node, identity, W_OK | X_OK, false);
|
||||||
|
if (status) {
|
||||||
|
vfs_node_unref(node);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
*out = node;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_mkdir(
|
||||||
|
struct vfs_path root,
|
||||||
|
struct vfs_path cwd,
|
||||||
|
const char* path,
|
||||||
|
mode_t mode,
|
||||||
|
const struct ident* identity
|
||||||
|
) {
|
||||||
|
struct vfs_path target;
|
||||||
|
errno_t status = vfs_path_lookup(root, cwd, path, identity, VFS_LOOKUP_MUST_NOT_EXIST, &target);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
struct vfs_node* parent;
|
||||||
|
status = get_writable_parent(target, identity, &parent);
|
||||||
|
if (status)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
status = parent->ops->dir.mkdir ? parent->ops->dir.mkdir(parent, &target, mode, identity) : EPERM;
|
||||||
|
vfs_node_unref(parent);
|
||||||
|
|
||||||
|
out:
|
||||||
|
vfs_path_drop(target);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_symlink(
|
||||||
|
struct vfs_path root,
|
||||||
|
struct vfs_path cwd,
|
||||||
|
const char* path,
|
||||||
|
const char* target_path,
|
||||||
|
const struct ident* identity
|
||||||
|
) {
|
||||||
|
struct vfs_path target;
|
||||||
|
errno_t status = vfs_path_lookup(root, cwd, path, identity, VFS_LOOKUP_MUST_NOT_EXIST, &target);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
struct vfs_node* parent;
|
||||||
|
status = get_writable_parent(target, identity, &parent);
|
||||||
|
if (status)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
status = parent->ops->dir.symlink
|
||||||
|
? parent->ops->dir.symlink(parent, &target, target_path, strlen(target_path), identity)
|
||||||
|
: EPERM;
|
||||||
|
vfs_node_unref(parent);
|
||||||
|
|
||||||
|
out:
|
||||||
|
vfs_path_drop(target);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool last_component_is(const char* path, const char* name) {
|
||||||
|
size_t len = strlen(path);
|
||||||
|
while (len > 1 && path[len - 1] == '/')
|
||||||
|
len--;
|
||||||
|
size_t begin = len;
|
||||||
|
while (begin > 0 && path[begin - 1] != '/')
|
||||||
|
begin--;
|
||||||
|
return len - begin == strlen(name) && !memcmp(path + begin, name, len - begin);
|
||||||
|
}
|
||||||
|
|
||||||
|
static errno_t check_sticky(struct vfs_node* dir, struct vfs_node* victim, const struct ident* identity) {
|
||||||
|
if (identity->euid == 0)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
mutex_lock(&dir->lock);
|
||||||
|
const bool sticky = dir->mode & __S_ISVTX;
|
||||||
|
const uid_t dir_owner = dir->uid;
|
||||||
|
mutex_unlock(&dir->lock);
|
||||||
|
if (!sticky)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
mutex_lock(&victim->lock);
|
||||||
|
const uid_t owner = victim->uid;
|
||||||
|
mutex_unlock(&victim->lock);
|
||||||
|
return identity->euid == owner || identity->euid == dir_owner ? 0 : EPERM;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_unlink(struct vfs_path root, struct vfs_path cwd, const char* path, const struct ident* identity) {
|
||||||
|
struct vfs_path target;
|
||||||
|
errno_t status = vfs_path_lookup(root, cwd, path, identity, VFS_LOOKUP_MUST_EXIST, &target);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
struct vfs_node* node = vfs_entry_get_inode(target.entry);
|
||||||
|
if (!node) {
|
||||||
|
status = ENOENT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (node->type == VNODE_DIRECTORY) {
|
||||||
|
status = EISDIR;
|
||||||
|
goto out_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct vfs_node* parent;
|
||||||
|
status = get_writable_parent(target, identity, &parent);
|
||||||
|
if (status)
|
||||||
|
goto out_node;
|
||||||
|
|
||||||
|
status = check_sticky(parent, node, identity);
|
||||||
|
if (!status)
|
||||||
|
status = parent->ops->dir.unlink ? parent->ops->dir.unlink(parent, &target, identity) : EPERM;
|
||||||
|
vfs_node_unref(parent);
|
||||||
|
|
||||||
|
out_node:
|
||||||
|
vfs_node_unref(node);
|
||||||
|
out:
|
||||||
|
vfs_path_drop(target);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probe for a single entry to check if the directory is empty.
|
||||||
|
static errno_t check_dir_empty(struct vfs_node* node, struct vfs_entry* entry, const struct ident* identity) {
|
||||||
|
struct __dirent probe;
|
||||||
|
size_t count = 0;
|
||||||
|
if (node->ops->dir.get_dir_entries) {
|
||||||
|
errno_t status = node->ops->dir.get_dir_entries(node, entry, 0, &probe, 1, identity, &count);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
return count > 0 ? ENOTEMPTY : 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_rmdir(struct vfs_path root, struct vfs_path cwd, const char* path, const struct ident* identity) {
|
||||||
|
if (last_component_is(path, "."))
|
||||||
|
return EINVAL;
|
||||||
|
if (last_component_is(path, ".."))
|
||||||
|
return ENOTEMPTY;
|
||||||
|
|
||||||
|
struct vfs_path target;
|
||||||
|
errno_t status = vfs_path_lookup(root, cwd, path, identity, VFS_LOOKUP_MUST_EXIST, &target);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
struct vfs_node* node = vfs_entry_get_inode(target.entry);
|
||||||
|
if (!node) {
|
||||||
|
status = ENOENT;
|
||||||
|
goto out;
|
||||||
|
}
|
||||||
|
if (node->type != VNODE_DIRECTORY) {
|
||||||
|
status = ENOTDIR;
|
||||||
|
goto out_node;
|
||||||
|
}
|
||||||
|
if (target.entry == target.mount->root) {
|
||||||
|
status = EBUSY;
|
||||||
|
goto out_node;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = check_dir_empty(node, target.entry, identity);
|
||||||
|
if (status)
|
||||||
|
goto out_node;
|
||||||
|
|
||||||
|
struct vfs_node* parent;
|
||||||
|
status = get_writable_parent(target, identity, &parent);
|
||||||
|
if (status)
|
||||||
|
goto out_node;
|
||||||
|
|
||||||
|
status = check_sticky(parent, node, identity);
|
||||||
|
if (!status) {
|
||||||
|
if (parent->ops->dir.rmdir)
|
||||||
|
status = parent->ops->dir.rmdir(parent, &target, identity);
|
||||||
|
else if (parent->ops->dir.unlink)
|
||||||
|
status = parent->ops->dir.unlink(parent, &target, identity);
|
||||||
|
else
|
||||||
|
status = EPERM;
|
||||||
|
}
|
||||||
|
vfs_node_unref(parent);
|
||||||
|
|
||||||
|
out_node:
|
||||||
|
vfs_node_unref(node);
|
||||||
|
out:
|
||||||
|
vfs_path_drop(target);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_mknod(
|
||||||
|
struct vfs_path root,
|
||||||
|
struct vfs_path cwd,
|
||||||
|
const char* path,
|
||||||
|
mode_t mode,
|
||||||
|
struct vfs_mknod_target* target,
|
||||||
|
const struct ident* identity,
|
||||||
|
struct vfs_node** out
|
||||||
|
) {
|
||||||
|
struct vfs_path new_path;
|
||||||
|
errno_t status = vfs_path_lookup(root, cwd, path, identity, VFS_LOOKUP_MUST_NOT_EXIST, &new_path);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
struct vfs_node* parent;
|
||||||
|
status = get_writable_parent(new_path, identity, &parent);
|
||||||
|
if (status)
|
||||||
|
goto out;
|
||||||
|
|
||||||
|
status = parent->ops->dir.mknod ? parent->ops->dir.mknod(parent, &new_path, mode, target, identity) : ENODEV;
|
||||||
|
vfs_node_unref(parent);
|
||||||
|
if (!status && out) {
|
||||||
|
*out = vfs_entry_get_inode(new_path.entry);
|
||||||
|
if (!*out)
|
||||||
|
status = ENOENT;
|
||||||
|
}
|
||||||
|
|
||||||
|
out:
|
||||||
|
vfs_path_drop(new_path);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool entry_is_ancestor(struct vfs_entry* ancestor, struct vfs_entry* entry) {
|
||||||
|
for (struct vfs_entry* it = entry; it; it = it->parent) {
|
||||||
|
if (it == ancestor)
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool entry_points_to(struct vfs_entry* entry, struct vfs_node* node) {
|
||||||
|
struct vfs_node* current = vfs_entry_get_inode(entry);
|
||||||
|
if (current)
|
||||||
|
vfs_node_unref(current);
|
||||||
|
return current == node;
|
||||||
|
}
|
||||||
|
|
||||||
|
static errno_t check_rename_target(struct vfs_node* moved, struct vfs_path target, const struct ident* identity) {
|
||||||
|
struct vfs_node* existing = vfs_entry_get_inode(target.entry);
|
||||||
|
if (!existing)
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
errno_t status = 0;
|
||||||
|
if (moved->type == VNODE_DIRECTORY && existing->type != VNODE_DIRECTORY)
|
||||||
|
status = ENOTDIR;
|
||||||
|
else if (moved->type != VNODE_DIRECTORY && existing->type == VNODE_DIRECTORY)
|
||||||
|
status = EISDIR;
|
||||||
|
else if (existing->type == VNODE_DIRECTORY)
|
||||||
|
status = check_dir_empty(existing, target.entry, identity);
|
||||||
|
|
||||||
|
vfs_node_unref(existing);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static errno_t check_rename_perms(
|
||||||
|
struct vfs_node* moved,
|
||||||
|
struct vfs_path new,
|
||||||
|
struct vfs_node* old_parent,
|
||||||
|
struct vfs_node* new_parent,
|
||||||
|
const struct ident* identity
|
||||||
|
) {
|
||||||
|
errno_t status = check_sticky(old_parent, moved, identity);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
struct vfs_node* existing = vfs_entry_get_inode(new.entry);
|
||||||
|
if (existing) {
|
||||||
|
status = check_sticky(new_parent, existing, identity);
|
||||||
|
vfs_node_unref(existing);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moved->type == VNODE_DIRECTORY && old_parent != new_parent)
|
||||||
|
return vfs_node_try_access(moved, identity, W_OK, false);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static errno_t do_rename(
|
||||||
|
struct vfs_path old,
|
||||||
|
struct vfs_path new,
|
||||||
|
struct vfs_node* moved,
|
||||||
|
const struct ident* identity
|
||||||
|
) {
|
||||||
|
struct vfs_node* old_parent;
|
||||||
|
errno_t status = get_writable_parent(old, identity, &old_parent);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
struct vfs_node* new_parent;
|
||||||
|
status = get_writable_parent(new, identity, &new_parent);
|
||||||
|
if (status) {
|
||||||
|
vfs_node_unref(old_parent);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
status = check_rename_perms(moved, new, old_parent, new_parent, identity);
|
||||||
|
if (!status) {
|
||||||
|
if (old_parent->ops->dir.rename)
|
||||||
|
status = old_parent->ops->dir.rename(old_parent, &old, new_parent, &new, identity);
|
||||||
|
else
|
||||||
|
status = EPERM;
|
||||||
|
}
|
||||||
|
vfs_node_unref(new_parent);
|
||||||
|
vfs_node_unref(old_parent);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_rename(
|
||||||
|
struct vfs_path root,
|
||||||
|
struct vfs_path old_cwd,
|
||||||
|
const char* old_path,
|
||||||
|
struct vfs_path new_cwd,
|
||||||
|
const char* new_path,
|
||||||
|
const struct ident* identity
|
||||||
|
) {
|
||||||
|
if (last_component_is(old_path, ".") || last_component_is(old_path, "..") || last_component_is(new_path, ".") ||
|
||||||
|
last_component_is(new_path, ".."))
|
||||||
|
return EINVAL;
|
||||||
|
|
||||||
|
struct vfs_path old;
|
||||||
|
errno_t status = vfs_path_lookup(root, old_cwd, old_path, identity, VFS_LOOKUP_MUST_EXIST, &old);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
struct vfs_path new;
|
||||||
|
status = vfs_path_lookup(root, new_cwd, new_path, identity, 0, &new);
|
||||||
|
if (status)
|
||||||
|
goto out_old;
|
||||||
|
|
||||||
|
if (old.mount != new.mount) {
|
||||||
|
status = EXDEV;
|
||||||
|
goto out_new;
|
||||||
|
}
|
||||||
|
if (old.entry == old.mount->root || new.entry == new.mount->root) {
|
||||||
|
status = EBUSY;
|
||||||
|
goto out_new;
|
||||||
|
}
|
||||||
|
if (old.entry == new.entry)
|
||||||
|
goto out_new;
|
||||||
|
|
||||||
|
struct vfs_node* moved = vfs_entry_get_inode(old.entry);
|
||||||
|
if (!moved) {
|
||||||
|
status = ENOENT;
|
||||||
|
goto out_new;
|
||||||
|
}
|
||||||
|
if (moved->type == VNODE_DIRECTORY && entry_is_ancestor(old.entry, new.entry))
|
||||||
|
status = EINVAL;
|
||||||
|
if (!status && !entry_points_to(new.entry, moved)) {
|
||||||
|
status = check_rename_target(moved, new, identity);
|
||||||
|
if (!status)
|
||||||
|
status = do_rename(old, new, moved, identity);
|
||||||
|
}
|
||||||
|
|
||||||
|
vfs_node_unref(moved);
|
||||||
|
out_new:
|
||||||
|
vfs_path_drop(new);
|
||||||
|
out_old:
|
||||||
|
vfs_path_drop(old);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_link(
|
||||||
|
struct vfs_path root,
|
||||||
|
struct vfs_path old_cwd,
|
||||||
|
const char* old_path,
|
||||||
|
struct vfs_path new_cwd,
|
||||||
|
const char* new_path,
|
||||||
|
bool follow_symlinks,
|
||||||
|
const struct ident* identity
|
||||||
|
) {
|
||||||
|
enum vfs_lookup_flags flags = VFS_LOOKUP_MUST_EXIST;
|
||||||
|
if (follow_symlinks)
|
||||||
|
flags |= VFS_LOOKUP_FOLLOW_SYMLINKS;
|
||||||
|
|
||||||
|
struct vfs_path old;
|
||||||
|
errno_t status = vfs_path_lookup(root, old_cwd, old_path, identity, flags, &old);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
struct vfs_node* target = vfs_entry_get_inode(old.entry);
|
||||||
|
if (!target) {
|
||||||
|
status = ENOENT;
|
||||||
|
goto out_old;
|
||||||
|
}
|
||||||
|
if (target->type == VNODE_DIRECTORY) {
|
||||||
|
status = EPERM;
|
||||||
|
goto out_target;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct vfs_path new;
|
||||||
|
status = vfs_path_lookup(root, new_cwd, new_path, identity, VFS_LOOKUP_MUST_NOT_EXIST, &new);
|
||||||
|
if (status)
|
||||||
|
goto out_target;
|
||||||
|
|
||||||
|
if (new.mount != old.mount) {
|
||||||
|
status = EXDEV;
|
||||||
|
goto out_new;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct vfs_node* parent;
|
||||||
|
status = get_writable_parent(new, identity, &parent);
|
||||||
|
if (status)
|
||||||
|
goto out_new;
|
||||||
|
|
||||||
|
status = parent->ops->dir.link ? parent->ops->dir.link(parent, &new, target, identity) : EPERM;
|
||||||
|
vfs_node_unref(parent);
|
||||||
|
|
||||||
|
out_new:
|
||||||
|
vfs_path_drop(new);
|
||||||
|
out_target:
|
||||||
|
vfs_node_unref(target);
|
||||||
|
out_old:
|
||||||
|
vfs_path_drop(old);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static mode_t node_type_mode(enum vfs_node_type type) {
|
||||||
|
switch (type) {
|
||||||
|
case VNODE_DIRECTORY:
|
||||||
|
return __S_IFDIR;
|
||||||
|
case VNODE_SYMLINK:
|
||||||
|
return __S_IFLNK;
|
||||||
|
case VNODE_FIFO:
|
||||||
|
return __S_IFIFO;
|
||||||
|
case VNODE_BLOCKDEV:
|
||||||
|
return __S_IFBLK;
|
||||||
|
case VNODE_CHARDEV:
|
||||||
|
return __S_IFCHR;
|
||||||
|
case VNODE_SOCKET:
|
||||||
|
return __S_IFSOCK;
|
||||||
|
default:
|
||||||
|
return __S_IFREG;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_stat(struct vfs_node* node, struct __stat* out) {
|
||||||
|
mutex_lock(&node->lock);
|
||||||
|
*out = (struct __stat){
|
||||||
|
.st_ino = node->id,
|
||||||
|
.st_mode = node->mode | node_type_mode(node->type),
|
||||||
|
.st_nlink = node->nlink,
|
||||||
|
.st_uid = node->uid,
|
||||||
|
.st_gid = node->gid,
|
||||||
|
.st_rdev = node->rdev,
|
||||||
|
.st_size = node->size,
|
||||||
|
.st_atim = node->atime,
|
||||||
|
.st_mtim = node->mtime,
|
||||||
|
.st_ctim = node->ctime,
|
||||||
|
.st_blksize = 4096, // TODO: Get from file system
|
||||||
|
.st_blocks = node->blocks,
|
||||||
|
};
|
||||||
|
mutex_unlock(&node->lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool is_owner(struct vfs_node* node, const struct ident* identity) {
|
||||||
|
return identity->euid == 0 || identity->euid == node->uid;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_chmod(struct vfs_node* node, mode_t mode, const struct ident* identity) {
|
||||||
|
if (is_read_only(node))
|
||||||
|
return EROFS;
|
||||||
|
|
||||||
|
mutex_lock(&node->lock);
|
||||||
|
if (!is_owner(node, identity)) {
|
||||||
|
mutex_unlock(&node->lock);
|
||||||
|
return EPERM;
|
||||||
|
}
|
||||||
|
mode &= 07777;
|
||||||
|
if (identity->euid != 0 && node->type == VNODE_REGULAR && !ident_in_group(identity, node->gid, false))
|
||||||
|
mode &= ~__S_ISGID;
|
||||||
|
node->mode = mode;
|
||||||
|
node->ctime = now();
|
||||||
|
mutex_unlock(&node->lock);
|
||||||
|
|
||||||
|
return sync_node(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_chown(struct vfs_node* node, uid_t uid, gid_t gid, const struct ident* identity) {
|
||||||
|
if (is_read_only(node))
|
||||||
|
return EROFS;
|
||||||
|
|
||||||
|
mutex_lock(&node->lock);
|
||||||
|
if (identity->euid != 0) {
|
||||||
|
const bool uid_ok = uid == (uid_t)-1 || uid == node->uid;
|
||||||
|
const bool gid_ok = gid == (gid_t)-1 || gid == node->gid || ident_in_group(identity, gid, false);
|
||||||
|
if (identity->euid != node->uid || !uid_ok || !gid_ok) {
|
||||||
|
mutex_unlock(&node->lock);
|
||||||
|
return EPERM;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uid == (uid_t)-1 && gid == (gid_t)-1) {
|
||||||
|
mutex_unlock(&node->lock);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uid != (uid_t)-1)
|
||||||
|
node->uid = uid;
|
||||||
|
if (gid != (gid_t)-1)
|
||||||
|
node->gid = gid;
|
||||||
|
if (identity->euid != 0 && node->type == VNODE_REGULAR && (node->mode & (__S_IXUSR | __S_IXGRP | __S_IXOTH)))
|
||||||
|
node->mode &= ~(__S_ISUID | __S_ISGID);
|
||||||
|
node->ctime = now();
|
||||||
|
mutex_unlock(&node->lock);
|
||||||
|
|
||||||
|
return sync_node(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool valid_utime(const timespec_t* ts) {
|
||||||
|
return ts->tv_nsec == __UTIME_NOW || ts->tv_nsec == __UTIME_OMIT ||
|
||||||
|
(ts->tv_nsec >= 0 && ts->tv_nsec < 1'000'000'000);
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_utimens(struct vfs_node* node, const timespec_t* times, const struct ident* identity) {
|
||||||
|
const timespec_t current = now();
|
||||||
|
timespec_t ts[2] = {
|
||||||
|
{.tv_nsec = __UTIME_NOW},
|
||||||
|
{.tv_nsec = __UTIME_NOW},
|
||||||
|
};
|
||||||
|
if (times) {
|
||||||
|
ts[0] = times[0];
|
||||||
|
ts[1] = times[1];
|
||||||
|
if (!valid_utime(&ts[0]) || !valid_utime(&ts[1]))
|
||||||
|
return EINVAL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ts[0].tv_nsec == __UTIME_OMIT && ts[1].tv_nsec == __UTIME_OMIT)
|
||||||
|
return 0;
|
||||||
|
if (is_read_only(node))
|
||||||
|
return EROFS;
|
||||||
|
|
||||||
|
const bool only_now = (ts[0].tv_nsec == __UTIME_NOW || ts[0].tv_nsec == __UTIME_OMIT) &&
|
||||||
|
(ts[1].tv_nsec == __UTIME_NOW || ts[1].tv_nsec == __UTIME_OMIT);
|
||||||
|
|
||||||
|
mutex_lock(&node->lock);
|
||||||
|
const bool owner = is_owner(node, identity);
|
||||||
|
mutex_unlock(&node->lock);
|
||||||
|
|
||||||
|
if (!owner) {
|
||||||
|
if (!only_now)
|
||||||
|
return EPERM;
|
||||||
|
errno_t status = vfs_node_try_access(node, identity, W_OK, false);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_lock(&node->lock);
|
||||||
|
for (size_t i = 0; i < 2; i++) {
|
||||||
|
if (ts[i].tv_nsec == __UTIME_OMIT)
|
||||||
|
continue;
|
||||||
|
timespec_t* target = i == 0 ? &node->atime : &node->mtime;
|
||||||
|
*target = ts[i].tv_nsec == __UTIME_NOW ? current : ts[i];
|
||||||
|
}
|
||||||
|
node->ctime = current;
|
||||||
|
mutex_unlock(&node->lock);
|
||||||
|
|
||||||
|
return sync_node(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_node_check_exec(struct vfs_node* node, const struct ident* identity) {
|
||||||
|
if (node->type != VNODE_REGULAR)
|
||||||
|
return EACCES;
|
||||||
|
return vfs_node_try_access(node, identity, X_OK, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_truncate(struct vfs_node* node, uint64_t length) {
|
||||||
|
if (node->type == VNODE_DIRECTORY)
|
||||||
|
return EISDIR;
|
||||||
|
if (node->type != VNODE_REGULAR)
|
||||||
|
return EINVAL;
|
||||||
|
if (!node->ops->regular.truncate)
|
||||||
|
return EPERM;
|
||||||
|
return node->ops->regular.truncate(node, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_read_link(struct vfs_node* node, iovec_iter_t* iter, size_t* out_read) {
|
||||||
|
if (node->type != VNODE_SYMLINK)
|
||||||
|
return EINVAL;
|
||||||
|
if (!node->ops->symlink.read_link)
|
||||||
|
return EINVAL;
|
||||||
|
return node->ops->symlink.read_link(node, iter, out_read);
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_statvfs(struct vfs_node* node, struct __statvfs* out) {
|
||||||
|
if (!node->sb || !node->sb->ops || !node->sb->ops->statvfs)
|
||||||
|
return ENOSYS;
|
||||||
|
return node->sb->ops->statvfs(node->sb, out);
|
||||||
|
}
|
||||||
@@ -0,0 +1,325 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#include <zinnia/alloc.h>
|
||||||
|
#include <zinnia/atomic.h>
|
||||||
|
#include <zinnia/percpu.h>
|
||||||
|
#include <zinnia/pipe.h>
|
||||||
|
#include <zinnia/poll.h>
|
||||||
|
#include <zinnia/ringbuf.h>
|
||||||
|
#include <zinnia/sched.h>
|
||||||
|
#include <zinnia/usercopy.h>
|
||||||
|
#include <zinnia/vfs.h>
|
||||||
|
#include <uapi/ioctls.h>
|
||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
constexpr size_t PIPE_CAPACITY = 0x10000;
|
||||||
|
|
||||||
|
struct pipe {
|
||||||
|
mutex_t lock;
|
||||||
|
struct ringbuf buffer;
|
||||||
|
size_t readers;
|
||||||
|
size_t writers;
|
||||||
|
uint64_t read_opens;
|
||||||
|
uint64_t write_opens;
|
||||||
|
struct poll_event read_event;
|
||||||
|
struct poll_event write_event;
|
||||||
|
struct poll_event open_event;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct vfs_file_ops pipe_file_ops;
|
||||||
|
|
||||||
|
static errno_t pipe_new(struct pipe** out) {
|
||||||
|
struct pipe* pipe = mem_alloc(sizeof(*pipe), 0);
|
||||||
|
if (!pipe)
|
||||||
|
return ENOMEM;
|
||||||
|
|
||||||
|
errno_t status = ringbuf_init(&pipe->buffer, PIPE_CAPACITY);
|
||||||
|
if (status) {
|
||||||
|
mem_free(pipe);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
mutex_init(&pipe->lock);
|
||||||
|
poll_event_init(&pipe->read_event);
|
||||||
|
poll_event_init(&pipe->write_event);
|
||||||
|
poll_event_init(&pipe->open_event);
|
||||||
|
*out = pipe;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void pipe_destroy(struct pipe* pipe) {
|
||||||
|
ringbuf_fini(&pipe->buffer);
|
||||||
|
mem_free(pipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool file_readable(struct vfs_file* file) {
|
||||||
|
return atomic_load(&file->flags, ATOMIC_RELAXED) & O_RDONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool file_writable(struct vfs_file* file) {
|
||||||
|
return atomic_load(&file->flags, ATOMIC_RELAXED) & O_WRONLY;
|
||||||
|
}
|
||||||
|
|
||||||
|
static bool file_nonblocking(struct vfs_file* file) {
|
||||||
|
return atomic_load(&file->flags, ATOMIC_RELAXED) & O_NONBLOCK;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void notify_all(struct pipe* pipe) {
|
||||||
|
poll_event_notify(&pipe->read_event);
|
||||||
|
poll_event_notify(&pipe->write_event);
|
||||||
|
poll_event_notify(&pipe->open_event);
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint64_t attach_locked(struct pipe* pipe, struct vfs_file* file) {
|
||||||
|
file->ops = &pipe_file_ops;
|
||||||
|
file->priv = pipe;
|
||||||
|
vfs_stream_open(file);
|
||||||
|
|
||||||
|
if (file_readable(file)) {
|
||||||
|
pipe->readers++;
|
||||||
|
pipe->read_opens++;
|
||||||
|
}
|
||||||
|
if (file_writable(file)) {
|
||||||
|
pipe->writers++;
|
||||||
|
pipe->write_opens++;
|
||||||
|
}
|
||||||
|
return file_readable(file) ? pipe->write_opens : pipe->read_opens;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void pipe_release(struct vfs_file* file) {
|
||||||
|
struct pipe* pipe = file->priv;
|
||||||
|
|
||||||
|
mutex_lock(&pipe->lock);
|
||||||
|
if (file_readable(file))
|
||||||
|
pipe->readers--;
|
||||||
|
if (file_writable(file))
|
||||||
|
pipe->writers--;
|
||||||
|
mutex_unlock(&pipe->lock);
|
||||||
|
|
||||||
|
notify_all(pipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
static errno_t pipe_read(struct vfs_file* file, iovec_iter_t* buffer, uint64_t offset, ssize_t* out_read) {
|
||||||
|
struct pipe* pipe = file->priv;
|
||||||
|
const size_t want = iovec_iter_len(buffer);
|
||||||
|
|
||||||
|
struct poll_table table;
|
||||||
|
poll_table_init(&table);
|
||||||
|
poll_wait(&table, &pipe->read_event);
|
||||||
|
|
||||||
|
errno_t status = 0;
|
||||||
|
size_t read = 0;
|
||||||
|
mutex_lock(&pipe->lock);
|
||||||
|
while (true) {
|
||||||
|
if (ringbuf_len(&pipe->buffer)) {
|
||||||
|
status = ringbuf_read_iter(&pipe->buffer, buffer, want, &read);
|
||||||
|
if (read)
|
||||||
|
poll_event_notify(&pipe->write_event);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (pipe->writers == 0)
|
||||||
|
break;
|
||||||
|
if (file_nonblocking(file)) {
|
||||||
|
status = EAGAIN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (poll_table_sleep(&table, &pipe->lock, nullptr) == EINTR) {
|
||||||
|
status = ERESTART;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex_unlock(&pipe->lock);
|
||||||
|
poll_table_fini(&table);
|
||||||
|
|
||||||
|
*out_read = read;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static errno_t pipe_write(struct vfs_file* file, iovec_iter_t* buffer, uint64_t offset, ssize_t* out_written) {
|
||||||
|
struct pipe* pipe = file->priv;
|
||||||
|
const size_t total = iovec_iter_len(buffer);
|
||||||
|
const bool atomic = total <= __PIPE_BUF;
|
||||||
|
|
||||||
|
struct poll_table table;
|
||||||
|
poll_table_init(&table);
|
||||||
|
poll_wait(&table, &pipe->write_event);
|
||||||
|
|
||||||
|
errno_t status = 0;
|
||||||
|
size_t done = 0;
|
||||||
|
mutex_lock(&pipe->lock);
|
||||||
|
while (done < total) {
|
||||||
|
if (pipe->readers == 0) {
|
||||||
|
status = EPIPE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
const size_t remaining = total - done;
|
||||||
|
const size_t space = ringbuf_space(&pipe->buffer);
|
||||||
|
if (space && (!atomic || space >= remaining)) {
|
||||||
|
size_t written;
|
||||||
|
status = ringbuf_write_iter(&pipe->buffer, buffer, remaining, &written);
|
||||||
|
done += written;
|
||||||
|
if (written)
|
||||||
|
poll_event_notify(&pipe->read_event);
|
||||||
|
if (status)
|
||||||
|
break;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (file_nonblocking(file)) {
|
||||||
|
status = EAGAIN;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if (poll_table_sleep(&table, &pipe->lock, nullptr) == EINTR) {
|
||||||
|
status = ERESTART;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex_unlock(&pipe->lock);
|
||||||
|
poll_table_fini(&table);
|
||||||
|
|
||||||
|
*out_written = done;
|
||||||
|
return done ? 0 : status;
|
||||||
|
}
|
||||||
|
|
||||||
|
static errno_t pipe_ioctl(struct vfs_file* file, uintptr_t request, uintptr_t arg, uintptr_t* out_result) {
|
||||||
|
struct pipe* pipe = file->priv;
|
||||||
|
|
||||||
|
switch ((uint32_t)request) {
|
||||||
|
case __FIONREAD: {
|
||||||
|
mutex_lock(&pipe->lock);
|
||||||
|
int len = ringbuf_len(&pipe->buffer);
|
||||||
|
mutex_unlock(&pipe->lock);
|
||||||
|
return usercopy_write((__user void*)arg, &len, sizeof(len)) ? 0 : EFAULT;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
return ENOTTY;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static enum poll_flags pipe_poll(struct vfs_file* file, enum poll_flags events, struct poll_table* table) {
|
||||||
|
struct pipe* pipe = file->priv;
|
||||||
|
|
||||||
|
if (file_readable(file))
|
||||||
|
poll_wait(table, &pipe->read_event);
|
||||||
|
if (file_writable(file))
|
||||||
|
poll_wait(table, &pipe->write_event);
|
||||||
|
|
||||||
|
enum poll_flags revents = 0;
|
||||||
|
mutex_lock(&pipe->lock);
|
||||||
|
if (file_readable(file)) {
|
||||||
|
if (ringbuf_len(&pipe->buffer))
|
||||||
|
revents |= POLLIN | POLLRDNORM;
|
||||||
|
if (pipe->writers == 0)
|
||||||
|
revents |= POLLHUP;
|
||||||
|
}
|
||||||
|
if (file_writable(file)) {
|
||||||
|
if (ringbuf_space(&pipe->buffer) >= __PIPE_BUF)
|
||||||
|
revents |= POLLOUT | POLLWRNORM;
|
||||||
|
if (pipe->readers == 0)
|
||||||
|
revents |= POLLERR;
|
||||||
|
}
|
||||||
|
mutex_unlock(&pipe->lock);
|
||||||
|
return revents;
|
||||||
|
}
|
||||||
|
|
||||||
|
static const struct vfs_file_ops pipe_file_ops = {
|
||||||
|
.read = pipe_read,
|
||||||
|
.write = pipe_write,
|
||||||
|
.ioctl = pipe_ioctl,
|
||||||
|
.poll = pipe_poll,
|
||||||
|
.release = pipe_release,
|
||||||
|
};
|
||||||
|
|
||||||
|
errno_t pipe_create(
|
||||||
|
enum open_flags flags,
|
||||||
|
const struct ident* identity,
|
||||||
|
struct vfs_file** out_read,
|
||||||
|
struct vfs_file** out_write
|
||||||
|
) {
|
||||||
|
struct vfs_node* node;
|
||||||
|
errno_t status = vfs_create_anon_inode(&pipe_file_ops, VNODE_FIFO, 0600, identity, &node);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
status = pipe_new(&node->pipe);
|
||||||
|
if (status) {
|
||||||
|
vfs_node_unref(node);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct pipe* pipe = node->pipe;
|
||||||
|
const enum open_flags extra = flags & O_NONBLOCK;
|
||||||
|
struct vfs_file* read_end;
|
||||||
|
struct vfs_file* write_end;
|
||||||
|
status = vfs_file_new(node, nullptr, O_RDONLY | extra, &read_end);
|
||||||
|
if (!status) {
|
||||||
|
mutex_lock(&pipe->lock);
|
||||||
|
attach_locked(pipe, read_end);
|
||||||
|
mutex_unlock(&pipe->lock);
|
||||||
|
|
||||||
|
status = vfs_file_new(node, nullptr, O_WRONLY | extra, &write_end);
|
||||||
|
if (status)
|
||||||
|
vfs_file_unref(read_end);
|
||||||
|
}
|
||||||
|
if (!status) {
|
||||||
|
mutex_lock(&pipe->lock);
|
||||||
|
attach_locked(pipe, write_end);
|
||||||
|
mutex_unlock(&pipe->lock);
|
||||||
|
}
|
||||||
|
vfs_node_unref(node);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
*out_read = read_end;
|
||||||
|
*out_write = write_end;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_fifo_open(struct vfs_node* node, struct vfs_file* file) {
|
||||||
|
const bool readable = file_readable(file);
|
||||||
|
const bool writable = file_writable(file);
|
||||||
|
const bool nonblock = atomic_load(&file->flags, ATOMIC_RELAXED) & O_NONBLOCK;
|
||||||
|
if (!readable && !writable)
|
||||||
|
return EINVAL;
|
||||||
|
|
||||||
|
errno_t status = 0;
|
||||||
|
mutex_lock(&node->lock);
|
||||||
|
if (!node->pipe)
|
||||||
|
status = pipe_new(&node->pipe);
|
||||||
|
struct pipe* pipe = node->pipe;
|
||||||
|
mutex_unlock(&node->lock);
|
||||||
|
if (status)
|
||||||
|
return status;
|
||||||
|
|
||||||
|
mutex_lock(&pipe->lock);
|
||||||
|
if (writable && !readable && nonblock && pipe->readers == 0) {
|
||||||
|
mutex_unlock(&pipe->lock);
|
||||||
|
return ENXIO;
|
||||||
|
}
|
||||||
|
const uint64_t peer_opens = attach_locked(pipe, file);
|
||||||
|
mutex_unlock(&pipe->lock);
|
||||||
|
notify_all(pipe);
|
||||||
|
|
||||||
|
if (nonblock || (readable && writable))
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
struct poll_table table;
|
||||||
|
poll_table_init(&table);
|
||||||
|
poll_wait(&table, &pipe->open_event);
|
||||||
|
|
||||||
|
mutex_lock(&pipe->lock);
|
||||||
|
while (true) {
|
||||||
|
const size_t peers = readable ? pipe->writers : pipe->readers;
|
||||||
|
const uint64_t opens = readable ? pipe->write_opens : pipe->read_opens;
|
||||||
|
if (peers || opens != peer_opens)
|
||||||
|
break;
|
||||||
|
if (poll_table_sleep(&table, &pipe->lock, nullptr) == EINTR) {
|
||||||
|
status = ERESTART;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mutex_unlock(&pipe->lock);
|
||||||
|
poll_table_fini(&table);
|
||||||
|
return status;
|
||||||
|
}
|
||||||
@@ -0,0 +1,249 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#include <zinnia/alloc.h>
|
||||||
|
#include <zinnia/assert.h>
|
||||||
|
#include <zinnia/clock.h>
|
||||||
|
#include <zinnia/vfs.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include "internal.h"
|
||||||
|
|
||||||
|
errno_t vfs_node_new(
|
||||||
|
struct vfs_super_block* sb,
|
||||||
|
enum vfs_node_type type,
|
||||||
|
const struct vfs_node_ops* ops,
|
||||||
|
mode_t mode,
|
||||||
|
struct vfs_node** out
|
||||||
|
) {
|
||||||
|
struct vfs_node* node = mem_alloc(sizeof(*node), 0);
|
||||||
|
if (!node)
|
||||||
|
return ENOMEM;
|
||||||
|
|
||||||
|
refcount_init(&node->rc);
|
||||||
|
node->type = type;
|
||||||
|
node->ops = ops;
|
||||||
|
node->file_ops = ops ? ops->file_ops : nullptr;
|
||||||
|
node->sb = sb;
|
||||||
|
if (sb)
|
||||||
|
vfs_super_block_ref(sb);
|
||||||
|
mutex_init(&node->lock);
|
||||||
|
node->mode = mode & 07777;
|
||||||
|
node->nlink = 1;
|
||||||
|
const timespec_t now = time_from_nano(clock_get_realtime_ns());
|
||||||
|
node->atime = now;
|
||||||
|
node->mtime = now;
|
||||||
|
node->ctime = now;
|
||||||
|
|
||||||
|
*out = node;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_node_ref(struct vfs_node* node) {
|
||||||
|
refcount_inc(&node->rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_node_unref(struct vfs_node* node) {
|
||||||
|
if (!refcount_dec(&node->rc))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (node->ops && node->ops->destroy)
|
||||||
|
node->ops->destroy(node);
|
||||||
|
if (node->type == VNODE_FIFO && node->pipe)
|
||||||
|
pipe_destroy(node->pipe);
|
||||||
|
if (node->sb)
|
||||||
|
vfs_super_block_unref(node->sb);
|
||||||
|
mem_free(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
errno_t vfs_entry_new(const char* name, size_t name_len, struct vfs_entry* parent, struct vfs_entry** out) {
|
||||||
|
struct vfs_entry* entry = mem_alloc(sizeof(*entry), 0);
|
||||||
|
if (!entry)
|
||||||
|
return ENOMEM;
|
||||||
|
|
||||||
|
entry->name = mem_alloc(name_len + 1, 0);
|
||||||
|
if (!entry->name) {
|
||||||
|
mem_free(entry);
|
||||||
|
return ENOMEM;
|
||||||
|
}
|
||||||
|
memcpy(entry->name, name, name_len);
|
||||||
|
entry->name[name_len] = '\0';
|
||||||
|
entry->name_len = name_len;
|
||||||
|
|
||||||
|
refcount_init(&entry->rc);
|
||||||
|
mutex_init(&entry->lock);
|
||||||
|
entry->state = VFS_ENTRY_NOT_CACHED;
|
||||||
|
entry->inode = nullptr;
|
||||||
|
entry->parent = parent;
|
||||||
|
if (parent)
|
||||||
|
vfs_entry_ref(parent);
|
||||||
|
tailq_init(&entry->children);
|
||||||
|
vec_init(&entry->mounts);
|
||||||
|
|
||||||
|
*out = entry;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_entry_ref(struct vfs_entry* entry) {
|
||||||
|
refcount_inc(&entry->rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_entry_unref(struct vfs_entry* entry) {
|
||||||
|
if (!refcount_dec(&entry->rc))
|
||||||
|
return;
|
||||||
|
|
||||||
|
// Children and mounts hold references to this entry, so both must be gone by now.
|
||||||
|
assert(tailq_empty(&entry->children), "Destroying an entry that still has children");
|
||||||
|
assert(vec_is_empty(&entry->mounts), "Destroying an entry that is still a mount point");
|
||||||
|
|
||||||
|
if (entry->inode)
|
||||||
|
vfs_node_unref(entry->inode);
|
||||||
|
if (entry->parent)
|
||||||
|
vfs_entry_unref(entry->parent);
|
||||||
|
vec_destroy(&entry->mounts);
|
||||||
|
mem_free(entry->name);
|
||||||
|
mem_free(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_entry_set_inode(struct vfs_entry* entry, struct vfs_node* node) {
|
||||||
|
vfs_node_ref(node);
|
||||||
|
|
||||||
|
mutex_lock(&entry->lock);
|
||||||
|
struct vfs_node* old = entry->inode;
|
||||||
|
entry->inode = node;
|
||||||
|
entry->state = VFS_ENTRY_PRESENT;
|
||||||
|
mutex_unlock(&entry->lock);
|
||||||
|
|
||||||
|
if (old)
|
||||||
|
vfs_node_unref(old);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_entry_set_negative(struct vfs_entry* entry) {
|
||||||
|
mutex_lock(&entry->lock);
|
||||||
|
struct vfs_node* old = entry->inode;
|
||||||
|
entry->inode = nullptr;
|
||||||
|
entry->state = VFS_ENTRY_NOT_PRESENT;
|
||||||
|
mutex_unlock(&entry->lock);
|
||||||
|
|
||||||
|
if (old)
|
||||||
|
vfs_node_unref(old);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_entry_replace_children(struct vfs_entry* dst, struct vfs_entry* src) {
|
||||||
|
tailq_head(struct vfs_entry) stale;
|
||||||
|
tailq_init(&stale);
|
||||||
|
|
||||||
|
mutex_lock(&src->lock);
|
||||||
|
mutex_lock(&dst->lock);
|
||||||
|
struct vfs_entry* child;
|
||||||
|
while ((child = tailq_first(&dst->children))) {
|
||||||
|
tailq_remove(&dst->children, child, child_link);
|
||||||
|
tailq_insert_tail(&stale, child, child_link);
|
||||||
|
}
|
||||||
|
while ((child = tailq_first(&src->children))) {
|
||||||
|
tailq_remove(&src->children, child, child_link);
|
||||||
|
child->parent = dst;
|
||||||
|
vfs_entry_ref(dst);
|
||||||
|
vfs_entry_unref(src);
|
||||||
|
tailq_insert_tail(&dst->children, child, child_link);
|
||||||
|
}
|
||||||
|
mutex_unlock(&dst->lock);
|
||||||
|
mutex_unlock(&src->lock);
|
||||||
|
|
||||||
|
while ((child = tailq_first(&stale))) {
|
||||||
|
tailq_remove(&stale, child, child_link);
|
||||||
|
vfs_entry_unref(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_entry_prune(struct vfs_entry* entry) {
|
||||||
|
tailq_head(struct vfs_entry) stale;
|
||||||
|
tailq_init(&stale);
|
||||||
|
|
||||||
|
mutex_lock(&entry->lock);
|
||||||
|
struct vfs_entry* child = tailq_first(&entry->children);
|
||||||
|
while (child) {
|
||||||
|
struct vfs_entry* next = tailq_next(child, child_link);
|
||||||
|
vfs_entry_prune(child);
|
||||||
|
|
||||||
|
mutex_lock(&child->lock);
|
||||||
|
const bool idle = tailq_empty(&child->children) && vec_is_empty(&child->mounts);
|
||||||
|
mutex_unlock(&child->lock);
|
||||||
|
if (idle && atomic_load(&child->rc.count, ATOMIC_RELAXED) == 1) {
|
||||||
|
tailq_remove(&entry->children, child, child_link);
|
||||||
|
tailq_insert_tail(&stale, child, child_link);
|
||||||
|
}
|
||||||
|
child = next;
|
||||||
|
}
|
||||||
|
mutex_unlock(&entry->lock);
|
||||||
|
|
||||||
|
while ((child = tailq_first(&stale))) {
|
||||||
|
tailq_remove(&stale, child, child_link);
|
||||||
|
vfs_entry_unref(child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct vfs_node* vfs_entry_get_inode(struct vfs_entry* entry) {
|
||||||
|
struct vfs_node* node = nullptr;
|
||||||
|
|
||||||
|
mutex_lock(&entry->lock);
|
||||||
|
if (entry->state == VFS_ENTRY_PRESENT) {
|
||||||
|
node = entry->inode;
|
||||||
|
vfs_node_ref(node);
|
||||||
|
}
|
||||||
|
mutex_unlock(&entry->lock);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_mount_ref(struct vfs_mount* mount) {
|
||||||
|
refcount_inc(&mount->rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_mount_unref(struct vfs_mount* mount) {
|
||||||
|
if (!refcount_dec(&mount->rc))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (mount->has_mount_point)
|
||||||
|
vfs_path_drop(mount->mount_point);
|
||||||
|
vfs_entry_unref(mount->root);
|
||||||
|
mem_free(mount);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_super_block_ref(struct vfs_super_block* sb) {
|
||||||
|
refcount_inc(&sb->rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_super_block_unref(struct vfs_super_block* sb) {
|
||||||
|
if (!refcount_dec(&sb->rc))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (sb->ops && sb->ops->destroy)
|
||||||
|
sb->ops->destroy(sb);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_file_ref(struct vfs_file* file) {
|
||||||
|
refcount_inc(&file->rc);
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_file_unref(struct vfs_file* file) {
|
||||||
|
if (!refcount_dec(&file->rc))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (file->ops && file->ops->release)
|
||||||
|
file->ops->release(file);
|
||||||
|
if (file->has_path)
|
||||||
|
vfs_path_drop(file->path);
|
||||||
|
vfs_node_unref(file->vnode);
|
||||||
|
mem_free(file->absolute_path);
|
||||||
|
mem_free(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
struct vfs_path vfs_path_clone(struct vfs_path path) {
|
||||||
|
vfs_mount_ref(path.mount);
|
||||||
|
vfs_entry_ref(path.entry);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
void vfs_path_drop(struct vfs_path path) {
|
||||||
|
vfs_entry_unref(path.entry);
|
||||||
|
vfs_mount_unref(path.mount);
|
||||||
|
}
|
||||||
+509
@@ -0,0 +1,509 @@
|
|||||||
|
// 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);
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <zinnia/errno.h>
|
||||||
|
#include <zinnia/tailq.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
struct process;
|
||||||
|
struct vfs_file;
|
||||||
|
struct vma;
|
||||||
|
struct vmo;
|
||||||
|
|
||||||
|
// Information passed to `exec_format.load`.
|
||||||
|
struct exec_info {
|
||||||
|
// An address space for the new process.
|
||||||
|
struct vma* space;
|
||||||
|
// The excutable to load.
|
||||||
|
struct vmo* file_obj;
|
||||||
|
// Path passed to execve.
|
||||||
|
const char* exec_path;
|
||||||
|
// Arguments.
|
||||||
|
const char** argv;
|
||||||
|
// Environment variables.
|
||||||
|
const char** envp;
|
||||||
|
|
||||||
|
size_t depth;
|
||||||
|
};
|
||||||
|
|
||||||
|
// An executable format.
|
||||||
|
struct exec_format {
|
||||||
|
const char* name;
|
||||||
|
|
||||||
|
// Identifies whether a file is a valid executable of this format.
|
||||||
|
bool (*identify)(struct vmo* obj);
|
||||||
|
|
||||||
|
// Loads the executable and returns a new initial thread.
|
||||||
|
// The implementation should not modify `proc` at all.
|
||||||
|
errno_t (*load)(struct process* proc, struct exec_info* info, uintptr_t* out_ip, uintptr_t* out_sp);
|
||||||
|
|
||||||
|
tailq_link(struct exec_format) link;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Attempts to identify the format of this executable file.
|
||||||
|
struct exec_format* exec_identify(struct vmo* obj);
|
||||||
|
|
||||||
|
// Installs a new executable format.
|
||||||
|
void exec_register(struct exec_format* format);
|
||||||
|
|
||||||
|
errno_t exec_open(struct process* proc, const char* path, struct vmo** out);
|
||||||
|
|
||||||
|
errno_t exec_file_object(struct process* proc, struct vfs_file* file, struct vmo** out);
|
||||||
@@ -15,3 +15,5 @@ struct ident {
|
|||||||
const struct ident* ident_kernel(void);
|
const struct ident* ident_kernel(void);
|
||||||
|
|
||||||
bool ident_in_group(const struct ident* identity, gid_t gid, bool use_real);
|
bool ident_in_group(const struct ident* identity, gid_t gid, bool use_real);
|
||||||
|
|
||||||
|
bool ident_is_privileged(const struct ident* identity, bool use_real);
|
||||||
|
|||||||
@@ -0,0 +1,14 @@
|
|||||||
|
// SPDX-License-Identifier: ISC
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <zinnia/errno.h>
|
||||||
|
#include <zinnia/ident.h>
|
||||||
|
#include <zinnia/vfs.h>
|
||||||
|
|
||||||
|
errno_t pipe_create(
|
||||||
|
enum open_flags flags,
|
||||||
|
const struct ident* identity,
|
||||||
|
struct vfs_file** out_read,
|
||||||
|
struct vfs_file** out_write
|
||||||
|
);
|
||||||
+131
-19
@@ -4,9 +4,11 @@
|
|||||||
|
|
||||||
#include <zinnia/errno.h>
|
#include <zinnia/errno.h>
|
||||||
#include <zinnia/ident.h>
|
#include <zinnia/ident.h>
|
||||||
|
#include <zinnia/initgraph.h>
|
||||||
#include <zinnia/iovec.h>
|
#include <zinnia/iovec.h>
|
||||||
#include <zinnia/mman.h>
|
#include <zinnia/mman.h>
|
||||||
#include <zinnia/mutex.h>
|
#include <zinnia/mutex.h>
|
||||||
|
#include <zinnia/poll.h>
|
||||||
#include <zinnia/refcount.h>
|
#include <zinnia/refcount.h>
|
||||||
#include <zinnia/spin.h>
|
#include <zinnia/spin.h>
|
||||||
#include <zinnia/tailq.h>
|
#include <zinnia/tailq.h>
|
||||||
@@ -34,6 +36,7 @@ struct vfs_super_block;
|
|||||||
struct vfs_filesystem;
|
struct vfs_filesystem;
|
||||||
struct vfs_node_ops;
|
struct vfs_node_ops;
|
||||||
struct vfs_file_ops;
|
struct vfs_file_ops;
|
||||||
|
struct pipe;
|
||||||
|
|
||||||
enum open_flags : uint32_t {
|
enum open_flags : uint32_t {
|
||||||
O_RDONLY = __O_RDONLY,
|
O_RDONLY = __O_RDONLY,
|
||||||
@@ -62,6 +65,13 @@ enum open_flags : uint32_t {
|
|||||||
O_ACCMODE = __O_ACCMODE,
|
O_ACCMODE = __O_ACCMODE,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum access_flags : uint32_t {
|
||||||
|
F_OK = __F_OK,
|
||||||
|
X_OK = __X_OK,
|
||||||
|
W_OK = __W_OK,
|
||||||
|
R_OK = __R_OK,
|
||||||
|
};
|
||||||
|
|
||||||
enum mount_flags : uint32_t {
|
enum mount_flags : uint32_t {
|
||||||
MNT_RDONLY = __MNT_RDONLY,
|
MNT_RDONLY = __MNT_RDONLY,
|
||||||
MNT_NOSUID = __MNT_NOSUID,
|
MNT_NOSUID = __MNT_NOSUID,
|
||||||
@@ -72,13 +82,6 @@ enum mount_flags : uint32_t {
|
|||||||
MNT_FORCE = __MNT_FORCE,
|
MNT_FORCE = __MNT_FORCE,
|
||||||
};
|
};
|
||||||
|
|
||||||
struct exec_info {
|
|
||||||
struct vma* space;
|
|
||||||
struct vmo* file_obj;
|
|
||||||
const char** argv;
|
|
||||||
const char** envp;
|
|
||||||
};
|
|
||||||
|
|
||||||
enum vfs_lookup_flags : uint32_t {
|
enum vfs_lookup_flags : uint32_t {
|
||||||
VFS_LOOKUP_FOLLOW_SYMLINKS = 1 << 0,
|
VFS_LOOKUP_FOLLOW_SYMLINKS = 1 << 0,
|
||||||
VFS_LOOKUP_MUST_EXIST = 1 << 1,
|
VFS_LOOKUP_MUST_EXIST = 1 << 1,
|
||||||
@@ -103,6 +106,7 @@ enum vfs_entry_state {
|
|||||||
VFS_ENTRY_NOT_PRESENT, // Entry is negative.
|
VFS_ENTRY_NOT_PRESENT, // Entry is negative.
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// A namecache entry.
|
||||||
struct vfs_entry {
|
struct vfs_entry {
|
||||||
struct refcount rc;
|
struct refcount rc;
|
||||||
// The name of this entry.
|
// The name of this entry.
|
||||||
@@ -111,12 +115,12 @@ struct vfs_entry {
|
|||||||
mutex_t lock;
|
mutex_t lock;
|
||||||
|
|
||||||
enum vfs_entry_state state;
|
enum vfs_entry_state state;
|
||||||
// The inode this entry points to when present.
|
// The inode this entry points to when present. Holds a reference.
|
||||||
struct vfs_node* inode;
|
struct vfs_node* inode;
|
||||||
// The parent of this entry, weak reference.
|
// The parent of this entry, strong reference. `nullptr` for file system roots.
|
||||||
struct vfs_entry* parent;
|
struct vfs_entry* parent;
|
||||||
|
|
||||||
// All cached children of this entry.
|
// All cached children of this entry. Each child in this list holds a reference.
|
||||||
tailq_head(struct vfs_entry) children;
|
tailq_head(struct vfs_entry) children;
|
||||||
tailq_link(struct vfs_entry) child_link;
|
tailq_link(struct vfs_entry) child_link;
|
||||||
|
|
||||||
@@ -132,6 +136,22 @@ struct vfs_path {
|
|||||||
struct vfs_path vfs_path_clone(struct vfs_path path);
|
struct vfs_path vfs_path_clone(struct vfs_path path);
|
||||||
void vfs_path_drop(struct vfs_path path);
|
void vfs_path_drop(struct vfs_path path);
|
||||||
|
|
||||||
|
// Allocates a new, uncached entry with the given `name`. `parent` gets referenced if non-null.
|
||||||
|
errno_t vfs_entry_new(const char* name, size_t name_len, struct vfs_entry* parent, struct vfs_entry** out);
|
||||||
|
|
||||||
|
// Makes `entry` positive and points it to `node`. Takes a new reference to `node`.
|
||||||
|
void vfs_entry_set_inode(struct vfs_entry* entry, struct vfs_node* node);
|
||||||
|
|
||||||
|
// Returns a new reference to the inode of `entry`, or `nullptr` if the entry is negative.
|
||||||
|
struct vfs_node* vfs_entry_get_inode(struct vfs_entry* entry);
|
||||||
|
|
||||||
|
// Makes `entry` negative and drops its reference to the inode.
|
||||||
|
void vfs_entry_set_negative(struct vfs_entry* entry);
|
||||||
|
|
||||||
|
void vfs_entry_replace_children(struct vfs_entry* dst, struct vfs_entry* src);
|
||||||
|
void vfs_entry_prune(struct vfs_entry* entry);
|
||||||
|
|
||||||
|
// A mount point.
|
||||||
struct vfs_mount {
|
struct vfs_mount {
|
||||||
struct refcount rc;
|
struct refcount rc;
|
||||||
enum mount_flags flags;
|
enum mount_flags flags;
|
||||||
@@ -142,9 +162,18 @@ struct vfs_mount {
|
|||||||
|
|
||||||
void vfs_mount_ref(struct vfs_mount* mount);
|
void vfs_mount_ref(struct vfs_mount* mount);
|
||||||
void vfs_mount_unref(struct vfs_mount* mount);
|
void vfs_mount_unref(struct vfs_mount* mount);
|
||||||
|
|
||||||
|
// Creates a unique instance of a mount point.
|
||||||
errno_t vfs_mount(const char* fs_name, enum mount_flags flags, void* arg, struct vfs_mount** out);
|
errno_t vfs_mount(const char* fs_name, enum mount_flags flags, void* arg, struct vfs_mount** out);
|
||||||
errno_t vfs_unmount(struct vfs_mount* mount);
|
errno_t vfs_unmount(struct vfs_mount* mount);
|
||||||
|
|
||||||
|
// Allocates a new mount with `root` as its root entry. Takes a new reference to `root`.
|
||||||
|
errno_t vfs_mount_new(enum mount_flags flags, struct vfs_entry* root, struct vfs_mount** out);
|
||||||
|
|
||||||
|
// Attaches `mount` on top of the entry referenced by `at`.
|
||||||
|
errno_t vfs_path_mount(struct vfs_path at, struct vfs_mount* mount);
|
||||||
|
errno_t vfs_path_unmount(struct vfs_path path);
|
||||||
|
|
||||||
struct vfs_super_block_ops {
|
struct vfs_super_block_ops {
|
||||||
errno_t (*sync)(struct vfs_super_block* sb);
|
errno_t (*sync)(struct vfs_super_block* sb);
|
||||||
errno_t (*statvfs)(struct vfs_super_block* sb, struct __statvfs* out);
|
errno_t (*statvfs)(struct vfs_super_block* sb, struct __statvfs* out);
|
||||||
@@ -154,6 +183,7 @@ struct vfs_super_block_ops {
|
|||||||
struct vfs_super_block {
|
struct vfs_super_block {
|
||||||
struct refcount rc;
|
struct refcount rc;
|
||||||
const struct vfs_super_block_ops* ops;
|
const struct vfs_super_block_ops* ops;
|
||||||
|
enum mount_flags flags;
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: Maybe not like this
|
// TODO: Maybe not like this
|
||||||
@@ -161,6 +191,7 @@ struct vfs_mknod_target {
|
|||||||
enum vfs_node_type type;
|
enum vfs_node_type type;
|
||||||
const struct vfs_node_ops* ops;
|
const struct vfs_node_ops* ops;
|
||||||
void* priv;
|
void* priv;
|
||||||
|
dev_t rdev;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct vfs_node_ops {
|
struct vfs_node_ops {
|
||||||
@@ -176,6 +207,8 @@ struct vfs_node_ops {
|
|||||||
struct {
|
struct {
|
||||||
// Truncates the file down to `new_length` bytes.
|
// Truncates the file down to `new_length` bytes.
|
||||||
errno_t (*truncate)(struct vfs_node* node, uint64_t new_length);
|
errno_t (*truncate)(struct vfs_node* node, uint64_t new_length);
|
||||||
|
// Returns a new reference to a memory object holding the file contents.
|
||||||
|
errno_t (*get_vmo)(struct vfs_node* node, struct vmo** out);
|
||||||
} regular;
|
} regular;
|
||||||
|
|
||||||
// VNODE_SYMLINK
|
// VNODE_SYMLINK
|
||||||
@@ -184,6 +217,11 @@ struct vfs_node_ops {
|
|||||||
errno_t (*read_link)(struct vfs_node* node, iovec_iter_t* iter, size_t* out_read);
|
errno_t (*read_link)(struct vfs_node* node, iovec_iter_t* iter, size_t* out_read);
|
||||||
} symlink;
|
} symlink;
|
||||||
|
|
||||||
|
// VNODE_CHARDEV or VNODE_BLOCKDEV
|
||||||
|
struct {
|
||||||
|
errno_t (*open)(struct vfs_node* node, struct vfs_file* file);
|
||||||
|
} device;
|
||||||
|
|
||||||
// VNODE_DIR
|
// VNODE_DIR
|
||||||
struct {
|
struct {
|
||||||
errno_t (*lookup)(struct vfs_node* node, struct vfs_path* child);
|
errno_t (*lookup)(struct vfs_node* node, struct vfs_path* child);
|
||||||
@@ -225,10 +263,10 @@ struct vfs_node_ops {
|
|||||||
);
|
);
|
||||||
errno_t (*mknod)(
|
errno_t (*mknod)(
|
||||||
struct vfs_node* node,
|
struct vfs_node* node,
|
||||||
|
struct vfs_path* path,
|
||||||
mode_t mode,
|
mode_t mode,
|
||||||
struct vfs_mknod_target* target,
|
struct vfs_mknod_target* target,
|
||||||
const struct ident* identity,
|
const struct ident* identity
|
||||||
struct vfs_node** out
|
|
||||||
);
|
);
|
||||||
errno_t (*get_dir_entries)(
|
errno_t (*get_dir_entries)(
|
||||||
struct vfs_node* node,
|
struct vfs_node* node,
|
||||||
@@ -246,7 +284,6 @@ struct vfs_node_ops {
|
|||||||
struct vfs_node {
|
struct vfs_node {
|
||||||
struct refcount rc;
|
struct refcount rc;
|
||||||
enum vfs_node_type type;
|
enum vfs_node_type type;
|
||||||
bool is_seekable;
|
|
||||||
|
|
||||||
const struct vfs_node_ops* ops;
|
const struct vfs_node_ops* ops;
|
||||||
const struct vfs_file_ops* file_ops;
|
const struct vfs_file_ops* file_ops;
|
||||||
@@ -254,9 +291,13 @@ struct vfs_node {
|
|||||||
|
|
||||||
mutex_t lock;
|
mutex_t lock;
|
||||||
void* priv;
|
void* priv;
|
||||||
|
struct pipe* pipe;
|
||||||
|
|
||||||
ino_t id;
|
ino_t id;
|
||||||
|
dev_t rdev;
|
||||||
size_t size;
|
size_t size;
|
||||||
|
blkcnt_t blocks;
|
||||||
|
nlink_t nlink;
|
||||||
mode_t mode;
|
mode_t mode;
|
||||||
uid_t uid;
|
uid_t uid;
|
||||||
gid_t gid;
|
gid_t gid;
|
||||||
@@ -280,7 +321,8 @@ struct vfs_file_ops {
|
|||||||
off_t offset,
|
off_t offset,
|
||||||
uintptr_t* out_addr
|
uintptr_t* out_addr
|
||||||
);
|
);
|
||||||
bool (*seekable)(struct vfs_file* file);
|
enum poll_flags (*poll)(struct vfs_file* file, enum poll_flags events, struct poll_table* table);
|
||||||
|
void (*release)(struct vfs_file* file);
|
||||||
};
|
};
|
||||||
|
|
||||||
enum vfs_file_position {
|
enum vfs_file_position {
|
||||||
@@ -303,7 +345,7 @@ struct vfs_file {
|
|||||||
|
|
||||||
// Absolute path resolved during open.
|
// Absolute path resolved during open.
|
||||||
char* absolute_path;
|
char* absolute_path;
|
||||||
enum open_flags flags;
|
_Atomic(enum open_flags) flags;
|
||||||
|
|
||||||
enum vfs_file_position position_kind;
|
enum vfs_file_position position_kind;
|
||||||
off_t position;
|
off_t position;
|
||||||
@@ -311,6 +353,9 @@ struct vfs_file {
|
|||||||
|
|
||||||
struct vfs_path path;
|
struct vfs_path path;
|
||||||
bool has_path;
|
bool has_path;
|
||||||
|
|
||||||
|
// Driver data.
|
||||||
|
void* priv;
|
||||||
};
|
};
|
||||||
|
|
||||||
// A file system driver.
|
// A file system driver.
|
||||||
@@ -319,6 +364,16 @@ struct vfs_filesystem {
|
|||||||
errno_t (*mount)(const struct vfs_filesystem* fs, enum mount_flags flags, void* arg, struct vfs_mount** out);
|
errno_t (*mount)(const struct vfs_filesystem* fs, enum mount_flags flags, void* arg, struct vfs_mount** out);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Allocates a new inode in `sb` with `type`, `ops` and permission bits `mode`.
|
||||||
|
// The inode takes a reference to `sb` if it's not `nullptr`.
|
||||||
|
errno_t vfs_node_new(
|
||||||
|
struct vfs_super_block* sb,
|
||||||
|
enum vfs_node_type type,
|
||||||
|
const struct vfs_node_ops* ops,
|
||||||
|
mode_t mode,
|
||||||
|
struct vfs_node** out
|
||||||
|
);
|
||||||
|
|
||||||
void vfs_node_ref(struct vfs_node* node);
|
void vfs_node_ref(struct vfs_node* node);
|
||||||
void vfs_node_unref(struct vfs_node* node);
|
void vfs_node_unref(struct vfs_node* node);
|
||||||
void vfs_entry_ref(struct vfs_entry* entry);
|
void vfs_entry_ref(struct vfs_entry* entry);
|
||||||
@@ -331,8 +386,6 @@ void vfs_super_block_unref(struct vfs_super_block* sb);
|
|||||||
// Gets the kernel path representation of the absolute root.
|
// Gets the kernel path representation of the absolute root.
|
||||||
struct vfs_path vfs_get_root();
|
struct vfs_path vfs_get_root();
|
||||||
|
|
||||||
void vfs_init();
|
|
||||||
|
|
||||||
errno_t vfs_register_filesystem(const struct vfs_filesystem* fs);
|
errno_t vfs_register_filesystem(const struct vfs_filesystem* fs);
|
||||||
|
|
||||||
errno_t vfs_sync_all();
|
errno_t vfs_sync_all();
|
||||||
@@ -392,12 +445,46 @@ errno_t vfs_mknod(
|
|||||||
const char* path,
|
const char* path,
|
||||||
mode_t mode,
|
mode_t mode,
|
||||||
struct vfs_mknod_target* target,
|
struct vfs_mknod_target* target,
|
||||||
|
const struct ident* identity,
|
||||||
|
struct vfs_node** out
|
||||||
|
);
|
||||||
|
|
||||||
|
errno_t vfs_rename(
|
||||||
|
struct vfs_path root,
|
||||||
|
struct vfs_path old_cwd,
|
||||||
|
const char* old_path,
|
||||||
|
struct vfs_path new_cwd,
|
||||||
|
const char* new_path,
|
||||||
const struct ident* identity
|
const struct ident* identity
|
||||||
);
|
);
|
||||||
|
|
||||||
// Checks if an access to a `node` with `flags` using `identity` is allowed.
|
errno_t vfs_link(
|
||||||
|
struct vfs_path root,
|
||||||
|
struct vfs_path old_cwd,
|
||||||
|
const char* old_path,
|
||||||
|
struct vfs_path new_cwd,
|
||||||
|
const char* new_path,
|
||||||
|
bool follow_symlinks,
|
||||||
|
const struct ident* identity
|
||||||
|
);
|
||||||
|
|
||||||
|
void vfs_stat(struct vfs_node* node, struct __stat* out);
|
||||||
|
errno_t vfs_statvfs(struct vfs_node* node, struct __statvfs* out);
|
||||||
|
errno_t vfs_chmod(struct vfs_node* node, mode_t mode, const struct ident* identity);
|
||||||
|
errno_t vfs_chown(struct vfs_node* node, uid_t uid, gid_t gid, const struct ident* identity);
|
||||||
|
errno_t vfs_utimens(struct vfs_node* node, const timespec_t* times, const struct ident* identity);
|
||||||
|
errno_t vfs_node_check_exec(struct vfs_node* node, const struct ident* identity);
|
||||||
|
errno_t vfs_truncate(struct vfs_node* node, uint64_t length);
|
||||||
|
errno_t vfs_read_link(struct vfs_node* node, iovec_iter_t* iter, size_t* out_read);
|
||||||
|
|
||||||
|
// Checks if an access to a `node` with `access` using `identity` is allowed.
|
||||||
// Returns 0 if the access is allowed.
|
// Returns 0 if the access is allowed.
|
||||||
errno_t vfs_node_try_access(struct vfs_node* node, const struct ident* identity, enum open_flags flags, bool use_real);
|
errno_t vfs_node_try_access(
|
||||||
|
struct vfs_node* node,
|
||||||
|
const struct ident* identity,
|
||||||
|
enum access_flags access,
|
||||||
|
bool use_real
|
||||||
|
);
|
||||||
|
|
||||||
// Opens a node at `path`, relative to `root` and `cwd`.
|
// Opens a node at `path`, relative to `root` and `cwd`.
|
||||||
// Returns the opened node as a file in `out`.
|
// Returns the opened node as a file in `out`.
|
||||||
@@ -411,6 +498,16 @@ errno_t vfs_file_open(
|
|||||||
struct vfs_file** out
|
struct vfs_file** out
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Creates a new open file backed by `node`, which must not be `nullptr`.
|
||||||
|
// If `path` is not `nullptr`, the file remembers it and takes a reference to it.
|
||||||
|
errno_t vfs_file_new(struct vfs_node* node, const struct vfs_path* path, enum open_flags flags, struct vfs_file** out);
|
||||||
|
|
||||||
|
// Changes the file's position mode to a stream.
|
||||||
|
void vfs_stream_open(struct vfs_file* file);
|
||||||
|
|
||||||
|
// Gets a new reference to a memory object holding the contents of `file`.
|
||||||
|
errno_t vfs_file_get_object(struct vfs_file* file, struct vmo** out);
|
||||||
|
|
||||||
// Reads from a `file` into a `buffer` from the current offset.
|
// Reads from a `file` into a `buffer` from the current offset.
|
||||||
// Returns the actual bytes read in `out_read`.
|
// Returns the actual bytes read in `out_read`.
|
||||||
errno_t vfs_file_read(struct vfs_file* file, iovec_iter_t* buffer, ssize_t* out_read);
|
errno_t vfs_file_read(struct vfs_file* file, iovec_iter_t* buffer, ssize_t* out_read);
|
||||||
@@ -447,6 +544,8 @@ errno_t vfs_file_mmap(
|
|||||||
uintptr_t* out_addr
|
uintptr_t* out_addr
|
||||||
);
|
);
|
||||||
|
|
||||||
|
enum poll_flags vfs_poll(struct vfs_file* file, enum poll_flags events, struct poll_table* table);
|
||||||
|
|
||||||
// Directory listing.
|
// Directory listing.
|
||||||
errno_t vfs_get_dir_entries(
|
errno_t vfs_get_dir_entries(
|
||||||
struct vfs_file* file,
|
struct vfs_file* file,
|
||||||
@@ -456,6 +555,17 @@ errno_t vfs_get_dir_entries(
|
|||||||
size_t* out_count
|
size_t* out_count
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Generic directory listing implementation which walks the cached children of `entry`.
|
||||||
|
errno_t vfs_dir_get_cached_entries(
|
||||||
|
struct vfs_node* node,
|
||||||
|
struct vfs_entry* entry,
|
||||||
|
off_t offset,
|
||||||
|
struct __dirent* buffer,
|
||||||
|
size_t buffer_len,
|
||||||
|
const struct ident* identity,
|
||||||
|
size_t* out_count
|
||||||
|
);
|
||||||
|
|
||||||
// Creates an inode belonging to the anonymous pseudo file system.
|
// Creates an inode belonging to the anonymous pseudo file system.
|
||||||
errno_t vfs_create_anon_inode(
|
errno_t vfs_create_anon_inode(
|
||||||
const struct vfs_file_ops* file_ops,
|
const struct vfs_file_ops* file_ops,
|
||||||
@@ -464,3 +574,5 @@ errno_t vfs_create_anon_inode(
|
|||||||
const struct ident* identity,
|
const struct ident* identity,
|
||||||
struct vfs_node** out
|
struct vfs_node** out
|
||||||
);
|
);
|
||||||
|
|
||||||
|
INIT_EXTERN(vfs_stage);
|
||||||
|
|||||||
Reference in New Issue
Block a user