Allow specifying output streams.
authorIain Patterson <me@iain.cx>
Thu, 25 Oct 2012 04:53:00 +0000 (21:53 -0700)
committerIain Patterson <me@iain.cx>
Sat, 29 Jun 2013 07:28:20 +0000 (08:28 +0100)
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.

ChangeLog.txt
README.txt
messages.mc
nssm.h
nssm.vcproj
registry.cpp
registry.h
service.cpp

index 77ba306..36a7ab3 100644 (file)
@@ -1,5 +1,8 @@
 Changes since 2.16
 -----------------
 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.
   * Allow building on Visual Studio Express.
 
   * Silently ignore INTERROGATE control.
index ddc6092..b772c36 100644 (file)
@@ -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\r
 they can clean up and shut down gracefully on receipt of the event.\r
 \r
 simulating a Control-C keypress.  If they have installed a handler routine\r
 they can clean up and shut down gracefully on receipt of the event.\r
 \r
+Since version 2.17, NSSM can redirect the managed application's I/O streams\r
+to an arbitrary path.\r
+\r
+\r
 Usage\r
 -----\r
 In the usage notes below, arguments to the program may be written in angle \r
 Usage\r
 -----\r
 In the usage notes below, arguments to the program may be written in angle \r
@@ -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.\r
 \r
 \r
 If only the default action is set to Suicide NSSM will instead exit gracefully.\r
 \r
 \r
+I/O redirection\r
+---------------\r
+NSSM can redirect the managed application's I/O to any path capable of being\r
+opened by CreateFile().  This enables, for example, capturing the log output\r
+of an application which would otherwise only write to the console or accepting\r
+input from a serial port.\r
+\r
+NSSM will look in the registry under\r
+HKLM\SYSTEM\CurrentControlSet\Services\<service>\Parameters for the keys\r
+corresponding to arguments to CreateFile().  All are optional.  If no path is\r
+given for a particular stream it will not be redirected.  If a path is given\r
+but any of the other values are omitted they will be receive sensible defaults.\r
+\r
+  AppStdin: Path to receive input.\r
+  AppStdout: Path to receive output.\r
+  AppStderr: Path to receive error output.\r
+\r
+Parameters for CreateFile() are providing with the "AppStdinShareMode",\r
+"AppStdinCreationDisposition" and "AppStdinFlagsAndAttributes" values (and\r
+analogously for stdout and stderr).\r
+\r
+In general, if you want the service to log its output, set AppStdout and\r
+AppStderr to the same path, eg C:\Users\Public\service.log, and it should\r
+work.  Remember, however, that the path must be accessible to the user\r
+running the service.\r
+\r
+\r
 Removing services using the GUI\r
 -------------------------------\r
 NSSM can also remove services.  Run\r
 Removing services using the GUI\r
 -------------------------------\r
 NSSM can also remove services.  Run\r
index e8dabe0..21eb640 100644 (file)
@@ -1192,3 +1192,49 @@ Error detaching from console for service %1.
 FreeConsole() fallita:
 %2
 .
 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 (file)
--- a/nssm.h
+++ b/nssm.h
@@ -10,6 +10,7 @@
 #include "messages.h"\r
 #include "process.h"\r
 #include "registry.h"\r
 #include "messages.h"\r
 #include "process.h"\r
 #include "registry.h"\r
+#include "io.h"\r
 #include "service.h"\r
 #include "gui.h"\r
 \r
 #include "service.h"\r
 #include "gui.h"\r
 \r
index f9afffd..2b16220 100755 (executable)
                                </FileConfiguration>\r
                        </File>\r
                        <File\r
                                </FileConfiguration>\r
                        </File>\r
                        <File\r
+                               RelativePath=".\io.cpp"\r
+                               >\r
+                       </File>\r
+                       <File\r
                                RelativePath="nssm.cpp"\r
                                >\r
                                <FileConfiguration\r
                                RelativePath="nssm.cpp"\r
                                >\r
                                <FileConfiguration\r
                                >\r
                        </File>\r
                        <File\r
                                >\r
                        </File>\r
                        <File\r
+                               RelativePath=".\io.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
                                RelativePath="nssm.h"\r
                                >\r
                        </File>\r
                                RelativePath="nssm.h"\r
                                >\r
                        </File>\r
index d6cde23..eacde54 100644 (file)
@@ -219,7 +219,7 @@ int get_number(HKEY key, char *value, unsigned long *number) {
   return get_number(key, value, number, true);\r
 }\r
 \r
   return get_number(key, value, number, true);\r
 }\r
 \r
-int get_parameters(char *service_name, char *exe, int exelen, char *flags, int flagslen, char *dir, int dirlen, char **env, unsigned long *throttle_delay) {\r
+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) {\r
   unsigned long ret;\r
 \r
   /* Get registry */\r
   unsigned long ret;\r
 \r
   /* Get registry */\r
@@ -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 */\r
   set_environment(service_name, key, env);\r
 \r
   /* Try to get environment variables - may fail */\r
   set_environment(service_name, key, env);\r
 \r
+  /* Try to get stdout and stderr */\r
+  if (get_output_handles(key, si)) {\r
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service_name, 0);\r
+    RegCloseKey(key);\r
+    return 5;\r
+  }\r
+\r
   /* Try to get throttle restart delay */\r
   unsigned long type = REG_DWORD;\r
   unsigned long buflen = sizeof(*throttle_delay);\r
   /* Try to get throttle restart delay */\r
   unsigned long type = REG_DWORD;\r
   unsigned long buflen = sizeof(*throttle_delay);\r
