Browse Source

Refactor kill functions to be independent of services.

Removed most explicit references to service data in kill functions so
they can be used to kill arbitrary process trees.

The function to wait for service status change has been renamed
await_single_handle() to reflect that it can wait for any single object
not just a service's process handle.
Iain Patterson 7 years ago
parent
commit
c62455090e
5 changed files with 131 additions and 63 deletions
  1. BIN
      messages.mc
  2. 89 47
      process.cpp
  3. 18 3
      process.h
  4. 23 12
      service.cpp
  5. 1 1
      service.h

BIN
messages.mc


+ 89 - 47
process.cpp

@@ -2,6 +2,25 @@
 
 extern imports_t imports;
 
+void service_kill_t(nssm_service_t *service, kill_t *k) {
+  if (! service) return;
+  if (! k) return;
+
+  ZeroMemory(k, sizeof(*k));
+  k->name = service->name;
+  k->process_handle = service->process_handle;
+  k->pid = service->pid;
+  k->exitcode = service->exitcode;
+  k->stop_method = service->stop_method;
+  k->kill_console_delay = service->kill_console_delay;
+  k->kill_window_delay = service->kill_window_delay;
+  k->kill_threads_delay = service->kill_threads_delay;
+  k->status_handle = service->status_handle;
+  k->status = &service->status;
+  k->creation_time = service->creation_time;
+  k->exit_time = service->exit_time;
+}
+
 int get_process_creation_time(HANDLE process_handle, FILETIME *ft) {
   FILETIME creation_time, exit_time, kernel_time, user_time;
 
@@ -29,7 +48,7 @@ int get_process_exit_time(HANDLE process_handle, FILETIME *ft) {
   return 0;
 }
 
-int check_parent(nssm_service_t *service, PROCESSENTRY32 *pe, unsigned long ppid) {
+int check_parent(kill_t *k, PROCESSENTRY32 *pe, unsigned long ppid) {
   /* Check parent process ID matches. */
   if (pe->th32ParentProcessID != ppid) return 1;
 
@@ -43,7 +62,7 @@ int check_parent(nssm_service_t *service, PROCESSENTRY32 *pe, unsigned long ppid
   if (! process_handle) {
     TCHAR pid_string[16];
     _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pe->th32ProcessID);
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, service->name, error_string(GetLastError()), 0);
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);
     return 2;
   }
 
@@ -56,10 +75,10 @@ int check_parent(nssm_service_t *service, PROCESSENTRY32 *pe, unsigned long ppid
   CloseHandle(process_handle);
 
   /* Verify that the parent's creation time is not later. */
-  if (CompareFileTime(&service->creation_time, &ft) > 0) return 4;
+  if (CompareFileTime(&k->creation_time, &ft) > 0) return 4;
 
   /* Verify that the parent's exit time is not earlier. */
-  if (CompareFileTime(&service->exit_time, &ft) < 0) return 5;
+  if (CompareFileTime(&k->exit_time, &ft) < 0) return 5;
 
   return 0;
 }
@@ -91,13 +110,13 @@ int CALLBACK kill_window(HWND window, LPARAM arg) {
   processes so this function returns only true if at least one thread was
   successfully prodded.
 */
-int kill_threads(TCHAR *service_name, kill_t *k) {
+int kill_threads(nssm_service_t *service, kill_t *k) {
   int ret = 0;
 
   /* Get a snapshot of all threads in the system. */
   HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPTHREAD, 0);
   if (! snapshot) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, service_name, error_string(GetLastError()), 0);
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_THREAD_FAILED, k->name, error_string(GetLastError()), 0);
     return 0;
   }
 
@@ -106,7 +125,7 @@ int kill_threads(TCHAR *service_name, kill_t *k) {
   te.dwSize = sizeof(te);
 
   if (! Thread32First(snapshot, &te)) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, service_name, error_string(GetLastError()), 0);
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
     CloseHandle(snapshot);
     return 0;
   }
