]> git.ipfire.org Git - thirdparty/gcc.git/blobdiff - libiberty/pex-unix.c
Add prange entries in gimple-range-op.cc.
[thirdparty/gcc.git] / libiberty / pex-unix.c
index 91619af1e2cbc7404bdf868a9d88d7cfaa0ce864..f3a1cc95adacd929b8a5706159f0b70140a9f9ae 100644 (file)
@@ -1,8 +1,7 @@
 /* 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-2024 Free Software Foundation, Inc.
 
 This file is part of the libiberty library.
 Libiberty is free software; you can redistribute it and/or
@@ -23,6 +22,7 @@ Boston, MA 02110-1301, USA.  */
 #include "config.h"
 #include "libiberty.h"
 #include "pex-common.h"
+#include "environ.h"
 
 #include <stdio.h>
 #include <signal.h>
@@ -55,7 +55,12 @@ extern int errno;
 #ifdef HAVE_SYS_STAT_H
 #include <sys/stat.h>
 #endif
-
+#ifdef HAVE_PROCESS_H
+#include <process.h>
+#endif
+#ifdef HAVE_SPAWN_H
+#include <spawn.h>
+#endif
 
 #ifdef vfork /* Autoconf may define this to fork for us. */
 # define VFORK_STRING "fork"
@@ -65,11 +70,42 @@ extern int errno;
 #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.  */
 
@@ -265,16 +301,15 @@ pex_wait (struct pex_obj *obj, pid_t pid, int *status, struct pex_time *time)
 #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 *,
+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, const char **, int *);
+                                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 *,
-                         int, const char **, int *);
+static pid_t 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);
@@ -316,11 +351,12 @@ pex_unix_open_read (struct pex_obj *obj ATTRIBUTE_UNUSED, const char *name,
 
 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.  */
@@ -331,144 +367,577 @@ pex_unix_close (struct pex_obj *obj ATTRIBUTE_UNUSED, int fd)
   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.  */
 
-extern char **environ;
+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);
+    }
 
-static long
-pex_unix_exec_child (struct pex_obj *obj, int flags, const char *executable,
+  /* 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 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;
 
-  /* We declare these to be volatile to avoid warnings from gcc about
-     them being clobbered by vfork.  */
-  volatile int sleep_interval;
-  volatile int retries;
-
-  sleep_interval = 1;
-  pid = -1;
-  for (retries = 0; retries < 4; ++retries)
+  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)
     {
-      pid = vfork ();
-      if (pid >= 0)
+      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;
-      sleep (sleep_interval);
-      sleep_interval *= 2;
+
+      *err = errno;
+      *errmsg = "spawn";
+      if (errno != EAGAIN || ++retries == 4)
+       return (pid_t) -1;
+      sleep (1 << retries);
     }
 
-  switch (pid)
+  /* 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;
+}
+
+#elif defined(HAVE_POSIX_SPAWN) && defined(HAVE_POSIX_SPAWNP)
+/* Implementation of pex->exec_child using posix_spawn.            */
+
+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 ret;
+  pid_t pid = -1;
+  posix_spawnattr_t attr;
+  posix_spawn_file_actions_t actions;
+  int attr_initialized = 0, actions_initialized = 0;
+
+  *err = 0;
+
+  ret = posix_spawnattr_init (&attr);
+  if (ret)
     {
-    case -1:
-      *err = errno;
-      *errmsg = VFORK_STRING;
-      return -1;
+      *err = ret;
+      *errmsg = "posix_spawnattr_init";
+      goto exit;
+    }
+  attr_initialized = 1;
 
-    case 0:
-      /* Child process.  */
-      if (in != STDIN_FILE_NO)
+  /* Use vfork() on glibc <=2.24. */
+#ifdef POSIX_SPAWN_USEVFORK
+  ret = posix_spawnattr_setflags (&attr, POSIX_SPAWN_USEVFORK);
+  if (ret)
+    {
+      *err = ret;
+      *errmsg = "posix_spawnattr_setflags";
+      goto exit;
+    }
+#endif
+
+  ret = posix_spawn_file_actions_init (&actions);
+  if (ret)
+    {
+      *err = ret;
+      *errmsg = "posix_spawn_file_actions_init";
+      goto exit;
+    }
+  actions_initialized = 1;
+
+  if (in != STDIN_FILE_NO)
+    {
+      ret = posix_spawn_file_actions_adddup2 (&actions, in, STDIN_FILE_NO);
+      if (ret)
        {
-         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);
+         *err = ret;
+         *errmsg = "posix_spawn_file_actions_adddup2";
+         goto exit;
        }
-      if (out != STDOUT_FILE_NO)
+
+      ret = posix_spawn_file_actions_addclose (&actions, in);
+      if (ret)
        {
-         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);
+         *err = ret;
+         *errmsg = "posix_spawn_file_actions_addclose";
+         goto exit;
        }
-      if (errdes != STDERR_FILE_NO)
+    }
+
+  if (out != STDOUT_FILE_NO)
+    {
+      ret = posix_spawn_file_actions_adddup2 (&actions, out, STDOUT_FILE_NO);
+      if (ret)
        {
-         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);
+         *err = ret;
+         *errmsg = "posix_spawn_file_actions_adddup2";
+         goto exit;
        }
