1 /* Copyright (C) 2020-2022 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 *
__mremap(void * addr,size_t old_len,size_t new_len,int flags,...)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