From fa2f3fe4a81e6958717ae05f6d37af2da91bcd66 Mon Sep 17 00:00:00 2001 From: Iain Patterson Date: Sun, 19 Jan 2014 18:01:36 +0000 Subject: [PATCH] Fake stdin for applications which exit on EOF. Some applications, like MonetDB server, expect to read commands on stdin. If they read a keyword, usually "exit" or "quit" or if stdin is closed, they will exit. Such applications will typically work when run in a service context via NSSM, as Windows will allocate a console window for them. However, when the service is configured to redirect stdout and/or stderr but not stdin, the application will exit immediately because it will see no input and think that the user closed stdin. To deal with this situation we interpret an AppStdin value of "|" (a single pipe character) as requesting a hack mode whereby we open a pipe and pass the reading end to the application as the stdin handle. We never actually write anything to the writing end of pipe but simply keep it around until the application exits or the service receives a stop control. Closing the pipe on receipt of a stop request may even be sufficient to close the application gracefully without resorting to any of the other stop methods. As an aside, MonetDB server can be run in batch mode, wherein it does not attempt to read from stdin at all. The hack is not necessary for it or other applications with similar functionality. Thanks Bryan Senseman. --- README.txt | 9 +++++++++ io.cpp | 59 ++++++++++++++++++++++++++++++++++++++++++++--------------- messages.mc | Bin 144172 -> 144888 bytes process.cpp | 7 +++++++ service.cpp | 5 +++++ service.h | 1 + 6 files changed, 66 insertions(+), 15 deletions(-) diff --git a/README.txt b/README.txt index b05fd78..ded8b7a 100644 --- a/README.txt +++ b/README.txt @@ -288,6 +288,13 @@ AppStderr to the same path, eg C:\Users\Public\service.log, and it should work. Remember, however, that the path must be accessible to the user running the service. +Note that if you set AppStdout and/or AppStderr, applications which attempt +to read stdin will fail due to a combination of factors including the way I/O +redirection is configured on Windows and how a console application starts in +a service context. NSSM can fake a stdin stream so that applications can +still work when they would otherwise exit when at end of file on stdin. Set +AppStdin to "|" (a single pipe character) to invoke the fake stdin. + File rotation ------------- @@ -624,6 +631,8 @@ Thanks to Арслан Сайдуганов for suggesting setting process prior Thanks to Robert Middleton for suggestion and draft implementation of process affinity support. Thanks to Andrew RedzMax for suggesting an unconditional restart delay. +Thanks to Bryan Senseman for noticing that applications with redirected stdout +and/or stderr which attempt to read from stdin would fail. Licence ------- diff --git a/io.cpp b/io.cpp index d18e31c..a4f6abb 100644 --- a/io.cpp +++ b/io.cpp @@ -250,21 +250,6 @@ int get_output_handles(nssm_service_t *service, HKEY key, STARTUPINFO *si) { ZeroMemory(&attributes, sizeof(attributes)); attributes.bInheritHandle = true; - /* stdin */ - if (get_createfile_parameters(key, NSSM_REG_STDIN, service->stdin_path, &service->stdin_sharing, NSSM_STDIN_SHARING, &service->stdin_disposition, NSSM_STDIN_DISPOSITION, &service->stdin_flags, NSSM_STDIN_FLAGS)) { - service->stdin_sharing = service->stdin_disposition = service->stdin_flags = 0; - ZeroMemory(service->stdin_path, _countof(service->stdin_path) * sizeof(TCHAR)); - return 1; - } - if (si && service->stdin_path[0]) { - si->hStdInput = CreateFile(service->stdin_path, FILE_READ_DATA, service->stdin_sharing, &attributes, service->stdin_disposition, service->stdin_flags, 0); - if (! si->hStdInput) { - log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, service->stdin_path, error_string(GetLastError()), 0); - return 2; - } - set_flags = true; - } - /* stdout */ if (get_createfile_parameters(key, NSSM_REG_STDOUT, service->stdout_path, &service->stdout_sharing, NSSM_STDOUT_SHARING, &service->stdout_disposition, NSSM_STDOUT_DISPOSITION, &service->stdout_flags, NSSM_STDOUT_FLAGS)) { service->stdout_sharing = service->stdout_disposition = service->stdout_flags = 0; @@ -346,6 +331,50 @@ int get_output_handles(nssm_service_t *service, HKEY key, STARTUPINFO *si) { } } + /* stdin */ + if (get_createfile_parameters(key, NSSM_REG_STDIN, service->stdin_path, &service->stdin_sharing, NSSM_STDIN_SHARING, &service->stdin_disposition, NSSM_STDIN_DISPOSITION, &service->stdin_flags, NSSM_STDIN_FLAGS)) { + service->stdin_sharing = service->stdin_disposition = service->stdin_flags = 0; + ZeroMemory(service->stdin_path, _countof(service->stdin_path) * sizeof(TCHAR)); + return 1; + } + if (si && service->stdin_path[0]) { + if (str_equiv(service->stdin_path, _T("|"))) { + /* Fake stdin with a pipe. */ + if (set_flags) { + /* + None of this is necessary if we aren't redirecting stdout and/or + stderr as well. + + If we don't redirect any handles the application will start and be + quite happy with its console. If we start it with + STARTF_USESTDHANDLES set it will interpret a NULL value for + hStdInput as meaning no input. Because the service starts with + no stdin we can't just pass GetStdHandle(STD_INPUT_HANDLE) to + the application. + + The only way we can successfully redirect the application's output + while preventing programs which exit after reading all input from + exiting prematurely is to create a pipe between ourselves and the + application but write nothing to it. + */ + if (! CreatePipe(&si->hStdInput, &service->stdin_pipe, 0, 0)) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_STDIN_CREATEPIPE_FAILED, service->name, error_string(GetLastError()), 0); + return 2; + } + SetHandleInformation(si->hStdInput, HANDLE_FLAG_INHERIT, HANDLE_FLAG_INHERIT); + } + } + else { + si->hStdInput = CreateFile(service->stdin_path, FILE_READ_DATA, service->stdin_sharing, &attributes, service->stdin_disposition, service->stdin_flags, 0); + if (! si->hStdInput) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEFILE_FAILED, service->stdin_path, error_string(GetLastError()), 0); + return 2; + } + + set_flags = true; + } + } + if (! set_flags) return 0; /* diff --git a/messages.mc b/messages.mc index 49ade07f1b327e7f0eef5ac39265c7a97314b7a9..79234c47e934eeb63e3febd13d74bc2da3bb3bfa 100644 GIT binary patch delta 157 zcmZ4UjN`{^j)oS-ElfJb)6YpUhHwTmgfO@;cry4+Z diff --git a/process.cpp b/process.cpp index 19b893a..75956de 100644 --- a/process.cpp +++ b/process.cpp @@ -149,6 +149,13 @@ int kill_process(nssm_service_t *service, HANDLE process_handle, unsigned long p kill_t k = { pid, exitcode, 0 }; + /* Close the stdin pipe. */ + if (service->stdin_pipe) { + CloseHandle(service->stdin_pipe); + service->stdin_pipe = 0; + if (! await_shutdown(service, _T(__FUNCTION__), service->kill_console_delay)) return 1; + } + /* Try to send a Control-C event to the console. */ if (service->stop_method & NSSM_STOP_METHOD_CONSOLE) { if (! kill_console(service)) return 1; diff --git a/service.cpp b/service.cpp index 9cdf9de..c920af8 100644 --- a/service.cpp +++ b/service.cpp @@ -1522,6 +1522,7 @@ int start_service(nssm_service_t *service) { bool inherit_handles = false; if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true; unsigned long flags = service->priority & priority_mask(); + if (service->stdin_pipe) flags |= DETACHED_PROCESS; if (service->affinity) flags |= CREATE_SUSPENDED; #ifdef UNICODE flags |= CREATE_UNICODE_ENVIRONMENT; @@ -1614,6 +1615,10 @@ int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, UnregisterWait(service->wait_handle); service->wait_handle = 0; } + if (service->stdin_pipe) { + CloseHandle(service->stdin_pipe); + service->stdin_pipe = 0; + } service->rotate_stdout_online = service->rotate_stderr_online = NSSM_ROTATE_OFFLINE; diff --git a/service.h b/service.h index 6da55f3..2eb2b42 100644 --- a/service.h +++ b/service.h @@ -57,6 +57,7 @@ typedef struct { unsigned long stdin_sharing; unsigned long stdin_disposition; unsigned long stdin_flags; + HANDLE stdin_pipe; TCHAR stdout_path[PATH_LENGTH]; unsigned long stdout_sharing; unsigned long stdout_disposition; -- 2.7.4