gpg: Un-deprecate option --auto-key-retrieve.
[gnupg.git] / common / exechelp-posix.c
index aaf6287..aefb653 100644 (file)
@@ -4,12 +4,22 @@
  *
  * This file is part of GnuPG.
  *
- * GnuPG is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 3 of the License, or
- * (at your option) any later version.
+ * This file is free software; you can redistribute it and/or modify
+ * it under the terms of either
  *
- * GnuPG is distributed in the hope that it will be useful,
+ *   - the GNU Lesser General Public License as published by the Free
+ *     Software Foundation; either version 3 of the License, or (at
+ *     your option) any later version.
+ *
+ * or
+ *
+ *   - the GNU General Public License as published by the Free
+ *     Software Foundation; either version 2 of the License, or (at
+ *     your option) any later version.
+ *
+ * or both in parallel, as here.
+ *
+ * This file 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 General Public License for more details.
 
 #include <stdio.h>
 #include <stdlib.h>
+#ifdef HAVE_STDINT_H
+# include <stdint.h>
+#endif
 #include <string.h>
 #include <errno.h>
 #include <assert.h>
 #ifdef HAVE_SIGNAL_H
 # include <signal.h>
 #endif
-#include <unistd.h> 
+#include <unistd.h>
 #include <fcntl.h>
 
-#ifdef WITHOUT_GNU_PTH /* Give the Makefile a chance to build without Pth.  */
-#undef HAVE_PTH
-#undef USE_GNU_PTH
+#ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth.  */
+#undef HAVE_NPTH
+#undef USE_NPTH
 #endif
 
-#ifdef USE_GNU_PTH      
-#include <pth.h>
+#ifdef HAVE_NPTH
+#include <npth.h>
 #endif
 #include <sys/wait.h>
 
 # include <sys/stat.h>
 #endif
 
+#if __linux__
+# include <sys/types.h>
+# include <dirent.h>
+#endif /*__linux__ */
+
 #include "util.h"
 #include "i18n.h"
 #include "sysutils.h"
 #include "exechelp.h"
 
 
-/* We have the usual problem here: Some modules are linked against pth
-   and some are not.  However we want to use pth_fork and pth_waitpid
-   here. Using a weak symbol works but is not portable - we should
-   provide a an explicit dummy pth module instead of using the
-   pragma.  */ 
-#pragma weak pth_fork
-#pragma weak pth_waitpid
+/* Helper */
+static inline gpg_error_t
+my_error_from_syserror (void)
+{
+  return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
+}
+
+static inline gpg_error_t
+my_error (int errcode)
+{
+  return gpg_err_make (default_errsource, errcode);
+}
 
 
 /* Return the maximum number of currently allowed open file
@@ -79,6 +102,43 @@ get_max_fds (void)
 #ifdef HAVE_GETRLIMIT
   struct rlimit rl;
 
+  /* Under Linux we can figure out the highest used file descriptor by
+   * reading /proc/PID/fd.  This is in the common cases much fast than
+   * for example doing 4096 close calls where almost all of them will
+   * fail.  On a system with a limit of 4096 files and only 8 files
+   * open with the highest number being 10, we speedup close_all_fds
+   * from 125ms to 0.4ms including readdir.
+   *
+   * Another option would be to close the file descriptors as returned
+   * from reading that directory - however then we need to snapshot
+   * that list before starting to close them.  */
+#ifdef __linux__
+  {
+    DIR *dir = NULL;
+    struct dirent *dir_entry;
+    const char *s;
+    int x;
+
+    dir = opendir ("/proc/self/fd");
+    if (dir)
+      {
+        while ((dir_entry = readdir (dir)))
+          {
+            s = dir_entry->d_name;
+            if ( *s < '0' || *s > '9')
+              continue;
+            x = atoi (s);
+            if (x > max_fds)
+              max_fds = x;
+          }
+        closedir (dir);
+      }
+    if (max_fds != -1)
+      return max_fds + 1;
+    }
+#endif /* __linux__ */
+
+
 # ifdef RLIMIT_NOFILE
   if (!getrlimit (RLIMIT_NOFILE, &rl))
     max_fds = rl.rlim_max;
@@ -113,6 +173,13 @@ get_max_fds (void)
   if (max_fds == -1)
     max_fds = 256;  /* Arbitrary limit.  */
 
+  /* AIX returns INT32_MAX instead of a proper value.  We assume that
+     this is always an error and use an arbitrary limit.  */
+#ifdef INT32_MAX
+  if (max_fds == INT32_MAX)
+    max_fds = 256;
+#endif
+
   return max_fds;
 }
 
