vfs: Implement syscalls
This commit is contained in:
@@ -0,0 +1,159 @@
|
||||
// SPDX-License-Identifier: ISC
|
||||
|
||||
#include <zinnia/atomic.h>
|
||||
#include <zinnia/mman.h>
|
||||
#include <zinnia/vfs.h>
|
||||
#include <zinnia/vma.h>
|
||||
#include <zinnia/vmo.h>
|
||||
#include <arch/mem.h>
|
||||
#include <stdckdint.h>
|
||||
#include <uapi/fcntl.h>
|
||||
#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;
|
||||
}
|
||||
Reference in New Issue
Block a user