diff --git a/ProcessUNIX.c b/ProcessUNIX.c index c7afb5eb1b582f695142de0e9d838b929e28890b..8c2b2e4633b734284016cb73f22dd9b556de7938 100644 --- a/ProcessUNIX.c +++ b/ProcessUNIX.c @@ -2582,8 +2582,10 @@ typedef struct kwsysProcessInstances_s } kwsysProcessInstances; static kwsysProcessInstances kwsysProcesses; -/* The old SIGCHLD handler. */ +/* The old SIGCHLD / SIGINT / SIGTERM handlers. */ static struct sigaction kwsysProcessesOldSigChldAction; +static struct sigaction kwsysProcessesOldSigIntAction; +static struct sigaction kwsysProcessesOldSigTermAction; /*--------------------------------------------------------------------------*/ static void kwsysProcessesUpdate(kwsysProcessInstances* newProcesses) @@ -2686,21 +2688,36 @@ static int kwsysProcessesAdd(kwsysProcess* cp) { /* Install our handler for SIGCHLD. Repeat call until it is not interrupted. */ - struct sigaction newSigChldAction; - memset(&newSigChldAction, 0, sizeof(struct sigaction)); + struct sigaction newSigAction; + memset(&newSigAction, 0, sizeof(struct sigaction)); #if KWSYSPE_USE_SIGINFO - newSigChldAction.sa_sigaction = kwsysProcessesSignalHandler; - newSigChldAction.sa_flags = SA_NOCLDSTOP | SA_SIGINFO; + newSigAction.sa_sigaction = kwsysProcessesSignalHandler; + newSigAction.sa_flags = SA_NOCLDSTOP | SA_SIGINFO; # ifdef SA_RESTART - newSigChldAction.sa_flags |= SA_RESTART; + newSigAction.sa_flags |= SA_RESTART; # endif #else - newSigChldAction.sa_handler = kwsysProcessesSignalHandler; - newSigChldAction.sa_flags = SA_NOCLDSTOP; + newSigAction.sa_handler = kwsysProcessesSignalHandler; + newSigAction.sa_flags = SA_NOCLDSTOP; #endif - while((sigaction(SIGCHLD, &newSigChldAction, + sigemptyset(&newSigAction.sa_mask); + while((sigaction(SIGCHLD, &newSigAction, &kwsysProcessesOldSigChldAction) < 0) && (errno == EINTR)); + + /* Install our handler for SIGINT / SIGTERM. Repeat call until + it is not interrupted. */ + sigemptyset(&newSigAction.sa_mask); + sigaddset(&newSigAction.sa_mask, SIGTERM); + while((sigaction(SIGINT, &newSigAction, + &kwsysProcessesOldSigIntAction) < 0) && + (errno == EINTR)); + + sigemptyset(&newSigAction.sa_mask); + sigaddset(&newSigAction.sa_mask, SIGINT); + while((sigaction(SIGTERM, &newSigAction, + &kwsysProcessesOldSigIntAction) < 0) && + (errno == EINTR)); } } @@ -2734,10 +2751,14 @@ static void kwsysProcessesRemove(kwsysProcess* cp) /* If this was the last process, disable the signal handler. */ if(newProcesses.Count == 0) { - /* Restore the SIGCHLD handler. Repeat call until it is not + /* Restore the signal handlers. Repeat call until it is not interrupted. */ while((sigaction(SIGCHLD, &kwsysProcessesOldSigChldAction, 0) < 0) && (errno == EINTR)); + while((sigaction(SIGINT, &kwsysProcessesOldSigIntAction, 0) < 0) && + (errno == EINTR)); + while((sigaction(SIGTERM, &kwsysProcessesOldSigTermAction, 0) < 0) && + (errno == EINTR)); /* Free the table of process pointers since it is now empty. This is safe because the signal handler has been removed. */ @@ -2763,39 +2784,84 @@ static void kwsysProcessesSignalHandler(int signum #endif ) { - (void)signum; + int i, procStatus, old_errno = errno; #if KWSYSPE_USE_SIGINFO (void)info; (void)ucontext; #endif /* Signal all process objects that a child has terminated. */ - { - int i; - for(i=0; i < kwsysProcesses.Count; ++i) + switch(signum) { - /* Set the pipe in a signalled state. */ - char buf = 1; - kwsysProcess* cp = kwsysProcesses.Processes[i]; - kwsysProcess_ssize_t status= - read(cp->PipeReadEnds[KWSYSPE_PIPE_SIGNAL], &buf, 1); - (void)status; - status=write(cp->SignalPipe, &buf, 1); - (void)status; + case SIGCHLD: + for(i=0; i < kwsysProcesses.Count; ++i) + { + /* Set the pipe in a signalled state. */ + char buf = 1; + kwsysProcess* cp = kwsysProcesses.Processes[i]; + kwsysProcess_ssize_t pipeStatus= + read(cp->PipeReadEnds[KWSYSPE_PIPE_SIGNAL], &buf, 1); + (void)pipeStatus; + pipeStatus=write(cp->SignalPipe, &buf, 1); + (void)pipeStatus; + } + break; + case SIGINT: + case SIGTERM: + /* Wait for all processes to terminate. */ + while(wait(&procStatus) >= 0 || errno != ECHILD) + { + } + /* Terminate the process, which is now in an inconsistent state + because we reaped all the PIDs that it may have been reaping + or may have reaped in the future. Reraise the signal so that + the proper exit code is returned. */ + { + /* Install default signal handler. */ + struct sigaction defSigAction; + sigset_t unblockSet; + memset(&defSigAction, 0, sizeof(defSigAction)); + defSigAction.sa_handler = SIG_DFL; + sigemptyset(&defSigAction.sa_mask); + while((sigaction(signum, &defSigAction, 0) < 0) && + (errno == EINTR)); + /* Unmask the signal. */ + sigemptyset(&unblockSet); + sigaddset(&unblockSet, signum); + sigprocmask(SIG_UNBLOCK, &unblockSet, 0); + /* Raise the signal again. */ + raise(signum); + /* We shouldn't get here... but if we do... */ + _exit(1); + } + /* break omitted to silence unreachable code clang compiler warning. */ } - } #if !KWSYSPE_USE_SIGINFO - /* Re-Install our handler for SIGCHLD. Repeat call until it is not - interrupted. */ + /* Re-Install our handler. Repeat call until it is not interrupted. */ { - struct sigaction newSigChldAction; - memset(&newSigChldAction, 0, sizeof(struct sigaction)); + struct sigaction newSigAction; + struct sigaction &oldSigAction; + memset(&newSigAction, 0, sizeof(struct sigaction)); newSigChldAction.sa_handler = kwsysProcessesSignalHandler; newSigChldAction.sa_flags = SA_NOCLDSTOP; - while((sigaction(SIGCHLD, &newSigChldAction, - &kwsysProcessesOldSigChldAction) < 0) && + sigemptyset(&newSigAction.sa_mask); + switch(signum) + { + case SIGCHLD: oldSigAction = &kwsysProcessesOldSigChldAction; break; + case SIGINT: + sigaddset(&newSigAction.sa_mask, SIGTERM); + oldSigAction = &kwsysProcessesOldSigIntAction; break; + case SIGTERM: + sigaddset(&newSigAction.sa_mask, SIGINT); + oldSigAction = &kwsysProcessesOldSigTermAction; break; + default: return 0; + } + while((sigaction(signum, &newSigAction, + oldSigAction) < 0) && (errno == EINTR)); } #endif + + errno = old_errno; } diff --git a/ProcessWin32.c b/ProcessWin32.c index a7dd2ca3e4ade84b0600034cd3206b3ab5c340a0..086fc391c3157be9ece056777b7f5078e87b4942 100644 --- a/ProcessWin32.c +++ b/ProcessWin32.c @@ -133,6 +133,12 @@ static kwsysProcessTime kwsysProcessTimeSubtract(kwsysProcessTime in1, kwsysProc static void kwsysProcessSetExitException(kwsysProcess* cp, int code); static void kwsysProcessKillTree(int pid); static void kwsysProcessDisablePipeThreads(kwsysProcess* cp); +static int kwsysProcessesInitialize(void); +static int kwsysTryEnterCreateProcessSection(void); +static void kwsysLeaveCreateProcessSection(void); +static int kwsysProcessesAdd(HANDLE hProcess); +static void kwsysProcessesRemove(HANDLE hProcess); +static BOOL WINAPI kwsysCtrlHandler(DWORD dwCtrlType); /*--------------------------------------------------------------------------*/ /* A structure containing synchronization data for each thread. */ @@ -321,6 +327,16 @@ kwsysProcess* kwsysProcess_New(void) /* Windows version number data. */ OSVERSIONINFO osv; + /* Initialize list of processes before we get any farther. It's especially + important that the console Ctrl handler be added BEFORE starting the + first process. This prevents the risk of an orphaned process being + started by the main thread while the default Ctrl handler is in + progress. */ + if(!kwsysProcessesInitialize()) + { + return 0; + } + /* Allocate a process control structure. */ cp = (kwsysProcess*)malloc(sizeof(kwsysProcess)); if(!cp) @@ -1487,7 +1503,8 @@ void kwsysProcess_Kill(kwsysProcess* cp) for(i=0; i < cp->NumberOfCommands; ++i) { kwsysProcessKillTree(cp->ProcessInformation[i].dwProcessId); - // close the handle if we kill it + /* Remove from global list of processes and close handles. */ + kwsysProcessesRemove(cp->ProcessInformation[i].hProcess); kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hThread); kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hProcess); } @@ -1728,7 +1745,19 @@ static int kwsysProcessCreateChildHandle(PHANDLE out, HANDLE in, int isStdIn) int kwsysProcessCreate(kwsysProcess* cp, int index, kwsysProcessCreateInformation* si) { - int res = + int res; + + /* Check if we are currently exiting. */ + if (!kwsysTryEnterCreateProcessSection()) + { + /* The Ctrl handler is currently working on exiting our process. Rather + than return an error code, which could cause incorrect conclusions to be + reached by the caller, we simply hang. (For example, a CMake try_run + configure step might cause the project to configure wrong.) */ + Sleep(INFINITE); + } + + res = /* Create inherited copies the handles. */ kwsysProcessCreateChildHandle(&si->StartupInfo.hStdInput, @@ -1757,6 +1786,19 @@ int kwsysProcessCreate(kwsysProcess* cp, int index, kwsysProcessCleanupHandle(&si->StartupInfo.hStdError); } + /* Add the process to the global list of processes. */ + if (!kwsysProcessesAdd(cp->ProcessInformation[index].hProcess)) + { + /* This failed for some reason. Kill the suspended process. */ + TerminateProcess(cp->ProcessInformation[index].hProcess, 1); + /* And clean up... */ + kwsysProcessCleanupHandle(&cp->ProcessInformation[index].hProcess); + kwsysProcessCleanupHandle(&cp->ProcessInformation[index].hThread); + res = 0; + } + + /* If the console Ctrl handler is waiting for us, this will release it... */ + kwsysLeaveCreateProcessSection(); return res; } @@ -1779,6 +1821,9 @@ void kwsysProcessDestroy(kwsysProcess* cp, int event) GetExitCodeProcess(cp->ProcessInformation[index].hProcess, &cp->CommandExitCodes[index]); + /* Remove from global list of processes. */ + kwsysProcessesRemove(cp->ProcessInformation[index].hProcess); + /* Close the process handle for the terminated process. */ kwsysProcessCleanupHandle(&cp->ProcessInformation[index].hProcess); @@ -1923,6 +1968,8 @@ void kwsysProcessCleanup(kwsysProcess* cp, int error) } for(i=0; i < cp->NumberOfCommands; ++i) { + /* Remove from global list of processes and close handles. */ + kwsysProcessesRemove(cp->ProcessInformation[i].hProcess); kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hThread); kwsysProcessCleanupHandle(&cp->ProcessInformation[i].hProcess); } @@ -2659,3 +2706,203 @@ static void kwsysProcessDisablePipeThreads(kwsysProcess* cp) ReleaseSemaphore(cp->Pipe[cp->CurrentIndex].Reader.Go, 1, 0); } } + +/*--------------------------------------------------------------------------*/ +/* Global set of executing processes for use by the Ctrl handler. + This global instance will be zero-initialized by the compiler. + + Note that the console Ctrl handler runs on a background thread and so + everything it does must be thread safe. Here, we track the hProcess + HANDLEs directly instead of kwsysProcess instances, so that we don't have + to make kwsysProcess thread safe. */ +typedef struct kwsysProcessInstances_s +{ + /* Whether we have initialized key fields below, like critical sections. */ + int Initialized; + + /* Ctrl handler runs on a different thread, so we must sync access. */ + CRITICAL_SECTION Lock; + + int Exiting; + size_t Count; + size_t Size; + HANDLE* Processes; +} kwsysProcessInstances; +static kwsysProcessInstances kwsysProcesses; + +/*--------------------------------------------------------------------------*/ +/* Initialize critial section and set up console Ctrl handler. You MUST call + this before using any other kwsysProcesses* functions below. */ +static int kwsysProcessesInitialize(void) +{ + /* Initialize everything if not done already. */ + if(!kwsysProcesses.Initialized) + { + InitializeCriticalSection(&kwsysProcesses.Lock); + + /* Set up console ctrl handler. */ + if(!SetConsoleCtrlHandler(kwsysCtrlHandler, TRUE)) + { + return 0; + } + + kwsysProcesses.Initialized = 1; + } + return 1; +} + +/*--------------------------------------------------------------------------*/ +/* The Ctrl handler waits on the global list of processes. To prevent an + orphaned process, do not create a new process if the Ctrl handler is + already running. Do so by using this function to check if it is ok to + create a process. */ +static int kwsysTryEnterCreateProcessSection(void) +{ + /* Enter main critical section; this means creating a process and the Ctrl + handler are mutually exclusive. */ + EnterCriticalSection(&kwsysProcesses.Lock); + /* Indicate to the caller if they can create a process. */ + if(kwsysProcesses.Exiting) + { + LeaveCriticalSection(&kwsysProcesses.Lock); + return 0; + } + else + { + return 1; + } +} + +/*--------------------------------------------------------------------------*/ +/* Matching function on successful kwsysTryEnterCreateProcessSection return. + Make sure you called kwsysProcessesAdd if applicable before calling this.*/ +static void kwsysLeaveCreateProcessSection(void) +{ + LeaveCriticalSection(&kwsysProcesses.Lock); +} + +/*--------------------------------------------------------------------------*/ +/* Add new process to global process list. The Ctrl handler will wait for + the process to exit before it returns. Do not close the process handle + until after calling kwsysProcessesRemove. */ +static int kwsysProcessesAdd(HANDLE hProcess) +{ + if(!kwsysProcessesInitialize() || !hProcess || + hProcess == INVALID_HANDLE_VALUE) + { + return 0; + } + + /* Enter the critical section. */ + EnterCriticalSection(&kwsysProcesses.Lock); + + /* Make sure there is enough space for the new process handle. */ + if(kwsysProcesses.Count == kwsysProcesses.Size) + { + size_t newSize; + HANDLE *newArray; + /* Start with enough space for a small number of process handles + and double the size each time more is needed. */ + newSize = kwsysProcesses.Size? kwsysProcesses.Size*2 : 4; + + /* Try allocating the new block of memory. */ + if(newArray = (HANDLE*)malloc(newSize*sizeof(HANDLE))) + { + /* Copy the old process handles to the new memory. */ + if(kwsysProcesses.Count > 0) + { + memcpy(newArray, kwsysProcesses.Processes, + kwsysProcesses.Count * sizeof(HANDLE)); + } + } + else + { + /* Failed to allocate memory for the new process handle set. */ + LeaveCriticalSection(&kwsysProcesses.Lock); + return 0; + } + + /* Free original array. */ + free(kwsysProcesses.Processes); + + /* Update original structure with new allocation. */ + kwsysProcesses.Size = newSize; + kwsysProcesses.Processes = newArray; + } + + /* Append the new process handle to the set. */ + kwsysProcesses.Processes[kwsysProcesses.Count++] = hProcess; + + /* Leave critical section and return success. */ + LeaveCriticalSection(&kwsysProcesses.Lock); + + return 1; +} + +/*--------------------------------------------------------------------------*/ +/* Removes process to global process list. */ +static void kwsysProcessesRemove(HANDLE hProcess) +{ + size_t i; + + if (!hProcess || hProcess == INVALID_HANDLE_VALUE) + { + return; + } + + EnterCriticalSection(&kwsysProcesses.Lock); + + /* Find the given process in the set. */ + for(i=0; i < kwsysProcesses.Count; ++i) + { + if(kwsysProcesses.Processes[i] == hProcess) + { + break; + } + } + if(i < kwsysProcesses.Count) + { + /* Found it! Remove the process from the set. */ + --kwsysProcesses.Count; + for(; i < kwsysProcesses.Count; ++i) + { + kwsysProcesses.Processes[i] = kwsysProcesses.Processes[i+1]; + } + + /* If this was the last process, free the array. */ + if(kwsysProcesses.Count == 0) + { + kwsysProcesses.Size = 0; + free(kwsysProcesses.Processes); + kwsysProcesses.Processes = 0; + } + } + + LeaveCriticalSection(&kwsysProcesses.Lock); +} + +/*--------------------------------------------------------------------------*/ +static BOOL WINAPI kwsysCtrlHandler(DWORD dwCtrlType) +{ + (void)dwCtrlType; + /* Enter critical section. */ + EnterCriticalSection(&kwsysProcesses.Lock); + + /* Set flag indicating that we are exiting. */ + kwsysProcesses.Exiting = 1; + + /* Wait for each child process to exit. This is the key step that prevents + us from leaving several orphaned children processes running in the + background when the user presses Ctrl+C. */ + if(kwsysProcesses.Count > 0) /* WaitForMultipleObjects forbids Count==0. */ + { + WaitForMultipleObjects((DWORD)kwsysProcesses.Count, + kwsysProcesses.Processes, TRUE, INFINITE); + } + + /* Leave critical section. */ + LeaveCriticalSection(&kwsysProcesses.Lock); + + /* Continue on to default Ctrl handler (which calls ExitProcess). */ + return FALSE; +}