/* ---------------------------------------------------------------------------- (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 ------------------------------------------------------------------------- */ // 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 // 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 #define forkGetpwuidFailed 128 #define forkInitgroupsFailed 129 __attribute__((__noreturn__)) static void childFailed(int pipe, int failCode) { int err; ssize_t unused __attribute__((unused)); err = errno; unused = write(pipe, &failCode, sizeof(failCode)); unused = write(pipe, &err, sizeof(err)); // As a fallback, exit with the failCode _exit(failCode); } ProcHandle runInteractiveProcess (char *const args[], char *workingDirectory, char **environment, int fdStdIn, int fdStdOut, int fdStdErr, 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; // 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; } } 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; } // 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 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; } 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; } if (pid == -1) { err = errno; } close(forkCommunicationFds[0]); unblockUserSignals(); startTimer(); if (pid == -1) { errno = err; } 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)) { *pExitCode = TERMSIG_EXITSTATUS(wstat); 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; if (waitpid(handle, &wstat, 0) < 0) { return -1; } if (WIFEXITED(wstat)) { *pret = WEXITSTATUS(wstat); return 0; } else { if (WIFSIGNALED(wstat)) { *pret = TERMSIG_EXITSTATUS(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; } static HANDLE createJob () { HANDLE hJob = CreateJobObject (NULL, NULL); JOBOBJECT_EXTENDED_LIMIT_INFORMATION jeli; ZeroMemory(&jeli, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION)); // Configure all child processes associated with the job to terminate when the // Last process in the job terminates. This prevent half dead processes. jeli.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE; if (SetInformationJobObject (hJob, JobObjectExtendedLimitInformation, &jeli, sizeof(JOBOBJECT_EXTENDED_LIMIT_INFORMATION))) { return hJob; } maperrno(); return NULL; } static HANDLE createCompletionPort (HANDLE hJob) { HANDLE ioPort = CreateIoCompletionPort (INVALID_HANDLE_VALUE, NULL, 0, 1); if (!ioPort) { // Something failed. Error is in GetLastError, let caller handler it. return NULL; } JOBOBJECT_ASSOCIATE_COMPLETION_PORT Port; Port.CompletionKey = hJob; Port.CompletionPort = ioPort; if (!SetInformationJobObject(hJob, JobObjectAssociateCompletionPortInformation, &Port, sizeof(Port))) { // Something failed. Error is in GetLastError, let caller handler it. return NULL; } return ioPort; } /* Note [Windows exec interaction] The basic issue that process jobs tried to solve is this: Say you have two programs A and B. Now A calls B. There are two ways to do this. 1) You can use the normal CreateProcess API, which is what normal Windows code do. Using this approach, the current waitForProcess works absolutely fine. 2) You can call the emulated POSIX function _exec, which of course is supposed to allow the child process to replace the parent. With approach 2) waitForProcess falls apart because the Win32's process model does not allow this the same way as linux. _exec is emulated by first making a call to CreateProcess to spawn B and then immediately exiting from A. So you have two different processes. waitForProcess is waiting on the termination of A. Because A is immediately killed, waitForProcess will return even though B is still running. This is why for instance the GHC testsuite on Windows had lots of file locked errors. This approach creates a new Job and assigned A to the job, but also all future processes spawned by A. This allows us to listen in on events, such as, when all processes in the job are finished, but also allows us to propagate exit codes from _exec calls. The only reason we need this at all is because we don't interact with just actual native code on Windows, and instead have a lot of ported POSIX code. The Job handle is returned to the user because Jobs have additional benefits as well, such as allowing you to specify resource limits on the to be spawned process. */ ProcHandle runInteractiveProcess (wchar_t *cmd, wchar_t *workingDirectory, wchar_t *environment, int fdStdIn, int fdStdOut, int fdStdErr, int *pfdStdInput, int *pfdStdOutput, int *pfdStdError, int flags, bool useJobObject, HANDLE *hJob, HANDLE *hIOcpPort) { 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; BOOL close_fds = ((flags & RUN_PROCESS_IN_CLOSE_FDS) != 0); // We always pass a wide environment block, so we MUST set this flag DWORD dwFlags = CREATE_UNICODE_ENVIRONMENT; BOOL status; BOOL inherit; ZeroMemory(&sInfo, sizeof(sInfo)); sInfo.cb = sizeof(sInfo); sInfo.dwFlags = STARTF_USESTDHANDLES; ZeroMemory(&pInfo, sizeof(pInfo)); if (fdStdIn == -1) { if (!mkAnonPipe(&hStdInputRead, TRUE, &hStdInputWrite, FALSE)) goto cleanup_err; sInfo.hStdInput = hStdInputRead; } else if (fdStdIn == -2) { sInfo.hStdInput = NULL; } 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 == -2) { sInfo.hStdOutput = NULL; } 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) { sInfo.hStdError = NULL; } 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 & RUN_PROCESS_IN_NEW_GROUP) == 0) dwFlags |= CREATE_NO_WINDOW; // Run without console window only when both output and error are redirected // See #3231 if (close_fds && fdStdIn == 0 && fdStdOut == 1 && fdStdErr == 2) { inherit = FALSE; } else { inherit = TRUE; } if ((flags & RUN_PROCESS_IN_NEW_GROUP) != 0) { dwFlags |= CREATE_NEW_PROCESS_GROUP; } if ((flags & RUN_PROCESS_DETACHED) != 0) { dwFlags |= DETACHED_PROCESS; } if ((flags & RUN_PROCESS_NEW_CONSOLE) != 0) { dwFlags |= CREATE_NEW_CONSOLE; } /* If we're going to use a job object, then we have to create the thread suspended. See Note [Windows exec interaction]. */ if (useJobObject) { dwFlags |= CREATE_SUSPENDED; *hJob = createJob(); if (!*hJob) { goto cleanup_err; } // Create the completion port and attach it to the job *hIOcpPort = createCompletionPort(*hJob); if (!*hIOcpPort) { goto cleanup_err; } } else { *hJob = NULL; *hIOcpPort = NULL; } if (!CreateProcess(NULL, cmd, NULL, NULL, inherit, dwFlags, environment, workingDirectory, &sInfo, &pInfo)) { goto cleanup_err; } if (useJobObject && hJob && *hJob) { // Then associate the process and the job; if (!AssignProcessToJobObject (*hJob, pInfo.hProcess)) { goto cleanup_err; } // And now that we've associated the new process with the job // we can actively resume it. ResumeThread (pInfo.hThread); } 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 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); if (useJobObject && hJob && *hJob ) CloseHandle(*hJob); if (useJobObject && hIOcpPort && *hIOcpPort) CloseHandle(*hIOcpPort); maperrno(); return NULL; } int terminateProcess (ProcHandle handle) { if (!TerminateProcess ((HANDLE) handle, 1)) { DWORD e = GetLastError(); DWORD exitCode; /* This is a crude workaround that is taken from libuv. For some reason TerminateProcess() can fail with ERROR_ACCESS_DENIED if the process already terminated. This situation can be detected by using GetExitCodeProcess() to check if the exit code is availble. Unfortunately this function succeeds and gives exit code 259 (STILL_ACTIVE) if the process is still running. So there is no way to ditinguish a process that exited with 259 and a process that did not exit because we had insufficient access to terminate it. One would expect WaitForSingleObject() to be the solid solution. But this function does return WAIT_TIMEOUT in that situation. Even if called after GetExitCodeProcess(). */ if (e == ERROR_ACCESS_DENIED && GetExitCodeProcess((HANDLE) handle, &exitCode) && exitCode != STILL_ACTIVE) return 0; SetLastError(e); maperrno(); return -1; } return 0; } int terminateJob (ProcHandle handle) { if (!TerminateJobObject ((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; } int waitForJobCompletion ( HANDLE hJob, HANDLE ioPort, DWORD timeout, int *pExitCode, setterDef set, getterDef get ) { DWORD CompletionCode; ULONG_PTR CompletionKey; LPOVERLAPPED Overlapped; *pExitCode = 0; // We have to loop here. It's a blocking call, but // we get notified on each completion event. So if it's // not one we care for we should just block again. // If all processes are finished before this call is made // then the initial call will return false. // List of events we can listen to: // https://msdn.microsoft.com/en-us/library/windows/desktop/ms684141(v=vs.85).aspx while (GetQueuedCompletionStatus (ioPort, &CompletionCode, &CompletionKey, &Overlapped, timeout)) { // If event wasn't meant of us, keep listening. if ((HANDLE)CompletionKey != hJob) continue; switch (CompletionCode) { case JOB_OBJECT_MSG_NEW_PROCESS: { // A new child process is born. // Retrieve and save the process handle from the process id. // We'll need it for later but we can't retrieve it after the // process has exited. DWORD pid = (DWORD)(uintptr_t)Overlapped; HANDLE pHwnd = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, TRUE, pid); set(pid, pHwnd); } break; case JOB_OBJECT_MSG_ABNORMAL_EXIT_PROCESS: case JOB_OBJECT_MSG_EXIT_PROCESS: { // A child process has just exited. // Read exit code, We assume the last process to exit // is the process whose exit code we're interested in. HANDLE pHwnd = get((DWORD)(uintptr_t)Overlapped); if (GetExitCodeProcess(pHwnd, (DWORD *)pExitCode) == 0) { maperrno(); return 1; } // Check to see if the child has actually exited. if (*(DWORD *)pExitCode == STILL_ACTIVE) waitForProcess ((ProcHandle)pHwnd, pExitCode); } break; case JOB_OBJECT_MSG_ACTIVE_PROCESS_ZERO: // All processes in the tree are done. return 0; default: break; } } // Check to see if a timeout has occurred or that the // all processes in the job were finished by the time we // got to the loop. if (Overlapped == NULL && (HANDLE)CompletionKey != hJob) { // Timeout occurred. return -1; } return 0; } #endif /* Win32 */