Files
zinnia/syscalls/vfs.c
T
2026-10-25 19:47:43 +01:00

1431 lines
36 KiB
C

// SPDX-License-Identifier: ISC
#include <zinnia/alloc.h>
#include <zinnia/atomic.h>
#include <zinnia/blockdev.h>
#include <zinnia/clock.h>
#include <zinnia/iovec.h>
#include <zinnia/pipe.h>
#include <zinnia/poll.h>
#include <zinnia/print.h>
#include <zinnia/time.h>
#include <zinnia/usercopy.h>
#include <zinnia/vfs.h>
#include <stdckdint.h>
#include <uapi/fcntl.h>
#include <uapi/ioctls.h>
#include <uapi/limits.h>
#include <uapi/poll.h>
#include <uapi/signal.h>
#include <uapi/stat.h>
#include <uapi/time.h>
#include "internal.h"
static errno_t get_file(int fd, enum open_flags access, struct vfs_file** out) {
struct vfs_file* file = process_fd_get(self(), fd);
if (!file)
return EBADF;
if (!(atomic_load(&file->flags, ATOMIC_RELAXED) & access)) {
vfs_file_unref(file);
return EBADF;
}
*out = file;
return 0;
}
static errno_t get_any_file(int fd, struct vfs_file** out) {
struct vfs_file* file = process_fd_get(self(), fd);
if (!file)
return EBADF;
*out = file;
return 0;
}
static void set_status_flags(struct vfs_file* file, enum open_flags mask, enum open_flags value) {
mutex_lock(&file->lock);
atomic_store(&file->flags, (atomic_load(&file->flags, ATOMIC_RELAXED) & ~mask) | (value & mask), ATOMIC_RELAXED);
mutex_unlock(&file->lock);
}
static errno_t copy_user_path(const __user char* path, char** out) {
if (!path)
return EFAULT;
size_t len;
if (!usercopy_strlen(path, __PATH_MAX, &len))
return EFAULT;
if (len >= __PATH_MAX)
return ENAMETOOLONG;
char* kpath = mem_alloc(len + 1, ALLOC_NOZERO);
if (!kpath)
return ENOMEM;
if (!usercopy_read(kpath, path, len)) {
mem_free(kpath);
return EFAULT;
}
kpath[len] = 0;
*out = kpath;
return 0;
}
static void get_root_cwd(struct process* proc, struct vfs_path* out_root, struct vfs_path* out_cwd) {
mutex_lock(&proc->lock);
*out_root = vfs_path_clone(proc->root);
if (out_cwd)
*out_cwd = vfs_path_clone(proc->cwd);
mutex_unlock(&proc->lock);
}
static errno_t get_at_start(
struct process* proc,
int dirfd,
const char* path,
struct vfs_path* out_root,
struct vfs_path* out_start
) {
struct vfs_path root, cwd;
get_root_cwd(proc, &root, &cwd);
if (dirfd != __AT_FDCWD && path[0] != '/') {
struct vfs_file* dir = process_fd_get(proc, dirfd);
errno_t status = !dir ? EBADF : !dir->has_path ? ENOTDIR : 0;
if (!status) {
vfs_path_drop(cwd);
cwd = vfs_path_clone(dir->path);
}
if (dir)
vfs_file_unref(dir);
if (status) {
vfs_path_drop(root);
vfs_path_drop(cwd);
return status;
}
}
*out_root = root;
*out_start = cwd;
return 0;
}
static errno_t get_at_node(
struct process* proc,
int dirfd,
const char* path,
uint32_t at_flags,
enum vfs_lookup_flags lookup_flags,
struct vfs_node** out
) {
if (path[0] == '\0' && (at_flags & __AT_EMPTY_PATH)) {
struct vfs_node* node;
if (dirfd == __AT_FDCWD) {
mutex_lock(&proc->lock);
node = vfs_entry_get_inode(proc->cwd.entry);
mutex_unlock(&proc->lock);
} else {
struct vfs_file* file = process_fd_get(proc, dirfd);
if (!file)
return EBADF;
node = file->vnode;
vfs_node_ref(node);
vfs_file_unref(file);
}
if (!node)
return ENOENT;
*out = node;
return 0;
}
lookup_flags |= VFS_LOOKUP_MUST_EXIST;
if (!(at_flags & __AT_SYMLINK_NOFOLLOW))
lookup_flags |= VFS_LOOKUP_FOLLOW_SYMLINKS;
struct vfs_path root, start;
errno_t status = get_at_start(proc, dirfd, path, &root, &start);
if (status)
return status;
struct vfs_path target;
status = vfs_path_lookup(root, start, path, &proc->identity, lookup_flags, &target);
vfs_path_drop(root);
vfs_path_drop(start);
if (status)
return status;
struct vfs_node* node = vfs_entry_get_inode(target.entry);
vfs_path_drop(target);
if (!node)
return ENOENT;
*out = node;
return 0;
}
static errno_t lookup_dir(struct process* proc, const char* path, struct vfs_path* out) {
struct vfs_path root, cwd;
get_root_cwd(proc, &root, &cwd);
struct vfs_path target;
errno_t status =
vfs_path_lookup(root, cwd, path, &proc->identity, VFS_LOOKUP_MUST_EXIST | VFS_LOOKUP_FOLLOW_SYMLINKS, &target);
vfs_path_drop(root);
vfs_path_drop(cwd);
if (status)
return status;
struct vfs_node* node = vfs_entry_get_inode(target.entry);
bool is_dir = node && node->type == VNODE_DIRECTORY;
if (node)
vfs_node_unref(node);
if (!is_dir) {
vfs_path_drop(target);
return ENOTDIR;
}
*out = target;
return 0;
}
static mode_t current_umask(struct process* proc) {
mutex_lock(&proc->lock);
mode_t mask = proc->umask;
mutex_unlock(&proc->lock);
return mask;
}
static void set_cwd(struct process* proc, struct vfs_path path) {
mutex_lock(&proc->lock);
struct vfs_path old = proc->cwd;
proc->cwd = path;
mutex_unlock(&proc->lock);
vfs_path_drop(old);
}
static struct syscall_ret do_dup(int fd, int min_fd, bool cloexec) {
struct syscall_ret ret = {};
struct vfs_file* file;
ret.error = get_any_file(fd, &file);
if (ret.error)
return ret;
int new_fd;
ret.error = process_fd_alloc_min(self(), file, min_fd, cloexec, &new_fd);
vfs_file_unref(file);
if (!ret.error)
ret.value = new_fd;
return ret;
}
static struct syscall_ret fcntl_getlk(int fd, uintptr_t arg) {
struct syscall_ret ret = {};
struct vfs_file* file;
ret.error = get_any_file(fd, &file);
if (ret.error)
return ret;
vfs_file_unref(file);
struct __flock lock;
if (!usercopy_read(&lock, (const __user void*)arg, sizeof(lock))) {
ret.error = EFAULT;
return ret;
}
lock.l_type = __F_UNLCK;
lock.l_pid = 0;
if (!usercopy_write((__user void*)arg, &lock, sizeof(lock)))
ret.error = EFAULT;
return ret;
}
constexpr size_t GETDENTS_MAX_BATCH = 32;
static struct syscall_ret do_two_paths(
int old_fd,
const __user char* old_path,
int new_fd,
const __user char* new_path,
bool link,
bool follow
) {
struct syscall_ret ret = {};
char* kold;
ret.error = copy_user_path(old_path, &kold);
if (ret.error)
return ret;
char* knew;
ret.error = copy_user_path(new_path, &knew);
if (ret.error) {
mem_free(kold);
return ret;
}
struct process* proc = self();
struct vfs_path root, old_start, new_root, new_start;
ret.error = get_at_start(proc, old_fd, kold, &root, &old_start);
if (ret.error)
goto out;
ret.error = get_at_start(proc, new_fd, knew, &new_root, &new_start);
if (ret.error)
goto out_old;
if (link)
ret.error = vfs_link(root, old_start, kold, new_start, knew, follow, &proc->identity);
else
ret.error = vfs_rename(root, old_start, kold, new_start, knew, &proc->identity);
vfs_path_drop(new_root);
vfs_path_drop(new_start);
out_old:
vfs_path_drop(root);
vfs_path_drop(old_start);
out:
mem_free(kold);
mem_free(knew);
return ret;
}
static size_t poll_scan(struct __pollfd* fds, struct vfs_file** files, size_t nfds, struct poll_table* table) {
size_t ready = 0;
for (size_t i = 0; i < nfds; i++) {
fds[i].revents = 0;
if (fds[i].fd < 0)
continue;
if (!files[i])
fds[i].revents = POLLNVAL;
else
fds[i].revents = vfs_poll(files[i], (uint16_t)fds[i].events, table);
if (fds[i].revents)
ready++;
}
return ready;
}
static errno_t read_poll_deadline(
const __user struct __timespec* timeout,
bool* out_nonblocking,
uint64_t* out_deadline
) {
struct __timespec ts;
if (!usercopy_read(&ts, timeout, sizeof(ts)))
return EFAULT;
if (ts.tv_sec < 0 || ts.tv_nsec < 0 || ts.tv_nsec >= 1'000'000'000)
return EINVAL;
uint64_t ns;
if (ckd_mul(&ns, (uint64_t)ts.tv_sec, 1'000'000'000) || ckd_add(&ns, ns, (uint64_t)ts.tv_nsec))
ns = UINT64_MAX;
*out_nonblocking = ns == 0;
if (ckd_add(out_deadline, clock_get_elapsed_ns(), ns))
*out_deadline = UINT64_MAX;
return 0;
}
static struct syscall_ret do_remove(int dirfd, const __user char* path, bool dir) {
struct syscall_ret ret = {};
char* kpath;
ret.error = copy_user_path(path, &kpath);
if (ret.error)
return ret;
struct process* proc = self();
struct vfs_path root, start;
ret.error = get_at_start(proc, dirfd, kpath, &root, &start);
if (!ret.error) {
if (dir)
ret.error = vfs_rmdir(root, start, kpath, &proc->identity);
else
ret.error = vfs_unlink(root, start, kpath, &proc->identity);
vfs_path_drop(root);
vfs_path_drop(start);
}
mem_free(kpath);
return ret;
}
constexpr size_t IOV_MAX = 1024;
static errno_t copy_iovecs(const __user iovec_t* iov, size_t count, iovec_t** out) {
if (count > IOV_MAX)
return EINVAL;
iovec_t* iovecs = mem_alloc(MAX(count, 1) * sizeof(iovec_t), 0);
if (!iovecs)
return ENOMEM;
if (count && !usercopy_read(iovecs, iov, count * sizeof(iovec_t))) {
mem_free(iovecs);
return EFAULT;
}
*out = iovecs;
return 0;
}
static struct syscall_ret do_io(int fd, iovec_t* iovecs, size_t count, bool write, bool positioned, uint64_t offset) {
struct syscall_ret ret = {};
struct vfs_file* file;
ret.error = get_file(fd, write ? O_WRONLY : O_RDONLY, &file);
if (ret.error)
return ret;
iovec_iter_t iter = iovec_iter_kernel(iovecs, count);
iter.user = true;
ssize_t done = 0;
if (write)
ret.error = positioned ? vfs_file_pwrite(file, &iter, offset, &done) : vfs_file_write(file, &iter, &done);
else
ret.error = positioned ? vfs_file_pread(file, &iter, offset, &done) : vfs_file_read(file, &iter, &done);
vfs_file_unref(file);
if (!ret.error)
ret.value = done;
return ret;
}
static struct syscall_ret do_iov(int fd, const __user iovec_t* iov, size_t count, bool write) {
struct syscall_ret ret = {};
iovec_t* iovecs;
ret.error = copy_iovecs(iov, count, &iovecs);
if (ret.error)
return ret;
ret = do_io(fd, iovecs, count, write, false, 0);
mem_free(iovecs);
return ret;
}
static struct syscall_ret do_sync(int fd, bool data_only) {
struct syscall_ret ret = {};
struct vfs_file* file;
ret.error = get_any_file(fd, &file);
if (ret.error)
return ret;
ret.error = vfs_file_sync(file, data_only);
vfs_file_unref(file);
return ret;
}
struct syscall_ret sys_pread(int fd, __user void* buf, size_t len, uint64_t offset) {
iovec_t iov = {.base = (uintptr_t)buf, .len = len};
return do_io(fd, &iov, 1, false, true, offset);
}
struct syscall_ret sys_readv(int fd, const __user iovec_t* iov, size_t count) {
return do_iov(fd, iov, count, false);
}
struct syscall_ret sys_pwrite(int fd, const __user void* buf, size_t len, uint64_t offset) {
iovec_t iov = {.base = (uintptr_t)buf, .len = len};
return do_io(fd, &iov, 1, true, true, offset);
}
struct syscall_ret sys_writev(int fd, const __user iovec_t* iov, size_t count) {
return do_iov(fd, iov, count, true);
}
struct syscall_ret sys_openat(int dirfd, const __user char* path, enum open_flags flags, mode_t mode) {
struct syscall_ret ret = {};
char* kpath;
ret.error = copy_user_path(path, &kpath);
if (ret.error)
return ret;
struct process* proc = self();
struct vfs_path root, start;
ret.error = get_at_start(proc, dirfd, kpath, &root, &start);
if (ret.error) {
mem_free(kpath);
return ret;
}
mutex_lock(&proc->lock);
mode &= ~proc->umask;
mutex_unlock(&proc->lock);
struct vfs_file* file = nullptr;
ret.error = vfs_file_open(root, start, kpath, flags & ~O_CLOEXEC, mode, &proc->identity, &file);
vfs_path_drop(root);
vfs_path_drop(start);
mem_free(kpath);
if (ret.error)
return ret;
int fd;
ret.error = process_fd_alloc(proc, file, flags & O_CLOEXEC, &fd);
vfs_file_unref(file);
if (!ret.error)
ret.value = fd;
return ret;
}
struct syscall_ret sys_seek(int fd, off_t offset, int whence) {
struct syscall_ret ret = {};
enum vfs_seek_anchor anchor;
switch (whence) {
case __SEEK_SET:
anchor = VFS_SEEK_START;
break;
case __SEEK_CUR:
anchor = VFS_SEEK_CURRENT;
break;
case __SEEK_END:
anchor = VFS_SEEK_END;
break;
default:
ret.error = EINVAL;
return ret;
}
struct vfs_file* file;
ret.error = get_any_file(fd, &file);
if (ret.error)
return ret;
off_t position = 0;
ret.error = vfs_file_seek(file, anchor, offset, &position);
vfs_file_unref(file);
if (!ret.error)
ret.value = position;
return ret;
}
struct syscall_ret sys_close(int fd) {
struct syscall_ret ret = {};
ret.error = process_fd_close(self(), fd);
return ret;
}
struct syscall_ret sys_sync() {
struct syscall_ret ret = {};
ret.error = vfs_sync_all();
errno_t block_status = blockdev_sync_all();
if (!ret.error)
ret.error = block_status;
return ret;
}
struct syscall_ret sys_fsync(int fd) {
return do_sync(fd, false);
}
struct syscall_ret sys_fdatasync(int fd) {
return do_sync(fd, true);
}
struct syscall_ret sys_ioctl(int fd, uintptr_t request, uintptr_t arg) {
struct syscall_ret ret = {};
struct vfs_file* file;
ret.error = get_any_file(fd, &file);
if (ret.error)
return ret;
uintptr_t value = 0;
if ((uint32_t)request == __FIONBIO) {
uint32_t nonblock;
if (usercopy_read(&nonblock, (const __user void*)arg, sizeof(nonblock)))
set_status_flags(file, O_NONBLOCK, nonblock ? O_NONBLOCK : 0);
else
ret.error = EFAULT;
} else {
ret.error = vfs_file_ioctl(file, request, arg, &value);
}
vfs_file_unref(file);
if (!ret.error)
ret.value = value;
return ret;
}
struct syscall_ret sys_getcwd(__user char* buf, size_t len) {
struct syscall_ret ret = {};
struct process* proc = self();
struct vfs_path root, cwd;
get_root_cwd(proc, &root, &cwd);
char* kbuf = mem_alloc(__PATH_MAX, ALLOC_NOZERO);
if (!kbuf)
ret.error = ENOMEM;
size_t path_len = 0;
if (!ret.error)
ret.error = vfs_path_absolute(cwd, &root, kbuf, __PATH_MAX, &path_len);
vfs_path_drop(root);
vfs_path_drop(cwd);
if (!ret.error && path_len + 1 > len)
ret.error = ERANGE;
if (!ret.error && !usercopy_write(buf, kbuf, path_len + 1))
ret.error = EFAULT;
mem_free(kbuf);
if (!ret.error)
ret.value = path_len + 1;
return ret;
}
struct syscall_ret sys_fstat(int fd, __user struct __stat* buf) {
struct syscall_ret ret = {};
struct vfs_file* file;
ret.error = get_any_file(fd, &file);
if (ret.error)
return ret;
struct __stat st;
vfs_stat(file->vnode, &st);
vfs_file_unref(file);
if (!usercopy_write(buf, &st, sizeof(st)))
ret.error = EFAULT;
return ret;
}
struct syscall_ret sys_fstatat(int dirfd, const __user char* path, __user struct __stat* buf, uint32_t flags) {
struct syscall_ret ret = {};
if (flags & ~(__AT_SYMLINK_NOFOLLOW | __AT_EMPTY_PATH)) {
ret.error = EINVAL;
return ret;
}
char* kpath;
ret.error = copy_user_path(path, &kpath);
if (ret.error)
return ret;
struct vfs_node* node;
ret.error = get_at_node(self(), dirfd, kpath, flags, 0, &node);
mem_free(kpath);
if (ret.error)
return ret;
struct __stat st;
vfs_stat(node, &st);
vfs_node_unref(node);
if (!usercopy_write(buf, &st, sizeof(st)))
ret.error = EFAULT;
return ret;
}
struct syscall_ret sys_dup(int fd) {
return do_dup(fd, 0, false);
}
struct syscall_ret sys_flock(int fd, int operation) {
struct syscall_ret ret = {};
struct vfs_file* file;
ret.error = get_any_file(fd, &file);
if (!ret.error)
vfs_file_unref(file);
return ret;
}
struct syscall_ret sys_dup3(int old_fd, int new_fd, uint32_t flags) {
struct syscall_ret ret = {};
if (flags & ~O_CLOEXEC) {
ret.error = EINVAL;
return ret;
}
if (old_fd == new_fd) {
bool cloexec;
ret.error = process_fd_get_cloexec(self(), old_fd, &cloexec);
if (!ret.error)
ret.value = old_fd;
return ret;
}
struct vfs_file* file;
ret.error = get_any_file(old_fd, &file);
if (ret.error)
return ret;
ret.error = process_fd_replace(self(), new_fd, file, flags & O_CLOEXEC);
vfs_file_unref(file);
if (!ret.error)
ret.value = new_fd;
return ret;
}
struct syscall_ret sys_mknodat(int dirfd, const __user char* path, mode_t mode, dev_t dev) {
struct syscall_ret ret = {};
struct process* proc = self();
struct vfs_mknod_target target = {};
switch (mode & __S_IFMT) {
case __S_IFIFO:
target.type = VNODE_FIFO;
break;
case __S_IFSOCK:
target.type = VNODE_SOCKET;
break;
case __S_IFCHR:
case __S_IFBLK:
if (!ident_is_privileged(&proc->identity, false)) {
ret.error = EPERM;
return ret;
}
target.type = (mode & __S_IFMT) == __S_IFCHR ? VNODE_CHARDEV : VNODE_BLOCKDEV;
target.rdev = dev;
break;
default:
ret.error = ENOTSUP;
return ret;
}
char* kpath;
ret.error = copy_user_path(path, &kpath);
if (ret.error)
return ret;
struct vfs_path root, start;
ret.error = get_at_start(proc, dirfd, kpath, &root, &start);
if (!ret.error) {
ret.error =
vfs_mknod(root, start, kpath, mode & ~current_umask(proc) & 07777, &target, &proc->identity, nullptr);
vfs_path_drop(root);
vfs_path_drop(start);
}
mem_free(kpath);
return ret;
}
struct syscall_ret sys_mkdirat(int dirfd, const __user char* path, mode_t mode) {
struct syscall_ret ret = {};
char* kpath;
ret.error = copy_user_path(path, &kpath);
if (ret.error)
return ret;
struct process* proc = self();
struct vfs_path root, start;
ret.error = get_at_start(proc, dirfd, kpath, &root, &start);
if (!ret.error) {
ret.error = vfs_mkdir(root, start, kpath, mode & ~current_umask(proc) & 07777, &proc->identity);
vfs_path_drop(root);
vfs_path_drop(start);
}
mem_free(kpath);
return ret;
}
struct syscall_ret sys_chdir(const __user char* path) {
struct syscall_ret ret = {};
char* kpath;
ret.error = copy_user_path(path, &kpath);
if (ret.error)
return ret;
struct process* proc = self();
struct vfs_path target;
ret.error = lookup_dir(proc, kpath, &target);
mem_free(kpath);
if (ret.error)
return ret;
set_cwd(proc, target);
return ret;
}
struct syscall_ret sys_fchdir(int fd) {
struct syscall_ret ret = {};
struct process* proc = self();
struct vfs_file* file = process_fd_get(proc, fd);
if (!file) {
ret.error = EBADF;
return ret;
}
if (file->has_path && file->vnode->type == VNODE_DIRECTORY)
set_cwd(proc, vfs_path_clone(file->path));
else
ret.error = ENOTDIR;
vfs_file_unref(file);
return ret;
}
struct syscall_ret sys_getdents(int fd, __user struct __dirent* buf, size_t len) {
struct syscall_ret ret = {};
size_t count = MIN(len / sizeof(struct __dirent), GETDENTS_MAX_BATCH);
if (count == 0) {
ret.error = EINVAL;
return ret;
}
struct vfs_file* file;
ret.error = get_file(fd, O_RDONLY, &file);
if (ret.error)
return ret;
struct __dirent* entries = mem_alloc(count * sizeof(*entries), 0);
if (!entries) {
vfs_file_unref(file);
ret.error = ENOMEM;
return ret;
}
size_t read = 0;
ret.error = vfs_get_dir_entries(file, entries, count, &self()->identity, &read);
vfs_file_unref(file);
if (!ret.error && read && !usercopy_write(buf, entries, read * sizeof(*entries)))
ret.error = EFAULT;
mem_free(entries);
if (!ret.error)
ret.value = read * sizeof(struct __dirent);
return ret;
}
struct syscall_ret sys_fcntl(int fd, int cmd, uintptr_t arg) {
struct syscall_ret ret = {};
struct process* proc = self();
struct vfs_file* file;
switch (cmd) {
case __F_DUPFD:
return do_dup(fd, arg, false);
case __F_DUPFD_CLOEXEC:
return do_dup(fd, arg, true);
case __F_GETFD: {
bool cloexec;
ret.error = process_fd_get_cloexec(proc, fd, &cloexec);
if (!ret.error)
ret.value = cloexec ? __FD_CLOEXEC : 0;
return ret;
}
case __F_SETFD:
ret.error = process_fd_set_cloexec(proc, fd, arg & __FD_CLOEXEC);
return ret;
case __F_GETFL:
ret.error = get_any_file(fd, &file);
if (ret.error)
return ret;
mutex_lock(&file->lock);
ret.value = atomic_load(&file->flags, ATOMIC_RELAXED);
mutex_unlock(&file->lock);
vfs_file_unref(file);
return ret;
case __F_SETFL:
ret.error = get_any_file(fd, &file);
if (ret.error)
return ret;
set_status_flags(file, O_NONBLOCK | O_APPEND, arg);
vfs_file_unref(file);
return ret;
case __F_GETOWN:
case __F_SETOWN:
case __F_GETOWN_EX:
case __F_SETOWN_EX:
log_warn("fcntl: Command %d is a stub\n", cmd);
return ret;
case __F_GETLK:
case __F_OFD_GETLK:
return fcntl_getlk(fd, arg);
case __F_SETLK:
case __F_SETLKW:
case __F_OFD_SETLK:
case __F_OFD_SETLKW:
ret.error = get_any_file(fd, &file);
if (!ret.error)
vfs_file_unref(file);
return ret;
default:
ret.error = EINVAL;
return ret;
}
}
struct syscall_ret sys_ppoll(
__user struct __pollfd* user_fds,
size_t nfds,
const __user struct __timespec* timeout,
const __user __sigset_t* sigmask
) {
struct syscall_ret ret = {};
if (nfds > process_fd_limit(self())) {
ret.error = EINVAL;
return ret;
}
bool nonblocking = false;
uint64_t deadline = 0;
if (timeout) {
ret.error = read_poll_deadline(timeout, &nonblocking, &deadline);
if (ret.error)
return ret;
}
struct __pollfd* fds = mem_alloc(MAX(nfds, 1) * sizeof(*fds), 0);
struct vfs_file** files = mem_alloc(MAX(nfds, 1) * sizeof(*files), 0);
if (!fds || !files) {
mem_free(fds);
mem_free(files);
ret.error = ENOMEM;
return ret;
}
if (nfds && !usercopy_read(fds, user_fds, nfds * sizeof(*fds))) {
mem_free(fds);
mem_free(files);
ret.error = EFAULT;
return ret;
}
struct process* proc = self();
for (size_t i = 0; i < nfds; i++)
files[i] = fds[i].fd >= 0 ? process_fd_get(proc, fds[i].fd) : nullptr;
struct poll_table table;
poll_table_init(&table);
size_t ready = 0;
bool registered = false;
while (true) {
atomic_store(&table.triggered, false, ATOMIC_RELAXED);
ready = poll_scan(fds, files, nfds, nonblocking || registered ? nullptr : &table);
registered = true;
if (table.error) {
ret.error = table.error;
break;
}
if (ready || nonblocking || (timeout && clock_get_elapsed_ns() >= deadline))
break;
if (poll_table_wait(&table, timeout ? &deadline : nullptr) == EINTR) {
ret.error = EINTR;
break;
}
}
poll_table_fini(&table);
for (size_t i = 0; i < nfds; i++) {
if (files[i])
vfs_file_unref(files[i]);
}
if (!ret.error && nfds && !usercopy_write(user_fds, fds, nfds * sizeof(*fds)))
ret.error = EFAULT;
mem_free(fds);
mem_free(files);
if (!ret.error)
ret.value = ready;
return ret;
}
struct syscall_ret sys_pipe(__user int* fds, uint32_t flags) {
struct syscall_ret ret = {};
if (flags & ~(O_CLOEXEC | O_NONBLOCK)) {
ret.error = EINVAL;
return ret;
}
struct process* proc = self();
struct vfs_file* read_end;
struct vfs_file* write_end;
ret.error = pipe_create(flags, &proc->identity, &read_end, &write_end);
if (ret.error)
return ret;
const bool cloexec = flags & O_CLOEXEC;
int kfds[2] = {-1, -1};
ret.error = process_fd_alloc(proc, read_end, cloexec, &kfds[0]);
if (!ret.error)
ret.error = process_fd_alloc(proc, write_end, cloexec, &kfds[1]);
if (!ret.error && !usercopy_write(fds, kfds, sizeof(kfds)))
ret.error = EFAULT;
if (ret.error) {
for (size_t i = 0; i < 2; i++) {
if (kfds[i] >= 0)
process_fd_close(proc, kfds[i]);
}
}
vfs_file_unref(read_end);
vfs_file_unref(write_end);
return ret;
}
struct syscall_ret sys_faccessat(int dirfd, const __user char* path, uint32_t amode, uint32_t flags) {
struct syscall_ret ret = {};
if (amode & ~(__R_OK | __W_OK | __X_OK)) {
ret.error = EINVAL;
return ret;
}
if (flags & ~(__AT_EACCESS | __AT_SYMLINK_NOFOLLOW | __AT_EMPTY_PATH)) {
ret.error = EINVAL;
return ret;
}
char* kpath;
ret.error = copy_user_path(path, &kpath);
if (ret.error)
return ret;
struct process* proc = self();
const bool use_real = !(flags & __AT_EACCESS);
struct vfs_node* node;
ret.error = get_at_node(proc, dirfd, kpath, flags, use_real ? VFS_LOOKUP_REAL_ID : 0, &node);
mem_free(kpath);
if (ret.error)
return ret;
ret.error = vfs_node_try_access(node, &proc->identity, amode, use_real);
vfs_node_unref(node);
return ret;
}
struct syscall_ret sys_statvfs(const __user char* path, __user struct __statvfs* buf) {
struct syscall_ret ret = {};
char* kpath;
ret.error = copy_user_path(path, &kpath);
if (ret.error)
return ret;
struct vfs_node* node;
ret.error = get_at_node(self(), __AT_FDCWD, kpath, 0, 0, &node);
mem_free(kpath);
if (ret.error)
return ret;
struct __statvfs st;
ret.error = vfs_statvfs(node, &st);
vfs_node_unref(node);
if (ret.error)
return ret;
if (!usercopy_write(buf, &st, sizeof(st)))
ret.error = EFAULT;
return ret;
}
struct syscall_ret sys_fstatvfs(int fd, __user struct __statvfs* buf) {
struct syscall_ret ret = {};
struct vfs_file* file;
ret.error = get_any_file(fd, &file);
if (ret.error)
return ret;
struct __statvfs st;
ret.error = vfs_statvfs(file->vnode, &st);
vfs_file_unref(file);
if (ret.error)
return ret;
if (!usercopy_write(buf, &st, sizeof(st)))
ret.error = EFAULT;
return ret;
}
struct syscall_ret sys_renameat(int old_fd, const __user char* old_path, int new_fd, const __user char* new_path) {
return do_two_paths(old_fd, old_path, new_fd, new_path, false, false);
}
struct syscall_ret sys_fchmod(int fd, mode_t mode) {
struct syscall_ret ret = {};
struct vfs_file* file;
ret.error = get_any_file(fd, &file);
if (ret.error)
return ret;
ret.error = vfs_chmod(file->vnode, mode, &self()->identity);
vfs_file_unref(file);
return ret;
}
struct syscall_ret sys_truncate(const __user char* path, off_t length) {
struct syscall_ret ret = {};
if (length < 0) {
ret.error = EINVAL;
return ret;
}
char* kpath;
ret.error = copy_user_path(path, &kpath);
if (ret.error)
return ret;
struct process* proc = self();
struct vfs_node* node;
ret.error = get_at_node(proc, __AT_FDCWD, kpath, 0, 0, &node);
mem_free(kpath);
if (ret.error)
return ret;
ret.error = vfs_node_try_access(node, &proc->identity, W_OK, false);
if (!ret.error)
ret.error = vfs_truncate(node, length);
vfs_node_unref(node);
return ret;
}
struct syscall_ret sys_ftruncate(int fd, off_t length) {
struct syscall_ret ret = {};
if (length < 0) {
ret.error = EINVAL;
return ret;
}
struct vfs_file* file;
ret.error = get_any_file(fd, &file);
if (ret.error)
return ret;
ret.error = (atomic_load(&file->flags, ATOMIC_RELAXED) & O_WRONLY) ? vfs_truncate(file->vnode, length) : EINVAL;
vfs_file_unref(file);
return ret;
}
struct syscall_ret sys_fallocate(int fd, off_t offset, off_t length) {
struct syscall_ret ret = {};
if (offset < 0 || length <= 0) {
ret.error = EINVAL;
return ret;
}
struct vfs_file* file;
ret.error = get_file(fd, O_WRONLY, &file);
if (ret.error)
return ret;
struct vfs_node* node = file->vnode;
if (node->type != VNODE_REGULAR) {
vfs_file_unref(file);
ret.error = ENODEV;
return ret;
}
uint64_t required;
if (ckd_add(&required, (uint64_t)offset, (uint64_t)length))
required = UINT64_MAX;
mutex_lock(&node->lock);
const bool grow = required > node->size;
mutex_unlock(&node->lock);
if (grow)
ret.error = vfs_truncate(node, required);
vfs_file_unref(file);
return ret;
}
struct syscall_ret sys_fchmodat(int dirfd, const __user char* path, mode_t mode, uint32_t flags) {
struct syscall_ret ret = {};
if (flags & ~(__AT_SYMLINK_NOFOLLOW | __AT_EMPTY_PATH)) {
ret.error = EINVAL;
return ret;
}
char* kpath;
ret.error = copy_user_path(path, &kpath);
if (ret.error)
return ret;
struct process* proc = self();
struct vfs_node* node;
ret.error = get_at_node(proc, dirfd, kpath, flags, 0, &node);
mem_free(kpath);
if (ret.error)
return ret;
ret.error = vfs_chmod(node, mode, &proc->identity);
vfs_node_unref(node);
return ret;
}
struct syscall_ret sys_fchownat(int dirfd, const __user char* path, uid_t uid, gid_t gid, uint32_t flags) {
struct syscall_ret ret = {};
if (flags & ~(__AT_SYMLINK_NOFOLLOW | __AT_EMPTY_PATH)) {
ret.error = EINVAL;
return ret;
}
char* kpath;
ret.error = copy_user_path(path, &kpath);
if (ret.error)
return ret;
struct process* proc = self();
struct vfs_node* node;
ret.error = get_at_node(proc, dirfd, kpath, flags, 0, &node);
mem_free(kpath);
if (ret.error)
return ret;
ret.error = vfs_chown(node, uid, gid, &proc->identity);
vfs_node_unref(node);
return ret;
}
struct syscall_ret sys_utimensat(int dirfd, const __user char* path, const __user timespec_t* times, uint32_t flags) {
struct syscall_ret ret = {};
if (flags & ~(__AT_SYMLINK_NOFOLLOW | __AT_EMPTY_PATH)) {
ret.error = EINVAL;
return ret;
}
timespec_t ts[2];
if (times && !usercopy_read(ts, times, sizeof(ts))) {
ret.error = EFAULT;
return ret;
}
char* kpath = nullptr;
if (path) {
ret.error = copy_user_path(path, &kpath);
if (ret.error)
return ret;
} else {
flags |= __AT_EMPTY_PATH;
}
struct process* proc = self();
struct vfs_node* node;
ret.error = get_at_node(proc, dirfd, kpath ? kpath : "", flags, 0, &node);
mem_free(kpath);
if (ret.error)
return ret;
ret.error = vfs_utimens(node, times ? ts : nullptr, &proc->identity);
vfs_node_unref(node);
return ret;
}
struct syscall_ret sys_unlinkat(int dirfd, const __user char* path, uint32_t flags) {
struct syscall_ret ret = {};
if (flags & ~__AT_REMOVEDIR) {
ret.error = EINVAL;
return ret;
}
return do_remove(dirfd, path, flags & __AT_REMOVEDIR);
}
struct syscall_ret sys_rmdirat(int dirfd, const __user char* path) {
return do_remove(dirfd, path, true);
}
struct syscall_ret sys_linkat(
int old_fd,
const __user char* old_path,
int new_fd,
const __user char* new_path,
uint32_t flags
) {
struct syscall_ret ret = {};
if (flags & ~__AT_SYMLINK_FOLLOW) {
ret.error = EINVAL;
return ret;
}
return do_two_paths(old_fd, old_path, new_fd, new_path, true, flags & __AT_SYMLINK_FOLLOW);
}
struct syscall_ret sys_symlinkat(const __user char* target, int dirfd, const __user char* path) {
struct syscall_ret ret = {};
char* ktarget;
ret.error = copy_user_path(target, &ktarget);
if (ret.error)
return ret;
if (ktarget[0] == '\0') {
mem_free(ktarget);
ret.error = ENOENT;
return ret;
}
char* kpath;
ret.error = copy_user_path(path, &kpath);
if (ret.error) {
mem_free(ktarget);
return ret;
}
struct process* proc = self();
struct vfs_path root, start;
ret.error = get_at_start(proc, dirfd, kpath, &root, &start);
if (!ret.error) {
ret.error = vfs_symlink(root, start, kpath, ktarget, &proc->identity);
vfs_path_drop(root);
vfs_path_drop(start);
}
mem_free(kpath);
mem_free(ktarget);
return ret;
}
struct syscall_ret sys_readlinkat(int dirfd, const __user char* path, __user char* buf, size_t len) {
struct syscall_ret ret = {};
char* kpath;
ret.error = copy_user_path(path, &kpath);
if (ret.error)
return ret;
struct vfs_node* node;
ret.error = get_at_node(self(), dirfd, kpath, __AT_SYMLINK_NOFOLLOW, 0, &node);
mem_free(kpath);
if (ret.error)
return ret;
iovec_t iov = {.base = (uintptr_t)buf, .len = len};
iovec_iter_t iter = iovec_iter_kernel(&iov, 1);
iter.user = true;
size_t read = 0;
ret.error = vfs_read_link(node, &iter, &read);
vfs_node_unref(node);
if (!ret.error)
ret.value = read;
return ret;
}
struct syscall_ret sys_mount(
const __user char* type,
const __user char* dir,
enum mount_flags flags,
const __user void* data
) {
struct syscall_ret ret = {};
struct process* proc = self();
if (!ident_is_privileged(&proc->identity, false)) {
ret.error = EPERM;
return ret;
}
char* ktype;
ret.error = copy_user_path(type, &ktype);
if (ret.error)
return ret;
char* kdir;
ret.error = copy_user_path(dir, &kdir);
if (ret.error) {
mem_free(ktype);
return ret;
}
char* kdata = nullptr;
if (data) {
ret.error = copy_user_path(data, &kdata);
if (ret.error)
goto out;
}
struct vfs_path target;
ret.error = lookup_dir(proc, kdir, &target);
if (ret.error)
goto out;
struct vfs_mount* mount;
ret.error = vfs_mount(ktype, flags, kdata, &mount);
if (!ret.error) {
ret.error = vfs_path_mount(target, mount);
if (ret.error)
vfs_unmount(mount);
vfs_mount_unref(mount);
}
vfs_path_drop(target);
out:
mem_free(kdata);
mem_free(kdir);
mem_free(ktype);
return ret;
}
struct syscall_ret sys_chroot(const __user char* path) {
struct syscall_ret ret = {};
struct process* proc = self();
if (!ident_is_privileged(&proc->identity, false)) {
ret.error = EPERM;
return ret;
}
char* kpath;
ret.error = copy_user_path(path, &kpath);
if (ret.error)
return ret;
struct vfs_path target;
ret.error = lookup_dir(proc, kpath, &target);
mem_free(kpath);
if (ret.error)
return ret;
mutex_lock(&proc->lock);
struct vfs_path old = proc->root;
proc->root = target;
mutex_unlock(&proc->lock);
vfs_path_drop(old);
return ret;
}
struct syscall_ret sys_umount(const __user char* dir, uint32_t flags) {
struct syscall_ret ret = {};
struct process* proc = self();
if (!ident_is_privileged(&proc->identity, false)) {
ret.error = EPERM;
return ret;
}
char* kdir;
ret.error = copy_user_path(dir, &kdir);
if (ret.error)
return ret;
struct vfs_path target;
ret.error = lookup_dir(proc, kdir, &target);
mem_free(kdir);
if (ret.error)
return ret;
ret.error = vfs_path_unmount(target);
vfs_path_drop(target);
return ret;
}
struct syscall_ret sys_fadvise(int fd, off_t offset, off_t length, int advice) {
struct syscall_ret ret = {};
if (length < 0 || advice < __POSIX_FADV_NORMAL || advice > __POSIX_FADV_NOREUSE) {
ret.error = EINVAL;
return ret;
}
struct vfs_file* file;
ret.error = get_any_file(fd, &file);
if (ret.error)
return ret;
if (file->vnode->type == VNODE_FIFO)
ret.error = ESPIPE;
vfs_file_unref(file);
return ret;
}