Alternative open_registry_key() signature.
[nssm.git] / service.cpp
index 0a8773a..d864fa2 100644 (file)
@@ -10,6 +10,8 @@ const TCHAR *exit_action_strings[] = { _T("Restart"), _T("Ignore"), _T("Exit"),
 const TCHAR *startup_strings[] = { _T("SERVICE_AUTO_START"), _T("SERVICE_DELAYED_AUTO_START"), _T("SERVICE_DEMAND_START"), _T("SERVICE_DISABLED"), 0 };\r
 const TCHAR *priority_strings[] = { _T("REALTIME_PRIORITY_CLASS"), _T("HIGH_PRIORITY_CLASS"), _T("ABOVE_NORMAL_PRIORITY_CLASS"), _T("NORMAL_PRIORITY_CLASS"), _T("BELOW_NORMAL_PRIORITY_CLASS"), _T("IDLE_PRIORITY_CLASS"), 0 };\r
 \r
+static hook_thread_t hook_threads = { NULL, 0 };\r
+\r
 typedef struct {\r
   int first;\r
   int last;\r
@@ -38,6 +40,7 @@ static inline int service_control_response(unsigned long control, unsigned long
     case SERVICE_CONTROL_STOP:\r
     case SERVICE_CONTROL_SHUTDOWN:\r
       switch (status) {\r
+        case SERVICE_RUNNING:\r
         case SERVICE_STOP_PENDING:\r
           return 1;\r
 \r
@@ -73,6 +76,7 @@ static inline int service_control_response(unsigned long control, unsigned long
       }\r
 \r
     case SERVICE_CONTROL_INTERROGATE:\r
+    case NSSM_SERVICE_CONTROL_ROTATE:\r
       return 0;\r
   }\r
 \r
@@ -81,12 +85,17 @@ static inline int service_control_response(unsigned long control, unsigned long
 \r
 static inline int await_service_control_response(unsigned long control, SC_HANDLE service_handle, SERVICE_STATUS *service_status, unsigned long initial_status) {\r
   int tries = 0;\r
+  unsigned long checkpoint = 0;\r
+  unsigned long waithint = 0;\r
   while (QueryServiceStatus(service_handle, service_status)) {\r
     int response = service_control_response(control, service_status->dwCurrentState);\r
     /* Alas we can't WaitForSingleObject() on an SC_HANDLE. */\r
     if (! response) return response;\r
     if (response > 0 || service_status->dwCurrentState == initial_status) {\r
-      if (++tries > 10) return response;\r
+      if (service_status->dwCheckPoint != checkpoint || service_status->dwWaitHint != waithint) tries = 0;\r
+      checkpoint = service_status->dwCheckPoint;\r
+      waithint = service_status->dwWaitHint;\r
+      if (++tries > 10) tries = 10;\r
       Sleep(50 * tries);\r
     }\r
     else return response;\r
@@ -94,6 +103,25 @@ static inline int await_service_control_response(unsigned long control, SC_HANDL
   return -1;\r
 }\r
 \r
+static inline void wait_for_hooks(nssm_service_t *service, bool notify) {\r
+  SERVICE_STATUS_HANDLE status_handle;\r
+  SERVICE_STATUS *status;\r
+\r
+  /* On a clean shutdown we need to keep the service's status up-to-date. */\r
+  if (notify) {\r
+    status_handle = service->status_handle;\r
+    status = &service->status;\r
+  }\r
+  else {\r
+    status_handle = NULL;\r
+    status = NULL;\r
+  }\r
+\r
+  EnterCriticalSection(&service->hook_section);\r
+  await_hook_threads(&hook_threads, status_handle, status, NSSM_HOOK_THREAD_DEADLINE);\r
+  LeaveCriticalSection(&service->hook_section);\r
+}\r
+\r
 int affinity_mask_to_string(__int64 mask, TCHAR **string) {\r
   if (! string) return 1;\r
   if (! mask) {\r
@@ -212,7 +240,7 @@ int affinity_string_to_mask(TCHAR *string, __int64 *mask) {
   return 0;\r
 }\r
 \r
-inline unsigned long priority_mask() {\r
+unsigned long priority_mask() {\r
  return REALTIME_PRIORITY_CLASS | HIGH_PRIORITY_CLASS | ABOVE_NORMAL_PRIORITY_CLASS | NORMAL_PRIORITY_CLASS | BELOW_NORMAL_PRIORITY_CLASS | IDLE_PRIORITY_CLASS;\r
 }\r
 \r
@@ -239,11 +267,33 @@ unsigned long priority_index_to_constant(int index) {
 }\r
 \r
 static inline unsigned long throttle_milliseconds(unsigned long throttle) {\r
+  if (throttle > 7) throttle = 8;\r
   /* pow() operates on doubles. */\r
   unsigned long ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;\r
   return ret * 1000;\r
 }\r
 \r
+void set_service_environment(nssm_service_t *service) {\r
+  if (! service) return;\r
+\r
+  /*\r
+    We have to duplicate the block because this function will be called\r
+    multiple times between registry reads.\r
+  */\r
+  if (service->env) duplicate_environment_strings(service->env);\r
+  if (! service->env_extra) return;\r
+  TCHAR *env_extra = copy_environment_block(service->env_extra);\r
+  if (! env_extra) return;\r
+\r
+  set_environment_block(env_extra);\r
+  HeapFree(GetProcessHeap(), 0, env_extra);\r
+}\r
+\r
+void unset_service_environment(nssm_service_t *service) {\r
+  if (! service) return;\r
+  duplicate_environment_strings(service->initial_env);\r
+}\r
+\r
 /*\r
   Wrapper to be called in a new thread so that we can acknowledge a STOP\r
   control immediately.\r
@@ -252,6 +302,14 @@ static unsigned long WINAPI shutdown_service(void *arg) {
   return stop_service((nssm_service_t *) arg, 0, true, true);\r
 }\r
 \r
+/*\r
+ Wrapper to be called in a new thread so that we can acknowledge start\r
+ immediately.\r
+*/\r
+static unsigned long WINAPI launch_service(void *arg) {\r
+  return monitor_service((nssm_service_t *) arg);\r
+}\r
+\r
 /* Connect to the service manager */\r
 SC_HANDLE open_service_manager(unsigned long access) {\r
   SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, access);\r
@@ -715,7 +773,8 @@ void cleanup_nssm_service(nssm_service_t *service) {
   if (service->wait_handle) UnregisterWait(service->wait_handle);\r
   if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);\r
   if (service->throttle_timer) CloseHandle(service->throttle_timer);\r
-  if (service->initial_env) FreeEnvironmentStrings(service->initial_env);\r
+  if (service->hook_section_initialised) DeleteCriticalSection(&service->hook_section);\r
+  if (service->initial_env) HeapFree(GetProcessHeap(), 0, service->initial_env);\r
   HeapFree(GetProcessHeap(), 0, service);\r
 }\r
 \r
@@ -1067,7 +1126,7 @@ int install_service(nssm_service_t *service) {
   }\r
 \r
   /* Get path of this program */\r
-  GetModuleFileName(0, service->image, _countof(service->image));\r
+  _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), nssm_imagepath());\r
 \r
   /* Create the service - settings will be changed in edit_service() */\r
   service->handle = CreateService(services, service->name, service->name, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, service->image, 0, 0, 0, 0, 0);\r
@@ -1363,7 +1422,7 @@ void WINAPI service_main(unsigned long argc, TCHAR **argv) {
   /* Initialise status */\r
   ZeroMemory(&service->status, sizeof(service->status));\r
   service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
-  service->status.dwControlsAccepted = SERVICE_ACCEPT_POWEREVENT | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;\r
+  service->status.dwControlsAccepted = 0;\r
   service->status.dwWin32ExitCode = NO_ERROR;\r
   service->status.dwServiceSpecificExitCode = 0;\r
   service->status.dwCheckPoint = 0;\r
@@ -1395,6 +1454,10 @@ void WINAPI service_main(unsigned long argc, TCHAR **argv) {
       service->handle = open_service(services, service->name, SERVICE_CHANGE_CONFIG, 0, 0);\r
       set_service_recovery(service);\r
 \r
+      /* Remember our display name. */\r
+      unsigned long displayname_len = _countof(service->displayname);\r
+      GetServiceDisplayName(services, service->name, service->displayname, &displayname_len);\r
+\r
       CloseServiceHandle(services);\r
     }\r
   }\r
@@ -1411,10 +1474,21 @@ void WINAPI service_main(unsigned long argc, TCHAR **argv) {
     }\r
   }\r
 \r
+  /* Critical section for hooks. */\r
+  InitializeCriticalSection(&service->hook_section);\r
+  service->hook_section_initialised = true;\r
+\r
   /* Remember our initial environment. */\r
-  service->initial_env = GetEnvironmentStrings();\r
+  service->initial_env = copy_environment();\r
+\r
+  /* Remember our creation time. */\r
+  if (get_process_creation_time(GetCurrentProcess(), &service->nssm_creation_time)) ZeroMemory(&service->nssm_creation_time, sizeof(service->nssm_creation_time));\r
 \r
-  monitor_service(service);\r
+  service->allow_restart = true;\r
+  if (! CreateThread(NULL, 0, launch_service, (void *) service, 0, NULL)) {\r
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
+    stop_service(service, 0, true, true);\r
+  }\r
 }\r
 \r
 /* Make sure service recovery actions are taken where necessary */\r
@@ -1520,7 +1594,18 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon
 \r
     case SERVICE_CONTROL_SHUTDOWN:\r
     case SERVICE_CONTROL_STOP:\r
+      service->last_control = control;\r
       log_service_control(service->name, control, true);\r
+\r
+      /* Immediately block further controls. */\r
+      service->allow_restart = false;\r
+      service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
+      service->status.dwControlsAccepted = 0;\r
+      SetServiceStatus(service->status_handle, &service->status);\r
+\r
+      /* Pre-stop hook. */\r
+      nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_STOP, NSSM_HOOK_ACTION_PRE, &control, NSSM_SERVICE_STATUS_DEADLINE, false);\r
+\r
       /*\r
         We MUST acknowledge the stop request promptly but we're committed to\r
         waiting for the application to exit.  Spawn a new thread to wait\r
@@ -1542,6 +1627,7 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon
       return NO_ERROR;\r
 \r
     case SERVICE_CONTROL_CONTINUE:\r
+      service->last_control = control;\r
       log_service_control(service->name, control, true);\r
       service->throttle = 0;\r
       if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);\r
@@ -1566,18 +1652,31 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon
       return ERROR_CALL_NOT_IMPLEMENTED;\r
 \r
     case NSSM_SERVICE_CONTROL_ROTATE:\r
+      service->last_control = control;\r
       log_service_control(service->name, control, true);\r
+      (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_ROTATE, NSSM_HOOK_ACTION_PRE, &control, NSSM_HOOK_DEADLINE, false);\r
       if (service->rotate_stdout_online == NSSM_ROTATE_ONLINE) service->rotate_stdout_online = NSSM_ROTATE_ONLINE_ASAP;\r
       if (service->rotate_stderr_online == NSSM_ROTATE_ONLINE) service->rotate_stderr_online = NSSM_ROTATE_ONLINE_ASAP;\r
+      (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_ROTATE, NSSM_HOOK_ACTION_POST, &control);\r
       return NO_ERROR;\r
 \r
     case SERVICE_CONTROL_POWEREVENT:\r
-      if (event != PBT_APMRESUMEAUTOMATIC) {\r
-        log_service_control(service->name, control, false);\r
+      /* Resume from suspend. */\r
+      if (event == PBT_APMRESUMEAUTOMATIC) {\r
+        service->last_control = control;\r
+        log_service_control(service->name, control, true);\r
+        (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_POWER, NSSM_HOOK_ACTION_RESUME, &control);\r
         return NO_ERROR;\r
       }\r
-      log_service_control(service->name, control, true);\r
-      end_service((void *) service, false);\r
+\r
+      /* Battery low or changed to A/C power or something. */\r
+      if (event == PBT_APMPOWERSTATUSCHANGE) {\r
+        service->last_control = control;\r
+        log_service_control(service->name, control, true);\r
+        (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_POWER, NSSM_HOOK_ACTION_CHANGE, &control);\r
+        return NO_ERROR;\r
+      }\r
+      log_service_control(service->name, control, false);\r
       return NO_ERROR;\r
   }\r
 \r
@@ -1589,9 +1688,9 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon
 /* Start the service */\r
 int start_service(nssm_service_t *service) {\r
   service->stopping = false;\r
-  service->allow_restart = true;\r
 \r
   if (service->process_handle) return 0;\r
+  service->start_requested_count++;\r
 \r
   /* Allocate a STARTUPINFO structure for a new process */\r
   STARTUPINFO si;\r
@@ -1606,6 +1705,7 @@ int start_service(nssm_service_t *service) {
   int ret = get_parameters(service, &si);\r
   if (ret) {\r
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);\r
+    unset_service_environment(service);\r
     return stop_service(service, 2, true, true);\r
   }\r
 \r
@@ -1613,94 +1713,113 @@ int start_service(nssm_service_t *service) {
   TCHAR cmd[CMD_LENGTH];\r
   if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {\r
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);\r
+    unset_service_environment(service);\r
     return stop_service(service, 2, true, true);\r
   }\r
 \r
   throttle_restart(service);\r
 \r
-  /* Set the environment. */\r
-  if (service->env) duplicate_environment(service->env);\r
-  if (service->env_extra) set_environment_block(service->env_extra);\r
+  service->status.dwCurrentState = SERVICE_START_PENDING;\r
+  service->status.dwControlsAccepted = SERVICE_ACCEPT_POWEREVENT | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;\r
+  SetServiceStatus(service->status_handle, &service->status);\r
 \r
-  /* Set up I/O redirection. */\r
-  if (get_output_handles(service, &si)) {\r
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);\r
-    if (! service->no_console) FreeConsole();\r
-    close_output_handles(&si);\r
-    return stop_service(service, 4, true, true);\r
-  }\r
+  /* Pre-start hook. */\r
+  unsigned long control = NSSM_SERVICE_CONTROL_START;\r
+  if (nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE, &control, NSSM_SERVICE_STATUS_DEADLINE, false) == NSSM_HOOK_STATUS_ABORT) {\r
+    TCHAR code[16];\r
+    _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), NSSM_HOOK_STATUS_ABORT);\r
+    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PRESTART_HOOK_ABORT, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE, service->name, code, 0);\r
+    unset_service_environment(service);\r
+    return stop_service(service, 5, true, true);\r
+  }\r
+\r
+  /* Did another thread receive a stop control? */\r
+  if (service->allow_restart) {\r
+    /* Set up I/O redirection. */\r
+    if (get_output_handles(service, &si)) {\r
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);\r
+      if (! service->no_console) FreeConsole();\r
+      close_output_handles(&si);\r
+      unset_service_environment(service);\r
+      return stop_service(service, 4, true, true);\r
+    }\r
 \r
