Browse Source

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.
Iain Patterson 11 years ago
parent
commit
40792fac2e
8 changed files with 112 additions and 4 deletions
  1. 3 0
      ChangeLog.txt
  2. 31 0
      README.txt
  3. 46 0
      messages.mc
  4. 1 0
      nssm.h
  5. 8 0
      nssm.vcproj
  6. 8 1
      registry.cpp
  7. 8 1
      registry.h
  8. 7 2
      service.cpp

+ 3 - 0
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.

+ 31 - 0
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\<service>\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

+ 46 - 0
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.
+.

+ 1 - 0
nssm.h

@@ -10,6 +10,7 @@
 #include "messages.h"
 #include "process.h"
 #include "registry.h"
+#include "io.h"
 #include "service.h"
 #include "gui.h"
 

+ 8 - 0
nssm.vcproj

@@ -466,6 +466,10 @@
 					/>
 				</FileConfiguration>
 			</File>
+			<File
+				RelativePath=".\io.cpp"
+				>
+			</File>
 			<File
 				RelativePath="nssm.cpp"
 				>
@@ -623,6 +627,10 @@
 				RelativePath="gui.h"
 				>
 			</File>
+			<File
+				RelativePath=".\io.h"
+				>
+			</File>
 			<File
 				RelativePath="nssm.h"
 				>

+ 8 - 1
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);

+ 8 - 1
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

+ 7 - 2
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);