Browse Source

Pass parameters around in a data structure.

Instead of static variables and functions taking a dozen arguments we
now use a single data structure, nssm_service_t, for the service.

The code is easier to read and understand, especially when adding more
variables through to functions.
Iain Patterson 9 years ago
parent
commit
fce252d07b
8 changed files with 409 additions and 344 deletions
  1. 61 36
      gui.cpp
  2. 1 14
      nssm.h
  3. 36 36
      process.cpp
  4. 4 4
      process.h
  5. 31 28
      registry.cpp
  6. 2 2
      registry.h
  7. 222 216
      service.cpp
  8. 52 8
      service.h

+ 61 - 36
gui.cpp

@@ -72,56 +72,68 @@ void centre_window(HWND window) {
 int install(HWND window) {
   if (! window) return 1;
 
-  /* Check parameters in the window */
-  char name[VALUE_LENGTH];
-  char exe[EXE_LENGTH];
-  char flags[VALUE_LENGTH];
-
-  /* Get service name */
-  if (! GetDlgItemText(window, IDC_NAME, name, sizeof(name))) {
-    popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_SERVICE_NAME);
-    return 2;
-  }
-
-  /* Get executable name */
-  if (! GetDlgItemText(window, IDC_PATH, exe, sizeof(exe))) {
-    popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_PATH);
-    return 3;
-  }
+  nssm_service_t *service = alloc_nssm_service();
+  if (service) {
+    /* Get service name. */
+    if (! GetDlgItemText(window, IDC_NAME, service->name, sizeof(service->name))) {
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_SERVICE_NAME);
+      cleanup_nssm_service(service);
+      return 2;
+    }
 
-  /* Get flags */
-  if (SendMessage(GetDlgItem(window, IDC_FLAGS), WM_GETTEXTLENGTH, 0, 0)) {
-    if (! GetDlgItemText(window, IDC_FLAGS, flags, sizeof(flags))) {
-      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_OPTIONS);
-      return 4;
+    /* Get executable name */
+    if (! GetDlgItemText(window, IDC_PATH, service->exe, sizeof(service->exe))) {
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_PATH);
+      return 3;
     }
+  
+    /* Get flags */
+    if (SendMessage(GetDlgItem(window, IDC_FLAGS), WM_GETTEXTLENGTH, 0, 0)) {
+      if (! GetDlgItemText(window, IDC_FLAGS, service->flags, sizeof(service->flags))) {
+        popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INVALID_OPTIONS);
+        return 4;
+      }
+    }
+
+    memmove(service->dir, service->exe, strlen(service->exe));
+    strip_basename(service->dir);
   }
-  else ZeroMemory(&flags, sizeof(flags));
 
-  /* See if it works */
-  switch (install_service(name, exe, flags)) {
+  /* See if it works. */
+  switch (install_service(service)) {
+    case 1:
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, "service", "install()");
+      cleanup_nssm_service(service);
+      return 1;
+
     case 2:
       popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
+      cleanup_nssm_service(service);
       return 2;
 
     case 3:
       popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);
+      cleanup_nssm_service(service);
       return 3;
 
     case 4:
       popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_OUT_OF_MEMORY_FOR_IMAGEPATH);
+      cleanup_nssm_service(service);
       return 4;
 
     case 5:
       popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_INSTALL_SERVICE_FAILED);
+      cleanup_nssm_service(service);
       return 5;
 
     case 6:
       popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_CREATE_PARAMETERS_FAILED);
+      cleanup_nssm_service(service);
       return 6;
   }
 
-  popup_message(MB_OK, NSSM_MESSAGE_SERVICE_INSTALLED, name);
+  popup_message(MB_OK, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
+  cleanup_nssm_service(service);
   return 0;
 }
 
@@ -129,34 +141,47 @@ int install(HWND window) {
 int remove(HWND window) {
   if (! window) return 1;
 
-  /* Check parameters in the window */
-  char name[VALUE_LENGTH];
+  /* See if it works */
+  nssm_service_t *service = alloc_nssm_service();
+  if (service) {
+    /* Get service name */
+    if (! GetDlgItemText(window, IDC_NAME, service->name, sizeof(service->name))) {
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_SERVICE_NAME);
+      cleanup_nssm_service(service);
+      return 2;
+    }
 
-  /* Get service name */
-  if (! GetDlgItemText(window, IDC_NAME, name, sizeof(name))) {
-    popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_MISSING_SERVICE_NAME);
-    return 2;
+    /* Confirm */
+    if (popup_message(MB_YESNO, NSSM_GUI_ASK_REMOVE_SERVICE, service->name) != IDYES) {
+      cleanup_nssm_service(service);
+      return 0;
+    }
   }
 
-  /* Confirm */
-  if (popup_message(MB_YESNO, NSSM_GUI_ASK_REMOVE_SERVICE, name) != IDYES) return 0;
+  switch (remove_service(service)) {
+    case 1:
+      popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_EVENT_OUT_OF_MEMORY, "service", "remove()");
+      cleanup_nssm_service(service);
+      return 1;
 
-  /* See if it works */
-  switch (remove_service(name)) {
     case 2:
       popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
+      cleanup_nssm_service(service);
       return 2;
 
     case 3:
       popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_SERVICE_NOT_INSTALLED);
       return 3;
+      cleanup_nssm_service(service);
 
     case 4:
       popup_message(MB_OK | MB_ICONEXCLAMATION, NSSM_GUI_REMOVE_SERVICE_FAILED);
+      cleanup_nssm_service(service);
       return 4;
   }
 
-  popup_message(MB_OK, NSSM_MESSAGE_SERVICE_REMOVED, name);
+  popup_message(MB_OK, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
+  cleanup_nssm_service(service);
   return 0;
 }
 

+ 1 - 14
nssm.h

@@ -6,13 +6,13 @@
 #include <stdarg.h>
 #include <stdio.h>
 #include <windows.h>
+#include "service.h"
 #include "event.h"
 #include "imports.h"
 #include "messages.h"
 #include "process.h"
 #include "registry.h"
 #include "io.h"
-#include "service.h"
 #include "gui.h"
 
 int str_equiv(const char *, const char *);
@@ -22,19 +22,6 @@ int str_equiv(const char *, const char *);
 #define NSSM_VERSIONINFO 2,18,0,0
 #define NSSM_DATE "2013-11-15"
 
-/*
-  MSDN says the commandline in CreateProcess() is limited to 32768 characters
-  and the application name to MAX_PATH.
-  A registry key is limited to 255 characters.
-  A registry value is limited to 16383 characters.
-  Therefore we limit the service name to accommodate the path under HKLM.
-*/
-#define EXE_LENGTH MAX_PATH
-#define CMD_LENGTH 32768
-#define KEY_LENGTH 255
-#define VALUE_LENGTH 16383
-#define SERVICE_NAME_LENGTH KEY_LENGTH - 55
-
 /*
   Throttle the restart of the service if it stops before this many
   milliseconds have elapsed since startup.  Override in registry.

+ 36 - 36
process.cpp

@@ -1,9 +1,6 @@
 #include "nssm.h"
 
 extern imports_t imports;
-extern unsigned long kill_console_delay;
-extern unsigned long kill_window_delay;
-extern unsigned long kill_threads_delay;
 
 int get_process_creation_time(HANDLE process_handle, FILETIME *ft) {
   FILETIME creation_time, exit_time, kernel_time, user_time;
@@ -31,7 +28,7 @@ int get_process_exit_time(HANDLE process_handle, FILETIME *ft) {
   return 0;
 }
 
-int check_parent(char *service_name, PROCESSENTRY32 *pe, unsigned long ppid, FILETIME *pft, FILETIME *exit_time) {
+int check_parent(nssm_service_t *service, PROCESSENTRY32 *pe, unsigned long ppid) {
   /* Check parent process ID matches. */
   if (pe->th32ParentProcessID != ppid) return 1;
 