@@ -121,7 +140,7 @@ int kill_threads(TCHAR *service_name, kill_t *k) {
     if (! Thread32Next(snapshot, &te)) {
       unsigned long error = GetLastError();
       if (error == ERROR_NO_MORE_FILES) break;
-      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, service_name, error_string(GetLastError()), 0);
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_THREAD_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
       CloseHandle(snapshot);
       return ret;
     }
@@ -136,23 +155,22 @@ int kill_threads(TCHAR *service_name, kill_t *k) {
   return ret;
 }
 
+int kill_threads(kill_t *k) {
+  return kill_threads(NULL, k);
+}
+
 /* Give the process a chance to die gracefully. */
-int kill_process(nssm_service_t *service, HANDLE process_handle, unsigned long pid, unsigned long exitcode) {
-  /* Shouldn't happen. */
-  if (! service) return 1;
-  if (! pid) return 1;
-  if (! process_handle) return 1;
+int kill_process(nssm_service_t *service, kill_t *k) {
+  if (! k) return 1;
 
   unsigned long ret;
-  if (GetExitCodeProcess(process_handle, &ret)) {
+  if (GetExitCodeProcess(k->process_handle, &ret)) {
     if (ret != STILL_ACTIVE) return 1;
   }
 
-  kill_t k = { pid, exitcode, 0 };
-
   /* Try to send a Control-C event to the console. */
-  if (service->stop_method & NSSM_STOP_METHOD_CONSOLE) {
-    if (! kill_console(service, &k)) return 1;
+  if (k->stop_method & NSSM_STOP_METHOD_CONSOLE) {
+    if (! kill_console(k)) return 1;
   }
 
   /*
@@ -160,10 +178,11 @@ int kill_process(nssm_service_t *service, HANDLE process_handle, unsigned long p
     If the process is a console application it won't have any windows so there's
     no guarantee of success.
   */
-  if (service->stop_method & NSSM_STOP_METHOD_WINDOW) {
-    EnumWindows((WNDENUMPROC) kill_window, (LPARAM) &k);
-    if (k.signalled) {
-      if (! await_shutdown(service, _T(__FUNCTION__), service->kill_window_delay)) return 1;
+  if (k->stop_method & NSSM_STOP_METHOD_WINDOW) {
+    EnumWindows((WNDENUMPROC) kill_window, (LPARAM) k);
+    if (k->signalled) {
+      if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_window_delay)) return 1;
+      k->signalled = 0;
     }
   }
 
@@ -172,25 +191,29 @@ int kill_process(nssm_service_t *service, HANDLE process_handle, unsigned long p
     process.  Console applications might have them (but probably won't) so
     there's still no guarantee of success.
   */
-  if (service->stop_method & NSSM_STOP_METHOD_THREADS) {
-    if (kill_threads(service->name, &k)) {
-      if (! await_shutdown(service, _T(__FUNCTION__), service->kill_threads_delay)) return 1;
+  if (k->stop_method & NSSM_STOP_METHOD_THREADS) {
+    if (kill_threads(k)) {
+      if (! await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_threads_delay)) return 1;
     }
   }
 
   /* We tried being nice.  Time for extreme prejudice. */
-  if (service->stop_method & NSSM_STOP_METHOD_TERMINATE) {
-    return TerminateProcess(process_handle, exitcode);
+  if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) {
+    return TerminateProcess(k->process_handle, k->exitcode);
   }
 
   return 0;
 }
 
+int kill_process(kill_t *k) {
+  return kill_process(NULL, k);
+}
+
 /* Simulate a Control-C event to our console (shared with the app). */
 int kill_console(nssm_service_t *service, kill_t *k) {
   unsigned long ret;
 
-  if (! service) return 1;
+  if (! k) return 1;
 
   /* Check we loaded AttachConsole(). */
   if (! imports.AttachConsole) return 4;
@@ -211,7 +234,7 @@ int kill_console(nssm_service_t *service, kill_t *k) {
       case ERROR_ACCESS_DENIED:
       default:
         /* We already have a console. */
-        log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, service->name, error_string(ret), 0);
+        log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_ATTACHCONSOLE_FAILED, k->name, error_string(ret), 0);
         return 3;
     }
   }
@@ -219,37 +242,43 @@ int kill_console(nssm_service_t *service, kill_t *k) {
   /* Ignore the event ourselves. */
   ret = 0;
   if (! SetConsoleCtrlHandler(0, TRUE)) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, service->name, error_string(GetLastError()), 0);
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETCONSOLECTRLHANDLER_FAILED, k->name, error_string(GetLastError()), 0);
     ret = 4;
   }
 
   /* Send the event. */
   if (! ret) {
     if (! GenerateConsoleCtrlEvent(CTRL_C_EVENT, 0)) {
-      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED, service->name, error_string(GetLastError()), 0);
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GENERATECONSOLECTRLEVENT_FAILED, k->name, error_string(GetLastError()), 0);
       ret = 5;
     }
   }
 
   /* Detach from the console. */
   if (! FreeConsole()) {
-    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_FREECONSOLE_FAILED, service->name, error_string(GetLastError()), 0);
+    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_FREECONSOLE_FAILED, k->name, error_string(GetLastError()), 0);
   }
 
   /* Wait for process to exit. */
