From: Iain Patterson Date: Thu, 25 Oct 2012 04:53:00 +0000 (-0700) Subject: Allow specifying output streams. X-Git-Tag: v2.17~13 X-Git-Url: http://git.iain.cx/?p=nssm.git;a=commitdiff_plain;h=40792fac2ef98e69c331b9cd5a9279dc3e1eb730 Allow specifying output streams. Allow starting the monitor application with one or more of stdin, stdout and stderr redirected to a file or anything which can be opened with CreateFile(). New registry values corresponding to CreateFile() arguments for stdin (and analogously for stdout and stderr): AppStdin: Path to open. AppStdinShareMode: Sharing mode. AppStdinCreationDisposition: Creation disposition. AppStdinFlagsAndAtrributes: Flags and attributes. All are optional. If no path is given for a particular stream it will not be redirected. If a path is given but any of the other values are omitted they will receive sensible defaults. --- diff --git a/ChangeLog.txt b/ChangeLog.txt index 77ba306..36a7ab3 100644 --- a/ChangeLog.txt +++ b/ChangeLog.txt @@ -1,5 +1,8 @@ Changes since 2.16 ----------------- + * NSSM can now redirect the service's I/O streams to any path + capable of being opened by CreateFile(). + * Allow building on Visual Studio Express. * Silently ignore INTERROGATE control. diff --git a/README.txt b/README.txt index ddc6092..b772c36 100644 --- a/README.txt +++ b/README.txt @@ -40,6 +40,10 @@ Since version 2.17, NSSM can try to shut down console applications by simulating a Control-C keypress. If they have installed a handler routine they can clean up and shut down gracefully on receipt of the event. +Since version 2.17, NSSM can redirect the managed application's I/O streams +to an arbitrary path. + + Usage ----- In the usage notes below, arguments to the program may be written in angle @@ -133,6 +137,33 @@ request to suicide if you explicitly configure a registry key for exit code 0. If only the default action is set to Suicide NSSM will instead exit gracefully. +I/O redirection +--------------- +NSSM can redirect the managed application's I/O to any path capable of being +opened by CreateFile(). This enables, for example, capturing the log output +of an application which would otherwise only write to the console or accepting +input from a serial port. + +NSSM will look in the registry under +HKLM\SYSTEM\CurrentControlSet\Services\\Parameters for the keys +corresponding to arguments to CreateFile(). All are optional. If no path is +given for a particular stream it will not be redirected. If a path is given +but any of the other values are omitted they will be receive sensible defaults. + + AppStdin: Path to receive input. + AppStdout: Path to receive output. + AppStderr: Path to receive error output. + +Parameters for CreateFile() are providing with the "AppStdinShareMode", +"AppStdinCreationDisposition" and "AppStdinFlagsAndAttributes" values (and +analogously for stdout and stderr). + +In general, if you want the service to log its output, set AppStdout and +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. + + Removing services using the GUI ------------------------------- NSSM can also remove services. Run diff --git a/messages.mc b/messages.mc index e8dabe0..21eb640 100644 --- a/messages.mc +++ b/messages.mc @@ -1192,3 +1192,49 @@ Error detaching from console for service %1. FreeConsole() fallita: %2 . + +MessageId = +1 +SymbolicName = NSSM_EVENT_CREATEFILE_FAILED +Severity = Error +Language = English +CreateFile() failed to open %1: +%2 +. +Language = French +CreateFile() a échoué %1: +%2 +. +Language = Italian +Chiamata a CreateFile() fallita %1: +%2 +. + +MessageId = +1 +SymbolicName = NSSM_EVENT_DUPLICATEHANDLE_FAILED +Severity = Error +Language = English +Error duplicating the filehandle previously opened for %1 as %2. +DuplicateHandle() failed: +%3 +. +Language = French +DuplicateHandle() a échoué (%1 -> %2): +%3 +. +Language = Italian +Chiamata a DuplicateHandle() fallita (%1 -> %2): +%3 +. + +MessageId = +1 +SymbolicName = NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED +Severity = Error +Language = English +Error setting up one or more I/O filehandles. Service %1 will not be started. +. +Language = French +Error setting up one or more I/O filehandles. Service %1 will not be started. +. +Language = Italian +Error setting up one or more I/O filehandles. Service %1 will not be started. +. diff --git a/nssm.h b/nssm.h index 01885eb..4a32859 100644 --- a/nssm.h +++ b/nssm.h @@ -10,6 +10,7 @@ #include "messages.h" #include "process.h" #include "registry.h" +#include "io.h" #include "service.h" #include "gui.h" diff --git a/nssm.vcproj b/nssm.vcproj index f9afffd..2b16220 100755 --- a/nssm.vcproj +++ b/nssm.vcproj @@ -466,6 +466,10 @@ /> + + @@ -623,6 +627,10 @@ RelativePath="gui.h" > + + diff --git a/registry.cpp b/registry.cpp index d6cde23..eacde54 100644 --- a/registry.cpp +++ b/registry.cpp @@ -219,7 +219,7 @@ int get_number(HKEY key, char *value, unsigned long *number) { return get_number(key, value, number, true); } -int get_parameters(char *service_name, char *exe, int exelen, char *flags, int flagslen, char *dir, int dirlen, char **env, unsigned long *throttle_delay) { +int get_parameters(char *service_name, char *exe, int exelen, char *flags, int flagslen, char *dir, int dirlen, char **env, unsigned long *throttle_delay, STARTUPINFO *si) { unsigned long ret; /* Get registry */ @@ -272,6 +272,13 @@ int get_parameters(char *service_name, char *exe, int exelen, char *flags, int f /* Try to get environment variables - may fail */ set_environment(service_name, key, env); + /* Try to get stdout and stderr */ + if (get_output_handles(key, si)) { + log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service_name, 0); + RegCloseKey(key); + return 5; + } + /* Try to get throttle restart delay */ unsigned long type = REG_DWORD; unsigned long buflen = sizeof(*throttle_delay); diff --git a/registry.h b/registry.h index 6344313..811ac75 100644 --- a/registry.h +++ b/registry.h @@ -8,6 +8,13 @@ #define NSSM_REG_ENV "AppEnvironment" #define NSSM_REG_EXIT "AppExit" #define NSSM_REG_THROTTLE "AppThrottle" +#define NSSM_REG_STDIN "AppStdin" +#define NSSM_REG_STDOUT "AppStdout" +#define NSSM_REG_STDERR "AppStderr" +#define NSSM_REG_STDIO_SHARING "ShareMode" +#define NSSM_REG_STDIO_DISPOSITION "CreationDisposition" +#define NSSM_REG_STDIO_FLAGS "FlagsAndAttributes" +#define NSSM_STDIO_LENGTH 29 int create_messages(); int create_parameters(char *, char *, char *, char *); @@ -17,7 +24,7 @@ int expand_parameter(HKEY, char *, char *, unsigned long, bool, bool); int expand_parameter(HKEY, char *, char *, unsigned long, bool); int get_number(HKEY, char *, unsigned long *, bool); int get_number(HKEY, char *, unsigned long *); -int get_parameters(char *, char *, int, char *, int, char *, int, char **, unsigned long *); +int get_parameters(char *, char *, int, char *, int, char *, int, char **, unsigned long *, STARTUPINFO *); int get_exit_action(char *, unsigned long *, unsigned char *, bool *); #endif diff --git a/service.cpp b/service.cpp index d4d023b..21726df 100644 --- a/service.cpp +++ b/service.cpp @@ -372,7 +372,7 @@ int start_service() { /* Get startup parameters */ char *env = 0; - int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay); + int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay, &si); if (ret) { log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0); return stop_service(2, true, true); @@ -382,15 +382,18 @@ int start_service() { char cmd[CMD_LENGTH]; if (_snprintf(cmd, sizeof(cmd), "\"%s\" %s", exe, flags) < 0) { log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0); + close_output_handles(&si); return stop_service(2, true, true); } throttle_restart(); - if (! CreateProcess(0, cmd, 0, 0, false, 0, env, dir, &si, &pi)) { + bool inherit_handles = (si.dwFlags & STARTF_USESTDHANDLES); + if (! CreateProcess(0, cmd, 0, 0, inherit_handles, 0, env, dir, &si, &pi)) { unsigned long error = GetLastError(); if (error == ERROR_INVALID_PARAMETER && env) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service_name, exe, NSSM_REG_ENV, 0); else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, error_string(error), 0); + close_output_handles(&si); return stop_service(3, true, true); } process_handle = pi.hProcess; @@ -398,6 +401,8 @@ int start_service() { if (get_process_creation_time(process_handle, &creation_time)) ZeroMemory(&creation_time, sizeof(creation_time)); + close_output_handles(&si); + /* Signal successful start */ service_status.dwCurrentState = SERVICE_RUNNING; SetServiceStatus(service_handle, &service_status);