-      if ((flags & PEX_STDERR_TO_STDOUT) != 0)
+
+      ret = posix_spawn_file_actions_addclose (&actions, out);
+      if (ret)
        {
-         if (dup2 (STDOUT_FILE_NO, STDERR_FILE_NO) < 0)
-           pex_child_error (obj, executable, "dup2", errno);
+         *err = ret;
+         *errmsg = "posix_spawn_file_actions_addclose";
+         goto exit;
        }
+    }
 
-      if (env)
-        environ = env;
+  if (errdes != STDERR_FILE_NO)
+    {
+      ret = posix_spawn_file_actions_adddup2 (&actions, errdes, STDERR_FILE_NO);
+      if (ret)
+       {
+         *err = ret;
+         *errmsg = "posix_spawn_file_actions_adddup2";
+         goto exit;
+       }
 
-      if ((flags & PEX_SEARCH) != 0)
+      ret = posix_spawn_file_actions_addclose (&actions, errdes);
+      if (ret)
        {
-         execvp (executable, argv);
-         pex_child_error (obj, executable, "execvp", errno);
+         *err = ret;
+         *errmsg = "posix_spawn_file_actions_addclose";
+         goto exit;
        }
-      else
+    }
+
+  if (toclose >= 0)
+    {
+      ret = posix_spawn_file_actions_addclose (&actions, toclose);
+      if (ret)
        {
-         execv (executable, argv);
-         pex_child_error (obj, executable, "execv", errno);
+         *err = ret;
+         *errmsg = "posix_spawn_file_actions_addclose";
+         goto exit;
        }
+    }
 
-      /* NOTREACHED */
-      return -1;
+  if ((flags & PEX_STDERR_TO_STDOUT) != 0)
+    {
+      ret = posix_spawn_file_actions_adddup2 (&actions, STDOUT_FILE_NO, STDERR_FILE_NO);
+      if (ret)
+       {
+         *err = ret;
+         *errmsg = "posix_spawn_file_actions_adddup2";
+         goto exit;
+       }
+    }
 
-    default:
-      /* Parent process.  */
-      if (in != STDIN_FILE_NO)
+  if ((flags & PEX_SEARCH) != 0)
+    {
+      ret = posix_spawnp (&pid, executable, &actions, &attr, argv, env ? env : environ);
+      if (ret)
        {
-         if (close (in) < 0)
-           {
-             *err = errno;
-             *errmsg = "close";
-             return -1;
-           }
+         *err = ret;
+         *errmsg = "posix_spawnp";
+         pid = -1; /* The value of pid is unspecified on failure.  */
+         goto exit;
        }
-      if (out != STDOUT_FILE_NO)
+    }
+  else
+    {
+      ret = posix_spawn (&pid, executable, &actions, &attr, argv, env ? env : environ);
+      if (ret)
        {
-         if (close (out) < 0)
-           {
-             *err = errno;
-             *errmsg = "close";
-             return -1;
-           }
+         *err = ret;
+         *errmsg = "posix_spawn";
+         pid = -1;
+         goto exit;
        }
-      if (errdes != STDERR_FILE_NO)
+    }
+
+exit:
+  if (actions_initialized)
+    posix_spawn_file_actions_destroy (&actions);
+  if (attr_initialized)
+    posix_spawnattr_destroy (&attr);
+
+  if (!*err && in != STDIN_FILE_NO)
+    if (close (in))
+      *errmsg = "close", *err = errno, pid = -1;
+  if (!*err && out != STDOUT_FILE_NO)
+    if (close (out))
+      *errmsg = "close", *err = errno, pid = -1;
+  if (!*err && errdes != STDERR_FILE_NO)
+    if (close (errdes))
+      *errmsg = "close", *err = errno, pid = -1;
+
+  return pid;
+}
+#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 (close (errdes) < 0)
+         if (fcntl (pipes[1], F_SETFD, FD_CLOEXEC) == -1)
            {
-             *err = errno;
-             *errmsg = "close";
-             return -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 = 1;
+  volatile int retries;
+
+  /* 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 ();
+      if (pid >= 0)
+       break;
+      sleep (sleep_interval);
+      sleep_interval *= 2;
+    }
+
+  switch (pid)
+    {
+    case -1:
+      if (do_pipe)
+       {
+         close (pipes[0]);
+         close (pipes[1]);
+       }
+      *err = errno;
+      *errmsg = VFORK_STRING;
+      return (pid_t) -1;
 
-      return (long) pid;
+    case 0:
+      /* Child process.  */
+      {
+       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 (pid_t) -1;
+
+    default:
+      /* Parent process.  */
+      {
+       /* 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)
+           failed.fn = "close", failed.err = errno;
+       if (!failed.fn && out != STDOUT_FILE_NO)
+         if (close (out) < 0)
+           failed.fn = "close", failed.err = errno;
+       if (!failed.fn && errdes != STDERR_FILE_NO)
+         if (close (errdes) < 0)
+           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,
+static pid_t
+pex_unix_wait (struct pex_obj *obj, pid_t pid, int *status,
               struct pex_time *time, int done, const char **errmsg,
               int *err)
 {