/* Distributed under the OSI-approved BSD 3-Clause License. See accompanying file LICENSE.rst or https://cmake.org/licensing for details. */ #if !defined(_POSIX_C_SOURCE) && !defined(_WIN32) && !defined(__sun) && \ !defined(__OpenBSD__) // POSIX APIs are needed (sigaction, sigemptyset, SA_RESETHAND). // NOLINTNEXTLINE(bugprone-reserved-identifier) # define _POSIX_C_SOURCE 200809L #endif #if defined(__FreeBSD__) || defined(__NetBSD__) || defined(__QNX__) // For isascii // NOLINTNEXTLINE(bugprone-reserved-identifier) # define _XOPEN_SOURCE 700 #endif #include "cmInstrumentationInterrupt.h" #include #include #ifdef _WIN32 # include # include #else # include #endif namespace { // Flag shared between the interrupt handler and the command flow that writes // the instrumentation envelope snippet. On Windows the console control // handler runs on a separate thread, so an atomic is required; on POSIX the // handler runs in signal context, where only `volatile sig_atomic_t` is // guaranteed safe. #ifdef _WIN32 std::atomic interruptSignal{ 0 }; BOOL WINAPI cmInstrumentationConsoleHandler(DWORD type) { if (type == CTRL_C_EVENT || type == CTRL_BREAK_EVENT) { int expected = 0; interruptSignal.compare_exchange_strong(expected, SIGINT); // Return TRUE so the main thread can finish writing the snippet before the // process exits. Child processes (native build tool, install scripts, // tests) share the console and receive the event directly, so they still // terminate and unblock our loop. return TRUE; } return FALSE; } #else sig_atomic_t volatile interruptSignal = 0; struct sigaction savedSigIntAction; extern "C" void cmInstrumentationSignalHandler(int sig) { interruptSignal = sig; } #endif // Set when the pending interrupt was injected by the test seam below rather // than delivered by the OS. An injected interrupt must NOT be re-raised (the // process exits normally after flushing the snippet), so the test stays a // clean-exit, leak-checkable case on every generator. bool interruptInjected = false; // Test-only seam. An undocumented, unsupported environment variable lets the // instrumentation test suite inject an "interrupted" condition // deterministically, with no real OS signal -- so the instrumentation // interrupt path can be exercised on every generator and platform. The // double-underscore name marks it internal; it is never set in normal use. // Mirrors CTest's internal fake-hook convention. void InjectTestInterrupt() { char const* value = std::getenv("__CMAKE_INSTRUMENTATION_TEST_INTERRUPT"); if (!value) { return; } int sig = std::atoi(value); if (sig <= 0) { return; } #ifdef _WIN32 interruptSignal.store(sig); #else interruptSignal = static_cast(sig); #endif interruptInjected = true; } // Install the interrupt handler and clear any previously recorded signal. void InstallInterruptHandler() { #ifdef _WIN32 interruptSignal.store(0); SetConsoleCtrlHandler(cmInstrumentationConsoleHandler, TRUE); #else interruptSignal = 0; struct sigaction sa; memset(&sa, 0, sizeof(sa)); sa.sa_handler = cmInstrumentationSignalHandler; sigemptyset(&sa.sa_mask); // One-shot: after the first interrupt the default disposition is restored, // so a second Ctrl+C terminates immediately even while we are mid-flush. sa.sa_flags = SA_RESETHAND; sigaction(SIGINT, &sa, &savedSigIntAction); #endif } // Restore the disposition that was in effect before InstallInterruptHandler(). void RestoreInterruptHandler() { #ifdef _WIN32 SetConsoleCtrlHandler(cmInstrumentationConsoleHandler, FALSE); #else sigaction(SIGINT, &savedSigIntAction, nullptr); #endif } } int cmInstrumentationInterrupt::PendingInterruptSignal() { #ifdef _WIN32 return interruptSignal.load(); #else return static_cast(interruptSignal); #endif } cmInstrumentationInterrupt::InterruptOutcome cmInstrumentationInterrupt::HandleInterrupt( bool active, std::function const& callback) { // Only trap interrupts when instrumentation is active, so non-instrumented // flows keep their default signal behavior. if (!active) { return { callback(), false, 0, true }; } InstallInterruptHandler(); interruptInjected = false; // Test-only: allow the suite to inject an interrupt deterministically. InjectTestInterrupt(); int ret = callback(); int sig = PendingInterruptSignal(); RestoreInterruptHandler(); // A real OS interrupt should be re-raised so the exit status reflects it; an // injected (test) interrupt should not, so the process exits cleanly. return { ret, sig != 0, sig, !interruptInjected }; } void cmInstrumentationInterrupt::RaiseInterrupt(int sig) { #ifdef _WIN32 // On Windows the process exits normally after flushing; the caller // propagates the (failed) build result. static_cast(sig); #else // Restore the default disposition (SA_RESETHAND already did so for the first // delivery) and re-raise so the exit status reflects the interrupt. signal(sig, SIG_DFL); raise(sig); #endif }