diff --git a/include/zinnia/syscall_list.inc b/include/zinnia/syscall_list.inc index e8595df..cf1b01f 100644 --- a/include/zinnia/syscall_list.inc +++ b/include/zinnia/syscall_list.inc @@ -8,10 +8,10 @@ SYSCALL_DECLARE(2, getuname, 1) SYSCALL_DECLARE(3, setuname, 1) SYSCALL_DECLARE(4, archctl, 2) // SYSCALL_DECLARE(5, reboot, -1) -// SYSCALL_DECLARE(6, mmap, -1) -// SYSCALL_DECLARE(7, munmap, -1) -// SYSCALL_DECLARE(8, mprotect, -1) -// SYSCALL_DECLARE(9, madvise, -1) +SYSCALL_DECLARE(6, mmap, 6) +SYSCALL_DECLARE(7, munmap, 2) +SYSCALL_DECLARE(8, mprotect, 3) +SYSCALL_DECLARE(9, madvise, 3) // SYSCALL_DECLARE(10, sigprocmask, -1) // SYSCALL_DECLARE(11, sigsuspend, -1) // SYSCALL_DECLARE(12, sigpending, -1) @@ -29,47 +29,47 @@ SYSCALL_DECLARE(4, archctl, 2) // SYSCALL_DECLARE(24, getppid, -1) // SYSCALL_DECLARE(25, waitid, -1) // SYSCALL_DECLARE(26, waitpid, -1) -// SYSCALL_DECLARE(27, pread, -1) -// SYSCALL_DECLARE(28, readv, -1) -// SYSCALL_DECLARE(29, pwrite, -1) -// SYSCALL_DECLARE(30, writev, -1) -// SYSCALL_DECLARE(31, seek, -1) -// SYSCALL_DECLARE(32, ioctl, -1) -// SYSCALL_DECLARE(33, openat, -1) -// SYSCALL_DECLARE(34, close, -1) -// SYSCALL_DECLARE(35, fstat, -1) -// SYSCALL_DECLARE(36, fstatat, -1) -// SYSCALL_DECLARE(37, statvfs, -1) -// SYSCALL_DECLARE(38, fstatvfs, -1) -// SYSCALL_DECLARE(39, faccessat, -1) -// SYSCALL_DECLARE(40, fcntl, -1) -// SYSCALL_DECLARE(41, ftruncate, -1) -// SYSCALL_DECLARE(42, truncate, -1) -// SYSCALL_DECLARE(43, fallocate, -1) -// SYSCALL_DECLARE(44, fadvise, -1) -// SYSCALL_DECLARE(45, utimensat, -1) -// SYSCALL_DECLARE(46, mknodat, -1) -// SYSCALL_DECLARE(47, getcwd, -1) -// SYSCALL_DECLARE(48, chdir, -1) -// SYSCALL_DECLARE(49, fchdir, -1) -// SYSCALL_DECLARE(50, mkdirat, -1) -// SYSCALL_DECLARE(51, rmdirat, -1) -// SYSCALL_DECLARE(52, getdents, -1) -// SYSCALL_DECLARE(53, renameat, -1) -// SYSCALL_DECLARE(54, fchmod, -1) -// SYSCALL_DECLARE(55, fchmodat, -1) -// SYSCALL_DECLARE(56, fchownat, -1) -// SYSCALL_DECLARE(57, linkat, -1) -// SYSCALL_DECLARE(58, symlinkat, -1) -// SYSCALL_DECLARE(59, unlinkat, -1) -// SYSCALL_DECLARE(60, readlinkat, -1) -// SYSCALL_DECLARE(61, flock, -1) -// SYSCALL_DECLARE(62, ppoll, -1) -// SYSCALL_DECLARE(63, dup, -1) -// SYSCALL_DECLARE(64, dup3, -1) -// SYSCALL_DECLARE(65, sync, -1) -// SYSCALL_DECLARE(66, fsync, -1) -// SYSCALL_DECLARE(67, fdatasync, -1) +SYSCALL_DECLARE(27, pread, 4) +SYSCALL_DECLARE(28, readv, 3) +SYSCALL_DECLARE(29, pwrite, 4) +SYSCALL_DECLARE(30, writev, 3) +SYSCALL_DECLARE(31, seek, 3) +SYSCALL_DECLARE(32, ioctl, 3) +SYSCALL_DECLARE(33, openat, 4) +SYSCALL_DECLARE(34, close, 1) +SYSCALL_DECLARE(35, fstat, 2) +SYSCALL_DECLARE(36, fstatat, 4) +SYSCALL_DECLARE(37, statvfs, 2) +SYSCALL_DECLARE(38, fstatvfs, 2) +SYSCALL_DECLARE(39, faccessat, 4) +SYSCALL_DECLARE(40, fcntl, 3) +SYSCALL_DECLARE(41, ftruncate, 2) +SYSCALL_DECLARE(42, truncate, 2) +SYSCALL_DECLARE(43, fallocate, 3) +SYSCALL_DECLARE(44, fadvise, 4) +SYSCALL_DECLARE(45, utimensat, 4) +SYSCALL_DECLARE(46, mknodat, 4) +SYSCALL_DECLARE(47, getcwd, 2) +SYSCALL_DECLARE(48, chdir, 1) +SYSCALL_DECLARE(49, fchdir, 1) +SYSCALL_DECLARE(50, mkdirat, 3) +SYSCALL_DECLARE(51, rmdirat, 2) +SYSCALL_DECLARE(52, getdents, 3) +SYSCALL_DECLARE(53, renameat, 4) +SYSCALL_DECLARE(54, fchmod, 2) +SYSCALL_DECLARE(55, fchmodat, 4) +SYSCALL_DECLARE(56, fchownat, 5) +SYSCALL_DECLARE(57, linkat, 5) +SYSCALL_DECLARE(58, symlinkat, 3) +SYSCALL_DECLARE(59, unlinkat, 3) +SYSCALL_DECLARE(60, readlinkat, 4) +SYSCALL_DECLARE(61, flock, 2) +SYSCALL_DECLARE(62, ppoll, 4) +SYSCALL_DECLARE(63, dup, 1) +SYSCALL_DECLARE(64, dup3, 3) +SYSCALL_DECLARE(65, sync, 0) +SYSCALL_DECLARE(66, fsync, 1) +SYSCALL_DECLARE(67, fdatasync, 1) // SYSCALL_DECLARE(68, getgroups, -1) // SYSCALL_DECLARE(69, setgroups, -1) // SYSCALL_DECLARE(70, getsid, -1) @@ -90,8 +90,8 @@ SYSCALL_DECLARE(4, archctl, 2) // SYSCALL_DECLARE(85, setresgid, -1) // SYSCALL_DECLARE(86, setreuid, -1) // SYSCALL_DECLARE(87, setregid, -1) -// SYSCALL_DECLARE(88, umask, -1) -// SYSCALL_DECLARE(89, pipe, -1) +SYSCALL_DECLARE(88, umask, 1) +SYSCALL_DECLARE(89, pipe, 2) // SYSCALL_DECLARE(90, futex_wait, -1) // SYSCALL_DECLARE(91, futex_wake, -1) // SYSCALL_DECLARE(92, thread_create, -1) @@ -111,9 +111,9 @@ SYSCALL_DECLARE(4, archctl, 2) // SYSCALL_DECLARE(106, clock_set, -1) // SYSCALL_DECLARE(107, sleep, -1) // SYSCALL_DECLARE(108, yield, -1) -// SYSCALL_DECLARE(109, chroot, -1) -// SYSCALL_DECLARE(110, mount, -1) -// SYSCALL_DECLARE(111, umount, -1) +SYSCALL_DECLARE(109, chroot, 1) +SYSCALL_DECLARE(110, mount, 4) +SYSCALL_DECLARE(111, umount, 2) // SYSCALL_DECLARE(112, socket, -1) // SYSCALL_DECLARE(113, socketpair, -1) // SYSCALL_DECLARE(114, shutdown, -1) @@ -145,5 +145,5 @@ SYSCALL_DECLARE(4, archctl, 2) // SYSCALL_DECLARE(140, timerfd_settime, -1) // SYSCALL_DECLARE(141, signalfd_create, -1) // SYSCALL_DECLARE(142, eventfd_create, -1) -// SYSCALL_DECLARE(143, msync, -1) +SYSCALL_DECLARE(143, msync, 3) // SYSCALL_DECLARE(144, sysconf, -1) diff --git a/syscalls/archctl.c b/syscalls/archctl.c deleted file mode 100644 index 9cc1d03..0000000 --- a/syscalls/archctl.c +++ /dev/null @@ -1,6 +0,0 @@ -#include -#include - -struct syscall_ret sys_archctl(uintptr_t op, uintptr_t arg) { - return arch_archctl(op, arg); -} diff --git a/syscalls/exit.c b/syscalls/exit.c deleted file mode 100644 index 868120e..0000000 --- a/syscalls/exit.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include - -struct syscall_ret sys_exit(int err) { - struct syscall_ret ret = {}; - - ret.error = ENOSYS; - - panic("sys::exit() not implemented!\n"); - - return ret; -} diff --git a/syscalls/internal.h b/syscalls/internal.h new file mode 100644 index 0000000..586ffe4 --- /dev/null +++ b/syscalls/internal.h @@ -0,0 +1,12 @@ +// SPDX-License-Identifier: ISC + +#pragma once + +#include +#include +#include +#include + +static inline struct process* self() { + return percpu_get()->sched.current->process; +} diff --git a/syscalls/memory.c b/syscalls/memory.c new file mode 100644 index 0000000..404081a --- /dev/null +++ b/syscalls/memory.c @@ -0,0 +1,159 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include +#include +#include +#include +#include +#include +#include "internal.h" + +static struct vma* space() { + return percpu_get()->sched.current->space; +} + +constexpr enum prot_flags PROT_MASK = (PROT_READ | PROT_WRITE | PROT_EXEC); + +static bool is_user_range(uintptr_t addr, size_t len) { + uintptr_t last; + if (len == 0 || ckd_add(&last, addr, len - 1)) + return false; + return arch_mem_addr_is_user(addr) && arch_mem_addr_is_user(last); +} + +static errno_t map_anon(struct vma* vma, uintptr_t addr, size_t len, uint32_t prot, uint32_t flags) { + if (!(flags & MAP_SHARED)) + return vma_map(vma, nullptr, addr, len, prot, 0, 0); + + struct vmo* anon; + errno_t status = vmo_new_anon(&anon); + if (status) + return status; + status = vma_map(vma, anon, addr, len, prot, VMA_SHARED, 0); + vmo_unref(anon); + return status; +} + +static errno_t get_map_file(int fd, uint32_t prot, uint32_t flags, struct vfs_file** out) { + struct vfs_file* file = process_fd_get(self(), fd); + if (!file) + return EBADF; + if (!(atomic_load(&file->flags, ATOMIC_RELAXED) & O_RDONLY) || + ((flags & MAP_SHARED) && (prot & PROT_WRITE) && !(atomic_load(&file->flags, ATOMIC_RELAXED) & O_WRONLY))) { + vfs_file_unref(file); + return EACCES; + } + *out = file; + return 0; +} + +struct syscall_ret sys_mmap(uintptr_t addr, size_t len, uint32_t prot, uint32_t flags, int fd, off_t offset) { + struct syscall_ret ret = {}; + + const size_t page_size = arch_mem_page_size(); + const uint32_t kind = flags & (MAP_SHARED | MAP_PRIVATE); + + if (len == 0 || (prot & ~PROT_MASK) || (kind != MAP_SHARED && kind != MAP_PRIVATE)) { + ret.error = EINVAL; + return ret; + } + if (offset < 0 || (uint64_t)offset % page_size != 0) { + ret.error = EINVAL; + return ret; + } + if ((flags & MAP_FIXED) && (addr % page_size != 0 || !is_user_range(addr, len))) { + ret.error = EINVAL; + return ret; + } + + struct vfs_file* file = nullptr; + if (!(flags & MAP_ANON)) { + ret.error = get_map_file(fd, prot, flags, &file); + if (ret.error) + return ret; + } + + struct vma* vma = space(); + mutex_lock(&vma->map_lock); + + if (!(flags & MAP_FIXED)) + ret.error = vma_find_free(vma, addr, len, &addr); + + if (!ret.error) { + if (file) + ret.error = vfs_file_mmap(file, vma, addr, len, prot, flags, offset, &addr); + else + ret.error = map_anon(vma, addr, len, prot, flags); + } + + mutex_unlock(&vma->map_lock); + if (file) + vfs_file_unref(file); + if (!ret.error) + ret.value = addr; + return ret; +} + +struct syscall_ret sys_mprotect(uintptr_t addr, size_t len, uint32_t prot) { + struct syscall_ret ret = {}; + + if ((prot & ~PROT_MASK) || !is_user_range(addr, len)) { + ret.error = EINVAL; + return ret; + } + + struct vma* vma = space(); + mutex_lock(&vma->map_lock); + ret.error = vma_protect(vma, addr, len, prot); + mutex_unlock(&vma->map_lock); + return ret; +} + +struct syscall_ret sys_munmap(uintptr_t addr, size_t len) { + struct syscall_ret ret = {}; + + if (!is_user_range(addr, len)) { + ret.error = EINVAL; + return ret; + } + + struct vma* vma = space(); + mutex_lock(&vma->map_lock); + ret.error = vma_unmap(vma, addr, len); + mutex_unlock(&vma->map_lock); + return ret; +} + +struct syscall_ret sys_madvise(uintptr_t addr, size_t len, int advice) { + struct syscall_ret ret = {}; + + if (addr % arch_mem_page_size() != 0) + ret.error = EINVAL; + return ret; +} + +struct syscall_ret sys_msync(uintptr_t addr, size_t len, int flags) { + struct syscall_ret ret = {}; + + if (flags & ~(MS_ASYNC | MS_INVALIDATE | MS_SYNC)) { + ret.error = EINVAL; + return ret; + } + if ((flags & MS_ASYNC) && (flags & MS_SYNC)) { + ret.error = EINVAL; + return ret; + } + if (addr % arch_mem_page_size() != 0 || !is_user_range(addr, len)) { + ret.error = EINVAL; + return ret; + } + if (flags & MS_ASYNC) { + if (!vma_range_mapped(space(), addr, len)) + ret.error = ENOMEM; + return ret; + } + ret.error = vma_sync(space(), addr, len); + return ret; +} diff --git a/syscalls/meson.build b/syscalls/meson.build index c7af383..9bf438f 100644 --- a/syscalls/meson.build +++ b/syscalls/meson.build @@ -1,7 +1,7 @@ kernel_sources += files( - 'archctl.c', 'dispatch.c', - 'exit.c', - 'syslog.c', - 'uname.c', + 'memory.c', + 'process.c', + 'system.c', + 'vfs.c', ) diff --git a/syscalls/process.c b/syscalls/process.c new file mode 100644 index 0000000..3e30bbb --- /dev/null +++ b/syscalls/process.c @@ -0,0 +1,26 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include "internal.h" + +struct syscall_ret sys_exit(int err) { + struct syscall_ret ret = {}; + + ret.error = ENOSYS; + + panic("sys::exit() not implemented!\n"); + + return ret; +} + +struct syscall_ret sys_umask(mode_t mask) { + struct syscall_ret ret = {}; + + struct process* proc = self(); + mutex_lock(&proc->lock); + ret.value = proc->umask; + proc->umask = mask & 0777; + mutex_unlock(&proc->lock); + return ret; +} diff --git a/syscalls/syslog.c b/syscalls/syslog.c deleted file mode 100644 index d10e015..0000000 --- a/syscalls/syslog.c +++ /dev/null @@ -1,67 +0,0 @@ -#include -#include -#include -#include -#include -#include - -enum syslog_kind : uintptr_t { - SYSLOG_EMERG = 0, - SYSLOG_ALERT = 1, - SYSLOG_CRIT = 2, - SYSLOG_ERR = 3, - SYSLOG_WARNING = 4, - SYSLOG_NOTICE = 5, - SYSLOG_INFO = 6, - SYSLOG_DEBUG = 7, -}; - -struct syscall_ret sys_syslog(enum syslog_kind kind, __user char* message, size_t len) { - struct syscall_ret ret = {}; - - const size_t buf_len = MIN(len + 1, 256); - char* buf = mem_alloc(buf_len, ALLOC_NOZERO); - if (!usercopy_read(buf, message, buf_len - 1)) { - mem_free(buf); - - ret.error = EFAULT; - return ret; - } - buf[buf_len - 1] = 0; - - const char* log_level; - switch (kind) { - case SYSLOG_EMERG: - log_level = "emerg"; - break; - case SYSLOG_ALERT: - log_level = "alert"; - break; - case SYSLOG_CRIT: - log_level = "crit"; - break; - case SYSLOG_ERR: - log_level = "err"; - break; - case SYSLOG_WARNING: - log_level = "warning"; - break; - case SYSLOG_NOTICE: - log_level = "notice"; - break; - case SYSLOG_INFO: - log_level = "info"; - break; - case SYSLOG_DEBUG: - log_level = "debug"; - break; - default: - log_level = "invalid"; - } - - log("syslog(%s): %s\n", log_level, buf); - - mem_free(buf); - - return ret; -} diff --git a/syscalls/system.c b/syscalls/system.c new file mode 100644 index 0000000..714e6a0 --- /dev/null +++ b/syscalls/system.c @@ -0,0 +1,141 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "internal.h" + +enum syslog_kind : uintptr_t { + SYSLOG_EMERG = 0, + SYSLOG_ALERT = 1, + SYSLOG_CRIT = 2, + SYSLOG_ERR = 3, + SYSLOG_WARNING = 4, + SYSLOG_NOTICE = 5, + SYSLOG_INFO = 6, + SYSLOG_DEBUG = 7, +}; + +static struct spinlock uname_lock = {}; + +static struct __utsname uname_buf = { + .sysname = "Zinnia", + .release = ZINNIA_VERSION, + .version = CONFIG_KERNEL_FLAVOR, + .nodename = "localhost", + .machine = ZINNIA_ARCH, + .domainname = "(none)", +}; + +struct syscall_ret sys_archctl(uintptr_t op, uintptr_t arg) { + return arch_archctl(op, arg); +} + +struct syscall_ret sys_getuname(__user struct __utsname* ptr) { + struct syscall_ret ret = {}; + + if (!ptr) { + ret.error = EINVAL; + return ret; + } + + spin_lock(&uname_lock); + struct __utsname write_buf = uname_buf; + spin_unlock(&uname_lock); + + if (!usercopy_write(ptr, &write_buf, sizeof(write_buf))) + ret.error = EFAULT; + + return ret; +} + +struct syscall_ret sys_setuname(const __user struct __utsname* ptr) { + struct syscall_ret ret = {}; + + if (!ptr) { + ret.error = EINVAL; + return ret; + } + + if (!ident_is_privileged(&self()->identity, false)) { + ret.error = EPERM; + return ret; + } + + struct __utsname read_buf; + if (!usercopy_read(&read_buf, ptr, sizeof(read_buf))) { + ret.error = EFAULT; + return ret; + } + + spin_lock(&uname_lock); + + strncpy(uname_buf.sysname, read_buf.sysname, sizeof(uname_buf.sysname) - 1); + strncpy(uname_buf.release, read_buf.release, sizeof(uname_buf.release) - 1); + strncpy(uname_buf.version, read_buf.version, sizeof(uname_buf.version) - 1); + strncpy(uname_buf.nodename, read_buf.nodename, sizeof(uname_buf.nodename) - 1); + strncpy(uname_buf.machine, read_buf.machine, sizeof(uname_buf.machine) - 1); + strncpy(uname_buf.domainname, read_buf.domainname, sizeof(uname_buf.domainname) - 1); + + spin_unlock(&uname_lock); + + return ret; +} + +struct syscall_ret sys_syslog(enum syslog_kind kind, __user char* message, size_t len) { + struct syscall_ret ret = {}; + + const size_t buf_len = MIN(len + 1, 256); + char* buf = mem_alloc(buf_len, ALLOC_NOZERO); + if (!usercopy_read(buf, message, buf_len - 1)) { + mem_free(buf); + + ret.error = EFAULT; + return ret; + } + buf[buf_len - 1] = 0; + + const char* log_level; + switch (kind) { + case SYSLOG_EMERG: + log_level = "emerg"; + break; + case SYSLOG_ALERT: + log_level = "alert"; + break; + case SYSLOG_CRIT: + log_level = "crit"; + break; + case SYSLOG_ERR: + log_level = "err"; + break; + case SYSLOG_WARNING: + log_level = "warning"; + break; + case SYSLOG_NOTICE: + log_level = "notice"; + break; + case SYSLOG_INFO: + log_level = "info"; + break; + case SYSLOG_DEBUG: + log_level = "debug"; + break; + default: + log_level = "invalid"; + } + + log("syslog(%s): %s\n", log_level, buf); + + mem_free(buf); + + return ret; +} diff --git a/syscalls/uname.c b/syscalls/uname.c deleted file mode 100644 index 300049a..0000000 --- a/syscalls/uname.c +++ /dev/null @@ -1,65 +0,0 @@ -#include -#include -#include -#include -#include -#include - -static struct spinlock uname_lock = {}; - -static struct __utsname uname_buf = { - .sysname = "Zinnia", - .release = ZINNIA_VERSION, - .version = CONFIG_KERNEL_FLAVOR, - .nodename = "localhost", - .machine = ZINNIA_ARCH, - .domainname = "(none)", -}; - -struct syscall_ret sys_setuname(const __user struct __utsname* ptr) { - struct syscall_ret ret = {}; - - if (!ptr) { - ret.error = EINVAL; - return ret; - } - - // TODO: User has to be root to do this. - - struct __utsname read_buf; - if (!usercopy_read(&read_buf, ptr, sizeof(read_buf))) { - ret.error = EFAULT; - return ret; - } - - spin_lock(&uname_lock); - - strncpy(uname_buf.sysname, read_buf.sysname, sizeof(uname_buf.sysname) - 1); - strncpy(uname_buf.release, read_buf.release, sizeof(uname_buf.release) - 1); - strncpy(uname_buf.version, read_buf.version, sizeof(uname_buf.version) - 1); - strncpy(uname_buf.nodename, read_buf.nodename, sizeof(uname_buf.nodename) - 1); - strncpy(uname_buf.machine, read_buf.machine, sizeof(uname_buf.machine) - 1); - strncpy(uname_buf.domainname, read_buf.domainname, sizeof(uname_buf.domainname) - 1); - - spin_unlock(&uname_lock); - - return ret; -} - -struct syscall_ret sys_getuname(__user struct __utsname* ptr) { - struct syscall_ret ret = {}; - - if (!ptr) { - ret.error = EINVAL; - return ret; - } - - spin_lock(&uname_lock); - struct __utsname write_buf = uname_buf; - spin_unlock(&uname_lock); - - if (!usercopy_write(ptr, &write_buf, sizeof(write_buf))) - ret.error = EFAULT; - - return ret; -} diff --git a/syscalls/vfs.c b/syscalls/vfs.c new file mode 100644 index 0000000..92a1936 --- /dev/null +++ b/syscalls/vfs.c @@ -0,0 +1,1430 @@ +// SPDX-License-Identifier: ISC + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#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; +}