-  if (await_shutdown(service, _T(__FUNCTION__), service->kill_console_delay)) ret = 6;
+  if (await_single_handle(k->status_handle, k->status, k->process_handle, k->name, _T(__FUNCTION__), k->kill_console_delay)) ret = 6;
 
   return ret;
 }
 
-void kill_process_tree(nssm_service_t *service, unsigned long pid, unsigned long exitcode, unsigned long ppid) {
+int kill_console(kill_t *k) {
+  return kill_console(NULL, k);
+}
+
+void kill_process_tree(nssm_service_t * service, kill_t *k, unsigned long ppid) {
+  if (! k) return;
   /* Shouldn't happen unless the service failed to start. */
-  if (! pid) return;
+  if (! k->pid) return; /* XXX: needed? */
+  unsigned long pid = k->pid;
 
   TCHAR pid_string[16], code[16];
   _sntprintf_s(pid_string, _countof(pid_string), _TRUNCATE, _T("%lu"), pid);
-  _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);
-  log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, service->name, pid_string, code, 0);
+  _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), k->exitcode);
+  log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, k->name, pid_string, code, 0);
 
   /* We will need a process handle in order to call TerminateProcess() later. */
   HANDLE process_handle = OpenProcess(SYNCHRONIZE | PROCESS_QUERY_INFORMATION | PROCESS_VM_READ | PROCESS_TERMINATE, false, pid);
@@ -257,24 +286,25 @@ void kill_process_tree(nssm_service_t *service, unsigned long pid, unsigned long
     /* Kill this process first, then its descendents. */
     TCHAR ppid_string[16];
     _sntprintf_s(ppid_string, _countof(ppid_string), _TRUNCATE, _T("%lu"), ppid);
-    log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, service->name, 0);
-    if (! kill_process(service, process_handle, pid, exitcode)) {
+    log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, k->name, 0);
+    k->process_handle = process_handle; /* XXX: open directly? */
+    if (! kill_process(k)) {
       /* Maybe it already died. */
       unsigned long ret;
       if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) {
-        if (service->stop_method & NSSM_STOP_METHOD_TERMINATE) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_TERMINATEPROCESS_FAILED, pid_string, service->name, error_string(GetLastError()), 0);
-        else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_PROCESS_STILL_ACTIVE, service->name, pid_string, NSSM, NSSM_REG_STOP_METHOD_SKIP, 0);
+        if (k->stop_method & NSSM_STOP_METHOD_TERMINATE) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_TERMINATEPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);
+        else log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_PROCESS_STILL_ACTIVE, k->name, pid_string, NSSM, NSSM_REG_STOP_METHOD_SKIP, 0);
       }
     }
 
     CloseHandle(process_handle);
   }
