Merge branch 'master' into javascript-binding
[gpgme.git] / src / posix-io.c
index 762051e..0448d29 100644 (file)
@@ -3,19 +3,19 @@
    Copyright (C) 2001, 2002, 2004, 2005, 2007, 2010 g10 Code GmbH
 
    This file is part of GPGME.
+
    GPGME is free software; you can redistribute it and/or modify it
    under the terms of the GNU Lesser General Public License as
    published by the Free Software Foundation; either version 2.1 of
    the License, or (at your option) any later version.
-   
+
    GPGME is distributed in the hope that it will be useful, but
    WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    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
@@ -23,6 +23,9 @@
 #endif
 #include <stdio.h>
 #include <stdlib.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
 #include <string.h>
 #include <assert.h>
 #include <errno.h>
@@ -34,7 +37,9 @@
 #ifdef HAVE_SYS_TIME_H
 # include <sys/time.h>
 #endif
-#include <sys/types.h>
+#ifdef HAVE_SYS_TYPES_H
+# include <sys/types.h>
+#endif
 #include <sys/wait.h>
 #ifdef HAVE_SYS_UIO_H
 # include <sys/uio.h>
 #include <ctype.h>
 #include <sys/resource.h>
 
+#ifdef USE_LINUX_GETDENTS
+# include <sys/syscall.h>
+# include <sys/types.h>
+# include <dirent.h>
+#endif /*USE_LINUX_GETDENTS*/
+
+
 #include "util.h"
 #include "priv-io.h"
 #include "sema.h"
 #include "ath.h"
 #include "debug.h"
 
+
 \f
 void
 _gpgme_io_subsystem_init (void)
@@ -193,7 +206,7 @@ _gpgme_io_close (int fd)
       handler (fd, handler_value);
     }
 
-  /* Then do the close.  */    
+  /* Then do the close.  */
   res = close (fd);
   return TRACE_SYSRES (res);
 }
@@ -210,7 +223,7 @@ _gpgme_io_set_close_notify (int fd, _gpgme_close_notify_handler_t handler,
              "close_handler=%p/%p", handler, value);
 
   assert (fd != -1);
-  
+
   LOCK (notify_table_lock);
   for (idx=0; idx < notify_table_size; idx++)
     if (notify_table[idx].fd == -1)
@@ -244,7 +257,7 @@ _gpgme_io_set_close_notify (int fd, _gpgme_close_notify_handler_t handler,
   notify_table[idx].fd = fd;
   notify_table[idx].handler = handler;
   notify_table[idx].value = value;
-  
+
  leave:
   UNLOCK (notify_table_lock);
 
@@ -268,23 +281,97 @@ _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)
 {
-  char *source = NULL;
+  const char *source = NULL;
   long int fds = -1;
   int rc;
 
-#ifdef RLIMIT_NOFILE
+  /* Under Linux we can figure out the highest used file descriptor by
+   * 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
   {
-    struct rlimit rl;
-    rc = getrlimit (RLIMIT_NOFILE, &rl);
-    if (rc == 0)
+    int dir_fd;
+    char dir_buf[DIR_BUF_SIZE];
+    struct linux_dirent64 *dir_entry;
+    int r, pos;
+    const char *s;
+    int x;
+
+    dir_fd = open ("/proc/self/fd", O_RDONLY | O_DIRECTORY);
+    if (dir_fd != -1)
+      {
+        for (;;)
+          {
+            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;
+              }
+          }
+
+        close (dir_fd);
+      }
+    if (fds != -1)
       {
-       source = "RLIMIT_NOFILE";
-       fds = rl.rlim_max;
+        fds++;
+        source = "/proc";
       }
-  }
+    }
+#endif /*USE_LINUX_GETDENTS*/
+
+#ifdef RLIMIT_NOFILE
+  if (fds == -1)
+    {
+      struct rlimit rl;
+      rc = getrlimit (RLIMIT_NOFILE, &rl);
+      if (rc == 0)
+        {
+          source = "RLIMIT_NOFILE";
+          fds = rl.rlim_max;
+        }
+    }
 #endif
 #ifdef RLIMIT_OFILE
   if (fds == -1)
@@ -329,6 +416,16 @@ get_max_fds (void)
       fds = 1024;
     }
 
