get-rusage-as: Improvement for Cygwin.
[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 done;
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 done;
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 done;
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   /* Restore the original rlim_cur value.  */
315   if (setrlimit (RLIMIT_AS, &orig_limit) < 0)
316     abort ();
317
318  done:
319 # if !HAVE_MAP_ANONYMOUS
320   close (fd);
321 # endif
322   return result;
323 }
324
325 #endif
326
327
328 /* Support for reading text files in the /proc file system.  */
329
330 #if defined __linux__ || defined __FreeBSD__ /* || defined __CYGWIN__ */
331
332 /* Buffered read-only streams.
333    We cannot use <stdio.h> here, because fopen() calls malloc(), and a malloc()
334    call may call mmap() and thus pre-allocate available memory.  */
335
336 struct rofile
337   {
338     int fd;
339     size_t position;
340     size_t filled;
341     int eof_seen;
342     char buffer[1024];
343   };
344
345 /* Open a read-only file stream.  */
346 static int
347 rof_open (struct rofile *rof, const char *filename)
348 {
349   int fd = open (filename, O_RDONLY);
350   if (fd < 0)
351     return -1;
352   rof->fd = fd;
353   rof->position = 0;
354   rof->filled = 0;
355   rof->eof_seen = 0;
356   return 0;
357 }
358
359 /* Return the next byte from a read-only file stream without consuming it,
360    or -1 at EOF.  */
361 static int
362 rof_peekchar (struct rofile *rof)
363 {
364   if (rof->position == rof->filled)
365     {
366       if (rof->eof_seen)
367         return -1;
368       else
369         for (;;)
370           {
371             int n = read (rof->fd, rof->buffer, sizeof (rof->buffer));
372 # ifdef EINTR
373             if (n < 0 && errno == EINTR)
374               continue;
375 # endif
376             if (n <= 0)
377               {
378                 rof->eof_seen = 1;
379                 return -1;
380               }
381             rof->filled = n;
382             rof->position = 0;
383             break;
384           }
385     }
386   return (unsigned char) rof->buffer[rof->position];
387 }
388
389 /* Return the next byte from a read-only file stream, or -1 at EOF.  */
390 static int
391 rof_getchar (struct rofile *rof)
392 {
393   int c = rof_peekchar (rof);
394   if (c >= 0)
395     rof->position++;
396   return c;
397 }
398
399 /* Parse an unsigned hexadecimal number from a read-only file stream.  */
400 static int
401 rof_scanf_lx (struct rofile *rof, unsigned long *valuep)
402 {
403   unsigned long value = 0;
404   unsigned int numdigits = 0;
405   for (;;)
406     {
407       int c = rof_peekchar (rof);
408       if (c >= '0' && c <= '9')
409         value = (value << 4) + (c - '0');
410       else if (c >= 'A' && c <= 'F')
411         value = (value << 4) + (c - 'A' + 10);
412       else if (c >= 'a' && c <= 'f')
413         value = (value << 4) + (c - 'a' + 10);
414       else
415         break;
416       rof_getchar (rof);
417       numdigits++;
418     }
419   if (numdigits == 0)
420     return -1;
421   *valuep = value;
422   return 0;
423 }
424
425 /* Close a read-only file stream.  */
426 static void
427 rof_close (struct rofile *rof)
428 {
429   close (rof->fd);
430 }
431
432 #endif
433
434
435 static inline uintptr_t
436 get_rusage_as_via_iterator (void)
437 {
438 #if defined __linux__ /* || defined __CYGWIN__ */
439
440   struct rofile rof;
441   int c;
442   unsigned long total;
443
444   /* Open the current process' maps file.  It describes one VMA per line.  */
445   if (rof_open (&rof, "/proc/self/maps") < 0)
446     return 0;
447
448   total = 0;
449   for (;;)
450     {
451       unsigned long start, end;
452
453       if (!(rof_scanf_lx (&rof, &start) >= 0
454             && rof_getchar (&rof) == '-'
455             && rof_scanf_lx (&rof, &end) >= 0))
456         break;
457       while (c = rof_getchar (&rof), c != -1 && c != '\n')
458         ;
459       total += end - start;
460     }
461   rof_close (&rof);
462   return total;
463
464 #elif defined __FreeBSD__
465
466   struct rofile rof;
467   int c;
468   unsigned long total;
469
470   /* Open the current process' maps file.  It describes one VMA per line.  */
471   if (rof_open (&rof, "/proc/curproc/map") < 0)
472     return 0;
473
474   total = 0;
475   for (;;)
476     {
477       unsigned long start, end;
478
479       if (!(rof_getchar (&rof) == '0'
480             && rof_getchar (&rof) == 'x'
481             && rof_scanf_lx (&rof, &start) >= 0))
482         break;
483       while (c = rof_peekchar (&rof), c == ' ' || c == '\t')
484         rof_getchar (&rof);
485       if (!(rof_getchar (&rof) == '0'
486             && rof_getchar (&rof) == 'x'
487             && rof_scanf_lx (&rof, &end) >= 0))
488         break;
489       while (c = rof_getchar (&rof), c != -1 && c != '\n')
490         continue;
491       total += end - start;
492     }
493   rof_close (&rof);
494   return total;
495
496 #elif defined __sgi || defined __osf__ /* IRIX, OSF/1 */
497
498   size_t pagesize;
499   char fnamebuf[6+10+1];
500   char *fname;
501   int fd;
502   int nmaps;
503   size_t memneed;
504 # if HAVE_MAP_ANONYMOUS
505 #  define zero_fd -1
506 #  define map_flags MAP_ANONYMOUS
507 # else
508   int zero_fd;
509 #  define map_flags 0
510 # endif
511   void *auxmap;
512   unsigned long auxmap_start;
513   unsigned long auxmap_end;
514   prmap_t* maps;
515   prmap_t* mp;
516   unsigned long total;
517
518   pagesize = getpagesize ();
519
520   /* Construct fname = sprintf (fnamebuf+i, "/proc/%u", getpid ()).  */
521   fname = fnamebuf + sizeof (fnamebuf) - 1;
522   *fname = '\0';
523   {
524     unsigned int value = getpid ();
525     do
526       *--fname = (value % 10) + '0';
527     while ((value = value / 10) > 0);
528   }
529   fname -= 6;
530   memcpy (fname, "/proc/", 6);
531
532   fd = open (fname, O_RDONLY);
533   if (fd < 0)
534     return 0;
535
536   if (ioctl (fd, PIOCNMAP, &nmaps) < 0)
537     goto fail2;
538
539   memneed = (nmaps + 10) * sizeof (prmap_t);
540   /* Allocate memneed bytes of memory.
541      We cannot use alloca here, because not much stack space is guaranteed.
542      We also cannot use malloc here, because a malloc() call may call mmap()
543      and thus pre-allocate available memory.
544      So use mmap(), and ignore the resulting VMA.  */
545   memneed = ((memneed - 1) / pagesize + 1) * pagesize;
546 # if !HAVE_MAP_ANONYMOUS
547   zero_fd = open ("/dev/zero", O_RDONLY, 0644);
548   if (zero_fd < 0)
549     goto fail2;
550 # endif
551   auxmap = (void *) mmap ((void *) 0, memneed, PROT_READ | PROT_WRITE,
552                           map_flags | MAP_PRIVATE, zero_fd, 0);
553 # if !HAVE_MAP_ANONYMOUS
554   close (zero_fd);
555 # endif
556   if (auxmap == (void *) -1)
557     goto fail2;
558   auxmap_start = (unsigned long) auxmap;
559   auxmap_end = auxmap_start + memneed;
560   maps = (prmap_t *) auxmap;
561
562   if (ioctl (fd, PIOCMAP, maps) < 0)
563     goto fail1;
564
565   total = 0;
566   for (mp = maps;;)
567     {
568       unsigned long start, end;
569
570       start = (unsigned long) mp->pr_vaddr;
571       end = start + mp->pr_size;
572       if (start == 0 && end == 0)
573         break;
574       mp++;
575       if (start <= auxmap_start && auxmap_end - 1 <= end - 1)
576         /* Consider [start,end-1] \ [auxmap_start,auxmap_end-1]
577            = [start,auxmap_start-1] u [auxmap_end,end-1].  */
578         total += (end - start) - memneed;
579       else
580         total += end - start;
581     }
582   munmap (auxmap, memneed);
583   close (fd);
584   return total;
585
586  fail1:
587   munmap (auxmap, memneed);
588  fail2:
589   close (fd);
590   return 0;
591
592 #elif defined __APPLE__ && defined __MACH__ /* MacOS X */
593
594   task_t task = mach_task_self ();
595   vm_address_t address;
596   vm_size_t size;
597   vm_address_t total = 0;
598
599   for (address = VM_MIN_ADDRESS;; address += size)
600     {
601       int more;
602       mach_port_t object_name;
603       /* In MacOS X 10.5, the types vm_address_t, vm_offset_t, vm_size_t have
604          32 bits in 32-bit processes and 64 bits in 64-bit processes. Whereas
605          mach_vm_address_t and mach_vm_size_t are always 64 bits large.
606          MacOS X 10.5 has three vm_region like methods:
607            - vm_region. It has arguments that depend on whether the current
608              process is 32-bit or 64-bit. When linking dynamically, this
609              function exists only in 32-bit processes. Therefore we use it only
610              in 32-bit processes.
611            - vm_region_64. It has arguments that depend on whether the current
612              process is 32-bit or 64-bit. It interprets a flavor
613              VM_REGION_BASIC_INFO as VM_REGION_BASIC_INFO_64, which is
614              dangerous since 'struct vm_region_basic_info_64' is larger than
615              'struct vm_region_basic_info'; therefore let's write
616              VM_REGION_BASIC_INFO_64 explicitly.
617            - mach_vm_region. It has arguments that are 64-bit always. This
618              function is useful when you want to access the VM of a process
619              other than the current process.
620          In 64-bit processes, we could use vm_region_64 or mach_vm_region.
621          I choose vm_region_64 because it uses the same types as vm_region,
622          resulting in less conditional code.  */
623 # if defined __ppc64__ || defined __x86_64__
624       struct vm_region_basic_info_64 info;
625       mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT_64;
626
627       more = (vm_region_64 (task, &address, &size, VM_REGION_BASIC_INFO_64,
628                             (vm_region_info_t)&info, &info_count, &object_name)
629               == KERN_SUCCESS);
630 # else
631       struct vm_region_basic_info info;
632       mach_msg_type_number_t info_count = VM_REGION_BASIC_INFO_COUNT;
633
634       more = (vm_region (task, &address, &size, VM_REGION_BASIC_INFO,
635                          (vm_region_info_t)&info, &info_count, &object_name)
636               == KERN_SUCCESS);
637 # endif
638       if (object_name != MACH_PORT_NULL)
639         mach_port_deallocate (mach_task_self (), object_name);
640       if (!more)
641         break;
642       total += size;
643     }
644   return total;
645
646 #elif (defined _WIN32 || defined __WIN32__) || defined __CYGWIN__
647   /* Windows platform.  Use the native Windows API.  */
648
649   MEMORY_BASIC_INFORMATION info;
650   unsigned long address = 0;
651   unsigned long total = 0;
652
653   while (VirtualQuery ((void*)address, &info, sizeof(info)) == sizeof(info))
654     {
655       if (info.State != MEM_FREE)
656         /* Ignore areas where info.Protect has the undocumented value 0.
657            This is needed, so that on Cygwin, areas used by malloc() are
658            distinguished from areas reserved for future malloc().  */
659         if (info.Protect != 0)
660           total += info.RegionSize;
661       address = (unsigned long)info.BaseAddress + info.RegionSize;
662     }
663   return total;
664
665 #elif defined __BEOS__
666   /* Use the BeOS specific API.  */
667
668   area_info info;
669   int32 cookie;
670   unsigned long total = 0;
671
672   cookie = 0;
673   while (get_next_area_info (0, &cookie, &info) == B_OK)
674     {
675       unsigned long start, end;
676
677       start = (unsigned long) info.address;
678       end = start + info.size;
679
680       total += end - start;
681     }
682   return total;
683
684 #else
685
686   return 0;
687
688 #endif
689 }
690
691
692 uintptr_t
693 get_rusage_as (void)
694 {
695 #if (defined __APPLE__ && defined __MACH__) || defined _AIX || defined __CYGWIN__ /* MacOS X, AIX, Cygwin */
696   /* get_rusage_as_via_setrlimit() does not work.
697      Prefer get_rusage_as_via_iterator().  */
698   return get_rusage_as_via_iterator ();
699 #elif HAVE_SETRLIMIT && defined RLIMIT_AS && HAVE_SYS_MMAN_H && HAVE_MPROTECT
700   /* Prefer get_rusage_as_via_setrlimit() if it succeeds,
701      because the caller may want to use the result with setrlimit().  */
702   uintptr_t result;
703
704   result = get_rusage_as_via_setrlimit ();
705   if (result == 0)
706     result = get_rusage_as_via_iterator ();
707   return result;
708 #else
709   return get_rusage_as_via_iterator ();
710 #endif
711 }