diff --git a/libc/Makefile b/libc/Makefile index a0473580..6c9e4c9b 100644 --- a/libc/Makefile +++ b/libc/Makefile @@ -304,6 +304,9 @@ stdlib/system.o \ sys/display/dispmsg_issue.o \ sys/ioctl/ioctl.o \ sys/kernelinfo/kernelinfo.o \ +sys/mman/mmap.o \ +sys/mman/mprotect.o \ +sys/mman/munmap.o \ sys/readdirents/readdirents.o \ sys/select/select.o \ sys/socket/accept4.o \ diff --git a/libc/include/sys/mman.h b/libc/include/sys/mman.h index bfce9e72..6ddffeb7 100644 --- a/libc/include/sys/mman.h +++ b/libc/include/sys/mman.h @@ -1,6 +1,6 @@ /******************************************************************************* - Copyright(C) Jonas 'Sortie' Termansen 2012. + Copyright(C) Jonas 'Sortie' Termansen 2013. This file is part of the Sortix C Library. @@ -22,14 +22,22 @@ *******************************************************************************/ -#ifndef _SYS_MMAN_H -#define _SYS_MMAN_H 1 +#ifndef INCLUDE_SYS_MMAN_H +#define INCLUDE_SYS_MMAN_H #include #include __BEGIN_DECLS +@include(mode_t.h) +@include(off_t.h) +@include(size_t.h) + +void* mmap(void*, size_t, int, int, int, off_t); +int mprotect(const void*, size_t, int); +int munmap(void*, size_t); + __END_DECLS #endif diff --git a/libc/sys/mman/mmap.cpp b/libc/sys/mman/mmap.cpp new file mode 100644 index 00000000..da1944b0 --- /dev/null +++ b/libc/sys/mman/mmap.cpp @@ -0,0 +1,56 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of the Sortix C Library. + + The Sortix C Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + The Sortix C Library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with the Sortix C Library. If not, see . + + sys/mman/mmap.cpp + Maps a window of a file into memory. + +*******************************************************************************/ + +#include +#include + +// TODO: We use a wrapper system call here because there are too many parameters +// to the system call for some platforms. We should extend the system call +// ABI so we can do system calls with huge parameter lists and huge return +// values portably - then we'll make a new mmap system call that uses this +// mechanism if needed. + +struct mmap_request /* duplicated in sortix/mmap.cpp */ +{ + void* addr; + size_t size; + int prot; + int flags; + int fd; + off_t offset; +}; + +DEFN_SYSCALL1(void*, sys_mmap_wrapper, SYSCALL_MMAP_WRAPPER, struct mmap_request*); + +static void* mmap_wrapper(struct mmap_request* request) +{ + return sys_mmap_wrapper(request); +} + +extern "C" +void* mmap(void* addr, size_t size, int prot, int flags, int fd, off_t offset) +{ + struct mmap_request request = { addr, size, prot, flags, fd, offset }; + return mmap_wrapper(&request); +} diff --git a/libc/sys/mman/mprotect.cpp b/libc/sys/mman/mprotect.cpp new file mode 100644 index 00000000..caac808d --- /dev/null +++ b/libc/sys/mman/mprotect.cpp @@ -0,0 +1,33 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of the Sortix C Library. + + The Sortix C Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + The Sortix C Library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with the Sortix C Library. If not, see . + + sys/mman/mprotect.cpp + Changes the protection of a memory region. + +*******************************************************************************/ + +#include +#include + +DEFN_SYSCALL3(int, sys_mprotect, SYSCALL_MPROTECT, const void*, size_t, int); + +extern "C" int mprotect(const void* addr, size_t size, int prot) +{ + return sys_mprotect(addr, size, prot); +} diff --git a/libc/sys/mman/munmap.cpp b/libc/sys/mman/munmap.cpp new file mode 100644 index 00000000..d52eb4b8 --- /dev/null +++ b/libc/sys/mman/munmap.cpp @@ -0,0 +1,33 @@ +/******************************************************************************* + + Copyright(C) Jonas 'Sortie' Termansen 2013. + + This file is part of the Sortix C Library. + + The Sortix C Library is free software: you can redistribute it and/or modify + it under the terms of the GNU Lesser General Public License as published by + the Free Software Foundation, either version 3 of the License, or (at your + option) any later version. + + The Sortix C Library is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + License for more details. + + You should have received a copy of the GNU Lesser General Public License + along with the Sortix C Library. If not, see . + + sys/mman/munmap.cpp + Unmaps a memory region. + +*******************************************************************************/ + +#include +#include + +DEFN_SYSCALL2(int, sys_munmap, SYSCALL_MUNMAP, void*, size_t); + +extern "C" int munmap(void* addr, size_t size) +{ + return sys_munmap(addr, size); +} diff --git a/sortix/include/sortix/syscallnum.h b/sortix/include/sortix/syscallnum.h index dcf08643..5d40a09d 100644 --- a/sortix/include/sortix/syscallnum.h +++ b/sortix/include/sortix/syscallnum.h @@ -137,6 +137,9 @@ #define SYSCALL_SETPGID 113 #define SYSCALL_TCGETPGRP 114 #define SYSCALL_TCSETPGRP 115 -#define SYSCALL_MAX_NUM 116 /* index of highest constant + 1 */ +#define SYSCALL_MMAP_WRAPPER 116 +#define SYSCALL_MPROTECT 117 +#define SYSCALL_MUNMAP 118 +#define SYSCALL_MAX_NUM 119 /* index of highest constant + 1 */ #endif diff --git a/sortix/memorymanagement.cpp b/sortix/memorymanagement.cpp index 48357038..fdd83533 100644 --- a/sortix/memorymanagement.cpp +++ b/sortix/memorymanagement.cpp @@ -31,7 +31,11 @@ #include #include +#include +#include +#include +#include #include #include #include @@ -94,6 +98,7 @@ void UnmapMemory(Process* process, uintptr_t addr, size_t size) struct segment right_segment; right_segment.addr = addr + size; right_segment.size = conflict->addr + conflict->size - (addr + size); + right_segment.prot = conflict->prot; conflict->size = addr - conflict->addr; // TODO: This shouldn't really fail as we free memory above, but // this code isn't really provably reliable. @@ -246,6 +251,189 @@ bool MapMemory(Process* process, uintptr_t addr, size_t size, int prot) return true; } +const int USER_SETTABLE_PROT = PROT_USER | PROT_HEAP; +const int UNDERSTOOD_MMAP_FLAGS = MAP_SHARED | + MAP_PRIVATE | + MAP_ANONYMOUS | + MAP_FIXED; + +static +void* sys_mmap(void* addr_ptr, size_t size, int prot, int flags, int fd, + off_t offset) +{ + // Verify that that the address is suitable aligned if fixed. + uintptr_t addr = (uintptr_t) addr_ptr; + if ( flags & MAP_FIXED && !Page::IsAligned(addr) ) + return errno = EINVAL, MAP_FAILED; + // We don't allow zero-size mappings. + if ( size == 0 ) + return errno = EINVAL, MAP_FAILED; + // Verify that the user didn't request permissions not allowed. + if ( prot & ~USER_SETTABLE_PROT ) + return errno = EINVAL, MAP_FAILED; + // Verify that we understand all the flags we were passed. + if ( flags & ~UNDERSTOOD_MMAP_FLAGS ) + return errno = EINVAL, MAP_FAILED; + // Verify that MAP_PRIVATE and MAP_SHARED are not both set. + if ( bool(flags & MAP_PRIVATE) == bool(flags & MAP_SHARED) ) + return errno = EINVAL, MAP_FAILED; + // TODO: MAP_SHARED is not currently supported. + if ( flags & MAP_SHARED ) + return errno = EINVAL, MAP_FAILED; + // Verify the fíle descriptor and the offset is suitable set if needed. + if ( !(flags & MAP_ANONYMOUS) && + (fd < 0 || offset < 0 || (offset & (Page::Size()-1))) ) + return errno = EINVAL, MAP_FAILED; + + uintptr_t aligned_addr = Page::AlignDown(addr); + uintptr_t aligned_size = Page::AlignUp(size); + + // Pick a good location near the end of user-space if no hint is given. + if ( !(flags & MAP_FIXED) && !aligned_addr ) + { + uintptr_t userspace_addr; + size_t userspace_size; + Memory::GetUserVirtualArea(&userspace_addr, &userspace_size); + addr = aligned_addr = + Page::AlignDown(userspace_addr + userspace_size - aligned_size); + } + + // Verify that the offset + size doesn't overflow. + if ( !(flags & MAP_ANONYMOUS) && + (uintmax_t) (OFF_MAX - offset) < (uintmax_t) aligned_size ) + return errno = EOVERFLOW, MAP_FAILED; + + Process* process = CurrentProcess(); + + // Verify whether the backing file is usable for memory mapping. + ioctx_t ctx; SetupUserIOCtx(&ctx); + Ref desc; + if ( !(flags & MAP_ANONYMOUS) ) + { + if ( !(desc = process->GetDescriptor(fd)) ) + return MAP_FAILED; + // Verify that the file is seekable. + if ( desc->lseek(&ctx, 0, SEEK_CUR) < 0 ) + return errno = ENODEV, MAP_FAILED; + // Verify that we have read access to the file. + if ( desc->read(&ctx, NULL, 0) != 0 ) + return errno = EACCES, MAP_FAILED; + // Verify that we have write access to the file if needed. + if ( (prot & PROT_WRITE) && !(flags & MAP_PRIVATE) && + desc->write(&ctx, NULL, 0) != 0 ) + return errno = EACCES, MAP_FAILED; + } + + ScopedLock lock(&process->segment_lock); + + // Determine where to put the new segment and its protection. + struct segment new_segment; + if ( flags & MAP_FIXED ) + new_segment.addr = aligned_addr, new_segment.size = aligned_size; + else if ( !PlaceSegment(&new_segment, process, (void*) addr, aligned_size, flags) ) + return errno = ENOMEM, MAP_FAILED; + new_segment.prot = prot | PROT_KREAD | PROT_KWRITE | PROT_FORK; + + // Allocate a memory segment with the desired properties. + if ( !MapMemory(process, new_segment.addr, new_segment.size, new_segment.prot) ) + return MAP_FAILED; + + // Read the file contents into the newly allocated memory. + if ( !(flags & MAP_ANONYMOUS) ) + { + for ( size_t so_far = 0; so_far < aligned_size; ) + { + uint8_t* ptr = (uint8_t*) (new_segment.addr + so_far); + size_t left = aligned_size - so_far; + off_t pos = offset + so_far; + ssize_t num_bytes = desc->pread(&ctx, ptr, left, pos); + if ( num_bytes < 0 ) + { + // TODO: How should this situation be handled? For now we'll + // just ignore the error condition. + errno = 0; + break; + } + if ( !num_bytes ) + { + // We got an unexpected early end-of-file condition, but that's + // alright as the MapMemory call zero'd the new memory and we + // are expected to zero the remainder. + break; + } + so_far += num_bytes; + } + } + + return (void*) new_segment.addr; +} + +static int sys_mprotect(const void* addr_ptr, size_t size, int prot) +{ + // Verify that that the address is suitable aligned. + uintptr_t addr = (uintptr_t) addr_ptr; + if ( !Page::IsAligned(addr) ) + return errno = EINVAL, -1; + // Verify that the user didn't request permissions not allowed. + if ( prot & ~USER_SETTABLE_PROT ) + return errno = EINVAL, -1; + + size = Page::AlignUp(size); + prot |= PROT_KREAD | PROT_KWRITE | PROT_FORK; + + Process* process = CurrentProcess(); + ScopedLock lock(&process->segment_lock); + + if ( !ProtectMemory(process, addr, size, prot) ) + return -1; + + return 0; +} + +static int sys_munmap(void* addr_ptr, size_t size) +{ + // Verify that that the address is suitable aligned. + uintptr_t addr = (uintptr_t) addr_ptr; + if ( !Page::IsAligned(addr) ) + return errno = EINVAL, -1; + // We don't allow zero-size unmappings. + if ( size == 0 ) + return errno = EINVAL, -1; + + size = Page::AlignUp(size); + + Process* process = CurrentProcess(); + ScopedLock lock(&process->segment_lock); + + UnmapMemory(process, addr, size); + + return 0; +} + +// TODO: We use a wrapper system call here because there are too many parameters +// to mmap for some platforms. We should extend the system call ABI so we +// can do system calls with huge parameter lists and huge return values +// portably - then we'll make sys_mmap use this mechanism if needed. + +struct mmap_request /* duplicated in libc/sys/mman/mmap.cpp */ +{ + void* addr; + size_t size; + int prot; + int flags; + int fd; + off_t offset; +}; + +static void* sys_mmap_wrapper(struct mmap_request* user_request) +{ + struct mmap_request request; + if ( !CopyFromUser(&request, user_request, sizeof(request)) ) + return MAP_FAILED; + return sys_mmap(request.addr, request.size, request.prot, request.flags, + request.fd, request.offset); +} + void InitCPU(multiboot_info_t* bootinfo); void Init(multiboot_info_t* bootinfo) @@ -253,6 +441,9 @@ void Init(multiboot_info_t* bootinfo) InitCPU(bootinfo); Syscall::Register(SYSCALL_MEMSTAT, (void*) sys_memstat); + Syscall::Register(SYSCALL_MMAP_WRAPPER, (void*) sys_mmap_wrapper); + Syscall::Register(SYSCALL_MPROTECT, (void*) sys_mprotect); + Syscall::Register(SYSCALL_MUNMAP, (void*) sys_munmap); } } // namespace Memory