Merge branch 'master' into javascript-binding
[gpgme.git] / src / posix-io.c
index c903072..0448d29 100644 (file)
@@ -15,7 +15,7 @@
    Lesser General Public License for more details.
 
    You should have received a copy of the GNU Lesser General Public
-   License along with this program; if not, see <http://www.gnu.org/licenses/>.
+   License along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 #ifdef HAVE_CONFIG_H
 #include <ctype.h>
 #include <sys/resource.h>
 
-#if __linux__
+#ifdef USE_LINUX_GETDENTS
+# include <sys/syscall.h>
 # include <sys/types.h>
 # include <dirent.h>
-#endif /*__linux__ */
+#endif /*USE_LINUX_GETDENTS*/
 
 
 #include "util.h"
@@ -59,6 +60,7 @@
 #include "ath.h"
 #include "debug.h"
 
+
 \f
 void
 _gpgme_io_subsystem_init (void)
@@ -279,6 +281,22 @@ _gpgme_io_set_nonblocking (int fd)
 }
 
 
+#ifdef USE_LINUX_GETDENTS
+/* This is not declared in public headers; getdents64(2) says that we must
+ * define it ourselves.  */
+struct linux_dirent64
+{
+  ino64_t d_ino;
+  off64_t d_off;
+  unsigned short d_reclen;
+  unsigned char d_type;
+  char d_name[];
+};
+
+# define DIR_BUF_SIZE 1024
+#endif /*USE_LINUX_GETDENTS*/
+
+
 static long int
 get_max_fds (void)
 {
@@ -287,29 +305,53 @@ get_max_fds (void)
   int rc;
 
   /* Under Linux we can figure out the highest used file descriptor by
-   * reading /proc/self/fd.  This is in the common cases much fast than
-   * for example doing 4096 close calls where almost all of them will
-   * fail.  */
-#ifdef __linux__
+   * reading /proc/self/fd.  This is in the common cases much faster
+   * than for example doing 4096 close calls where almost all of them
+   * will fail.
+   *
+   * We can't use the normal opendir/readdir/closedir interface between
+   * fork and exec in a multi-threaded process because opendir uses
+   * malloc and thus a mutex which may deadlock with a malloc in another
+   * thread.  However, the underlying getdents system call is safe.  */
+#ifdef USE_LINUX_GETDENTS
   {
-    DIR *dir = NULL;
-    struct dirent *dir_entry;
+    int dir_fd;
+    char dir_buf[DIR_BUF_SIZE];
+    struct linux_dirent64 *dir_entry;
+    int r, pos;
     const char *s;
     int x;
 
-    dir = opendir ("/proc/self/fd");
-    if (dir)
+    dir_fd = open ("/proc/self/fd", O_RDONLY | O_DIRECTORY);
+    if (dir_fd != -1)
       {
-        while ((dir_entry = readdir (dir)))
+        for (;;)
           {
-            s = dir_entry->d_name;
-            if ( *s < '0' || *s > '9')
-              continue;
-            x = atoi (s);
-            if (x > fds)
-              fds = x;
+            r = syscall(SYS_getdents64, dir_fd, dir_buf, DIR_BUF_SIZE);
+            if (r == -1)
+              {
+                /* Fall back to other methods.  */
+                fds = -1;
+                break;
+              }
+            if (r == 0)
+              break;
+
+            for (pos = 0; pos < r; pos += dir_entry->d_reclen)
+              {
+                dir_entry = (struct linux_dirent64 *) (dir_buf + pos);
+                s = dir_entry->d_name;
+                if (*s < '0' || *s > '9')
+                  continue;
+                /* atoi is not guaranteed to be async-signal-safe.  */
+                for (x = 0; *s >= '0' && *s <= '9'; s++)
+                  x = x * 10 + (*s - '0');
+                if (!*s && x > fds && x != dir_fd)
+                  fds = x;
+              }
           }
-        closedir (dir);
+
+        close (dir_fd);
       }
     if (fds != -1)
       {
@@ -317,7 +359,7 @@ get_max_fds (void)
         source = "/proc";
       }
     }
-#endif /* __linux__ */
+#endif /*USE_LINUX_GETDENTS*/
 
 #ifdef RLIMIT_NOFILE
   if (fds == -1)
@@ -453,10 +495,9 @@ _gpgme_io_spawn (const char *path, char *const argv[], unsigned int flags,
       /* Intermediate child to prevent zombie processes.  */
       if ((pid = fork ()) == 0)
        {
-         int max_fds = get_max_fds ();
-         int fd;
-
          /* Child.  */
+          int max_fds = -1;
+          int fd;
          int seen_stdin = 0;
          int seen_stdout = 0;
          int seen_stderr = 0;
@@ -464,15 +505,40 @@ _gpgme_io_spawn (const char *path, char *const argv[], unsigned int flags,
          if (atfork)
            atfork (atforkvalue, 0);
 
-         /* First close all fds which will not be inherited.  */
-         for (fd = 0; fd < max_fds; fd++)
-           {
-             for (i = 0; fd_list[i].fd != -1; i++)
-               if (fd_list[i].fd == fd)
-                 break;
-             if (fd_list[i].fd == -1)
-               close (fd);
-           }
+          /* First close all fds which will not be inherited.  If we
+           * have closefrom(2) we first figure out the highest fd we
+           * do not want to close, then call closefrom, and on success
+           * use the regular code to close all fds up to the start
+           * point of closefrom.  Note that Solaris' and FreeBSD's closefrom do
+           * not return errors.  */
+#ifdef HAVE_CLOSEFROM
+          {
+            fd = -1;
+            for (i = 0; fd_list[i].fd != -1; i++)
+              if (fd_list[i].fd > fd)
+                fd = fd_list[i].fd;
+            fd++;
+#if defined(__sun) || defined(__FreeBSD__)
+            closefrom (fd);
+            max_fds = fd;
+#else /*!__sun */
+            while ((i = closefrom (fd)) && errno == EINTR)
+              ;
+            if (!i || errno == EBADF)
+              max_fds = fd;
+#endif /*!__sun*/
+          }
+#endif /*HAVE_CLOSEFROM*/
+          if (max_fds == -1)
+            max_fds = get_max_fds ();
+          for (fd = 0; fd < max_fds; fd++)
+            {
+              for (i = 0; fd_list[i].fd != -1; i++)
+                if (fd_list[i].fd == fd)
+                  break;
+              if (fd_list[i].fd == -1)
+                close (fd);
+            }
 
          /* And now dup and close those to be duplicated.  */
          for (i = 0; fd_list[i].fd != -1; i++)