maint: update copyright
[gnulib.git] / lib / get-rusage-as.c
1 /* Getter for RLIMIT_AS.
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 "address space size" is defined as the total size of the virtual memory
24    areas of the current process.  This includes
25      - areas belonging to the executable and shared libraries,
26      - areas allocated by malloc() or mmap(),
27      - the stack and environment areas,
28      - gaps and guard pages (mappings with PROT_NONE),
29      - other system dependent areas, such as vsyscall or vdso on Linux.
30
31    There are two ways of retrieving the current address space size:
32      a) by trying setrlimit with various values and observing whether the
33         kernel allows additional mmap calls,
34      b) by using system dependent APIs that allow to iterate over the list
35         of virtual memory areas.
36    We don't use the mincore() based approach here, because it would be very
37    slow when applied to an entire address space, especially on 64-bit
38    platforms.
39    We define two functions
40      get_rusage_as_via_setrlimit(),
41      get_rusage_as_via_iterator().
42
43    Discussion per platform:
44
45    Linux:
46      a) setrlimit with RLIMIT_AS works.
47      b) The /proc/self/maps file contains a list of the virtual memory areas.
48      Both methods agree, except that on x86_64 systems, the value of
49      get_rusage_as_via_iterator() is 4 KB higher than
50      get_rusage_as_via_setrlimit().
51
52    Mac OS X:
53      a) setrlimit with RLIMIT_AS succeeds but does not really work: The OS
54         ignores RLIMIT_AS. mmap() of a page always succeeds, therefore
55         get_rusage_as_via_setrlimit() is always 0.
56      b) The Mach based API works.
57
58    FreeBSD:
59      a) setrlimit with RLIMIT_AS works.
60      b) The /proc/self/maps file contains a list of the virtual memory areas.
61
62    NetBSD:
63      a) setrlimit with RLIMIT_AS works.
64      b) The /proc/self/maps file contains a list of the virtual memory areas.
65      Both methods agree,
66
67    OpenBSD:
68      a) setrlimit exists, but RLIMIT_AS is not defined.
69      b) mquery() can be used to find out about the virtual memory areas.
70
71    AIX:
72      a) setrlimit with RLIMIT_AS succeeds but does not really work: The OS
73         apparently ignores RLIMIT_AS. mmap() of a page always succeeds,
74         therefore get_rusage_as_via_setrlimit() is always 0.
75      b) No VMA iteration API exists.
76
77    HP-UX:
78      a) setrlimit with RLIMIT_AS works.
79      b) No VMA iteration API exists.
80
81    IRIX:
82      a) setrlimit with RLIMIT_AS works.
83      b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
84      Both methods agree,
85
86    OSF/1:
87      a) setrlimit with RLIMIT_AS works.
88      b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
89      The value returned by get_rusage_as_via_setrlimit() is 64 KB higher than
90      get_rusage_as_via_iterator().  It's not clear why.
91
92    Solaris:
93      a) setrlimit with RLIMIT_AS works.
94      b) No VMA iteration API exists.
95
96    Cygwin:
97      a) setrlimit with RLIMIT_AS always fails when the limit is < 0x80000000.
98         get_rusage_as_via_setrlimit() therefore produces a wrong value.
99      b) The /proc/$pid/maps file lists only the memory areas belonging to
100         the executable and shared libraries, not the anonymous memory.
101         But the native Windows API works.
102
103    mingw:
104      a) There is no setrlimit function.
105      b) The native Windows API works.
106
107    BeOS, Haiku:
108      a) On BeOS, there is no setrlimit function.
109         On Haiku, setrlimit exists. RLIMIT_AS is defined but unsupported.
110      b) There is a specific BeOS API: get_next_area_info().
111  */
112
113
114 #include <errno.h> /* errno */
115 #include <stdlib.h> /* size_t, abort */
116 #include <fcntl.h> /* open, O_RDONLY */
117 #include <unistd.h> /* getpagesize, read, close */
118
119
120 /* System support for get_rusage_as_via_setrlimit().  */
121
122 #if HAVE_SETRLIMIT
123 # include <sys/time.h>
124 # include <sys/resource.h> /* getrlimit, setrlimit */
125 #endif
126
127 /* Test whether mmap() and mprotect() are available.
128    We don't use HAVE_MMAP, because AC_FUNC_MMAP would not define it on HP-UX.
129    HAVE_MPROTECT is not enough, because mingw does not have mmap() but has an
130    mprotect() function in libgcc.a.  */
131 #if HAVE_SYS_MMAN_H && HAVE_MPROTECT
132 # include <fcntl.h>
133 # include <sys/types.h>
134 # include <sys/mman.h> /* mmap, munmap */
135 /* Define MAP_FILE when it isn't otherwise.  */
136 # ifndef MAP_FILE
137 #  define MAP_FILE 0
138 # endif
139 #endif
140
141
142 /* System support for get_rusage_as_via_iterator().  */
143
144 #include "vma-iter.h"
145
146
147 #if HAVE_SETRLIMIT && defined RLIMIT_AS && HAVE_SYS_MMAN_H && HAVE_MPROTECT
148
149 static uintptr_t
150 get_rusage_as_via_setrlimit (void)
151 {
152   uintptr_t result;
153
154   struct rlimit orig_limit;
155
156 # if HAVE_MAP_ANONYMOUS
157   const int flags = MAP_ANONYMOUS | MAP_PRIVATE;
158   const int fd = -1;
159 # else /* !HAVE_MAP_ANONYMOUS */
160   const int flags = MAP_FILE | MAP_PRIVATE;
161   int fd = open ("/dev/zero", O_RDONLY, 0666);
162   if (fd < 0)
163     return 0;
164 # endif
165
166   /* Record the original limit.  */
167   if (getrlimit (RLIMIT_AS, &orig_limit) < 0)
168     {
169       result = 0;
170       goto done2;
171     }
172
173   if (orig_limit.rlim_max != RLIM_INFINITY
174       && (orig_limit.rlim_cur == RLIM_INFINITY
175           || orig_limit.rlim_cur > orig_limit.rlim_max))
176     /* We may not be able to restore the current rlim_cur value.
177        So bail out.  */
178     {
179       result = 0;
180       goto done2;
181     }
182
183   {
184     /* The granularity is a single page.  */
185     const size_t pagesize = getpagesize ();
186
187     uintptr_t low_bound = 0;
188     uintptr_t high_bound;
189
190     for (;;)
191       {
192         /* Here we know that the address space size is >= low_bound.  */
193         struct rlimit try_limit;
194         uintptr_t try_next = 2 * low_bound + pagesize;
195
196         if (try_next < low_bound)
197           /* Overflow.  */
198           try_next = ((uintptr_t) (~ 0) / pagesize) * pagesize;
199
200         /* There's no point in trying a value > orig_limit.rlim_max, as
201            setrlimit would fail anyway.  */
202         if (orig_limit.rlim_max != RLIM_INFINITY
203             && orig_limit.rlim_max < try_next)
204           try_next = orig_limit.rlim_max;
205
206         /* Avoid endless loop.  */
207         if (try_next == low_bound)
208           {
209             /* try_next could not be increased.  */
210             result = low_bound;
211             goto done1;
212           }
213
214         try_limit.rlim_max = orig_limit.rlim_max;
215         try_limit.rlim_cur = try_next;
216         if (setrlimit (RLIMIT_AS, &try_limit) == 0)
217           {
218             /* Allocate a page of memory, to compare the current address space
219                size with try_limit.rlim_cur.  */
220             void *new_page =
221               mmap (NULL, pagesize, PROT_READ | PROT_WRITE, flags, fd, 0);
222
223             if (new_page != (void *)(-1))
224               {
225                 /* The page could be added successfully.  Free it.  */
226                 if (munmap (new_page, pagesize) < 0)
227                   abort ();
228                 /* We know that the address space size is
229                    < try_limit.rlim_cur.  */
230                 high_bound = try_next;
231                 break;
232               }
233             else
234               {
235                 /* We know that the address space size is
236                    >= try_limit.rlim_cur.  */
237                 low_bound = try_next;
238               }
239           }
240         else
241           {
242             /* Here we expect only EINVAL, not EPERM.  */
243             if (errno != EINVAL)
244               abort ();
245             /* We know that the address space size is
246                >= try_limit.rlim_cur.  */
247             low_bound = try_next;
248           }
249       }
250
251     /* Here we know that the address space 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_AS, &try_limit) == 0)
263           {
264             /* Allocate a page of memory, to compare the current address space
265                size with try_limit.rlim_cur.  */
266             void *new_page =
267               mmap (NULL, pagesize, PROT_READ | PROT_WRITE, flags, fd, 0);
268
269             if (new_page != (void *)(-1))
270               {
271                 /* The page could be added successfully.  Free it.  */
272                 if (munmap (new_page, pagesize) < 0)
273                   abort ();
274                 /* We know that the address space size is
275                    < try_limit.rlim_cur.  */
276                 high_bound = try_next;
277               }
278             else
279               {
280                 /* We know that the address space size is
281                    >= try_limit.rlim_cur.  */
282                 low_bound = try_next;
283               }
284           }
285         else
286           {
287             /* Here we expect only EINVAL, not EPERM.  */
288             if (errno != EINVAL)
289               abort ();
290             /* We know that the address space size is
291                >= try_limit.rlim_cur.  */
292             low_bound = try_next;
293           }
294       }
295
296     result = low_bound;
297   }
298
299  done1:
300   /* Restore the original rlim_cur value.  */
301   if (setrlimit (RLIMIT_AS, &orig_limit) < 0)
302     abort ();
303
304  done2:
305 # if !HAVE_MAP_ANONYMOUS
306   close (fd);
307 # endif
308   return result;
309 }
310
311 #else
312
313 static uintptr_t
314 get_rusage_as_via_setrlimit (void)
315 {
316   return 0;
317 }
318
319 #endif
320
321
322 #if VMA_ITERATE_SUPPORTED
323
324 static int
325 vma_iterate_callback (void *data, uintptr_t start, uintptr_t end,
326                       unsigned int flags)
327 {
328   uintptr_t *totalp = (uintptr_t *) data;
329
330   *totalp += end - start;
331   return 0;
332 }
333
334 static uintptr_t
335 get_rusage_as_via_iterator (void)
336 {
337   uintptr_t total = 0;
338
339   vma_iterate (vma_iterate_callback, &total);
340
341   return total;
342 }
343
344 #else
345
346 static uintptr_t
347 get_rusage_as_via_iterator (void)
348 {
349   return 0;
350 }
351
352 #endif
353
354
355 uintptr_t
356 get_rusage_as (void)
357 {
358 #if (defined __APPLE__ && defined __MACH__) || defined _AIX || defined __CYGWIN__ /* Mac OS X, AIX, Cygwin */
359   /* get_rusage_as_via_setrlimit() does not work.
360      Prefer get_rusage_as_via_iterator().  */
361   return get_rusage_as_via_iterator ();
362 #elif HAVE_SETRLIMIT && defined RLIMIT_AS && HAVE_SYS_MMAN_H && HAVE_MPROTECT
363   /* Prefer get_rusage_as_via_setrlimit() if it succeeds,
364      because the caller may want to use the result with setrlimit().  */
365   uintptr_t result;
366
367   result = get_rusage_as_via_setrlimit ();
368   if (result == 0)
369     result = get_rusage_as_via_iterator ();
370   return result;
371 #else
372   return get_rusage_as_via_iterator ();
373 #endif
374 }
375
376
377 #ifdef TEST
378
379 #include <stdio.h>
380
381 int
382 main ()
383 {
384   printf ("Initially:           0x%08lX 0x%08lX 0x%08lX\n",
385           get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
386           get_rusage_as ());
387   malloc (0x88);
388   printf ("After small malloc:  0x%08lX 0x%08lX 0x%08lX\n",
389           get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
390           get_rusage_as ());
391   malloc (0x8812);
392   printf ("After medium malloc: 0x%08lX 0x%08lX 0x%08lX\n",
393           get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
394           get_rusage_as ());
395   malloc (0x281237);
396   printf ("After large malloc:  0x%08lX 0x%08lX 0x%08lX\n",
397           get_rusage_as_via_setrlimit (), get_rusage_as_via_iterator (),
398           get_rusage_as ());
399   return 0;
400 }
401
402 #endif /* TEST */