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
 -----------------
+  * 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.
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
+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
@@ -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
+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
index e8dabe0..21eb640 100644 (file)
@@ -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 (file)
--- a/nssm.h
+++ b/nssm.h
@@ -10,6 +10,7 @@
 #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
index f9afffd..2b16220 100755 (executable)
                                        />\r
                                </FileConfiguration>\r
                        </File>\r
+                       <File\r
+                               RelativePath=".\io.cpp"\r
+                               >\r
+                       </File>\r
                        <File\r
                                RelativePath="nssm.cpp"\r
                                >\r
                                RelativePath="gui.h"\r
                                >\r
                        </File>\r
+                       <File\r
+                               RelativePath=".\io.h"\r
+                               >\r
+                       </File>\r
                        <File\r
                                RelativePath="nssm.h"\r
                                >\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
-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
@@ -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 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
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_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
@@ -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 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
index d4d023b..21726df 100644 (file)
@@ -372,7 +372,7 @@ int start_service() {
 \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
@@ -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
+    close_output_handles(&si);\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
+    close_output_handles(&si);\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
+  close_output_handles(&si);\r
+\r
   /* Signal successful start */\r
   service_status.dwCurrentState = SERVICE_RUNNING;\r
   SetServiceStatus(service_handle, &service_status);\r