-  else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, service->name, error_string(GetLastError()), 0);
+  else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENPROCESS_FAILED, pid_string, k->name, error_string(GetLastError()), 0);
 
   /* Get a snapshot of all processes in the system. */
   HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
   if (! snapshot) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, service->name, error_string(GetLastError()), 0);
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETOOLHELP32SNAPSHOT_PROCESS_FAILED, k->name, error_string(GetLastError()), 0);
     return;
   }
 
@@ -283,26 +313,38 @@ void kill_process_tree(nssm_service_t *service, unsigned long pid, unsigned long
   pe.dwSize = sizeof(pe);
 
   if (! Process32First(snapshot, &pe)) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, service->name, error_string(GetLastError()), 0);
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
     CloseHandle(snapshot);
     return;
   }
 
   /* This is a child of the doomed process so kill it. */
-  if (! check_parent(service, &pe, pid)) kill_process_tree(service, pe.th32ProcessID, exitcode, ppid);
+  if (! check_parent(k, &pe, pid)) {
+    k->pid = pe.th32ProcessID;
+    kill_process_tree(k, ppid);
+  }
+  k->pid = pid;
 
   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, error_string(GetLastError()), 0);
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, k->name, error_string(GetLastError()), 0);
       CloseHandle(snapshot);
       return;
     }
 
-    if (! check_parent(service, &pe, pid)) kill_process_tree(service, pe.th32ProcessID, exitcode, ppid);
+    if (! check_parent(k, &pe, pid)) {
+      k->pid = pe.th32ProcessID;
+      kill_process_tree(k, ppid);
+    }
+    k->pid = pid;
   }
 
   CloseHandle(snapshot);
 }
+
+void kill_process_tree(kill_t *k, unsigned long ppid) {
+  return kill_process_tree(NULL, k, ppid);
+}

+ 18 - 3
process.h

@@ -4,18 +4,33 @@
 #include <tlhelp32.h>
 
 typedef struct {
+  TCHAR *name;
+  HANDLE process_handle;
   unsigned long pid;
   unsigned long exitcode;
+  unsigned long stop_method;
+  unsigned long kill_console_delay;
+  unsigned long kill_window_delay;
+  unsigned long kill_threads_delay;
+  SERVICE_STATUS_HANDLE status_handle;
+  SERVICE_STATUS *status;
+  FILETIME creation_time;
+  FILETIME exit_time;
   int signalled;
 } kill_t;
 
+void service_kill_t(nssm_service_t *, kill_t *);
 int get_process_creation_time(HANDLE, FILETIME *);
 int get_process_exit_time(HANDLE, FILETIME *);
-int check_parent(TCHAR *, PROCESSENTRY32 *, unsigned long, FILETIME *, FILETIME *);
+int check_parent(kill_t *, PROCESSENTRY32 *, unsigned long);
 int CALLBACK kill_window(HWND, LPARAM);
 int kill_threads(nssm_service_t *, kill_t *);
+int kill_threads(kill_t *);
 int kill_console(nssm_service_t *, kill_t *);
-int kill_process(nssm_service_t *, HANDLE, unsigned long, unsigned long);
-void kill_process_tree(nssm_service_t *, unsigned long, unsigned long, unsigned long);
+int kill_console(kill_t *);
+int kill_process(nssm_service_t *, kill_t *);
+int kill_process(kill_t *);
+void kill_process_tree(nssm_service_t *, kill_t *, unsigned long);
+void kill_process_tree(kill_t *, unsigned long);
 
 #endif

+ 23 - 12
service.cpp

