aboutsummaryrefslogtreecommitdiff
path: root/sysdeps/mach/hurd
diff options
context:
space:
mode:
authorRoland McGrath <roland@gnu.org>2001-12-16 22:10:46 +0000
committerRoland McGrath <roland@gnu.org>2001-12-16 22:10:46 +0000
commit03aae005044c863767c559d8d7c5b6cd62d72232 (patch)
tree037875899e6bc4271e2db21242aa4a158ba130f0 /sysdeps/mach/hurd
parent8eadd4f38180bd75084d76952d526575749eb877 (diff)
downloadglibc-03aae005044c863767c559d8d7c5b6cd62d72232.tar
glibc-03aae005044c863767c559d8d7c5b6cd62d72232.tar.gz
glibc-03aae005044c863767c559d8d7c5b6cd62d72232.tar.bz2
glibc-03aae005044c863767c559d8d7c5b6cd62d72232.zip
2001-12-16 Roland McGrath <roland@frob.com>
* sysdeps/mach/hurd/spawni.c: New file, by me with a couple fixes by Neal H Walfield <neal@cs.uml.edu>.
Diffstat (limited to 'sysdeps/mach/hurd')
-rw-r--r--sysdeps/mach/hurd/spawni.c751
1 files changed, 751 insertions, 0 deletions
diff --git a/sysdeps/mach/hurd/spawni.c b/sysdeps/mach/hurd/spawni.c
new file mode 100644
index 0000000000..4e2d089dcb
--- /dev/null
+++ b/sysdeps/mach/hurd/spawni.c
@@ -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;
+}