-  bool inherit_handles = false;\r
-  if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
-  unsigned long flags = service->priority & priority_mask();\r
-  if (service->affinity) flags |= CREATE_SUSPENDED;\r
-  if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, 0, service->dir, &si, &pi)) {\r
-    unsigned long exitcode = 3;\r
-    unsigned long error = GetLastError();\r
-    log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
-    close_output_handles(&si);\r
-    duplicate_environment_strings(service->initial_env);\r
-    return stop_service(service, exitcode, true, true);\r
-  }\r
-  service->process_handle = pi.hProcess;\r
-  service->pid = pi.dwProcessId;\r
+    /* The pre-start hook will have cleaned the environment. */\r
+    set_service_environment(service);\r
+\r
+    bool inherit_handles = false;\r
+    if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
+    unsigned long flags = service->priority & priority_mask();\r
+    if (service->affinity) flags |= CREATE_SUSPENDED;\r
+    if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, 0, service->dir, &si, &pi)) {\r
+      unsigned long exitcode = 3;\r
+      unsigned long error = GetLastError();\r
+      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
+      close_output_handles(&si);\r
+      unset_service_environment(service);\r
+      return stop_service(service, exitcode, true, true);\r
+    }\r
+    service->start_count++;\r
+    service->process_handle = pi.hProcess;\r
+    service->pid = pi.dwProcessId;\r
 \r
