changeset 19215:dca8325aacc6

vma-iter: Improvements for Linux and BSD platforms. - Add support for DragonFly BSD. - Make it more reliable on Linux, GNU/kFreeBSD, FreeBSD, NetBSD. * lib/vma-iter.c (struct rofile, rof_open, rof_peekchar, rof_close): Read the entire file into memory in a single system call. (vma_iterate): Update. Read from /proc on DragonFly BSD like on FreeBSD. * lib/vma-iter.h (VMA_ITERATE_SUPPORTED): Define also on DragonFly BSD.
author Bruno Haible <bruno@clisp.org>
date Tue, 26 Sep 2017 19:48:39 +0200
parents 43391e423ccc
children c35c68cae282
files ChangeLog lib/vma-iter.c lib/vma-iter.h
diffstat 3 files changed, 173 insertions(+), 36 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog	Tue Sep 26 17:03:48 2017 +0200
+++ b/ChangeLog	Tue Sep 26 19:48:39 2017 +0200
@@ -1,3 +1,13 @@
+2017-09-26  Bruno Haible  <bruno@clisp.org>
+
+	vma-iter: Improvements for Linux and BSD platforms.
+	- Add support for DragonFly BSD.
+	- Make it more reliable on Linux, GNU/kFreeBSD, FreeBSD, NetBSD.
+	* lib/vma-iter.c (struct rofile, rof_open, rof_peekchar, rof_close):
+	Read the entire file into memory in a single system call.
+	(vma_iterate): Update. Read from /proc on DragonFly BSD like on FreeBSD.
+	* lib/vma-iter.h (VMA_ITERATE_SUPPORTED): Define also on DragonFly BSD.
+
 2017-09-26  Bruno Haible  <bruno@clisp.org>
 
 	vma-iter: Provide the protection flags on FreeBSD.
--- a/lib/vma-iter.c	Tue Sep 26 17:03:48 2017 +0200
+++ b/lib/vma-iter.c	Tue Sep 26 19:48:39 2017 +0200
@@ -23,7 +23,12 @@
 #include <errno.h> /* errno */
 #include <stdlib.h> /* size_t */
 #include <fcntl.h> /* open, O_RDONLY */
-#include <unistd.h> /* getpagesize, read, close, getpid */
+#include <unistd.h> /* getpagesize, lseek, read, close, getpid */
+
+#if defined __linux__ || defined __FreeBSD_kernel__ || defined __FreeBSD__ || defined __DragonFly__ || defined __NetBSD__ /* || defined __CYGWIN__ */
+# include <sys/types.h>
+# include <sys/mman.h> /* mmap, munmap */
+#endif
 
 #if defined __FreeBSD__ || defined __FreeBSD_kernel__ /* FreeBSD, GNU/kFreeBSD */
 # include <sys/types.h>
@@ -81,33 +86,131 @@
 
 /* Support for reading text files in the /proc file system.  */
 