@@ -1688,7 +1688,8 @@ int start_service(nssm_service_t *service) {
     but be mindful of the fact that we are blocking the service control manager
     so abandon the wait before too much time has elapsed.
   */
-  if (await_service_status_change(service, SERVICE_START_PENDING, _T("start_service"), service->throttle_delay) == 1) service->throttle = 0;
+  service->status.dwCurrentState = SERVICE_START_PENDING;
+  if (await_single_handle(service->status_handle, &service->status, service->process_handle, service->name, _T("start_service"), service->throttle_delay) == 1) service->throttle = 0;
 
   /* Signal successful start */
   service->status.dwCurrentState = SERVICE_RUNNING;
@@ -1734,7 +1735,10 @@ int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful,
   if (service->pid) {
     /* Shut down service */
     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
-    kill_process(service, service->process_handle, service->pid, 0);
+    kill_t k;
+    service_kill_t(service, &k);
+    k.exitcode = 0;
+    kill_process(&k);
   }
   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
 
@@ -1793,7 +1797,11 @@ void CALLBACK end_service(void *arg, unsigned char why) {
 
   /* Clean up. */
   if (exitcode == STILL_ACTIVE) exitcode = 0;
-  if (service->pid && service->kill_process_tree) kill_process_tree(service, service->pid, exitcode, service->pid);
+  if (service->pid && service->kill_process_tree) {
+    kill_t k;
+    service_kill_t(service, &k);
+    kill_process_tree(&k, service->pid);
+  }
   service->pid = 0;
 
   /*
@@ -1915,13 +1923,16 @@ void throttle_restart(nssm_service_t *service) {
 
   Only doing both these things will prevent the system from killing the service.
 
+  If the status_handle and service_status arguments are omitted, this function
+  will not try to update the service manager but it will still log to the
+  event log that it is waiting for a handle.
+
   Returns: 1 if the wait timed out.
            0 if the wait completed.
           -1 on error.
 */
-int await_service_status_change(nssm_service_t *service, unsigned long status, unsigned long desired, TCHAR *function_name, unsigned long timeout) {
+int await_single_handle(SERVICE_STATUS_HANDLE status_handle, SERVICE_STATUS *status, HANDLE handle, TCHAR *name, TCHAR *function_name, unsigned long timeout) {
   unsigned long interval;
-  unsigned long waithint;
   unsigned long ret;
   unsigned long waited;
   TCHAR interval_milliseconds[16];
@@ -1938,24 +1949,24 @@ int await_service_status_change(nssm_service_t *service, unsigned long status, u
 
   _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);
 
-  waithint = service->status.dwWaitHint;
   waited = 0;
   while (waited < timeout) {
     interval = timeout - waited;
     if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
 
-    service->status.dwCurrentState = control;
-    service->status.dwWaitHint += interval;
-    service->status.dwCheckPoint++;
-    SetServiceStatus(service->status_handle, &service->status);
+    if (status) {
+      status->dwWaitHint += interval;
+      status->dwCheckPoint++;
+      SetServiceStatus(status_handle, status);
+    }
 
     if (waited) {
       _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);
       _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);
-      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SERVICE, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, service_status_text(status), 0);
+      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SINGLE_HANDLE, function, name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
     }
 
-    switch (WaitForSingleObject(service->process_handle, interval)) {
+    switch (WaitForSingleObject(handle, interval)) {
       case WAIT_OBJECT_0:
         ret = 0;
         goto awaited;

+ 1 - 1
service.h

@@ -145,6 +145,6 @@ int start_service(nssm_service_t *);
 int stop_service(nssm_service_t *, unsigned long, bool, bool);
 void CALLBACK end_service(void *, unsigned char);
 void throttle_restart(nssm_service_t *);
-int await_service_status_change(nssm_service_t *, unsigned long, unsigned long, TCHAR *, unsigned long);
+int await_single_handle(SERVICE_STATUS_HANDLE, SERVICE_STATUS *, HANDLE, TCHAR *, TCHAR *, unsigned long);
 
 #endif