20c49ce19db3ed68d30484802f3c849224530f48
[gnulib.git] / lib / get-rusage-data.c
1 /* Getter for RLIMIT_DATA.
2    Copyright (C) 2011 Free Software Foundation, Inc.
3    Written by Bruno Haible <bruno@clisp.org>, 2011.
4
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.
9
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.
14
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/>.  */
17
18 #include <config.h>
19
20 /* Specification.  */
21 #include "resource-ext.h"
22
23 /* The "data segment size" is defined as the virtual memory area of the
24    current process that contains malloc()ed memory.
25
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().
34
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.
41
42    Discussion per platform:
43
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.
52
53    MacOS 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.
59
60    FreeBSD:
61      a) setrlimit with RLIMIT_DATA works.
62      b) The /proc/self/maps file contains a list of the virtual memory areas.
63
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.
69
70    OpenBSD:
71      a) setrlimit with RLIMIT_DATA works.
72      b) No VMA iteration API exists.
73      Note that malloc() appears to use mmap() for both large and small
74      allocations.
75
76    AIX:
77      a) setrlimit with RLIMIT_DATA works.
78      b) No VMA iteration API exists.
79
80    HP-UX:
81      a) setrlimit with RLIMIT_DATA works, except on HP-UX 11.00, where it
82         cannot restore the previous limits, and except on HP-UX 11.11, where
83         it sometimes has no effect.
84      b) No VMA iteration API exists.
85
86    IRIX:
87      a) setrlimit with RLIMIT_DATA works.
88      b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
89      get_rusage_data_via_setrlimit() works slightly better than
90      get_rusage_data_via_iterator() before the first malloc() call.
91
92    OSF/1:
93      a) setrlimit with RLIMIT_DATA works.
94      b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
95      Both methods agree.
96
97    Solaris:
98      a) setrlimit with RLIMIT_DATA works.
99      b) No VMA iteration API exists.
100
101    Cygwin:
102      a) setrlimit with RLIMIT_DATA always fails.
103         get_rusage_data_via_setrlimit() therefore produces a wrong value.
104      b) The /proc/$pid/maps file lists only the memory areas belonging to
105         the executable and shared libraries, not the anonymous memory.
106         But the native Win32 API works.
107      Note that malloc() apparently falls back on mmap() for large allocations.
108
109    mingw:
110      a) There is no setrlimit function.
111      b) There is no sbrk() function.
112      Note that malloc() falls back on VirtualAlloc() for large allocations.
113
114    BeOS, Haiku:
115      a) On BeOS, there is no setrlimit function.
116         On Haiku, setrlimit exists. RLIMIT_DATA is defined but unsupported:
117         getrlimit of RLIMIT_DATA always fails with errno = EINVAL.
118      b) There is a specific BeOS API: get_next_area_info().
119  */
120
121
122 #include <errno.h> /* errno */
123 #include <stdlib.h> /* size_t, abort, malloc, free, sbrk */
124 #include <fcntl.h> /* open, O_RDONLY */
125 #include <unistd.h> /* getpagesize, read, close */
126
127
128 /* System support for get_rusage_data_via_setrlimit().  */
129
130 #if HAVE_SETRLIMIT
131 # include <sys/time.h>
132 # include <sys/resource.h> /* getrlimit, setrlimit */
133 # include <sys/utsname.h>
134 # include <string.h> /* strlen, strcmp */
135 #endif
136
137
138 /* System support for get_rusage_data_via_iterator().  */
139
140 #include "vma-iter.h"
141
142
143 #if HAVE_SETRLIMIT && defined RLIMIT_DATA
144
145 # ifdef _AIX
146 #  define errno_expected() (errno == EINVAL || errno == EFAULT)
147 # else
148 #  define errno_expected() (errno == EINVAL)
149 # endif
150
151 static inline uintptr_t
152 get_rusage_data_via_setrlimit (void)
153 {
154   uintptr_t result;
155
156   struct rlimit orig_limit;
157
158 # ifdef __hpux
159   /* On HP-UX 11.00, setrlimit() RLIMIT_DATA of does not work: It cannot
160      restore the previous limits.
161      On HP-UX 11.11, setrlimit() RLIMIT_DATA of does not work: It sometimes
162      has no effect on the next sbrk() call.  */
163   {
164     struct utsname buf;
165
166     if (uname (&buf) == 0
167         && strlen (buf.release) >= 5
168         && (strcmp (buf.release + strlen (buf.release) - 5, "11.00") == 0
169             || strcmp (buf.release + strlen (buf.release) - 5, "11.11") == 0))
170       return 0;
171   }
172 # endif
173
174   /* Record the original limit.  */
175   if (getrlimit (RLIMIT_DATA, &orig_limit) < 0)
176     return 0;
177
178   if (orig_limit.rlim_max != RLIM_INFINITY
179       && (orig_limit.rlim_cur == RLIM_INFINITY
180           || orig_limit.rlim_cur > orig_limit.rlim_max))
181     /* We may not be able to restore the current rlim_cur value.
182        So bail out.  */
183     return 0;
184
185   {
186     /* The granularity is a single page.  */
187     const intptr_t pagesize = getpagesize ();
188
189     uintptr_t low_bound = 0;
190     uintptr_t high_bound;
191
192     for (;;)
193       {
194         /* Here we know that the data segment size is >= low_bound.  */
195         struct rlimit try_limit;
196         uintptr_t try_next = 2 * low_bound + pagesize;
197
198         if (try_next < low_bound)
199           /* Overflow.  */
200           try_next = ((uintptr_t) (~ 0) / pagesize) * pagesize;
201
202         /* There's no point in trying a value > orig_limit.rlim_max, as
203            setrlimit would fail anyway.  */
204         if (orig_limit.rlim_max != RLIM_INFINITY
205             && orig_limit.rlim_max < try_next)
206           try_next = orig_limit.rlim_max;
207
208         /* Avoid endless loop.  */
209         if (try_next == low_bound)
210           {
211             /* try_next could not be increased.  */
212             result = low_bound;
213             goto done;
214           }
215
216         try_limit.rlim_max = orig_limit.rlim_max;
217         try_limit.rlim_cur = try_next;
218         if (setrlimit (RLIMIT_DATA, &try_limit) == 0)
219           {
220             /* Allocate a page of memory, to compare the current data segment
221                size with try_limit.rlim_cur.  */
222             void *new_page = sbrk (pagesize);
223
224             if (new_page != (void *)(-1))
225               {
226                 /* The page could be added successfully.  Free it.  */
227                 sbrk (- pagesize);
228                 /* We know that the data segment size is
229                    < try_limit.rlim_cur.  */
230                 high_bound = try_next;
231                 break;
232               }
233             else
234               {
235                 /* We know that the data segment size is
236                    >= try_limit.rlim_cur.  */
237                 low_bound = try_next;
238               }
239           }
240         else
241           {
242             /* Here we expect only EINVAL or (on AIX) EFAULT, not EPERM.  */
243             if (! errno_expected ())
244               abort ();
245             /* We know that the data segment size is
246                >= try_limit.rlim_cur.  */
247             low_bound = try_next;
248           }
249       }
250
251     /* Here we know that the data segment size is
252        >= low_bound and < high_bound.  */
253     while (high_bound - low_bound > pagesize)
254       {
255         struct rlimit try_limit;
256         uintptr_t try_next =
257           low_bound + (((high_bound - low_bound) / 2) / pagesize) * pagesize;
258
259         /* Here low_bound <= try_next < high_bound.  */
260         try_limit.rlim_max = orig_limit.rlim_max;
261         try_limit.rlim_cur = try_next;
262         if (setrlimit (RLIMIT_DATA, &try_limit) == 0)
263           {
264             /* Allocate a page of memory, to compare the current data segment
265                size with try_limit.rlim_cur.  */
266             void *new_page = sbrk (pagesize);
267
268             if (new_page != (void *)(-1))
269               {
270                 /* The page could be added successfully.  Free it.  */
271                 sbrk (- pagesize);
272                 /* We know that the data segment size is
273                    < try_limit.rlim_cur.  */
274                 high_bound = try_next;
275               }
276             else
277               {
278                 /* We know that the data segment size is
279                    >= try_limit.rlim_cur.  */
280                 low_bound = try_next;
281               }
282           }
283         else
284           {
285             /* Here we expect only EINVAL or (on AIX) EFAULT, not EPERM.  */
286             if (! errno_expected ())
287               abort ();
288             /* We know that the data segment size is
289                >= try_limit.rlim_cur.  */
290             low_bound = try_next;
291           }
292       }
293
294     result = low_bound;
295   }
296
297  done:
298   /* Restore the original rlim_cur value.  */
299   if (setrlimit (RLIMIT_DATA, &orig_limit) < 0)
300     abort ();
301
302   return result;
303 }
304
305 #else
306
307 static inline uintptr_t
308 get_rusage_data_via_setrlimit (void)
309 {
310   return 0;
311 }
312
313 #endif
314
315
316 #if VMA_ITERATE_SUPPORTED
317
318 struct locals
319 {
320   uintptr_t brk_value;
321   uintptr_t data_segment_size;
322 };
323
324 static int
325 vma_iterate_callback (void *data, uintptr_t start, uintptr_t end,
326                       unsigned int flags)
327 {
328   struct locals *lp = (struct locals *) data;
329
330   if (start <= lp->brk_value && lp->brk_value - 1 <= end - 1)
331     {
332       lp->data_segment_size = end - start;
333       return 1;
334     }
335   return 0;
336 }
337
338 static inline uintptr_t
339 get_rusage_data_via_iterator (void)
340 {
341 # if ((defined _WIN32 || defined __WIN32__) && !defined __CYGWIN__) || defined __BEOS__ || defined __HAIKU__
342   /* On mingw, there is no sbrk() function.
343      On Haiku, sbrk(0) always returns 0.  */
344   static void *brk_value;
345
346   if (brk_value == NULL)
347     {
348       brk_value = malloc (1);
349       if (brk_value == NULL)
350         return 0;
351     }
352 # else
353   void *brk_value;
354
355   brk_value = sbrk (0);
356   if (brk_value == (void *)-1)
357     return 0;
358 # endif
359
360   {
361     struct locals l;
362
363     l.brk_value = (uintptr_t) brk_value;
364     l.data_segment_size = 0;
365     vma_iterate (vma_iterate_callback, &l);
366
367     return l.data_segment_size;
368   }
369 }
370
371 #else
372
373 static inline uintptr_t
374 get_rusage_data_via_iterator (void)
375 {
376   return 0;
377 }
378
379 #endif
380
381
382 uintptr_t
383 get_rusage_data (void)
384 {
385 #if (defined __APPLE__ && defined __MACH__) || defined __CYGWIN__ /* MacOS X, Cygwin */
386   /* get_rusage_data_via_setrlimit() does not work.
387      Prefer get_rusage_data_via_iterator().  */
388   return get_rusage_data_via_iterator ();
389 #elif HAVE_SETRLIMIT && defined RLIMIT_DATA
390 # if defined __linux__ || defined __FreeBSD__ || defined __NetBSD__ || defined __OpenBSD__ || defined _AIX || defined __sgi || defined __osf__ || defined __sun /* Linux, FreeBSD, NetBSD, OpenBSD, AIX, IRIX, OSF/1, Solaris */
391   /* get_rusage_data_via_setrlimit() works.  */
392   return get_rusage_data_via_setrlimit ();
393 # else
394   /* Prefer get_rusage_data_via_setrlimit() if it succeeds,
395      because the caller may want to use the result with setrlimit().  */
396   uintptr_t result;
397
398   result = get_rusage_data_via_setrlimit ();
399   if (result == 0)
400     result = get_rusage_data_via_iterator ();
401   return result;
402 # endif
403 #else
404   return get_rusage_data_via_iterator ();
405 #endif
406 }
407
408
409 #ifdef TEST
410
411 #include <stdio.h>
412
413 int
414 main ()
415 {
416   printf ("Initially:           0x%08lX 0x%08lX 0x%08lX\n",
417           get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (),
418           get_rusage_data ());
419   malloc (0x88);
420   printf ("After small malloc:  0x%08lX 0x%08lX 0x%08lX\n",
421           get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (),
422           get_rusage_data ());
423   malloc (0x8812);
424   printf ("After medium malloc: 0x%08lX 0x%08lX 0x%08lX\n",
425           get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (),
426           get_rusage_data ());
427   malloc (0x281237);
428   printf ("After large malloc:  0x%08lX 0x%08lX 0x%08lX\n",
429           get_rusage_data_via_setrlimit (), get_rusage_data_via_iterator (),
430           get_rusage_data ());
431   return 0;
432 }
433
434 #endif /* TEST */