From 628db65504a69cff1d04a0639ace4a9358504fff Mon Sep 17 00:00:00 2001 From: Mahmoud Al-Qudsi Date: Sat, 29 Jul 2017 16:42:06 -0500 Subject: [PATCH] OS X EINVAL compatibility for waitpid The return value on OS X is more along the lines of the documented waitpid behavior; EINVAL is returned if the group no longer exists. --- src/exec.cpp | 9 ++++++++- src/proc.cpp | 23 +++++++++++++++++------ 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/src/exec.cpp b/src/exec.cpp index 23e604f48..89d24f06a 100644 --- a/src/exec.cpp +++ b/src/exec.cpp @@ -1163,7 +1163,14 @@ void exec_job(parser_t &parser, job_t *j) { //but we only need to call set_child_group for the first process in the group. //If needs_keepalive is set, this has already been called for the keepalive process pid_t pid_status{}; - if (waitpid(p->pid, &pid_status, WUNTRACED) == -1) { + int result; + while ((result = waitpid(p->pid, &pid_status, WUNTRACED)) == -1 && errno == EINTR) { + //This could be a superfluous interrupt or Ctrl+C at the terminal + //In all cases, it is OK to retry since the forking code above is specifically designed + //to never, ever hang/block in a child process before the SIGSTOP call is reached. + continue; + } + if (result == -1) { exec_error = true; debug(1, L"waitpid(%d) call in unblock_pid failed:!\n", p->pid); wperror(L"waitpid"); diff --git a/src/proc.cpp b/src/proc.cpp index 37c267850..a4b577b68 100644 --- a/src/proc.cpp +++ b/src/proc.cpp @@ -819,22 +819,33 @@ bool terminal_give_to_job(job_t *j, int cont) { //thing is that we can guarantee the process isn't going to exit while we wait (which would cause us to //possibly block indefinitely). + auto pgroupTerminated = [&j]() { + //everyone in the process group has exited + //The only way that can happen is if the very last process in the group terminated, and didn't need + //to access the terminal, otherwise it would have hung waiting for terminal IO. We can ignore this. + debug(3, L"terminal_give_to_job(): tcsetpgrp called but process group %d has terminated.\n", j->pgid); + }; + while (tcsetpgrp(STDIN_FILENO, j->pgid) != 0) { if (errno == EINTR) { //always retry on EINTR } + else if (errno == EINVAL) { + //OS X returns EINVAL if the process group no longer lives. Probably other OSes, too. + //See comments in pgroupTerminated() above. + pgroupTerminated(); + break; + } else if (errno == EPERM) { - //so long as this isn't because the process group is dead + //retry so long as this isn't because the process group is dead int wait_result = waitpid(-1 * j->pgid, &wait_result, WNOHANG); if (wait_result == -1) { - //everyone in the process group has exited - //The only way that can happen is if the very last process in the group terminated, and didn't need - //to access the terminal, otherwise it would have hung waiting for terminal IO. We can ignore this. //Note that -1 is technically an "error" for waitpid in the sense that an invalid argument was specified - //because no such process group exists any longer. + //because no such process group exists any longer. This is the observed behavior on Linux 4.4.0. //a "success" result would mean processes from the group still exist but is still running in some state //or the other. - debug(3, L"terminal_give_to_job(): tcsetpgrp called but process group %d has terminated.\n", j->pgid); + //See comments in pgroupTerminated() above. + pgroupTerminated(); break; } debug(2, L"terminal_give_to_job(): EPERM.\n", j->pgid);