warnings: fix compilation with old autoconf
[gnulib/ericb.git] / lib / get-rusage-data.c
blob1fc8202131afdbb3cf91bbad020d0ef4349d8acd
1 /* Getter for RLIMIT_DATA.
2 Copyright (C) 2011-2017 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 <http://www.gnu.org/licenses/>. */
18 #include <config.h>
20 /* Specification. */
21 #include "resource-ext.h"
23 /* The "data segment size" is defined as the virtual memory area of the
24 current process that contains malloc()ed memory.
26 There are two ways of retrieving the current data segment size:
27 a) by trying setrlimit with various values and observing whether the
28 kernel allows additional sbrk() calls,
29 b) by using system dependent APIs that allow to iterate over the list
30 of virtual memory areas.
31 We define two functions
32 get_rusage_data_via_setrlimit(),
33 get_rusage_data_via_iterator().
35 The variant
36 a') by trying setrlimit with various values and observing whether
37 additional malloc() calls succeed
38 is not as good as a), because a malloc() call can be served by already
39 allocated memory or through mmap(), and because a malloc() of 1 page may
40 require 2 pages.
42 Discussion per platform:
44 Linux:
45 a) setrlimit with RLIMIT_DATA works.
46 b) The /proc/self/maps file contains a list of the virtual memory areas.
47 get_rusage_data_via_setrlimit() returns the sum of the length of the
48 executable's data segment plus the heap VMA (an anonymous memory area),
49 whereas get_rusage_data_via_iterator() returns only the latter.
50 Note that malloc() falls back on mmap() for large allocations and also
51 for small allocations if there is not enough room in the data segment.
53 Mac OS X:
54 a) setrlimit with RLIMIT_DATA succeeds but does not really work: The OS
55 ignores RLIMIT_DATA. Therefore get_rusage_data_via_setrlimit() is
56 always 0.
57 b) The Mach based API works.
58 Note that malloc() falls back on mmap() for large allocations.
60 FreeBSD:
61 a) setrlimit with RLIMIT_DATA works.
62 b) The /proc/self/maps file contains a list of the virtual memory areas.
64 NetBSD:
65 a) setrlimit with RLIMIT_DATA works.
66 b) The /proc/self/maps file contains a list of the virtual memory areas.
67 Both methods agree.
68 Note that malloc() uses mmap() for large allocations.
70 OpenBSD:
71 a) setrlimit with RLIMIT_DATA works.
72 b) mquery() can be used to find out about the virtual memory areas.
73 get_rusage_data_via_setrlimit() works much better than
74 get_rusage_data_via_iterator().
75 Note that malloc() appears to use mmap() for both large and small
76 allocations.
78 AIX:
79 a) setrlimit with RLIMIT_DATA works.
80 b) No VMA iteration API exists.
82 HP-UX:
83 a) setrlimit with RLIMIT_DATA works, except on HP-UX 11.00, where it
84 cannot restore the previous limits, and except on HP-UX 11.11, where
85 it sometimes has no effect.
86 b) pstat_getprocvm() can be used to find out about the virtual memory
87 areas.
88 Both methods agree, except that the value of get_rusage_data_via_iterator()
89 is sometimes 4 KB larger than get_rusage_data_via_setrlimit().
91 IRIX:
92 a) setrlimit with RLIMIT_DATA works.
93 b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
94 get_rusage_data_via_setrlimit() works slightly better than
95 get_rusage_data_via_iterator() before the first malloc() call.
97 OSF/1:
98 a) setrlimit with RLIMIT_DATA works.
99 b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
100 Both methods agree.
102 Solaris:
103 a) setrlimit with RLIMIT_DATA works.
104 b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP, and the
105 /proc/self/maps file contains a list of the virtual memory areas.
106 get_rusage_data_via_setrlimit() ignores the data segment of the executable,
107 whereas get_rusage_data_via_iterator() includes it.
109 Cygwin:
110 a) setrlimit with RLIMIT_DATA always fails.
111 get_rusage_data_via_setrlimit() therefore produces a wrong value.
112 b) The /proc/$pid/maps file lists only the memory areas belonging to
113 the executable and shared libraries, not the anonymous memory.
114 But the native Windows API works.
115 Note that malloc() apparently falls back on mmap() for large allocations.
117 mingw:
118 a) There is no setrlimit function.
119 b) There is no sbrk() function.
120 Note that malloc() falls back on VirtualAlloc() for large allocations.
122 BeOS, Haiku:
123 a) On BeOS, there is no setrlimit function.
124 On Haiku, setrlimit exists. RLIMIT_DATA is defined but unsupported:
125 getrlimit of RLIMIT_DATA always fails with errno = EINVAL.
126 b) There is a specific BeOS API: get_next_area_info().
130 #include <errno.h> /* errno */
131 #include <stdlib.h> /* size_t, abort, malloc, free, sbrk */
132 #include <fcntl.h> /* open, O_RDONLY */
133 #include <unistd.h> /* getpagesize, read, close */
136 /* System support for get_rusage_data_via_setrlimit(). */
138 #if HAVE_SETRLIMIT
139 # include <sys/time.h>
140 # include <sys/resource.h> /* getrlimit, setrlimit */
141 # include <sys/utsname.h>
142 # include <string.h> /* strlen, strcmp */
143 #endif
146 /* System support for get_rusage_data_via_iterator(). */
148 #include "vma-iter.h"
151 #if !(defined __APPLE__ && defined __MACH__) || defined TEST
152 /* Implement get_rusage_data_via_setrlimit(). */
154 # if HAVE_SETRLIMIT && defined RLIMIT_DATA
156 # ifdef _AIX
157 # define errno_expected() (errno == EINVAL || errno == EFAULT)
158 # else
159 # define errno_expected() (errno == EINVAL)
160 # endif
162 static uintptr_t
163 get_rusage_data_via_setrlimit (void)
165 uintptr_t result;
167 struct rlimit orig_limit;
169 # ifdef __hpux
170 /* On HP-UX 11.00, setrlimit() RLIMIT_DATA of does not work: It cannot
171 restore the previous limits.
172 On HP-UX 11.11, setrlimit() RLIMIT_DATA of does not work: It sometimes
173 has no effect on the next sbrk() call. */
175 struct utsname buf;
177 if (uname (&buf) == 0
178 && strlen (buf.release) >= 5
179 && (strcmp (buf.release + strlen (buf.release) - 5, "11.00") == 0
180 || strcmp (buf.release + strlen (buf.release) - 5, "11.11") == 0))
181 return 0;
183 # endif
185 /* Record the original limit. */
186 if (getrlimit (RLIMIT_DATA, &orig_limit) < 0)
187 return 0;
189 if (orig_limit.rlim_max != RLIM_INFINITY
190 && (orig_limit.rlim_cur == RLIM_INFINITY
191 || orig_limit.rlim_cur > orig_limit.rlim_max))
192 /* We may not be able to restore the current rlim_cur value.
193 So bail out. */
194 return 0;
197 /* The granularity is a single page. */
198 const intptr_t pagesize = getpagesize ();
200 uintptr_t low_bound = 0;
201 uintptr_t high_bound;
203 for (;;)
205 /* Here we know that the data segment size is >= low_bound. */
206 struct rlimit try_limit;
207 uintptr_t try_next = 2 * low_bound + pagesize;
209 if (try_next < low_bound)
210 /* Overflow. */
211 try_next = ((uintptr_t) (~ 0) / pagesize) * pagesize;
213 /* There's no point in trying a value > orig_limit.rlim_max, as
214 setrlimit would fail anyway. */
215 if (orig_limit.rlim_max != RLIM_INFINITY
216 && orig_limit.rlim_max < try_next)
217 try_next = orig_limit.rlim_max;
219 /* Avoid endless loop. */
220 if (try_next == low_bound)
222 /* try_next could not be increased. */
223 result = low_bound;
224 goto done;
227 try_limit.rlim_max = orig_limit.rlim_max;
228 try_limit.rlim_cur = try_next;
229 if (setrlimit (RLIMIT_DATA, &try_limit) == 0)
231 /* Allocate a page of memory, to compare the current data segment
232 size with try_limit.rlim_cur. */
233 void *new_page = sbrk (pagesize);
235 if (new_page != (void *)(-1))
237 /* The page could be added successfully. Free it. */
238 sbrk (- pagesize);
239 /* We know that the data segment size is
240 < try_limit.rlim_cur. */
241 high_bound = try_next;
242 break;
244 else
246 /* We know that the data segment size is
247 >= try_limit.rlim_cur. */
248 low_bound = try_next;
251 else
253 /* Here we expect only EINVAL or (on AIX) EFAULT, not EPERM. */
254 if (! errno_expected ())
255 abort ();
256 /* We know that the data segment size is
257 >= try_limit.rlim_cur. */
258 low_bound = try_next;
262 /* Here we know that the data segment size is
263 >= low_bound and < high_bound. */
264 while (high_bound - low_bound > pagesize)
266 struct rlimit try_limit;
267 uintptr_t try_next =
268 low_bound + (((high_bound - low_bound) / 2) / pagesize) * pagesize;
270 /* Here low_bound <= try_next < high_bound. */
271 try_limit.rlim_max = orig_limit.rlim_max;
272 try_limit.rlim_cur = try_next;
273 if (setrlimit (RLIMIT_DATA, &try_limit) == 0)
275 /* Allocate a page of memory, to compare the current data segment
276 size with try_limit.rlim_cur. */
277 void *new_page = sbrk (pagesize);
279 if (new_page != (void *)(-1))
281 /* The page could be added successfully. Free it. */
282 sbrk (- pagesize);
283 /* We know that the data segment size is
284 < try_limit.rlim_cur. */
285 high_bound = try_next;
287 else
289 /* We know that the data segment size is
290 >= try_limit.rlim_cur. */
291 low_bound = try_next;
294 else
296 /* Here we expect only EINVAL or (on AIX) EFAULT, not EPERM. */
297 if (! errno_expected ())
298 abort ();
299 /* We know that the data segment size is
300 >= try_limit.rlim_cur. */
301 low_bound = try_next;
305 result = low_bound;
308 done:
309 /* Restore the original rlim_cur value. */
310 if (setrlimit (RLIMIT_DATA, &orig_limit) < 0)
311 abort ();
313 return result;
316 # else
318 static uintptr_t
319 get_rusage_data_via_setrlimit (void)
321 return 0;
324 # endif
326 #endif
329 #if !(defined __APPLE__ && defined __MACH__) || defined TEST
330 /* Implement get_rusage_data_via_iterator(). */
332 # if VMA_ITERATE_SUPPORTED
334 struct locals
336 uintptr_t brk_value;
337 uintptr_t data_segment_size;
340 static int
341 vma_iterate_callback (void *data, uintptr_t start, uintptr_t end,
342 unsigned int flags)
344 struct locals *lp = (struct locals *) data;
346 if (start <= lp->brk_value && lp->brk_value - 1 <= end - 1)
348 lp->data_segment_size = end - start;
349 return 1;
351 return 0;
354 static uintptr_t
355 get_rusage_data_via_iterator (void)
357 # if ((defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__) || defined __BEOS__ || defined __HAIKU__
358 /* On native Windows, there is no sbrk() function.
359 On Haiku, sbrk(0) always returns 0. */
360 static void *brk_value;
362 if (brk_value == NULL)
364 brk_value = malloc (1);
365 if (brk_value == NULL)
366 return 0;
368 # else
369 void *brk_value;
371 brk_value = sbrk (0);
372 if (brk_value == (void *)-1)
373 return 0;
374 # endif
377 struct locals l;
379 l.brk_value = (uintptr_t) brk_value;
380 l.data_segment_size = 0;
381 vma_iterate (vma_iterate_callback, &l);
383 return l.data_segment_size;
387 # else
389 static uintptr_t
390 get_rusage_data_via_iterator (void)
392 return 0;
395 # endif
397 #endif
400 uintptr_t
401 get_rusage_data (void)
403 #if (defined __APPLE__ && defined __MACH__) /* Mac OS X */
404 /* get_rusage_data_via_setrlimit() does not work: it always returns 0.
405 get_rusage_data_via_iterator() does not work: it always returns 0x400000.
406 And sbrk() is deprecated. */
407 return 0;
408 #elif defined __CYGWIN__ /* Cygwin */
409 /* get_rusage_data_via_setrlimit() does not work.
410 Prefer get_rusage_data_via_iterator(). */
411 return get_rusage_data_via_iterator ();
412 #elif HAVE_SETRLIMIT && defined RLIMIT_DATA
413 # if defined __linux__ || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined _AIX || defined __hpux || defined __sgi || defined __osf__ || defined __sun /* Linux, FreeBSD, NetBSD, OpenBSD, AIX, HP-UX, IRIX, OSF/1, Solaris */
414 /* get_rusage_data_via_setrlimit() works. */
415 return get_rusage_data_via_setrlimit ();
416 # else
417 /* Prefer get_rusage_data_via_setrlimit() if it succeeds,
418 because the caller may want to use the result with setrlimit(). */
419 uintptr_t result;
421 result = get_rusage_data_via_setrlimit ();
422 if (result == 0)
423 result = get_rusage_data_via_iterator ();
424 return result;
425 # endif
426 #else
427 return get_rusage_data_via_iterator ();
428 #endif
432 #ifdef TEST
434 #include <stdio.h>
437 main ()
439 printf ("Initially: 0x%08lX 0x%08lX 0x%08lX\n",
440 get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (),
441 get_rusage_data ());
442 malloc (0x88);
443 printf ("After small malloc: 0x%08lX 0x%08lX 0x%08lX\n",
444 get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (),
445 get_rusage_data ());
446 malloc (0x8812);
447 printf ("After medium malloc: 0x%08lX 0x%08lX 0x%08lX\n",
448 get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (),
449 get_rusage_data ());
450 malloc (0x281237);
451 printf ("After large malloc: 0x%08lX 0x%08lX 0x%08lX\n",
452 get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (),
453 get_rusage_data ());
454 return 0;
457 #endif /* TEST */