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