Kill process tree when stopping service.
authorIain Patterson <me@iain.cx>
Tue, 25 Jan 2011 10:21:20 +0000 (10:21 +0000)
committerIain Patterson <me@iain.cx>
Tue, 25 Jan 2011 10:21:20 +0000 (10:21 +0000)
Ensure that all child processes of the monitored application are
killed when the service stops by recursing through all running
processes and terminating those whose parent is the application
or one of its descendents.

nssm.h
nssm.vcproj
process.cpp [new file with mode: 0644]
process.h [new file with mode: 0644]
service.cpp

diff --git a/nssm.h b/nssm.h
index ba114f9..292868e 100644 (file)
--- a/nssm.h
+++ b/nssm.h
@@ -7,6 +7,7 @@
 #include <windows.h>\r
 #include "event.h"\r
 #include "messages.h"\r
+#include "process.h"\r
 #include "registry.h"\r
 #include "service.h"\r
 #include "gui.h"\r
index 93492e2..4130d2e 100755 (executable)
                                </FileConfiguration>\r
                        </File>\r
                        <File\r
+                               RelativePath="process.cpp"\r
+                               >\r
+                               <FileConfiguration\r
+                                       Name="Debug|Win32"\r
+                                       >\r
+                                       <Tool\r
+                                               Name="VCCLCompilerTool"\r
+                                               PreprocessorDefinitions=""\r
+                                       />\r
+                               </FileConfiguration>\r
+                               <FileConfiguration\r
+                                       Name="Debug|x64"\r
+                                       >\r
+                                       <Tool\r
+                                               Name="VCCLCompilerTool"\r
+                                               PreprocessorDefinitions=""\r
+                                       />\r
+                               </FileConfiguration>\r
+                               <FileConfiguration\r
+                                       Name="Release|Win32"\r
+                                       >\r
+                                       <Tool\r
+                                               Name="VCCLCompilerTool"\r
+                                               PreprocessorDefinitions=""\r
+                                       />\r
+                               </FileConfiguration>\r
+                               <FileConfiguration\r
+                                       Name="Release|x64"\r
+                                       >\r
+                                       <Tool\r
+                                               Name="VCCLCompilerTool"\r
+                                               PreprocessorDefinitions=""\r
+                                       />\r
+                               </FileConfiguration>\r
+                       </File>\r
+                       <File\r
                                RelativePath="registry.cpp"\r
                                >\r
                                <FileConfiguration\r
                                >\r
                        </File>\r
                        <File\r
+                               RelativePath="process.h"\r
+                               >\r
+                       </File>\r
+                       <File\r
                                RelativePath="registry.h"\r
                                >\r
                        </File>\r
diff --git a/process.cpp b/process.cpp
new file mode 100644 (file)
index 0000000..15fc83d
--- /dev/null
@@ -0,0 +1,49 @@
+#include "nssm.h"
+
+void kill_process_tree(char *service_name, unsigned long pid, unsigned long exitcode, unsigned long ppid) {
+  log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, service_name, pid, exitcode, 0);
+
+  /* Shouldn't happen. */
+  if (! pid) return;
+
+  HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
+  if (! snapshot) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_FAILED, service_name, GetLastError(), 0);
+    return;
+  }
+
+  PROCESSENTRY32 pe;
+  ZeroMemory(&pe, sizeof(pe));
+  pe.dwSize = sizeof(pe);
+
+  if (! Process32First(snapshot, &pe)) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, service_name, GetLastError(), 0);
+    return;
+  }
+
+  if (pe.th32ParentProcessID == pid) kill_process_tree(service_name, pe.th32ProcessID, exitcode, ppid);
+
+  while (true) {
+    /* Try to get the next process. */
+    if (! Process32Next(snapshot, &pe)) {
+      unsigned long ret = GetLastError();
+      if (ret == ERROR_NO_MORE_FILES) break;
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, service_name, GetLastError(), 0);
+      return;
+    }
+
+    if (pe.th32ParentProcessID == pid) kill_process_tree(service_name, pe.th32ProcessID, exitcode, ppid);
+  }
+
+  HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid);
+  if (! process_handle) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid, service_name, GetLastError(), 0);
+    return;
+  }
+
+  log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid, ppid, service_name, 0);
+  if (! TerminateProcess(process_handle, exitcode)) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_TERMINATEPROCESS_FAILED, pid, service_name, GetLastError(), 0);
+    return;
+  }
+}
diff --git a/process.h b/process.h
new file mode 100644 (file)
index 0000000..95f87b2
--- /dev/null
+++ b/process.h
@@ -0,0 +1,8 @@
+#ifndef PROCESS_H
+#define PROCESS_H
+
+#include <tlhelp32.h>
+
+void kill_process_tree(char *, unsigned long, unsigned long, unsigned long);
+
+#endif
index 8c42972..4bcf8ab 100644 (file)
@@ -2,12 +2,14 @@
 \r
 SERVICE_STATUS service_status;\r
 SERVICE_STATUS_HANDLE service_handle;\r
+HANDLE process_handle;\r
 HANDLE wait_handle;\r
-HANDLE pid;\r
+unsigned long pid;\r
 static char service_name[SERVICE_NAME_LENGTH];\r
 char exe[EXE_LENGTH];\r
 char flags[CMD_LENGTH];\r
 char dir[MAX_PATH];\r
