aboutsummaryrefslogtreecommitdiffstats
path: root/common/exechelp-posix.c
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2010-03-22 15:00:54 +0000
committerWerner Koch <[email protected]>2010-03-22 15:00:54 +0000
commitd1591a97f4ea9705f18185c95775afeb965af68f (patch)
tree7f6fc5b50e90af5c8fdd2ca7168b25e127201e1d /common/exechelp-posix.c
parentCode cleanup. (diff)
downloadgnupg-d1591a97f4ea9705f18185c95775afeb965af68f.tar.gz
gnupg-d1591a97f4ea9705f18185c95775afeb965af68f.zip
Reorganized the exechelp code.
Diffstat (limited to 'common/exechelp-posix.c')
-rw-r--r--common/exechelp-posix.c555
1 files changed, 555 insertions, 0 deletions
diff --git a/common/exechelp-posix.c b/common/exechelp-posix.c
new file mode 100644
index 000000000..81d5a8185
--- /dev/null
+++ b/common/exechelp-posix.c
@@ -0,0 +1,555 @@
+/* exechelp.c - Fork and exec helpers for POSIX
+ * Copyright (C) 2004, 2007, 2008, 2009,
+ * 2010 Free Software Foundation, Inc.
+ *
+ * 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.
+ *
+ * GnuPG 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.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ */
+
+#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 <string.h>
+#include <errno.h>
+#include <assert.h>
+#ifdef HAVE_SIGNAL_H
+# include <signal.h>
+#endif
+#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
+#endif
+
+#ifdef USE_GNU_PTH
+#include <pth.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
+
+#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
+
+
+/* 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;
+
+# 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. */
+
+ 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 fucntion 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,
+ 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, NULL);
+
+ 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 = gpg_error_from_syserror ();
+ filedes[0] = filedes[1] = -1;
+ }
+
+ return err;
+}
+
+/* Portable function to create a pipe. Under Windows the write end is
+ inheritable. */
+gpg_error_t
+gnupg_create_inbound_pipe (int filedes[2])
+{
+ return do_create_pipe (filedes);
+}
+
+
+/* Portable function to create a pipe. Under Windows the read end is
+ inheritable. */
+gpg_error_t
+gnupg_create_outbound_pipe (int filedes[2])
+{
+ return do_create_pipe (filedes);
+}
+
+
+/* Fork and exec the PGMNAME, connect the file descriptor of INFILE to
+ stdin, write the output to OUTFILE, return a new stream in
+ STATUSFILE for stderr and the pid of the process in PID. 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, that function will be called right before the
+ exec. Calling gnupg_wait_process is required.
+
+ FLAGS is a bit vector with just one bit defined for now:
+
+ Bit 7: If set the process will be started as a background process.
+ This flag is only useful under W32 systems, so that no new
+ console is created and pops up a console window when
+ starting the server
+
+ Bit 6: On W32 run AllowSetForegroundWindow for the child. Due to
+ error problems this actually allows SetForegroundWindow for
+ childs of this process.
+
+ Returns 0 on success or an error code. */
+gpg_error_t
+gnupg_spawn_process (const char *pgmname, const char *argv[],
+ FILE *infile, estream_t outfile,
+ void (*preexec)(void), unsigned int flags,
+ FILE **statusfile, pid_t *pid)
+{
+ gpg_error_t err;
+ int fd, fdout, rp[2];
+
+ (void)flags; /* Currently not used. */
+
+ *statusfile = NULL;
+ *pid = (pid_t)(-1);
+ fflush (infile);
+ rewind (infile);
+ fd = 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 (pipe (rp) == -1)
+ {
+ err = gpg_error_from_syserror ();
+ log_error (_("error creating a pipe: %s\n"), strerror (errno));
+ 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]);
+ return err;
+ }
+
+ if (!*pid)
+ {
+ gcry_control (GCRYCTL_TERM_SECMEM);
+ /* Run child. */
+ do_exec (pgmname, argv, fd, fdout, rp[1], preexec);
+ /*NOTREACHED*/
+ }
+
+ /* Parent. */
+ close (rp[1]);
+
+ *statusfile = 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;
+ }
+
+ 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;
+
+#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));
+ return err;
+ }
+
+ if (!*pid)
+ {
+ gcry_control (GCRYCTL_TERM_SECMEM);
+ /* Run child. */
+ do_exec (pgmname, argv, infd, outfd, errfd, NULL);
+ /*NOTREACHED*/
+ }
+
+ return 0;
+}
+
+
+/* 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. */
+gpg_error_t
+gnupg_wait_process (const char *pgmname, pid_t pid, int *exitcode)
+{
+ gpg_err_code_t ec;
+
+ int i, status;
+
+ if (exitcode)
+ *exitcode = -1;
+
+ if (pid == (pid_t)(-1))
+ return gpg_error (GPG_ERR_INV_VALUE);
+
+#ifdef USE_GNU_PTH
+ i = pth_waitpid ? pth_waitpid (pid, &status, 0) : waitpid (pid, &status, 0);
+#else
+ while ( (i=waitpid (pid, &status, 0)) == -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))
+ {
+ log_error (_("error running `%s': exit status %d\n"), pgmname,
+ WEXITSTATUS (status));
+ if (exitcode)
+ *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;
+ }
+
+ return gpg_err_make (GPG_ERR_SOURCE_DEFAULT, ec);
+}
+
+
+/* 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).
+ 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 gpg_error (GPG_ERR_BUG);
+
+ if (access (pgmname, X_OK))
+ return gpg_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 ();
+ }
+ if (!pid)
+ {
+ gcry_control (GCRYCTL_TERM_SECMEM);
+ if (setsid() == -1 || chdir ("/"))
+ _exit (1);
+ pid = fork (); /* Double fork to let init takes over the new child. */
+ if (pid == (pid_t)(-1))
+ _exit (1);
+ if (pid)
+ _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);
+
+ /*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);
+ }
+}