4 bool use_critical_section;
\r
6 extern imports_t imports;
\r
8 const TCHAR *exit_action_strings[] = { _T("Restart"), _T("Ignore"), _T("Exit"), _T("Suicide"), 0 };
\r
10 static inline int throttle_milliseconds(unsigned long throttle) {
\r
11 /* pow() operates on doubles. */
\r
12 int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;
\r
17 Wrapper to be called in a new thread so that we can acknowledge a STOP
\r
18 control immediately.
\r
20 static unsigned long WINAPI shutdown_service(void *arg) {
\r
21 return stop_service((nssm_service_t *) arg, 0, true, true);
\r
24 /* Connect to the service manager */
\r
25 SC_HANDLE open_service_manager() {
\r
26 SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
\r
28 if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);
\r
35 /* Set default values which aren't zero. */
\r
36 void set_nssm_service_defaults(nssm_service_t *service) {
\r
37 if (! service) return;
\r
39 service->type = SERVICE_WIN32_OWN_PROCESS;
\r
40 service->stdin_sharing = NSSM_STDIN_SHARING;
\r
41 service->stdin_disposition = NSSM_STDIN_DISPOSITION;
\r
42 service->stdin_flags = NSSM_STDIN_FLAGS;
\r
43 service->stdout_sharing = NSSM_STDOUT_SHARING;
\r
44 service->stdout_disposition = NSSM_STDOUT_DISPOSITION;
\r
45 service->stdout_flags = NSSM_STDOUT_FLAGS;
\r
46 service->stderr_sharing = NSSM_STDERR_SHARING;
\r
47 service->stderr_disposition = NSSM_STDERR_DISPOSITION;
\r
48 service->stderr_flags = NSSM_STDERR_FLAGS;
\r
49 service->throttle_delay = NSSM_RESET_THROTTLE_RESTART;
\r
50 service->stop_method = ~0;
\r
51 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
52 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
53 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
56 /* Allocate and zero memory for a service. */
\r
57 nssm_service_t *alloc_nssm_service() {
\r
58 nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));
\r
59 if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("alloc_nssm_service()"), 0);
\r
63 /* Free memory for a service. */
\r
64 void cleanup_nssm_service(nssm_service_t *service) {
\r
65 if (! service) return;
\r
66 if (service->username) HeapFree(GetProcessHeap(), 0, service->username);
\r
67 if (service->password) {
\r
68 SecureZeroMemory(service->password, service->passwordlen);
\r
69 HeapFree(GetProcessHeap(), 0, service->password);
\r
71 if (service->env) HeapFree(GetProcessHeap(), 0, service->env);
\r
72 if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);
\r
73 if (service->handle) CloseServiceHandle(service->handle);
\r
74 if (service->process_handle) CloseHandle(service->process_handle);
\r
75 if (service->wait_handle) UnregisterWait(service->process_handle);
\r
76 if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);
\r
77 if (service->throttle_timer) CloseHandle(service->throttle_timer);
\r
78 HeapFree(GetProcessHeap(), 0, service);
\r
81 /* About to install the service */
\r
82 int pre_install_service(int argc, TCHAR **argv) {
\r
83 nssm_service_t *service = alloc_nssm_service();
\r
84 set_nssm_service_defaults(service);
\r
85 if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
87 /* Show the dialogue box if we didn't give the service name and path */
\r
88 if (argc < 2) return nssm_gui(IDD_INSTALL, service);
\r
91 print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("pre_install_service()"));
\r
94 _sntprintf_s(service->exe, _countof(service->exe), _TRUNCATE, _T("%s"), argv[1]);
\r
96 /* Arguments are optional */
\r
97 size_t flagslen = 0;
\r
100 for (i = 2; i < argc; i++) flagslen += _tcslen(argv[i]) + 1;
\r
101 if (! flagslen) flagslen = 1;
\r
102 if (flagslen > _countof(service->flags)) {
\r
103 print_message(stderr, NSSM_MESSAGE_FLAGS_TOO_LONG);
\r
107 for (i = 2; i < argc; i++) {
\r
108 size_t len = _tcslen(argv[i]);
\r
109 memmove(service->flags + s, argv[i], len * sizeof(TCHAR));
\r
111 if (i < argc - 1) service->flags[s++] = _T(' ');
\r
114 /* Work out directory name */
\r
115 _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);
\r
116 strip_basename(service->dir);
\r
118 int ret = install_service(service);
\r
119 cleanup_nssm_service(service);
\r
123 /* About to remove the service */
\r
124 int pre_remove_service(int argc, TCHAR **argv) {
\r
125 nssm_service_t *service = alloc_nssm_service();
\r
126 set_nssm_service_defaults(service);
\r
127 if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
129 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
130 if (argc < 2) return nssm_gui(IDD_REMOVE, service);
\r
131 if (str_equiv(argv[1], _T("confirm"))) {
\r
132 int ret = remove_service(service);
\r
133 cleanup_nssm_service(service);
\r
136 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
140 /* Install the service */
\r
141 int install_service(nssm_service_t *service) {
\r
142 if (! service) return 1;
\r
144 /* Open service manager */
\r
145 SC_HANDLE services = open_service_manager();
\r
147 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
148 cleanup_nssm_service(service);
\r
152 /* Get path of this program */
\r
153 TCHAR command[MAX_PATH];
\r
154 GetModuleFileName(0, command, _countof(command));
\r
157 The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS
\r
158 and SERVICE_INTERACTIVE_PROCESS.
\r
160 service->type &= SERVICE_INTERACTIVE_PROCESS;
\r
161 service->type |= SERVICE_WIN32_OWN_PROCESS;
\r
163 /* Startup type. */
\r
164 unsigned long startup;
\r
165 switch (service->startup) {
\r
166 case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;
\r
167 case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;
\r
168 default: startup = SERVICE_AUTO_START;
\r
171 /* Display name. */
\r
172 if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);
\r
174 /* Create the service */
\r
175 service->handle = CreateService(services, service->name, service->displayname, SC_MANAGER_ALL_ACCESS, service->type, startup, SERVICE_ERROR_NORMAL, command, 0, 0, 0, service->username, service->password);
\r
176 if (! service->handle) {
\r
177 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
178 CloseServiceHandle(services);
\r
182 if (service->description[0]) {
\r
183 SERVICE_DESCRIPTION description;
\r
184 ZeroMemory(&description, sizeof(description));
\r
185 description.lpDescription = service->description;
\r
186 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, &description)) {
\r
187 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service->name, error_string(GetLastError()), 0);
\r
191 if (service->startup == NSSM_STARTUP_DELAYED) {
\r
192 SERVICE_DELAYED_AUTO_START_INFO delayed;
\r
193 ZeroMemory(&delayed, sizeof(delayed));
\r
194 delayed.fDelayedAutostart = 1;
\r
195 /* Delayed startup isn't supported until Vista. */
\r
196 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {
\r
197 unsigned long error = GetLastError();
\r
198 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
199 if (error != ERROR_INVALID_LEVEL) {
\r
200 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);
\r
205 /* Now we need to put the parameters into the registry */
\r
206 if (create_parameters(service)) {
\r
207 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
208 DeleteService(service->handle);
\r
209 CloseServiceHandle(services);
\r
213 set_service_recovery(service);
\r
215 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
218 CloseServiceHandle(services);
\r
223 /* Remove the service */
\r
224 int remove_service(nssm_service_t *service) {
\r
225 if (! service) return 1;
\r
227 /* Open service manager */
\r
228 SC_HANDLE services = open_service_manager();
\r
230 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
234 /* Try to open the service */
\r
235 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
236 if (! service->handle) {
\r
237 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
238 CloseServiceHandle(services);
\r
242 /* Try to delete the service */
\r
243 if (! DeleteService(service->handle)) {
\r
244 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
245 CloseServiceHandle(services);
\r
250 CloseServiceHandle(services);
\r
252 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\r
256 /* Service initialisation */
\r
257 void WINAPI service_main(unsigned long argc, TCHAR **argv) {
\r
258 nssm_service_t *service = alloc_nssm_service();
\r
259 if (! service) return;
\r
261 if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {
\r
262 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);
\r
266 /* We can use a condition variable in a critical section on Vista or later. */
\r
267 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
268 else use_critical_section = false;
\r
270 /* Initialise status */
\r
271 ZeroMemory(&service->status, sizeof(service->status));
\r
272 service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
273 service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
274 service->status.dwWin32ExitCode = NO_ERROR;
\r
275 service->status.dwServiceSpecificExitCode = 0;
\r
276 service->status.dwCheckPoint = 0;
\r
277 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
279 /* Signal we AREN'T running the server */
\r
280 service->process_handle = 0;
\r
283 /* Register control handler */
\r
284 service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
\r
285 if (! service->status_handle) {
\r
286 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
290 log_service_control(service->name, 0, true);
\r
292 service->status.dwCurrentState = SERVICE_START_PENDING;
\r
293 service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
\r
294 SetServiceStatus(service->status_handle, &service->status);
\r
297 /* Try to create the exit action parameters; we don't care if it fails */
\r
298 create_exit_action(service->name, exit_action_strings[0]);
\r
300 SC_HANDLE services = open_service_manager();
\r
302 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
303 set_service_recovery(service);
\r
304 CloseServiceHandle(services);
\r
308 /* Used for signalling a resume if the service pauses when throttled. */
\r
309 if (use_critical_section) {
\r
310 InitializeCriticalSection(&service->throttle_section);
\r
311 service->throttle_section_initialised = true;
\r
314 service->throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
315 if (! service->throttle_timer) {
\r
316 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
\r
320 monitor_service(service);
\r
323 /* Make sure service recovery actions are taken where necessary */
\r
324 void set_service_recovery(nssm_service_t *service) {
\r
325 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
326 ZeroMemory(&flag, sizeof(flag));
\r
327 flag.fFailureActionsOnNonCrashFailures = true;
\r
329 /* This functionality was added in Vista so the call may fail */
\r
330 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
331 unsigned long error = GetLastError();
\r
332 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
333 if (error != ERROR_INVALID_LEVEL) {
\r
334 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);
\r
339 int monitor_service(nssm_service_t *service) {
\r
340 /* Set service status to started */
\r
341 int ret = start_service(service);
\r
344 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);
\r
345 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
\r
348 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\r
350 /* Monitor service */
\r
351 if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
352 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
\r
358 TCHAR *service_control_text(unsigned long control) {
\r
360 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
361 case 0: return _T("START");
\r
362 case SERVICE_CONTROL_STOP: return _T("STOP");
\r
363 case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");
\r
364 case SERVICE_CONTROL_PAUSE: return _T("PAUSE");
\r
365 case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");
\r
366 case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");
\r
371 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {
\r
372 TCHAR *text = service_control_text(control);
\r
373 unsigned long event;
\r
376 /* "0x" + 8 x hex + NULL */
\r
377 text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));
\r
379 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
382 if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {
\r
383 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
384 HeapFree(GetProcessHeap(), 0, text);
\r
388 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
390 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
391 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
393 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
395 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
396 HeapFree(GetProcessHeap(), 0, text);
\r
400 /* Service control handler */
\r
401 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
402 nssm_service_t *service = (nssm_service_t *) context;
\r
405 case SERVICE_CONTROL_INTERROGATE:
\r
406 /* We always keep the service status up-to-date so this is a no-op. */
\r
409 case SERVICE_CONTROL_SHUTDOWN:
\r
410 case SERVICE_CONTROL_STOP:
\r
411 log_service_control(service->name, control, true);
\r
413 We MUST acknowledge the stop request promptly but we're committed to
\r
414 waiting for the application to exit. Spawn a new thread to wait
\r
415 while we acknowledge the request.
\r
417 if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
\r
418 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
421 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
422 to complete in time in this thread.
\r
424 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
425 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
426 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
428 stop_service(service, 0, true, true);
\r
432 case SERVICE_CONTROL_CONTINUE:
\r
433 log_service_control(service->name, control, true);
\r
434 service->throttle = 0;
\r
435 if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
\r
437 if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
438 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
439 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
441 service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
442 service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
\r
443 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
\r
444 SetServiceStatus(service->status_handle, &service->status);
\r
447 case SERVICE_CONTROL_PAUSE:
\r
449 We don't accept pause messages but it isn't possible to register
\r
450 only for continue messages so we have to handle this case.
\r
452 log_service_control(service->name, control, false);
\r
453 return ERROR_CALL_NOT_IMPLEMENTED;
\r
456 /* Unknown control */
\r
457 log_service_control(service->name, control, false);
\r
458 return ERROR_CALL_NOT_IMPLEMENTED;
\r
461 /* Start the service */
\r
462 int start_service(nssm_service_t *service) {
\r
463 service->stopping = false;
\r
464 service->allow_restart = true;
\r
466 if (service->process_handle) return 0;
\r
468 /* Allocate a STARTUPINFO structure for a new process */
\r
470 ZeroMemory(&si, sizeof(si));
\r
471 si.cb = sizeof(si);
\r
473 /* Allocate a PROCESSINFO structure for the process */
\r
474 PROCESS_INFORMATION pi;
\r
475 ZeroMemory(&pi, sizeof(pi));
\r
477 /* Get startup parameters */
\r
478 int ret = get_parameters(service, &si);
\r
480 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
\r
481 return stop_service(service, 2, true, true);
\r
484 /* Launch executable with arguments */
\r
485 TCHAR cmd[CMD_LENGTH];
\r
486 if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {
\r
487 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);
\r
488 close_output_handles(&si);
\r
489 return stop_service(service, 2, true, true);
\r
492 throttle_restart(service);
\r
494 bool inherit_handles = false;
\r
495 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
496 unsigned long flags = 0;
\r
498 flags |= CREATE_UNICODE_ENVIRONMENT;
\r
500 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {
\r
501 unsigned long exitcode = 3;
\r
502 unsigned long error = GetLastError();
\r
503 if (error == ERROR_INVALID_PARAMETER && service->env) {
\r
504 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);
\r
505 if (test_environment(service->env)) exitcode = 4;
\r
507 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
\r
508 close_output_handles(&si);
\r
509 return stop_service(service, exitcode, true, true);
\r
511 service->process_handle = pi.hProcess;
\r
512 service->pid = pi.dwProcessId;
\r
514 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
516 close_output_handles(&si);
\r
519 Wait for a clean startup before changing the service status to RUNNING
\r
520 but be mindful of the fact that we are blocking the service control manager
\r
521 so abandon the wait before too much time has elapsed.
\r
523 unsigned long delay = service->throttle_delay;
\r
524 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
525 TCHAR delay_milliseconds[16];
\r
526 _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);
\r
527 TCHAR deadline_milliseconds[16];
\r
528 _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);
\r
529 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
530 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
532 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
534 /* Signal successful start */
\r
535 service->status.dwCurrentState = SERVICE_RUNNING;
\r
536 SetServiceStatus(service->status_handle, &service->status);
\r
538 /* Continue waiting for a clean startup. */
\r
539 if (deadline == WAIT_TIMEOUT) {
\r
540 if (service->throttle_delay > delay) {
\r
541 if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
\r
543 else service->throttle = 0;
\r
549 /* Stop the service */
\r
550 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
\r
551 service->allow_restart = false;
\r
552 if (service->wait_handle) {
\r
553 UnregisterWait(service->wait_handle);
\r
554 service->wait_handle = 0;
\r
557 if (default_action && ! exitcode && ! graceful) {
\r
558 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);
\r
562 /* Signal we are stopping */
\r
564 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
565 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
566 SetServiceStatus(service->status_handle, &service->status);
\r
569 /* Nothing to do if service isn't running */
\r
570 if (service->pid) {
\r
571 /* Shut down service */
\r
572 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
\r
573 kill_process(service, service->process_handle, service->pid, 0);
\r
575 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
577 end_service((void *) service, true);
\r
579 /* Signal we stopped */
\r
581 service->status.dwCurrentState = SERVICE_STOPPED;
\r
583 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
584 service->status.dwServiceSpecificExitCode = exitcode;
\r
587 service->status.dwWin32ExitCode = NO_ERROR;
\r
588 service->status.dwServiceSpecificExitCode = 0;
\r
590 SetServiceStatus(service->status_handle, &service->status);
\r
596 /* Callback function triggered when the server exits */
\r
597 void CALLBACK end_service(void *arg, unsigned char why) {
\r
598 nssm_service_t *service = (nssm_service_t *) arg;
\r
600 if (service->stopping) return;
\r
602 service->stopping = true;
\r
604 /* Check exit code */
\r
605 unsigned long exitcode = 0;
\r
607 if (service->process_handle) {
\r
608 GetExitCodeProcess(service->process_handle, &exitcode);
\r
609 if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);
\r
610 CloseHandle(service->process_handle);
\r
612 else GetSystemTimeAsFileTime(&service->exit_time);
\r
614 service->process_handle = 0;
\r
617 Log that the service ended BEFORE logging about killing the process
\r
618 tree. See below for the possible values of the why argument.
\r
621 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);
\r
622 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
\r
626 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
627 if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);
\r
631 The why argument is true if our wait timed out or false otherwise.
\r
632 Our wait is infinite so why will never be true when called by the system.
\r
633 If it is indeed true, assume we were called from stop_service() because
\r
634 this is a controlled shutdown, and don't take any restart action.
\r
637 if (! service->allow_restart) return;
\r
639 /* What action should we take? */
\r
640 int action = NSSM_EXIT_RESTART;
\r
641 TCHAR action_string[ACTION_LEN];
\r
642 bool default_action;
\r
643 if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
\r
644 for (int i = 0; exit_action_strings[i]; i++) {
\r
645 if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
653 /* Try to restart the service or return failure code to service manager */
\r
654 case NSSM_EXIT_RESTART:
\r
655 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
\r
656 while (monitor_service(service)) {
\r
657 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
\r
662 /* Do nothing, just like srvany would */
\r
663 case NSSM_EXIT_IGNORE:
\r
664 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
\r
668 /* Tell the service manager we are finished */
\r
669 case NSSM_EXIT_REALLY:
\r
670 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
\r
671 stop_service(service, exitcode, true, default_action);
\r
674 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
675 case NSSM_EXIT_UNCLEAN:
\r
676 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
\r
677 stop_service(service, exitcode, false, default_action);
\r
684 void throttle_restart(nssm_service_t *service) {
\r
685 /* This can't be a restart if the service is already running. */
\r
686 if (! service->throttle++) return;
\r
688 int ms = throttle_milliseconds(service->throttle);
\r
690 if (service->throttle > 7) service->throttle = 8;
\r
692 TCHAR threshold[8], milliseconds[8];
\r
693 _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);
\r
694 _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);
\r
695 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
\r
697 if (use_critical_section) EnterCriticalSection(&service->throttle_section);
\r
698 else if (service->throttle_timer) {
\r
699 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
700 service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
701 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
704 service->status.dwCurrentState = SERVICE_PAUSED;
\r
705 SetServiceStatus(service->status_handle, &service->status);
\r
707 if (use_critical_section) {
\r
708 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
709 LeaveCriticalSection(&service->throttle_section);
\r
712 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\r
718 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
719 the number of milliseconds we expect the operation to take, and optionally
\r
720 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
721 operation completing or dwCheckPoint increasing, the system will consider the
\r
722 service to be hung.
\r
724 However the system will consider the service to be hung after 30000
\r
725 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
726 changed. Therefore if we want to wait longer than that we must periodically
\r
727 increase dwCheckPoint.
\r
729 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
730 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
731 time dwCheckPoint is also increased.
\r
733 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
734 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
735 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
736 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
739 Only doing both these things will prevent the system from killing the service.
\r
741 Returns: 1 if the wait timed out.
\r
742 0 if the wait completed.
\r
745 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {
\r
746 unsigned long interval;
\r
747 unsigned long waithint;
\r
749 unsigned long waited;
\r
750 TCHAR interval_milliseconds[16];
\r
751 TCHAR timeout_milliseconds[16];
\r
752 TCHAR waited_milliseconds[16];
\r
753 TCHAR *function = function_name;
\r
755 /* Add brackets to function name. */
\r
756 size_t funclen = _tcslen(function_name) + 3;
\r
757 TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));
\r
759 if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;
\r
762 _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);
\r
764 waithint = service->status.dwWaitHint;
\r
766 while (waited < timeout) {
\r
767 interval = timeout - waited;
\r
768 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
770 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
771 service->status.dwWaitHint += interval;
\r
772 service->status.dwCheckPoint++;
\r
773 SetServiceStatus(service->status_handle, &service->status);
\r
776 _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);
\r
777 _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);
\r
778 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
781 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
782 case WAIT_OBJECT_0:
\r
795 waited += interval;
\r
799 if (func) HeapFree(GetProcessHeap(), 0, func);
\r