process 1.6.12.0 → 1.6.13.0
raw patch · 14 files changed
+938/−408 lines, 14 files
Files
- System/Process/Posix.hs +8/−7
- cbits/posix/find_executable.c +79/−0
- cbits/posix/fork_exec.c +312/−0
- cbits/posix/posix_spawn.c +211/−0
- cbits/posix/runProcess.c +156/−370
- cbits/win32/runProcess.c +4/−0
- changelog.md +4/−0
- configure +115/−0
- configure.ac +7/−0
- include/HsProcessConfig.h.in +25/−0
- include/processFlags.h +1/−0
- include/runProcess.h +6/−29
- process.cabal +4/−1
- test/main.hs +6/−1
System/Process/Posix.hs view
@@ -139,18 +139,20 @@ when mb_delegate_ctlc startDelegateControlC + let flags = (if mb_close_fds then RUN_PROCESS_IN_CLOSE_FDS else 0)+ .|.(if mb_create_group then RUN_PROCESS_IN_NEW_GROUP else 0)+ .|.(if mb_detach_console then RUN_PROCESS_DETACHED else 0)+ .|.(if mb_create_new_console then RUN_PROCESS_NEW_CONSOLE else 0)+ .|.(if mb_new_session then RUN_PROCESS_NEW_SESSION else 0)+ .|.(if mb_delegate_ctlc then RESET_INT_QUIT_HANDLERS else 0)+ -- See the comment on runInteractiveProcess_lock proc_handle <- withMVar runInteractiveProcess_lock $ \_ -> c_runInteractiveProcess pargs pWorkDir pEnv fdin fdout fderr pfdStdInput pfdStdOutput pfdStdError pChildGroup pChildUser- (if mb_delegate_ctlc then 1 else 0)- ((if mb_close_fds then RUN_PROCESS_IN_CLOSE_FDS else 0)- .|.(if mb_create_group then RUN_PROCESS_IN_NEW_GROUP else 0)- .|.(if mb_detach_console then RUN_PROCESS_DETACHED else 0)- .|.(if mb_create_new_console then RUN_PROCESS_NEW_CONSOLE else 0)- .|.(if mb_new_session then RUN_PROCESS_NEW_SESSION else 0))+ flags pFailedDoing when (proc_handle == -1) $ do@@ -273,7 +275,6 @@ -> Ptr FD -> Ptr CGid -> Ptr CUid- -> CInt -- reset child's SIGINT & SIGQUIT handlers -> CInt -- flags -> Ptr CString -> IO PHANDLE
+ cbits/posix/find_executable.c view
@@ -0,0 +1,79 @@+/* ----------------------------------------------------------------------------+ * search path search logic+ * (c) Ben Gamari 2021+ */++#include <string.h>+#include <unistd.h>+#include <stdlib.h>+#include <stdio.h>+#include <stdbool.h>++#include "common.h"++// the below is only necessary when we don't have execvpe.+#if !defined(HAVE_execvpe)++/* Return true if the given file exists and is an executable. */+static bool is_executable(const char *path) {+ return access(path, X_OK) == 0;+}++/* Find an executable with the given filename in the given search path. The+ * result must be freed by the caller. Returns NULL if a matching file is not+ * found.+ */+static char *find_in_search_path(char *search_path, const char *filename) {+ const int filename_len = strlen(filename);+ char *tokbuf;+ char *path = strtok_r(search_path, ":", &tokbuf);+ while (path != NULL) {+ const int tmp_len = filename_len + 1 + strlen(path) + 1;+ char *tmp = malloc(tmp_len);+ snprintf(tmp, tmp_len, "%s/%s", path, filename);+ if (is_executable(tmp)) {+ return tmp;+ } else {+ free(tmp);+ }++ path = strtok_r(NULL, ":", &tokbuf);+ }+ return NULL;+}++/* Identify the executable search path. The result must be freed by the caller. */+static char *get_executable_search_path(void) {+ char *search_path;++ search_path = getenv("PATH");+ if (search_path) {+ search_path = strdup(search_path);+ return search_path;+ }++#if defined(HAVE_CONFSTR)+ int len = confstr(_CS_PATH, NULL, 0);+ search_path = malloc(len + 1)+ if (search_path != NULL) {+ search_path[0] = ':';+ (void) confstr (_CS_PATH, search_path + 1, len);+ return search_path;+ }+#endif++ return strdup(":");+}++/* Find the given executable in the executable search path. */+char *find_executable(char *filename) {+ /* If it's an absolute or relative path name, it's easy. */+ if (strchr(filename, '/') && is_executable(filename)) {+ return filename;+ }++ char *search_path = get_executable_search_path();+ return find_in_search_path(search_path, filename);+}++#endif
+ cbits/posix/fork_exec.c view
@@ -0,0 +1,312 @@+#include "common.h"++#include <sys/types.h>+#include <sys/wait.h>+#include <errno.h>+#include <unistd.h>+#include <stdlib.h>++#if !(defined(_MSC_VER) || defined(__MINGW32__) || defined(_WIN32))+#include <pwd.h>+#include <grp.h>+#endif++#if defined(HAVE_FCNTL_H)+#include <fcntl.h>+#endif++#if defined(HAVE_SIGNAL_H)+#include <signal.h>+#endif++#if defined(HAVE_VFORK_H)+#include <vfork.h>+#endif++#include <Rts.h>++#if defined(HAVE_WORKING_VFORK)+#define myfork vfork+#elif defined(HAVE_WORKING_FORK)+#define myfork fork+// We don't need a fork command on Windows+#else+#error Cannot find a working fork command+#endif++// Rts internal API, not exposed in a public header file:+extern void blockUserSignals(void);+extern void unblockUserSignals(void);++__attribute__((__noreturn__))+static void+child_failed(int pipe, const char *failed_doing) {+ int err;+ ssize_t unused __attribute__((unused));++ err = errno;+ // Having the child send the failed_doing pointer across the pipe is safe as+ // we know that the child still has the same address space as the parent.+ unused = write(pipe, &failed_doing, sizeof(failed_doing));+ unused = write(pipe, &err, sizeof(err));+ // As a fallback, exit+ _exit(127);+}++static int+setup_std_handle_fork(int fd,+ struct std_handle *b,+ int pipe)+{+ switch (b->behavior) {+ case STD_HANDLE_CLOSE:+ if (close(fd) == -1) {+ child_failed(pipe, "close");+ return -1;+ }+ return 0;++ case STD_HANDLE_USE_FD:+ if (dup2(b->use_fd, fd) == -1) {+ child_failed(pipe, "dup2");+ }+ return 0;++ case STD_HANDLE_USE_PIPE:+ if (b->use_pipe.child_end != fd) {+ if (dup2(b->use_pipe.child_end, fd) == -1) {+ child_failed(pipe, "dup2(child_end)");+ }+ if (close(b->use_pipe.child_end) == -1) {+ child_failed(pipe, "close(child_end)");+ }+ }+ if (close(b->use_pipe.parent_end) == -1) {+ child_failed(pipe, "close(parent_end)");+ }+ break;+ }+}++/* Try spawning with fork. */+ProcHandle+do_spawn_fork (char *const args[],+ char *workingDirectory, char **environment,+ struct std_handle *stdInHdl,+ struct std_handle *stdOutHdl,+ struct std_handle *stdErrHdl,+ gid_t *childGroup, uid_t *childUser,+ int flags,+ char **failed_doing)+{+ int forkCommunicationFds[2];+ int r = pipe(forkCommunicationFds);+ if (r == -1) {+ *failed_doing = "pipe";+ return -1;+ }++ // Block signals with Haskell handlers. The danger here is that+ // with the threaded RTS, a signal arrives in the child process,+ // the RTS writes the signal information into the pipe (which is+ // shared between parent and child), and the parent behaves as if+ // the signal had been raised.+ blockUserSignals();++ // See #4074. Sometimes fork() gets interrupted by the timer+ // signal and keeps restarting indefinitely.+ stopTimer();++ // N.B. execvpe is not supposed on some platforms. In this case+ // we emulate this using fork and exec. However, to safely do so+ // we need to perform all allocations *prior* to forking. Consequently, we+ // need to find_executable before forking.+#if !defined(HAVE_execvpe)+ char *exec_path;+ if (environment) {+ exec_path = find_executable(args[0]);+ }+#endif++ int pid = myfork();+ switch(pid)+ {+ case -1:+ unblockUserSignals();+ startTimer();+ close(forkCommunicationFds[0]);+ close(forkCommunicationFds[1]);+ *failed_doing = "fork";+ return -1;++ case 0:+ // WARNING! We may now be in the child of vfork(), and any+ // memory we modify below may also be seen in the parent+ // process.++ close(forkCommunicationFds[0]);+ fcntl(forkCommunicationFds[1], F_SETFD, FD_CLOEXEC);++ if ((flags & RUN_PROCESS_NEW_SESSION) != 0) {+ setsid();+ }+ if ((flags & RUN_PROCESS_IN_NEW_GROUP) != 0) {+ setpgid(0, 0);+ }++ if (childGroup) {+ if (setgid( *childGroup) != 0) {+ // ERROR+ child_failed(forkCommunicationFds[1], "setgid");+ }+ }++ if (childUser) {+ // Using setuid properly first requires that we initgroups.+ // However, to do this we must know the username of the user we are+ // switching to.+ struct passwd pw;+ struct passwd *res = NULL;+ int buf_len = sysconf(_SC_GETPW_R_SIZE_MAX);+ // TODO: Strictly speaking malloc is a no-no after fork() since it+ // may try to take a lock+ char *buf = malloc(buf_len);+ gid_t suppl_gid = childGroup ? *childGroup : getgid();+ if ( getpwuid_r(*childUser, &pw, buf, buf_len, &res) != 0) {+ child_failed(forkCommunicationFds[1], "getpwuid");+ }+ if ( res == NULL ) {+ child_failed(forkCommunicationFds[1], "getpwuid");+ }+ if ( initgroups(res->pw_name, suppl_gid) != 0) {+ child_failed(forkCommunicationFds[1], "initgroups");+ }+ if ( setuid( *childUser) != 0) {+ // ERROR+ child_failed(forkCommunicationFds[1], "setuid");+ }+ }++ unblockUserSignals();++ if (workingDirectory) {+ if (chdir (workingDirectory) < 0) {+ child_failed(forkCommunicationFds[1], "chdir");+ }+ }++ // Note [Ordering of handle closing]+ // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~+ // Ordering matters here. If any of the FDs+ // 0,1,2 were initially closed, then our pipes may have used+ // these FDs. So when we dup2 the pipe FDs down to 0,1,2, we+ // must do it in that order, otherwise we could overwrite an+ // FD that we need later. See ticket #431.++ setup_std_handle_fork(STDIN_FILENO, stdInHdl, forkCommunicationFds[1]);+ setup_std_handle_fork(STDOUT_FILENO, stdOutHdl, forkCommunicationFds[1]);+ setup_std_handle_fork(STDERR_FILENO, stdErrHdl, forkCommunicationFds[1]);++ if ((flags & RUN_PROCESS_IN_CLOSE_FDS) != 0) {+ int max_fd = get_max_fd();+ // XXX Not the pipe+ for (int i = 3; i < max_fd; i++) {+ if (i != forkCommunicationFds[1]) {+ close(i);+ }+ }+ }++ /* Reset the SIGINT/SIGQUIT signal handlers in the child, if requested+ */+ if (flags & RESET_INT_QUIT_HANDLERS != 0) {+ struct sigaction dfl;+ (void)sigemptyset(&dfl.sa_mask);+ dfl.sa_flags = 0;+ dfl.sa_handler = SIG_DFL;+ (void)sigaction(SIGINT, &dfl, NULL);+ (void)sigaction(SIGQUIT, &dfl, NULL);+ }++ /* the child */+ if (environment) {+#if defined(HAVE_execvpe)+ // XXX Check result+ execvpe(args[0], args, environment);+#else+ // XXX Check result+ execve(exec_path, args, environment);+#endif+ } else {+ // XXX Check result+ execvp(args[0], args);+ }++ child_failed(forkCommunicationFds[1], "exec");++ default:+ if ((flags & RUN_PROCESS_IN_NEW_GROUP) != 0) {+ setpgid(pid, pid);+ }+ close(forkCommunicationFds[1]);+ fcntl(forkCommunicationFds[0], F_SETFD, FD_CLOEXEC);++ break;+ }++ // If the child process had a problem, then it will tell us via the+ // forkCommunicationFds pipe. First we try to read what the problem+ // was. Note that if none of these conditionals match then we fall+ // through and just return pid.+ char *fail_reason;+ r = read(forkCommunicationFds[0], &fail_reason, sizeof(fail_reason));+ if (r == -1) {+ *failed_doing = "read pipe";+ pid = -1;+ }+ else if (r == sizeof(fail_reason)) {+ *failed_doing = fail_reason;++ // Now we try to get the errno from the child+ int err;+ r = read(forkCommunicationFds[0], &err, sizeof(err));+ if (r == -1) {+ *failed_doing = "read pipe";+ } else if (r != sizeof(err)) {+ *failed_doing = "read pipe bad length";+ } else {+ // If we succeed then we set errno. It'll be saved and+ // restored again below. Note that in any other case we'll+ // get the errno of whatever else went wrong instead.+ errno = err;+ }++ // We forked the child, but the child had a problem and stopped so it's+ // our responsibility to reap here as nobody else can.+ waitpid(pid, NULL, 0);++ // Already closed child ends above+ if (stdInHdl->behavior == STD_HANDLE_USE_PIPE) {+ close(stdInHdl->use_pipe.parent_end);+ }+ if (stdOutHdl->behavior == STD_HANDLE_USE_PIPE) {+ close(stdOutHdl->use_pipe.parent_end);+ }+ if (stdErrHdl->behavior == STD_HANDLE_USE_PIPE) {+ close(stdErrHdl->use_pipe.parent_end);+ }++ pid = -1;+ }+ else if (r != 0) {+ *failed_doing = "read pipe bad length";+ pid = -1;+ }++ close(forkCommunicationFds[0]);++ unblockUserSignals();+ startTimer();++ return pid;+}
+ cbits/posix/posix_spawn.c view
@@ -0,0 +1,211 @@+#include "runProcess.h"+#include "common.h"++#include <unistd.h>+#include <errno.h>+#include <signal.h>++#if !defined(HAVE_POSIX_SPAWNP)+ProcHandle+do_spawn_posix (char *const args[],+ char *workingDirectory, char **environment,+ struct std_handle *stdInHdl,+ struct std_handle *stdOutHdl,+ struct std_handle *stdErrHdl,+ gid_t *childGroup, uid_t *childUser,+ int flags,+ char **failed_doing)+{+ return -2;+}++#else++// Necessary for POSIX_SPAWN_SETSID under glibc.+#define _GNU_SOURCE+#include <spawn.h>++extern char **environ;++static int+setup_std_handle_spawn (int fd,+ struct std_handle *hdl,+ posix_spawn_file_actions_t *fa,+ char **failed_doing)+{+ switch (hdl->behavior) {+ case STD_HANDLE_CLOSE:+ if (posix_spawn_file_actions_addclose(fa, fd) != 0) {+ *failed_doing = "posix_spawn_file_actions_addclose";+ return -1;+ }+ return 0;++ case STD_HANDLE_USE_FD:+ if (posix_spawn_file_actions_adddup2(fa, hdl->use_fd, fd) != 0) {+ *failed_doing = "posix_spawn_file_actions_adddup2";+ return -1;+ }+ return 0;++ case STD_HANDLE_USE_PIPE:+ if (hdl->use_pipe.child_end != fd) {+ if (posix_spawn_file_actions_adddup2(fa, hdl->use_pipe.child_end, fd) != 0) {+ *failed_doing = "posix_spawn_file_actions_adddup2(child_end)";+ return -1;+ }+ if (posix_spawn_file_actions_addclose(fa, hdl->use_pipe.child_end) != 0) {+ *failed_doing = "posix_spawn_file_actions_addclose(child_end)";+ return -1;+ }+ }+ if (posix_spawn_file_actions_addclose(fa, hdl->use_pipe.parent_end) != 0) {+ *failed_doing = "posix_spawn_file_actions_addclose(parent_end)";+ return -1;+ }+ return 0;+ }+}++/* Try spawning with posix_spawn. Returns -1 if posix_spawn (or the particular+ * requested configuration) is not supported.+ */+ProcHandle+do_spawn_posix (char *const args[],+ char *workingDirectory, char **environment,+ struct std_handle *stdInHdl,+ struct std_handle *stdOutHdl,+ struct std_handle *stdErrHdl,+ gid_t *childGroup, uid_t *childUser,+ int flags,+ char **failed_doing)+{+ // First do catch some cases that posix_spawn simply can't handle...+ if (childGroup || childUser) {+ return -2;+ }+ if ((flags & RUN_PROCESS_IN_CLOSE_FDS) != 0) {+ // TODO: can this be efficiently supported?+ return -2;+ }++ // Now the main act...+ pid_t pid;+ posix_spawn_file_actions_t fa;+ posix_spawnattr_t sa;+ int r;+ ProcHandle ret;+ short spawn_flags = 0;++ r = posix_spawn_file_actions_init(&fa);+ if (r != 0) {+ *failed_doing = "posix_spawn_file_actions_init";+ return -1;+ }++ r = posix_spawnattr_init(&sa);+ if (r != 0) {+ posix_spawn_file_actions_destroy(&fa);+ *failed_doing = "posix_spawnattr_init";+ return -1;+ }++ if (workingDirectory) {+#if defined(HAVE_posix_spawn_file_actions_addchdir_np)+ // N.B. this function is broken on macOS.+ // See https://github.com/rust-lang/rust/pull/80537.+ r = posix_spawn_file_actions_addchdir(&fa, workingDirectory);+ if (r != 0) {+ *failed_doing = "posix_spawn_file_actions_addchdir";+ goto fail;+ }+#else+ goto not_supported;+#endif+ }++ if ((flags & RUN_PROCESS_NEW_SESSION) != 0) {+#if defined(HAVE_POSIX_SPAWN_SETSID)+ // N.B. POSIX_SPAWN_SETSID is only supported on macOS 10.15 and later,+ // but an proper error is not returned in earlier versions.+ // See https://bugs.python.org/issue37586.+ if (posix_spawnattr_setflags(&sa, POSIX_SPAWN_SETSID) != 0) {+ *failed_doing = "posix_spawnattr_setflags(POSIX_SPAWN_SETSID)";+ goto fail;+ }+#elif defined(HAVE_POSIX_SPAWN_SETSID_NP)+ if (posix_spawnattr_setflags(&sa, POSIX_SPAWN_SETSID_NP) != 0) {+ *failed_doing = "posix_spawnattr_setflags(POSIX_SPAWN_SETSID_NP)";+ goto fail;+ }+#else+ goto not_supported;+#endif+ }++#if defined(HAVE_POSIX_SPAWN_SETPGROUP)+ if ((flags & RUN_PROCESS_IN_NEW_GROUP) != 0) {+ spawn_flags |= POSIX_SPAWN_SETPGROUP;+ }+#endif++ if (setup_std_handle_spawn(STDIN_FILENO, stdInHdl, &fa, failed_doing) != 0) {+ goto fail;+ }+ if (setup_std_handle_spawn(STDOUT_FILENO, stdOutHdl, &fa, failed_doing) != 0) {+ goto fail;+ }+ if (setup_std_handle_spawn(STDERR_FILENO, stdErrHdl, &fa, failed_doing) != 0) {+ goto fail;+ }++ sigset_t ss;+ if ((flags & RESET_INT_QUIT_HANDLERS) != 0) {+ if (sigemptyset(&ss) == -1) {+ *failed_doing = "sigemptyset";+ goto fail;+ }+ if (sigaddset(&ss, SIGINT) == -1) {+ *failed_doing = "sigaddset(SIGINT)";+ goto fail;+ }+ if (sigaddset(&ss, SIGQUIT) == -1) {+ *failed_doing = "sigaddset(SIGQUIT)";+ goto fail;+ }+ if (posix_spawnattr_setsigdefault(&sa, &ss) != 0) {+ *failed_doing = "posix_spawnattr_setsigdefault";+ goto fail;+ }+ spawn_flags |= POSIX_SPAWN_SETSIGDEF;+ }++ if (posix_spawnattr_setflags(&sa, spawn_flags) != 0) {+ *failed_doing = "posix_spawnattr_setflags";+ goto fail;+ }++ r = posix_spawnp(&pid, args[0], &fa, &sa, args, environment ? environment : environ);+ if (r != 0) {+ *failed_doing = "posix_spawnp";+ goto fail;+ } else {+ ret = pid;+ goto finish;+ }++not_supported:+ ret = -2;+ goto finish;++fail:+ ret = -1;+ goto finish;++finish:+ posix_spawn_file_actions_destroy(&fa);+ posix_spawnattr_destroy(&sa);+ return ret;+}++#endif
cbits/posix/runProcess.c view
@@ -4,409 +4,196 @@ Support for System.Process ------------------------------------------------------------------------- */ -/* XXX This is a nasty hack; should put everything necessary in this package */-#include "HsBase.h"-#include "Rts.h"- #include "runProcess.h"+#include "common.h" -#include "execvpe.h"+#include <unistd.h>+#include <errno.h>+#include <sys/wait.h> -/* ----------------------------------------------------------------------------- UNIX versions- ------------------------------------------------------------------------- */+#ifdef HAVE_FCNTL_H+#include <fcntl.h>+#endif +#if defined(HAVE_SIGNAL_H)+#include <signal.h>+#endif++int+get_max_fd()+{+ static int cache = 0;+ if (cache == 0) {+#if HAVE_SYSCONF+ cache = sysconf(_SC_OPEN_MAX);+ if (cache == -1) {+ cache = 256;+ }+#else+ cache = 256;+#endif+ }+ return cache;+}+ // If a process was terminated by a signal, the exit status we return // via the System.Process API is (-signum). This encoding avoids collision with // normal process termination status codes. See also #7229. #define TERMSIG_EXITSTATUS(s) (-(WTERMSIG(s))) -static long max_fd = 0;--// Rts internal API, not exposed in a public header file:-extern void blockUserSignals(void);-extern void unblockUserSignals(void);--// These are arbitrarily chosen -- JP-#define forkSetgidFailed 124-#define forkSetuidFailed 125+/*+ * Spawn a new process. We first try posix_spawn but since this isn't supported+ * on all platforms we fall back to fork/exec in some cases on some platforms.+ */+static ProcHandle+do_spawn (char *const args[],+ char *workingDirectory, char **environment,+ struct std_handle *stdInHdl,+ struct std_handle *stdOutHdl,+ struct std_handle *stdErrHdl,+ gid_t *childGroup, uid_t *childUser,+ int flags,+ char **failed_doing)+{+ ProcHandle r;+ r = do_spawn_posix(args,+ workingDirectory, environment,+ stdInHdl, stdOutHdl, stdErrHdl,+ childGroup, childUser,+ flags,+ failed_doing);+ if (r == -2) {+ // configuration not supported by posix_spawn, fall back to fork/exec+ } else {+ return r;+ } -// See #1593. The convention for the exit code when-// exec() fails seems to be 127 (gleened from C's-// system()), but there's no equivalent convention for-// chdir(), so I'm picking 126 --SimonM.-#define forkChdirFailed 126-#define forkExecFailed 127+ r = do_spawn_fork(args,+ workingDirectory, environment,+ stdInHdl, stdOutHdl, stdErrHdl,+ childGroup, childUser,+ flags,+ failed_doing);+ return r;+} -#define forkGetpwuidFailed 128-#define forkInitgroupsFailed 129+enum pipe_direction {+ CHILD_READS, CHILD_WRITES+}; -__attribute__((__noreturn__))-static void childFailed(int pipe, int failCode) {- int err;- ssize_t unused __attribute__((unused));+/* Initialize a std_handle_behavior from a "pseudo-fd":+ * - fd == -1 means create a pipe+ * - fd == -2 means close the handle+ * - otherwise use fd+ * Returns 0 on success, -1 otherwise.+ */+static int+init_std_handle(int fd, enum pipe_direction direction,+ /* out */ struct std_handle *hdl,+ char **failed_doing)+{+ switch (fd) {+ case -1: {+ int pipe_fds[2];+ int r = pipe(pipe_fds);+ if (r == -1) {+ *failed_doing = "pipe";+ return -1;+ } - err = errno;- unused = write(pipe, &failCode, sizeof(failCode));- unused = write(pipe, &err, sizeof(err));- // As a fallback, exit with the failCode- _exit(failCode);+ int child_end = direction == CHILD_READS ? 0 : 1;+ int parent_end = direction == CHILD_READS ? 1 : 0;+ *hdl = (struct std_handle) {+ .behavior = STD_HANDLE_USE_PIPE,+ .use_pipe = { .child_end = pipe_fds[child_end], .parent_end = pipe_fds[parent_end] }+ };+ break;+ }+ case -2:+ *hdl = (struct std_handle) {+ .behavior = STD_HANDLE_CLOSE+ };+ break;+ default:+ *hdl = (struct std_handle) {+ .behavior = STD_HANDLE_USE_FD,+ .use_fd = fd+ };+ break;+ }+ return 0; } ProcHandle runInteractiveProcess (char *const args[], char *workingDirectory, char **environment,+ // handles to use for the standard handles. -1 indicates+ // create pipe, -2 indicates close. int fdStdIn, int fdStdOut, int fdStdErr,+ // output arguments to return created pipe handle to caller int *pfdStdInput, int *pfdStdOutput, int *pfdStdError, gid_t *childGroup, uid_t *childUser,- int reset_int_quit_handlers, int flags, char **failed_doing) {- int close_fds = ((flags & RUN_PROCESS_IN_CLOSE_FDS) != 0);- int pid;- int fdStdInput[2], fdStdOutput[2], fdStdError[2];- int forkCommunicationFds[2];- int r;- int failCode, err;+ struct std_handle stdInHdl, stdOutHdl, stdErrHdl;+ ProcHandle r; - // Ordering matters here, see below [Note #431].- if (fdStdIn == -1) {- r = pipe(fdStdInput);- if (r == -1) {- *failed_doing = "runInteractiveProcess: pipe";- return -1;- }- }- if (fdStdOut == -1) {- r = pipe(fdStdOutput);- if (r == -1) {- if (fdStdIn == -1) {- close(fdStdInput[0]);- close(fdStdInput[1]);- }- *failed_doing = "runInteractiveProcess: pipe";- return -1;- }- }- if (fdStdErr == -1) {- r = pipe(fdStdError);- if (r == -1) {- *failed_doing = "runInteractiveProcess: pipe";- if (fdStdIn == -1) {- close(fdStdInput[0]);- close(fdStdInput[1]);- }- if (fdStdOut == -1) {- close(fdStdOutput[0]);- close(fdStdOutput[1]);- }- return -1;- }- }+ // A bit of paranoia to ensure that we catch if we fail to set this on+ // failure.+ *failed_doing = NULL; - r = pipe(forkCommunicationFds);- if (r == -1) {- *failed_doing = "runInteractiveProcess: pipe";- if (fdStdIn == -1) {- close(fdStdInput[0]);- close(fdStdInput[1]);- }- if (fdStdOut == -1) {- close(fdStdOutput[0]);- close(fdStdOutput[1]);- }- if (fdStdErr == -1) {- close(fdStdError[0]);- close(fdStdError[1]);- }- return -1;+ // Ordering matters here, see below Note [Ordering of handle closing].+ if (init_std_handle(fdStdIn, CHILD_READS, &stdInHdl, failed_doing) != 0) {+ goto fail; } - // Block signals with Haskell handlers. The danger here is that- // with the threaded RTS, a signal arrives in the child process,- // the RTS writes the signal information into the pipe (which is- // shared between parent and child), and the parent behaves as if- // the signal had been raised.- blockUserSignals();-- // See #4074. Sometimes fork() gets interrupted by the timer- // signal and keeps restarting indefinitely.- stopTimer();-- switch(pid = myfork())- {- case -1:- unblockUserSignals();- startTimer();- if (fdStdIn == -1) {- close(fdStdInput[0]);- close(fdStdInput[1]);- }- if (fdStdOut == -1) {- close(fdStdOutput[0]);- close(fdStdOutput[1]);- }- if (fdStdErr == -1) {- close(fdStdError[0]);- close(fdStdError[1]);- }- close(forkCommunicationFds[0]);- close(forkCommunicationFds[1]);- *failed_doing = "fork";- return -1;-- case 0:- // WARNING! We may now be in the child of vfork(), and any- // memory we modify below may also be seen in the parent- // process.-- close(forkCommunicationFds[0]);- fcntl(forkCommunicationFds[1], F_SETFD, FD_CLOEXEC);-- if ((flags & RUN_PROCESS_NEW_SESSION) != 0) {- setsid();- }- if ((flags & RUN_PROCESS_IN_NEW_GROUP) != 0) {- setpgid(0, 0);- }-- if ( childGroup) {- if ( setgid( *childGroup) != 0) {- // ERROR- childFailed(forkCommunicationFds[1], forkSetgidFailed);- }- }-- if ( childUser) {- // Using setuid properly first requires that we initgroups.- // However, to do this we must know the username of the user we are- // switching to.- struct passwd pw;- struct passwd *res = NULL;- int buf_len = sysconf(_SC_GETPW_R_SIZE_MAX);- char *buf = malloc(buf_len);- gid_t suppl_gid = childGroup ? *childGroup : getgid();- if ( getpwuid_r(*childUser, &pw, buf, buf_len, &res) != 0) {- childFailed(forkCommunicationFds[1], forkGetpwuidFailed);- }- if ( res == NULL ) {- childFailed(forkCommunicationFds[1], forkGetpwuidFailed);- }- if ( initgroups(res->pw_name, suppl_gid) != 0) {- childFailed(forkCommunicationFds[1], forkInitgroupsFailed);- }- if ( setuid( *childUser) != 0) {- // ERROR- childFailed(forkCommunicationFds[1], forkSetuidFailed);- }- }-- unblockUserSignals();-- if (workingDirectory) {- if (chdir (workingDirectory) < 0) {- childFailed(forkCommunicationFds[1], forkChdirFailed);- }- }-- // [Note #431]: Ordering matters here. If any of the FDs- // 0,1,2 were initially closed, then our pipes may have used- // these FDs. So when we dup2 the pipe FDs down to 0,1,2, we- // must do it in that order, otherwise we could overwrite an- // FD that we need later.-- if (fdStdIn == -1) {- if (fdStdInput[0] != STDIN_FILENO) {- dup2 (fdStdInput[0], STDIN_FILENO);- close(fdStdInput[0]);- }- close(fdStdInput[1]);- } else if (fdStdIn == -2) {- close(STDIN_FILENO);- } else {- dup2(fdStdIn, STDIN_FILENO);- }-- if (fdStdOut == -1) {- if (fdStdOutput[1] != STDOUT_FILENO) {- dup2 (fdStdOutput[1], STDOUT_FILENO);- close(fdStdOutput[1]);- }- close(fdStdOutput[0]);- } else if (fdStdOut == -2) {- close(STDOUT_FILENO);- } else {- dup2(fdStdOut, STDOUT_FILENO);- }-- if (fdStdErr == -1) {- if (fdStdError[1] != STDERR_FILENO) {- dup2 (fdStdError[1], STDERR_FILENO);- close(fdStdError[1]);- }- close(fdStdError[0]);- } else if (fdStdErr == -2) {- close(STDERR_FILENO);- } else {- dup2(fdStdErr, STDERR_FILENO);- }-- if (close_fds) {- int i;- if (max_fd == 0) {-#if HAVE_SYSCONF- max_fd = sysconf(_SC_OPEN_MAX);- if (max_fd == -1) {- max_fd = 256;- }-#else- max_fd = 256;-#endif- }- // XXX Not the pipe- for (i = 3; i < max_fd; i++) {- if (i != forkCommunicationFds[1]) {- close(i);- }- }- }-- /* Reset the SIGINT/SIGQUIT signal handlers in the child, if requested- */- if (reset_int_quit_handlers) {- struct sigaction dfl;- (void)sigemptyset(&dfl.sa_mask);- dfl.sa_flags = 0;- dfl.sa_handler = SIG_DFL;- (void)sigaction(SIGINT, &dfl, NULL);- (void)sigaction(SIGQUIT, &dfl, NULL);- }-- /* the child */- if (environment) {- // XXX Check result- execvpe(args[0], args, environment);- } else {- // XXX Check result- execvp(args[0], args);- }-- childFailed(forkCommunicationFds[1], forkExecFailed);-- default:- if ((flags & RUN_PROCESS_IN_NEW_GROUP) != 0) {- setpgid(pid, pid);- }- if (fdStdIn == -1) {- close(fdStdInput[0]);- fcntl(fdStdInput[1], F_SETFD, FD_CLOEXEC);- *pfdStdInput = fdStdInput[1];- }- if (fdStdOut == -1) {- close(fdStdOutput[1]);- fcntl(fdStdOutput[0], F_SETFD, FD_CLOEXEC);- *pfdStdOutput = fdStdOutput[0];- }- if (fdStdErr == -1) {- close(fdStdError[1]);- fcntl(fdStdError[0], F_SETFD, FD_CLOEXEC);- *pfdStdError = fdStdError[0];- }- close(forkCommunicationFds[1]);- fcntl(forkCommunicationFds[0], F_SETFD, FD_CLOEXEC);-- break;+ if (init_std_handle(fdStdOut, CHILD_WRITES, &stdOutHdl, failed_doing) != 0) {+ goto fail; } - // If the child process had a problem, then it will tell us via the- // forkCommunicationFds pipe. First we try to read what the problem- // was. Note that if none of these conditionals match then we fall- // through and just return pid.- r = read(forkCommunicationFds[0], &failCode, sizeof(failCode));- if (r == -1) {- *failed_doing = "runInteractiveProcess: read pipe";- pid = -1;+ if (init_std_handle(fdStdErr, CHILD_WRITES, &stdErrHdl, failed_doing) != 0) {+ goto fail; }- else if (r == sizeof(failCode)) {- // This is the case where we successfully managed to read- // the problem- switch (failCode) {- case forkChdirFailed:- *failed_doing = "runInteractiveProcess: chdir";- break;- case forkExecFailed:- *failed_doing = "runInteractiveProcess: exec";- break;- case forkSetgidFailed:- *failed_doing = "runInteractiveProcess: setgid";- break;- case forkSetuidFailed:- *failed_doing = "runInteractiveProcess: setuid";- break;- case forkGetpwuidFailed:- *failed_doing = "runInteractiveProcess: getpwuid";- break;- case forkInitgroupsFailed:- *failed_doing = "runInteractiveProcess: initgroups";- break;- default:- *failed_doing = "runInteractiveProcess: unknown";- break;- }- // Now we try to get the errno from the child- r = read(forkCommunicationFds[0], &err, sizeof(err));- if (r == -1) {- *failed_doing = "runInteractiveProcess: read pipe";- }- else if (r != sizeof(failCode)) {- *failed_doing = "runInteractiveProcess: read pipe bad length";- }- else {- // If we succeed then we set errno. It'll be saved and- // restored again below. Note that in any other case we'll- // get the errno of whatever else went wrong instead.- errno = err;- } - // We forked the child, but the child had a problem and stopped so it's- // our responsibility to reap here as nobody else can.- waitpid(pid, NULL, 0);-- if (fdStdIn == -1) {- // Already closed fdStdInput[0] above- close(fdStdInput[1]);- }- if (fdStdOut == -1) {- close(fdStdOutput[0]);- // Already closed fdStdOutput[1] above- }- if (fdStdErr == -1) {- close(fdStdError[0]);- // Already closed fdStdError[1] above- }-- pid = -1;- }- else if (r != 0) {- *failed_doing = "runInteractiveProcess: read pipe bad length";- pid = -1;+ r = do_spawn(args,+ workingDirectory, environment,+ &stdInHdl, &stdOutHdl, &stdErrHdl,+ childGroup, childUser,+ flags,+ failed_doing);+ if (r == -1) {+ goto fail; } - if (pid == -1) {- err = errno;+ // Close the remote ends of any pipes we created.+#define FINALISE_STD_HANDLE(hdl, pfd) \+ if (hdl.behavior == STD_HANDLE_USE_PIPE) { \+ close(hdl.use_pipe.child_end); \+ fcntl(hdl.use_pipe.parent_end, F_SETFD, FD_CLOEXEC); \+ *pfd = hdl.use_pipe.parent_end; \ } - close(forkCommunicationFds[0]);+ FINALISE_STD_HANDLE(stdInHdl, pfdStdInput);+ FINALISE_STD_HANDLE(stdOutHdl, pfdStdOutput);+ FINALISE_STD_HANDLE(stdErrHdl, pfdStdError);+#undef FINALISE_STD_HANDLE - unblockUserSignals();- startTimer();+ return r; - if (pid == -1) {- errno = err;+fail:+#define CLOSE_PIPE(hdl) \+ if (hdl.behavior == STD_HANDLE_USE_PIPE) { \+ close(hdl.use_pipe.child_end); \+ close(hdl.use_pipe.parent_end); \ } - return pid;+ CLOSE_PIPE(stdInHdl);+ CLOSE_PIPE(stdOutHdl);+ CLOSE_PIPE(stdErrHdl);+#undef CLOSE_PIPE++ return -1; } int@@ -422,14 +209,11 @@ *pExitCode = 0; - if ((res = waitpid(handle, &wstat, WNOHANG)) > 0)- {- if (WIFEXITED(wstat))- {+ if ((res = waitpid(handle, &wstat, WNOHANG)) > 0) {+ if (WIFEXITED(wstat)) { *pExitCode = WEXITSTATUS(wstat); return 1;- }- else+ } else { if (WIFSIGNALED(wstat)) { *pExitCode = TERMSIG_EXITSTATUS(wstat);@@ -439,6 +223,7 @@ { /* This should never happen */ }+ } } if (res == 0) return 0;@@ -452,7 +237,8 @@ return -1; } -int waitForProcess (ProcHandle handle, int *pret)+int+waitForProcess (ProcHandle handle, int *pret) { int wstat;
cbits/win32/runProcess.c view
@@ -553,6 +553,10 @@ if (pid_list == NULL) { pid_list = malloc(pid_list_size);+ if (pid_list == NULL) {+ errno = ENOMEM;+ return false;+ } pid_list->NumberOfAssignedProcesses = process_count; }
changelog.md view
@@ -1,5 +1,9 @@ # Changelog for [`process` package](http://hackage.haskell.org/package/process) +## 1.6.13.0 *July 2021*++* Refactoring of POSIX process logic [#208](https://github.com/haskell/process/pull/208)+ ## 1.6.12.0 *June 2021* * Add function `getCurrentPid` to get the currently executing process' ID [#205](https://github.com/haskell/process/pull/205)
configure view
@@ -1798,6 +1798,52 @@ } # ac_fn_c_check_func +# ac_fn_c_check_decl LINENO SYMBOL VAR INCLUDES+# ---------------------------------------------+# Tests whether SYMBOL is declared in INCLUDES, setting cache variable VAR+# accordingly.+ac_fn_c_check_decl ()+{+ as_lineno=${as_lineno-"$1"} as_lineno_stack=as_lineno_stack=$as_lineno_stack+ as_decl_name=`echo $2|sed 's/ *(.*//'`+ as_decl_use=`echo $2|sed -e 's/(/((/' -e 's/)/) 0&/' -e 's/,/) 0& (/g'`+ { $as_echo "$as_me:${as_lineno-$LINENO}: checking whether $as_decl_name is declared" >&5+$as_echo_n "checking whether $as_decl_name is declared... " >&6; }+if eval \${$3+:} false; then :+ $as_echo_n "(cached) " >&6+else+ cat confdefs.h - <<_ACEOF >conftest.$ac_ext+/* end confdefs.h. */+$4+int+main ()+{+#ifndef $as_decl_name+#ifdef __cplusplus+ (void) $as_decl_use;+#else+ (void) $as_decl_name;+#endif+#endif++ ;+ return 0;+}+_ACEOF+if ac_fn_c_try_compile "$LINENO"; then :+ eval "$3=yes"+else+ eval "$3=no"+fi+rm -f core conftest.err conftest.$ac_objext conftest.$ac_ext+fi+eval ac_res=\$$3+ { $as_echo "$as_me:${as_lineno-$LINENO}: result: $ac_res" >&5+$as_echo "$ac_res" >&6; }+ eval $as_lineno_stack; ${as_lineno_stack:+:} unset as_lineno++} # ac_fn_c_check_decl+ # ac_fn_c_compute_int LINENO EXPR VAR INCLUDES # -------------------------------------------- # Tries to find the compile-time value of EXPR in a program that includes@@ -3778,6 +3824,75 @@ fi done++for ac_func in execvpe+do :+ ac_fn_c_check_func "$LINENO" "execvpe" "ac_cv_func_execvpe"+if test "x$ac_cv_func_execvpe" = xyes; then :+ cat >>confdefs.h <<_ACEOF+#define HAVE_EXECVPE 1+_ACEOF++fi+done+++# posix_spawn checks+for ac_header in spawn.h+do :+ ac_fn_c_check_header_mongrel "$LINENO" "spawn.h" "ac_cv_header_spawn_h" "$ac_includes_default"+if test "x$ac_cv_header_spawn_h" = xyes; then :+ cat >>confdefs.h <<_ACEOF+#define HAVE_SPAWN_H 1+_ACEOF++fi++done++for ac_func in posix_spawnp posix_spawn_file_actions_addchdir+do :+ as_ac_var=`$as_echo "ac_cv_func_$ac_func" | $as_tr_sh`+ac_fn_c_check_func "$LINENO" "$ac_func" "$as_ac_var"+if eval test \"x\$"$as_ac_var"\" = x"yes"; then :+ cat >>confdefs.h <<_ACEOF+#define `$as_echo "HAVE_$ac_func" | $as_tr_cpp` 1+_ACEOF++fi+done++ac_fn_c_check_decl "$LINENO" "POSIX_SPAWN_SETSID" "ac_cv_have_decl_POSIX_SPAWN_SETSID" "$ac_includes_default"+if test "x$ac_cv_have_decl_POSIX_SPAWN_SETSID" = xyes; then :+ ac_have_decl=1+else+ ac_have_decl=0+fi++cat >>confdefs.h <<_ACEOF+#define HAVE_DECL_POSIX_SPAWN_SETSID $ac_have_decl+_ACEOF+ac_fn_c_check_decl "$LINENO" "POSIX_SPAWN_SETSID_NP" "ac_cv_have_decl_POSIX_SPAWN_SETSID_NP" "$ac_includes_default"+if test "x$ac_cv_have_decl_POSIX_SPAWN_SETSID_NP" = xyes; then :+ ac_have_decl=1+else+ ac_have_decl=0+fi++cat >>confdefs.h <<_ACEOF+#define HAVE_DECL_POSIX_SPAWN_SETSID_NP $ac_have_decl+_ACEOF++ac_fn_c_check_decl "$LINENO" "POSIX_SPAWN_SETPGROUP" "ac_cv_have_decl_POSIX_SPAWN_SETPGROUP" "$ac_includes_default"+if test "x$ac_cv_have_decl_POSIX_SPAWN_SETPGROUP" = xyes; then :+ ac_have_decl=1+else+ ac_have_decl=0+fi++cat >>confdefs.h <<_ACEOF+#define HAVE_DECL_POSIX_SPAWN_SETPGROUP $ac_have_decl+_ACEOF for fp_const_name in SIG_DFL SIG_IGN
configure.ac view
@@ -14,6 +14,13 @@ AC_CHECK_HEADERS([signal.h sys/wait.h fcntl.h]) AC_CHECK_FUNCS([setitimer sysconf])+AC_CHECK_FUNCS([execvpe])++# posix_spawn checks+AC_CHECK_HEADERS([spawn.h])+AC_CHECK_FUNCS([posix_spawnp posix_spawn_file_actions_addchdir])+AC_CHECK_DECLS([POSIX_SPAWN_SETSID, POSIX_SPAWN_SETSID_NP])+AC_CHECK_DECLS([POSIX_SPAWN_SETPGROUP]) FP_CHECK_CONSTS([SIG_DFL SIG_IGN])
include/HsProcessConfig.h.in view
@@ -6,6 +6,21 @@ /* The value of SIG_IGN. */ #undef CONST_SIG_IGN +/* Define to 1 if you have the declaration of `POSIX_SPAWN_SETPGROUP', and to+ 0 if you don't. */+#undef HAVE_DECL_POSIX_SPAWN_SETPGROUP++/* Define to 1 if you have the declaration of `POSIX_SPAWN_SETSID', and to 0+ if you don't. */+#undef HAVE_DECL_POSIX_SPAWN_SETSID++/* Define to 1 if you have the declaration of `POSIX_SPAWN_SETSID_NP', and to+ 0 if you don't. */+#undef HAVE_DECL_POSIX_SPAWN_SETSID_NP++/* Define to 1 if you have the `execvpe' function. */+#undef HAVE_EXECVPE+ /* Define to 1 if you have the <fcntl.h> header file. */ #undef HAVE_FCNTL_H @@ -18,11 +33,21 @@ /* Define to 1 if you have the <memory.h> header file. */ #undef HAVE_MEMORY_H +/* Define to 1 if you have the `posix_spawnp' function. */+#undef HAVE_POSIX_SPAWNP++/* Define to 1 if you have the `posix_spawn_file_actions_addchdir' function.+ */+#undef HAVE_POSIX_SPAWN_FILE_ACTIONS_ADDCHDIR+ /* Define to 1 if you have the `setitimer' function. */ #undef HAVE_SETITIMER /* Define to 1 if you have the <signal.h> header file. */ #undef HAVE_SIGNAL_H++/* Define to 1 if you have the <spawn.h> header file. */+#undef HAVE_SPAWN_H /* Define to 1 if you have the <stdint.h> header file. */ #undef HAVE_STDINT_H
include/processFlags.h view
@@ -9,3 +9,4 @@ #define RUN_PROCESS_DETACHED 0x4 #define RUN_PROCESS_NEW_SESSION 0x8 #define RUN_PROCESS_NEW_CONSOLE 0x10+#define RESET_INT_QUIT_HANDLERS 0x20
include/runProcess.h view
@@ -4,6 +4,8 @@ Interface for code in runProcess.c (providing support for System.Process) ------------------------------------------------------------------------- */ +#pragma once+ #include "HsProcessConfig.h" // Otherwise these clash with similar definitions from other packages: #undef PACKAGE_BUGREPORT@@ -19,35 +21,8 @@ #include <stdbool.h> #endif -#include <unistd.h>-#include <sys/types.h> #if !(defined(_MSC_VER) || defined(__MINGW32__) || defined(_WIN32))-#include <pwd.h>-#include <grp.h>-#endif--#ifdef HAVE_FCNTL_H-#include <fcntl.h>-#endif--#ifdef HAVE_VFORK_H-#include <vfork.h>-#endif--#if defined(HAVE_WORKING_VFORK)-#define myfork vfork-#elif defined(HAVE_WORKING_FORK)-#define myfork fork-// We don't need a fork command on Windows-#elif !(defined(_MSC_VER) || defined(__MINGW32__) || defined(_WIN32))-#error Cannot find a working fork command-#endif--#ifdef HAVE_SIGNAL_H-#include <signal.h>-#endif--#if !(defined(_MSC_VER) || defined(__MINGW32__) || defined(_WIN32))+#include <sys/types.h> typedef pid_t ProcHandle; #else // Should really be intptr_t, but we don't have that type on the Haskell side@@ -58,6 +33,9 @@ #if !(defined(_MSC_VER) || defined(__MINGW32__) || defined(_WIN32)) +#include <pwd.h>+#include <grp.h>+ extern ProcHandle runInteractiveProcess( char *const args[], char *workingDirectory, char **environment,@@ -69,7 +47,6 @@ int *pfdStdError, gid_t *childGroup, uid_t *childUser,- int reset_int_quit_handlers, int flags, char **failed_doing);
process.cabal view
@@ -1,5 +1,5 @@ name: process-version: 1.6.12.0+version: 1.6.13.0 -- NOTE: Don't forget to update ./changelog.md license: BSD3 license-file: LICENSE@@ -65,6 +65,9 @@ else c-sources: cbits/posix/runProcess.c+ cbits/posix/fork_exec.c+ cbits/posix/posix_spawn.c+ cbits/posix/find_executable.c other-modules: System.Process.Posix build-depends: unix >= 2.5 && < 2.8
test/main.hs view
@@ -14,6 +14,11 @@ import qualified Data.ByteString.Char8 as S8 import System.Directory (getTemporaryDirectory, removeFile) +ifWindows :: IO () -> IO ()+ifWindows action+ | not isWindows = return ()+ | otherwise = action+ isWindows :: Bool #if WINDOWS isWindows = True@@ -51,7 +56,7 @@ putStrLn "Testing subdirectories" - withCurrentDirectory "exes" $ do+ ifWindows $ withCurrentDirectory "exes" $ do res1 <- readCreateProcess (proc "./echo.bat" []) "" unless ("parent" `isInfixOf` res1 && not ("child" `isInfixOf` res1)) $ error $ "echo.bat with cwd failed: " ++ show res1