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