@@ -160,7 +227,7 @@ close_all_fds (int first, int *except)
 /* Returns an array with all currently open file descriptors.  The end
    of the array is marked by -1.  The caller needs to release this
    array using the *standard free* and not with xfree.  This allow the
-   use of this fucntion right at startup even before libgcrypt has
+   use of this function right at startup even before libgcrypt has
    been initialized.  Returns NULL on error and sets ERRNO
    accordingly.  */
 int *
@@ -181,7 +248,7 @@ get_all_open_fds (void)
   array = calloc (narray, sizeof *array);
   if (!array)
     return NULL;
-  
+
   /* Note:  The list we return is ordered.  */
   for (idx=0, fd=0; fd < max_fd; fd++)
     if (!(fstat (fd, &statbuf) == -1 && errno == EBADF))
@@ -211,7 +278,7 @@ get_all_open_fds (void)
 static void
 do_exec (const char *pgmname, const char *argv[],
          int fd_in, int fd_out, int fd_err,
-         void (*preexec)(void) )
+         int *except, void (*preexec)(void) )
 {
   char **arg_list;
   int i, j;
@@ -243,7 +310,7 @@ do_exec (const char *pgmname, const char *argv[],
         {
           fds[i] = open ("/dev/null", i? O_WRONLY : O_RDONLY);
           if (fds[i] == -1)
-            log_fatal ("failed to open `%s': %s\n",
+            log_fatal ("failed to open '%s': %s\n",
                        "/dev/null", strerror (errno));
         }
     }
@@ -257,8 +324,8 @@ do_exec (const char *pgmname, const char *argv[],
     }
 
   /* Close all other files. */
-  close_all_fds (3, NULL);
-  
+  close_all_fds (3, except);
+
   if (preexec)
     preexec ();
   execv (pgmname, arg_list);
@@ -274,26 +341,77 @@ do_create_pipe (int filedes[2])
 
   if (pipe (filedes) == -1)
     {
-      err = gpg_error_from_syserror ();
+      err = my_error_from_syserror ();
       filedes[0] = filedes[1] = -1;
     }
 
   return err;
 }
 
+
+static gpg_error_t
+create_pipe_and_estream (int filedes[2], estream_t *r_fp,
+                         int outbound, int nonblock)
+{
+  gpg_error_t err;
+
+  if (pipe (filedes) == -1)
+    {
+      err = my_error_from_syserror ();
+      log_error (_("error creating a pipe: %s\n"), gpg_strerror (err));
+      filedes[0] = filedes[1] = -1;
+      *r_fp = NULL;
+      return err;
+    }
+
+  if (!outbound)
+    *r_fp = es_fdopen (filedes[0], nonblock? "r,nonblock" : "r");
+  else
+    *r_fp = es_fdopen (filedes[1], nonblock? "w,nonblock" : "w");
+  if (!*r_fp)
+    {
+      err = my_error_from_syserror ();
+      log_error (_("error creating a stream for a pipe: %s\n"),
+                 gpg_strerror (err));
+      close (filedes[0]);
+      close (filedes[1]);
+      filedes[0] = filedes[1] = -1;
+      return err;
+    }
+  return 0;
+}
+
+
 /* Portable function to create a pipe.  Under Windows the write end is
-   inheritable.  */
+   inheritable.  If R_FP is not NULL, an estream is created for the
+   read end and stored at R_FP.  */
 gpg_error_t
-gnupg_create_inbound_pipe (int filedes[2])
+gnupg_create_inbound_pipe (int filedes[2], estream_t *r_fp, int nonblock)
 {
-  return do_create_pipe (filedes);
+  if (r_fp)
+    return create_pipe_and_estream (filedes, r_fp, 0, nonblock);
+  else
+    return do_create_pipe (filedes);
 }
 
 
 /* Portable function to create a pipe.  Under Windows the read end is
+   inheritable.  If R_FP is not NULL, an estream is created for the
+   write end and stored at R_FP.  */
+gpg_error_t
+gnupg_create_outbound_pipe (int filedes[2], estream_t *r_fp, int nonblock)
+{
+  if (r_fp)
+    return create_pipe_and_estream (filedes, r_fp, 1, nonblock);
+  else
+    return do_create_pipe (filedes);
+}
+
+
+/* Portable function to create a pipe.  Under Windows both ends are
    inheritable.  */
 gpg_error_t
-gnupg_create_outbound_pipe (int filedes[2])
+gnupg_create_pipe (int filedes[2])
 {
   return do_create_pipe (filedes);
 }
@@ -302,65 +420,130 @@ gnupg_create_outbound_pipe (int filedes[2])
 /* Fork and exec the PGMNAME, see exechelp.h for details.  */
 gpg_error_t
 gnupg_spawn_process (const char *pgmname, const char *argv[],
-                     estream_t infile, estream_t outfile,
-                     void (*preexec)(void), unsigned int flags,
-                     estream_t *statusfile, pid_t *pid)
+                     int *except, void (*preexec)(void), unsigned int flags,
+                     estream_t *r_infp,
+                     estream_t *r_outfp,
+                     estream_t *r_errfp,
+                     pid_t *pid)
 {
   gpg_error_t err;
-  int fd, fdout, rp[2];
-
-  (void)flags; /* Currently not used.  */
+  int inpipe[2] = {-1, -1};
+  int outpipe[2] = {-1, -1};
+  int errpipe[2] = {-1, -1};
+  estream_t infp = NULL;
+  estream_t outfp = NULL;
+  estream_t errfp = NULL;
+  int nonblock = !!(flags & GNUPG_SPAWN_NONBLOCK);
+
+  if (r_infp)
+    *r_infp = NULL;
+  if (r_outfp)
+    *r_outfp = NULL;
+  if (r_errfp)
+    *r_errfp = NULL;
+  *pid = (pid_t)(-1); /* Always required.  */
+
+  if (r_infp)
+    {
+      err = create_pipe_and_estream (inpipe, &infp, 1, nonblock);
+      if (err)
+        return err;
+    }
 
-  *statusfile = NULL;
-  *pid = (pid_t)(-1);
-  es_fflush (infile);
-  es_rewind (infile);
-  fd = es_fileno (infile);
-  fdout = es_fileno (outfile);
-  if (fd == -1 || fdout == -1)
-    log_fatal ("no file descriptor for file passed to gnupg_spawn_process\n");
+  if (r_outfp)
+    {
+      err = create_pipe_and_estream (outpipe, &outfp, 0, nonblock);
+      if (err)
+        {
+          if (infp)
+            es_fclose (infp);
+          else if (inpipe[1] != -1)
+            close (inpipe[1]);
+          if (inpipe[0] != -1)
+            close (inpipe[0]);
+
+          return err;
+        }
+    }
 
-  if (pipe (rp) == -1)
+  if (r_errfp)
     {
-      err = gpg_error_from_syserror ();
-      log_error (_("error creating a pipe: %s\n"), strerror (errno));
-      return err;
+      err = create_pipe_and_estream (errpipe, &errfp, 0, nonblock);
+      if (err)
+        {
+          if (infp)
+            es_fclose (infp);
+          else if (inpipe[1] != -1)
+            close (inpipe[1]);
+          if (inpipe[0] != -1)
+            close (inpipe[0]);
+
+          if (outfp)
+            es_fclose (outfp);
+          else if (outpipe[0] != -1)
+            close (outpipe[0]);
+          if (outpipe[1] != -1)
+            close (outpipe[1]);
+
+          return err;
+        }
     }
 
-#ifdef USE_GNU_PTH      
-  *pid = pth_fork? pth_fork () : fork ();
-#else
+
   *pid = fork ();
-#endif
   if (*pid == (pid_t)(-1))
     {
-      err = gpg_error_from_syserror ();
-      log_error (_("error forking process: %s\n"), strerror (errno));
-      close (rp[0]);
-      close (rp[1]);
+      err = my_error_from_syserror ();
+      log_error (_("error forking process: %s\n"), gpg_strerror (err));
+
+      if (infp)
+        es_fclose (infp);
+      else if (inpipe[1] != -1)
+        close (inpipe[1]);
+      if (inpipe[0] != -1)
+        close (inpipe[0]);
+
+      if (outfp)
+        es_fclose (outfp);
+      else if (outpipe[0] != -1)
+        close (outpipe[0]);
+      if (outpipe[1] != -1)
+        close (outpipe[1]);
+
+      if (errfp)
+        es_fclose (errfp);
+      else if (errpipe[0] != -1)
+        close (errpipe[0]);
+      if (errpipe[1] != -1)
+        close (errpipe[1]);
       return err;
     }
 
   if (!*pid)
-    { 
+    {
+      /* This is the child. */
       gcry_control (GCRYCTL_TERM_SECMEM);
-      /* Run child. */
-      do_exec (pgmname, argv, fd, fdout, rp[1], preexec);
+      es_fclose (outfp);
+      es_fclose (errfp);
+      do_exec (pgmname, argv, inpipe[0], outpipe[1], errpipe[1],
+               except, preexec);
       /*NOTREACHED*/
     }
 
-  /* Parent. */
-  close (rp[1]);
-
-  *statusfile = es_fdopen (rp[0], "r");
-  if (!*statusfile)
-    {
-      err = gpg_error_from_syserror ();
-      log_error (_("can't fdopen pipe for reading: %s\n"), strerror (errno));
-      kill (*pid, SIGTERM);
-      *pid = (pid_t)(-1);
-      return err;
-    }
+  /* This is the parent. */
+  if (inpipe[0] != -1)
+    close (inpipe[0]);
+  if (outpipe[1] != -1)
+    close (outpipe[1]);
+  if (errpipe[1] != -1)
+    close (errpipe[1]);
+
+  if (r_infp)
+    *r_infp = infp;
+  if (r_outfp)
+    *r_outfp = outfp;
+  if (r_errfp)
+    *r_errfp = errfp;
 
   return 0;
 }
@@ -381,23 +564,19 @@ gnupg_spawn_process_fd (const char *pgmname, const char *argv[],
 {
   gpg_error_t err;
 
-#ifdef USE_GNU_PTH      
-  *pid = pth_fork? pth_fork () : fork ();
-#else
   *pid = fork ();
-#endif
   if (*pid == (pid_t)(-1))
     {
-      err = gpg_error_from_syserror ();
+      err = my_error_from_syserror ();
       log_error (_("error forking process: %s\n"), strerror (errno));
       return err;
     }
 
   if (!*pid)
-    { 
+    {
       gcry_control (GCRYCTL_TERM_SECMEM);
       /* Run child. */
-      do_exec (pgmname, argv, infd, outfd, errfd, NULL);
+      do_exec (pgmname, argv, infd, outfd, errfd, NULL, NULL);
       /*NOTREACHED*/
     }
 
@@ -405,69 +584,108 @@ gnupg_spawn_process_fd (const char *pgmname, const char *argv[],
 }
 
 
-/* Wait for the process identified by PID to terminate. PGMNAME should
-   be the same as supplied to the spawn function and is only used for
-   diagnostics. Returns 0 if the process succeeded, GPG_ERR_GENERAL
-   for any failures of the spawned program or other error codes.  If
-   EXITCODE is not NULL the exit code of the process is stored at this
-   address or -1 if it could not be retrieved and no error message is
-   logged.  */
+/* See exechelp.h for a description.  */
 gpg_error_t
-gnupg_wait_process (const char *pgmname, pid_t pid, int *exitcode)
+gnupg_wait_process (const char *pgmname, pid_t pid, int hang, int *r_exitcode)
 {
-  gpg_err_code_t ec;
+  return gnupg_wait_processes (&pgmname, &pid, 1, hang, r_exitcode);
+}
+
+/* See exechelp.h for a description.  */
+gpg_error_t
+gnupg_wait_processes (const char **pgmnames, pid_t *pids, size_t count,
+                      int hang, int *r_exitcodes)
+{
+  gpg_err_code_t ec = 0;
+  size_t i, left;
 
-  int i, status;
+  for (i = 0; i < count; i++)
+    {
+      if (r_exitcodes)
+        r_exitcodes[i] = -1;
 
-  if (exitcode)
-    *exitcode = -1;
+      if (pids[i] == (pid_t)(-1))
+        return my_error (GPG_ERR_INV_VALUE);
+    }
 
-  if (pid == (pid_t)(-1))
-    return gpg_error (GPG_ERR_INV_VALUE);
+  left = count;
+  while (left > 0)
+    {
+      pid_t pid;
+      int status;
 
-#ifdef USE_GNU_PTH
-  i = pth_waitpid ? pth_waitpid (pid, &status, 0) : waitpid (pid, &status, 0);
+#ifdef USE_NPTH
+      pid = npth_waitpid (-1, &status, hang ? 0 : WNOHANG);
 #else
-  while ( (i=waitpid (pid, &status, 0)) == -1 && errno == EINTR)
-    ;
+      while ((pid = waitpid (-1, &status, hang ? 0 : WNOHANG)) == (pid_t)(-1)
+             && errno == EINTR);
 #endif
-  if (i == (pid_t)(-1))
-    {
-      log_error (_("waiting for process %d to terminate failed: %s\n"),
-                 (int)pid, strerror (errno));
-      ec = gpg_err_code_from_errno (errno);
-    }
-  else if (WIFEXITED (status) && WEXITSTATUS (status) == 127)
-    {
-      log_error (_("error running `%s': probably not installed\n"), pgmname);
-      ec = GPG_ERR_CONFIGURATION;
-    }
-  else if (WIFEXITED (status) && WEXITSTATUS (status))
-    {
-      if (!exitcode)
-        log_error (_("error running `%s': exit status %d\n"), pgmname,
-                   WEXITSTATUS (status));
+
+      if (pid == (pid_t)(-1))
+        {
+          ec = gpg_err_code_from_errno (errno);
+          log_error (_("waiting for processes to terminate failed: %s\n"),
+                     strerror (errno));
+          break;
+        }
+      else if (!pid)
+        {
+          ec = GPG_ERR_TIMEOUT; /* Still running.  */
+          break;
+        }
       else
-        *exitcode = WEXITSTATUS (status);
-      ec = GPG_ERR_GENERAL;
-    }
-  else if (!WIFEXITED (status))
-    {
-      log_error (_("error running `%s': terminated\n"), pgmname);
-      ec = GPG_ERR_GENERAL;
-    }
-  else 
-    {
-      if (exitcode)
-        *exitcode = 0;
-      ec = 0;
+        {
+          for (i = 0; i < count; i++)
+            if (pid == pids[i])
+              break;
+
+          if (i == count)
+            /* No match, ignore this pid.  */
+            continue;
+
+          /* Process PIDS[i] died.  */
+          left -= 1;
+
+          if (WIFEXITED (status) && WEXITSTATUS (status) == 127)
+            {
+              log_error (_("error running '%s': probably not installed\n"),
+                         pgmnames[i]);
+              ec = GPG_ERR_CONFIGURATION;
+            }
+          else if (WIFEXITED (status) && WEXITSTATUS (status))
+            {
+              if (!r_exitcodes)
+                log_error (_("error running '%s': exit status %d\n"),
+                           pgmnames[i], WEXITSTATUS (status));
+              else
+                r_exitcodes[i] = WEXITSTATUS (status);
+              ec = GPG_ERR_GENERAL;
+            }
+          else if (!WIFEXITED (status))
+            {
+              log_error (_("error running '%s': terminated\n"), pgmnames[i]);
+              ec = GPG_ERR_GENERAL;
+            }
+          else
+            {
+              if (r_exitcodes)
+                r_exitcodes[i] = 0;
+            }
+        }
     }
 
   return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec);
 }
 
 
-/* Spawn a new process and immediatley detach from it.  The name of
+void
+gnupg_release_process (pid_t pid)
+{
+  (void)pid;
+}
+
+
+/* Spawn a new process and immediately detach from it.  The name of
    the program to exec is PGMNAME and its arguments are in ARGV (the
    programname is automatically passed as first argument).
    Environment strings in ENVP are set.  An error is returned if
@@ -482,20 +700,16 @@ gnupg_spawn_process_detached (const char *pgmname, const char *argv[],
   int i;
 
   if (getuid() != geteuid())
-    return gpg_error (GPG_ERR_BUG);
+    return my_error (GPG_ERR_BUG);
 
   if (access (pgmname, X_OK))
-    return gpg_error_from_syserror ();
+    return my_error_from_syserror ();
 
-#ifdef USE_GNU_PTH      
-  pid = pth_fork? pth_fork () : fork ();
-#else
   pid = fork ();
-#endif
   if (pid == (pid_t)(-1))
     {
       log_error (_("error forking process: %s\n"), strerror (errno));
-      return gpg_error_from_syserror ();
+      return my_error_from_syserror ();
     }
   if (!pid)
     {
@@ -514,12 +728,12 @@ gnupg_spawn_process_detached (const char *pgmname, const char *argv[],
       if (envp)
         for (i=0; envp[i]; i++)
           putenv (xstrdup (envp[i]));
-      
-      do_exec (pgmname, argv, -1, -1, -1, NULL);
+
+      do_exec (pgmname, argv, -1, -1, -1, NULL, NULL);
 
       /*NOTREACHED*/
     }
-  
+
   if (waitpid (pid, NULL, 0) == -1)
     log_error ("waitpid failed in gnupg_spawn_process_detached: %s",
                strerror (errno));
@@ -536,6 +750,6 @@ gnupg_kill_process (pid_t pid)
 {
   if (pid != (pid_t)(-1))
     {
-      kill (pid, SIGTERM); 
+      kill (pid, SIGTERM);
     }
 }