+bool stopping;\r
 \r
 static enum { NSSM_EXIT_RESTART, NSSM_EXIT_IGNORE, NSSM_EXIT_REALLY, NSSM_EXIT_UNCLEAN } exit_actions;\r
 static const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", "Suicide", 0 };\r
@@ -153,6 +155,7 @@ void WINAPI service_main(unsigned long argc, char **argv) {
   service_status.dwWaitHint = 1000;\r
 \r
   /* Signal we AREN'T running the server */\r
+  process_handle = 0;\r
   pid = 0;\r
 \r
   /* Register control handler */\r
@@ -213,7 +216,7 @@ int monitor_service() {
   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, exe, flags, service_name, dir, 0);\r
 \r
   /* Monitor service service */\r
-  if (! RegisterWaitForSingleObject(&wait_handle, pid, end_service, 0, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
+  if (! RegisterWaitForSingleObject(&wait_handle, process_handle, end_service, (void *) pid, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service_name, exe, GetLastError(), 0);\r
   }\r
 \r
@@ -235,7 +238,9 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon
 \r
 /* Start the service */\r
 int start_service() {\r
-  if (pid) return 0;\r
+  stopping = false;\r
+\r
+  if (process_handle) return 0;\r
 \r
   /* Allocate a STARTUPINFO structure for a new process */\r
   STARTUPINFO si;\r
@@ -252,11 +257,12 @@ int start_service() {
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);\r
     return stop_service(2, true, true);\r
   }\r
-  if (! CreateProcess(0, cmd, 0, 0, 0, 0, 0, dir, &si, &pi)) {\r
+  if (! CreateProcess(0, cmd, 0, 0, false, 0, 0, dir, &si, &pi)) {\r
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, GetLastError(), 0);\r
     return stop_service(3, true, true);\r
   }\r
-  pid = pi.hProcess;\r
+  process_handle = pi.hProcess;\r
+  pid = pi.dwProcessId;\r
 \r
   /* Signal successful start */\r
   service_status.dwCurrentState = SERVICE_RUNNING;\r
@@ -282,11 +288,13 @@ int stop_service(unsigned long exitcode, bool graceful, bool default_action) {
   if (pid) {\r
     /* Shut down server */\r
     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);\r
-    TerminateProcess(pid, 0);\r
-    pid = 0;\r
+    TerminateProcess(process_handle, 0);\r
+    process_handle = 0;\r
   }\r
   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);\r
 \r
+  end_service((void *) pid, true);\r
+\r
   /* Signal we stopped */\r
   if (graceful) {\r
     service_status.dwCurrentState = SERVICE_STOPPED;\r
@@ -306,19 +314,36 @@ int stop_service(unsigned long exitcode, bool graceful, bool default_action) {
 \r
 /* Callback function triggered when the server exits */\r
 void CALLBACK end_service(void *arg, unsigned char why) {\r
+  if (stopping) return;\r
+\r
+  stopping = true;\r
+\r
+  pid = (unsigned long) arg;\r
+\r
   /* Check exit code */\r
-  unsigned long ret = 0;\r
-  GetExitCodeProcess(pid, &ret);\r
+  unsigned long exitcode = 0;\r
+  GetExitCodeProcess(process_handle, &exitcode);\r
+\r
+  /* Clean up. */\r
+  kill_process_tree(service_name, pid, exitcode, pid);\r
+\r
+  /*\r
+    The why argument is true if our wait timed out or false otherwise.\r
+    Our wait is infinite so why will never be true when called by the system.\r
+    If it is indeed true, assume we were called from stop_service() because\r
+    this is a controlled shutdown, and don't take any restart action.\r
+  */\r
+  if (why) return;\r
 \r
   char code[16];\r
-  _snprintf(code, sizeof(code), "%d", ret);\r
+  _snprintf(code, sizeof(code), "%d", exitcode);\r
   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);\r
 \r
   /* What action should we take? */\r
   int action = NSSM_EXIT_RESTART;\r
   unsigned char action_string[ACTION_LEN];\r
   bool default_action;\r
-  if (! get_exit_action(service_name, &ret, action_string, &default_action)) {\r
+  if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {\r
     for (int i = 0; exit_action_strings[i]; i++) {\r
       if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
         action = i;\r
@@ -327,6 +352,7 @@ void CALLBACK end_service(void *arg, unsigned char why) {
     }\r
   }\r
 \r
+  process_handle = 0;\r
   pid = 0;\r
   switch (action) {\r
     /* Try to restart the service or return failure code to service manager */\r
@@ -347,13 +373,13 @@ void CALLBACK end_service(void *arg, unsigned char why) {
     /* Tell the service manager we are finished */\r
     case NSSM_EXIT_REALLY:\r
       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);\r
-      stop_service(ret, true, default_action);\r
+      stop_service(exitcode, true, default_action);\r
     break;\r
 \r
     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
     case NSSM_EXIT_UNCLEAN:\r
       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);\r
-      exit(stop_service(ret, false, default_action));\r
+      exit(stop_service(exitcode, false, default_action));\r
     break;\r
   }\r
 }\r