mirror of
https://sourceware.org/git/glibc.git
synced 2024-11-29 16:21:07 +00:00
181 lines
4.7 KiB
C
181 lines
4.7 KiB
C
/* Copyright (C) 2020-2023 Free Software Foundation, Inc.
|
|
This file is part of the GNU C Library.
|
|
|
|
The GNU 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 2.1 of the License, or (at your option) any later version.
|
|
|
|
The GNU 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 GNU C Library; if not, see
|
|
<https://www.gnu.org/licenses/>. */
|
|
|
|
#include <sys/types.h>
|
|
#include <sys/mman.h>
|
|
#include <errno.h>
|
|
#include <stdarg.h>
|
|
#include <hurd.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
/* Remap pages mapped by the range [ADDR,ADDR+OLD_LEN) to new length
|
|
NEW_LEN. If MREMAP_MAYMOVE is set in FLAGS the returned address
|
|
may differ from ADDR. If MREMAP_FIXED is set in FLAGS the function
|
|
takes another parameter which is a fixed address at which the block
|
|
resides after a successful call. */
|
|
|
|
void *
|
|
__mremap (void *addr, size_t old_len, size_t new_len, int flags, ...)
|
|
{
|
|
error_t err;
|
|
vm_address_t vm_addr = (vm_address_t) addr;
|
|
vm_offset_t new_vm_addr = 0;
|
|
|
|
vm_address_t begin = vm_addr;
|
|
vm_address_t end;
|
|
vm_size_t len;
|
|
vm_prot_t prot;
|
|
vm_prot_t max_prot;
|
|
vm_inherit_t inherit;
|
|
boolean_t shared;
|
|
memory_object_name_t obj;
|
|
vm_offset_t offset;
|
|
|
|
if ((flags & ~(MREMAP_MAYMOVE | MREMAP_FIXED)) ||
|
|
((flags & MREMAP_FIXED) && !(flags & MREMAP_MAYMOVE)) ||
|
|
(old_len == 0 && !(flags & MREMAP_MAYMOVE)))
|
|
return (void *) (long int) __hurd_fail (EINVAL);
|
|
|
|
if (flags & MREMAP_FIXED)
|
|
{
|
|
va_list arg;
|
|
va_start (arg, flags);
|
|
new_vm_addr = (vm_offset_t) va_arg (arg, void *);
|
|
va_end (arg);
|
|
}
|
|
|
|
err = __vm_region (__mach_task_self (),
|
|
&begin, &len, &prot, &max_prot, &inherit,
|
|
&shared, &obj, &offset);
|
|
if (err)
|
|
return (void *) (uintptr_t) __hurd_fail (err);
|
|
|
|
if (begin > vm_addr)
|
|
{
|
|
err = EFAULT;
|
|
goto out;
|
|
}
|
|
|
|
if (begin < vm_addr || (old_len != 0 && old_len != len))
|
|
{
|
|
err = EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
end = begin + len;
|
|
|
|
if ((flags & MREMAP_FIXED) &&
|
|
((new_vm_addr + new_len > vm_addr && new_vm_addr < end)))
|
|
{
|
|
/* Overlapping is not supported, like in Linux. */
|
|
err = EINVAL;
|
|
goto out;
|
|
}
|
|
|
|
/* FIXME: locked memory. */
|
|
|
|
if (old_len != 0 && !(flags & MREMAP_FIXED))
|
|
{
|
|
/* A mere change of the existing map. */
|
|
|
|
if (new_len == len)
|
|
{
|
|
new_vm_addr = vm_addr;
|
|
goto out;
|
|
}
|
|
|
|
if (new_len < len)
|
|
{
|
|
/* Shrink. */
|
|
__mach_port_deallocate (__mach_task_self (), obj);
|
|
err = __vm_deallocate (__mach_task_self (),
|
|
begin + new_len, len - new_len);
|
|
new_vm_addr = vm_addr;
|
|
goto out;
|
|
}
|
|
|
|
/* Try to expand. */
|
|
err = __vm_map (__mach_task_self (),
|
|
&end, new_len - len, 0, 0,
|
|
obj, offset + len, 0, prot, max_prot, inherit);
|
|
if (!err)
|
|
{
|
|
/* Ok, that worked. Now coalesce them. */
|
|
new_vm_addr = vm_addr;
|
|
|
|
/* XXX this is not atomic as it is in unix! */
|
|
err = __vm_deallocate (__mach_task_self (), begin, new_len);
|
|
if (err)
|
|
{
|
|
__vm_deallocate (__mach_task_self (), end, new_len - len);
|
|
goto out;
|
|
}
|
|
|
|
err = __vm_map (__mach_task_self (),
|
|
&begin, new_len, 0, 0,
|
|
obj, offset, 0, prot, max_prot, inherit);
|
|
if (err)
|
|
{
|
|
/* Oops, try to remap before reporting. */
|
|
__vm_map (__mach_task_self (),
|
|
&begin, len, 0, 0,
|
|
obj, offset, 0, prot, max_prot, inherit);
|
|
}
|
|
|
|
goto out;
|
|
}
|
|
}
|
|
|
|
if (!(flags & MREMAP_MAYMOVE))
|
|
{
|
|
/* Can not map here */
|
|
err = ENOMEM;
|
|
goto out;
|
|
}
|
|
|
|
err = __vm_map (__mach_task_self (),
|
|
&new_vm_addr, new_len, 0,
|
|
new_vm_addr == 0, obj, offset,
|
|
old_len == 0, prot, max_prot, inherit);
|
|
|
|
if (err == KERN_NO_SPACE && (flags & MREMAP_FIXED))
|
|
{
|
|
/* XXX this is not atomic as it is in unix! */
|
|
/* The region is already allocated; deallocate it first. */
|
|
err = __vm_deallocate (__mach_task_self (), new_vm_addr, new_len);
|
|
if (! err)
|
|
err = __vm_map (__mach_task_self (),
|
|
&new_vm_addr, new_len, 0,
|
|
0, obj, offset,
|
|
old_len == 0, prot, max_prot, inherit);
|
|
}
|
|
|
|
if (!err)
|
|
/* Alright, can remove old mapping. */
|
|
__vm_deallocate (__mach_task_self (), begin, len);
|
|
|
|
out:
|
|
__mach_port_deallocate (__mach_task_self (), obj);
|
|
if (err)
|
|
return (void *) (uintptr_t) __hurd_fail (err);
|
|
return (void *) new_vm_addr;
|
|
}
|
|
|
|
libc_hidden_def (__mremap)
|
|
weak_alias (__mremap, mremap)
|