fs: Initial VFS port

This commit is contained in:
2026-10-25 18:35:03 +01:00
parent efe1feb852
commit d14f895288
18 changed files with 3296 additions and 23 deletions
+1
View File
@@ -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>
+40
View File
@@ -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
View File
@@ -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, &current, 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, &current, 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
View File
@@ -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>
+628
View File
@@ -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;
}
+4
View File
@@ -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;
}
+20 -2
View File
@@ -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);
+16
View File
@@ -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
View File
@@ -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
View File
@@ -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;
}
+656
View File
@@ -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);
}
+325
View File
@@ -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;
}
+249
View File
@@ -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
View File
@@ -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);
+53
View File
@@ -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);
+2
View File
@@ -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);
+14
View File
@@ -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
View File
@@ -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);