Re-implemented GPG's --passwd command and improved it.
[gnupg.git] / common / exechelp-w32.c
index ed026e2..e76c7d7 100644 (file)
@@ -94,43 +94,12 @@ get_max_fds (void)
 }
 
 
-/* Close all file descriptors starting with descriptor FIRST.  If
-   EXCEPT is not NULL, it is expected to be a list of file descriptors
-   which shall not be closed.  This list shall be sorted in ascending
-   order with the end marked by -1.  */
+/* Under Windows this is a dummy function.  */
 void
 close_all_fds (int first, int *except)
 {
-  int max_fd = get_max_fds ();
-  int fd, i, except_start;
-
-  if (except)
-    {
-      except_start = 0;
-      for (fd=first; fd < max_fd; fd++)
-        {
-          for (i=except_start; except[i] != -1; i++)
-            {
-              if (except[i] == fd)
-                {
-                  /* If we found the descriptor in the exception list
-                     we can start the next compare run at the next
-                     index because the exception list is ordered.  */
-                except_start = i + 1;
-                break;
-                }
-            }
-          if (except[i] == -1)
-            close (fd);
-        }
-    }
-  else
-    {
-      for (fd=first; fd < max_fd; fd++)
-        close (fd);
-    }
-
-  gpg_err_set_errno (0);
+  (void)first;
+  (void)except;
 }
 
 
@@ -257,7 +226,7 @@ build_w32_commandline (const char *pgmname, const char * const *argv,
 /* Create pipe where one end is inheritable: With an INHERIT_IDX of 0
    the read end is inheritable, with 1 the write end is inheritable.  */
 static int
-create_inheritable_pipe (int filedes[2], int inherit_idx)
+create_inheritable_pipe (HANDLE filedes[2], int inherit_idx)
 {
   HANDLE r, w, h;
   SECURITY_ATTRIBUTES sec_attr;
@@ -290,8 +259,8 @@ create_inheritable_pipe (int filedes[2], int inherit_idx)
       r = h;
     }
 
-  filedes[0] = handle_to_fd (r);
-  filedes[1] = handle_to_fd (w);
+  filedes[0] = r;
+  filedes[1] = w;
   return 0;
 }
 
