| 1 | /* Copyright (C) 1991-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 <errno.h> |
| 19 | #include <hurd.h> |
| 20 | #include <hurd/resource.h> |
| 21 | #include <lock-intern.h> /* For `struct mutex'. */ |
| 22 | #include <vm_param.h> |
| 23 | |
| 24 | #include "set-hooks.h" |
| 25 | |
| 26 | |
| 27 | /* Initial maximum size of the data segment (this is arbitrary). */ |
| 28 | #define DATA_SIZE (128 * 1024 * 1024) |
| 29 | |
| 30 | /* Up to the page including this address is allocated from the kernel. |
| 31 | This address is the data resource limit. */ |
| 32 | vm_address_t _hurd_data_end; |
| 33 | |
| 34 | /* Up to this address is actually available to the user. |
| 35 | Pages beyond the one containing this address allow no access. */ |
| 36 | vm_address_t _hurd_brk = 0; |
| 37 | |
| 38 | /* This name is used by the Linux crtbeginS.o for reasons you don't even |
| 39 | want to think about it. It's just easier to provide some definition for |
| 40 | it than even to explain the braindamage involved. */ |
| 41 | weak_alias (_hurd_brk, ___brk_addr) |
| 42 | |
| 43 | struct mutex _hurd_brk_lock; |
| 44 | |
| 45 | static vm_address_t brk_start; |
| 46 | |
| 47 | |
| 48 | /* Set the end of the process's data space to INADDR. |
| 49 | Return 0 if successful, -1 if not. */ |
| 50 | int |
| 51 | __brk (void *inaddr) |
| 52 | { |
| 53 | int ret; |
| 54 | HURD_CRITICAL_BEGIN; |
| 55 | __mutex_lock (&_hurd_brk_lock); |
| 56 | ret = _hurd_set_brk ((vm_address_t) inaddr); |
| 57 | __mutex_unlock (&_hurd_brk_lock); |
| 58 | HURD_CRITICAL_END; |
| 59 | return ret; |
| 60 | } |
| 61 | weak_alias (__brk, brk) |
| 62 | |
| 63 | |
| 64 | int |
| 65 | _hurd_set_brk (vm_address_t addr) |
| 66 | { |
| 67 | error_t err = 0; |
| 68 | vm_address_t pagend = round_page (addr); |
| 69 | vm_address_t pagebrk = round_page (_hurd_brk); |
| 70 | long int rlimit; |
| 71 | |
| 72 | if (pagend <= pagebrk) |
| 73 | { |
| 74 | if (pagend < pagebrk) |
| 75 | { |
| 76 | /* XXX wish this were atomic... */ |
| 77 | /* First deallocate the memory to release its backing space. */ |
| 78 | __vm_deallocate (__mach_task_self (), pagend, pagebrk - pagend); |
| 79 | /* Now reallocate it with no access allowed. */ |
| 80 | err = __vm_map (__mach_task_self (), |
| 81 | &pagend, pagebrk - pagend, |
| 82 | 0, 0, MACH_PORT_NULL, 0, 0, |
| 83 | 0, VM_PROT_READ|VM_PROT_WRITE|VM_PROT_EXECUTE, |
| 84 | VM_INHERIT_COPY); |
| 85 | /* XXX what if error? */ |
| 86 | } |
| 87 | _hurd_brk = addr; |
| 88 | return 0; |
| 89 | } |
| 90 | |
| 91 | __mutex_lock (&_hurd_rlimit_lock); |
| 92 | rlimit = _hurd_rlimits[RLIMIT_DATA].rlim_cur; |
| 93 | __mutex_unlock (&_hurd_rlimit_lock); |
| 94 | |
| 95 | if (addr - brk_start > rlimit) |
| 96 | /* Need to increase the resource limit. */ |
| 97 | return __hurd_fail (ENOMEM); |
| 98 | |
| 99 | if (pagend > _hurd_data_end) |
| 100 | { |
| 101 | vm_address_t alloc_start = _hurd_data_end; |
| 102 | |
| 103 | /* We didn't allocate enough space! Hopefully we can get some more! */ |
| 104 | |
| 105 | if (_hurd_data_end > pagebrk) |
| 106 | /* First finish allocation. */ |
| 107 | err = __vm_protect (__mach_task_self (), pagebrk, |
| 108 | alloc_start - pagebrk, 0, |
| 109 | VM_PROT_READ|VM_PROT_WRITE); |
| 110 | if (! err) |
| 111 | _hurd_brk = alloc_start; |
| 112 | |
| 113 | if (! err) |
| 114 | err = __vm_allocate (__mach_task_self (), &alloc_start, |
| 115 | pagend - alloc_start, 0); |
| 116 | |
| 117 | if (! err) |
| 118 | _hurd_data_end = pagend; |
| 119 | } |
| 120 | else |
| 121 | /* Make the memory accessible. */ |
| 122 | err = __vm_protect (__mach_task_self (), pagebrk, pagend - pagebrk, |
| 123 | 0, VM_PROT_READ|VM_PROT_WRITE); |
| 124 | |
| 125 | if (err) |
| 126 | return __hurd_fail (err); |
| 127 | |
| 128 | _hurd_brk = addr; |
| 129 | return 0; |
| 130 | } |
| 131 | |
| 132 | static void attribute_used_retain |
| 133 | init_brk (void) |
| 134 | { |
| 135 | vm_address_t pagend; |
| 136 | |
| 137 | __mutex_init (&_hurd_brk_lock); |
| 138 | |
| 139 | brk_start = (vm_address_t) BRK_START; |
| 140 | |
| 141 | /* If _hurd_brk is already set, don't change it. The assumption is that |
| 142 | it was set in a previous run before something like Emacs's unexec was |
| 143 | called and dumped all the data up to the break at that point. */ |
| 144 | if (_hurd_brk == 0) { |
| 145 | _hurd_brk = (vm_address_t) BRK_START; |
| 146 | } |
| 147 | |
| 148 | pagend = round_page (_hurd_brk); |
| 149 | |
| 150 | _hurd_data_end = round_page (brk_start + DATA_SIZE); |
| 151 | |
| 152 | if (pagend < _hurd_data_end) |
| 153 | { |
| 154 | /* We use vm_map to allocate and change permissions atomically. */ |
| 155 | if (__vm_map (__mach_task_self (), &pagend, _hurd_data_end - pagend, |
| 156 | 0, 0, MACH_PORT_NULL, 0, 0, |
| 157 | 0, VM_PROT_READ|VM_PROT_WRITE|VM_PROT_EXECUTE, |
| 158 | VM_INHERIT_COPY)) |
| 159 | /* Couldn't allocate the memory. The break will be very short. */ |
| 160 | _hurd_data_end = pagend; |
| 161 | } |
| 162 | } |
| 163 | SET_RELHOOK (_hurd_preinit_hook, init_brk); |
| 164 | |