-  if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
+    if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
 \r
-  close_output_handles(&si);\r
+    close_output_handles(&si);\r
 \r
-  if (! service->no_console) FreeConsole();\r
+    if (! service->no_console) FreeConsole();\r
 \r
-  /* Restore our environment. */\r
-  duplicate_environment_strings(service->initial_env);\r
+    if (service->affinity) {\r
+      /*\r
+        We are explicitly storing service->affinity as a 64-bit unsigned integer\r
+        so that we can parse it regardless of whether we're running in 32-bit\r
+        or 64-bit mode.  The arguments to SetProcessAffinityMask(), however, are\r
+        defined as type DWORD_PTR and hence limited to 32 bits on a 32-bit system\r
+        (or when running the 32-bit NSSM).\r
+\r
+        The result is a lot of seemingly-unnecessary casting throughout the code\r
+        and potentially confusion when we actually try to start the service.\r
+        Having said that, however, it's unlikely that we're actually going to\r
+        run in 32-bit mode on a system which has more than 32 CPUs so the\r
+        likelihood of seeing a confusing situation is somewhat diminished.\r
+      */\r
+      DWORD_PTR affinity, system_affinity;\r
 \r
-  if (service->affinity) {\r
-    /*\r
-      We are explicitly storing service->affinity as a 64-bit unsigned integer\r
-      so that we can parse it regardless of whether we're running in 32-bit\r
-      or 64-bit mode.  The arguments to SetProcessAffinityMask(), however, are\r
-      defined as type DWORD_PTR and hence limited to 32 bits on a 32-bit system\r
-      (or when running the 32-bit NSSM).\r
-\r
-      The result is a lot of seemingly-unnecessary casting throughout the code\r
-      and potentially confusion when we actually try to start the service.\r
-      Having said that, however, it's unlikely that we're actually going to\r
-      run in 32-bit mode on a system which has more than 32 CPUs so the\r
-      likelihood of seeing a confusing situation is somewhat diminished.\r
-    */\r
-    DWORD_PTR affinity, system_affinity;\r
+      if (GetProcessAffinityMask(service->process_handle, &affinity, &system_affinity)) affinity = service->affinity & system_affinity;\r
+      else {\r
+        affinity = (DWORD_PTR) service->affinity;\r
+        log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
+      }\r
 \r
-    if (GetProcessAffinityMask(service->process_handle, &affinity, &system_affinity)) affinity = service->affinity & system_affinity;\r
-    else {\r
-      affinity = (DWORD_PTR) service->affinity;\r
-      log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
-    }\r
+      if (! SetProcessAffinityMask(service->process_handle, affinity)) {\r
+        log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
+      }\r
 \r
-    if (! SetProcessAffinityMask(service->process_handle, affinity)) {\r
-      log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
+      ResumeThread(pi.hThread);\r
     }\r
-\r
-    ResumeThread(pi.hThread);\r
   }\r
 \r
+  /* Restore our environment. */\r
+  unset_service_environment(service);\r
+\r
   /*\r
     Wait for a clean startup before changing the service status to RUNNING\r
     but be mindful of the fact that we are blocking the service control manager\r
     so abandon the wait before too much time has elapsed.\r
   */\r
-  service->status.dwCurrentState = SERVICE_START_PENDING;\r
   if (await_single_handle(service->status_handle, &service->status, service->process_handle, service->name, _T("start_service"), service->throttle_delay) == 1) service->throttle = 0;\r
 \r
+  /* Did another thread receive a stop control? */\r
+  if (! service->allow_restart) return 0;\r
+\r
   /* Signal successful start */\r
   service->status.dwCurrentState = SERVICE_RUNNING;\r
+  service->status.dwControlsAccepted &= ~SERVICE_ACCEPT_PAUSE_CONTINUE;\r
   SetServiceStatus(service->status_handle, &service->status);\r
 \r
-  /* Continue waiting for a clean startup. */\r
-  if (deadline == WAIT_TIMEOUT) {\r
-    if (service->throttle_delay > delay) {\r
-      if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;\r
-    }\r
-    else service->throttle = 0;\r
+  /* Post-start hook. */\r
+  if (! service->throttle) {\r
+    (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_POST, &control);\r
   }\r
 \r
   /* Ensure the restart delay is always applied. */\r
@@ -1746,6 +1865,8 @@ int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful,
 \r
   /* Signal we stopped */\r
   if (graceful) {\r
+    service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
+    wait_for_hooks(service, true);\r
     service->status.dwCurrentState = SERVICE_STOPPED;\r
     if (exitcode) {\r
       service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
@@ -1779,6 +1900,7 @@ void CALLBACK end_service(void *arg, unsigned char why) {
   TCHAR code[16];\r
   if (service->process_handle) {\r
     GetExitCodeProcess(service->process_handle, &exitcode);\r
+    service->exitcode = exitcode;\r
     /* Check real exit time. */\r
     if (exitcode != STILL_ACTIVE) get_process_exit_time(service->process_handle, &service->exit_time);\r
     CloseHandle(service->process_handle);\r
@@ -1804,6 +1926,10 @@ void CALLBACK end_service(void *arg, unsigned char why) {
   }\r
   service->pid = 0;\r
 \r
+  /* Exit hook. */\r
+  service->exit_count++;\r
+  (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_EXIT, NSSM_HOOK_ACTION_POST, NULL, NSSM_HOOK_DEADLINE, true);\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
@@ -1839,6 +1965,7 @@ void CALLBACK end_service(void *arg, unsigned char why) {
     /* Do nothing, just like srvany would */\r
     case NSSM_EXIT_IGNORE:\r
       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
+      wait_for_hooks(service, false);\r
       Sleep(INFINITE);\r
     break;\r
 \r
@@ -1852,6 +1979,7 @@ void CALLBACK end_service(void *arg, unsigned char why) {
     case NSSM_EXIT_UNCLEAN:\r
       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
       stop_service(service, exitcode, false, default_action);\r
+      wait_for_hooks(service, false);\r
       free_imports();\r
       exit(exitcode);\r
     break;\r
@@ -1869,8 +1997,6 @@ void throttle_restart(nssm_service_t *service) {
   if (service->restart_delay > throttle_ms) ms = service->restart_delay;\r
   else ms = throttle_ms;\r
 \r
-  if (service->throttle > 7) service->throttle = 8;\r
-\r
   _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
 \r
   if (service->throttle == 1 && service->restart_delay > throttle_ms) log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESTART_DELAY, service->name, milliseconds, 0);\r
@@ -1887,6 +2013,7 @@ void throttle_restart(nssm_service_t *service) {
   }\r
 \r
   service->status.dwCurrentState = SERVICE_PAUSED;\r
+  service->status.dwControlsAccepted |= SERVICE_ACCEPT_PAUSE_CONTINUE;\r
   SetServiceStatus(service->status_handle, &service->status);\r
 \r
   if (use_critical_section) {\r