/* Utilities to execute a program in a subprocess (possibly linked by pipes
with other subprocesses), and wait for it. Generic Unix version
(also used for UWIN and VMS).
- Copyright (C) 1996, 1997, 1998, 1999, 2000, 2001, 2003, 2004, 2005
- Free Software Foundation, Inc.
+ Copyright (C) 1996-2019 Free Software Foundation, Inc.
This file is part of the libiberty library.
Libiberty is free software; you can redistribute it and/or
You should have received a copy of the GNU Library General Public
License along with libiberty; see the file COPYING.LIB. If not,
-write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
-Boston, MA 02111-1307, USA. */
+write to the Free Software Foundation, Inc., 51 Franklin Street - Fifth Floor,
+Boston, MA 02110-1301, USA. */
#include "config.h"
#include "libiberty.h"
#include "pex-common.h"
+#include "environ.h"
#include <stdio.h>
#include <signal.h>
#ifdef HAVE_SYS_STAT_H
#include <sys/stat.h>
#endif
-
+#ifdef HAVE_PROCESS_H
+#include <process.h>
+#endif
#ifdef vfork /* Autoconf may define this to fork for us. */
# define VFORK_STRING "fork"
#ifdef HAVE_VFORK_H
#include <vfork.h>
#endif
-#ifdef VMS
-#define vfork() (decc$$alloc_vfork_blocks() >= 0 ? \
- lib$get_current_invo_context(decc$$get_vfork_jmpbuf()) : -1)
-#endif /* VMS */
+#if defined(VMS) && defined (__LONG_POINTERS)
+#ifndef __CHAR_PTR32
+typedef char * __char_ptr32
+__attribute__ ((mode (SI)));
+#endif
+
+typedef __char_ptr32 *__char_ptr_char_ptr32
+__attribute__ ((mode (SI)));
+
+/* Return a 32 bit pointer to an array of 32 bit pointers
+ given a 64 bit pointer to an array of 64 bit pointers. */
+
+static __char_ptr_char_ptr32
+to_ptr32 (char **ptr64)
+{
+ int argc;
+ __char_ptr_char_ptr32 short_argv;
+
+ /* Count number of arguments. */
+ for (argc = 0; ptr64[argc] != NULL; argc++)
+ ;
+ /* Reallocate argv with 32 bit pointers. */
+ short_argv = (__char_ptr_char_ptr32) decc$malloc
+ (sizeof (__char_ptr32) * (argc + 1));
+
+ for (argc = 0; ptr64[argc] != NULL; argc++)
+ short_argv[argc] = (__char_ptr32) decc$strdup (ptr64[argc]);
+
+ short_argv[argc] = (__char_ptr32) 0;
+ return short_argv;
+
+}
+#else
+#define to_ptr32(argv) argv
+#endif
/* File mode to use for private and world-readable files. */
return cpid;
}
- psl = xmalloc (sizeof (struct status_list));
+ psl = XNEW (struct status_list);
psl->pid = cpid;
psl->status = *status;
if (time != NULL)
#endif /* ! defined (HAVE_WAITPID) */
#endif /* ! defined (HAVE_WAIT4) */
-static void pex_child_error (struct pex_obj *, const char *, const char *, int)
- ATTRIBUTE_NORETURN;
static int pex_unix_open_read (struct pex_obj *, const char *, int);
-static int pex_unix_open_write (struct pex_obj *, const char *, int);
-static long pex_unix_exec_child (struct pex_obj *, int, const char *,
- char * const *, int, int, int,
+static int pex_unix_open_write (struct pex_obj *, const char *, int, int);
+static pid_t pex_unix_exec_child (struct pex_obj *, int, const char *,
+ char * const *, char * const *,
+ int, int, int, int,
const char **, int *);
static int pex_unix_close (struct pex_obj *, int);
-static int pex_unix_wait (struct pex_obj *, long, int *, struct pex_time *,
+static int pex_unix_wait (struct pex_obj *, pid_t, int *, struct pex_time *,
int, const char **, int *);
static int pex_unix_pipe (struct pex_obj *, int *, int);
static FILE *pex_unix_fdopenr (struct pex_obj *, int, int);
+static FILE *pex_unix_fdopenw (struct pex_obj *, int, int);
static void pex_unix_cleanup (struct pex_obj *);
/* The list of functions we pass to the common routines. */
pex_unix_wait,
pex_unix_pipe,
pex_unix_fdopenr,
+ pex_unix_fdopenw,
pex_unix_cleanup
};
static int
pex_unix_open_write (struct pex_obj *obj ATTRIBUTE_UNUSED, const char *name,
- int binary ATTRIBUTE_UNUSED)
+ int binary ATTRIBUTE_UNUSED, int append)
{
/* Note that we can't use O_EXCL here because gcc may have already
created the temporary file via make_temp_file. */
- return open (name, O_WRONLY | O_CREAT | O_TRUNC, PUBLIC_MODE);
+ return open (name, O_WRONLY | O_CREAT
+ | (append ? O_APPEND : O_TRUNC), PUBLIC_MODE);
}
/* Close a file. */
return close (fd);
}
-/* Report an error from a child process. We don't use stdio routines,
- because we might be here due to a vfork call. */
+/* Execute a child. */
-static void
-pex_child_error (struct pex_obj *obj, const char *executable,
- const char *errmsg, int err)
+#if defined(HAVE_SPAWNVE) && defined(HAVE_SPAWNVPE)
+/* Implementation of pex->exec_child using the Cygwin spawn operation. */
+
+/* Subroutine of pex_unix_exec_child. Move OLD_FD to a new file descriptor
+ to be stored in *PNEW_FD, save the flags in *PFLAGS, and arrange for the
+ saved copy to be close-on-exec. Move CHILD_FD into OLD_FD. If CHILD_FD
+ is -1, OLD_FD is to be closed. Return -1 on error. */
+
+static int
+save_and_install_fd(int *pnew_fd, int *pflags, int old_fd, int child_fd)
{
-#define writeerr(s) write (STDERR_FILE_NO, s, strlen (s))
- writeerr (obj->pname);
- writeerr (": error trying to exec '");
- writeerr (executable);
- writeerr ("': ");
- writeerr (errmsg);
- writeerr (": ");
- writeerr (xstrerror (err));
- writeerr ("\n");
- _exit (-1);
+ int new_fd, flags;
+
+ flags = fcntl (old_fd, F_GETFD);
+
+ /* If we could not retrieve the flags, then OLD_FD was not open. */
+ if (flags < 0)
+ {
+ new_fd = -1, flags = 0;
+ if (child_fd >= 0 && dup2 (child_fd, old_fd) < 0)
+ return -1;
+ }
+ /* If we wish to close OLD_FD, just mark it CLOEXEC. */
+ else if (child_fd == -1)
+ {
+ new_fd = old_fd;
+ if ((flags & FD_CLOEXEC) == 0 && fcntl (old_fd, F_SETFD, FD_CLOEXEC) < 0)
+ return -1;
+ }
+ /* Otherwise we need to save a copy of OLD_FD before installing CHILD_FD. */
+ else
+ {
+#ifdef F_DUPFD_CLOEXEC
+ new_fd = fcntl (old_fd, F_DUPFD_CLOEXEC, 3);
+ if (new_fd < 0)
+ return -1;
+#else
+ /* Prefer F_DUPFD over dup in order to avoid getting a new fd
+ in the range 0-2, right where a new stderr fd might get put. */
+ new_fd = fcntl (old_fd, F_DUPFD, 3);
+ if (new_fd < 0)
+ return -1;
+ if (fcntl (new_fd, F_SETFD, FD_CLOEXEC) < 0)
+ return -1;
+#endif
+ if (dup2 (child_fd, old_fd) < 0)
+ return -1;
+ }
+
+ *pflags = flags;
+ if (pnew_fd)
+ *pnew_fd = new_fd;
+ else if (new_fd != old_fd)
+ abort ();
+
+ return 0;
}
-/* Execute a child. */
+/* Subroutine of pex_unix_exec_child. Move SAVE_FD back to OLD_FD
+ restoring FLAGS. If SAVE_FD < 0, OLD_FD is to be closed. */
-static long
-pex_unix_exec_child (struct pex_obj *obj, int flags, const char *executable,
- char * const * argv, int in, int out, int errdes,
+static int
+restore_fd(int old_fd, int save_fd, int flags)
+{
+ /* For SAVE_FD < 0, all we have to do is restore the
+ "closed-ness" of the original. */
+ if (save_fd < 0)
+ return close (old_fd);
+
+ /* For SAVE_FD == OLD_FD, all we have to do is restore the
+ original setting of the CLOEXEC flag. */
+ if (save_fd == old_fd)
+ {
+ if (flags & FD_CLOEXEC)
+ return 0;
+ return fcntl (old_fd, F_SETFD, flags);
+ }
+
+ /* Otherwise we have to move the descriptor back, restore the flags,
+ and close the saved copy. */
+#ifdef HAVE_DUP3
+ if (flags == FD_CLOEXEC)
+ {
+ if (dup3 (save_fd, old_fd, O_CLOEXEC) < 0)
+ return -1;
+ }
+ else
+#endif
+ {
+ if (dup2 (save_fd, old_fd) < 0)
+ return -1;
+ if (flags != 0 && fcntl (old_fd, F_SETFD, flags) < 0)
+ return -1;
+ }
+ return close (save_fd);
+}
+
+static pid_t
+pex_unix_exec_child (struct pex_obj *obj ATTRIBUTE_UNUSED,
+ int flags, const char *executable,
+ char * const * argv, char * const * env,
+ int in, int out, int errdes, int toclose,
const char **errmsg, int *err)
{
+ int fl_in = 0, fl_out = 0, fl_err = 0, fl_tc = 0;
+ int save_in = -1, save_out = -1, save_err = -1;
+ int max, retries;
pid_t pid;
+
+ if (flags & PEX_STDERR_TO_STDOUT)
+ errdes = out;
+
+ /* We need the three standard file descriptors to be set up as for
+ the child before we perform the spawn. The file descriptors for
+ the parent need to be moved and marked for close-on-exec. */
+ if (in != STDIN_FILE_NO
+ && save_and_install_fd (&save_in, &fl_in, STDIN_FILE_NO, in) < 0)
+ goto error_dup2;
+ if (out != STDOUT_FILE_NO
+ && save_and_install_fd (&save_out, &fl_out, STDOUT_FILE_NO, out) < 0)
+ goto error_dup2;
+ if (errdes != STDERR_FILE_NO
+ && save_and_install_fd (&save_err, &fl_err, STDERR_FILE_NO, errdes) < 0)
+ goto error_dup2;
+ if (toclose >= 0
+ && save_and_install_fd (NULL, &fl_tc, toclose, -1) < 0)
+ goto error_dup2;
+
+ /* Now that we've moved the file descriptors for the child into place,
+ close the originals. Be careful not to close any of the standard
+ file descriptors that we just set up. */
+ max = -1;
+ if (errdes >= 0)
+ max = STDERR_FILE_NO;
+ else if (out >= 0)
+ max = STDOUT_FILE_NO;
+ else if (in >= 0)
+ max = STDIN_FILE_NO;
+ if (in > max)
+ close (in);
+ if (out > max)
+ close (out);
+ if (errdes > max && errdes != out)
+ close (errdes);
+
+ /* If we were not given an environment, use the global environment. */
+ if (env == NULL)
+ env = environ;
+
+ /* Launch the program. If we get EAGAIN (normally out of pid's), try
+ again a few times with increasing backoff times. */
+ retries = 0;
+ while (1)
+ {
+ typedef const char * const *cc_cp;
+
+ if (flags & PEX_SEARCH)
+ pid = spawnvpe (_P_NOWAITO, executable, (cc_cp)argv, (cc_cp)env);
+ else
+ pid = spawnve (_P_NOWAITO, executable, (cc_cp)argv, (cc_cp)env);
+
+ if (pid > 0)
+ break;
+
+ *err = errno;
+ *errmsg = "spawn";
+ if (errno != EAGAIN || ++retries == 4)
+ return (pid_t) -1;
+ sleep (1 << retries);
+ }
+
+ /* Success. Restore the parent's file descriptors that we saved above. */
+ if (toclose >= 0
+ && restore_fd (toclose, toclose, fl_tc) < 0)
+ goto error_dup2;
+ if (in != STDIN_FILE_NO
+ && restore_fd (STDIN_FILE_NO, save_in, fl_in) < 0)
+ goto error_dup2;
+ if (out != STDOUT_FILE_NO
+ && restore_fd (STDOUT_FILE_NO, save_out, fl_out) < 0)
+ goto error_dup2;
+ if (errdes != STDERR_FILE_NO
+ && restore_fd (STDERR_FILE_NO, save_err, fl_err) < 0)
+ goto error_dup2;
+
+ return pid;
+
+ error_dup2:
+ *err = errno;
+ *errmsg = "dup2";
+ return (pid_t) -1;
+}
+
+#else
+/* Implementation of pex->exec_child using standard vfork + exec. */
+
+static pid_t
+pex_unix_exec_child (struct pex_obj *obj, int flags, const char *executable,
+ char * const * argv, char * const * env,
+ int in, int out, int errdes,
+ int toclose, const char **errmsg, int *err)
+{
+ pid_t pid = -1;
+ /* Tuple to communicate error from child to parent. We can safely
+ transfer string literal pointers as both run with identical
+ address mappings. */
+ struct fn_err
+ {
+ const char *fn;
+ int err;
+ };
+ volatile int do_pipe = 0;
+ volatile int pipes[2]; /* [0]:reader,[1]:writer. */
+#ifdef O_CLOEXEC
+ do_pipe = 1;
+#endif
+ if (do_pipe)
+ {
+#ifdef HAVE_PIPE2
+ if (pipe2 ((int *)pipes, O_CLOEXEC))
+ do_pipe = 0;
+#else
+ if (pipe ((int *)pipes))
+ do_pipe = 0;
+ else
+ {
+ if (fcntl (pipes[1], F_SETFD, FD_CLOEXEC) == -1)
+ {
+ close (pipes[0]);
+ close (pipes[1]);
+ do_pipe = 0;
+ }
+ }
+#endif
+ }
+
/* We declare these to be volatile to avoid warnings from gcc about
them being clobbered by vfork. */
- volatile int sleep_interval;
+ volatile int sleep_interval = 1;
volatile int retries;
- sleep_interval = 1;
- pid = -1;
+ /* We vfork and then set environ in the child before calling execvp.
+ This clobbers the parent's environ so we need to restore it.
+ It would be nice to use one of the exec* functions that takes an
+ environment as a parameter, but that may have portability
+ issues. It is marked volatile so the child doesn't consider it a
+ dead variable and therefore clobber where ever it is stored. */
+ char **volatile save_environ = environ;
+
for (retries = 0; retries < 4; ++retries)
{
pid = vfork ();
switch (pid)
{
case -1:
+ if (do_pipe)
+ {
+ close (pipes[0]);
+ close (pipes[1]);
+ }
*err = errno;
*errmsg = VFORK_STRING;
- return -1;
+ return (pid_t) -1;
case 0:
/* Child process. */
- if (in != STDIN_FILE_NO)
- {
- if (dup2 (in, STDIN_FILE_NO) < 0)
- pex_child_error (obj, executable, "dup2", errno);
- if (close (in) < 0)
- pex_child_error (obj, executable, "close", errno);
- }
- if (out != STDOUT_FILE_NO)
- {
- if (dup2 (out, STDOUT_FILE_NO) < 0)
- pex_child_error (obj, executable, "dup2", errno);
- if (close (out) < 0)
- pex_child_error (obj, executable, "close", errno);
- }
- if (errdes != STDERR_FILE_NO)
- {
- if (dup2 (errdes, STDERR_FILE_NO) < 0)
- pex_child_error (obj, executable, "dup2", errno);
- if (close (errdes) < 0)
- pex_child_error (obj, executable, "close", errno);
- }
- if ((flags & PEX_STDERR_TO_STDOUT) != 0)
- {
- if (dup2 (STDOUT_FILE_NO, STDERR_FILE_NO) < 0)
- pex_child_error (obj, executable, "dup2", errno);
- }
- if ((flags & PEX_SEARCH) != 0)
- {
- execvp (executable, argv);
- pex_child_error (obj, executable, "execvp", errno);
- }
- else
- {
- execv (executable, argv);
- pex_child_error (obj, executable, "execv", errno);
- }
-
+ {
+ struct fn_err failed;
+ failed.fn = NULL;
+
+ if (do_pipe)
+ close (pipes[0]);
+ if (!failed.fn && in != STDIN_FILE_NO)
+ {
+ if (dup2 (in, STDIN_FILE_NO) < 0)
+ failed.fn = "dup2", failed.err = errno;
+ else if (close (in) < 0)
+ failed.fn = "close", failed.err = errno;
+ }
+ if (!failed.fn && out != STDOUT_FILE_NO)
+ {
+ if (dup2 (out, STDOUT_FILE_NO) < 0)
+ failed.fn = "dup2", failed.err = errno;
+ else if (close (out) < 0)
+ failed.fn = "close", failed.err = errno;
+ }
+ if (!failed.fn && errdes != STDERR_FILE_NO)
+ {
+ if (dup2 (errdes, STDERR_FILE_NO) < 0)
+ failed.fn = "dup2", failed.err = errno;
+ else if (close (errdes) < 0)
+ failed.fn = "close", failed.err = errno;
+ }
+ if (!failed.fn && toclose >= 0)
+ {
+ if (close (toclose) < 0)
+ failed.fn = "close", failed.err = errno;
+ }
+ if (!failed.fn && (flags & PEX_STDERR_TO_STDOUT) != 0)
+ {
+ if (dup2 (STDOUT_FILE_NO, STDERR_FILE_NO) < 0)
+ failed.fn = "dup2", failed.err = errno;
+ }
+ if (!failed.fn)
+ {
+ if (env)
+ /* NOTE: In a standard vfork implementation this clobbers
+ the parent's copy of environ "too" (in reality there's
+ only one copy). This is ok as we restore it below. */
+ environ = (char**) env;
+ if ((flags & PEX_SEARCH) != 0)
+ {
+ execvp (executable, to_ptr32 (argv));
+ failed.fn = "execvp", failed.err = errno;
+ }
+ else
+ {
+ execv (executable, to_ptr32 (argv));
+ failed.fn = "execv", failed.err = errno;
+ }
+ }
+
+ /* Something failed, report an error. We don't use stdio
+ routines, because we might be here due to a vfork call. */
+ ssize_t retval = 0;
+
+ if (!do_pipe
+ || write (pipes[1], &failed, sizeof (failed)) != sizeof (failed))
+ {
+ /* The parent will not see our scream above, so write to
+ stdout. */
+#define writeerr(s) (retval |= write (STDERR_FILE_NO, s, strlen (s)))
+ writeerr (obj->pname);
+ writeerr (": error trying to exec '");
+ writeerr (executable);
+ writeerr ("': ");
+ writeerr (failed.fn);
+ writeerr (": ");
+ writeerr (xstrerror (failed.err));
+ writeerr ("\n");
+#undef writeerr
+ }
+
+ /* Exit with -2 if the error output failed, too. */
+ _exit (retval < 0 ? -2 : -1);
+ }
/* NOTREACHED */
- return -1;
+ return (pid_t) -1;
default:
/* Parent process. */
- if (in != STDIN_FILE_NO)
- {
+ {
+ /* Restore environ. Note that the parent either doesn't run
+ until the child execs/exits (standard vfork behaviour), or
+ if it does run then vfork is behaving more like fork. In
+ either case we needn't worry about clobbering the child's
+ copy of environ. */
+ environ = save_environ;
+
+ struct fn_err failed;
+ failed.fn = NULL;
+ if (do_pipe)
+ {
+ close (pipes[1]);
+ ssize_t len = read (pipes[0], &failed, sizeof (failed));
+ if (len < 0)
+ failed.fn = NULL;
+ close (pipes[0]);
+ }
+
+ if (!failed.fn && in != STDIN_FILE_NO)
if (close (in) < 0)
- {
- *err = errno;
- *errmsg = "close";
- return -1;
- }
- }
- if (out != STDOUT_FILE_NO)
- {
+ failed.fn = "close", failed.err = errno;
+ if (!failed.fn && out != STDOUT_FILE_NO)
if (close (out) < 0)
- {
- *err = errno;
- *errmsg = "close";
- return -1;
- }
- }
- if (errdes != STDERR_FILE_NO)
- {
+ failed.fn = "close", failed.err = errno;
+ if (!failed.fn && errdes != STDERR_FILE_NO)
if (close (errdes) < 0)
- {
- *err = errno;
- *errmsg = "close";
- return -1;
- }
- }
-
- return (long) pid;
+ failed.fn = "close", failed.err = errno;
+
+ if (failed.fn)
+ {
+ *err = failed.err;
+ *errmsg = failed.fn;
+ return (pid_t) -1;
+ }
+ }
+ return pid;
}
}
+#endif /* SPAWN */
/* Wait for a child process to complete. */
static int
-pex_unix_wait (struct pex_obj *obj, long pid, int *status,
+pex_unix_wait (struct pex_obj *obj, pid_t pid, int *status,
struct pex_time *time, int done, const char **errmsg,
int *err)
{
return fdopen (fd, "r");
}
+static FILE *
+pex_unix_fdopenw (struct pex_obj *obj ATTRIBUTE_UNUSED, int fd,
+ int binary ATTRIBUTE_UNUSED)
+{
+ if (fcntl (fd, F_SETFD, FD_CLOEXEC) < 0)
+ return NULL;
+ return fdopen (fd, "w");
+}
+
static void
pex_unix_cleanup (struct pex_obj *obj ATTRIBUTE_UNUSED)
{