From b993e630395855e66c4fab207631555e1869d5d9 Mon Sep 17 00:00:00 2001 From: Jim Klimov Date: Mon, 9 Feb 2026 16:39:47 +0100 Subject: [PATCH] scripts/Windows/wininit.c, NEWS.adoc, docs/man/nut.exe.txt: support exiting from "nut.exe -N" with Ctrl+C [#3312] Signed-off-by: Jim Klimov --- NEWS.adoc | 5 +++ docs/man/nut.exe.txt | 6 ++++ scripts/Windows/wininit.c | 73 +++++++++++++++++++++++++++++++++++++-- 3 files changed, 82 insertions(+), 2 deletions(-) diff --git a/NEWS.adoc b/NEWS.adoc index b618b57d3c..07251d523a 100644 --- a/NEWS.adoc +++ b/NEWS.adoc @@ -114,6 +114,11 @@ https://github.com/networkupstools/nut/milestone/12 operations. (Re-)registration of the "Network UPS Tools" service should now populate a nice description of it. The `-U` (uninstall) action should now also try to stop the service first. [PR #3235] + * Using `nut.exe -N` for testing and pressing 'Ctrl+C' should now cause the + started daemons to be killed off. Previously they would linger and e.g. + preclude subsequent experiments with the service wrapper. Console close + events are ignored, so there is a way to indefinitely keep the daemons + started by test-mode wrapper running (kill via Task Manager). [#3312] * Revised WIN32 `WSAStartup()` and registration of `atexit(WSACleanup)` to only be done once per program (and cleanups to be always registered); this impacts the C `libupsclient` and C++ `libnutclient` libraries (and so most diff --git a/docs/man/nut.exe.txt b/docs/man/nut.exe.txt index ed4e233bc3..a9717a6976 100644 --- a/docs/man/nut.exe.txt +++ b/docs/man/nut.exe.txt @@ -99,6 +99,12 @@ Install as a Windows service called "Network UPS Tools". Does not start it. *-N*:: Run once in non-service mode (for troubleshooting). ++ +Since NUT v2.8.5, using `nut.exe -N` for testing and pressing 'Ctrl+C' should +cause the started daemons to be killed off. Previously they would linger and +e.g. preclude subsequent experiments with the service wrapper. Console close +events are ignored, so there is a way to indefinitely keep the daemons started +by the test-mode wrapper running (kill later if needed via Task Manager). *start*:: Install as a Windows service called "Network UPS Tools" (if not yet done), diff --git a/scripts/Windows/wininit.c b/scripts/Windows/wininit.c index 864c7ce05f..a1fc5577a1 100644 --- a/scripts/Windows/wininit.c +++ b/scripts/Windows/wininit.c @@ -38,6 +38,7 @@ typedef struct conn_s { struct conn_s *next; } conn_t; +static int exit_flag = 0; static DWORD upsd_pid = 0; static DWORD upsmon_pid = 0; static DWORD upsdrvctl_pid = 0; @@ -581,6 +582,54 @@ static void ReportSvcStatus( SetServiceStatus(SvcStatusHandle, &SvcStatus); } +/* For `nut.exe -N` runs + * https://learn.microsoft.com/en-us/windows/console/registering-a-control-handler-function + */ +static BOOL WINAPI ConsoleCtrlHandler(DWORD Ctrl) +{ + switch(Ctrl) + { + case CTRL_C_EVENT: + print_event(LOG_INFO, "Ctrl+C received on console"); + exit_flag = 1; /* Break SvcMain() infinite loop */ + if (upsmon_pid) { + print_event(LOG_INFO, "Stopping upsmon"); + stop_upsmon(); + } + if (upsd_pid) { + print_event(LOG_INFO, "Stopping upsd"); + stop_upsd(); + } + if (upsdrvctl_pid) { + print_event(LOG_INFO, "Stopping drivers"); + stop_drivers(); + } + exit_flag = 2; /* Allow to finish SvcMain() infinite loop and proceed to exit() */ + /* confirm that the user wants to exit */ + return TRUE; + + case CTRL_CLOSE_EVENT: + print_event(LOG_INFO, "Close event received on console, currently ignored"); + return FALSE; + + case CTRL_BREAK_EVENT: + print_event(LOG_INFO, "Ctrl+Break event received on console, currently ignored"); + return FALSE; + + /* TOTHINK: Do we in fact want these two handled specially? */ + case CTRL_LOGOFF_EVENT: + print_event(LOG_INFO, "Logoff event received on console, currently ignored"); + return FALSE; + + case CTRL_SHUTDOWN_EVENT: + print_event(LOG_INFO, "Shutdown event received on console, currently ignored"); + return FALSE; + + default: + return FALSE; + } +} + static void WINAPI SvcCtrlHandler(DWORD Ctrl) { switch(Ctrl) @@ -659,7 +708,7 @@ static void close_all(void) static void WINAPI SvcMain(DWORD argc, LPTSTR *argv) { DWORD ret; - HANDLE handles[MAXIMUM_WAIT_OBJECTS]; /* 64 per current WinAPI sheaders */ + HANDLE handles[MAXIMUM_WAIT_OBJECTS]; /* 64 per current WinAPI headers */ size_t maxhandle = 0; pipe_conn_t *conn; DWORD priority; @@ -697,7 +746,7 @@ static void WINAPI SvcMain(DWORD argc, LPTSTR *argv) SvcReady(); } - while (1) { + while (!exit_flag) { maxhandle = 0; memset(&handles, 0, sizeof(handles)); @@ -766,6 +815,10 @@ static void WINAPI SvcMain(DWORD argc, LPTSTR *argv) } } + /* Do not check on daemons nor revive them in vain if we are exiting */ + if (exit_flag) + break; + /* Check on each daemon: Was it supposed to run? Does it still? */ if (upsdrvctl_pid) { DWORD status = 0; @@ -785,6 +838,10 @@ static void WINAPI SvcMain(DWORD argc, LPTSTR *argv) } } + /* Do not check on daemons nor revive them in vain if we are exiting */ + if (exit_flag) + break; + if (upsd_pid) { DWORD status = 0; BOOL res = FALSE; @@ -803,6 +860,10 @@ static void WINAPI SvcMain(DWORD argc, LPTSTR *argv) } } + /* Do not check on daemons nor revive them in vain if we are exiting */ + if (exit_flag) + break; + if (upsmon_pid) { DWORD status = 0; BOOL res = FALSE; @@ -821,6 +882,10 @@ static void WINAPI SvcMain(DWORD argc, LPTSTR *argv) } } } + + while (exit_flag < 2) { + Sleep(1000); + } } static void help(const char *arg_progname) @@ -1020,6 +1085,10 @@ int main(int argc, char **argv) } } else { + if (!SetConsoleCtrlHandler(ConsoleCtrlHandler, TRUE)) { + upslog_with_errno(LOG_ERR, "SetConsoleCtrlHandler failed, may ignore Ctrl+C"); + } + SvcMain(argc, argv); }