@@ -315,27 +284,27 @@ static gpg_error_t
 do_create_pipe (int filedes[2], int inherit_idx)
 {
   gpg_error_t err = 0;
-  int fds[2];
+  HANDLE fds[2];
 
   filedes[0] = filedes[1] = -1;
   err = gpg_error (GPG_ERR_GENERAL);
   if (!create_inheritable_pipe (fds, inherit_idx))
     {
-      filedes[0] = _open_osfhandle (fds[0], 0);
+      filedes[0] = _open_osfhandle (handle_to_fd (fds[0]), 0);
       if (filedes[0] == -1)
         {
-          log_error ("failed to translate osfhandle %p\n", (void*)fds[0]);
-          CloseHandle (fd_to_handle (fds[1]));
+          log_error ("failed to translate osfhandle %p\n", fds[0]);
+          CloseHandle (fds[1]);
         }
       else 
         {
-          filedes[1] = _open_osfhandle (fds[1], 1);
+          filedes[1] = _open_osfhandle (handle_to_fd (fds[1]), 1);
           if (filedes[1] == -1)
             {
-              log_error ("failed to translate osfhandle %p\n", (void*)fds[1]);
+              log_error ("failed to translate osfhandle %p\n", fds[1]);
               close (filedes[0]);
               filedes[0] = -1;
-              CloseHandle (fd_to_handle (fds[1]));
+              CloseHandle (fds[1]);
             }
           else
             err = 0;
@@ -365,9 +334,12 @@ 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,
+                     gpg_err_source_t errsource,
                      void (*preexec)(void), unsigned int flags,
-                     estream_t *statusfile, pid_t *pid)
+                     estream_t infp,
+                     estream_t *r_outfp,
+                     estream_t *r_errfp,
+                     pid_t *pid)
 {
   gpg_error_t err;
   SECURITY_ATTRIBUTES sec_attr;
@@ -381,19 +353,103 @@ gnupg_spawn_process (const char *pgmname, const char *argv[],
   STARTUPINFO si;
   int cr_flags;
   char *cmdline;
-  int fd, fdout, rp[2];
+  HANDLE inhandle = INVALID_HANDLE_VALUE;
+  HANDLE outpipe[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
+  HANDLE errpipe[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
+  estream_t outfp = NULL;
+  estream_t errfp = NULL;
+  HANDLE nullhd[3] = {INVALID_HANDLE_VALUE,
+                      INVALID_HANDLE_VALUE,
+                      INVALID_HANDLE_VALUE};
+  int i;
+  es_syshd_t syshd;
 
-  (void)preexec;
+  if (r_outfp)
+    *r_outfp = NULL;
+  if (r_errfp)
+    *r_errfp = NULL;
+  *pid = (pid_t)(-1); /* Always required.  */
+  
+  if (infp)
+    {
+      es_fflush (infp);
+      es_rewind (infp);
+      es_syshd (infp, &syshd);
+      switch (syshd.type)
+        {
+        case ES_SYSHD_FD:
+          inhandle = (HANDLE)_get_osfhandle (syshd.u.fd);
+          break;
+        case ES_SYSHD_SOCK:
+          inhandle = (HANDLE)_get_osfhandle (syshd.u.sock);
+          break;
+        case ES_SYSHD_HANDLE:
+          inhandle = syshd.u.handle;
+          break;
+        default:
+          inhandle = INVALID_HANDLE_VALUE;
+          break;
+        }      
+      if (inhandle == INVALID_HANDLE_VALUE)
+        return gpg_err_make (errsource, GPG_ERR_INV_VALUE);
+      /* FIXME: In case we can't get a system handle (e.g. due to
+         es_fopencookie we should create a piper and a feeder
+         thread.  */
+    }
 
-  /* Setup return values.  */
-  *statusfile = NULL;
-  *pid = (pid_t)(-1);
-  es_fflush (infile);
-  es_rewind (infile);
-  fd = _get_osfhandle (es_fileno (infile));
-  fdout = _get_osfhandle (es_fileno (outfile));
-  if (fd == -1 || fdout == -1)
-    log_fatal ("no file descriptor for file passed to gnupg_spawn_process\n");
+  if (r_outfp)
+    {
+      if (create_inheritable_pipe (outpipe, 1))
+        {
+          err = gpg_err_make (errsource, GPG_ERR_GENERAL);
+          log_error (_("error creating a pipe: %s\n"), gpg_strerror (err));
+          return err;
+        }
+
+      syshd.type = ES_SYSHD_HANDLE;
+      syshd.u.handle = outpipe[0];
+      outfp = es_sysopen (&syshd, "r");
+      if (!outfp)
+        {
+          err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
+          log_error (_("error creating a stream for a pipe: %s\n"),
+                     gpg_strerror (err));
+          CloseHandle (outpipe[0]);
+          CloseHandle (outpipe[1]);
+          outpipe[0] = outpipe[1] = INVALID_HANDLE_VALUE;
+          return err;
+        }
+    }
+
+  if (r_errfp)
+    {
+      if (create_inheritable_pipe (errpipe, 1))
+        {
+          err = gpg_err_make (errsource, GPG_ERR_GENERAL);
+          log_error (_("error creating a pipe: %s\n"), gpg_strerror (err));
+          return err;
+        }
+
+      syshd.type = ES_SYSHD_HANDLE;
+      syshd.u.handle = errpipe[0];
+      errfp = es_sysopen (&syshd, "r");
+      if (!errfp)
+        {
+          err = gpg_err_make (errsource, gpg_err_code_from_syserror ());
+          log_error (_("error creating a stream for a pipe: %s\n"),
+                     gpg_strerror (err));
+          CloseHandle (errpipe[0]);
+          CloseHandle (errpipe[1]);
+          errpipe[0] = errpipe[1] = INVALID_HANDLE_VALUE;
+          if (outfp)
+            es_fclose (outfp);
+          else if (outpipe[0] != INVALID_HANDLE_VALUE)
+            CloseHandle (outpipe[0]);
+          if (outpipe[1] != INVALID_HANDLE_VALUE)
+            CloseHandle (outpipe[1]);
+          return err;
+        }
+    }
 
   /* Prepare security attributes.  */
   memset (&sec_attr, 0, sizeof sec_attr );
@@ -405,24 +461,24 @@ gnupg_spawn_process (const char *pgmname, const char *argv[],
   if (err)
     return err; 
 
-  /* Create a pipe.  */
-  if (create_inheritable_pipe (rp, 1))
-    {
-      err = gpg_error (GPG_ERR_GENERAL);
-      log_error (_("error creating a pipe: %s\n"), gpg_strerror (err));
-      xfree (cmdline);
-      return err;
-    }
-  
+  if (inhandle != INVALID_HANDLE_VALUE)
+    nullhd[0] = w32_open_null (0);
+  if (outpipe[1] != INVALID_HANDLE_VALUE)
+    nullhd[1] = w32_open_null (0);
+  if (errpipe[1] != INVALID_HANDLE_VALUE)
+    nullhd[2] = w32_open_null (0);
+
   /* Start the process.  Note that we can't run the PREEXEC function
-     because this would change our own environment. */
+     because this might change our own environment. */
+  (void)preexec;
+
   memset (&si, 0, sizeof si);
   si.cb = sizeof (si);
   si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
   si.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_MINIMIZE;
-  si.hStdInput  = fd_to_handle (fd);
-  si.hStdOutput = fd_to_handle (fdout);
-  si.hStdError  = fd_to_handle (rp[1]);
+  si.hStdInput  =   inhandle == INVALID_HANDLE_VALUE? nullhd[0] : inhandle;
+  si.hStdOutput = outpipe[1] == INVALID_HANDLE_VALUE? nullhd[1] : outpipe[1];
+  si.hStdError  = errpipe[1] == INVALID_HANDLE_VALUE? nullhd[2] : errpipe[1];
 
   cr_flags = (CREATE_DEFAULT_ERROR_MODE
               | ((flags & 128)? DETACHED_PROCESS : 0)
@@ -443,24 +499,43 @@ gnupg_spawn_process (const char *pgmname, const char *argv[],
     {
       log_error ("CreateProcess failed: %s\n", w32_strerror (-1));
       xfree (cmdline);
-      CloseHandle (fd_to_handle (rp[0]));
-      CloseHandle (fd_to_handle (rp[1]));
-      return gpg_error (GPG_ERR_GENERAL);
+      if (outfp)
+        es_fclose (outfp);
+      else if (outpipe[0] != INVALID_HANDLE_VALUE)
+        CloseHandle (outpipe[0]);
+      if (outpipe[1] != INVALID_HANDLE_VALUE)
+        CloseHandle (outpipe[1]);
+      if (errfp)
+        es_fclose (errfp);
+      else if (errpipe[0] != INVALID_HANDLE_VALUE)
+        CloseHandle (errpipe[0]);
+      if (errpipe[1] != INVALID_HANDLE_VALUE)
+        CloseHandle (errpipe[1]);
+      return gpg_err_make (errsource, GPG_ERR_GENERAL);
     }
   xfree (cmdline);
   cmdline = NULL;
 
-  /* Close the other end of the pipe.  */
-  CloseHandle (fd_to_handle (rp[1]));
-  
-/*   log_debug ("CreateProcess ready: hProcess=%p hThread=%p" */
-/*              " dwProcessID=%d dwThreadId=%d\n", */
-/*              pi.hProcess, pi.hThread, */
-/*              (int) pi.dwProcessId, (int) pi.dwThreadId); */
+  /* Close the inherited handles to /dev/null.  */
+  for (i=0; i < DIM (nullhd); i++)
+    if (nullhd[i] != INVALID_HANDLE_VALUE)
+      CloseHandle (nullhd[i]);
+
+  /* Close the inherited ends of the pipes.  */
+  if (outpipe[1] != INVALID_HANDLE_VALUE)
+    CloseHandle (outpipe[1]);
+  if (errpipe[1] != INVALID_HANDLE_VALUE)
+    CloseHandle (errpipe[1]);
   
+  /* log_debug ("CreateProcess ready: hProcess=%p hThread=%p" */
+  /*            " dwProcessID=%d dwThreadId=%d\n", */
+  /*            pi.hProcess, pi.hThread, */
+  /*            (int) pi.dwProcessId, (int) pi.dwThreadId); */
+  /* log_debug ("                     outfp=%p errfp=%p\n", outfp, errfp); */
+
   /* Fixme: For unknown reasons AllowSetForegroundWindow returns an
-     invalid argument error if we pass the correct processID to
-     it.  As a workaround we use -1 (ASFW_ANY).  */
+     invalid argument error if we pass it the correct processID.  As a
+     workaround we use -1 (ASFW_ANY).  */
   if ( (flags & 64) )
     gnupg_allow_set_foregound_window ((pid_t)(-1)/*pi.dwProcessId*/);
 
@@ -468,22 +543,10 @@ gnupg_spawn_process (const char *pgmname, const char *argv[],
   ResumeThread (pi.hThread);
   CloseHandle (pi.hThread); 
 
-  {
-    int x;
-
-    x = _open_osfhandle (rp[0], 0);
-    if (x == -1)
-      log_error ("failed to translate osfhandle %p\n", (void*)rp[0] );
-    else 
-      *statusfile = es_fdopen (x, "r");
-  }
-  if (!*statusfile)
-    {
-      err = gpg_error_from_syserror ();
-      log_error (_("can't fdopen pipe for reading: %s\n"), gpg_strerror (err));
-      CloseHandle (pi.hProcess);
-      return err;
-    }
+  if (r_outfp)
+    *r_outfp = outfp;
+  if (r_errfp)
+    *r_errfp = errfp;
 
   *pid = handle_to_pid (pi.hProcess);
   return 0;
@@ -578,22 +641,17 @@ 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. */
+/* 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;
   HANDLE proc = fd_to_handle (pid);
   int code;
   DWORD exc;
 
-  if (exitcode)
-    *exitcode = -1;
+  if (r_exitcode)
+    *r_exitcode = -1;
 
   if (pid == (pid_t)(-1))
     return gpg_error (GPG_ERR_INV_VALUE);
@@ -601,50 +659,66 @@ gnupg_wait_process (const char *pgmname, pid_t pid, int *exitcode)
   /* FIXME: We should do a pth_waitpid here.  However this has not yet
      been implemented.  A special W32 pth system call would even be
      better.  */
-  code = WaitForSingleObject (proc, INFINITE);
+  code = WaitForSingleObject (proc, hang? INFINITE : 0);
   switch (code) 
     {
-      case WAIT_FAILED:
-        log_error (_("waiting for process %d to terminate failed: %s\n"),
-                   (int)pid, w32_strerror (-1));
-        ec = GPG_ERR_GENERAL;
-        break;
-
-      case WAIT_OBJECT_0:
-        if (!GetExitCodeProcess (proc, &exc))
-          {
-            log_error (_("error getting exit code of process %d: %s\n"),
-                         (int)pid, w32_strerror (-1) );
-            ec = GPG_ERR_GENERAL;
-          }
-        else if (exc)
-          {
-            log_error (_("error running `%s': exit status %d\n"),
-                       pgmname, (int)exc );
-            if (exitcode)
-              *exitcode = (int)exc;
-            ec = GPG_ERR_GENERAL;
-          }
-        else
-          {
-            if (exitcode)
-              *exitcode = 0;
-            ec = 0;
-          }
-        CloseHandle (proc);
-        break;
-
-      default:
-        log_error ("WaitForSingleObject returned unexpected "
-                   "code %d for pid %d\n", code, (int)pid );
-        ec = GPG_ERR_GENERAL;
-        break;
+    case WAIT_TIMEOUT:
+      ec = GPG_ERR_TIMEOUT;
+      break;
+
+    case WAIT_FAILED:
+      log_error (_("waiting for process %d to terminate failed: %s\n"),
+                 (int)pid, w32_strerror (-1));
+      ec = GPG_ERR_GENERAL;
+      break;
+
+    case WAIT_OBJECT_0:
+      if (!GetExitCodeProcess (proc, &exc))
+        {
+          log_error (_("error getting exit code of process %d: %s\n"),
+                     (int)pid, w32_strerror (-1) );
+          ec = GPG_ERR_GENERAL;
+        }
+      else if (exc)
+        {
+          log_error (_("error running `%s': exit status %d\n"),
+                     pgmname, (int)exc );
+          if (r_exitcode)
+            *r_exitcode = (int)exc;
+          ec = GPG_ERR_GENERAL;
+        }
+      else
+        {
+          if (r_exitcode)
+            *r_exitcode = 0;
+          ec = 0;
+        }
+      break;
+      
+    default:
+      log_error ("WaitForSingleObject returned unexpected "
+                 "code %d for pid %d\n", code, (int)pid );
+      ec = GPG_ERR_GENERAL;
+      break;
     }
-
+  
   return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec);
 }
 
 
+
+void
+gnupg_release_process (pid_t pid)
+{
+  if (pid != (pid_t)INVALID_HANDLE_VALUE)
+    {
+      HANDLE process = (HANDLE)pid;
+      
+      CloseHandle (process);
+    }
+}
+
+
 /* Spawn a new process and immediatley 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).