| /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying | |
| file LICENSE.rst or https://cmake.org/licensing for details. */ | |
| /* Test helper to exercise instrumentation handling of an interrupted build. | |
| Usage: InterruptBuild <delay-seconds> <command> [args...] | |
| It runs <command> in its own process group, waits <delay-seconds>, then | |
| delivers a user interrupt to that group only -- mimicking a user pressing | |
| Ctrl+C on the build's foreground process group, while leaving the calling | |
| test driver untouched. It then reports a fixed exit code so the test can | |
| assert deterministically: | |
| 42 - the build was interrupted and then stopped (expected) | |
| 2 - usage error | |
| 3 - failed to start the build | |
| 99 - the build did not stop after the interrupt | |
| 100 + N - the build exited normally with code N (unexpected, POSIX only) | |
| On POSIX the interrupt is SIGINT sent to the child's process group; the | |
| instrumented `cmake` re-raises it, so the child terminates via a signal. | |
| On Windows the interrupt is a CTRL_BREAK_EVENT sent to the child's process | |
| group (created with CREATE_NEW_PROCESS_GROUP); `cmake`'s console handler | |
| treats CTRL_BREAK identically to CTRL_C. Because Windows `cmake` returns | |
| the build's (failed) exit code rather than a signal status, success is | |
| detected as "the child exited promptly after the interrupt". The real | |
| assertion -- that an interrupted snippet was written -- is done by the | |
| accompanying check script. */ | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #if defined(_WIN32) | |
| # include <windows.h> | |
| # include <string.h> | |
| int main(int argc, char** argv) | |
| { | |
| int delay; | |
| char cmdline[32768]; | |
| size_t len = 0; | |
| int i; | |
| size_t n; | |
| STARTUPINFOA si; | |
| PROCESS_INFORMATION pi; | |
| DWORD waitRc; | |
| if (argc < 3) { | |
| fprintf(stderr, | |
| "Usage: InterruptBuild <delay-seconds> <command> [args...]\n"); | |
| return 2; | |
| } | |
| delay = atoi(argv[1]); | |
| if (delay <= 0) { | |
| delay = 1; | |
| } | |
| /* Rebuild a command line from argv[2..]. The inputs are controlled by the | |
| test (a cmake path plus simple flags) and never contain embedded quotes, | |
| so wrapping each argument in quotes is sufficient and correct. The buffer | |
| is sized to the CreateProcess command-line limit of 32768 characters, | |
| including the terminating null. */ | |
| for (i = 2; i < argc; ++i) { | |
| n = strlen(argv[i]); | |
| if (len + n + 4 >= sizeof(cmdline)) { | |
| return 2; | |
| } | |
| if (i > 2) { | |
| cmdline[len++] = ' '; | |
| } | |
| cmdline[len++] = '"'; | |
| memcpy(cmdline + len, argv[i], n); | |
| len += n; | |
| cmdline[len++] = '"'; | |
| } | |
| cmdline[len] = '\0'; | |
| ZeroMemory(&si, sizeof(si)); | |
| si.cb = sizeof(si); | |
| ZeroMemory(&pi, sizeof(pi)); | |
| /* New process group so the control event reaches only the build (and its | |
| children), not this helper or the test driver. */ | |
| if (!CreateProcessA(NULL, cmdline, NULL, NULL, FALSE, | |
| CREATE_NEW_PROCESS_GROUP, NULL, NULL, &si, &pi)) { | |
| return 3; | |
| } | |
| Sleep((DWORD)delay * 1000); | |
| GenerateConsoleCtrlEvent(CTRL_BREAK_EVENT, pi.dwProcessId); | |
| waitRc = WaitForSingleObject(pi.hProcess, 30000); | |
| if (waitRc != WAIT_OBJECT_0) { | |
| TerminateProcess(pi.hProcess, 1); | |
| CloseHandle(pi.hProcess); | |
| CloseHandle(pi.hThread); | |
| return 99; | |
| } | |
| CloseHandle(pi.hProcess); | |
| CloseHandle(pi.hThread); | |
| return 42; | |
| } | |
| #else | |
| # include <signal.h> | |
| # include <unistd.h> | |
| # include <sys/types.h> | |
| # include <sys/wait.h> | |
| int main(int argc, char** argv) | |
| { | |
| int delay; | |
| pid_t build; | |
| pid_t killer; | |
| int status = 0; | |
| if (argc < 3) { | |
| fprintf(stderr, | |
| "Usage: InterruptBuild <delay-seconds> <command> [args...]\n"); | |
| return 2; | |
| } | |
| delay = atoi(argv[1]); | |
| if (delay <= 0) { | |
| delay = 1; | |
| } | |
| build = fork(); | |
| if (build < 0) { | |
| return 3; | |
| } | |
| if (build == 0) { | |
| /* Become the leader of a new process group, then run the build. The | |
| native build tool inherits this group, so a later group signal reaches | |
| it too. */ | |
| setpgid(0, 0); | |
| execvp(argv[2], &argv[2]); | |
| _exit(127); | |
| } | |
| /* Best-effort from the parent side to avoid a setpgid race; ignore errors. | |
| */ | |
| setpgid(build, build); | |
| killer = fork(); | |
| if (killer == 0) { | |
| sleep((unsigned int)delay); | |
| killpg(build, SIGINT); | |
| _exit(0); | |
| } | |
| waitpid(build, &status, 0); | |
| if (killer > 0) { | |
| kill(killer, SIGKILL); | |
| waitpid(killer, NULL, 0); | |
| } | |
| if (WIFSIGNALED(status)) { | |
| return 42; | |
| } | |
| if (WIFEXITED(status)) { | |
| return 100 + WEXITSTATUS(status); | |
| } | |
| return 99; | |
| } | |
| #endif |