| /* Guts of POSIX spawn interface. Generic POSIX.1 version. |
| Copyright (C) 2000-2006, 2008-2012 Free Software Foundation, Inc. |
| This file is part of the GNU C Library. |
| |
| This program is free software: you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 3 of the License, or |
| (at your option) any later version. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with this program. If not, see <http://www.gnu.org/licenses/>. */ |
| |
| #include <config.h> |
| |
| /* Specification. */ |
| #include <spawn.h> |
| #include "spawn_int.h" |
| |
| #include <alloca.h> |
| #include <errno.h> |
| |
| #include <fcntl.h> |
| #ifndef O_LARGEFILE |
| # define O_LARGEFILE 0 |
| #endif |
| |
| #if _LIBC || HAVE_PATHS_H |
| # include <paths.h> |
| #else |
| # define _PATH_BSHELL "/bin/sh" |
| #endif |
| |
| #include <signal.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #if _LIBC |
| # include <not-cancel.h> |
| #else |
| # define close_not_cancel close |
| # define open_not_cancel open |
| #endif |
| |
| #if _LIBC |
| # include <local-setxid.h> |
| #else |
| # if !HAVE_SETEUID |
| # define seteuid(id) setresuid (-1, id, -1) |
| # endif |
| # if !HAVE_SETEGID |
| # define setegid(id) setresgid (-1, id, -1) |
| # endif |
| # define local_seteuid(id) seteuid (id) |
| # define local_setegid(id) setegid (id) |
| #endif |
| |
| #if _LIBC |
| # define alloca __alloca |
| # define execve __execve |
| # define dup2 __dup2 |
| # define fork __fork |
| # define getgid __getgid |
| # define getuid __getuid |
| # define sched_setparam __sched_setparam |
| # define sched_setscheduler __sched_setscheduler |
| # define setpgid __setpgid |
| # define sigaction __sigaction |
| # define sigismember __sigismember |
| # define sigprocmask __sigprocmask |
| # define strchrnul __strchrnul |
| # define vfork __vfork |
| #else |
| # undef internal_function |
| # define internal_function /* empty */ |
| #endif |
| |
| |
| /* The Unix standard contains a long explanation of the way to signal |
| an error after the fork() was successful. Since no new wait status |
| was wanted there is no way to signal an error using one of the |
| available methods. The committee chose to signal an error by a |
| normal program exit with the exit code 127. */ |
| #define SPAWN_ERROR 127 |
| |
| |
| #if (defined _WIN32 || defined __WIN32__) && ! defined __CYGWIN__ |
| |
| /* Native Windows API. */ |
| 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) |
| { |
| /* Not yet implemented. */ |
| return ENOSYS; |
| } |
| |
| #else |
| |
| |
| /* The file is accessible but it is not an executable file. Invoke |
| the shell to interpret it as a script. */ |
| static void |
| internal_function |
| script_execute (const char *file, char *const argv[], char *const envp[]) |
| { |
| /* Count the arguments. */ |
| int argc = 0; |
| while (argv[argc++]) |
| ; |
| |
| /* Construct an argument list for the shell. */ |
| { |
| char **new_argv = (char **) alloca ((argc + 1) * sizeof (char *)); |
| new_argv[0] = (char *) _PATH_BSHELL; |
| new_argv[1] = (char *) file; |
| while (argc > 1) |
| { |
| new_argv[argc] = argv[argc - 1]; |
| --argc; |
| } |
| |
| /* Execute the shell. */ |
| execve (new_argv[0], new_argv, envp); |
| } |
| } |
| |
| |
| /* 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; |
| |
| /* Do this once. */ |
| short int flags = attrp == NULL ? 0 : attrp->_flags; |
| |
| /* Avoid gcc warning |
| "variable 'flags' might be clobbered by 'longjmp' or 'vfork'" */ |
| (void) &flags; |
| |
| /* Generate the new process. */ |
| #if HAVE_VFORK |
| if ((flags & POSIX_SPAWN_USEVFORK) != 0 |
| /* If no major work is done, allow using vfork. Note that we |
| might perform the path searching. But this would be done by |
| a call to execvp(), too, and such a call must be OK according |
| to POSIX. */ |
| || ((flags & (POSIX_SPAWN_SETSIGMASK | POSIX_SPAWN_SETSIGDEF |
| | POSIX_SPAWN_SETSCHEDPARAM | POSIX_SPAWN_SETSCHEDULER |
| | POSIX_SPAWN_SETPGROUP | POSIX_SPAWN_RESETIDS)) == 0 |
| && file_actions == NULL)) |
| new_pid = vfork (); |
| else |
| #endif |
| new_pid = fork (); |
| |
| if (new_pid != 0) |
| { |
| if (new_pid < 0) |
| return errno; |
| |
| /* The call was successful. Store the PID if necessary. */ |
| if (pid != NULL) |
| *pid = new_pid; |
| |
| return 0; |
| } |
| |
| /* Set signal mask. */ |
| if ((flags & POSIX_SPAWN_SETSIGMASK) != 0 |
| && sigprocmask (SIG_SETMASK, &attrp->_ss, NULL) != 0) |
| _exit (SPAWN_ERROR); |
| |
| /* Set signal default action. */ |
| if ((flags & POSIX_SPAWN_SETSIGDEF) != 0) |
| { |
| /* We have to iterate over all signals. This could possibly be |
| done better but it requires system specific solutions since |
| the sigset_t data type can be very different on different |
| architectures. */ |
| int sig; |
| struct sigaction sa; |
| |
| memset (&sa, '\0', sizeof (sa)); |
| sa.sa_handler = SIG_DFL; |
| |
| for (sig = 1; sig <= NSIG; ++sig) |
| if (sigismember (&attrp->_sd, sig) != 0 |
| && sigaction (sig, &sa, NULL) != 0) |
| _exit (SPAWN_ERROR); |
| |
| } |
| |
| #if (_LIBC ? defined _POSIX_PRIORITY_SCHEDULING : HAVE_SCHED_SETPARAM && HAVE_SCHED_SETSCHEDULER) |
| /* Set the scheduling algorithm and parameters. */ |
| 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 ((flags & POSIX_SPAWN_SETPGROUP) != 0 |
| && setpgid (0, attrp->_pgrp) != 0) |
| _exit (SPAWN_ERROR); |
| |
| /* Set the effective user and group IDs. */ |
| if ((flags & POSIX_SPAWN_RESETIDS) != 0 |
| && (local_seteuid (getuid ()) != 0 |
| || local_setegid (getgid ()) != 0)) |
| _exit (SPAWN_ERROR); |
| |
| /* Execute the file actions. */ |
| if (file_actions != NULL) |
| { |
| int cnt; |
| |
| for (cnt = 0; cnt < file_actions->_used; ++cnt) |
| { |
| struct __spawn_action *action = &file_actions->_actions[cnt]; |
| |
| switch (action->tag) |
| { |
| case spawn_do_close: |
| if (close_not_cancel (action->action.close_action.fd) != 0) |
| /* Signal the error. */ |
| _exit (SPAWN_ERROR); |
| break; |
| |
| case spawn_do_open: |
| { |
| int new_fd = open_not_cancel (action->action.open_action.path, |
| action->action.open_action.oflag |
| | O_LARGEFILE, |
| action->action.open_action.mode); |
| |
| if (new_fd == -1) |
| /* The 'open' call failed. */ |
| _exit (SPAWN_ERROR); |
| |
| /* Make sure the desired file descriptor is used. */ |
| if (new_fd != action->action.open_action.fd) |
| { |
| if (dup2 (new_fd, action->action.open_action.fd) |
| != action->action.open_action.fd) |
| /* The 'dup2' call failed. */ |
| _exit (SPAWN_ERROR); |
| |
| if (close_not_cancel (new_fd) != 0) |
| /* The 'close' call failed. */ |
| _exit (SPAWN_ERROR); |
| } |
| } |
| break; |
| |
| case spawn_do_dup2: |
| if (dup2 (action->action.dup2_action.fd, |
| action->action.dup2_action.newfd) |
| != action->action.dup2_action.newfd) |
| /* The 'dup2' call failed. */ |
| _exit (SPAWN_ERROR); |
| break; |
| } |
| } |
| } |
| |
| if (! use_path || strchr (file, '/') != NULL) |
| { |
| /* The FILE parameter is actually a path. */ |
| execve (file, argv, envp); |
| |
| if (errno == ENOEXEC) |
| script_execute (file, argv, envp); |
| |
| /* Oh, oh. 'execve' returns. This is bad. */ |
| _exit (SPAWN_ERROR); |
| } |
| |
| /* We have to search for FILE on the path. */ |
| path = getenv ("PATH"); |
| if (path == NULL) |
| { |
| #if HAVE_CONFSTR |
| /* 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); |
| #else |
| /* Pretend that the PATH contains only the current directory. */ |
| path = ""; |
| #endif |
| } |
| |
| 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 execute this name. If it works, execv will not return. */ |
| execve (startp, argv, envp); |
| |
| if (errno == ENOEXEC) |
| script_execute (startp, argv, envp); |
| |
| switch (errno) |
| { |
| case EACCES: |
| case ENOENT: |
| case ESTALE: |
| case ENOTDIR: |
| /* Those errors indicate the file is missing or not executable |
| by us, in which case we want to just try the next path |
| directory. */ |
| break; |
| |
| default: |
| /* Some other error means we found an executable file, but |
| something went wrong executing it; return the error to our |
| caller. */ |
| _exit (SPAWN_ERROR); |
| } |
| } |
| while (*p++ != '\0'); |
| |
| /* Return with an error. */ |
| _exit (SPAWN_ERROR); |
| } |
| |
| #endif |