1 | /* Copyright (C) 2020-2024 Free Software Foundation, Inc. |
2 | This file is part of the GNU C Library. |
3 | |
4 | The GNU C Library is free software; you can redistribute it and/or |
5 | modify it under the terms of the GNU Lesser General Public |
6 | License as published by the Free Software Foundation; either |
7 | version 2.1 of the License, or (at your option) any later version. |
8 | |
9 | The GNU C Library is distributed in the hope that it will be useful, |
10 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | Lesser General Public License for more details. |
13 | |
14 | You should have received a copy of the GNU Lesser General Public |
15 | License along with the GNU C Library; if not, see |
16 | <https://www.gnu.org/licenses/>. */ |
17 | |
18 | #include <sys/types.h> |
19 | #include <sys/mman.h> |
20 | #include <errno.h> |
21 | #include <stdarg.h> |
22 | #include <hurd.h> |
23 | |
24 | #include <stdio.h> |
25 | |
26 | /* Remap pages mapped by the range [ADDR,ADDR+OLD_LEN) to new length |
27 | NEW_LEN. If MREMAP_MAYMOVE is set in FLAGS the returned address |
28 | may differ from ADDR. If MREMAP_FIXED is set in FLAGS the function |
29 | takes another parameter which is a fixed address at which the block |
30 | resides after a successful call. */ |
31 | |
32 | void * |
33 | __mremap (void *addr, size_t old_len, size_t new_len, int flags, ...) |
34 | { |
35 | error_t err; |
36 | vm_address_t vm_addr = (vm_address_t) addr; |
37 | vm_offset_t new_vm_addr = 0; |
38 | |
39 | vm_address_t begin = vm_addr; |
40 | vm_address_t end; |
41 | vm_size_t len; |
42 | vm_prot_t prot; |
43 | vm_prot_t max_prot; |
44 | vm_inherit_t inherit; |
45 | boolean_t shared; |
46 | memory_object_name_t obj; |
47 | vm_offset_t offset; |
48 | |
49 | if ((flags & ~(MREMAP_MAYMOVE | MREMAP_FIXED)) || |
50 | ((flags & MREMAP_FIXED) && !(flags & MREMAP_MAYMOVE)) || |
51 | (old_len == 0 && !(flags & MREMAP_MAYMOVE))) |
52 | return (void *) (long int) __hurd_fail (EINVAL); |
53 | |
54 | if (flags & MREMAP_FIXED) |
55 | { |
56 | va_list arg; |
57 | va_start (arg, flags); |
58 | new_vm_addr = (vm_offset_t) va_arg (arg, void *); |
59 | va_end (arg); |
60 | } |
61 | |
62 | err = __vm_region (__mach_task_self (), |
63 | &begin, &len, &prot, &max_prot, &inherit, |
64 | &shared, &obj, &offset); |
65 | if (err) |
66 | return (void *) (uintptr_t) __hurd_fail (err); |
67 | |
68 | if (begin > vm_addr) |
69 | { |
70 | err = EFAULT; |
71 | goto out; |
72 | } |
73 | |
74 | if (begin < vm_addr || (old_len != 0 && old_len != len)) |
75 | { |
76 | err = EINVAL; |
77 | goto out; |
78 | } |
79 | |
80 | end = begin + len; |
81 | |
82 | if ((flags & MREMAP_FIXED) && |
83 | ((new_vm_addr + new_len > vm_addr && new_vm_addr < end))) |
84 | { |
85 | /* Overlapping is not supported, like in Linux. */ |
86 | err = EINVAL; |
87 | goto out; |
88 | } |
89 | |
90 | /* FIXME: locked memory. */ |
91 | |
92 | if (old_len != 0 && !(flags & MREMAP_FIXED)) |
93 | { |
94 | /* A mere change of the existing map. */ |
95 | |
96 | if (new_len == len) |
97 | { |
98 | new_vm_addr = vm_addr; |
99 | goto out; |
100 | } |
101 | |
102 | if (new_len < len) |
103 | { |
104 | /* Shrink. */ |
105 | __mach_port_deallocate (__mach_task_self (), obj); |
106 | err = __vm_deallocate (__mach_task_self (), |
107 | begin + new_len, len - new_len); |
108 | new_vm_addr = vm_addr; |
109 | goto out; |
110 | } |
111 | |
112 | /* Try to expand. */ |
113 | err = __vm_map (__mach_task_self (), |
114 | &end, new_len - len, 0, 0, |
115 | obj, offset + len, 0, prot, max_prot, inherit); |
116 | if (!err) |
117 | { |
118 | /* Ok, that worked. Now coalesce them. */ |
119 | new_vm_addr = vm_addr; |
120 | |
121 | /* XXX this is not atomic as it is in unix! */ |
122 | err = __vm_deallocate (__mach_task_self (), begin, new_len); |
123 | if (err) |
124 | { |
125 | __vm_deallocate (__mach_task_self (), end, new_len - len); |
126 | goto out; |
127 | } |
128 | |
129 | err = __vm_map (__mach_task_self (), |
130 | &begin, new_len, 0, 0, |
131 | obj, offset, 0, prot, max_prot, inherit); |
132 | if (err) |
133 | { |
134 | /* Oops, try to remap before reporting. */ |
135 | __vm_map (__mach_task_self (), |
136 | &begin, len, 0, 0, |
137 | obj, offset, 0, prot, max_prot, inherit); |
138 | } |
139 | |
140 | goto out; |
141 | } |
142 | } |
143 | |
144 | if (!(flags & MREMAP_MAYMOVE)) |
145 | { |
146 | /* Can not map here */ |
147 | err = ENOMEM; |
148 | goto out; |
149 | } |
150 | |
151 | err = __vm_map (__mach_task_self (), |
152 | &new_vm_addr, new_len, 0, |
153 | new_vm_addr == 0, obj, offset, |
154 | old_len == 0, prot, max_prot, inherit); |
155 | |
156 | if (err == KERN_NO_SPACE && (flags & MREMAP_FIXED)) |
157 | { |
158 | /* XXX this is not atomic as it is in unix! */ |
159 | /* The region is already allocated; deallocate it first. */ |
160 | err = __vm_deallocate (__mach_task_self (), new_vm_addr, new_len); |
161 | if (! err) |
162 | err = __vm_map (__mach_task_self (), |
163 | &new_vm_addr, new_len, 0, |
164 | 0, obj, offset, |
165 | old_len == 0, prot, max_prot, inherit); |
166 | } |
167 | |
168 | if (!err) |
169 | /* Alright, can remove old mapping. */ |
170 | __vm_deallocate (__mach_task_self (), begin, len); |
171 | |
172 | out: |
173 | __mach_port_deallocate (__mach_task_self (), obj); |
174 | if (err) |
175 | return (void *) (uintptr_t) __hurd_fail (err); |
176 | return (void *) new_vm_addr; |
177 | } |
178 | |
179 | libc_hidden_def (__mremap) |
180 | weak_alias (__mremap, mremap) |
181 | |