2001-12-16 Roland McGrath <roland@frob.com>
authorRoland McGrath <roland@gnu.org>
Sun, 16 Dec 2001 22:10:46 +0000 (22:10 +0000)
committerRoland McGrath <roland@gnu.org>
Sun, 16 Dec 2001 22:10:46 +0000 (22:10 +0000)
* sysdeps/mach/hurd/spawni.c: New file, by me with a
couple fixes by Neal H Walfield <neal@cs.uml.edu>.

sysdeps/mach/hurd/spawni.c [new file with mode: 0644]

diff --git a/sysdeps/mach/hurd/spawni.c b/sysdeps/mach/hurd/spawni.c
new file mode 100644 (file)
index 0000000..4e2d089
--- /dev/null
@@ -0,0 +1,751 @@
+/* spawn a new process running an executable.  Hurd version.
+   Copyright (C) 2001 Free Software Foundation, Inc.
+   This file is part of the GNU C Library.
+
+   The GNU C Library is free software; you can redistribute it and/or
+   modify it under the terms of the GNU Library General Public License as
+   published by the Free Software Foundation; either version 2 of the
+   License, or (at your option) any later version.
+
+   The GNU C Library 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
+   Library General Public License for more details.
+
+   You should have received a copy of the GNU Library General Public
+   License along with the GNU C Library; see the file COPYING.LIB.  If not,
+   write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
+   Boston, MA 02111-1307, USA.  */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <paths.h>
+#include <spawn.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <hurd.h>
+#include <hurd/signal.h>
+#include <hurd/fd.h>
+#include <hurd/id.h>
+#include <hurd/lookup.h>
+#include <hurd/resource.h>
+#include <assert.h>
+#include <argz.h>
+#include "spawn_int.h"
+
+/* Spawn a new process executing PATH with the attributes describes in *ATTRP.
+   Before running the process perform the actions described in FILE-ACTIONS. */
+int
+__spawni (pid_t *pid, const char *file,
+         const posix_spawn_file_actions_t *file_actions,
+         const posix_spawnattr_t *attrp,
+         char *const argv[], char *const envp[],
+         int use_path)
+{
+  pid_t new_pid;
+  char *path, *p, *name;
+  size_t len;
+  size_t pathlen;
+  short int flags;
+
+  /* The generic POSIX.1 implementation of posix_spawn uses fork and exec.
+     In traditional POSIX systems (Unix, Linux, etc), the only way to
+     create a new process is by fork, which also copies all the things from
+     the parent process that will be immediately wiped and replaced by the
+     exec.
+
+     This Hurd implementation works by doing an exec on a fresh task,
+     without ever doing all the work of fork.  The only work done by fork
+     that remains visible after an exec is registration with the proc
+     server, and the inheritance of various values and ports.  All those
+     inherited values and ports are what get collected up and passed in the
+     file_exec RPC by an exec call.  So we do the proc server registration
+     here, following the model of fork (see fork.c).  We then collect up
+     the inherited values and ports from this (parent) process following
+     the model of exec (see hurd/hurdexec.c), modify or replace each value
+     that fork would (plus the specific changes demanded by ATTRP and
+     FILE_ACTIONS), and make the file_exec RPC on the requested executable
+     file with the child process's task port rather than our own.  This
+     should be indistinguishable from the fork + exec implementation,
+     except that all errors will be detected here (in the parent process)
+     and return proper errno codes rather than the child dying with 127.
+
+     XXX The one exception to this supposed indistinguishableness is that
+     when posix_spawn_file_actions_addopen has been used, the parent
+     process can do various filesystem RPCs on the child's behalf, rather
+     than the child process doing it.  If these block due to a broken or
+     malicious filesystem server or just a blocked network fs or a serial
+     port waiting for carrier detect (!!), the parent's posix_spawn call
+     can block arbitrarily rather than just the child blocking.  Possible
+     solutions include:
+     * punt to plain fork + exec implementation if addopen was used
+     ** easy to do
+     ** gives up all benefits of this implementation in that case
+     * if addopen was used, don't do any file actions at all here;
+       instead, exec an installed helper program e.g.:
+       /libexec/spawn-helper close 3 dup2 1 2 open 0 /file 0x123 0666 exec /bin/foo foo a1 a2
+     ** extra exec might be more or less overhead than fork
+     * could do some weird half-fork thing where the child would inherit
+       our vm and run some code here, but not do the full work of fork
+
+     XXX Actually, the parent opens the executable file on behalf of
+     the child, and that has all the same issues.
+
+     I am favoring the half-fork solution.  That is, we do task_create with
+     vm inheritance, and we setjmp/longjmp the child like fork does.  But
+     rather than all the fork hair, the parent just packs up init/dtable
+     ports and does a single IPC to a receive right inserted in the child.  */
+
+  error_t err;
+  task_t task;
+  file_t execfile;
+  process_t proc;
+  auth_t auth;
+  int ints[INIT_INT_MAX];
+  file_t *dtable;
+  unsigned int dtablesize, orig_dtablesize, i;
+  struct hurd_port **dtable_cells;
+  char *dtable_cloexec;
+  struct hurd_userlink *ulink_dtable = NULL;
+  struct hurd_sigstate *ss;
+
+  /* For POSIX_SPAWN_RESETIDS, this reauthenticates our root/current
+     directory ports with the new AUTH port.  */
+  file_t rcrdir = MACH_PORT_NULL, rcwdir = MACH_PORT_NULL;
+  error_t reauthenticate (int which, file_t *result)
+    {
+      error_t err;
+      mach_port_t ref;
+      if (*result != MACH_PORT_NULL)
+       return 0;
+      ref = __mach_reply_port ();
+      err = HURD_PORT_USE
+       (&_hurd_ports[which],
+        ({
+          err = __io_reauthenticate (port, ref, MACH_MSG_TYPE_MAKE_SEND);
+          if (!err)
+            err = __auth_user_authenticate (auth,
+                                            ref, MACH_MSG_TYPE_MAKE_SEND,
+                                            result);
+          err;
+        }));
+      __mach_port_destroy (__mach_task_self (), ref);
+      return err;
+    }
+
+  /* Reauthenticate one of our file descriptors for the child.  A null
+     element of DTABLE_CELLS indicates a descriptor that was already
+     reauthenticated, or was newly opened on behalf of the child.  */
+  error_t reauthenticate_fd (int fd)
+    {
+      if (dtable_cells[fd] != NULL)
+       {
+         file_t newfile;
+         mach_port_t ref = __mach_reply_port ();
+         error_t err = __io_reauthenticate (dtable[fd],
+                                            ref, MACH_MSG_TYPE_MAKE_SEND);
+         if (!err)
+           err = __auth_user_authenticate (auth,
+                                           ref, MACH_MSG_TYPE_MAKE_SEND,
+                                           &newfile);
+         __mach_port_destroy (__mach_task_self (), ref);
+         if (err)
+           return err;
+         _hurd_port_free (dtable_cells[fd], &ulink_dtable[fd], dtable[fd]);
+         dtable_cells[fd] = NULL;
+         dtable[fd] = newfile;
+       }
+      return 0;
+    }
+
+  /* These callbacks are for looking up file names on behalf of the child.  */
+  error_t child_init_port (int which, error_t (*operate) (mach_port_t))
+    {
+      if (flags & POSIX_SPAWN_RESETIDS)
+       switch (which)
+         {
+         case INIT_PORT_AUTH:
+           return (*operate) (auth);
+         case INIT_PORT_CRDIR:
+           return (reauthenticate (INIT_PORT_CRDIR, &rcrdir)
+                   ?: (*operate) (rcrdir));
+         case INIT_PORT_CWDIR:
+           return (reauthenticate (INIT_PORT_CWDIR, &rcwdir)
+                   ?: (*operate) (rcwdir));
+         }
+      assert (which != INIT_PORT_PROC);
+      return _hurd_ports_use (which, operate);
+    }
+  file_t child_fd (int fd)
+    {
+      if ((unsigned int) fd < dtablesize && dtable[fd] != MACH_PORT_NULL)
+       {
+         if (flags & POSIX_SPAWN_RESETIDS)
+           {
+             /* Reauthenticate this descriptor right now,
+                since it is going to be used on behalf of the child.  */
+             errno = reauthenticate_fd (fd);
+             if (errno)
+               return MACH_PORT_NULL;
+           }
+         __mach_port_mod_refs (__mach_task_self (), dtable[fd],
+                               MACH_PORT_RIGHT_SEND, +1);
+         return dtable[fd];
+       }
+      errno = EBADF;
+      return MACH_PORT_NULL;
+    }
+  inline error_t child_lookup (const char *file, int oflag, mode_t mode,
+                              file_t *result)
+    {
+      return __hurd_file_name_lookup (&child_init_port, &child_fd, 0,
+                                     file, oflag, mode, result);
+    }
+
+
+  /* Do this once.  */
+  flags = attrp == NULL ? 0 : attrp->__flags;
+
+  /* Generate the new process.  We create a task that does not inherit our
+     memory, and then register it as our child like fork does.  See fork.c
+     for comments about the sequencing of these proc operations.  */
+
+  err = __task_create (__mach_task_self (), 0, &task);
+  if (err)
+    return __hurd_fail (err);
+  // From here down we must deallocate TASK and PROC before returning.
+  proc = MACH_PORT_NULL;
+  auth = MACH_PORT_NULL;
+  err = __USEPORT (PROC, __proc_task2pid (port, task, &new_pid));
+  if (!err)
+    err = __USEPORT (PROC, __proc_task2proc (port, task, &proc));
+  if (!err)
+    err = __USEPORT (PROC, __proc_child (port, task));
+  if (err)
+    goto out;
+
+  /* Load up the ints to give the new program.  */
+  memset (ints, 0, sizeof ints);
+  ints[INIT_UMASK] = _hurd_umask;
+  ints[INIT_TRACEMASK] = _hurdsig_traced;
+
+  ss = _hurd_self_sigstate ();
+
+  assert (! __spin_lock_locked (&ss->critical_section_lock));
+  __spin_lock (&ss->critical_section_lock);
+
+  __spin_lock (&ss->lock);
+  ints[INIT_SIGMASK] = ss->blocked;
+  ints[INIT_SIGPENDING] = ss->pending;
+  ints[INIT_SIGIGN] = 0;
+  /* Unless we were asked to reset all handlers to SIG_DFL,
+     pass down the set of signals that were set to SIG_IGN.  */
+  if ((flags & POSIX_SPAWN_SETSIGDEF) == 0)
+    for (i = 1; i < NSIG; ++i)
+      if (ss->actions[i].sa_handler == SIG_IGN)
+       ints[INIT_SIGIGN] |= __sigmask (i);
+
+  /* We hold the sigstate lock until the exec has failed so that no signal
+     can arrive between when we pack the blocked and ignored signals, and
+     when the exec actually happens.  A signal handler could change what
+     signals are blocked and ignored.  Either the change will be reflected
+     in the exec, or the signal will never be delivered.  Setting the
+     critical section flag avoids anything we call trying to acquire the
+     sigstate lock.  */
+
+  __spin_unlock (&ss->lock);
+
+  /* Set signal mask.  */
+  if ((flags & POSIX_SPAWN_SETSIGMASK) != 0)
+    ints[INIT_SIGMASK] = attrp->__ss;
+
+#ifdef _POSIX_PRIORITY_SCHEDULING
+  /* Set the scheduling algorithm and parameters.  */
+# error implement me
+  if ((flags & (POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER))
+      == POSIX_SPAWN_SETSCHEDPARAM)
+    {
+      if (__sched_setparam (0, &attrp->__sp) == -1)
+       _exit (SPAWN_ERROR);
+    }
+  else if ((flags & POSIX_SPAWN_SETSCHEDULER) != 0)
+    {
+      if (__sched_setscheduler (0, attrp->__policy,
+                               (flags & POSIX_SPAWN_SETSCHEDPARAM) != 0
+                               ? &attrp->__sp : NULL) == -1)
+       _exit (SPAWN_ERROR);
+    }
+#endif
+
+  /* Set the process group ID.  */
+  if (!err && (flags & POSIX_SPAWN_SETPGROUP) != 0)
+    err = __proc_setpgrp (proc, new_pid, attrp->__pgrp);
+
+  /* Set the effective user and group IDs.  */
+  if (!err && (flags & POSIX_SPAWN_RESETIDS) != 0)
+    {
+      /* We need a different auth port for the child.  */
+
+      __mutex_lock (&_hurd_id.lock);
+      err = _hurd_check_ids (); /* Get _hurd_id up to date.  */
+      if (!err && _hurd_id.rid_auth == MACH_PORT_NULL)
+       {
+         /* Set up _hurd_id.rid_auth.  This is a special auth server port
+            which uses the real uid and gid (the first aux uid and gid) as
+            the only effective uid and gid.  */
+
+         if (_hurd_id.aux.nuids < 1 || _hurd_id.aux.ngids < 1)
+           /* We do not have a real UID and GID.  Lose, lose, lose!  */
+           err = EGRATUITOUS;
+
+         /* Create a new auth port using our real UID and GID (the first
+            auxiliary UID and GID) as the only effective IDs.  */
+         if (!err)
+           err = __USEPORT (AUTH,
+                            __auth_makeauth (port,
+                                             NULL, MACH_MSG_TYPE_COPY_SEND, 0,
+                                             _hurd_id.aux.uids, 1,
+                                             _hurd_id.aux.uids,
+                                             _hurd_id.aux.nuids,
+                                             _hurd_id.aux.gids, 1,
+                                             _hurd_id.aux.gids,
+                                             _hurd_id.aux.ngids,
+                                             &_hurd_id.rid_auth));
+       }
+      if (!err)
+       {
+         /* Use the real-ID auth port in place of the normal one.  */
+         assert (_hurd_id.rid_auth != MACH_PORT_NULL);
+         auth = _hurd_id.rid_auth;
+         __mach_port_mod_refs (__mach_task_self (), auth,
+                               MACH_PORT_RIGHT_SEND, +1);
+       }
+      __mutex_unlock (&_hurd_id.lock);
+    }
+  else
+    /* Copy our existing auth port.  */
+    err = __USEPORT (AUTH, __mach_port_mod_refs (__mach_task_self (),
+                                                (auth = port),
+                                                MACH_PORT_RIGHT_SEND, +1));
+
+  if (err)
+    goto out;
+
+  /* Pack up the descriptor table to give the new program.
+     These descriptors will need to be reauthenticated below
+     if POSIX_SPAWN_RESETIDS is set.  */
+  __mutex_lock (&_hurd_dtable_lock);
+  dtablesize = _hurd_dtablesize;
+  orig_dtablesize = _hurd_dtablesize;
+  dtable = __alloca (dtablesize * sizeof (dtable[0]));
+  ulink_dtable = __alloca (dtablesize * sizeof (ulink_dtable[0]));
+  dtable_cells = __alloca (dtablesize * sizeof (dtable_cells[0]));
+  dtable_cloexec = __alloca (dtablesize);
+  for (i = 0; i < dtablesize; ++i)
+    {
+      struct hurd_fd *const d = _hurd_dtable[i];
+      if (d == NULL)
+       {
+         dtable[i] = MACH_PORT_NULL;
+         dtable_cells[i] = NULL;
+         continue;
+       }
+      /* Note that this might return MACH_PORT_NULL.  */
+      dtable[i] = _hurd_port_get (&d->port, &ulink_dtable[i]);
+      dtable_cells[i] = &d->port;
+      dtable_cloexec[i] = (d->flags & FD_CLOEXEC) != 0;
+    }
+  __mutex_unlock (&_hurd_dtable_lock);
+
+  /* Safe to let signals happen now.  */
+  _hurd_critical_section_unlock (ss);
+
+  /* Execute the file actions.  */
+  if (file_actions != NULL)
+    for (i = 0; i < file_actions->__used; ++i)
+      {
+       /* Close a file descriptor in the child.  */
+       error_t do_close (int fd)
+         {
+           if ((unsigned int)fd < dtablesize
+               && dtable[fd] != MACH_PORT_NULL)
+             {
+               if (dtable_cells[fd] == NULL)
+                 __mach_port_deallocate (__mach_task_self (), dtable[fd]);
+               else
+                 {
+                   _hurd_port_free (dtable_cells[fd],
+                                    &ulink_dtable[fd], dtable[fd]);
+                 }
+               dtable_cells[fd] = NULL;
+               dtable[fd] = MACH_PORT_NULL;
+               return 0;
+             }
+           return EBADF;
+         }
+
+       /* Make sure the dtable can hold NEWFD.  */
+#define EXPAND_DTABLE(newfd)                                                 \
+       ({                                                                    \
+         if ((unsigned int)newfd >= dtablesize                               \
+             && newfd < _hurd_rlimits[RLIMIT_OFILE].rlim_cur)                \
+           {                                                                 \
+             /* We need to expand the dtable for the child.  */              \
+             NEW_TABLE (dtable, newfd);                                      \
+             NEW_TABLE (ulink_dtable, newfd);                                \
+             NEW_TABLE (dtable_cells, newfd);                                \
+           }                                                                 \
+         ((unsigned int)newfd < dtablesize ? 0 : EMFILE);                    \
+       })
+#define NEW_TABLE(x, newfd) \
+  do { __typeof (x) new_##x = __alloca ((newfd + 1) * sizeof (x[0]));        \
+  memcpy (new_##x, x, dtablesize * sizeof (x[0]));                           \
+  memset (&new_##x[dtablesize], 0, (newfd + 1 - dtablesize) * sizeof (x[0])); \
+  x = new_##x; } while (0)
+
+       struct __spawn_action *action = &file_actions->__actions[i];
+
+       switch (action->tag)
+         {
+         case spawn_do_close:
+           err = do_close (action->action.close_action.fd);
+           break;
+
+         case spawn_do_dup2:
+           if ((unsigned int)action->action.dup2_action.fd < dtablesize
+               && dtable[action->action.dup2_action.fd] != MACH_PORT_NULL)
+             {
+               const int fd = action->action.dup2_action.fd;
+               const int newfd = action->action.dup2_action.newfd;
+               // dup2 always clears any old FD_CLOEXEC flag on the new fd.
+               if (newfd < orig_dtablesize)
+                 dtable_cloexec[newfd] = 0;
+               if (fd == newfd)
+                 // Same is same as same was.
+                 break;
+               err = EXPAND_DTABLE (newfd);
+               if (!err)
+                 {
+                   /* Close the old NEWFD and replace it with FD's
+                      contents, which can be either an original
+                      descriptor (DTABLE_CELLS[FD] != 0) or a new
+                      right that we acquired in this function.  */
+                   do_close (newfd);
+                   dtable_cells[newfd] = dtable_cells[fd];
+                   if (dtable_cells[newfd] != NULL)
+                     dtable[newfd] = _hurd_port_get (dtable_cells[newfd],
+                                                     &ulink_dtable[newfd]);
+                   else
+                     {
+                       dtable[newfd] = dtable[fd];
+                       err = __mach_port_mod_refs (__mach_task_self (),
+                                                   dtable[fd],
+                                                   MACH_PORT_RIGHT_SEND, +1);
+                     }
+                 }
+             }
+           else
+             // The old FD specified was bogus.
+             err = EBADF;
+           break;
+
+         case spawn_do_open:
+           /* Open a file on behalf of the child.
+
+              XXX note that this can subject the parent to arbitrary
+              delays waiting for the files to open.  I don't know what the
+              spec says about this.  If it's not permissible, then this
+              whole forkless implementation is probably untenable.  */
+           {
+             const int fd = action->action.open_action.fd;
+
+             do_close (fd);
+             if (fd < orig_dtablesize)
+               dtable_cloexec[fd] = 0;
+             err = EXPAND_DTABLE (fd);
+             if (err)
+               break;
+
+             err = child_lookup (action->action.open_action.path,
+                                 action->action.open_action.oflag,
+                                 action->action.open_action.mode,
+                                 &dtable[fd]);
+             dtable_cells[fd] = NULL;
+             break;
+           }
+         }
+
+       if (err)
+         goto out;
+      }
+
+  /* Only now can we perform FD_CLOEXEC.  We had to leave the descriptors
+     unmolested for the file actions to use.  Note that the DTABLE_CLOEXEC
+     array is never expanded by file actions, so it might now have fewer
+     than DTABLESIZE elements.  */
+  for (i = 0; i < orig_dtablesize; ++i)
+    if (dtable[i] != MACH_PORT_NULL && dtable_cloexec[i])
+      {
+       assert (dtable_cells[i] != NULL);
+       _hurd_port_free (dtable_cells[i], &ulink_dtable[i], dtable[i]);
+       dtable[i] = MACH_PORT_NULL;
+      }
+
+  /* Prune trailing null ports from the descriptor table.  */
+  while (dtablesize > 0 && dtable[dtablesize - 1] == MACH_PORT_NULL)
+    --dtablesize;
+
+  if (flags & POSIX_SPAWN_RESETIDS)
+    {
+      /* Reauthenticate all the child's ports with its new auth handle.  */
+
+      mach_port_t ref;
+      process_t newproc;
+
+      /* Reauthenticate with the proc server.  */
+      ref = __mach_reply_port ();
+      err = __proc_reauthenticate (proc, ref, MACH_MSG_TYPE_MAKE_SEND);
+      if (!err)
+       err = __auth_user_authenticate (auth,
+                                       ref, MACH_MSG_TYPE_MAKE_SEND,
+                                       &newproc);
+      __mach_port_destroy (__mach_task_self (), ref);
+      if (!err)
+       {
+         __mach_port_deallocate (__mach_task_self (), proc);
+         proc = newproc;
+       }
+
+      if (!err)
+       err = reauthenticate (INIT_PORT_CRDIR, &rcrdir);
+      if (!err)
+       err = reauthenticate (INIT_PORT_CWDIR, &rcwdir);
+
+      /* We must reauthenticate all the fds except those that came from
+        `spawn_do_open' file actions, which were opened using the child's
+        auth port to begin with.  */
+      for (i = 0; !err && i < dtablesize; ++i)
+       err = reauthenticate_fd (i);
+    }
+  if (err)
+    goto out;
+
+  /* Now we are ready to open the executable file using the child's ports.
+     We do this after performing all the file actions so the order of
+     events is the same as for a fork, exec sequence.  This affects things
+     like the meaning of a /dev/fd file name, as well as which error
+     conditions are diagnosed first and what side effects (file creation,
+     etc) can be observed before what errors.  */
+
+  if (! use_path || strchr (file, '/') != NULL)
+    /* The FILE parameter is actually a path.  */
+    err = child_lookup (file, O_EXEC, 0, &execfile);
+  else
+    {
+      /* We have to search for FILE on the path.  */
+      path = getenv ("PATH");
+      if (path == NULL)
+       {
+         /* There is no `PATH' in the environment.
+            The default search path is the current directory
+            followed by the path `confstr' returns for `_CS_PATH'.  */
+         len = confstr (_CS_PATH, (char *) NULL, 0);
+         path = (char *) __alloca (1 + len);
+         path[0] = ':';
+         (void) confstr (_CS_PATH, path + 1, len);
+       }
+
+      len = strlen (file) + 1;
+      pathlen = strlen (path);
+      name = __alloca (pathlen + len + 1);
+      /* Copy the file name at the top.  */
+      name = (char *) memcpy (name + pathlen + 1, file, len);
+      /* And add the slash.  */
+      *--name = '/';
+
+      p = path;
+      do
+       {
+         char *startp;
+
+         path = p;
+         p = __strchrnul (path, ':');
+
+         if (p == path)
+           /* Two adjacent colons, or a colon at the beginning or the end
+              of `PATH' means to search the current directory.  */
+           startp = name + 1;
+         else
+           startp = (char *) memcpy (name - (p - path), path, p - path);
+
+         /* Try to open this file name.  */
+         err = child_lookup (startp, O_EXEC, 0, &execfile);
+         switch (err)
+           {
+           case EACCES:
+           case ENOENT:
+           case ESTALE:
+           case ENOTDIR:
+             /* Those errors indicate the file is missing or not executable
+v               by us, in which case we want to just try the next path
+                directory.  */
+             continue;
+
+           case 0:             /* Success! */
+           default:
+             /* Some other error means we found an executable file, but
+                something went wrong executing it; return the error to our
+                caller.  */
+             break;
+           }
+
+         // We only get here when we are done looking for the file.
+         break;
+       }
+      while (*p++ != '\0');
+    }
+  if (err)
+    goto out;
+
+  /* Almost there!  */
+  {
+    mach_port_t ports[_hurd_nports];
+    struct hurd_userlink ulink_ports[_hurd_nports];
+    char *args = NULL, *env = NULL;
+    size_t argslen = 0, envlen = 0;
+
+    inline error_t exec (file_t file)
+      {
+       return __file_exec (file, task,
+                           (__sigismember (&_hurdsig_traced, SIGKILL)
+                            ? EXEC_SIGTRAP : 0),
+                           args, argslen, env, envlen,
+                           dtable, MACH_MSG_TYPE_COPY_SEND, dtablesize,
+                           ports, MACH_MSG_TYPE_COPY_SEND, _hurd_nports,
+                           ints, INIT_INT_MAX,
+                           NULL, 0, NULL, 0);
+      }
+
+    /* Now we are out of things that can fail before the file_exec RPC,
+       for which everything else must be prepared.  The only thing left
+       to do is packing up the argument and environment strings,
+       and the array of init ports.  */
+
+    if (argv != NULL)
+      err = __argz_create (argv, &args, &argslen);
+    if (!err && envp != NULL)
+      err = __argz_create (envp, &env, &envlen);
+
+    /* Load up the ports to give to the new program.
+       Note the loop/switch below must parallel exactly to release refs.  */
+    for (i = 0; i < _hurd_nports; ++i)
+      {
+       switch (i)
+         {
+         case INIT_PORT_AUTH:
+           ports[i] = auth;
+           continue;
+         case INIT_PORT_PROC:
+           ports[i] = proc;
+           continue;
+         case INIT_PORT_CRDIR:
+           if (flags & POSIX_SPAWN_RESETIDS)
+             {
+               ports[i] = rcrdir;
+               continue;
+             }
+           break;
+         case INIT_PORT_CWDIR:
+           if (flags & POSIX_SPAWN_RESETIDS)
+             {
+               ports[i] = rcwdir;
+               continue;
+             }
+           break;
+         }
+       ports[i] = _hurd_port_get (&_hurd_ports[i], &ulink_ports[i]);
+      }
+
+    /* Finally, try executing the file we opened.  */
+    if (!err)
+      err = exec (execfile);
+    __mach_port_deallocate (__mach_task_self (), execfile);
+
+    if (err == ENOEXEC)
+      {
+       /* The file is accessible but it is not an executable file.
+          Invoke the shell to interpret it as a script.  */
+       err = __argz_insert (&args, &argslen, args, _PATH_BSHELL);
+       if (!err)
+         err = child_lookup (_PATH_BSHELL, O_EXEC, 0, &execfile);
+       if (!err)
+         {
+           err = exec (execfile);
+           __mach_port_deallocate (__mach_task_self (), execfile);
+         }
+      }
+
+    /* Release the references just packed up in PORTS.
+       This switch must always parallel the one above that fills PORTS.  */
+    for (i = 0; i < _hurd_nports; ++i)
+      {
+       switch (i)
+         {
+         case INIT_PORT_AUTH:
+         case INIT_PORT_PROC:
+           continue;
+         case INIT_PORT_CRDIR:
+           if (flags & POSIX_SPAWN_RESETIDS)
+             continue;
+           break;
+         case INIT_PORT_CWDIR:
+           if (flags & POSIX_SPAWN_RESETIDS)
+             continue;
+           break;
+         }
+       _hurd_port_free (&_hurd_ports[i], &ulink_ports[i], ports[i]);
+      }
+
+    free (args);
+    free (env);
+  }
+
+  /* We did it!  We have a child!  */
+  if (pid != NULL)
+    *pid = new_pid;
+
+ out:
+  /* Clean up all the references we are now holding.  */
+
+  if (task != MACH_PORT_NULL)
+    {
+      if (err)
+       /* We failed after creating the task, so kill it.  */
+       __task_terminate (task);
+      __mach_port_deallocate (__mach_task_self (), task);
+    }
+  __mach_port_deallocate (__mach_task_self (), auth);
+  __mach_port_deallocate (__mach_task_self (), proc);
+  if (rcrdir != MACH_PORT_NULL)
+    __mach_port_deallocate (__mach_task_self (), rcrdir);
+  if (rcwdir != MACH_PORT_NULL)
+    __mach_port_deallocate (__mach_task_self (), rcwdir);
+
+  if (ulink_dtable)
+    /* Release references to the file descriptor ports.  */
+    for (i = 0; i < dtablesize; ++i)
+      if (dtable[i] != MACH_PORT_NULL)
+       {
+         if (dtable_cells[i] == NULL)
+           __mach_port_deallocate (__mach_task_self (), dtable[i]);
+         else
+           _hurd_port_free (dtable_cells[i], &ulink_dtable[i], dtable[i]);
+       }
+
+  if (err)
+    /* This hack canonicalizes the error code that we return.  */
+    err = (__hurd_fail (err), errno);
+
+  return err;
+}