get-rusage-as: Fix bug.
[gnulib.git] / lib / get-rusage-as.c
1 /* Getter for RLIMIT_AS.
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 "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    MacOS 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) No VMA iteration API exists.
65
66    OpenBSD:
67      a) setrlimit exists, but RLIMIT_AS is not defined.
68      b) No VMA iteration API exists.
69
70    AIX:
71      a) setrlimit with RLIMIT_AS succeeds but does not really work: The OS
72         apparently ignores RLIMIT_AS. mmap() of a page always succeeds,
73         therefore get_rusage_as_via_setrlimit() is always 0.
74      b) No VMA iteration API exists.
75
76    HP-UX:
77      a) setrlimit with RLIMIT_AS works.
78      b) No VMA iteration API exists.
79
80    IRIX:
81      a) setrlimit with RLIMIT_AS works.
82      b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
83      Both methods agree,
84
85    OSF/1:
86      a) setrlimit with RLIMIT_AS works.
87      b) The /proc/$pid file supports ioctls PIOCNMAP and PIOCMAP.
88      The value returned by get_rusage_as_via_setrlimit() is 64 KB higher than
89      get_rusage_as_via_iterator().  It's not clear why.
90
91    Solaris:
92      a) setrlimit with RLIMIT_AS works.
93      b) No VMA iteration API exists.
94
95    Cygwin:
96      a) setrlimit with RLIMIT_AS always fails when the limit is < 0x80000000.
97         get_rusage_as_via_setrlimit() therefore produces a wrong value.
98      b) The /proc/$pid/maps file lists only the memory areas belonging to
99         the executable and shared libraries, not the anonymous memory.
100         But the native Win32 API works.
101
102    mingw:
103      a) There is no setrlimit function.
104      b) The native Win32 API works.
105
106    BeOS, Haiku:
107      a) On BeOS, there is no setrlimit function.
108         On Haiku, setrlimit exists. RLIMIT_AS is defined but unsupported.
109      b) There is a specific BeOS API: get_next_area_info().
110  */
111
112
113 #include <errno.h> /* errno */
114 #include <stdlib.h> /* size_t, abort */
115 #include <fcntl.h> /* open, O_RDONLY */
116 #include <unistd.h> /* getpagesize, read, close */
117
118
119 /* System support for get_rusage_as_via_setrlimit().  */
120
121 #if HAVE_SETRLIMIT
122 # include <sys/time.h>
123 # include <sys/resource.h> /* getrlimit, setrlimit */
124 #endif
125
126 /* Test whether mmap() and mprotect() are available.
127    We don't use HAVE_MMAP, because AC_FUNC_MMAP would not define it on HP-UX.
128    HAVE_MPROTECT is not enough, because mingw does not have mmap() but has an
129    mprotect() function in libgcc.a.  */
130 #if HAVE_SYS_MMAN_H && HAVE_MPROTECT
131 # include <fcntl.h>
132 # include <sys/types.h>
133 # include <sys/mman.h> /* mmap, munmap */
134 /* Define MAP_FILE when it isn't otherwise.  */
135 # ifndef MAP_FILE
136 #  define MAP_FILE 0
137 # endif
138 #endif
139
140
141 /* System support for get_rusage_as_via_iterator().  */
142
143 #if defined __sgi || defined __osf__ /* IRIX, OSF/1 */
144 # include <string.h> /* memcpy */
145 # include <sys/types.h>
146 # include <sys/procfs.h> /* PIOC*, prmap_t */
147 #endif
148
149 #if defined __APPLE__ && defined __MACH__ /* MacOS X */
150 # include <mach/mach.h>
151 #endif
152
153 #if (defined _WIN32 || defined __WIN32__) || defined __CYGWIN__ /* Windows */
154 # include <windows.h>
155 #endif
156
157 #if defined __BEOS__ /* BeOS */
158 # include <OS.h>
159 #endif
160
161
162 #if HAVE_SETRLIMIT && defined RLIMIT_AS && HAVE_SYS_MMAN_H && HAVE_MPROTECT
163
164 static inline uintptr_t
165 get_rusage_as_via_setrlimit (void)
166 {
167   uintptr_t result;
168
169   struct rlimit orig_limit;
170
171 # if HAVE_MAP_ANONYMOUS
172   const int flags = MAP_ANONYMOUS | MAP_PRIVATE;
173   const int fd = -1;
174 # else /* !HAVE_MAP_ANONYMOUS */
175   const int flags = MAP_FILE | MAP_PRIVATE;
176   int fd = open ("/dev/zero", O_RDONLY, 0666);
177   if (fd < 0)
178     return 0;
179 # endif
180
181   /* Record the original limit.  */
182   if (getrlimit (RLIMIT_AS, &orig_limit) < 0)
183     {
184       result = 0;
185       goto done2;
186     }
187
188   if (orig_limit.rlim_max != RLIM_INFINITY
189       && (orig_limit.rlim_cur == RLIM_INFINITY
190           || orig_limit.rlim_cur > orig_limit.rlim_max))
191     /* We may not be able to restore the current rlim_cur value.
192        So bail out.  */
193     {
194       result = 0;
195       goto done2;
196     }
197
198   {
199     /* The granularity is a single page.  */
200     const size_t pagesize = getpagesize ();
201
202     uintptr_t low_bound = 0;
203     uintptr_t high_bound;
204
205     for (;;)
206       {
207         /* Here we know that the address space size is >= low_bound.  */
208         struct rlimit try_limit;
209         uintptr_t try_next = 2 * low_bound + pagesize;
210
211         if (try_next < low_bound)
212           /* Overflow.  */
213           try_next = ((uintptr_t) (~ 0) / pagesize) * pagesize;
214
215         /* There's no point in trying a value > orig_limit.rlim_max, as
216            setrlimit would fail anyway.  */
217         if (orig_limit.rlim_max != RLIM_INFINITY
218             && orig_limit.rlim_max < try_next)
219           try_next = orig_limit.rlim_max;
220
221         /* Avoid endless loop.  */
222         if (try_next == low_bound)
223           {
224             /* try_next could not be increased.  */
225             result = low_bound;
226             goto done1;
227           }
228
229         try_limit.rlim_max = orig_limit.rlim_max;
230         try_limit.rlim_cur = try_next;
231         if (setrlimit (RLIMIT_AS, &try_limit) == 0)
232           {
233             /* Allocate a page of memory, to compare the current address space
234                size with try_limit.rlim_cur.  */
235             void *new_page =
236               mmap (NULL, pagesize, PROT_READ | PROT_WRITE, flags, fd, 0);
237
238             if (new_page != (void *)(-1))
239               {
240                 /* The page could be added successfully.  Free it.  */
241                 if (munmap (new_page, pagesize) < 0)
242                   abort ();
243                 /* We know that the address space size is
244                    < try_limit.rlim_cur.  */
245                 high_bound = try_next;
246                 break;
247               }
248             else
249               {
250                 /* We know that the address space size is
251                    >= try_limit.rlim_cur.  */
252                 low_bound = try_next;
253               }
254           }
255         else
256           {
257             /* Here we expect only EINVAL, not EPERM.  */
258             if (errno != EINVAL)
259               abort ();
260             /* We know that the address space size is
261                >= try_limit.rlim_cur.  */
262             low_bound = try_next;
263           }
264       }
265
266     /* Here we know that the address space size is
267        >= low_bound and < high_bound.  */
268     while (high_bound - low_bound > pagesize)
269       {
270         struct rlimit try_limit;
271         uintptr_t try_next =
272           low_bound + (((high_bound - low_bound) / 2) / pagesize) * pagesize;
273
274         /* Here low_bound <= try_next < high_bound.  */
275         try_limit.rlim_max = orig_limit.rlim_max;
276         try_limit.rlim_cur = try_next;
277         if (setrlimit (RLIMIT_AS, &try_limit) == 0)
278           {
279             /* Allocate a page of memory, to compare the current address space
280                size with try_limit.rlim_cur.  */
281             void *new_page =
282               mmap (NULL, pagesize, PROT_READ | PROT_WRITE, flags, fd, 0);
283
284             if (new_page != (void *)(-1))
285               {
286                 /* The page could be added successfully.  Free it.  */
287                 if (munmap (new_page, pagesize) < 0)
288                   abort ();
289                 /* We know that the address space size is
290                    < try_limit.rlim_cur.  */
291                 high_bound = try_next;
292               }
293             else
294               {
295                 /* We know that the address space size is
296                    >= try_limit.rlim_cur.  */
297                 low_bound = try_next;
298               }
299           }
300         else
301           {
302             /* Here we expect only EINVAL, not EPERM.  */
303             if (errno != EINVAL)
304               abort ();
305             /* We know that the address space size is
306                >= try_limit.rlim_cur.  */
307             low_bound = try_next;
308           }
309       }
310
311     result = low_bound;
312   }
313
314  done1:
315   /* Restore the original rlim_cur value.  */
316   if (setrlimit (RLIMIT_AS, &orig_limit) < 0)
317     abort ();
318
319  done2:
320 # if !HAVE_MAP_ANONYMOUS
321   close (fd);
322 # endif
323   return result;
324 }
325
326 #endif
327
328
329 /* Support for reading text files in the /proc file system.  */
330
331 #if defined __linux__ || defined __FreeBSD__ /* || defined __CYGWIN__ */
332
333 /* Buffered read-only streams.
334    We cannot use <stdio.h> here, because fopen() calls malloc(), and a malloc()
335    call may call mmap() and thus pre-allocate available memory.  */
336
337 struct rofile
338   {
339     int fd;
340     size_t position;
341     size_t filled;
342     int eof_seen;
343     char buffer[1024];
344   };
345
346 /* Open a read-only file stream.  */
347 static int
348 rof_open (struct rofile *rof, const char *filename)
349 {
350   int fd = open (filename, O_RDONLY);
351   if (fd < 0)
352     return -1;
353   rof->fd = fd;
354   rof->position = 0;
355   rof->filled = 0;
356   rof->eof_seen = 0;
357   return 0;
358 }
359
360 /* Return the next byte from a read-only file stream without consuming it,
361    or -1 at EOF.  */
362 static int
363 rof_peekchar (struct rofile *rof)
364 {
365   if (rof->position == rof->filled)
366     {
367       if (rof->eof_seen)
368         return -1;
369       else
370         for (;;)
371           {
372             int n = read (rof->fd, rof->buffer, sizeof (rof->buffer));
373 # ifdef EINTR
374             if (n < 0 && errno == EINTR)
375               continue;
376 # endif
377             if (n <= 0)
378               {
379                 rof->eof_seen = 1;
380                 return -1;
381               }
382             rof->filled = n;
383             rof->position = 0;
384             break;
385           }
386     }
387   return (unsigned char) rof->buffer[rof->position];
388 }
389
390 /* Return the next byte from a read-only file stream, or -1 at EOF.  */
391 static int
392 rof_getchar (struct rofile *rof)
393 {
394   int c = rof_peekchar (rof);
395   if (c >= 0)
396     rof->position++;
397   return c;
398 }
399
400 /* Parse an unsigned hexadecimal number from a read-only file stream.  */
401 static int
402 rof_scanf_lx (struct rofile *rof, unsigned long *valuep)
403 {
404   unsigned long value = 0;
405   unsigned int numdigits = 0;
406   for (;;)
407     {
408       int c = rof_peekchar (rof);
409       if (c >= '0' && c <= '9')
410         value = (value << 4) + (c - '0');
411       else if (c >= 'A' && c <= 'F')
412         value = (value << 4) + (c - 'A' + 10);
413       else if (c >= 'a' && c <= 'f')
414         value = (value << 4) + (c - 'a' + 10);
415       else
416         break;
417       rof_getchar (rof);
418       numdigits++;
419     }
420   if (numdigits == 0)
421     return -1;
422   *valuep = value;
423   return 0;
424 }
425
426 /* Close a read-only file stream.  */
427 static void
428 rof_close (struct rofile *rof)
429 {
430   close (rof->fd);
431 }
432
433 #endif
434
435
436 static inline uintptr_t
437 get_rusage_as_via_iterator (void)
438 {
439 #if defined __linux__ /* || defined __CYGWIN__ */
440
441   struct rofile rof;
442   int c;
443   unsigned long total;
444
445   /* Open the current process' maps file.  It describes one VMA per line.  */
446   if (rof_open (&rof, "/proc/self/maps") < 0)
447     return 0;
448
449   total = 0;
450   for (;;)
451     {
452       unsigned long start, end;
453
454       if (!(rof_scanf_lx (&rof, &start) >= 0
455             && rof_getchar (&rof) == '-'
456             && rof_scanf_lx (&rof, &end) >= 0))
457         break;
458       while (c = rof_getchar (&rof), c != -1 && c != '\n')
459         ;
460       total += end - start;
461     }
462   rof_close (&rof);
463   return total;
464
465 #elif defined __FreeBSD__
466
467   struct rofile rof;
468   int c;
469   unsigned long total;
470
471   /* Open the current process' maps file.  It describes one VMA per line.  */
472   if (rof_open (&rof, "/proc/curproc/map") < 0)
473     return 0;
474
475   total = 0;
476   for (;;)
477     {
478       unsigned long start, end;
479
480       if (!(rof_getchar (&rof) == '0'
481             && rof_getchar (&rof) == 'x'
482             && rof_scanf_lx (&rof, &start) >= 0))
483         break;
484       while (c = rof_peekchar (&rof), c == ' ' || c == '\t')
485         rof_getchar (&rof);
486       if (!(rof_getchar (&rof) == '0'
487             && rof_getchar (&rof) == 'x'
488             && rof_scanf_lx (&rof, &end) >= 0))
489         break;
490       while (c = rof_getchar (&rof), c != -1 && c != '\n')
491         continue;
492       total += end - start;
493     }
494   rof_close (&rof);
495   return total;
496
497 #elif defined __sgi || defined __osf__ /* IRIX, OSF/1 */
498
499   size_t pagesize;
500   char fnamebuf[6+10+1];
501   char *fname;
502   int fd;
503   int nmaps;
504   size_t memneed;
505 # if HAVE_MAP_ANONYMOUS
506 #  define zero_fd -1
507 #  define map_flags MAP_ANONYMOUS
508 # else
509   int zero_fd;
510 #  define map_flags 0
511 # endif
512   void *auxmap;
513   unsigned long auxmap_start;
514   unsigned long auxmap_end;
515   prmap_t* maps;
516   prmap_t* mp;
517   unsigned long total;
518
519   pagesize = getpagesize ();
520
521   /* Construct fname = sprintf (fnamebuf+i, "/proc/%u", getpid ()).  */
522   fname = fnamebuf + sizeof (fnamebuf) - 1;
523   *fname = '\0';
524   {
525     unsigned int value = getpid ();
526     do
527       *--fname = (value % 10) + '0';
528     while ((value = value / 10) > 0);
529   }
530   fname -= 6;
531   memcpy (fname, "/proc/", 6);
532
533   fd = open (fname, O_RDONLY);
534   if (fd < 0)
535     return 0;
536
537   if (ioctl (fd, PIOCNMAP, &nmaps) < 0)
538     goto fail2;
539
540   memneed = (nmaps + 10) * sizeof (prmap_t);
541   /* Allocate memneed bytes of memory.
542      We cannot use alloca here, because not much stack space is guaranteed.
543      We also cannot use malloc here, because a malloc() call may call mmap()
544      and thus pre-allocate available memory.
545      So use mmap(), and ignore the resulting VMA.  */
546   memneed = ((memneed - 1) / pagesize + 1) * pagesize;
547 # if !HAVE_MAP_ANONYMOUS
548   zero_fd = open ("/dev/zero", O_RDONLY, 0644);
549   if (zero_fd < 0)
550     goto fail2;
551 # endif
552   auxmap = (void *) mmap ((void *) 0, memneed, PROT_READ | PROT_WRITE,
553                           map_flags | MAP_PRIVATE, zero_fd, 0);
554 # if !HAVE_MAP_ANONYMOUS
555   close (zero_fd);
556 # endif
557   if (auxmap == (void *) -1)
558     goto fail2;
559   auxmap_start = (unsigned long) auxmap;
560   auxmap_end = auxmap_start + memneed;
561   maps = (prmap_t *) auxmap;
562
563   if (ioctl (fd, PIOCMAP, maps) < 0)
564     goto fail1;
565
566   total = 0;
567   for (mp = maps;;)
568     {
569       unsigned long start, end;
570
571       start = (unsigned long) mp->pr_vaddr;
572       end = start + mp->pr_size;
573       if (start == 0 && end == 0)
574         break;
575       mp++;
576       if (start <= auxmap_start && auxmap_end - 1 <= end - 1)
577         /* Consider [start,end-1] \ [auxmap_start,auxmap_end-1]
578            = [start,auxmap_start-1] u [auxmap_end,end-1].  */
579         total += (end - start) - memneed;
580       else
581         total += end - start;
582     }
583   munmap (auxmap, memneed);
584   close (fd);
585   return total;
586
587  fail1:
588   munmap (auxmap, memneed);
589  fail2:
590   close (fd);
591   return 0;
592
593 #elif defined __APPLE__ && defined __MACH__ /* MacOS X */
594
595   task_t task = mach_task_self ();
596   vm_address_t address;
597   vm_size_t size;
598   vm_address_t total = 0;
599
600   for (address = VM_MIN_ADDRESS;; address += size)
601     {
602       int more;
603       mach_port_t object_name;
604       /* In MacOS X 10.5, the types vm_address_t, vm_offset_t, vm_size_t have
605          32 bits in 32-bit processes and 64 bits in 64-bit processes. Whereas
606          mach_vm_address_t and mach_vm_size_t are always 64 bits large.
607          MacOS X 10.5 has three vm_region like methods:
608            - vm_region. It has arguments that depend on whether the current
609              process is 32-bit or 64-bit. When linking dynamically, this
610              function exists only in 32-bit processes. Therefore we use it only
611              in 32-bit processes.
612            - vm_region_64. It has arguments that depend on whether the current
613              process is 32-bit or 64-bit. It interprets a flavor
614              VM_REGION_BASIC_INFO as VM_REGION_BASIC_INFO_64, which is
615              dangerous since 'struct vm_region_basic_info_64' is larger than
616              'struct vm_region_basic_info'; therefore let's write
617              VM_REGION_BASIC_INFO_64 explicitly.
618            - mach_vm_region. It has arguments that are 64-bit always. This
619              function is useful when you want to access the VM of a process
620              other than the current process.
621          In 64-bit processes, we could use vm_region_64 or mach_vm_region.
622          I choose vm_region_64 because it uses the same types as vm_region,
623          resulting in less conditional code.  */
624 # if defined __ppc64__ || defined __x86_64__
625       struct vm_region_basic_info_64 info;
626       mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
627
628       more = (vm_region_64 (task, &address, &size, VM_REGION_BASIC_INFO_64,
629                             (vm_region_info_t)&info, &info_count, &object_name)
630               == KERN_SUCCESS);
631 # else
632       struct vm_region_basic_info info;
633       mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT;
634
635       more = (vm_region (task, &address, &size, VM_REGION_BASIC_INFO,
636                          (vm_region_info_t)&info, &info_count, &object_name)
637               == KERN_SUCCESS);
638 # endif
639       if (object_name != MACH_PORT_NULL)
640         mach_port_deallocate (mach_task_self (), object_name);
641       if (!more)
642         break;
643       total += size;
644     }
645   return total;
646
647 #elif (defined _WIN32 || defined __WIN32__) || defined __CYGWIN__
648   /* Windows platform.  Use the native Windows API.  */
649
650   MEMORY_BASIC_INFORMATION info;
651   unsigned long address = 0;
652   unsigned long total = 0;
653
654   while (VirtualQuery ((void*)address, &info, sizeof(info)) == sizeof(info))
655     {
656       if (info.State != MEM_FREE)
657         /* Ignore areas where info.Protect has the undocumented value 0.
658            This is needed, so that on Cygwin, areas used by malloc() are
659            distinguished from areas reserved for future malloc().  */
660         if (info.Protect != 0)
661           total += info.RegionSize;
662       address = (unsigned long)info.BaseAddress + info.RegionSize;
663     }
664   return total;
665
666 #elif defined __BEOS__
667   /* Use the BeOS specific API.  */
668
669   area_info info;
670   int32 cookie;
671   unsigned long total = 0;
672
673   cookie = 0;
674   while (get_next_area_info (0, &cookie, &info) == B_OK)
675     {
676       unsigned long start, end;
677
678       start = (unsigned long) info.address;
679       end = start + info.size;
680
681       total += end - start;
682     }
683   return total;
684
685 #else
686
687   return 0;
688
689 #endif
690 }
691
692
693 uintptr_t
694 get_rusage_as (void)
695 {
696 #if (defined __APPLE__ && defined __MACH__) || defined _AIX || defined __CYGWIN__ /* MacOS X, AIX, Cygwin */
697   /* get_rusage_as_via_setrlimit() does not work.
698      Prefer get_rusage_as_via_iterator().  */
699   return get_rusage_as_via_iterator ();
700 #elif HAVE_SETRLIMIT && defined RLIMIT_AS && HAVE_SYS_MMAN_H && HAVE_MPROTECT
701   /* Prefer get_rusage_as_via_setrlimit() if it succeeds,
702      because the caller may want to use the result with setrlimit().  */
703   uintptr_t result;
704
705   result = get_rusage_as_via_setrlimit ();
706   if (result == 0)
707     result = get_rusage_as_via_iterator ();
708   return result;
709 #else
710   return get_rusage_as_via_iterator ();
711 #endif
712 }