Browse Source

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.
Iain Patterson 9 years ago
parent
commit
fa2f3fe4a8
6 changed files with 66 additions and 15 deletions
  1. 9 0
      README.txt
  2. 44 15
      io.cpp
  3. BIN
      messages.mc
  4. 7 0
      process.cpp
  5. 5 0
      service.cpp
  6. 1 0
      service.h

+ 9 - 0
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
 -------

+ 44 - 15
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;
 
   /*

BIN
messages.mc


+ 7 - 0
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;

+ 5 - 0
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;
 

+ 1 - 0
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;