aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/gpgrt-int.h168
-rw-r--r--src/spawn-posix.c887
-rw-r--r--src/spawn-w32.c925
3 files changed, 1980 insertions, 0 deletions
diff --git a/src/gpgrt-int.h b/src/gpgrt-int.h
index dc1305e..e144ff1 100644
--- a/src/gpgrt-int.h
+++ b/src/gpgrt-int.h
@@ -550,6 +550,174 @@ int _gpgrt_logv_internal (int level, int ignore_arg_ptr,
const char *prefmt, const char *fmt,
va_list arg_ptr);
+
+/*
+ * Local prototypes for the spawn functions.
+ */
+
+/* Return the maximum number of currently allowed file descriptors.
+ * Only useful on POSIX systems. */
+/* int 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 are not to close. This list shall be sorted in ascending
+ * order with its end marked by -1. */
+/* void 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 function right at startup even before libgcrypt has
+ * been initialized. Returns NULL on error and sets ERRNO accordingly. */
+/* int *get_all_open_fds (void); */
+
+
+/* Portable function to create a pipe. Under Windows the write 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_inbound_pipe (int filedes[2],
+ estream_t *r_fp, int nonblock);
+
+/* 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);
+
+/* Portable function to create a pipe. Under Windows both ends are
+ * inheritable. */
+gpg_error_t gnupg_create_pipe (int filedes[2]);
+
+
+#define GNUPG_SPAWN_NONBLOCK 16
+#define GNUPG_SPAWN_RUN_ASFW 64
+#define GNUPG_SPAWN_DETACHED 128
+
+
+/* Fork and exec the program PGMNAME.
+ *
+ * If R_INFP is NULL connect stdin of the new process to /dev/null; if
+ * it is not NULL store the address of a pointer to a new estream
+ * there. If R_OUTFP is NULL connect stdout of the new process to
+ * /dev/null; if it is not NULL store the address of a pointer to a
+ * new estream there. If R_ERRFP is NULL connect stderr of the new
+ * process to /dev/null; if it is not NULL store the address of a
+ * pointer to a new estream there. On success the pid of the new
+ * process is stored at PID. On error -1 is stored at PID and if
+ * R_OUTFP or R_ERRFP are not NULL, NULL is stored there.
+ *
+ * The arguments for the process are expected in the NULL terminated
+ * array ARGV. The program name itself should not be included there.
+ * If PREEXEC is not NULL, the given function will be called right
+ * before the exec.
+ *
+ * IF EXCEPT is not NULL, it is expected to be an ordered list of file
+ * descriptors, terminated by an entry with the value (-1). These
+ * file descriptors won't be closed before spawning a new program.
+ *
+ * Returns 0 on success or an error code. Calling gnupg_wait_process
+ * and gnupg_release_process is required if the function succeeded.
+ *
+ * FLAGS is a bit vector:
+ *
+ * GNUPG_SPAWN_NONBLOCK
+ * If set the two output streams are created in non-blocking
+ * mode and the input stream is switched to non-blocking mode.
+ * This is merely a convenience feature because the caller
+ * could do the same with gpgrt_set_nonblock. Does not yet
+ * work for Windows.
+ *
+ * GNUPG_SPAWN_DETACHED
+ * If set the process will be started as a background process.
+ * This flag is only useful under W32 (but not W32CE) systems,
+ * so that no new console is created and pops up a console
+ * window when starting the server. Does not work on W32CE.
+ *
+ * GNUPG_SPAWN_RUN_ASFW
+ * On W32 (but not on W32CE) run AllowSetForegroundWindow for
+ * the child. Note that due to unknown problems this actually
+ * allows SetForegroundWindow for all children of this process.
+ */
+gpg_error_t
+gnupg_spawn_process (const char *pgmname, const char *argv[],
+ int *execpt, void (*preexec)(void), unsigned int flags,
+ estream_t *r_infp,
+ estream_t *r_outfp,
+ estream_t *r_errfp,
+ pid_t *pid);
+
+
+/* Simplified version of gnupg_spawn_process. This function forks and
+ * then execs PGMNAME, while connecting INFD to stdin, OUTFD to stdout
+ * and ERRFD to stderr (any of them may be -1 to connect them to
+ * /dev/null). The arguments for the process are expected in the NULL
+ * terminated array ARGV. The program name itself should not be
+ * included there. Calling gnupg_wait_process and
+ * gnupg_release_process is required. Returns 0 on success or an
+ * error code. */
+gpg_error_t gnupg_spawn_process_fd (const char *pgmname,
+ const char *argv[],
+ int infd, int outfd, int errfd,
+ pid_t *pid);
+
+
+/* If HANG is true, waits for the process identified by PID to exit;
+ * if HANG is false, checks whether the process has terminated.
+ * PGMNAME should be the same as supplied to the spawn function and is
+ * only used for diagnostics. Return values:
+ *
+ * 0
+ * The process exited successful. 0 is stored at R_EXITCODE.
+ *
+ * GPG_ERR_GENERAL
+ * The process exited without success. The exit code of process
+ * is then stored at R_EXITCODE. An exit code of -1 indicates
+ * that the process terminated abnormally (e.g. due to a signal).
+ *
+ * GPG_ERR_TIMEOUT
+ * The process is still running (returned only if HANG is false).
+ *
+ * GPG_ERR_INV_VALUE
+ * An invalid PID has been specified.
+ *
+ * Other error codes may be returned as well. Unless otherwise noted,
+ * -1 will be stored at R_EXITCODE. R_EXITCODE may be passed as NULL
+ * if the exit code is not required (in that case an error message will
+ * be printed). Note that under Windows PID is not the process id but
+ * the handle of the process. */
+gpg_error_t gnupg_wait_process (const char *pgmname, pid_t pid, int hang,
+ int *r_exitcode);
+
+/* Like gnupg_wait_process, but for COUNT processes. */
+gpg_error_t gnupg_wait_processes (const char **pgmnames, pid_t *pids,
+ size_t count, int hang, int *r_exitcodes);
+
+
+/* Kill a process; that is send an appropriate signal to the process.
+ * gnupg_wait_process must be called to actually remove the process
+ * from the system. An invalid PID is ignored. */
+void gnupg_kill_process (pid_t pid);
+
+/* Release the process identified by PID. This function is actually
+ * only required for Windows but it does not harm to always call it.
+ * It is a nop if PID is invalid. */
+void gnupg_release_process (pid_t 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
+ * pgmname is not executable; to make this work it is necessary to
+ * provide an absolute file name. */
+gpg_error_t gnupg_spawn_process_detached (const char *pgmname,
+ const char *argv[],
+ const char *envp[] );
+
+
/*
diff --git a/src/spawn-posix.c b/src/spawn-posix.c
new file mode 100644
index 0000000..9e7800f
--- /dev/null
+++ b/src/spawn-posix.c
@@ -0,0 +1,887 @@
+/* exechelp.c - Fork and exec helpers for POSIX
+ * Copyright (C) 2004, 2007-2009, 2010 Free Software Foundation, Inc.
+ * Copyright (C) 2004, 2006-2012, 2014-2017 g10 Code GmbH
+ *
+ * This file is part of Libgpg-error.
+ *
+ * Libgpg-error 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.
+ *
+ * Libgpg-error 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 <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * This file was originally a part of GnuPG.
+ */
+
+#include <config.h>
+
+#if defined(HAVE_W32_SYSTEM) || defined (HAVE_W32CE_SYSTEM)
+#error This code is only used on POSIX
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdint.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#ifdef HAVE_SIGNAL_H
+# include <signal.h>
+#endif
+#include <unistd.h>
+#include <fcntl.h>
+
+#ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */
+#undef HAVE_NPTH
+#undef USE_NPTH
+#endif
+
+#ifdef HAVE_NPTH
+#include <npth.h>
+#endif
+#include <sys/wait.h>
+
+#ifdef HAVE_GETRLIMIT
+#include <sys/time.h>
+#include <sys/resource.h>
+#endif /*HAVE_GETRLIMIT*/
+
+#ifdef HAVE_STAT
+# 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"
+
+
+/* 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
+ descriptors. Only useful on POSIX systems but returns a value on
+ other systems too. */
+int
+get_max_fds (void)
+{
+ int max_fds = -1;
+#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;
+# endif
+
+# ifdef RLIMIT_OFILE
+ if (max_fds == -1 && !getrlimit (RLIMIT_OFILE, &rl))
+ max_fds = rl.rlim_max;
+
+# endif
+#endif /*HAVE_GETRLIMIT*/
+
+#ifdef _SC_OPEN_MAX
+ if (max_fds == -1)
+ {
+ long int scres = sysconf (_SC_OPEN_MAX);
+ if (scres >= 0)
+ max_fds = scres;
+ }
+#endif
+
+#ifdef _POSIX_OPEN_MAX
+ if (max_fds == -1)
+ max_fds = _POSIX_OPEN_MAX;
+#endif
+
+#ifdef OPEN_MAX
+ if (max_fds == -1)
+ max_fds = OPEN_MAX;
+#endif
+
+ 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;
+}
+
+
+/* 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. */
+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);
+}
+
+
+/* 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 function right at startup even before libgcrypt has
+ been initialized. Returns NULL on error and sets ERRNO
+ accordingly. */
+int *
+get_all_open_fds (void)
+{
+ int *array;
+ size_t narray;
+ int fd, max_fd, idx;
+#ifndef HAVE_STAT
+ array = calloc (1, sizeof *array);
+ if (array)
+ array[0] = -1;
+#else /*HAVE_STAT*/
+ struct stat statbuf;
+
+ max_fd = get_max_fds ();
+ narray = 32; /* If you change this change also t-exechelp.c. */
+ 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))
+ {
+ if (idx+1 >= narray)
+ {
+ int *tmp;
+
+ narray += (narray < 256)? 32:256;
+ tmp = realloc (array, narray * sizeof *array);
+ if (!tmp)
+ {
+ free (array);
+ return NULL;
+ }
+ array = tmp;
+ }
+ array[idx++] = fd;
+ }
+ array[idx] = -1;
+#endif /*HAVE_STAT*/
+ return array;
+}
+
+
+/* The exec core used right after the fork. This will never return. */
+static void
+do_exec (const char *pgmname, const char *argv[],
+ int fd_in, int fd_out, int fd_err,
+ int *except, void (*preexec)(void) )
+{
+ char **arg_list;
+ int i, j;
+ int fds[3];
+
+ fds[0] = fd_in;
+ fds[1] = fd_out;
+ fds[2] = fd_err;
+
+ /* Create the command line argument array. */
+ i = 0;
+ if (argv)
+ while (argv[i])
+ i++;
+ arg_list = xcalloc (i+2, sizeof *arg_list);
+ arg_list[0] = strrchr (pgmname, '/');
+ if (arg_list[0])
+ arg_list[0]++;
+ else
+ arg_list[0] = xstrdup (pgmname);
+ if (argv)
+ for (i=0,j=1; argv[i]; i++, j++)
+ arg_list[j] = (char*)argv[i];
+
+ /* Assign /dev/null to unused FDs. */
+ for (i=0; i <= 2; i++)
+ {
+ if (fds[i] == -1 )
+ {
+ fds[i] = open ("/dev/null", i? O_WRONLY : O_RDONLY);
+ if (fds[i] == -1)
+ log_fatal ("failed to open '%s': %s\n",
+ "/dev/null", strerror (errno));
+ }
+ }
+
+ /* Connect the standard files. */
+ for (i=0; i <= 2; i++)
+ {
+ if (fds[i] != i && dup2 (fds[i], i) == -1)
+ log_fatal ("dup2 std%s failed: %s\n",
+ i==0?"in":i==1?"out":"err", strerror (errno));
+ }
+
+ /* Close all other files. */
+ close_all_fds (3, except);
+
+ if (preexec)
+ preexec ();
+ execv (pgmname, arg_list);
+ /* No way to print anything, as we have closed all streams. */
+ _exit (127);
+}
+
+
+static gpg_error_t
+do_create_pipe (int filedes[2])
+{
+ gpg_error_t err = 0;
+
+ if (pipe (filedes) == -1)
+ {
+ 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. 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], estream_t *r_fp, int nonblock)
+{
+ 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_pipe (int filedes[2])
+{
+ return do_create_pipe (filedes);
+}
+
+
+/* Fork and exec the PGMNAME, see exechelp.h for details. */
+gpg_error_t
+gnupg_spawn_process (const char *pgmname, const char *argv[],
+ 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 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;
+ }
+
+ 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 (r_errfp)
+ {
+ 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;
+ }
+ }
+
+
+ *pid = fork ();
+ if (*pid == (pid_t)(-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);
+ es_fclose (infp);
+ es_fclose (outfp);
+ es_fclose (errfp);
+ do_exec (pgmname, argv, inpipe[0], outpipe[1], errpipe[1],
+ except, preexec);
+ /*NOTREACHED*/
+ }
+
+ /* 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;
+}
+
+
+
+/* Simplified version of gnupg_spawn_process. This function forks and
+ then execs PGMNAME, while connecting INFD to stdin, OUTFD to stdout
+ and ERRFD to stderr (any of them may be -1 to connect them to
+ /dev/null). The arguments for the process are expected in the NULL
+ terminated array ARGV. The program name itself should not be
+ included there. Calling gnupg_wait_process is required.
+
+ Returns 0 on success or an error code. */
+gpg_error_t
+gnupg_spawn_process_fd (const char *pgmname, const char *argv[],
+ int infd, int outfd, int errfd, pid_t *pid)
+{
+ gpg_error_t err;
+
+ *pid = fork ();
+ if (*pid == (pid_t)(-1))
+ {
+ 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, NULL);
+ /*NOTREACHED*/
+ }
+
+ return 0;
+}
+
+
+
+
+/* Waiting for child processes.
+
+ waitpid(2) may return information about terminated children that we
+ did not yet request, and there is no portable way to wait for a
+ specific set of children.
+
+ As a workaround, we store the results of children for later use.
+
+ XXX: This assumes that PIDs are not reused too quickly. */
+
+struct terminated_child
+{
+ pid_t pid;
+ int exitcode;
+ struct terminated_child *next;
+};
+
+struct terminated_child *terminated_children;
+
+
+static gpg_error_t
+store_result (pid_t pid, int exitcode)
+{
+ struct terminated_child *c;
+
+ c = xtrymalloc (sizeof *c);
+ if (c == NULL)
+ return gpg_err_code_from_syserror ();
+
+ c->pid = pid;
+ c->exitcode = exitcode;
+ c->next = terminated_children;
+ terminated_children = c;
+
+ return 0;
+}
+
+
+static int
+get_result (pid_t pid, int *r_exitcode)
+{
+ struct terminated_child *c, **prevp;
+
+ for (prevp = &terminated_children, c = terminated_children;
+ c;
+ prevp = &c->next, c = c->next)
+ if (c->pid == pid)
+ {
+ *prevp = c->next;
+ *r_exitcode = c->exitcode;
+ xfree (c);
+ return 1;
+ }
+
+ return 0;
+}
+
+
+/* See exechelp.h for a description. */
+gpg_error_t
+gnupg_wait_process (const char *pgmname, pid_t pid, int hang, int *r_exitcode)
+{
+ gpg_err_code_t ec;
+ int i, status;
+
+ if (r_exitcode)
+ *r_exitcode = -1;
+
+ if (pid == (pid_t)(-1))
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+#ifdef USE_NPTH
+ i = npth_waitpid (pid, &status, hang? 0:WNOHANG);
+#else
+ while ((i=waitpid (pid, &status, hang? 0:WNOHANG)) == (pid_t)(-1)
+ && errno == EINTR);
+#endif
+
+ if (i == (pid_t)(-1))
+ {
+ ec = gpg_err_code_from_errno (errno);
+ log_error (_("waiting for process %d to terminate failed: %s\n"),
+ (int)pid, strerror (errno));
+ }
+ else if (!i)
+ {
+ ec = GPG_ERR_TIMEOUT; /* Still running. */
+ }
+ 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 (!r_exitcode)
+ log_error (_("error running '%s': exit status %d\n"), pgmname,
+ WEXITSTATUS (status));
+ else
+ *r_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 (r_exitcode)
+ *r_exitcode = 0;
+ ec = 0;
+ }
+
+ return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec);
+}
+
+/* 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 *dummy = NULL;
+
+ if (r_exitcodes == NULL)
+ {
+ dummy = r_exitcodes = xtrymalloc (sizeof *r_exitcodes * count);
+ if (dummy == NULL)
+ return gpg_err_code_from_syserror ();
+ }
+
+ for (i = 0, left = count; i < count; i++)
+ {
+ int status = -1;
+
+ if (pids[i] == (pid_t)(-1))
+ return my_error (GPG_ERR_INV_VALUE);
+
+ /* See if there was a previously stored result for this pid. */
+ if (get_result (pids[i], &status))
+ left -= 1;
+
+ r_exitcodes[i] = status;
+ }
+
+ while (left > 0)
+ {
+ pid_t pid;
+ int status;
+
+#ifdef USE_NPTH
+ pid = npth_waitpid (-1, &status, hang ? 0 : WNOHANG);
+#else
+ while ((pid = waitpid (-1, &status, hang ? 0 : WNOHANG)) == (pid_t)(-1)
+ && errno == EINTR);
+#endif
+
+ 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
+ {
+ for (i = 0; i < count; i++)
+ if (pid == pids[i])
+ break;
+
+ if (i == count)
+ {
+ /* No match, store this result. */
+ ec = store_result (pid, status);
+ if (ec)
+ break;
+ continue;
+ }
+
+ /* Process PIDS[i] died. */
+ if (r_exitcodes[i] != (pid_t) -1)
+ {
+ log_error ("PID %d was reused", pid);
+ ec = GPG_ERR_GENERAL;
+ break;
+ }
+
+ left -= 1;
+ r_exitcodes[i] = status;
+ }
+ }
+
+ for (i = 0; i < count; i++)
+ {
+ if (r_exitcodes[i] == -1)
+ continue;
+
+ if (WIFEXITED (r_exitcodes[i]) && WEXITSTATUS (r_exitcodes[i]) == 127)
+ {
+ log_error (_("error running '%s': probably not installed\n"),
+ pgmnames[i]);
+ ec = GPG_ERR_CONFIGURATION;
+ }
+ else if (WIFEXITED (r_exitcodes[i]) && WEXITSTATUS (r_exitcodes[i]))
+ {
+ if (dummy)
+ log_error (_("error running '%s': exit status %d\n"),
+ pgmnames[i], WEXITSTATUS (r_exitcodes[i]));
+ else
+ r_exitcodes[i] = WEXITSTATUS (r_exitcodes[i]);
+ ec = GPG_ERR_GENERAL;
+ }
+ else if (!WIFEXITED (r_exitcodes[i]))
+ {
+ log_error (_("error running '%s': terminated\n"), pgmnames[i]);
+ ec = GPG_ERR_GENERAL;
+ }
+ }
+
+ xfree (dummy);
+ return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec);
+}
+
+
+
+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
+ pgmname is not executable; to make this work it is necessary to
+ provide an absolute file name. All standard file descriptors are
+ connected to /dev/null. */
+gpg_error_t
+gnupg_spawn_process_detached (const char *pgmname, const char *argv[],
+ const char *envp[] )
+{
+ pid_t pid;
+ int i;
+
+ if (getuid() != geteuid())
+ return my_error (GPG_ERR_BUG);
+
+ if (access (pgmname, X_OK))
+ return my_error_from_syserror ();
+
+ pid = fork ();
+ if (pid == (pid_t)(-1))
+ {
+ log_error (_("error forking process: %s\n"), strerror (errno));
+ return my_error_from_syserror ();
+ }
+ if (!pid)
+ {
+ pid_t pid2;
+
+ gcry_control (GCRYCTL_TERM_SECMEM);
+ if (setsid() == -1 || chdir ("/"))
+ _exit (1);
+
+ pid2 = fork (); /* Double fork to let init take over the new child. */
+ if (pid2 == (pid_t)(-1))
+ _exit (1);
+ if (pid2)
+ _exit (0); /* Let the parent exit immediately. */
+
+ if (envp)
+ for (i=0; envp[i]; i++)
+ putenv (xstrdup (envp[i]));
+
+ 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));
+
+ return 0;
+}
+
+
+/* Kill a process; that is send an appropriate signal to the process.
+ gnupg_wait_process must be called to actually remove the process
+ from the system. An invalid PID is ignored. */
+void
+gnupg_kill_process (pid_t pid)
+{
+ if (pid != (pid_t)(-1))
+ {
+ kill (pid, SIGTERM);
+ }
+}
diff --git a/src/spawn-w32.c b/src/spawn-w32.c
new file mode 100644
index 0000000..0e4201f
--- /dev/null
+++ b/src/spawn-w32.c
@@ -0,0 +1,925 @@
+/* spawn-w32.c - Fork and exec helpers for W32.
+ * Copyright (C) 2004, 2007-2009, 2010 Free Software Foundation, Inc.
+ * Copyright (C) 2004, 2006-2012, 2014-2017 g10 Code GmbH
+ *
+ * This file is part of Libgpg-error.
+ *
+ * Libgpg-error 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.
+ *
+ * Libgpg-error 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 <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: LGPL-2.1+
+ *
+ * This file was originally a part of GnuPG.
+ */
+
+#include <config.h>
+
+#if !defined(HAVE_W32_SYSTEM) || defined (HAVE_W32CE_SYSTEM)
+#error This code is only used on W32.
+#endif
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <assert.h>
+#ifdef HAVE_SIGNAL_H
+# include <signal.h>
+#endif
+#include <unistd.h>
+#include <fcntl.h>
+
+#ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */
+#undef HAVE_NPTH
+#undef USE_NPTH
+#endif
+
+#ifdef HAVE_NPTH
+#include <npth.h>
+#endif
+
+#ifdef HAVE_STAT
+# include <sys/stat.h>
+#endif
+
+
+#include "util.h"
+#include "i18n.h"
+#include "sysutils.h"
+#include "exechelp.h"
+
+/* Define to 1 do enable debugging. */
+#define DEBUG_W32_SPAWN 0
+
+
+/* It seems Vista doesn't grok X_OK and so fails access() tests.
+ Previous versions interpreted X_OK as F_OK anyway, so we'll just
+ use F_OK directly. */
+#undef X_OK
+#define X_OK F_OK
+
+/* We assume that a HANDLE can be represented by an int which should
+ be true for all i386 systems (HANDLE is defined as void *) and
+ these are the only systems for which Windows is available. Further
+ we assume that -1 denotes an invalid handle. */
+# define fd_to_handle(a) ((HANDLE)(a))
+# define handle_to_fd(a) ((int)(a))
+# define pid_to_handle(a) ((HANDLE)(a))
+# define handle_to_pid(a) ((int)(a))
+
+
+/* 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
+ descriptors. Only useful on POSIX systems but returns a value on
+ other systems too. */
+int
+get_max_fds (void)
+{
+ int max_fds = -1;
+
+#ifdef OPEN_MAX
+ if (max_fds == -1)
+ max_fds = OPEN_MAX;
+#endif
+
+ if (max_fds == -1)
+ max_fds = 256; /* Arbitrary limit. */
+
+ return max_fds;
+}
+
+
+/* Under Windows this is a dummy function. */
+void
+close_all_fds (int first, int *except)
+{
+ (void)first;
+ (void)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 function right at startup even before libgcrypt has
+ * been initialized. Returns NULL on error and sets ERRNO
+ * accordingly. Note that fstat prints a warning to DebugView for all
+ * invalid fds which is a bit annoying. We actually do not need this
+ * function in real code (close_all_fds is a dummy anyway) but we keep
+ * it for use by t-exechelp.c. */
+int *
+get_all_open_fds (void)
+{
+ int *array;
+ size_t narray;
+ int fd, max_fd, idx;
+#ifndef HAVE_STAT
+ array = calloc (1, sizeof *array);
+ if (array)
+ array[0] = -1;
+#else /*HAVE_STAT*/
+ struct stat statbuf;
+
+ max_fd = get_max_fds ();
+ narray = 32; /* If you change this change also t-exechelp.c. */
+ 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))
+ {
+ if (idx+1 >= narray)
+ {
+ int *tmp;
+
+ narray += (narray < 256)? 32:256;
+ tmp = realloc (array, narray * sizeof *array);
+ if (!tmp)
+ {
+ free (array);
+ return NULL;
+ }
+ array = tmp;
+ }
+ array[idx++] = fd;
+ }
+ array[idx] = -1;
+#endif /*HAVE_STAT*/
+ return array;
+}
+
+
+/* Helper function to build_w32_commandline. */
+static char *
+build_w32_commandline_copy (char *buffer, const char *string)
+{
+ char *p = buffer;
+ const char *s;
+
+ if (!*string) /* Empty string. */
+ p = stpcpy (p, "\"\"");
+ else if (strpbrk (string, " \t\n\v\f\""))
+ {
+ /* Need to do some kind of quoting. */
+ p = stpcpy (p, "\"");
+ for (s=string; *s; s++)
+ {
+ *p++ = *s;
+ if (*s == '\"')
+ *p++ = *s;
+ }
+ *p++ = '\"';
+ *p = 0;
+ }
+ else
+ p = stpcpy (p, string);
+
+ return p;
+}
+
+/* Build a command line for use with W32's CreateProcess. On success
+ CMDLINE gets the address of a newly allocated string. */
+static gpg_error_t
+build_w32_commandline (const char *pgmname, const char * const *argv,
+ char **cmdline)
+{
+ int i, n;
+ const char *s;
+ char *buf, *p;
+
+ *cmdline = NULL;
+ n = 0;
+ s = pgmname;
+ n += strlen (s) + 1 + 2; /* (1 space, 2 quoting */
+ for (; *s; s++)
+ if (*s == '\"')
+ n++; /* Need to double inner quotes. */
+ for (i=0; (s=argv[i]); i++)
+ {
+ n += strlen (s) + 1 + 2; /* (1 space, 2 quoting */
+ for (; *s; s++)
+ if (*s == '\"')
+ n++; /* Need to double inner quotes. */
+ }
+ n++;
+
+ buf = p = xtrymalloc (n);
+ if (!buf)
+ return my_error_from_syserror ();
+
+ p = build_w32_commandline_copy (p, pgmname);
+ for (i=0; argv[i]; i++)
+ {
+ *p++ = ' ';
+ p = build_w32_commandline_copy (p, argv[i]);
+ }
+
+ *cmdline= buf;
+ return 0;
+}
+
+
+#define INHERIT_READ 1
+#define INHERIT_WRITE 2
+#define INHERIT_BOTH (INHERIT_READ|INHERIT_WRITE)
+
+/* Create pipe. FLAGS indicates which ends are inheritable. */
+static int
+create_inheritable_pipe (HANDLE filedes[2], int flags)
+{
+ HANDLE r, w;
+ SECURITY_ATTRIBUTES sec_attr;
+
+ memset (&sec_attr, 0, sizeof sec_attr );
+ sec_attr.nLength = sizeof sec_attr;
+ sec_attr.bInheritHandle = TRUE;
+
+ if (!CreatePipe (&r, &w, &sec_attr, 0))
+ return -1;
+
+ if ((flags & INHERIT_READ) == 0)
+ if (! SetHandleInformation (r, HANDLE_FLAG_INHERIT, 0))
+ goto fail;
+
+ if ((flags & INHERIT_WRITE) == 0)
+ if (! SetHandleInformation (w, HANDLE_FLAG_INHERIT, 0))
+ goto fail;
+
+ filedes[0] = r;
+ filedes[1] = w;
+ return 0;
+
+ fail:
+ log_error ("SetHandleInformation failed: %s\n", w32_strerror (-1));
+ CloseHandle (r);
+ CloseHandle (w);
+ return -1;
+}
+
+
+static HANDLE
+w32_open_null (int for_write)
+{
+ HANDLE hfile;
+
+ hfile = CreateFileW (L"nul",
+ for_write? GENERIC_WRITE : GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_WRITE,
+ NULL, OPEN_EXISTING, 0, NULL);
+ if (hfile == INVALID_HANDLE_VALUE)
+ log_debug ("can't open 'nul': %s\n", w32_strerror (-1));
+ return hfile;
+}
+
+
+static gpg_error_t
+create_pipe_and_estream (int filedes[2], int flags,
+ estream_t *r_fp, int outbound, int nonblock)
+{
+ gpg_error_t err = 0;
+ HANDLE fds[2];
+ es_syshd_t syshd;
+
+ filedes[0] = filedes[1] = -1;
+ err = my_error (GPG_ERR_GENERAL);
+ if (!create_inheritable_pipe (fds, flags))
+ {
+ filedes[0] = _open_osfhandle (handle_to_fd (fds[0]), O_RDONLY);
+ if (filedes[0] == -1)
+ {
+ log_error ("failed to translate osfhandle %p\n", fds[0]);
+ CloseHandle (fds[1]);
+ }
+ else
+ {
+ filedes[1] = _open_osfhandle (handle_to_fd (fds[1]), O_APPEND);
+ if (filedes[1] == -1)
+ {
+ log_error ("failed to translate osfhandle %p\n", fds[1]);
+ close (filedes[0]);
+ filedes[0] = -1;
+ CloseHandle (fds[1]);
+ }
+ else
+ err = 0;
+ }
+ }
+
+ if (! err && r_fp)
+ {
+ syshd.type = ES_SYSHD_HANDLE;
+ if (!outbound)
+ {
+ syshd.u.handle = fds[0];
+ *r_fp = es_sysopen (&syshd, nonblock? "r,nonblock" : "r");
+ }
+ else
+ {
+ syshd.u.handle = fds[1];
+ *r_fp = es_sysopen (&syshd, 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 err;
+}
+
+/* Portable function to create a pipe. Under Windows the write end is
+ 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], estream_t *r_fp, int nonblock)
+{
+ return create_pipe_and_estream (filedes, INHERIT_WRITE,
+ r_fp, 0, nonblock);
+}
+
+
+/* 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)
+{
+ return create_pipe_and_estream (filedes, INHERIT_READ,
+ r_fp, 1, nonblock);
+}
+
+
+/* Portable function to create a pipe. Under Windows both ends are
+ inheritable. */
+gpg_error_t
+gnupg_create_pipe (int filedes[2])
+{
+ return create_pipe_and_estream (filedes, INHERIT_BOTH,
+ NULL, 0, 0);
+}
+
+
+/* Fork and exec the PGMNAME, see exechelp.h for details. */
+gpg_error_t
+gnupg_spawn_process (const char *pgmname, const char *argv[],
+ 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;
+ SECURITY_ATTRIBUTES sec_attr;
+ PROCESS_INFORMATION pi =
+ {
+ NULL, /* Returns process handle. */
+ 0, /* Returns primary thread handle. */
+ 0, /* Returns pid. */
+ 0 /* Returns tid. */
+ };
+ STARTUPINFO si;
+ int cr_flags;
+ char *cmdline;
+ HANDLE inpipe[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
+ HANDLE outpipe[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
+ HANDLE errpipe[2] = {INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE};
+ estream_t infp = NULL;
+ 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;
+ gpg_err_source_t errsource = default_errsource;
+ int nonblock = !!(flags & GNUPG_SPAWN_NONBLOCK);
+
+ (void)except; /* Not yet used. */
+
+ 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)
+ {
+ if (create_inheritable_pipe (inpipe, INHERIT_READ))
+ {
+ 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 = inpipe[1];
+ infp = es_sysopen (&syshd, nonblock? "w,nonblock" : "w");
+ if (!infp)
+ {
+ 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 (inpipe[0]);
+ CloseHandle (inpipe[1]);
+ inpipe[0] = inpipe[1] = INVALID_HANDLE_VALUE;
+ return err;
+ }
+ }
+
+ if (r_outfp)
+ {
+ if (create_inheritable_pipe (outpipe, INHERIT_WRITE))
+ {
+ 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, nonblock? "r,nonblock" : "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;
+ if (infp)
+ es_fclose (infp);
+ else if (inpipe[1] != INVALID_HANDLE_VALUE)
+ CloseHandle (inpipe[1]);
+ if (inpipe[0] != INVALID_HANDLE_VALUE)
+ CloseHandle (inpipe[0]);
+ return err;
+ }
+ }
+
+ if (r_errfp)
+ {
+ if (create_inheritable_pipe (errpipe, INHERIT_WRITE))
+ {
+ 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, nonblock? "r,nonblock" : "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]);
+ if (infp)
+ es_fclose (infp);
+ else if (inpipe[1] != INVALID_HANDLE_VALUE)
+ CloseHandle (inpipe[1]);
+ if (inpipe[0] != INVALID_HANDLE_VALUE)
+ CloseHandle (inpipe[0]);
+ return err;
+ }
+ }
+
+ /* Prepare security attributes. */
+ memset (&sec_attr, 0, sizeof sec_attr );
+ sec_attr.nLength = sizeof sec_attr;
+ sec_attr.bInheritHandle = FALSE;
+
+ /* Build the command line. */
+ err = build_w32_commandline (pgmname, argv, &cmdline);
+ if (err)
+ return err;
+
+ if (inpipe[0] == INVALID_HANDLE_VALUE)
+ nullhd[0] = w32_open_null (0);
+ if (outpipe[1] == INVALID_HANDLE_VALUE)
+ nullhd[1] = w32_open_null (1);
+ if (errpipe[1] == INVALID_HANDLE_VALUE)
+ nullhd[2] = w32_open_null (1);
+
+ /* Start the process. Note that we can't run the PREEXEC function
+ 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 = inpipe[0] == INVALID_HANDLE_VALUE? nullhd[0] : inpipe[0];
+ 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 & GNUPG_SPAWN_DETACHED)? DETACHED_PROCESS : 0)
+ | GetPriorityClass (GetCurrentProcess ())
+ | CREATE_SUSPENDED);
+/* log_debug ("CreateProcess, path='%s' cmdline='%s'\n", pgmname, cmdline); */
+ if (!CreateProcess (pgmname, /* Program to start. */
+ cmdline, /* Command line arguments. */
+ &sec_attr, /* Process security attributes. */
+ &sec_attr, /* Thread security attributes. */
+ TRUE, /* Inherit handles. */
+ cr_flags, /* Creation flags. */
+ NULL, /* Environment. */
+ NULL, /* Use current drive/directory. */
+ &si, /* Startup information. */
+ &pi /* Returns process information. */
+ ))
+ {
+ log_error ("CreateProcess failed: %s\n", w32_strerror (-1));
+ xfree (cmdline);
+ if (infp)
+ es_fclose (infp);
+ else if (inpipe[1] != INVALID_HANDLE_VALUE)
+ CloseHandle (outpipe[1]);
+ if (inpipe[0] != INVALID_HANDLE_VALUE)
+ CloseHandle (inpipe[0]);
+ 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 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 (inpipe[0] != INVALID_HANDLE_VALUE)
+ CloseHandle (inpipe[0]);
+ 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 it the correct processID. As a
+ workaround we use -1 (ASFW_ANY). */
+ if ((flags & GNUPG_SPAWN_RUN_ASFW))
+ gnupg_allow_set_foregound_window ((pid_t)(-1)/*pi.dwProcessId*/);
+
+ /* Process has been created suspended; resume it now. */
+ ResumeThread (pi.hThread);
+ CloseHandle (pi.hThread);
+
+ if (r_infp)
+ *r_infp = infp;
+ if (r_outfp)
+ *r_outfp = outfp;
+ if (r_errfp)
+ *r_errfp = errfp;
+
+ *pid = handle_to_pid (pi.hProcess);
+ return 0;
+
+}
+
+
+
+/* Simplified version of gnupg_spawn_process. This function forks and
+ then execs PGMNAME, while connecting INFD to stdin, OUTFD to stdout
+ and ERRFD to stderr (any of them may be -1 to connect them to
+ /dev/null). The arguments for the process are expected in the NULL
+ terminated array ARGV. The program name itself should not be
+ included there. Calling gnupg_wait_process is required.
+
+ Returns 0 on success or an error code. */
+gpg_error_t
+gnupg_spawn_process_fd (const char *pgmname, const char *argv[],
+ int infd, int outfd, int errfd, pid_t *pid)
+{
+ gpg_error_t err;
+ SECURITY_ATTRIBUTES sec_attr;
+ PROCESS_INFORMATION pi = { NULL, 0, 0, 0 };
+ STARTUPINFO si;
+ char *cmdline;
+ int i;
+ HANDLE stdhd[3];
+
+ /* Setup return values. */
+ *pid = (pid_t)(-1);
+
+ /* Prepare security attributes. */
+ memset (&sec_attr, 0, sizeof sec_attr );
+ sec_attr.nLength = sizeof sec_attr;
+ sec_attr.bInheritHandle = FALSE;
+
+ /* Build the command line. */
+ err = build_w32_commandline (pgmname, argv, &cmdline);
+ if (err)
+ return err;
+
+ memset (&si, 0, sizeof si);
+ si.cb = sizeof (si);
+ si.dwFlags = STARTF_USESTDHANDLES | STARTF_USESHOWWINDOW;
+ si.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_MINIMIZE;
+ stdhd[0] = infd == -1? w32_open_null (0) : INVALID_HANDLE_VALUE;
+ stdhd[1] = outfd == -1? w32_open_null (1) : INVALID_HANDLE_VALUE;
+ stdhd[2] = errfd == -1? w32_open_null (1) : INVALID_HANDLE_VALUE;
+ si.hStdInput = infd == -1? stdhd[0] : (void*)_get_osfhandle (infd);
+ si.hStdOutput = outfd == -1? stdhd[1] : (void*)_get_osfhandle (outfd);
+ si.hStdError = errfd == -1? stdhd[2] : (void*)_get_osfhandle (errfd);
+
+/* log_debug ("CreateProcess, path='%s' cmdline='%s'\n", pgmname, cmdline); */
+ if (!CreateProcess (pgmname, /* Program to start. */
+ cmdline, /* Command line arguments. */
+ &sec_attr, /* Process security attributes. */
+ &sec_attr, /* Thread security attributes. */
+ TRUE, /* Inherit handles. */
+ (CREATE_DEFAULT_ERROR_MODE
+ | GetPriorityClass (GetCurrentProcess ())
+ | CREATE_SUSPENDED | DETACHED_PROCESS),
+ NULL, /* Environment. */
+ NULL, /* Use current drive/directory. */
+ &si, /* Startup information. */
+ &pi /* Returns process information. */
+ ))
+ {
+ log_error ("CreateProcess failed: %s\n", w32_strerror (-1));
+ err = my_error (GPG_ERR_GENERAL);
+ }
+ else
+ err = 0;
+ xfree (cmdline);
+ for (i=0; i < 3; i++)
+ if (stdhd[i] != INVALID_HANDLE_VALUE)
+ CloseHandle (stdhd[i]);
+ if (err)
+ return err;
+
+/* log_debug ("CreateProcess ready: hProcess=%p hThread=%p" */
+/* " dwProcessID=%d dwThreadId=%d\n", */
+/* pi.hProcess, pi.hThread, */
+/* (int) pi.dwProcessId, (int) pi.dwThreadId); */
+
+ /* Process has been created suspended; resume it now. */
+ ResumeThread (pi.hThread);
+ CloseHandle (pi.hThread);
+
+ *pid = handle_to_pid (pi.hProcess);
+ return 0;
+
+}
+
+
+/* See exechelp.h for a description. */
+gpg_error_t
+gnupg_wait_process (const char *pgmname, pid_t pid, int hang, int *r_exitcode)
+{
+ 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;
+ HANDLE *procs;
+ int code;
+
+ procs = xtrycalloc (count, sizeof *procs);
+ if (procs == NULL)
+ return my_error_from_syserror ();
+
+ for (i = 0; i < count; i++)
+ {
+ if (r_exitcodes)
+ r_exitcodes[i] = -1;
+
+ if (pids[i] == (pid_t)(-1))
+ return my_error (GPG_ERR_INV_VALUE);
+
+ procs[i] = fd_to_handle (pids[i]);
+ }
+
+ /* 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 = WaitForMultipleObjects (count, procs, TRUE, hang? INFINITE : 0);
+ switch (code)
+ {
+ case WAIT_TIMEOUT:
+ ec = GPG_ERR_TIMEOUT;
+ goto leave;
+
+ case WAIT_FAILED:
+ log_error (_("waiting for processes to terminate failed: %s\n"),
+ w32_strerror (-1));
+ ec = GPG_ERR_GENERAL;
+ goto leave;
+
+ case WAIT_OBJECT_0:
+ for (i = 0; i < count; i++)
+ {
+ DWORD exc;
+
+ if (! GetExitCodeProcess (procs[i], &exc))
+ {
+ log_error (_("error getting exit code of process %d: %s\n"),
+ (int) pids[i], w32_strerror (-1) );
+ ec = GPG_ERR_GENERAL;
+ }
+ else if (exc)
+ {
+ if (!r_exitcodes)
+ log_error (_("error running '%s': exit status %d\n"),
+ pgmnames[i], (int)exc);
+ else
+ r_exitcodes[i] = (int)exc;
+ ec = GPG_ERR_GENERAL;
+ }
+ else
+ {
+ if (r_exitcodes)
+ r_exitcodes[i] = 0;
+ }
+ }
+ break;
+
+ default:
+ log_error ("WaitForMultipleObjects returned unexpected "
+ "code %d\n", code);
+ ec = GPG_ERR_GENERAL;
+ break;
+ }
+
+ leave:
+ 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 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
+ pgmname is not executable; to make this work it is necessary to
+ provide an absolute file name. All standard file descriptors are
+ connected to /dev/null. */
+gpg_error_t
+gnupg_spawn_process_detached (const char *pgmname, const char *argv[],
+ const char *envp[] )
+{
+ gpg_error_t err;
+ SECURITY_ATTRIBUTES sec_attr;
+ PROCESS_INFORMATION pi =
+ {
+ NULL, /* Returns process handle. */
+ 0, /* Returns primary thread handle. */
+ 0, /* Returns pid. */
+ 0 /* Returns tid. */
+ };
+ STARTUPINFO si;
+ int cr_flags;
+ char *cmdline;
+
+
+ /* We don't use ENVP. */
+ (void)envp;
+
+ if (access (pgmname, X_OK))
+ return my_error_from_syserror ();
+
+ /* Prepare security attributes. */
+ memset (&sec_attr, 0, sizeof sec_attr );
+ sec_attr.nLength = sizeof sec_attr;
+ sec_attr.bInheritHandle = FALSE;
+
+ /* Build the command line. */
+ err = build_w32_commandline (pgmname, argv, &cmdline);
+ if (err)
+ return err;
+
+ /* Start the process. */
+ memset (&si, 0, sizeof si);
+ si.cb = sizeof (si);
+ si.dwFlags = STARTF_USESHOWWINDOW;
+ si.wShowWindow = DEBUG_W32_SPAWN? SW_SHOW : SW_MINIMIZE;
+
+ cr_flags = (CREATE_DEFAULT_ERROR_MODE
+ | GetPriorityClass (GetCurrentProcess ())
+ | CREATE_NEW_PROCESS_GROUP
+ | DETACHED_PROCESS);
+/* log_debug ("CreateProcess(detached), path='%s' cmdline='%s'\n", */
+/* pgmname, cmdline); */
+ if (!CreateProcess (pgmname, /* Program to start. */
+ cmdline, /* Command line arguments. */
+ &sec_attr, /* Process security attributes. */
+ &sec_attr, /* Thread security attributes. */
+ FALSE, /* Inherit handles. */
+ cr_flags, /* Creation flags. */
+ NULL, /* Environment. */
+ NULL, /* Use current drive/directory. */
+ &si, /* Startup information. */
+ &pi /* Returns process information. */
+ ))
+ {
+ log_error ("CreateProcess(detached) failed: %s\n", w32_strerror (-1));
+ xfree (cmdline);
+ return my_error (GPG_ERR_GENERAL);
+ }
+ xfree (cmdline);
+ cmdline = NULL;
+
+/* log_debug ("CreateProcess(detached) ready: hProcess=%p hThread=%p" */
+/* " dwProcessID=%d dwThreadId=%d\n", */
+/* pi.hProcess, pi.hThread, */
+/* (int) pi.dwProcessId, (int) pi.dwThreadId); */
+
+ CloseHandle (pi.hThread);
+ CloseHandle (pi.hProcess);
+
+ return 0;
+}
+
+
+/* Kill a process; that is send an appropriate signal to the process.
+ gnupg_wait_process must be called to actually remove the process
+ from the system. An invalid PID is ignored. */
+void
+gnupg_kill_process (pid_t pid)
+{
+ if (pid != (pid_t) INVALID_HANDLE_VALUE)
+ {
+ HANDLE process = (HANDLE) pid;
+
+ /* Arbitrary error code. */
+ TerminateProcess (process, 1);
+ }
+}