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
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
}\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
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->hook_section_initialised) DeleteCriticalSection(&service->hook_section);\r
if (service->initial_env) FreeEnvironmentStrings(service->initial_env);\r
HeapFree(GetProcessHeap(), 0, service);\r
}\r
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
}\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
\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
}\r
\r
\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
+ /* Pre-stop hook. */\r
+ service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
+ SetServiceStatus(service->status_handle, &service->status);\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
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
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
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
if (service->env) duplicate_environment(service->env);\r
if (service->env_extra) set_environment_block(service->env_extra);\r
\r
+ /* Pre-start hook. */\r
+ unsigned long control = NSSM_SERVICE_CONTROL_START;\r
+ service->status.dwCurrentState = SERVICE_START_PENDING;\r
+ service->status.dwControlsAccepted = SERVICE_ACCEPT_POWEREVENT | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;\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
+ return stop_service(service, 5, true, true);\r
+ }\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
duplicate_environment_strings(service->initial_env);\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
so abandon the wait before too much time has elapsed.\r
*/\r
service->status.dwCurrentState = SERVICE_START_PENDING;\r
- service->status.dwControlsAccepted = SERVICE_ACCEPT_POWEREVENT | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;\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
/* Signal successful start */\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
\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
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
}\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
/* 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
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
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