+  /* AIX returns INT32_MAX instead of a proper value.  We assume that
+   * this is always an error and use a more reasonable limit.  */
+#ifdef INT32_MAX
+  if (fds == INT32_MAX)
+    {
+      source = "aix-fix";
+      fds = 1024;
+    }
+#endif
+
   TRACE2 (DEBUG_SYSIO, "gpgme:max_fds", 0, "max fds=%i (%s)", fds, source);
   return fds;
 }
@@ -338,10 +435,15 @@ int
 _gpgme_io_waitpid (int pid, int hang, int *r_status, int *r_signal)
 {
   int status;
+  pid_t ret;
 
   *r_status = 0;
   *r_signal = 0;
-  if (_gpgme_ath_waitpid (pid, &status, hang? 0 : WNOHANG) == pid)
+  do
+    ret = _gpgme_ath_waitpid (pid, &status, hang? 0 : WNOHANG);
+  while (ret == (pid_t)(-1) && errno == EINTR);
+
+  if (ret == pid)
     {
       if (WIFSIGNALED (status))
        {
@@ -370,8 +472,6 @@ _gpgme_io_spawn (const char *path, char *const argv[], unsigned int flags,
   int status;
   int signo;
 
-  (void)flags;
-
   TRACE_BEG1 (DEBUG_SYSIO, "_gpgme_io_spawn", path,
              "path=%s", path);
   i = 0;
@@ -387,7 +487,7 @@ _gpgme_io_spawn (const char *path, char *const argv[], unsigned int flags,
       TRACE_LOG3 ("fd[%i] = 0x%x -> 0x%x", i, fd_list[i].fd, fd_list[i].dup_to);
 
   pid = fork ();
-  if (pid == -1) 
+  if (pid == -1)
     return TRACE_SYSRES (-1);
 
   if (!pid)
@@ -395,25 +495,50 @@ _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;
 
          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++)
@@ -428,6 +553,8 @@ _gpgme_io_spawn (const char *path, char *const argv[], unsigned int flags,
 
              if (child_fd == 0)
                seen_stdin = 1;
+             else if (child_fd == 1)
+               seen_stdout = 1;
              else if (child_fd == 2)
                seen_stderr = 1;
 
@@ -448,57 +575,39 @@ _gpgme_io_spawn (const char *path, char *const argv[], unsigned int flags,
 
              close (fd_list[i].fd);
            }
-         
-         if (! seen_stdin || ! seen_stderr)
+
+         if (! seen_stdin || ! seen_stdout || !seen_stderr)
            {
              fd = open ("/dev/null", O_RDWR);
              if (fd == -1)
                {
-#if 0
-                 /* FIXME: The debug file descriptor is not dup'ed
-                    anyway, so we can't see this.  */
-                 TRACE_LOG1 ("can't open `/dev/null': %s\n",
-                             strerror (errno));
-#endif
+                 /* The debug file descriptor is not dup'ed, so we
+                    can't do a trace output.  */
                  _exit (8);
                }
-             /* Make sure that the process has connected stdin.  */
+             /* Make sure that the process has connected stdin.  */
              if (! seen_stdin && fd != 0)
                {
                  if (dup2 (fd, 0) == -1)
-                   {
-#if 0
-                 /* FIXME: The debug file descriptor is not dup'ed
-                    anyway, so we can't see this.  */
-                     TRACE_LOG1 ("dup2(/dev/null, 0) failed: %s\n",
-                                 strerror (errno));
-#endif
-                     _exit (8);
-                   }
+                    _exit (8);
                }
+             if (! seen_stdout && fd != 1)
+                {
+                  if (dup2 (fd, 1) == -1)
+                    _exit (8);
+                }
              if (! seen_stderr && fd != 2)
-               if (dup2 (fd, 2) == -1)
-                 {
-#if 0
-                   /* FIXME: The debug file descriptor is not dup'ed
-                      anyway, so we can't see this.  */
-                   TRACE_LOG1 ("dup2(dev/null, 2) failed: %s\n",
-                               strerror (errno));
-#endif
-                   _exit (8);
-                 }
-             if (fd != 0 && fd != 2)
+                {
+                  if (dup2 (fd, 2) == -1)
+                    _exit (8);
+                }
+             if (fd != 0 && fd != 1 && fd != 2)
                close (fd);
            }
-    
+
          execv (path, (char *const *) argv);
          /* Hmm: in that case we could write a special status code to the
             status-pipe.  */
-#if 0
-         /* FIXME: The debug file descriptor is not dup'ed anyway, so
-            we can't see this.  */
-         TRACE_LOG1 ("exec of `%s' failed\n", path);
-#endif
          _exit (8);
          /* End child.  */
        }
@@ -557,10 +666,16 @@ _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock)
   any = 0;
   for (i = 0; i < nfds; i++)
     {
-      if (fds[i].fd == -1) 
+      if (fds[i].fd == -1)
        continue;
       if (fds[i].for_read)
        {
+          if (fds[i].fd >= FD_SETSIZE)
+            {
+              TRACE_END (dbg_help, " -BAD- ]");
+              gpg_err_set_errno (EMFILE);
+              return TRACE_SYSRES (-1);
+            }
          assert (!FD_ISSET (fds[i].fd, &readfds));
          FD_SET (fds[i].fd, &readfds);
          if (fds[i].fd > max_fd)
@@ -570,6 +685,12 @@ _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock)
         }
       else if (fds[i].for_write)
        {
+          if (fds[i].fd >= FD_SETSIZE)
+            {
+              TRACE_END (dbg_help, " -BAD- ]");
+              gpg_err_set_errno (EMFILE);
+              return TRACE_SYSRES (-1);
+            }
          assert (!FD_ISSET (fds[i].fd, &writefds));
          FD_SET (fds[i].fd, &writefds);
          if (fds[i].fd > max_fd)
@@ -579,7 +700,7 @@ _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock)
         }
       fds[i].signaled = 0;
     }
-  TRACE_END (dbg_help, "]"); 
+  TRACE_END (dbg_help, "]");
   if (!any)
     return TRACE_SYSRES (0);
 
@@ -604,7 +725,7 @@ _gpgme_io_select (struct io_select_fd_s *fds, size_t nfds, int nonblock)
         }
       TRACE_END (dbg_help, "]");
     }
