/* ---------------------------------------------------------------------------- (c) The University of Glasgow 2004 Support for System.Process ------------------------------------------------------------------------- */ #if defined(_MSC_VER) || defined(__MINGW32__) || defined(_WIN32) #define UNICODE #endif /* XXX This is a nasty hack; should put everything necessary in this package */ #include "HsBase.h" #include "Rts.h" #include "runProcess.h" #if !(defined(_MSC_VER) || defined(__MINGW32__) || defined(_WIN32)) #include "execvpe.h" /* ---------------------------------------------------------------------------- UNIX versions ------------------------------------------------------------------------- */ static long max_fd = 0; // Rts internal API, not exposed in a public header file: extern void blockUserSignals(void); extern void unblockUserSignals(void); ProcHandle runInteractiveProcess (char *const args[], char *workingDirectory, char **environment, int fdStdIn, int fdStdOut, int fdStdErr, int *pfdStdInput, int *pfdStdOutput, int *pfdStdError, int set_inthandler, long inthandler, int set_quithandler, long quithandler, int close_fds) { int pid; int fdStdInput[2], fdStdOutput[2], fdStdError[2]; int r; struct sigaction dfl; // Ordering matters here, see below [Note #431]. if (fdStdIn == -1) { r = pipe(fdStdInput); if (r == -1) { sysErrorBelch("runInteractiveProcess: pipe"); return -1; } } if (fdStdOut == -1) { r = pipe(fdStdOutput); if (r == -1) { sysErrorBelch("runInteractiveProcess: pipe"); return -1; } } if (fdStdErr == -1) { r = pipe(fdStdError); if (r == -1) { sysErrorBelch("runInteractiveProcess: 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(); switch(pid = fork()) { case -1: unblockUserSignals(); #if __GLASGOW_HASKELL__ > 612 startTimer(); #endif 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; case 0: { // WARNING! we are now in the child of vfork(), so any memory // we modify below will also be seen in the parent process. unblockUserSignals(); if (workingDirectory) { if (chdir (workingDirectory) < 0) { // 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. _exit(126); } } // [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 { dup2(fdStdIn, STDIN_FILENO); } if (fdStdOut == -1) { if (fdStdOutput[1] != STDOUT_FILENO) { dup2 (fdStdOutput[1], STDOUT_FILENO); close(fdStdOutput[1]); } close(fdStdOutput[0]); } 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 { 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 } for (i = 3; i < max_fd; i++) { close(i); } } /* Set the SIGINT/SIGQUIT signal handlers in the child, if requested */ (void)sigemptyset(&dfl.sa_mask); dfl.sa_flags = 0; if (set_inthandler) { dfl.sa_handler = (void *)inthandler; (void)sigaction(SIGINT, &dfl, NULL); } if (set_quithandler) { dfl.sa_handler = (void *)quithandler; (void)sigaction(SIGQUIT, &dfl, NULL); } /* the child */ if (environment) { execvpe(args[0], args, environment); } else { execvp(args[0], args); } } _exit(127); default: 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]; } break; } unblockUserSignals(); startTimer(); return pid; } int terminateProcess (ProcHandle handle) { return (kill(handle, SIGTERM) == 0); } int getProcessExitCode (ProcHandle handle, int *pExitCode) { int wstat, res; *pExitCode = 0; if ((res = waitpid(handle, &wstat, WNOHANG)) > 0) { if (WIFEXITED(wstat)) { *pExitCode = WEXITSTATUS(wstat); return 1; } else if (WIFSIGNALED(wstat)) { errno = EINTR; return -1; } else { /* This should never happen */ } } if (res == 0) return 0; if (errno == ECHILD) { *pExitCode = 0; return 1; } return -1; } int waitForProcess (ProcHandle handle, int *pret) { int wstat; while (waitpid(handle, &wstat, 0) < 0) { if (errno != EINTR) { return -1; } } if (WIFEXITED(wstat)) { *pret = WEXITSTATUS(wstat); return 0; } else if (WIFSIGNALED(wstat)) { *pret = wstat; return 0; } else { /* This should never happen */ } return -1; } #else /* ---------------------------------------------------------------------------- Win32 versions ------------------------------------------------------------------------- */ /* -------------------- WINDOWS VERSION --------------------- */ /* * Function: mkAnonPipe * * Purpose: create an anonymous pipe with read and write ends being * optionally (non-)inheritable. */ static BOOL mkAnonPipe (HANDLE* pHandleIn, BOOL isInheritableIn, HANDLE* pHandleOut, BOOL isInheritableOut) { HANDLE hTemporaryIn = NULL; HANDLE hTemporaryOut = NULL; /* Create the anon pipe with both ends inheritable */ if (!CreatePipe(&hTemporaryIn, &hTemporaryOut, NULL, 0)) { maperrno(); *pHandleIn = NULL; *pHandleOut = NULL; return FALSE; } if (isInheritableIn) { // SetHandleInformation requires at least Win2k if (!SetHandleInformation(hTemporaryIn, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) { maperrno(); *pHandleIn = NULL; *pHandleOut = NULL; CloseHandle(hTemporaryIn); CloseHandle(hTemporaryOut); return FALSE; } } *pHandleIn = hTemporaryIn; if (isInheritableOut) { if (!SetHandleInformation(hTemporaryOut, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT)) { maperrno(); *pHandleIn = NULL; *pHandleOut = NULL; CloseHandle(hTemporaryIn); CloseHandle(hTemporaryOut); return FALSE; } } *pHandleOut = hTemporaryOut; return TRUE; } ProcHandle runInteractiveProcess (wchar_t *cmd, wchar_t *workingDirectory, void *environment, int fdStdIn, int fdStdOut, int fdStdErr, int *pfdStdInput, int *pfdStdOutput, int *pfdStdError, int close_fds) { STARTUPINFO sInfo; PROCESS_INFORMATION pInfo; HANDLE hStdInputRead = INVALID_HANDLE_VALUE; HANDLE hStdInputWrite = INVALID_HANDLE_VALUE; HANDLE hStdOutputRead = INVALID_HANDLE_VALUE; HANDLE hStdOutputWrite = INVALID_HANDLE_VALUE; HANDLE hStdErrorRead = INVALID_HANDLE_VALUE; HANDLE hStdErrorWrite = INVALID_HANDLE_VALUE; DWORD flags; BOOL status; BOOL inherit; ZeroMemory(&sInfo, sizeof(sInfo)); sInfo.cb = sizeof(sInfo); sInfo.dwFlags = STARTF_USESTDHANDLES; if (fdStdIn == -1) { if (!mkAnonPipe(&hStdInputRead, TRUE, &hStdInputWrite, FALSE)) goto cleanup_err; sInfo.hStdInput = hStdInputRead; } else if (fdStdIn == 0) { // Don't duplicate stdin, as console handles cannot be // duplicated and inherited. urg. sInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); } else { // The handle might not be inheritable, so duplicate it status = DuplicateHandle(GetCurrentProcess(), (HANDLE) _get_osfhandle(fdStdIn), GetCurrentProcess(), &hStdInputRead, 0, TRUE, /* inheritable */ DUPLICATE_SAME_ACCESS); if (!status) goto cleanup_err; sInfo.hStdInput = hStdInputRead; } if (fdStdOut == -1) { if (!mkAnonPipe(&hStdOutputRead, FALSE, &hStdOutputWrite, TRUE)) goto cleanup_err; sInfo.hStdOutput = hStdOutputWrite; } else if (fdStdOut == 1) { // Don't duplicate stdout, as console handles cannot be // duplicated and inherited. urg. sInfo.hStdOutput = GetStdHandle(STD_OUTPUT_HANDLE); } else { // The handle might not be inheritable, so duplicate it status = DuplicateHandle(GetCurrentProcess(), (HANDLE) _get_osfhandle(fdStdOut), GetCurrentProcess(), &hStdOutputWrite, 0, TRUE, /* inheritable */ DUPLICATE_SAME_ACCESS); if (!status) goto cleanup_err; sInfo.hStdOutput = hStdOutputWrite; } if (fdStdErr == -1) { if (!mkAnonPipe(&hStdErrorRead, TRUE, &hStdErrorWrite, TRUE)) goto cleanup_err; sInfo.hStdError = hStdErrorWrite; } else if (fdStdErr == 2) { // Don't duplicate stderr, as console handles cannot be // duplicated and inherited. urg. sInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); } else { /* The handle might not be inheritable, so duplicate it */ status = DuplicateHandle(GetCurrentProcess(), (HANDLE) _get_osfhandle(fdStdErr), GetCurrentProcess(), &hStdErrorWrite, 0, TRUE, /* inheritable */ DUPLICATE_SAME_ACCESS); if (!status) goto cleanup_err; sInfo.hStdError = hStdErrorWrite; } if (sInfo.hStdInput != GetStdHandle(STD_INPUT_HANDLE) && sInfo.hStdOutput != GetStdHandle(STD_OUTPUT_HANDLE) && sInfo.hStdError != GetStdHandle(STD_ERROR_HANDLE)) flags = CREATE_NO_WINDOW; // Run without console window only when both output and error are redirected else flags = 0; // See #3231 if (close_fds && fdStdIn == 0 && fdStdOut == 1 && fdStdErr == 2) { inherit = FALSE; } else { inherit = TRUE; } if (!CreateProcess(NULL, cmd, NULL, NULL, inherit, flags, environment, workingDirectory, &sInfo, &pInfo)) { goto cleanup_err; } CloseHandle(pInfo.hThread); // Close the ends of the pipes that were inherited by the // child process. This is important, otherwise we won't see // EOF on these pipes when the child process exits. if (hStdInputRead != INVALID_HANDLE_VALUE) CloseHandle(hStdInputRead); if (hStdOutputWrite != INVALID_HANDLE_VALUE) CloseHandle(hStdOutputWrite); if (hStdErrorWrite != INVALID_HANDLE_VALUE) CloseHandle(hStdErrorWrite); *pfdStdInput = _open_osfhandle((intptr_t) hStdInputWrite, _O_WRONLY); *pfdStdOutput = _open_osfhandle((intptr_t) hStdOutputRead, _O_RDONLY); *pfdStdError = _open_osfhandle((intptr_t) hStdErrorRead, _O_RDONLY); return (int) pInfo.hProcess; cleanup_err: if (hStdInputRead != INVALID_HANDLE_VALUE) CloseHandle(hStdInputRead); if (hStdInputWrite != INVALID_HANDLE_VALUE) CloseHandle(hStdInputWrite); if (hStdOutputRead != INVALID_HANDLE_VALUE) CloseHandle(hStdOutputRead); if (hStdOutputWrite != INVALID_HANDLE_VALUE) CloseHandle(hStdOutputWrite); if (hStdErrorRead != INVALID_HANDLE_VALUE) CloseHandle(hStdErrorRead); if (hStdErrorWrite != INVALID_HANDLE_VALUE) CloseHandle(hStdErrorWrite); maperrno(); return -1; } int terminateProcess (ProcHandle handle) { if (!TerminateProcess((HANDLE) handle, 1)) { maperrno(); return -1; } return 0; } int getProcessExitCode (ProcHandle handle, int *pExitCode) { *pExitCode = 0; if (WaitForSingleObject((HANDLE) handle, 1) == WAIT_OBJECT_0) { if (GetExitCodeProcess((HANDLE) handle, (DWORD *) pExitCode) == 0) { maperrno(); return -1; } return 1; } return 0; } int waitForProcess (ProcHandle handle, int *pret) { DWORD retCode; if (WaitForSingleObject((HANDLE) handle, INFINITE) == WAIT_OBJECT_0) { if (GetExitCodeProcess((HANDLE) handle, &retCode) == 0) { maperrno(); return -1; } *pret = retCode; return 0; } maperrno(); return -1; } #endif /* Win32 */