index 6344313..811ac75 100644 (file)
@@ -8,6 +8,13 @@
 #define NSSM_REG_ENV "AppEnvironment"\r
 #define NSSM_REG_EXIT "AppExit"\r
 #define NSSM_REG_THROTTLE "AppThrottle"\r
 #define NSSM_REG_ENV "AppEnvironment"\r
 #define NSSM_REG_EXIT "AppExit"\r
 #define NSSM_REG_THROTTLE "AppThrottle"\r
+#define NSSM_REG_STDIN "AppStdin"\r
+#define NSSM_REG_STDOUT "AppStdout"\r
+#define NSSM_REG_STDERR "AppStderr"\r
+#define NSSM_REG_STDIO_SHARING "ShareMode"\r
+#define NSSM_REG_STDIO_DISPOSITION "CreationDisposition"\r
+#define NSSM_REG_STDIO_FLAGS "FlagsAndAttributes"\r
+#define NSSM_STDIO_LENGTH 29\r
 \r
 int create_messages();\r
 int create_parameters(char *, char *, char *, char *);\r
 \r
 int create_messages();\r
 int create_parameters(char *, char *, char *, char *);\r
@@ -17,7 +24,7 @@ int expand_parameter(HKEY, char *, char *, unsigned long, bool, bool);
 int expand_parameter(HKEY, char *, char *, unsigned long, bool);\r
 int get_number(HKEY, char *, unsigned long *, bool);\r
 int get_number(HKEY, char *, unsigned long *);\r
 int expand_parameter(HKEY, char *, char *, unsigned long, bool);\r
 int get_number(HKEY, char *, unsigned long *, bool);\r
 int get_number(HKEY, char *, unsigned long *);\r
-int get_parameters(char *, char *, int, char *, int, char *, int, char **, unsigned long *);\r
+int get_parameters(char *, char *, int, char *, int, char *, int, char **, unsigned long *, STARTUPINFO *);\r
 int get_exit_action(char *, unsigned long *, unsigned char *, bool *);\r
 \r
 #endif\r
 int get_exit_action(char *, unsigned long *, unsigned char *, bool *);\r
 \r
 #endif\r
index d4d023b..21726df 100644 (file)
@@ -372,7 +372,7 @@ int start_service() {
 \r
   /* Get startup parameters */\r
   char *env = 0;\r
 \r
   /* Get startup parameters */\r
   char *env = 0;\r
-  int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay);\r
+  int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay, &si);\r
   if (ret) {\r
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0);\r
     return stop_service(2, true, true);\r
   if (ret) {\r
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0);\r
     return stop_service(2, true, true);\r
@@ -382,15 +382,18 @@ int start_service() {
   char cmd[CMD_LENGTH];\r
   if (_snprintf(cmd, sizeof(cmd), "\"%s\" %s", exe, flags) < 0) {\r
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);\r
   char cmd[CMD_LENGTH];\r
   if (_snprintf(cmd, sizeof(cmd), "\"%s\" %s", exe, flags) < 0) {\r
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);\r
+    close_output_handles(&si);\r
     return stop_service(2, true, true);\r
   }\r
 \r
   throttle_restart();\r
 \r
     return stop_service(2, true, true);\r
   }\r
 \r
   throttle_restart();\r
 \r
-  if (! CreateProcess(0, cmd, 0, 0, false, 0, env, dir, &si, &pi)) {\r
+  bool inherit_handles = (si.dwFlags & STARTF_USESTDHANDLES);\r
+  if (! CreateProcess(0, cmd, 0, 0, inherit_handles, 0, env, dir, &si, &pi)) {\r
     unsigned long error = GetLastError();\r
     if (error == ERROR_INVALID_PARAMETER && env) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service_name, exe, NSSM_REG_ENV, 0);\r
     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, error_string(error), 0);\r
     unsigned long error = GetLastError();\r
     if (error == ERROR_INVALID_PARAMETER && env) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service_name, exe, NSSM_REG_ENV, 0);\r
     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, error_string(error), 0);\r
+    close_output_handles(&si);\r
     return stop_service(3, true, true);\r
   }\r
   process_handle = pi.hProcess;\r
     return stop_service(3, true, true);\r
   }\r
   process_handle = pi.hProcess;\r
@@ -398,6 +401,8 @@ int start_service() {
 \r
   if (get_process_creation_time(process_handle, &creation_time)) ZeroMemory(&creation_time, sizeof(creation_time));\r
 \r
 \r
   if (get_process_creation_time(process_handle, &creation_time)) ZeroMemory(&creation_time, sizeof(creation_time));\r
 \r
+  close_output_handles(&si);\r
+\r
   /* Signal successful start */\r
   service_status.dwCurrentState = SERVICE_RUNNING;\r
   SetServiceStatus(service_handle, &service_status);\r
   /* Signal successful start */\r
   service_status.dwCurrentState = SERVICE_RUNNING;\r
   SetServiceStatus(service_handle, &service_status);\r