diff --git a/boot/main.c b/boot/main.c index 99db0b8..e309a20 100644 --- a/boot/main.c +++ b/boot/main.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include diff --git a/fs/anon.c b/fs/anon.c new file mode 100644 index 0000000..69d3f18 --- /dev/null +++ b/fs/anon.c @@ -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 +#include + +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; +} diff --git a/fs/cache.c b/fs/cache.c new file mode 100644 index 0000000..0ebb297 --- /dev/null +++ b/fs/cache.c @@ -0,0 +1,411 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include +#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; +} diff --git a/fs/exec/elf.c b/fs/exec/elf.c index 4917838..34692d4 100644 --- a/fs/exec/elf.c +++ b/fs/exec/elf.c @@ -1,6 +1,7 @@ #include #include #include +#include #include #include #include diff --git a/fs/file.c b/fs/file.c new file mode 100644 index 0000000..9533ac6 --- /dev/null +++ b/fs/file.c @@ -0,0 +1,628 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/fs/ident.c b/fs/ident.c index 5106fa3..02b4875 100644 --- a/fs/ident.c +++ b/fs/ident.c @@ -15,3 +15,7 @@ bool ident_in_group(const struct ident* identity, gid_t gid, bool use_real) { } return false; } + +bool ident_is_privileged(const struct ident* identity, bool use_real) { + return (use_real ? identity->uid : identity->euid) == 0; +} diff --git a/fs/init.c b/fs/init.c index ed9caf7..b6329a9 100644 --- a/fs/init.c +++ b/fs/init.c @@ -1,3 +1,21 @@ -#include +// SPDX-License-Identifier: ISC -void vfs_init() {} +#include +#include +#include +#include +#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); diff --git a/fs/internal.h b/fs/internal.h new file mode 100644 index 0000000..a237ec3 --- /dev/null +++ b/fs/internal.h @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: ISC + +#pragma once + +#include + +// 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); diff --git a/fs/meson.build b/fs/meson.build index 2448b0a..dc81521 100644 --- a/fs/meson.build +++ b/fs/meson.build @@ -1,7 +1,15 @@ -subdir('exec') - kernel_sources += files( + 'anon.c', + 'cache.c', + 'file.c', 'ident.c', 'init.c', + 'mount.c', + 'ops.c', + 'pipe.c', 'poll.c', + 'refs.c', + 'tmpfs.c', ) + +subdir('exec') diff --git a/fs/mount.c b/fs/mount.c new file mode 100644 index 0000000..c5fcd8b --- /dev/null +++ b/fs/mount.c @@ -0,0 +1,226 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/fs/ops.c b/fs/ops.c new file mode 100644 index 0000000..45a9753 --- /dev/null +++ b/fs/ops.c @@ -0,0 +1,656 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include + +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); +} diff --git a/fs/pipe.c b/fs/pipe.c new file mode 100644 index 0000000..1f35f11 --- /dev/null +++ b/fs/pipe.c @@ -0,0 +1,325 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; +} diff --git a/fs/refs.c b/fs/refs.c new file mode 100644 index 0000000..94f4ace --- /dev/null +++ b/fs/refs.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include +#include +#include +#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); +} diff --git a/fs/tmpfs.c b/fs/tmpfs.c new file mode 100644 index 0000000..48fb221 --- /dev/null +++ b/fs/tmpfs.c @@ -0,0 +1,509 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +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); diff --git a/include/zinnia/exec.h b/include/zinnia/exec.h new file mode 100644 index 0000000..0fffa6c --- /dev/null +++ b/include/zinnia/exec.h @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: ISC + +#pragma once + +#include +#include +#include +#include + +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); diff --git a/include/zinnia/ident.h b/include/zinnia/ident.h index b9ec626..fc1a592 100644 --- a/include/zinnia/ident.h +++ b/include/zinnia/ident.h @@ -15,3 +15,5 @@ struct ident { const struct ident* ident_kernel(void); 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); diff --git a/include/zinnia/pipe.h b/include/zinnia/pipe.h new file mode 100644 index 0000000..e8628e1 --- /dev/null +++ b/include/zinnia/pipe.h @@ -0,0 +1,14 @@ +// SPDX-License-Identifier: ISC + +#pragma once + +#include +#include +#include + +errno_t pipe_create( + enum open_flags flags, + const struct ident* identity, + struct vfs_file** out_read, + struct vfs_file** out_write +); diff --git a/include/zinnia/vfs.h b/include/zinnia/vfs.h index e38969b..e0f1132 100644 --- a/include/zinnia/vfs.h +++ b/include/zinnia/vfs.h @@ -4,9 +4,11 @@ #include #include +#include #include #include #include +#include #include #include #include @@ -34,6 +36,7 @@ struct vfs_super_block; struct vfs_filesystem; struct vfs_node_ops; struct vfs_file_ops; +struct pipe; enum open_flags : uint32_t { O_RDONLY = __O_RDONLY, @@ -62,6 +65,13 @@ enum open_flags : uint32_t { 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 { MNT_RDONLY = __MNT_RDONLY, MNT_NOSUID = __MNT_NOSUID, @@ -72,13 +82,6 @@ enum mount_flags : uint32_t { 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 { VFS_LOOKUP_FOLLOW_SYMLINKS = 1 << 0, VFS_LOOKUP_MUST_EXIST = 1 << 1, @@ -103,6 +106,7 @@ enum vfs_entry_state { VFS_ENTRY_NOT_PRESENT, // Entry is negative. }; +// A namecache entry. struct vfs_entry { struct refcount rc; // The name of this entry. @@ -111,12 +115,12 @@ struct vfs_entry { mutex_t lock; 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; - // The parent of this entry, weak reference. + // The parent of this entry, strong reference. `nullptr` for file system roots. 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_link(struct vfs_entry) child_link; @@ -132,6 +136,22 @@ struct vfs_path { struct vfs_path vfs_path_clone(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 refcount rc; enum mount_flags flags; @@ -142,9 +162,18 @@ struct vfs_mount { void vfs_mount_ref(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_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 { errno_t (*sync)(struct vfs_super_block* sb); 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 refcount rc; const struct vfs_super_block_ops* ops; + enum mount_flags flags; }; // TODO: Maybe not like this @@ -161,6 +191,7 @@ struct vfs_mknod_target { enum vfs_node_type type; const struct vfs_node_ops* ops; void* priv; + dev_t rdev; }; struct vfs_node_ops { @@ -176,6 +207,8 @@ struct vfs_node_ops { struct { // Truncates the file down to `new_length` bytes. 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; // 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); } symlink; + // VNODE_CHARDEV or VNODE_BLOCKDEV + struct { + errno_t (*open)(struct vfs_node* node, struct vfs_file* file); + } device; + // VNODE_DIR struct { errno_t (*lookup)(struct vfs_node* node, struct vfs_path* child); @@ -225,10 +263,10 @@ struct vfs_node_ops { ); errno_t (*mknod)( struct vfs_node* node, + struct vfs_path* path, mode_t mode, struct vfs_mknod_target* target, - const struct ident* identity, - struct vfs_node** out + const struct ident* identity ); errno_t (*get_dir_entries)( struct vfs_node* node, @@ -246,7 +284,6 @@ struct vfs_node_ops { struct vfs_node { struct refcount rc; enum vfs_node_type type; - bool is_seekable; const struct vfs_node_ops* ops; const struct vfs_file_ops* file_ops; @@ -254,9 +291,13 @@ struct vfs_node { mutex_t lock; void* priv; + struct pipe* pipe; ino_t id; + dev_t rdev; size_t size; + blkcnt_t blocks; + nlink_t nlink; mode_t mode; uid_t uid; gid_t gid; @@ -280,7 +321,8 @@ struct vfs_file_ops { off_t offset, 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 { @@ -303,7 +345,7 @@ struct vfs_file { // Absolute path resolved during open. char* absolute_path; - enum open_flags flags; + _Atomic(enum open_flags) flags; enum vfs_file_position position_kind; off_t position; @@ -311,6 +353,9 @@ struct vfs_file { struct vfs_path path; bool has_path; + + // Driver data. + void* priv; }; // 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); }; +// 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_unref(struct vfs_node* node); 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. struct vfs_path vfs_get_root(); -void vfs_init(); - errno_t vfs_register_filesystem(const struct vfs_filesystem* fs); errno_t vfs_sync_all(); @@ -392,12 +445,46 @@ errno_t vfs_mknod( const char* path, mode_t mode, 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 ); -// 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. -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`. // Returns the opened node as a file in `out`. @@ -411,6 +498,16 @@ errno_t vfs_file_open( 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. // 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); @@ -447,6 +544,8 @@ errno_t vfs_file_mmap( uintptr_t* out_addr ); +enum poll_flags vfs_poll(struct vfs_file* file, enum poll_flags events, struct poll_table* table); + // Directory listing. errno_t vfs_get_dir_entries( struct vfs_file* file, @@ -456,6 +555,17 @@ errno_t vfs_get_dir_entries( 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. errno_t vfs_create_anon_inode( const struct vfs_file_ops* file_ops, @@ -464,3 +574,5 @@ errno_t vfs_create_anon_inode( const struct ident* identity, struct vfs_node** out ); + +INIT_EXTERN(vfs_stage);