1431 lines
36 KiB
C
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;
|
|
}
|