Files
2026-10-25 19:47:43 +01:00

160 lines
4.3 KiB
C

// 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;
}