-#if defined __linux__ || defined __FreeBSD_kernel__ || defined __FreeBSD__ || defined __NetBSD__ /* || defined __CYGWIN__ */
+#if defined __linux__ || defined __FreeBSD_kernel__ || defined __FreeBSD__ || defined __DragonFly__ || defined __NetBSD__ /* || defined __CYGWIN__ */
 
 /* Buffered read-only streams.
    We cannot use <stdio.h> here, because fopen() calls malloc(), and a malloc()
-   call may call mmap() and thus pre-allocate available memory.  */
+   call may call mmap() and thus pre-allocate available memory.
+   Also, we cannot use multiple read() calls, because if the buffer size is
+   smaller than the file's contents:
+     - On NetBSD, the second read() call would return 0, thus making the file
+       appear truncated.
+     - On DragonFly BSD, the first read() call would fail with errno = EFBIG.
+     - On all platforms, if some other thread is doing memory allocations or
+       deallocations between two read() calls, there is a high risk that the
+       result of these two read() calls don't fit together, and as a
+       consequence we will parse gargage and either omit some VMAs or return
+       VMAs with nonsensical addresses.
+   So use mmap(), and ignore the resulting VMA.  */
+
+# ifdef TEST
+/* During testing, we want to run into the hairy cases.  */
+#  define STACK_ALLOCATED_BUFFER_SIZE 32
+# else
+#  define STACK_ALLOCATED_BUFFER_SIZE 1024
+# endif
 
 struct rofile
   {
-    int fd;
     size_t position;
     size_t filled;
     int eof_seen;
-    char buffer[1024];
+    /* These fields deal with allocation of the buffer.  */
+    char *buffer;
+    char *auxmap;
+    size_t auxmap_length;
+    unsigned long auxmap_start;
+    unsigned long auxmap_end;
+    char stack_allocated_buffer[STACK_ALLOCATED_BUFFER_SIZE];
   };
 
 /* Open a read-only file stream.  */
 static int
 rof_open (struct rofile *rof, const char *filename)
 {
-  int fd = open (filename, O_RDONLY);
+  int fd;
+  unsigned long pagesize;
+  size_t size;
+
+  fd = open (filename, O_RDONLY);
   if (fd < 0)
     return -1;
-  rof->fd = fd;
   rof->position = 0;
-  rof->filled = 0;
   rof->eof_seen = 0;
-  return 0;
+  /* Try the static buffer first.  */
+  pagesize = 0;
+  rof->buffer = rof->stack_allocated_buffer;
+  size = sizeof (rof->stack_allocated_buffer);
+  rof->auxmap = NULL;
+  rof->auxmap_start = 0;
+  rof->auxmap_end = 0;
+  for (;;)
+    {
+      /* Attempt to read the contents in a single system call.  */
+      {
+        int n = read (fd, rof->buffer, size);
+# ifdef EINTR
+        if (n < 0 && errno == EINTR)
+          goto retry;
+# endif
+# if defined __DragonFly__
+        if (!(n < 0 && errno == EFBIG))
+# endif
+          {
+            if (n <= 0)
+              /* Empty file.  */
+              goto fail1;
+            if (n < size)
+              {
+                /* The buffer was sufficiently large.  */
+                rof->filled = n;
+                close (fd);
+                return 0;
+              }
+          }
+      }
+      /* Allocate a larger buffer.  */
+      if (pagesize == 0)
+        {
+          pagesize = getpagesize ();
+          size = pagesize;
+        }
+      else
+        {
+          size = 2 * size;
+          if (size == 0)
+            /* Wraparound.  */
+            goto fail1;
+          if (rof->auxmap != NULL)
+            munmap (rof->auxmap, rof->auxmap_length);
+        }
+      rof->auxmap = (void *) mmap ((void *) 0, size, PROT_READ | PROT_WRITE,
+                                   MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
+      if (rof->auxmap == (void *) -1)
+        {
+          close (fd);
+          return -1;
+        }
+      rof->auxmap_length = size;
+      rof->auxmap_start = (unsigned long) rof->auxmap;
+      rof->auxmap_end = rof->auxmap_start + size;
+      rof->buffer = (char *) rof->auxmap;
+     retry:
+      /* Restart.  */
+      if (lseek (fd, 0, SEEK_SET) < 0)
+        {
+          close (fd);
+          fd = open (filename, O_RDONLY);
+          if (fd < 0)
+            goto fail2;
+        }
+    }
+ fail1:
+  close (fd);
+ fail2:
+  if (rof->auxmap != NULL)
+    munmap (rof->auxmap, rof->auxmap_length);
+  return -1;
 }
 
 /* Return the next byte from a read-only file stream without consuming it,
@@ -117,25 +220,8 @@
 {
   if (rof->position == rof->filled)
     {
-      if (rof->eof_seen)
-        return -1;
-      else
-        for (;;)
-          {
-            int n = read (rof->fd, rof->buffer, sizeof (rof->buffer));
-# ifdef EINTR
-            if (n < 0 && errno == EINTR)
-              continue;
-# endif
-            if (n <= 0)
-              {
-                rof->eof_seen = 1;
-                return -1;
-              }
-            rof->filled = n;
-            rof->position = 0;
-            break;
-          }
+      rof->eof_seen = 1;
+      return -1;
     }
   return (unsigned char) rof->buffer[rof->position];
 }
@@ -180,7 +266,8 @@
 static void
 rof_close (struct rofile *rof)
 {
-  close (rof->fd);
+  if (rof->auxmap != NULL)
+    munmap (rof->auxmap, rof->auxmap_length);
 }
 
 #endif
@@ -470,6 +557,9 @@
   /* Open the current process' maps file.  It describes one VMA per line.  */
   if (rof_open (&rof, "/proc/self/maps") >= 0)
     {
+      unsigned long auxmap_start = rof.auxmap_start;
+      unsigned long auxmap_end = rof.auxmap_end;
+
       for (;;)
         {
           unsigned long start, end;
@@ -497,8 +587,22 @@
           while (c = rof_getchar (&rof), c != -1 && c != '\n')
             ;
 
-          if (callback (data, start, end, flags))
-            break;
+          if (start <= auxmap_start && auxmap_end - 1 <= end - 1)
+            {
+              /* Consider [start,end-1] \ [auxmap_start,auxmap_end-1]
+                 = [start,auxmap_start-1] u [auxmap_end,end-1].  */
+              if (start < auxmap_start)
+                if (callback (data, start, auxmap_start, flags))
+                  break;
+              if (auxmap_end - 1 < end - 1)
+                if (callback (data, auxmap_end, end, flags))
+                  break;
+            }
+          else
+            {
+              if (callback (data, start, end, flags))
+                break;
+            }
         }
       rof_close (&rof);
       return 0;
@@ -507,13 +611,16 @@
   /* Fallback if /proc is not accessible: Use sysctl().  */
   return vma_iterate_bsd (callback, data);
 
-#elif defined __FreeBSD__ || defined __NetBSD__
+#elif defined __FreeBSD__ || defined __DragonFly__ || defined __NetBSD__
 
   struct rofile rof;
 
   /* Open the current process' maps file.  It describes one VMA per line.  */
   if (rof_open (&rof, "/proc/curproc/map") >= 0)
     {
+      unsigned long auxmap_start = rof.auxmap_start;
+      unsigned long auxmap_end = rof.auxmap_end;
+
       for (;;)
         {
           unsigned long start, end;
@@ -532,7 +639,7 @@
                 && rof_getchar (&rof) == 'x'
                 && rof_scanf_lx (&rof, &end) >= 0))
             break;
-# if defined __FreeBSD__
+# if defined __FreeBSD__ || defined __DragonFly__
           /* Then the resident pages count.  */
           do
             c = rof_getchar (&rof);
@@ -571,8 +678,22 @@
           while (c = rof_getchar (&rof), c != -1 && c != '\n')
             ;
 
-          if (callback (data, start, end, flags))
-            break;
+          if (start <= auxmap_start && auxmap_end - 1 <= end - 1)
+            {
+              /* Consider [start,end-1] \ [auxmap_start,auxmap_end-1]
+                 = [start,auxmap_start-1] u [auxmap_end,end-1].  */
+              if (start < auxmap_start)
+                if (callback (data, start, auxmap_start, flags))
+                  break;
+              if (auxmap_end - 1 < end - 1)
+                if (callback (data, auxmap_end, end, flags))
+                  break;
+            }
+          else
+            {
+              if (callback (data, start, end, flags))
+                break;
+            }
         }
       rof_close (&rof);
       return 0;
@@ -1279,4 +1400,10 @@
   return 0;
 }
 