@@ -45,7 +42,7 @@ int check_parent(char *service_name, PROCESSENTRY32 *pe, unsigned long ppid, FIL
   if (! process_handle) {
     char pid_string[16];
     _snprintf_s(pid_string, sizeof(pid_string), _TRUNCATE, "%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, service->name, error_string(GetLastError()), 0);
     return 2;
   }
 
@@ -58,10 +55,10 @@ int check_parent(char *service_name, PROCESSENTRY32 *pe, unsigned long ppid, FIL
   CloseHandle(process_handle);
 
   /* Verify that the parent's creation time is not later. */
-  if (CompareFileTime(pft, &ft) > 0) return 4;
+  if (CompareFileTime(&service->creation_time, &ft) > 0) return 4;
 
   /* Verify that the parent's exit time is not earlier. */
-  if (CompareFileTime(exit_time, &ft) < 0) return 5;
+  if (CompareFileTime(&service->exit_time, &ft) < 0) return 5;
 
   return 0;
 }
@@ -139,8 +136,9 @@ int kill_threads(char *service_name, kill_t *k) {
 }
 
 /* Give the process a chance to die gracefully. */
-int kill_process(char *service_name, SERVICE_STATUS_HANDLE service_handle, SERVICE_STATUS *service_status, unsigned long stop_method, HANDLE process_handle, unsigned long pid, unsigned long exitcode) {
+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;
 
@@ -152,8 +150,8 @@ int kill_process(char *service_name, SERVICE_STATUS_HANDLE service_handle, SERVI
   kill_t k = { pid, exitcode, 0 };
 
   /* Try to send a Control-C event to the console. */
-  if (stop_method & NSSM_STOP_METHOD_CONSOLE) {
-    if (! kill_console(service_name, service_handle, service_status, process_handle, pid)) return 1;
+  if (service->stop_method & NSSM_STOP_METHOD_CONSOLE) {
+    if (! kill_console(service)) return 1;
   }
 
   /*
@@ -161,10 +159,10 @@ int kill_process(char *service_name, SERVICE_STATUS_HANDLE service_handle, SERVI
     If the process is a console application it won't have any windows so there's
     no guarantee of success.
   */
-  if (stop_method & NSSM_STOP_METHOD_WINDOW) {
+  if (service->stop_method & NSSM_STOP_METHOD_WINDOW) {
     EnumWindows((WNDENUMPROC) kill_window, (LPARAM) &k);
     if (k.signalled) {
-      if (! await_shutdown(__FUNCTION__, service_name, service_handle, service_status, process_handle, kill_window_delay)) return 1;
+      if (! await_shutdown(service, __FUNCTION__, service->kill_window_delay)) return 1;
     }
   }
 
@@ -173,29 +171,31 @@ int kill_process(char *service_name, SERVICE_STATUS_HANDLE service_handle, SERVI
     process.  Console applications might have them (but probably won't) so
     there's still no guarantee of success.
   */
-  if (stop_method & NSSM_STOP_METHOD_THREADS) {
-    if (kill_threads(service_name, &k)) {
-      if (! await_shutdown(__FUNCTION__, service_name, service_handle, service_status, process_handle, kill_threads_delay)) return 1;
+  if (service->stop_method & NSSM_STOP_METHOD_THREADS) {
+    if (kill_threads(service->name, &k)) {
+      if (! await_shutdown(service, __FUNCTION__, service->kill_threads_delay)) return 1;
     }
   }
 
   /* We tried being nice.  Time for extreme prejudice. */
-  if (stop_method & NSSM_STOP_METHOD_TERMINATE) {
-    return TerminateProcess(process_handle, exitcode);
+  if (service->stop_method & NSSM_STOP_METHOD_TERMINATE) {
+    return TerminateProcess(service->process_handle, exitcode);
   }
 
   return 0;
 }
 
 /* Simulate a Control-C event to our console (shared with the app). */
-int kill_console(char *service_name, SERVICE_STATUS_HANDLE service_handle, SERVICE_STATUS *service_status, HANDLE process_handle, unsigned long pid) {
+int kill_console(nssm_service_t *service) {
   unsigned long ret;
 
+  if (! service) return 1;
+
   /* Check we loaded AttachConsole(). */
   if (! imports.AttachConsole) return 4;
 
   /* Try to attach to the process's console. */
-  if (! imports.AttachConsole(pid)) {
+  if (! imports.AttachConsole(service->pid)) {
     ret = GetLastError();
 
     switch (ret) {
@@ -210,7 +210,7 @@ int kill_console(char *service_name, SERVICE_STATUS_HANDLE service_handle, SERVI
       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, service->name, error_string(ret), 0);
         return 3;
     }
   }
@@ -218,42 +218,42 @@ int kill_console(char *service_name, SERVICE_STATUS_HANDLE service_handle, SERVI
   /* 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, service->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, service->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, service->name, error_string(GetLastError()), 0);
   }
 
   /* Wait for process to exit. */
-  if (await_shutdown(__FUNCTION__, service_name, service_handle, service_status, process_handle, kill_console_delay)) ret = 6;
+  if (await_shutdown(service, __FUNCTION__, service->kill_console_delay)) ret = 6;
 
   return ret;
 }
 
-void kill_process_tree(char *service_name, SERVICE_STATUS_HANDLE service_handle, SERVICE_STATUS *service_status, unsigned long stop_method, unsigned long pid, unsigned long exitcode, unsigned long ppid, FILETIME *parent_creation_time, FILETIME *parent_exit_time) {
+void kill_process_tree(nssm_service_t *service, unsigned long pid, unsigned long exitcode, unsigned long ppid) {
   /* Shouldn't happen unless the service failed to start. */
   if (! pid) return;
 
   char pid_string[16], code[16];
   _snprintf_s(pid_string, sizeof(pid_string), _TRUNCATE, "%lu", pid);
   _snprintf_s(code, sizeof(code), _TRUNCATE, "%lu", exitcode);
-  log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, service_name, pid_string, code, 0);
+  log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILLING, service->name, pid_string, code, 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, service->name, error_string(GetLastError()), 0);
     return;
   }
 
@@ -262,25 +262,25 @@ void kill_process_tree(char *service_name, SERVICE_STATUS_HANDLE service_handle,
   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, service->name, error_string(GetLastError()), 0);
     CloseHandle(snapshot);
     return;
   }
 
   /* This is a child of the doomed process so kill it. */
-  if (! check_parent(service_name, &pe, pid, parent_creation_time, parent_exit_time)) kill_process_tree(service_name, service_handle, service_status, stop_method, pe.th32ProcessID, exitcode, ppid, parent_creation_time, parent_exit_time);
+  if (! check_parent(service, &pe, pid)) kill_process_tree(service, 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, error_string(GetLastError()), 0);
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PROCESS_ENUMERATE_FAILED, service->name, error_string(GetLastError()), 0);
       CloseHandle(snapshot);
       return;
     }
 
-    if (! check_parent(service_name, &pe, pid, parent_creation_time, parent_exit_time)) kill_process_tree(service_name, service_handle, service_status, stop_method, pe.th32ProcessID, exitcode, ppid, parent_creation_time, parent_exit_time);
+    if (! check_parent(service, &pe, pid)) kill_process_tree(service, pe.th32ProcessID, exitcode, ppid);
   }
 
   CloseHandle(snapshot);
@@ -288,19 +288,19 @@ void kill_process_tree(char *service_name, SERVICE_STATUS_HANDLE service_handle,
   /* 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);
   if (! process_handle) {
-    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, service->name, error_string(GetLastError()), 0);
     return;
   }
 
   char ppid_string[16];
   _snprintf_s(ppid_string, sizeof(ppid_string), _TRUNCATE, "%lu", ppid);
-  log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_KILL_PROCESS_TREE, pid_string, ppid_string, service_name, 0);
-  if (! kill_process(service_name, service_handle, service_status, stop_method, process_handle, pid, exitcode)) {
+  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)) {
     /* Maybe it already died. */
     unsigned long ret;
     if (! GetExitCodeProcess(process_handle, &ret) || ret == STILL_ACTIVE) {
-      if (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 (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);
     }
   }
 

+ 4 - 4
process.h

@@ -13,9 +13,9 @@ int get_process_creation_time(HANDLE, FILETIME *);
 int get_process_exit_time(HANDLE, FILETIME *);
 int check_parent(char *, PROCESSENTRY32 *, unsigned long, FILETIME *, FILETIME *);
 int CALLBACK kill_window(HWND, LPARAM);
-int kill_threads(char *, SERVICE_STATUS_HANDLE, SERVICE_STATUS *, kill_t *);
-int kill_console(char *, SERVICE_STATUS_HANDLE, SERVICE_STATUS *, HANDLE, unsigned long);
-int kill_process(char *, SERVICE_STATUS_HANDLE, SERVICE_STATUS *, unsigned long, HANDLE, unsigned long, unsigned long);
-void kill_process_tree(char *, SERVICE_STATUS_HANDLE, SERVICE_STATUS *, unsigned long, unsigned long, unsigned long, unsigned long, FILETIME *, FILETIME *);
+int kill_threads(nssm_service_t *, kill_t *);
+int kill_console(nssm_service_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);
 
 #endif

+ 31 - 28
registry.cpp

@@ -26,10 +26,10 @@ int create_messages() {
   return 0;
 }
 
-int create_parameters(char *service_name, char *exe, char *flags, char *dir) {
+int create_parameters(nssm_service_t *service) {
   /* Get registry */
   char registry[KEY_LENGTH];
-  if (_snprintf_s(registry, sizeof(registry), _TRUNCATE, NSSM_REGISTRY, service_name) < 0) {
+  if (_snprintf_s(registry, sizeof(registry), _TRUNCATE, NSSM_REGISTRY, service->name) < 0) {
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "NSSM_REGISTRY", "create_parameters()", 0);
     return 1;
   }
@@ -42,19 +42,19 @@ int create_parameters(char *service_name, char *exe, char *flags, char *dir) {
   }
 
   /* Try to create the parameters */
-  if (RegSetValueEx(key, NSSM_REG_EXE, 0, REG_EXPAND_SZ, (const unsigned char *) exe, (unsigned long) strlen(exe) + 1) != ERROR_SUCCESS) {
+  if (RegSetValueEx(key, NSSM_REG_EXE, 0, REG_EXPAND_SZ, (const unsigned char *) service->exe, (unsigned long) strlen(service->exe) + 1) != ERROR_SUCCESS) {
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_EXE, error_string(GetLastError()), 0);
     RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY);
     RegCloseKey(key);
     return 3;
   }
-  if (RegSetValueEx(key, NSSM_REG_FLAGS, 0, REG_EXPAND_SZ, (const unsigned char *) flags, (unsigned long) strlen(flags) + 1) != ERROR_SUCCESS) {
+  if (RegSetValueEx(key, NSSM_REG_FLAGS, 0, REG_EXPAND_SZ, (const unsigned char *) service->flags, (unsigned long) strlen(service->flags) + 1) != ERROR_SUCCESS) {
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_FLAGS, error_string(GetLastError()), 0);
     RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY);
     RegCloseKey(key);
     return 4;
   }
-  if (RegSetValueEx(key, NSSM_REG_DIR, 0, REG_EXPAND_SZ, (const unsigned char *) dir, (unsigned long) strlen(dir) + 1) != ERROR_SUCCESS) {
+  if (RegSetValueEx(key, NSSM_REG_DIR, 0, REG_EXPAND_SZ, (const unsigned char *) service->dir, (unsigned long) strlen(service->dir) + 1) != ERROR_SUCCESS) {
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SETVALUE_FAILED, NSSM_REG_DIR, error_string(GetLastError()), 0);
     RegDeleteKey(HKEY_LOCAL_MACHINE, NSSM_REGISTRY);
     RegCloseKey(key);
@@ -123,6 +123,9 @@ int set_environment(char *service_name, HKEY key, char **env) {
   /* Probably not possible */
   if (! envlen) return 0;
 
+  /* Previously initialised? */
+  if (*env) HeapFree(GetProcessHeap(), 0, *env);
+
   *env = (char *) HeapAlloc(GetProcessHeap(), 0, envlen);
   if (! *env) {
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "environment registry", "set_environment()", 0);
@@ -261,12 +264,12 @@ void override_milliseconds(char *service_name, HKEY key, char *value, unsigned l
   if (! ok) *buffer = default_value;
 }
 
-int get_parameters(char *service_name, char *exe, unsigned long exelen, char *flags, unsigned long flagslen, char *dir, unsigned long dirlen, char **env, unsigned long *throttle_delay, unsigned long *stop_method, unsigned long *kill_console_delay, unsigned long *kill_window_delay, unsigned long *kill_threads_delay, STARTUPINFO *si) {
+int get_parameters(nssm_service_t *service, STARTUPINFO *si) {
   unsigned long ret;
 
   /* Get registry */
   char registry[KEY_LENGTH];
-  if (_snprintf_s(registry, sizeof(registry), _TRUNCATE, NSSM_REGISTRY, service_name) < 0) {
+  if (_snprintf_s(registry, sizeof(registry), _TRUNCATE, NSSM_REGISTRY, service->name) < 0) {
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "NSSM_REGISTRY", "get_parameters()", 0);
     return 1;
   }
@@ -279,50 +282,50 @@ int get_parameters(char *service_name, char *exe, unsigned long exelen, char *fl
   }
 
   /* Try to get executable file - MUST succeed */
-  if (expand_parameter(key, NSSM_REG_EXE, exe, exelen, false)) {
+  if (expand_parameter(key, NSSM_REG_EXE, service->exe, sizeof(service->exe), false)) {
     RegCloseKey(key);
     return 3;
   }
 
   /* Try to get flags - may fail and we don't care */
-  if (expand_parameter(key, NSSM_REG_FLAGS, flags, flagslen, false)) {
-    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_FLAGS, NSSM_REG_FLAGS, service_name, exe, 0);
-    ZeroMemory(flags, flagslen);
+  if (expand_parameter(key, NSSM_REG_FLAGS, service->flags, sizeof(service->flags), false)) {
+    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_FLAGS, NSSM_REG_FLAGS, service->name, service->exe, 0);
+    ZeroMemory(service->flags, sizeof(service->flags));
   }
 
   /* Try to get startup directory - may fail and we fall back to a default */
-  if (expand_parameter(key, NSSM_REG_DIR, dir, dirlen, true) || ! dir[0]) {
+  if (expand_parameter(key, NSSM_REG_DIR, service->dir, sizeof(service->dir), true) || ! service->dir[0]) {
     /* Our buffers are defined to be long enough for this to be safe */
     size_t i;
-    for (i = strlen(exe); i && exe[i] != '\\' && exe[i] != '/'; i--);
+    for (i = strlen(service->exe); i && service->exe[i] != '\\' && service->exe[i] != '/'; i--);
     if (i) {
-      memmove(dir, exe, i);
-      dir[i] = '\0';
+      memmove(service->dir, service->exe, i);
+      service->dir[i] = '\0';
     }
     else {
       /* Help! */
-      ret = GetWindowsDirectory(dir, dirlen);
-      if (! ret || ret > dirlen) {
-        log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_NO_DIR_AND_NO_FALLBACK, NSSM_REG_DIR, service_name, 0);
+      ret = GetWindowsDirectory(service->dir, sizeof(service->dir));
+      if (! ret || ret > sizeof(service->dir)) {
+        log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_NO_DIR_AND_NO_FALLBACK, NSSM_REG_DIR, service->name, 0);
         RegCloseKey(key);
         return 4;
       }
     }
-    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_DIR, NSSM_REG_DIR, service_name, dir, 0);
+    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_NO_DIR, NSSM_REG_DIR, service->name, service->dir, 0);
   }
 
   /* Try to get environment variables - may fail */
-  set_environment(service_name, key, env);
+  set_environment(service->name, key, &service->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);
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);
     RegCloseKey(key);
     return 5;
   }
 
   /* Try to get throttle restart delay */
-  override_milliseconds(service_name, key, NSSM_REG_THROTTLE, throttle_delay, NSSM_RESET_THROTTLE_RESTART, NSSM_EVENT_BOGUS_THROTTLE);
+  override_milliseconds(service->name, key, NSSM_REG_THROTTLE, &service->throttle_delay, NSSM_RESET_THROTTLE_RESTART, NSSM_EVENT_BOGUS_THROTTLE);
 
   /* Try to get service stop flags. */
   unsigned long type = REG_DWORD;
@@ -333,7 +336,7 @@ int get_parameters(char *service_name, char *exe, unsigned long exelen, char *fl
   if (ret != ERROR_SUCCESS) {
     if (ret != ERROR_FILE_NOT_FOUND) {
       if (type != REG_DWORD) {
-        log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_STOP_METHOD_SKIP, service_name, NSSM_REG_STOP_METHOD_SKIP, NSSM, 0);
+        log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_BOGUS_STOP_METHOD_SKIP, service->name, NSSM_REG_STOP_METHOD_SKIP, NSSM, 0);
       }
       else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_QUERYVALUE_FAILED, NSSM_REG_STOP_METHOD_SKIP, error_string(GetLastError()), 0);
     }
@@ -341,13 +344,13 @@ int get_parameters(char *service_name, char *exe, unsigned long exelen, char *fl
   else stop_ok = true;
 
   /* Try all methods except those requested to be skipped. */
-  *stop_method = ~0;
-  if (stop_ok) *stop_method &= ~stop_method_skip;
+  service->stop_method = ~0;
+  if (stop_ok) service->stop_method &= ~stop_method_skip;
 
   /* Try to get kill delays - may fail. */
-  override_milliseconds(service_name, key, NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, kill_console_delay, NSSM_KILL_CONSOLE_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_CONSOLE_GRACE_PERIOD);
-  override_milliseconds(service_name, key, NSSM_REG_KILL_WINDOW_GRACE_PERIOD, kill_window_delay, NSSM_KILL_WINDOW_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_WINDOW_GRACE_PERIOD);
-  override_milliseconds(service_name, key, NSSM_REG_KILL_THREADS_GRACE_PERIOD, kill_threads_delay, NSSM_KILL_THREADS_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_THREADS_GRACE_PERIOD);
+  override_milliseconds(service->name, key, NSSM_REG_KILL_CONSOLE_GRACE_PERIOD, &service->kill_console_delay, NSSM_KILL_CONSOLE_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_CONSOLE_GRACE_PERIOD);
+  override_milliseconds(service->name, key, NSSM_REG_KILL_WINDOW_GRACE_PERIOD, &service->kill_window_delay, NSSM_KILL_WINDOW_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_WINDOW_GRACE_PERIOD);
+  override_milliseconds(service->name, key, NSSM_REG_KILL_THREADS_GRACE_PERIOD, &service->kill_threads_delay, NSSM_KILL_THREADS_GRACE_PERIOD, NSSM_EVENT_BOGUS_KILL_THREADS_GRACE_PERIOD);
 
   /* Close registry */
   RegCloseKey(key);

+ 2 - 2
registry.h

@@ -21,7 +21,7 @@
 #define NSSM_STDIO_LENGTH 29
 
 int create_messages();
-int create_parameters(char *, char *, char *, char *);
+int create_parameters(nssm_service_t *);
 int create_exit_action(char *, const char *);
 int set_environment(char *, HKEY, char **);
 int expand_parameter(HKEY, char *, char *, unsigned long, bool, bool);
@@ -31,7 +31,7 @@ int set_number(HKEY, char *, unsigned long);
 int get_number(HKEY, char *, unsigned long *, bool);
 int get_number(HKEY, char *, unsigned long *);
 void override_milliseconds(char *, HKEY, char *, unsigned long *, unsigned long, unsigned long);
-int get_parameters(char *, char *, unsigned long, char *, unsigned long, char *, unsigned long, char **, unsigned long *, unsigned long *, unsigned long *, unsigned long *, unsigned long *, STARTUPINFO *);
+int get_parameters(nssm_service_t *, STARTUPINFO *);
 int get_exit_action(char *, unsigned long *, unsigned char *, bool *);
 
 #endif

+ 222 - 216
service.cpp

@@ -1,37 +1,14 @@
 #include "nssm.h"
 
 bool is_admin;
-SERVICE_STATUS service_status;
-SERVICE_STATUS_HANDLE service_handle;
-HANDLE process_handle;
-HANDLE wait_handle;
-unsigned long pid;
-static char service_name[SERVICE_NAME_LENGTH];
-char exe[EXE_LENGTH];
-char flags[CMD_LENGTH];
-char dir[MAX_PATH];
-bool stopping;
-bool allow_restart;
-unsigned long throttle_delay;
-unsigned long stop_method;
-unsigned long kill_console_delay;
-unsigned long kill_window_delay;
-unsigned long kill_threads_delay;
-CRITICAL_SECTION throttle_section;
-CONDITION_VARIABLE throttle_condition;
-HANDLE throttle_timer;
-LARGE_INTEGER throttle_duetime;
 bool use_critical_section;
-FILETIME creation_time;
 
 extern imports_t imports;
 
 static enum { NSSM_EXIT_RESTART, NSSM_EXIT_IGNORE, NSSM_EXIT_REALLY, NSSM_EXIT_UNCLEAN } exit_actions;
 static const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", "Suicide", 0 };
 
-static unsigned long throttle;
-
-static inline int throttle_milliseconds() {
+static inline int throttle_milliseconds(unsigned long throttle) {
   /* pow() operates on doubles. */
   int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;
   return ret * 1000;
@@ -42,7 +19,7 @@ static inline int throttle_milliseconds() {
   control immediately.
 */
 static unsigned long WINAPI shutdown_service(void *arg) {
-  return stop_service(0, true, true);
+  return stop_service((nssm_service_t *) arg, 0, true, true);
 }
 
 /* Connect to the service manager */
@@ -56,26 +33,46 @@ SC_HANDLE open_service_manager() {
   return ret;
 }
 
+/* Allocate and zero memory for a service. */
+nssm_service_t *alloc_nssm_service() {
+  nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));
+  if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service", "alloc_nssm_service()", 0);
+  return service;
+}
+
+/* Free memory for a service. */
+void cleanup_nssm_service(nssm_service_t *service) {
+  if (! service) return;
+  if (service->env) HeapFree(GetProcessHeap(), 0, service->env);
+  if (service->handle) CloseServiceHandle(service->handle);
+  if (service->process_handle) CloseHandle(service->process_handle);
+  if (service->wait_handle) UnregisterWait(service->process_handle);
+  if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);
+  if (service->throttle_timer) CloseHandle(service->throttle_timer);
+  HeapFree(GetProcessHeap(), 0, service);
+}
+
 /* About to install the service */
 int pre_install_service(int argc, char **argv) {
   /* Show the dialogue box if we didn't give the service name and path */
   if (argc < 2) return nssm_gui(IDD_INSTALL, argv[0]);
 
+  nssm_service_t *service = alloc_nssm_service();
+  if (! service) {
+    print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, "service", "pre_install_service()");
+    return 1;
+  }
+
+  memmove(service->name, argv[0], strlen(argv[0]));
+  memmove(service->exe, argv[1], strlen(argv[1]));
+
   /* Arguments are optional */
-  char *flags;
   size_t flagslen = 0;
   size_t s = 0;
-  int i;
+  size_t i;
   for (i = 2; i < argc; i++) flagslen += strlen(argv[i]) + 1;
   if (! flagslen) flagslen = 1;
 
-  flags = (char *) HeapAlloc(GetProcessHeap(), 0, flagslen);
-  if (! flags) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "flags", "pre_install_service()", 0);
-    return 2;
-  }
-  ZeroMemory(flags, flagslen);
-
   /*
     This probably isn't UTF8-safe and should use std::string or something
     but it's been broken for the best part of a decade and due for a rewrite
@@ -84,29 +81,46 @@ int pre_install_service(int argc, char **argv) {
   */
   for (i = 2; i < argc; i++) {
     size_t len = strlen(argv[i]);
-    memmove(flags + s, argv[i], len);
+    memmove(service->flags + s, argv[i], len);
     s += len;
-    if (i < argc - 1) flags[s++] = ' ';
+    if (i < argc - 1) service->flags[s++] = ' ';
   }
 
-  return install_service(argv[0], argv[1], flags);
+  /* Work out directory name */
+  size_t len = strlen(service->exe);
+  for (i = len; i && service->exe[i] != '\\' && service->exe[i] != '/'; i--);
+  memmove(service->dir, service->exe, i);
+  service->dir[i] = '\0';
+
+  int ret = install_service(service);
+  cleanup_nssm_service(service);
+  return ret;
 }
 
 /* About to remove the service */
 int pre_remove_service(int argc, char **argv) {
   /* Show dialogue box if we didn't pass service name and "confirm" */
   if (argc < 2) return nssm_gui(IDD_REMOVE, argv[0]);
-  if (str_equiv(argv[1], "confirm")) return remove_service(argv[0]);
+  if (str_equiv(argv[1], "confirm")) {
+    nssm_service_t *service = alloc_nssm_service();
+    memmove(service->name, argv[0], strlen(argv[0]));
+    int ret = remove_service(service);
+    cleanup_nssm_service(service);
+    return ret;
+  }
   print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
   return 100;
 }
 
 /* Install the service */
-int install_service(char *name, char *exe, char *flags) {
+int install_service(nssm_service_t *service) {
+  if (! service) return 1;
+
   /* Open service manager */
   SC_HANDLE services = open_service_manager();
   if (! services) {
     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
+    cleanup_nssm_service(service);
     return 2;
   }
 
@@ -126,42 +140,36 @@ int install_service(char *name, char *exe, char *flags) {
     return 4;
   }
 
-  /* Work out directory name */
-  size_t len = strlen(exe);
-  size_t i;
-  for (i = len; i && exe[i] != '\\' && exe[i] != '/'; i--);
-  char dir[MAX_PATH];
-  memmove(dir, exe, i);
-  dir[i] = '\0';
-
   /* Create the service */
-  SC_HANDLE service = CreateService(services, name, name, SC_MANAGER_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, command, 0, 0, 0, 0, 0);
-  if (! service) {
+  service->handle = CreateService(services, service->name, service->name, SC_MANAGER_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, command, 0, 0, 0, 0, 0);
+  if (! service->handle) {
     print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
     CloseServiceHandle(services);
     return 5;
   }
 
   /* Now we need to put the parameters into the registry */
-  if (create_parameters(name, exe, flags, dir)) {
+  if (create_parameters(service)) {
     print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
-    DeleteService(service);
+    DeleteService(service->handle);
     CloseServiceHandle(services);
     return 6;
   }
 
-  set_service_recovery(service, name);
+  set_service_recovery(service);
+
+  print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
 
   /* Cleanup */
-  CloseServiceHandle(service);
   CloseServiceHandle(services);
 
-  print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, name);
   return 0;
 }
 
 /* Remove the service */
-int remove_service(char *name) {
+int remove_service(nssm_service_t *service) {
+  if (! service) return 1;
+
   /* Open service manager */
   SC_HANDLE services = open_service_manager();
   if (! services) {
@@ -170,33 +178,34 @@ int remove_service(char *name) {
   }
 
   /* Try to open the service */
-  SC_HANDLE service = OpenService(services, name, SC_MANAGER_ALL_ACCESS);
-  if (! service) {
+  service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
+  if (! service->handle) {
     print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
     CloseServiceHandle(services);
     return 3;
   }
 
   /* Try to delete the service */
-  if (! DeleteService(service)) {
+  if (! DeleteService(service->handle)) {
     print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
-    CloseServiceHandle(service);
     CloseServiceHandle(services);
     return 4;
   }
 
   /* Cleanup */
-  CloseServiceHandle(service);
   CloseServiceHandle(services);
 
-  print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, name);
+  print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
   return 0;
 }
 
 /* Service initialisation */
 void WINAPI service_main(unsigned long argc, char **argv) {
-  if (_snprintf_s(service_name, sizeof(service_name), _TRUNCATE, "%s", argv[0]) < 0) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service_name", "service_main()", 0);
+  nssm_service_t *service = alloc_nssm_service();
+  if (! service) return;
+
+  if (_snprintf_s(service->name, sizeof(service->name), _TRUNCATE, "%s", argv[0]) < 0) {
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service->name", "service_main()", 0);
     return;
   }
 
@@ -205,95 +214,88 @@ void WINAPI service_main(unsigned long argc, char **argv) {
   else use_critical_section = false;
 
   /* Initialise status */
-  ZeroMemory(&service_status, sizeof(service_status));
-  service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
-  service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
-  service_status.dwWin32ExitCode = NO_ERROR;
-  service_status.dwServiceSpecificExitCode = 0;
-  service_status.dwCheckPoint = 0;
-  service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;
+  ZeroMemory(&service->status, sizeof(service->status));
+  service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
+  service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
+  service->status.dwWin32ExitCode = NO_ERROR;
+  service->status.dwServiceSpecificExitCode = 0;
+  service->status.dwCheckPoint = 0;
+  service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
 
   /* Signal we AREN'T running the server */
-  process_handle = 0;
-  pid = 0;
+  service->process_handle = 0;
+  service->pid = 0;
 
   /* Register control handler */
-  service_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, 0);
-  if (! service_handle) {
+  service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
+  if (! service->status_handle) {
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
     return;
   }
 
-  log_service_control(service_name, 0, true);
+  log_service_control(service->name, 0, true);
 
-  service_status.dwCurrentState = SERVICE_START_PENDING;
-  service_status.dwWaitHint = throttle_delay + NSSM_WAITHINT_MARGIN;
-  SetServiceStatus(service_handle, &service_status);
+  service->status.dwCurrentState = SERVICE_START_PENDING;
+  service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
+  SetServiceStatus(service->status_handle, &service->status);
 
   if (is_admin) {
     /* Try to create the exit action parameters; we don't care if it fails */
-    create_exit_action(argv[0], exit_action_strings[0]);
+    create_exit_action(service->name, exit_action_strings[0]);
 
-    set_service_recovery(0, service_name);
+    SC_HANDLE services = open_service_manager();
+    if (services) {
+      service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
+      set_service_recovery(service);
+      CloseServiceHandle(services);
+    }
   }
 
   /* Used for signalling a resume if the service pauses when throttled. */
-  if (use_critical_section) InitializeCriticalSection(&throttle_section);
+  if (use_critical_section) {
+    InitializeCriticalSection(&service->throttle_section);
+    service->throttle_section_initialised = true;
+  }
   else {
-    throttle_timer = CreateWaitableTimer(0, 1, 0);
-    if (! throttle_timer) {
-      log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service_name, error_string(GetLastError()), 0);
+    service->throttle_timer = CreateWaitableTimer(0, 1, 0);
+    if (! service->throttle_timer) {
+      log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
     }
   }
 
-  monitor_service();
+  monitor_service(service);
 }
 
 /* Make sure service recovery actions are taken where necessary */
-void set_service_recovery(SC_HANDLE service, char *service_name) {
-  SC_HANDLE services = 0;
-
-  if (! service) {
-    services = open_service_manager();
-    if (! services) return;
-
-    service = OpenService(services, service_name, SC_MANAGER_ALL_ACCESS);
-    if (! service) return;
-  }
-
+void set_service_recovery(nssm_service_t *service) {
   SERVICE_FAILURE_ACTIONS_FLAG flag;
   ZeroMemory(&flag, sizeof(flag));
   flag.fFailureActionsOnNonCrashFailures = true;
 
   /* This functionality was added in Vista so the call may fail */
-  if (! ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
+  if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
     unsigned long error = GetLastError();
     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
     if (error != ERROR_INVALID_LEVEL) {
-      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CHANGESERVICECONFIG2_FAILED, service_name, error_string(error), 0);
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CHANGESERVICECONFIG2_FAILED, service->name, error_string(error), 0);
     }
   }
-
-  if (services) {
-    CloseServiceHandle(service);
-    CloseServiceHandle(services);
-  }
 }
 
-int monitor_service() {
+int monitor_service(nssm_service_t *service) {
   /* Set service status to started */
-  int ret = start_service();
+  int ret = start_service(service);
   if (ret) {
     char code[16];
     _snprintf_s(code, sizeof(code), _TRUNCATE, "%d", ret);
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, exe, service_name, ret, 0);
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
     return ret;
   }
-  log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, exe, flags, service_name, dir, 0);
+  log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
 
   /* Monitor service */
-  if (! RegisterWaitForSingleObject(&wait_handle, process_handle, end_service, (void *) pid, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
-    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service_name, exe, error_string(GetLastError()), 0);
+  if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
+    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
   }
 
   return 0;
@@ -343,6 +345,8 @@ void log_service_control(char *service_name, unsigned long control, bool handled
 
 /* Service control handler */
 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
+  nssm_service_t *service = (nssm_service_t *) context;
+
   switch (control) {
     case SERVICE_CONTROL_INTERROGATE:
       /* We always keep the service status up-to-date so this is a no-op. */
@@ -350,40 +354,40 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon
 
     case SERVICE_CONTROL_SHUTDOWN:
     case SERVICE_CONTROL_STOP:
-      log_service_control(service_name, control, true);
+      log_service_control(service->name, control, true);
       /*
         We MUST acknowledge the stop request promptly but we're committed to
         waiting for the application to exit.  Spawn a new thread to wait
         while we acknowledge the request.
       */
-      if (! CreateThread(NULL, 0, shutdown_service, (void *) service_name, 0, NULL)) {
+      if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
 
         /*
           We couldn't create a thread to tidy up so we'll have to force the tidyup
           to complete in time in this thread.
         */
-        kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
-        kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
-        kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
+        service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
+        service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
+        service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
 
-        stop_service(0, true, true);
+        stop_service(service, 0, true, true);
       }
       return NO_ERROR;
 
     case SERVICE_CONTROL_CONTINUE:
-      log_service_control(service_name, control, true);
-      throttle = 0;
-      if (use_critical_section) imports.WakeConditionVariable(&throttle_condition);
+      log_service_control(service->name, control, true);
+      service->throttle = 0;
+      if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
       else {
-        if (! throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
-        ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));
-        SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);
+        if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
+        ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
+        SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
       }
-      service_status.dwCurrentState = SERVICE_CONTINUE_PENDING;
-      service_status.dwWaitHint = throttle_milliseconds() + NSSM_WAITHINT_MARGIN;
-      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service_name, 0);
-      SetServiceStatus(service_handle, &service_status);
+      service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
+      service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
+      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
+      SetServiceStatus(service->status_handle, &service->status);
       return NO_ERROR;
 
     case SERVICE_CONTROL_PAUSE:
@@ -391,21 +395,21 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon
         We don't accept pause messages but it isn't possible to register
         only for continue messages so we have to handle this case.
       */
-      log_service_control(service_name, control, false);
+      log_service_control(service->name, control, false);
       return ERROR_CALL_NOT_IMPLEMENTED;
   }
 
   /* Unknown control */
-  log_service_control(service_name, control, false);
+  log_service_control(service->name, control, false);
   return ERROR_CALL_NOT_IMPLEMENTED;
 }
 
 /* Start the service */
-int start_service() {
-  stopping = false;
-  allow_restart = true;
+int start_service(nssm_service_t *service) {
+  service->stopping = false;
+  service->allow_restart = true;
 
-  if (process_handle) return 0;
+  if (service->process_handle) return 0;
 
   /* Allocate a STARTUPINFO structure for a new process */
   STARTUPINFO si;
@@ -417,36 +421,35 @@ int start_service() {
   ZeroMemory(&pi, sizeof(pi));
 
   /* Get startup parameters */
-  char *env = 0;
-  int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay, &stop_method, &kill_console_delay, &kill_window_delay, &kill_threads_delay, &si);
+  int ret = get_parameters(service, &si);
   if (ret) {
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0);
-    return stop_service(2, true, true);
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
+    return stop_service(service, 2, true, true);
   }
 
   /* Launch executable with arguments */
   char cmd[CMD_LENGTH];
-  if (_snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "\"%s\" %s", exe, flags) < 0) {
+  if (_snprintf_s(cmd, sizeof(cmd), _TRUNCATE, "\"%s\" %s", service->exe, service->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);
+    return stop_service(service, 2, true, true);
   }
 
-  throttle_restart();
+  throttle_restart(service);
 
   bool inherit_handles = false;
   if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
-  if (! CreateProcess(0, cmd, 0, 0, inherit_handles, 0, env, dir, &si, &pi)) {
+  if (! CreateProcess(0, cmd, 0, 0, inherit_handles, 0, service->env, service->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);
+    if (error == ERROR_INVALID_PARAMETER && service->env) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);
+    else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
     close_output_handles(&si);
-    return stop_service(3, true, true);
+    return stop_service(service, 3, true, true);
   }
-  process_handle = pi.hProcess;
-  pid = pi.dwProcessId;
+  service->process_handle = pi.hProcess;
+  service->pid = pi.dwProcessId;
 
-  if (get_process_creation_time(process_handle, &creation_time)) ZeroMemory(&creation_time, sizeof(creation_time));
+  if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
 
   close_output_handles(&si);
 
@@ -455,71 +458,74 @@ int start_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.
   */
-  unsigned long delay = throttle_delay;
+  unsigned long delay = service->throttle_delay;
   if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
     char delay_milliseconds[16];
     _snprintf_s(delay_milliseconds, sizeof(delay_milliseconds), _TRUNCATE, "%lu", delay);
     char deadline_milliseconds[16];
     _snprintf_s(deadline_milliseconds, sizeof(deadline_milliseconds), _TRUNCATE, "%lu", NSSM_SERVICE_STATUS_DEADLINE);
-    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service_name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
+    log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
     delay = NSSM_SERVICE_STATUS_DEADLINE;
   }
-  unsigned long deadline = WaitForSingleObject(process_handle, delay);
+  unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
 
   /* Signal successful start */
-  service_status.dwCurrentState = SERVICE_RUNNING;
-  SetServiceStatus(service_handle, &service_status);
+  service->status.dwCurrentState = SERVICE_RUNNING;
+  SetServiceStatus(service->status_handle, &service->status);
 
   /* Continue waiting for a clean startup. */
   if (deadline == WAIT_TIMEOUT) {
-    if (throttle_delay > delay) {
-      if (WaitForSingleObject(process_handle, throttle_delay - delay) == WAIT_TIMEOUT) throttle = 0;
+    if (service->throttle_delay > delay) {
+      if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
     }
-    else throttle = 0;
+    else service->throttle = 0;
   }
 
   return 0;
 }
 
 /* Stop the service */
-int stop_service(unsigned long exitcode, bool graceful, bool default_action) {
-  allow_restart = false;
-  if (wait_handle) UnregisterWait(wait_handle);
+int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
+  service->allow_restart = false;
+  if (service->wait_handle) {
+    UnregisterWait(service->wait_handle);
+    service->wait_handle = 0;
+  }
 
   if (default_action && ! exitcode && ! graceful) {
-    log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_GRACEFUL_SUICIDE, service_name, exe, exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_REALLY] ,0);
+    log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_GRACEFUL_SUICIDE, service->name, service->exe, exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_UNCLEAN], exit_action_strings[NSSM_EXIT_REALLY] ,0);
     graceful = true;
   }
 
   /* Signal we are stopping */
   if (graceful) {
-    service_status.dwCurrentState = SERVICE_STOP_PENDING;
-    service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;
-    SetServiceStatus(service_handle, &service_status);
+    service->status.dwCurrentState = SERVICE_STOP_PENDING;
+    service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
+    SetServiceStatus(service->status_handle, &service->status);
   }
 
   /* Nothing to do if service isn't running */
-  if (pid) {
+  if (service->pid) {
     /* Shut down service */
-    log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);
-    kill_process(service_name, service_handle, &service_status, stop_method, process_handle, pid, 0);
+    log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
+    kill_process(service, service->process_handle, service->pid, 0);
   }
-  else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);
+  else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
 
-  end_service((void *) pid, true);
+  end_service((void *) service, true);
 
   /* Signal we stopped */
   if (graceful) {
-    service_status.dwCurrentState = SERVICE_STOPPED;
+    service->status.dwCurrentState = SERVICE_STOPPED;
     if (exitcode) {
-      service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
-      service_status.dwServiceSpecificExitCode = exitcode;
+      service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
+      service->status.dwServiceSpecificExitCode = exitcode;
     }
     else {
-      service_status.dwWin32ExitCode = NO_ERROR;
-      service_status.dwServiceSpecificExitCode = 0;
+      service->status.dwWin32ExitCode = NO_ERROR;
+      service->status.dwServiceSpecificExitCode = 0;
     }
-    SetServiceStatus(service_handle, &service_status);
+    SetServiceStatus(service->status_handle, &service->status);
   }
 
   return exitcode;
@@ -527,19 +533,21 @@ int stop_service(unsigned long exitcode, bool graceful, bool default_action) {
 
 /* Callback function triggered when the server exits */
 void CALLBACK end_service(void *arg, unsigned char why) {
-  if (stopping) return;
+  nssm_service_t *service = (nssm_service_t *) arg;
 
-  stopping = true;
+  if (service->stopping) return;
 
-  pid = (unsigned long) arg;
+  service->stopping = true;
 
   /* Check exit code */
   unsigned long exitcode = 0;
   char code[16];
-  FILETIME exit_time;
-  GetExitCodeProcess(process_handle, &exitcode);
-  if (exitcode == STILL_ACTIVE || get_process_exit_time(process_handle, &exit_time)) GetSystemTimeAsFileTime(&exit_time);
-  CloseHandle(process_handle);
+  GetExitCodeProcess(service->process_handle, &exitcode);
+  if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);
+  CloseHandle(service->process_handle);
+
+  service->process_handle = 0;
+  service->pid = 0;
 
   /*
     Log that the service ended BEFORE logging about killing the process
@@ -547,12 +555,12 @@ void CALLBACK end_service(void *arg, unsigned char why) {
   */
   if (! why) {
     _snprintf_s(code, sizeof(code), _TRUNCATE, "%lu", exitcode);
-    log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);
+    log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
   }
 
   /* Clean up. */
   if (exitcode == STILL_ACTIVE) exitcode = 0;
-  kill_process_tree(service_name, service_handle, &service_status, stop_method, pid, exitcode, pid, &creation_time, &exit_time);
+  kill_process_tree(service, service->pid, exitcode, service->pid);
 
   /*
     The why argument is true if our wait timed out or false otherwise.
@@ -561,13 +569,13 @@ void CALLBACK end_service(void *arg, unsigned char why) {
     this is a controlled shutdown, and don't take any restart action.
   */
   if (why) return;
-  if (! allow_restart) return;
+  if (! service->allow_restart) return;
 
   /* What action should we take? */
   int action = NSSM_EXIT_RESTART;
   unsigned char action_string[ACTION_LEN];
   bool default_action;
-  if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {
+  if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
     for (int i = 0; exit_action_strings[i]; i++) {
       if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {
         action = i;
@@ -576,69 +584,67 @@ void CALLBACK end_service(void *arg, unsigned char why) {
     }
   }
 
-  process_handle = 0;
-  pid = 0;
   switch (action) {
     /* Try to restart the service or return failure code to service manager */
     case NSSM_EXIT_RESTART:
-      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);
-      while (monitor_service()) {
-        log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);
+      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
+      while (monitor_service(service)) {
+        log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
         Sleep(30000);
       }
     break;
 
     /* Do nothing, just like srvany would */
     case NSSM_EXIT_IGNORE:
-      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);
+      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
       Sleep(INFINITE);
     break;
 
     /* Tell the service manager we are finished */
     case NSSM_EXIT_REALLY:
-      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);
-      stop_service(exitcode, true, default_action);
+      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
+      stop_service(service, exitcode, true, default_action);
     break;
 
     /* Fake a crash so pre-Vista service managers will run recovery actions. */
     case NSSM_EXIT_UNCLEAN:
-      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);
-      stop_service(exitcode, false, default_action);
+      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
+      stop_service(service, exitcode, false, default_action);
       free_imports();
       exit(exitcode);
     break;
   }
 }
 
-void throttle_restart() {
+void throttle_restart(nssm_service_t *service) {
   /* This can't be a restart if the service is already running. */
-  if (! throttle++) return;
+  if (! service->throttle++) return;
 
-  int ms = throttle_milliseconds();
+  int ms = throttle_milliseconds(service->throttle);
 
-  if (throttle > 7) throttle = 8;
+  if (service->throttle > 7) service->throttle = 8;
 
   char threshold[8], milliseconds[8];
-  _snprintf_s(threshold, sizeof(threshold), _TRUNCATE, "%lu", throttle_delay);
+  _snprintf_s(threshold, sizeof(threshold), _TRUNCATE, "%lu", service->throttle_delay);
   _snprintf_s(milliseconds, sizeof(milliseconds), _TRUNCATE, "%lu", ms);
-  log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);
+  log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
 
-  if (use_critical_section) EnterCriticalSection(&throttle_section);
-  else if (throttle_timer) {
-    ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));
-    throttle_duetime.QuadPart = 0 - (ms * 10000LL);
-    SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);
+  if (use_critical_section) EnterCriticalSection(&service->throttle_section);
+  else if (service->throttle_timer) {
+    ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
+    service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
+    SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
   }
 
