diff --git a/headers/posix/sys/mman.h b/headers/posix/sys/mman.h new file mode 100644 index 0000000000..c9bfcad0fa --- /dev/null +++ b/headers/posix/sys/mman.h @@ -0,0 +1,42 @@ +/* + * Copyright 2008, Haiku Inc. All Rights Reserved. + * Distributed under the terms of the MIT License. + */ +#ifndef _SYS_MMAN_H +#define _SYS_MMAN_H + +#include + + +// memory protection for mmap() and others +#define PROT_READ 0x01 +#define PROT_WRITE 0x02 +#define PROT_EXEC 0x04 +#define PROT_NONE 0x00 + +// mmap() flags +#define MAP_SHARED 0x01 /* changes are seen by others */ +#define MAP_PRIVATE 0x02 /* changes are only seen by caller */ +#define MAP_FIXED 0x04 /* require mapping to specified addr */ +#define MAP_ANONYMOUS 0x08 /* no underlying object */ +#define MAP_ANON MAP_ANONYMOUS + +// mmap() error return code +#define MAP_FAILED ((void*)-1) + +#ifdef __cplusplus +extern "C" { +#endif + + +extern void* mmap(void* address, size_t length, int protection, int flags, + int fd, off_t offset); +extern int munmap(void* address, size_t length); + + +#ifdef __cplusplus +} +#endif + + +#endif // _SYS_MMAN_H diff --git a/headers/private/kernel/syscalls.h b/headers/private/kernel/syscalls.h index af1f0be8e2..66da2c0a88 100644 --- a/headers/private/kernel/syscalls.h +++ b/headers/private/kernel/syscalls.h @@ -296,8 +296,10 @@ extern area_id _kern_clone_area(const char *name, void **_address, uint32 addre extern status_t _kern_reserve_heap_address_range(addr_t* _address, uint32 addressSpec, addr_t size); -area_id sys_vm_map_file(const char *name, void **address, int addr_type, - addr_t size, int lock, int mapping, const char *path, off_t offset); +extern area_id _kern_map_file(const char *name, void **address, + int addressSpec, addr_t size, int protection, + int mapping, int fd, off_t offset); +extern status_t _kern_unmap_memory(void *address, addr_t size); /* kernel port functions */ extern port_id _kern_create_port(int32 queue_length, const char *name); diff --git a/headers/private/kernel/vm.h b/headers/private/kernel/vm.h index e85492a10b..6d6295ab20 100644 --- a/headers/private/kernel/vm.h +++ b/headers/private/kernel/vm.h @@ -45,14 +45,17 @@ struct vnode; | B_KERNEL_STACK_AREA) #define B_OVERCOMMITTING_AREA 0x1000 - // ToDo: this is not really a protection flag, but since the "protection" +#define B_SHARED_AREA 0x2000 + // TODO: These aren't really a protection flags, but since the "protection" // field is the only flag field, we currently use it for this. // A cleaner approach would be appreciated - maybe just an official generic // flags region in the protection field. + #define B_USER_AREA_FLAGS (B_USER_PROTECTION) #define B_KERNEL_AREA_FLAGS \ - (B_KERNEL_PROTECTION | B_USER_CLONEABLE_AREA | B_OVERCOMMITTING_AREA) + (B_KERNEL_PROTECTION | B_USER_CLONEABLE_AREA | B_OVERCOMMITTING_AREA \ + | B_SHARED_AREA) // flags for vm_get_physical_page() enum { @@ -106,12 +109,13 @@ status_t vm_unreserve_address_range(team_id team, void *address, addr_t size); status_t vm_reserve_address_range(team_id team, void **_address, uint32 addressSpec, addr_t size, uint32 flags); area_id vm_create_anonymous_area(team_id team, const char *name, void **address, - uint32 addressSpec, addr_t size, uint32 wiring, uint32 protection); + uint32 addressSpec, addr_t size, uint32 wiring, uint32 protection, + bool unmapAddressRange); area_id vm_map_physical_memory(team_id team, const char *name, void **address, uint32 addressSpec, addr_t size, uint32 protection, addr_t phys_addr); area_id vm_map_file(team_id aid, const char *name, void **address, uint32 addressSpec, addr_t size, uint32 protection, uint32 mapping, - const char *path, off_t offset); + int fd, off_t offset); struct vm_cache *vm_area_get_locked_cache(struct vm_area *area); void vm_area_put_locked_cache(struct vm_cache *cache); area_id vm_create_null_area(team_id team, const char *name, void **address, @@ -144,8 +148,9 @@ off_t vm_available_memory(void); area_id _user_create_area(const char *name, void **address, uint32 addressSpec, size_t size, uint32 lock, uint32 protection); status_t _user_delete_area(area_id area); -area_id _user_vm_map_file(const char *uname, void **uaddress, int addr_type, - addr_t size, int lock, int mapping, const char *upath, off_t offset); +area_id _user_map_file(const char *uname, void **uaddress, int addressSpec, + addr_t size, int protection, int mapping, int fd, off_t offset); +status_t _user_unmap_memory(void *address, addr_t size); area_id _user_area_for(void *address); area_id _user_find_area(const char *name); status_t _user_get_area_info(area_id area, area_info *info); diff --git a/src/system/kernel/elf.cpp b/src/system/kernel/elf.cpp index b94f8d4131..157602bdf1 100644 --- a/src/system/kernel/elf.cpp +++ b/src/system/kernel/elf.cpp @@ -1406,7 +1406,7 @@ elf_load_user_image(const char *path, struct team *team, int flags, addr_t *entr B_EXACT_ADDRESS, fileUpperBound, B_READ_AREA | B_WRITE_AREA, REGION_PRIVATE_MAP, - path, ROUNDOWN(programHeaders[i].p_offset, B_PAGE_SIZE)); + fd, ROUNDOWN(programHeaders[i].p_offset, B_PAGE_SIZE)); if (id < B_OK) { dprintf("error mapping file data: %s!\n", strerror(id)); status = B_NOT_AN_EXECUTABLE; @@ -1453,7 +1453,7 @@ elf_load_user_image(const char *path, struct team *team, int flags, addr_t *entr B_EXACT_ADDRESS, ROUNDUP(programHeaders[i].p_memsz + (programHeaders[i].p_vaddr % B_PAGE_SIZE), B_PAGE_SIZE), B_READ_AREA | B_EXECUTE_AREA, REGION_PRIVATE_MAP, - path, ROUNDOWN(programHeaders[i].p_offset, B_PAGE_SIZE)); + fd, ROUNDOWN(programHeaders[i].p_offset, B_PAGE_SIZE)); if (id < B_OK) { dprintf("error mapping file text: %s!\n", strerror(id)); status = B_NOT_AN_EXECUTABLE; diff --git a/src/system/kernel/vm/vm.cpp b/src/system/kernel/vm/vm.cpp index d1f941a656..50307a631e 100644 --- a/src/system/kernel/vm/vm.cpp +++ b/src/system/kernel/vm/vm.cpp @@ -19,6 +19,7 @@ #include +#include #include #include #include @@ -1185,6 +1186,43 @@ insert_area(vm_address_space *addressSpace, void **_address, } +/*! Deletes all areas in the given address range. + The address space must be write-locked. + NOTE: At the moment deleting only complete areas is supported. +*/ +static status_t +unmap_address_range(vm_address_space *addressSpace, addr_t address, addr_t size) +{ + // TODO: Support deleting partial areas! + + addr_t lastAddress = address + (size - 1); + + // check whether any areas are only partially covered + vm_area* area = vm_area_lookup(addressSpace, address); + if (area != NULL && area->base < address) + return B_UNSUPPORTED; + + area = vm_area_lookup(addressSpace, lastAddress); + if (area != NULL && lastAddress - area->base < area->size - 1) + return B_UNSUPPORTED; + + // all areas (if any) are fully covered; let's delete them + area = addressSpace->areas; + while (area != NULL) { + vm_area* nextArea = area->address_space_next; + + if (area->id != RESERVED_AREA_ID) { + if (area->base >= address && area->base < lastAddress) + delete_area(addressSpace, area); + } + + area = nextArea; + } + + return B_OK; +} + + /*! You need to hold the lock of the cache and the write lock of the address space when calling this function. Note, that in case of error your cache will be temporarily unlocked. @@ -1193,7 +1231,7 @@ static status_t map_backing_store(vm_address_space *addressSpace, vm_cache *cache, void **_virtualAddress, off_t offset, addr_t size, uint32 addressSpec, int wiring, int protection, int mapping, vm_area **_area, - const char *areaName) + const char *areaName, bool unmapAddressRange) { TRACE(("map_backing_store: aspace %p, cache %p, *vaddr %p, offset 0x%Lx, size %lu, addressSpec %ld, wiring %d, protection %d, _area %p, area_name '%s'\n", addressSpace, cache, *_virtualAddress, offset, size, addressSpec, @@ -1254,6 +1292,13 @@ map_backing_store(vm_address_space *addressSpace, vm_cache *cache, goto err2; } + if (addressSpec == B_EXACT_ADDRESS && unmapAddressRange) { + status = unmap_address_range(addressSpace, (addr_t)*_virtualAddress, + size); + if (status != B_OK) + goto err2; + } + status = insert_area(addressSpace, _virtualAddress, addressSpec, size, area); if (status < B_OK) goto err2; @@ -1379,7 +1424,8 @@ vm_reserve_address_range(team_id team, void **_address, uint32 addressSpec, area_id vm_create_anonymous_area(team_id team, const char *name, void **address, - uint32 addressSpec, addr_t size, uint32 wiring, uint32 protection) + uint32 addressSpec, addr_t size, uint32 wiring, uint32 protection, + bool unmapAddressRange) { vm_area *area; vm_cache *cache; @@ -1482,7 +1528,8 @@ vm_create_anonymous_area(team_id team, const char *name, void **address, mutex_lock(&cache->lock); status = map_backing_store(addressSpace, cache, address, 0, size, - addressSpec, wiring, protection, REGION_NO_PRIVATE_MAP, &area, name); + addressSpec, wiring, protection, REGION_NO_PRIVATE_MAP, &area, name, + unmapAddressRange); mutex_unlock(&cache->lock); @@ -1696,7 +1743,7 @@ vm_map_physical_memory(team_id team, const char *name, void **_address, status_t status = map_backing_store(locker.AddressSpace(), cache, _address, 0, size, addressSpec & ~B_MTR_MASK, B_FULL_LOCK, protection, - REGION_NO_PRIVATE_MAP, &area, name); + REGION_NO_PRIVATE_MAP, &area, name, false); mutex_unlock(&cache->lock); @@ -1777,7 +1824,8 @@ vm_create_null_area(team_id team, const char *name, void **address, mutex_lock(&cache->lock); status = map_backing_store(locker.AddressSpace(), cache, address, 0, size, - addressSpec, 0, B_KERNEL_READ_AREA, REGION_NO_PRIVATE_MAP, &area, name); + addressSpec, 0, B_KERNEL_READ_AREA, REGION_NO_PRIVATE_MAP, &area, name, + false); mutex_unlock(&cache->lock); @@ -1821,18 +1869,16 @@ err1: } -/*! Will map the file at the path specified by \a name to an area in memory. - The file will be mirrored beginning at the specified \a offset. The \a offset - and \a size arguments have to be page aligned. +/*! Will map the file specified by \a fd to an area in memory. + The file will be mirrored beginning at the specified \a offset. The + \a offset and \a size arguments have to be page aligned. */ static area_id _vm_map_file(team_id team, const char *name, void **_address, uint32 addressSpec, - size_t size, uint32 protection, uint32 mapping, const char *path, - off_t offset, bool kernel) + size_t size, uint32 protection, uint32 mapping, int fd, off_t offset, + bool kernel) { - // ToDo: maybe attach to an FD, not a path (or both, like VFS calls) - // ToDo: check file access permissions (would be already done if the above were true) - // ToDo: for binary files, we want to make sure that they get the + // TODO: for binary files, we want to make sure that they get the // copy of a file at a given time, ie. later changes should not // make it into the mapped copy -- this will need quite some changes // to be done in a nice way @@ -1842,38 +1888,56 @@ _vm_map_file(team_id team, const char *name, void **_address, uint32 addressSpec offset = ROUNDOWN(offset, B_PAGE_SIZE); size = PAGE_ALIGN(size); + if (mapping == REGION_NO_PRIVATE_MAP) + protection |= B_SHARED_AREA; + + if (fd < 0) { + return vm_create_anonymous_area(team, name, _address, addressSpec, size, + B_NO_LOCK, protection, addressSpec == B_EXACT_ADDRESS); + } + + // get the open flags of the FD + file_descriptor *descriptor = get_fd(get_current_io_context(kernel), fd); + if (descriptor == NULL) + return EBADF; + int32 openMode = descriptor->open_mode; + put_fd(descriptor); + + // The FD must open for reading at any rate. For shared mapping with write + // access, additionally the FD must be open for writing. + if ((openMode & O_ACCMODE) == O_WRONLY + || mapping == REGION_NO_PRIVATE_MAP + && (protection & (B_WRITE_AREA | B_KERNEL_WRITE_AREA)) != 0 + && (openMode & O_ACCMODE) == O_RDONLY) { + return EACCES; + } + // get the vnode for the object, this also grabs a ref to it - struct vnode *vnode; - status_t status = vfs_get_vnode_from_path(path, kernel, &vnode); + struct vnode *vnode = NULL; + status_t status = vfs_get_vnode_from_fd(fd, kernel, &vnode); if (status < B_OK) return status; + CObjectDeleter vnodePutter(vnode, vfs_put_vnode); AddressSpaceWriteLocker locker(team); - if (!locker.IsLocked()) { - vfs_put_vnode(vnode); + if (!locker.IsLocked()) return B_BAD_TEAM_ID; - } - // ToDo: this only works for file systems that use the file cache + // TODO: this only works for file systems that use the file cache vm_cache *cache; status = vfs_get_vnode_cache(vnode, &cache, false); - if (status < B_OK) { - vfs_put_vnode(vnode); + if (status < B_OK) return status; - } mutex_lock(&cache->lock); vm_area *area; status = map_backing_store(locker.AddressSpace(), cache, _address, - offset, size, addressSpec, 0, protection, mapping, &area, name); + offset, size, addressSpec, 0, protection, mapping, &area, name, + addressSpec == B_EXACT_ADDRESS); mutex_unlock(&cache->lock); - vfs_put_vnode(vnode); - // we don't need this vnode anymore - if the above call was - // successful, the store already has a ref to it - if (status < B_OK || mapping == REGION_PRIVATE_MAP) { // map_backing_store() cannot know we no longer need the ref vm_cache_release_ref(cache); @@ -1888,14 +1952,13 @@ _vm_map_file(team_id team, const char *name, void **_address, uint32 addressSpec area_id vm_map_file(team_id aid, const char *name, void **address, uint32 addressSpec, - addr_t size, uint32 protection, uint32 mapping, const char *path, - off_t offset) + addr_t size, uint32 protection, uint32 mapping, int fd, off_t offset) { if (!arch_vm_supports_protection(protection)) return B_NOT_SUPPORTED; return _vm_map_file(aid, name, address, addressSpec, size, protection, - mapping, path, offset, true); + mapping, fd, offset, true); } @@ -1973,7 +2036,7 @@ vm_clone_area(team_id team, const char *name, void **address, else { status = map_backing_store(targetAddressSpace, cache, address, sourceArea->cache_offset, sourceArea->size, addressSpec, - sourceArea->wiring, protection, mapping, &newArea, name); + sourceArea->wiring, protection, mapping, &newArea, name, false); } if (status == B_OK && mapping != REGION_PRIVATE_MAP) { // If the mapping is REGION_PRIVATE_MAP, map_backing_store() needed @@ -2227,22 +2290,33 @@ vm_copy_area(team_id team, const char *name, void **_address, *_address = (void *)source->base; } - // First, create a cache on top of the source area + bool sharedArea = (source->protection & B_SHARED_AREA) != 0; + + // First, create a cache on top of the source area, respectively use the + // existing one, if this is a shared area. vm_area *target; status = map_backing_store(targetAddressSpace, cache, _address, source->cache_offset, source->size, addressSpec, source->wiring, - protection, REGION_PRIVATE_MAP, &target, name); - + protection, sharedArea ? REGION_NO_PRIVATE_MAP : REGION_PRIVATE_MAP, + &target, name, false); if (status < B_OK) return status; + if (sharedArea) { + // The new area uses the old area's cache, but map_backing_store() + // hasn't acquired a ref. So we have to do that now. + vm_cache_acquire_ref(cache); + } + // If the source area is writable, we need to move it one layer up as well - if ((source->protection & (B_KERNEL_WRITE_AREA | B_WRITE_AREA)) != 0) { - // ToDo: do something more useful if this fails! - if (vm_copy_on_write_area(cache) < B_OK) - panic("vm_copy_on_write_area() failed!\n"); + if (!sharedArea) { + if ((source->protection & (B_KERNEL_WRITE_AREA | B_WRITE_AREA)) != 0) { + // TODO: do something more useful if this fails! + if (vm_copy_on_write_area(cache) < B_OK) + panic("vm_copy_on_write_area() failed!\n"); + } } // we return the ID of the newly created area @@ -5075,7 +5149,7 @@ create_area_etc(struct team *team, const char *name, void **address, uint32 addr fix_protection(&protection); return vm_create_anonymous_area(team->id, (char *)name, address, - addressSpec, size, lock, protection); + addressSpec, size, lock, protection, false); } @@ -5086,7 +5160,7 @@ create_area(const char *name, void **_address, uint32 addressSpec, size_t size, fix_protection(&protection); return vm_create_anonymous_area(vm_kernel_address_space_id(), (char *)name, _address, - addressSpec, size, lock, protection); + addressSpec, size, lock, protection, false); } @@ -5319,7 +5393,7 @@ _user_create_area(const char *userName, void **userAddress, uint32 addressSpec, fix_protection(&protection); area_id area = vm_create_anonymous_area(vm_current_user_address_space_id(), - (char *)name, &address, addressSpec, size, lock, protection); + (char *)name, &address, addressSpec, size, lock, protection, false); if (area >= B_OK && user_memcpy(userAddress, &address, sizeof(address)) < B_OK) { delete_area(area); @@ -5344,26 +5418,33 @@ _user_delete_area(area_id area) // ToDo: create a BeOS style call for this! area_id -_user_vm_map_file(const char *userName, void **userAddress, int addressSpec, - addr_t size, int protection, int mapping, const char *userPath, off_t offset) +_user_map_file(const char *userName, void **userAddress, int addressSpec, + addr_t size, int protection, int mapping, int fd, off_t offset) { char name[B_OS_NAME_LENGTH]; - char path[B_PATH_NAME_LENGTH]; void *address; area_id area; if (!IS_USER_ADDRESS(userName) || !IS_USER_ADDRESS(userAddress) - || !IS_USER_ADDRESS(userPath) || user_strlcpy(name, userName, B_OS_NAME_LENGTH) < B_OK - || user_strlcpy(path, userPath, B_PATH_NAME_LENGTH) < B_OK || user_memcpy(&address, userAddress, sizeof(address)) < B_OK) return B_BAD_ADDRESS; + if (addressSpec == B_EXACT_ADDRESS) { + if ((addr_t)address + size < (addr_t)address) + return B_BAD_VALUE; + if (!IS_USER_ADDRESS(address) + || !IS_USER_ADDRESS((addr_t)address + size)) { + return B_BAD_ADDRESS; + } + } + // userland created areas can always be accessed by the kernel - protection |= B_KERNEL_READ_AREA | (protection & B_WRITE_AREA ? B_KERNEL_WRITE_AREA : 0); + protection |= B_KERNEL_READ_AREA + | (protection & B_WRITE_AREA ? B_KERNEL_WRITE_AREA : 0); area = _vm_map_file(vm_current_user_address_space_id(), name, &address, - addressSpec, size, protection, mapping, path, offset, false); + addressSpec, size, protection, mapping, fd, offset, false); if (area < B_OK) return area; @@ -5373,3 +5454,25 @@ _user_vm_map_file(const char *userName, void **userAddress, int addressSpec, return area; } + +status_t +_user_unmap_memory(void *_address, addr_t size) +{ + addr_t address = (addr_t)_address; + + // check params + if (size == 0 || (addr_t)address + size < (addr_t)address) + return B_BAD_VALUE; + + if (!IS_USER_ADDRESS(address) || !IS_USER_ADDRESS((addr_t)address + size)) + return B_BAD_ADDRESS; + + // write lock the address space + AddressSpaceWriteLocker locker; + status_t status = locker.SetTo(team_get_current_team_id()); + if (status != B_OK) + return status; + + // unmap + return unmap_address_range(locker.AddressSpace(), address, size); +} diff --git a/src/system/libroot/posix/sys/Jamfile b/src/system/libroot/posix/sys/Jamfile index ec0e1e178a..7ea9c29e40 100644 --- a/src/system/libroot/posix/sys/Jamfile +++ b/src/system/libroot/posix/sys/Jamfile @@ -1,5 +1,6 @@ SubDir HAIKU_TOP src system libroot posix sys ; +UsePrivateHeaders shared ; UseHeaders $(TARGET_PRIVATE_KERNEL_HEADERS) : true ; MergeObject posix_sys.o : @@ -11,6 +12,7 @@ MergeObject posix_sys.o : itimer.c mkdir.c mkfifo.c + mman.cpp rlimit.c select.c stat.c diff --git a/src/system/libroot/posix/sys/mman.cpp b/src/system/libroot/posix/sys/mman.cpp new file mode 100644 index 0000000000..cc9a98f56d --- /dev/null +++ b/src/system/libroot/posix/sys/mman.cpp @@ -0,0 +1,73 @@ +/* + * Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de. All rights reserved. + * Distributed under the terms of the MIT License. + */ + +#include + +#include + +#include + +#include +#include +#include + + +void* +mmap(void* address, size_t length, int protection, int flags, int fd, + off_t offset) +{ + // offset and length must be page-aligned + if (length == 0 || (offset | length) % B_PAGE_SIZE != 0) { + errno = B_BAD_VALUE; + return MAP_FAILED; + } + + // check anonymous mapping + if ((flags & MAP_ANONYMOUS) != 0) { + fd = -1; + } else if (fd < 0) { + errno = EBADF; + return MAP_FAILED; + } + + // either MAP_SHARED or MAP_PRIVATE must be specified + if (((flags & MAP_SHARED) != 0) == ((flags & MAP_PRIVATE) != 0)) { + errno = B_BAD_VALUE; + return MAP_FAILED; + } + + // translate mapping, address specification, and protection + int mapping = (flags & MAP_SHARED) != 0 + ? REGION_NO_PRIVATE_MAP : REGION_PRIVATE_MAP; + + uint32 addressSpec = B_ANY_ADDRESS; + if ((flags & MAP_FIXED) != 0) + addressSpec = B_EXACT_ADDRESS; + + uint32 areaProtection = 0; + if ((protection & PROT_READ) != 0) + areaProtection |= B_READ_AREA; + if ((protection & PROT_WRITE) != 0) + areaProtection |= B_WRITE_AREA; + if ((protection & PROT_EXEC) != 0) + areaProtection |= B_EXECUTE_AREA; + + // ask the kernel to map + area_id area = _kern_map_file("mmap area", &address, addressSpec, + length, areaProtection, mapping, fd, offset); + if (area < 0) { + errno = area; + return MAP_FAILED; + } + + return address; +} + + +int +munmap(void* address, size_t length) +{ + RETURN_AND_SET_ERRNO(_kern_unmap_memory(address, length)); +} diff --git a/src/system/runtime_loader/elf.cpp b/src/system/runtime_loader/elf.cpp index 56fc78ca2a..94a645b03a 100644 --- a/src/system/runtime_loader/elf.cpp +++ b/src/system/runtime_loader/elf.cpp @@ -794,9 +794,10 @@ map_image(int fd, char const *path, image_t *image, bool fixed) image->regions[i].delta = loadAddress - image->regions[i].vmstart; image->regions[i].vmstart = loadAddress; } else { - image->regions[i].id = sys_vm_map_file(regionName, (void **)&loadAddress, - addressSpecifier, image->regions[i].vmsize, B_READ_AREA | B_WRITE_AREA, - REGION_PRIVATE_MAP, path, PAGE_BASE(image->regions[i].fdstart)); + image->regions[i].id = _kern_map_file(regionName, + (void **)&loadAddress, addressSpecifier, + image->regions[i].vmsize, B_READ_AREA | B_WRITE_AREA, + REGION_PRIVATE_MAP, fd, PAGE_BASE(image->regions[i].fdstart)); if (image->regions[i].id < 0) { status = image->regions[i].id;