+/*
+ * Local Variables:
+ * compile-command: "gcc -ggdb -DTEST -Wall -I.. vma-iter.c"
+ * End:
+ */
+
 #endif /* TEST */
--- a/lib/vma-iter.h	Tue Sep 26 17:03:48 2017 +0200
+++ b/lib/vma-iter.h	Tue Sep 26 19:48:39 2017 +0200
@@ -52,7 +52,7 @@
    this platform.
    Note that even when this macro is defined, vma_iterate() may still fail to
    find any virtual memory area, for example if /proc is not mounted.  */
-#if defined __linux__ || defined __FreeBSD_kernel__ || defined __FreeBSD__ || defined __NetBSD__ || defined __sgi || defined __osf__ || (defined __sun && HAVE_SYS_PROCFS_H) || HAVE_PSTAT_GETPROCVM || (defined __APPLE__ && defined __MACH__) || (defined _WIN32 || defined __WIN32__) || defined __CYGWIN__ || defined __BEOS__ || defined __HAIKU__ || HAVE_MQUERY
+#if defined __linux__ || defined __FreeBSD_kernel__ || defined __FreeBSD__ || defined __DragonFly__ || defined __NetBSD__ || defined __sgi || defined __osf__ || (defined __sun && HAVE_SYS_PROCFS_H) || HAVE_PSTAT_GETPROCVM || (defined __APPLE__ && defined __MACH__) || (defined _WIN32 || defined __WIN32__) || defined __CYGWIN__ || defined __BEOS__ || defined __HAIKU__ || HAVE_MQUERY
 # define VMA_ITERATE_SUPPORTED 1
 #endif