-    
+
   /* The variable N is used to optimize it a little bit.  */
   for (n = count, i = 0; i < nfds && n; i++)
     {
@@ -647,7 +768,7 @@ _gpgme_io_recvmsg (int fd, struct msghdr *msg, int flags)
       nread += iov->iov_len;
       iov++;
     }
-  
+
   TRACE_LOG1 ("about to receive %d bytes", nread);
 
   do
@@ -712,7 +833,11 @@ _gpgme_io_sendmsg (int fd, const struct msghdr *msg, int flags)
 int
 _gpgme_io_dup (int fd)
 {
-  int new_fd = dup (fd);
+  int new_fd;
+
+  do
+    new_fd = dup (fd);
+  while (new_fd == -1 && errno == EINTR);
 
   TRACE1 (DEBUG_SYSIO, "_gpgme_io_dup", fd, "new fd==%i", new_fd);
 
@@ -742,7 +867,9 @@ _gpgme_io_connect (int fd, struct sockaddr *addr, int addrlen)
   TRACE_BEG2 (DEBUG_SYSIO, "_gpgme_io_connect", fd,
              "addr=%p, addrlen=%i", addr, addrlen);
 
-  res = ath_connect (fd, addr, addrlen);
+  do
+    res = ath_connect (fd, addr, addrlen);
+  while (res == -1 && errno == EINTR);
 
   return TRACE_SYSRES (res);
 }