-  service_status.dwCurrentState = SERVICE_PAUSED;
-  SetServiceStatus(service_handle, &service_status);
+  service->status.dwCurrentState = SERVICE_PAUSED;
+  SetServiceStatus(service->status_handle, &service->status);
 
   if (use_critical_section) {
-    imports.SleepConditionVariableCS(&throttle_condition, &throttle_section, ms);
-    LeaveCriticalSection(&throttle_section);
+    imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
+    LeaveCriticalSection(&service->throttle_section);
   }
   else {
-    if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);
+    if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
     else Sleep(ms);
   }
 }
@@ -671,7 +677,7 @@ void throttle_restart() {
            0 if the wait completed.
           -1 on error.
 */
-int await_shutdown(char *function_name, char *service_name, SERVICE_STATUS_HANDLE service_handle, SERVICE_STATUS *service_status, HANDLE process_handle, unsigned long timeout) {
+int await_shutdown(nssm_service_t *service, char *function_name, unsigned long timeout) {
   unsigned long interval;
   unsigned long waithint;
   unsigned long ret;
@@ -690,24 +696,24 @@ int await_shutdown(char *function_name, char *service_name, SERVICE_STATUS_HANDL
 
   _snprintf_s(timeout_milliseconds, sizeof(timeout_milliseconds), _TRUNCATE, "%lu", timeout);
 
-  waithint = service_status->dwWaitHint;
+  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 = SERVICE_STOP_PENDING;
-    service_status->dwWaitHint += interval;
-    service_status->dwCheckPoint++;
-    SetServiceStatus(service_handle, service_status);
+    service->status.dwCurrentState = SERVICE_STOP_PENDING;
+    service->status.dwWaitHint += interval;
+    service->status.dwCheckPoint++;
+    SetServiceStatus(service->status_handle, &service->status);
 
     if (waited) {
       _snprintf_s(waited_milliseconds, sizeof(waited_milliseconds), _TRUNCATE, "%lu", waited);
       _snprintf_s(interval_milliseconds, sizeof(interval_milliseconds), _TRUNCATE, "%lu", interval);
-      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service_name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
+      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
     }
 
-    switch (WaitForSingleObject(process_handle, interval)) {
+    switch (WaitForSingleObject(service->process_handle, interval)) {
       case WAIT_OBJECT_0:
         ret = 0;
         goto awaited;

+ 52 - 8
service.h

@@ -1,24 +1,68 @@
 #ifndef SERVICE_H
 #define SERVICE_H
 
+/*
+  MSDN says the commandline in CreateProcess() is limited to 32768 characters
+  and the application name to MAX_PATH.
+  A registry key is limited to 255 characters.
+  A registry value is limited to 16383 characters.
+  Therefore we limit the service name to accommodate the path under HKLM.
+*/
+#define EXE_LENGTH MAX_PATH
+#define CMD_LENGTH 32768
+#define KEY_LENGTH 255
+#define VALUE_LENGTH 16383
+#define SERVICE_NAME_LENGTH KEY_LENGTH - 55
+
 #define ACTION_LEN 16
 
+typedef struct {
+  char name[SERVICE_NAME_LENGTH];
+  char exe[EXE_LENGTH];
+  char flags[VALUE_LENGTH];
+  char dir[MAX_PATH];
+  char *env;
+  unsigned long throttle_delay;
+  unsigned long stop_method;
+  unsigned long kill_console_delay;
+  unsigned long kill_window_delay;
+  unsigned long kill_threads_delay;
+  SC_HANDLE handle;
+  SERVICE_STATUS status;
+  SERVICE_STATUS_HANDLE status_handle;
+  HANDLE process_handle;
+  unsigned long pid;
+  HANDLE wait_handle;
+  bool stopping;
+  bool allow_restart;
+  unsigned long throttle;
+  CRITICAL_SECTION throttle_section;
+  bool throttle_section_initialised;
+  CONDITION_VARIABLE throttle_condition;
+  HANDLE throttle_timer;
+  LARGE_INTEGER throttle_duetime;
+  FILETIME creation_time;
+  FILETIME exit_time;
+} nssm_service_t;
+
 void WINAPI service_main(unsigned long, char **);
 char *service_control_text(unsigned long);
 void log_service_control(char *, unsigned long, bool);
 unsigned long WINAPI service_control_handler(unsigned long, unsigned long, void *, void *);
 
+nssm_service_t *alloc_nssm_service();
+void cleanup_nssm_service(nssm_service_t *);
 SC_HANDLE open_service_manager();
 int pre_install_service(int, char **);
 int pre_remove_service(int, char **);
-int install_service(char *, char *, char *);
-int remove_service(char *);
-void set_service_recovery(SC_HANDLE, char *);
-int monitor_service();
-int start_service();
-int stop_service(unsigned long, bool, bool);
+int install_service(nssm_service_t *);
+int remove_service(nssm_service_t *);
+void set_service_recovery(nssm_service_t *);
+int monitor_service(nssm_service_t *);
+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();
-int await_shutdown(char *, char *, SERVICE_STATUS_HANDLE, SERVICE_STATUS *, HANDLE, unsigned long);
+void throttle_restart(nssm_service_t *);
+int await_shutdown(nssm_service_t *, char *, unsigned long);
 
 #endif