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
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
}\r
\r
case SERVICE_CONTROL_INTERROGATE:\r
+ case NSSM_SERVICE_CONTROL_ROTATE:\r
return 0;\r
}\r
\r
\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
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
service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
+ service->kill_process_tree = 1;\r
}\r
\r
/* Allocate and zero memory for a service. */\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
/* 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
if (services) {\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
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
- unsigned long delay = service->throttle_delay;\r
- if (delay > NSSM_SERVICE_STATUS_DEADLINE) {\r
- TCHAR delay_milliseconds[16];\r
- _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);\r
- TCHAR deadline_milliseconds[16];\r
- _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);\r
- log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);\r
- delay = NSSM_SERVICE_STATUS_DEADLINE;\r
- }\r
- unsigned long deadline = WaitForSingleObject(service->process_handle, delay);\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
/* 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
if (graceful) {\r
service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
- SetServiceStatus(service->status_handle, &service->status);\r
}\r
+ service->status.dwControlsAccepted = 0;\r
+ SetServiceStatus(service->status_handle, &service->status);\r
\r
/* Nothing to do if service isn't running */\r
if (service->pid) {\r
/* Shut down service */\r
log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);\r
- kill_process(service, service->process_handle, service->pid, 0);\r
+ kill_t k;\r
+ service_kill_t(service, &k);\r
+ k.exitcode = 0;\r
+ kill_process(&k);\r
}\r
else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);\r
\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
/* Clean up. */\r
if (exitcode == STILL_ACTIVE) exitcode = 0;\r
- if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);\r
+ if (service->pid && service->kill_process_tree) {\r
+ kill_t k;\r
+ service_kill_t(service, &k);\r
+ kill_process_tree(&k, service->pid);\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
}\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
\r
Only doing both these things will prevent the system from killing the service.\r
\r
+ If the status_handle and service_status arguments are omitted, this function\r
+ will not try to update the service manager but it will still log to the\r
+ event log that it is waiting for a handle.\r
+\r
Returns: 1 if the wait timed out.\r
0 if the wait completed.\r
-1 on error.\r
*/\r
-int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {\r
+int await_single_handle(SERVICE_STATUS_HANDLE status_handle, SERVICE_STATUS *status, HANDLE handle, TCHAR *name, TCHAR *function_name, unsigned long timeout) {\r
unsigned long interval;\r
- unsigned long waithint;\r
unsigned long ret;\r
unsigned long waited;\r
TCHAR interval_milliseconds[16];\r
\r
_sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);\r
\r
- waithint = service->status.dwWaitHint;\r
waited = 0;\r
while (waited < timeout) {\r
interval = timeout - waited;\r
if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
\r
- service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
- service->status.dwWaitHint += interval;\r
- service->status.dwCheckPoint++;\r
- SetServiceStatus(service->status_handle, &service->status);\r
+ if (status) {\r
+ status->dwWaitHint += interval;\r
+ status->dwCheckPoint++;\r
+ SetServiceStatus(status_handle, status);\r
+ }\r
\r
if (waited) {\r
_sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);\r
_sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);\r
- log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
+ log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SINGLE_HANDLE, function, name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
}\r
\r
- switch (WaitForSingleObject(service->process_handle, interval)) {\r
+ switch (WaitForSingleObject(handle, interval)) {\r
case WAIT_OBJECT_0:\r
ret = 0;\r
goto awaited;\r
\r
case WAIT_TIMEOUT:\r
ret = 1;\r
- break;\r
+ break;\r
\r
default:\r
ret = -1;\r