packages feed

process 1.6.12.0 → 1.6.13.0

raw patch · 14 files changed

+938/−408 lines, 14 files

Files

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