exp2l: Work around a NetBSD 10.0/i386 bug.
[gnulib.git] / lib / get-rusage-as.c
blob81f208ddb5119f40b837b5321670c3525f81abdc
1 /* Getter for RLIMIT_AS.
2 Copyright (C) 2011-2024 Free Software Foundation, Inc.
3 Written by Bruno Haible <bruno@clisp.org>, 2011.
5 This program is free software: you can redistribute it and/or modify
6 it under the terms of the GNU General Public License as published by
7 the Free Software Foundation, either version 3 of the License, or
8 (at your option) any later version.
10 This program is distributed in the hope that it will be useful,
11 but WITHOUT ANY WARRANTY; without even the implied warranty of
12 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 GNU General Public License for more details.
15 You should have received a copy of the GNU General Public License
16 along with this program. If not, see <https://www.gnu.org/licenses/>. */
18 #include <config.h>
20 /* On Android, when targeting Android 4.4 or older with a GCC toolchain,
21 prevent a compilation error
22 "error: call to 'mmap' declared with attribute error: mmap is not
23 available with _FILE_OFFSET_BITS=64 when using GCC until android-21.
24 Either raise your minSdkVersion, disable _FILE_OFFSET_BITS=64, or
25 switch to Clang."
26 The files that we access in this compilation unit are less than 2 GB
27 large. */
28 #if defined __ANDROID__
29 # undef _FILE_OFFSET_BITS
30 #endif
32 /* Specification. */
33 #include "resource-ext.h"
35 /* The "address space size" is defined as the total size of the virtual memory
36 areas of the current process. This includes
37 - areas belonging to the executable and shared libraries,
38 - areas allocated by malloc() or mmap(),
39 - the stack and environment areas,
40 - gaps and guard pages (mappings with PROT_NONE),
41 - other system dependent areas, such as vsyscall or vdso on Linux.
43 There are two ways of retrieving the current address space size:
44 a) by trying setrlimit with various values and observing whether the
45 kernel allows additional mmap calls,
46 b) by using system dependent APIs that allow to iterate over the list
47 of virtual memory areas.
48 We don't use the mincore() based approach here, because it would be very
49 slow when applied to an entire address space, especially on 64-bit
50 platforms.
51 We define two functions
52 get_rusage_as_via_setrlimit(),
53 get_rusage_as_via_iterator().
55 Discussion per platform:
57 Linux:
58 a) setrlimit with RLIMIT_AS works.
59 b) The /proc/self/maps file contains a list of the virtual memory areas.
60 Both methods agree, except that on x86_64 systems, the value of
61 get_rusage_as_via_iterator() is 4 KB higher than
62 get_rusage_as_via_setrlimit().
64 Mac OS X:
65 a) setrlimit with RLIMIT_AS succeeds but does not really work: The OS
66 ignores RLIMIT_AS. mmap() of a page always succeeds, therefore
67 get_rusage_as_via_setrlimit() is always 0.
68 b) The Mach based API works.
70 FreeBSD:
71 a) setrlimit with RLIMIT_AS works.
72 b) The /proc/self/maps file contains a list of the virtual memory areas.
74 NetBSD:
75 a) setrlimit with RLIMIT_AS works.
76 b) The /proc/self/maps file contains a list of the virtual memory areas.
77 Both methods agree,
79 OpenBSD:
80 a) setrlimit exists, but RLIMIT_AS is not defined.
81 b) mquery() can be used to find out about the virtual memory areas.
83 AIX:
84 a) setrlimit with RLIMIT_AS succeeds but does not really work: The OS
85 apparently ignores RLIMIT_AS. mmap() of a page always succeeds,
86 therefore get_rusage_as_via_setrlimit() is always 0.
87 b) The /proc/$pid/map file contains a list of the virtual memory areas.
89 HP-UX:
90 a) setrlimit with RLIMIT_AS works.
91 b) pstat_getprocvm() can be used to find out about the virtual memory
92 areas.
93 Both methods agree, except that the value of get_rusage_as_via_iterator()
94 is slightly larger higher than get_rusage_as_via_setrlimit(), by 4 KB in
95 32-bit mode and by 40 KB in 64-bit mode.
97 IRIX:
98 a) setrlimit with RLIMIT_AS works.
99 b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
100 Both methods agree,
102 OSF/1:
103 a) setrlimit with RLIMIT_AS works.
104 b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
105 The value returned by get_rusage_as_via_setrlimit() is 64 KB higher than
106 get_rusage_as_via_iterator(). It's not clear why.
108 Solaris:
109 a) setrlimit with RLIMIT_AS works.
110 b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP, and the
111 /proc/self/maps file contains a list of the virtual memory areas.
112 Both methods agree,
114 Cygwin:
115 a) setrlimit with RLIMIT_AS always fails when the limit is < 0x80000000.
116 get_rusage_as_via_setrlimit() therefore produces a wrong value.
117 b) The /proc/$pid/maps file lists only the memory areas belonging to
118 the executable and shared libraries, not the anonymous memory.
119 But the native Windows API works.
121 mingw:
122 a) There is no setrlimit function.
123 b) The native Windows API works.
125 BeOS, Haiku:
126 a) On BeOS, there is no setrlimit function.
127 On Haiku, setrlimit exists. RLIMIT_AS is defined but setrlimit fails.
128 b) There is a specific BeOS API: get_next_area_info().
132 #include <errno.h> /* errno */
133 #include <stdlib.h> /* size_t, abort */
134 #include <fcntl.h> /* open, O_RDONLY */
135 #include <unistd.h> /* getpagesize, read, close */
138 /* System support for get_rusage_as_via_setrlimit(). */
140 #if HAVE_SETRLIMIT
141 # include <sys/time.h>
142 # include <sys/resource.h> /* getrlimit, setrlimit */
143 #endif
145 /* Test whether mmap() and mprotect() are available.
146 We don't use HAVE_MMAP, because AC_FUNC_MMAP would not define it on HP-UX.
147 HAVE_MPROTECT is not enough, because mingw does not have mmap() but has an
148 mprotect() function in libgcc.a. */
149 #if HAVE_SYS_MMAN_H && HAVE_MPROTECT
150 # include <fcntl.h>
151 # include <sys/types.h>
152 # include <sys/mman.h> /* mmap, munmap */
153 /* Define MAP_FILE when it isn't otherwise. */
154 # ifndef MAP_FILE
155 # define MAP_FILE 0
156 # endif
157 #endif
160 /* System support for get_rusage_as_via_iterator(). */
162 #include "vma-iter.h"
165 #if HAVE_SETRLIMIT && defined RLIMIT_AS && HAVE_SYS_MMAN_H && HAVE_MPROTECT && !defined __HAIKU__
167 static uintptr_t
168 get_rusage_as_via_setrlimit (void)
170 uintptr_t result;
172 struct rlimit orig_limit;
174 # if HAVE_MAP_ANONYMOUS
175 const int flags = MAP_ANONYMOUS | MAP_PRIVATE;
176 const int fd = -1;
177 # else /* !HAVE_MAP_ANONYMOUS */
178 const int flags = MAP_FILE | MAP_PRIVATE;
179 int fd = open ("/dev/zero", O_RDONLY | O_CLOEXEC, 0666);
180 if (fd < 0)
181 return 0;
182 # endif
184 /* Record the original limit. */
185 if (getrlimit (RLIMIT_AS, &orig_limit) < 0)
187 result = 0;
188 goto done2;
191 if (orig_limit.rlim_max != RLIM_INFINITY
192 && (orig_limit.rlim_cur == RLIM_INFINITY
193 || orig_limit.rlim_cur > orig_limit.rlim_max))
194 /* We may not be able to restore the current rlim_cur value.
195 So bail out. */
197 result = 0;
198 goto done2;
202 /* The granularity is a single page. */
203 const size_t pagesize = getpagesize ();
205 uintptr_t low_bound = 0;
206 uintptr_t high_bound;
208 for (;;)
210 /* Here we know that the address space size is >= low_bound. */
211 struct rlimit try_limit;
212 uintptr_t try_next = 2 * low_bound + pagesize;
214 if (try_next < low_bound)
215 /* Overflow. */
216 try_next = ((uintptr_t) (~ 0) / pagesize) * pagesize;
218 /* There's no point in trying a value > orig_limit.rlim_max, as
219 setrlimit would fail anyway. */
220 if (orig_limit.rlim_max != RLIM_INFINITY
221 && orig_limit.rlim_max < try_next)
222 try_next = orig_limit.rlim_max;
224 /* Avoid endless loop. */
225 if (try_next == low_bound)
227 /* try_next could not be increased. */
228 result = low_bound;
229 goto done1;
232 try_limit.rlim_max = orig_limit.rlim_max;
233 try_limit.rlim_cur = try_next;
234 if (setrlimit (RLIMIT_AS, &try_limit) == 0)
236 /* Allocate a page of memory, to compare the current address space
237 size with try_limit.rlim_cur. */
238 void *new_page =
239 mmap (NULL, pagesize, PROT_READ | PROT_WRITE, flags, fd, 0);
241 if (new_page != (void *)(-1))
243 /* The page could be added successfully. Free it. */
244 if (munmap (new_page, pagesize) < 0)
245 abort ();
246 /* We know that the address space size is
247 < try_limit.rlim_cur. */
248 high_bound = try_next;
249 break;
251 else
253 /* We know that the address space size is
254 >= try_limit.rlim_cur. */
255 low_bound = try_next;
258 else
260 /* Here we expect only EINVAL, not EPERM. */
261 if (errno != EINVAL)
262 abort ();
263 /* We know that the address space size is
264 >= try_limit.rlim_cur. */
265 low_bound = try_next;
269 /* Here we know that the address space size is
270 >= low_bound and < high_bound. */
271 while (high_bound - low_bound > pagesize)
273 struct rlimit try_limit;
274 uintptr_t try_next =
275 low_bound + (((high_bound - low_bound) / 2) / pagesize) * pagesize;
277 /* Here low_bound <= try_next < high_bound. */
278 try_limit.rlim_max = orig_limit.rlim_max;
279 try_limit.rlim_cur = try_next;
280 if (setrlimit (RLIMIT_AS, &try_limit) == 0)
282 /* Allocate a page of memory, to compare the current address space
283 size with try_limit.rlim_cur. */
284 void *new_page =
285 mmap (NULL, pagesize, PROT_READ | PROT_WRITE, flags, fd, 0);
287 if (new_page != (void *)(-1))
289 /* The page could be added successfully. Free it. */
290 if (munmap (new_page, pagesize) < 0)
291 abort ();
292 /* We know that the address space size is
293 < try_limit.rlim_cur. */
294 high_bound = try_next;
296 else
298 /* We know that the address space size is
299 >= try_limit.rlim_cur. */
300 low_bound = try_next;
303 else
305 /* Here we expect only EINVAL, not EPERM. */
306 if (errno != EINVAL)
307 abort ();
308 /* We know that the address space size is
309 >= try_limit.rlim_cur. */
310 low_bound = try_next;
314 result = low_bound;
317 done1:
318 /* Restore the original rlim_cur value. */
319 if (setrlimit (RLIMIT_AS, &orig_limit) < 0)
320 abort ();
322 done2:
323 # if !HAVE_MAP_ANONYMOUS
324 close (fd);
325 # endif
326 return result;
329 #else
331 static uintptr_t
332 get_rusage_as_via_setrlimit (void)
334 return 0;
337 #endif
340 #if VMA_ITERATE_SUPPORTED
342 static int
343 vma_iterate_callback (void *data, uintptr_t start, uintptr_t end,
344 unsigned int flags)
346 uintptr_t *totalp = (uintptr_t *) data;
348 *totalp += end - start;
349 return 0;
352 static uintptr_t
353 get_rusage_as_via_iterator (void)
355 uintptr_t total = 0;
357 vma_iterate (vma_iterate_callback, &total);
359 return total;
362 #else
364 static uintptr_t
365 get_rusage_as_via_iterator (void)
367 return 0;
370 #endif
373 uintptr_t
374 get_rusage_as (void)
376 #if (defined __APPLE__ && defined __MACH__) || defined _AIX || defined __CYGWIN__ || defined __MVS__ || defined __SANITIZE_THREAD__ /* Mac OS X, AIX, Cygwin, z/OS, gcc -fsanitize=thread */
377 /* get_rusage_as_via_setrlimit() does not work.
378 Prefer get_rusage_as_via_iterator(). */
379 return get_rusage_as_via_iterator ();
380 #elif HAVE_SETRLIMIT && defined RLIMIT_AS && HAVE_SYS_MMAN_H && HAVE_MPROTECT && !defined __HAIKU__
381 /* Prefer get_rusage_as_via_setrlimit() if it succeeds,
382 because the caller may want to use the result with setrlimit(). */
383 uintptr_t result;
385 result = get_rusage_as_via_setrlimit ();
386 if (result == 0)
387 result = get_rusage_as_via_iterator ();
388 return result;
389 #else
390 return get_rusage_as_via_iterator ();
391 #endif
395 #ifdef TEST
397 #include <stdio.h>
400 main ()
402 printf ("Initially: 0x%08lX 0x%08lX 0x%08lX\n",
403 get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
404 get_rusage_as ());
405 malloc (0x88);
406 printf ("After small malloc: 0x%08lX 0x%08lX 0x%08lX\n",
407 get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
408 get_rusage_as ());
409 malloc (0x8812);
410 printf ("After medium malloc: 0x%08lX 0x%08lX 0x%08lX\n",
411 get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
412 get_rusage_as ());
413 malloc (0x281237);
414 printf ("After large malloc: 0x%08lX 0x%08lX 0x%08lX\n",
415 get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
416 get_rusage_as ());
417 return 0;
420 #endif /* TEST */