3 extern const TCHAR *exit_action_strings[];
\r
6 bool use_critical_section;
\r
8 extern imports_t imports;
\r
10 const TCHAR *exit_action_strings[] = { _T("Restart"), _T("Ignore"), _T("Exit"), _T("Suicide"), 0 };
\r
12 static inline int throttle_milliseconds(unsigned long throttle) {
\r
13 /* pow() operates on doubles. */
\r
14 int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;
\r
19 Wrapper to be called in a new thread so that we can acknowledge a STOP
\r
20 control immediately.
\r
22 static unsigned long WINAPI shutdown_service(void *arg) {
\r
23 return stop_service((nssm_service_t *) arg, 0, true, true);
\r
26 /* Connect to the service manager */
\r
27 SC_HANDLE open_service_manager() {
\r
28 SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
\r
30 if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);
\r
37 /* Set default values which aren't zero. */
\r
38 void set_nssm_service_defaults(nssm_service_t *service) {
\r
39 if (! service) return;
\r
41 service->type = SERVICE_WIN32_OWN_PROCESS;
\r
42 service->stdin_sharing = NSSM_STDIN_SHARING;
\r
43 service->stdin_disposition = NSSM_STDIN_DISPOSITION;
\r
44 service->stdin_flags = NSSM_STDIN_FLAGS;
\r
45 service->stdout_sharing = NSSM_STDOUT_SHARING;
\r
46 service->stdout_disposition = NSSM_STDOUT_DISPOSITION;
\r
47 service->stdout_flags = NSSM_STDOUT_FLAGS;
\r
48 service->stderr_sharing = NSSM_STDERR_SHARING;
\r
49 service->stderr_disposition = NSSM_STDERR_DISPOSITION;
\r
50 service->stderr_flags = NSSM_STDERR_FLAGS;
\r
51 service->throttle_delay = NSSM_RESET_THROTTLE_RESTART;
\r
52 service->stop_method = ~0;
\r
53 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
54 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
55 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
58 /* Allocate and zero memory for a service. */
\r
59 nssm_service_t *alloc_nssm_service() {
\r
60 nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));
\r
61 if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("alloc_nssm_service()"), 0);
\r
65 /* Free memory for a service. */
\r
66 void cleanup_nssm_service(nssm_service_t *service) {
\r
67 if (! service) return;
\r
68 if (service->username) HeapFree(GetProcessHeap(), 0, service->username);
\r
69 if (service->password) {
\r
70 SecureZeroMemory(service->password, service->passwordlen);
\r
71 HeapFree(GetProcessHeap(), 0, service->password);
\r
73 if (service->env) HeapFree(GetProcessHeap(), 0, service->env);
\r
74 if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);
\r
75 if (service->handle) CloseServiceHandle(service->handle);
\r
76 if (service->process_handle) CloseHandle(service->process_handle);
\r
77 if (service->wait_handle) UnregisterWait(service->process_handle);
\r
78 if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);
\r
79 if (service->throttle_timer) CloseHandle(service->throttle_timer);
\r
80 HeapFree(GetProcessHeap(), 0, service);
\r
83 /* About to install the service */
\r
84 int pre_install_service(int argc, TCHAR **argv) {
\r
85 nssm_service_t *service = alloc_nssm_service();
\r
86 set_nssm_service_defaults(service);
\r
87 if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
89 /* Show the dialogue box if we didn't give the service name and path */
\r
90 if (argc < 2) return nssm_gui(IDD_INSTALL, service);
\r
93 print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("pre_install_service()"));
\r
96 _sntprintf_s(service->exe, _countof(service->exe), _TRUNCATE, _T("%s"), argv[1]);
\r
98 /* Arguments are optional */
\r
99 size_t flagslen = 0;
\r
102 for (i = 2; i < argc; i++) flagslen += _tcslen(argv[i]) + 1;
\r
103 if (! flagslen) flagslen = 1;
\r
104 if (flagslen > _countof(service->flags)) {
\r
105 print_message(stderr, NSSM_MESSAGE_FLAGS_TOO_LONG);
\r
109 for (i = 2; i < argc; i++) {
\r
110 size_t len = _tcslen(argv[i]);
\r
111 memmove(service->flags + s, argv[i], len * sizeof(TCHAR));
\r
113 if (i < argc - 1) service->flags[s++] = _T(' ');
\r
116 /* Work out directory name */
\r
117 _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);
\r
118 strip_basename(service->dir);
\r
120 int ret = install_service(service);
\r
121 cleanup_nssm_service(service);
\r
125 /* About to edit the service. */
\r
126 int pre_edit_service(int argc, TCHAR **argv) {
\r
127 /* Require service name. */
\r
128 if (argc < 1) return usage(1);
\r
130 nssm_service_t *service = alloc_nssm_service();
\r
131 _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
133 /* Open service manager */
\r
134 SC_HANDLE services = open_service_manager();
\r
136 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
140 /* Try to open the service */
\r
141 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
142 if (! service->handle) {
\r
143 CloseServiceHandle(services);
\r
144 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
148 /* Get system details. */
\r
149 unsigned long bufsize;
\r
150 unsigned long error;
\r
151 QUERY_SERVICE_CONFIG *qsc;
\r
153 QueryServiceConfig(service->handle, 0, 0, &bufsize);
\r
154 error = GetLastError();
\r
155 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
156 qsc = (QUERY_SERVICE_CONFIG *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufsize);
\r
158 print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, _T("QUERY_SERVICE_CONFIG"), _T("pre_edit_service()"), 0);
\r
163 CloseHandle(service->handle);
\r
164 CloseServiceHandle(services);
\r
165 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service->name, error_string(error), 0);
\r
169 if (! QueryServiceConfig(service->handle, qsc, bufsize, &bufsize)) {
\r
170 HeapFree(GetProcessHeap(), 0, qsc);
\r
171 CloseHandle(service->handle);
\r
172 CloseServiceHandle(services);
\r
173 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service->name, error_string(GetLastError()), 0);
\r
177 service->type = qsc->dwServiceType;
\r
178 if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {
\r
179 HeapFree(GetProcessHeap(), 0, qsc);
\r
180 CloseHandle(service->handle);
\r
181 CloseServiceHandle(services);
\r
182 print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, _T("SERVICE_WIN32_OWN_PROCESS"), 0);
\r
186 switch (qsc->dwStartType) {
\r
187 case SERVICE_DEMAND_START: service->startup = NSSM_STARTUP_MANUAL; break;
\r
188 case SERVICE_DISABLED: service->startup = NSSM_STARTUP_DISABLED; break;
\r
189 default: service->startup = NSSM_STARTUP_AUTOMATIC;
\r
191 if (! str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) {
\r
192 size_t len = _tcslen(qsc->lpServiceStartName);
\r
193 service->username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(TCHAR));
\r
194 if (service->username) {
\r
195 memmove(service->username, qsc->lpServiceStartName, (len + 1) * sizeof(TCHAR));
\r
196 service->usernamelen = (unsigned long) len;
\r
199 HeapFree(GetProcessHeap(), 0, qsc);
\r
200 CloseHandle(service->handle);
\r
201 CloseServiceHandle(services);
\r
202 print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, _T("username"), _T("pre_edit_service()"));
\r
206 _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);
\r
208 /* Get the canonical service name. We open it case insensitively. */
\r
209 bufsize = _countof(service->name);
\r
210 GetServiceKeyName(services, service->displayname, service->name, &bufsize);
\r
212 /* Remember the executable in case it isn't NSSM. */
\r
213 _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);
\r
214 HeapFree(GetProcessHeap(), 0, qsc);
\r
216 /* Get extended system details. */
\r
217 if (service->startup == NSSM_STARTUP_AUTOMATIC) {
\r
218 QueryServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, 0, 0, &bufsize);
\r
219 error = GetLastError();
\r
220 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
221 SERVICE_DELAYED_AUTO_START_INFO *info = (SERVICE_DELAYED_AUTO_START_INFO *) HeapAlloc(GetProcessHeap(), 0, bufsize);
\r
223 CloseHandle(service->handle);
\r
224 CloseServiceHandle(services);
\r
225 print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, _T("SERVICE_DELAYED_AUTO_START_INFO"), _T("pre_edit_service()"));
\r
229 if (QueryServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (unsigned char *) info, bufsize, &bufsize)) {
\r
230 if (info->fDelayedAutostart) service->startup = NSSM_STARTUP_DELAYED;
\r
233 error = GetLastError();
\r
234 if (error != ERROR_INVALID_LEVEL) {
\r
235 CloseHandle(service->handle);
\r
236 CloseServiceHandle(services);
\r
237 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
242 else if (error != ERROR_INVALID_LEVEL) {
\r
243 CloseHandle(service->handle);
\r
244 CloseServiceHandle(services);
\r
245 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));
\r
250 QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);
\r
251 error = GetLastError();
\r
252 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
253 SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);
\r
254 if (! description) {
\r
255 CloseHandle(service->handle);
\r
256 CloseServiceHandle(services);
\r
257 print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("pre_edit_service()"));
\r
261 if (QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {
\r
262 if (description->lpDescription) _sntprintf_s(service->description, _countof(service->description), _TRUNCATE, _T("%s"), description->lpDescription);
\r
263 HeapFree(GetProcessHeap(), 0, description);
\r
266 HeapFree(GetProcessHeap(), 0, description);
\r
267 CloseHandle(service->handle);
\r
268 CloseServiceHandle(services);
\r
269 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
274 CloseHandle(service->handle);
\r
275 CloseServiceHandle(services);
\r
276 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
280 /* Get NSSM details. */
\r
281 get_parameters(service, 0);
\r
283 CloseServiceHandle(services);
\r
285 if (! service->exe[0]) {
\r
286 print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);
\r
287 service->native = true;
\r
290 nssm_gui(IDD_EDIT, service);
\r
295 /* About to remove the service */
\r
296 int pre_remove_service(int argc, TCHAR **argv) {
\r
297 nssm_service_t *service = alloc_nssm_service();
\r
298 set_nssm_service_defaults(service);
\r
299 if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
301 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
302 if (argc < 2) return nssm_gui(IDD_REMOVE, service);
\r
303 if (str_equiv(argv[1], _T("confirm"))) {
\r
304 int ret = remove_service(service);
\r
305 cleanup_nssm_service(service);
\r
308 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
312 /* Install the service */
\r
313 int install_service(nssm_service_t *service) {
\r
314 if (! service) return 1;
\r
316 /* Open service manager */
\r
317 SC_HANDLE services = open_service_manager();
\r
319 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
320 cleanup_nssm_service(service);
\r
324 /* Get path of this program */
\r
325 GetModuleFileName(0, service->image, _countof(service->image));
\r
327 /* Create the service - settings will be changed in edit_service() */
\r
328 service->handle = CreateService(services, service->name, service->name, SC_MANAGER_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, service->image, 0, 0, 0, 0, 0);
\r
329 if (! service->handle) {
\r
330 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
331 CloseServiceHandle(services);
\r
335 if (edit_service(service, false)) {
\r
336 DeleteService(service->handle);
\r
337 CloseServiceHandle(services);
\r
341 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
344 CloseServiceHandle(services);
\r
349 /* Edit the service. */
\r
350 int edit_service(nssm_service_t *service, bool editing) {
\r
351 if (! service) return 1;
\r
354 The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS
\r
355 and SERVICE_INTERACTIVE_PROCESS.
\r
357 service->type &= SERVICE_INTERACTIVE_PROCESS;
\r
358 service->type |= SERVICE_WIN32_OWN_PROCESS;
\r
360 /* Startup type. */
\r
361 unsigned long startup;
\r
362 switch (service->startup) {
\r
363 case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;
\r
364 case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;
\r
365 default: startup = SERVICE_AUTO_START;
\r
368 /* Display name. */
\r
369 if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);
\r
372 Username must be NULL if we aren't changing or an account name.
\r
373 We must explicitly user LOCALSYSTEM to change it when we are editing.
\r
374 Password must be NULL if we aren't changing, a password or "".
\r
375 Empty passwords are valid but we won't allow them in the GUI.
\r
377 TCHAR *username = 0;
\r
378 TCHAR *password = 0;
\r
379 if (service->usernamelen) {
\r
380 username = service->username;
\r
381 if (service->passwordlen) password = service->password;
\r
382 else password = _T("");
\r
384 else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;
\r
386 if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {
\r
387 print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
\r
391 if (service->description[0] || editing) {
\r
392 SERVICE_DESCRIPTION description;
\r
393 ZeroMemory(&description, sizeof(description));
\r
394 if (service->description[0]) description.lpDescription = service->description;
\r
395 else description.lpDescription = 0;
\r
396 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, &description)) {
\r
397 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service->name, error_string(GetLastError()), 0);
\r
401 SERVICE_DELAYED_AUTO_START_INFO delayed;
\r
402 ZeroMemory(&delayed, sizeof(delayed));
\r
403 if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;
\r
404 else delayed.fDelayedAutostart = 0;
\r
405 /* Delayed startup isn't supported until Vista. */
\r
406 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {
\r
407 unsigned long error = GetLastError();
\r
408 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
409 if (error != ERROR_INVALID_LEVEL) {
\r
410 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);
\r
414 /* Don't mess with parameters which aren't ours. */
\r
415 if (! service->native) {
\r
416 /* Now we need to put the parameters into the registry */
\r
417 if (create_parameters(service, editing)) {
\r
418 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
422 set_service_recovery(service);
\r
428 /* Remove the service */
\r
429 int remove_service(nssm_service_t *service) {
\r
430 if (! service) return 1;
\r
432 /* Open service manager */
\r
433 SC_HANDLE services = open_service_manager();
\r
435 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
439 /* Try to open the service */
\r
440 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
441 if (! service->handle) {
\r
442 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
443 CloseServiceHandle(services);
\r
447 /* Get the canonical service name. We open it case insensitively. */
\r
448 unsigned long bufsize = _countof(service->displayname);
\r
449 GetServiceDisplayName(services, service->name, service->displayname, &bufsize);
\r
450 bufsize = _countof(service->name);
\r
451 GetServiceKeyName(services, service->displayname, service->name, &bufsize);
\r
453 /* Try to delete the service */
\r
454 if (! DeleteService(service->handle)) {
\r
455 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
456 CloseServiceHandle(services);
\r
461 CloseServiceHandle(services);
\r
463 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\r
467 /* Service initialisation */
\r
468 void WINAPI service_main(unsigned long argc, TCHAR **argv) {
\r
469 nssm_service_t *service = alloc_nssm_service();
\r
470 if (! service) return;
\r
472 if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {
\r
473 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);
\r
477 /* We can use a condition variable in a critical section on Vista or later. */
\r
478 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
479 else use_critical_section = false;
\r
481 /* Initialise status */
\r
482 ZeroMemory(&service->status, sizeof(service->status));
\r
483 service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
484 service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
485 service->status.dwWin32ExitCode = NO_ERROR;
\r
486 service->status.dwServiceSpecificExitCode = 0;
\r
487 service->status.dwCheckPoint = 0;
\r
488 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
490 /* Signal we AREN'T running the server */
\r
491 service->process_handle = 0;
\r
494 /* Register control handler */
\r
495 service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
\r
496 if (! service->status_handle) {
\r
497 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
501 log_service_control(service->name, 0, true);
\r
503 service->status.dwCurrentState = SERVICE_START_PENDING;
\r
504 service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
\r
505 SetServiceStatus(service->status_handle, &service->status);
\r
508 /* Try to create the exit action parameters; we don't care if it fails */
\r
509 create_exit_action(service->name, exit_action_strings[0], false);
\r
511 SC_HANDLE services = open_service_manager();
\r
513 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
514 set_service_recovery(service);
\r
515 CloseServiceHandle(services);
\r
519 /* Used for signalling a resume if the service pauses when throttled. */
\r
520 if (use_critical_section) {
\r
521 InitializeCriticalSection(&service->throttle_section);
\r
522 service->throttle_section_initialised = true;
\r
525 service->throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
526 if (! service->throttle_timer) {
\r
527 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
\r
531 monitor_service(service);
\r
534 /* Make sure service recovery actions are taken where necessary */
\r
535 void set_service_recovery(nssm_service_t *service) {
\r
536 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
537 ZeroMemory(&flag, sizeof(flag));
\r
538 flag.fFailureActionsOnNonCrashFailures = true;
\r
540 /* This functionality was added in Vista so the call may fail */
\r
541 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
542 unsigned long error = GetLastError();
\r
543 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
544 if (error != ERROR_INVALID_LEVEL) {
\r
545 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);
\r
550 int monitor_service(nssm_service_t *service) {
\r
551 /* Set service status to started */
\r
552 int ret = start_service(service);
\r
555 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);
\r
556 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
\r
559 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\r
561 /* Monitor service */
\r
562 if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
563 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
\r
569 TCHAR *service_control_text(unsigned long control) {
\r
571 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
572 case 0: return _T("START");
\r
573 case SERVICE_CONTROL_STOP: return _T("STOP");
\r
574 case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");
\r
575 case SERVICE_CONTROL_PAUSE: return _T("PAUSE");
\r
576 case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");
\r
577 case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");
\r
582 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {
\r
583 TCHAR *text = service_control_text(control);
\r
584 unsigned long event;
\r
587 /* "0x" + 8 x hex + NULL */
\r
588 text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));
\r
590 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
593 if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {
\r
594 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
595 HeapFree(GetProcessHeap(), 0, text);
\r
599 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
601 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
602 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
604 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
606 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
607 HeapFree(GetProcessHeap(), 0, text);
\r
611 /* Service control handler */
\r
612 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
613 nssm_service_t *service = (nssm_service_t *) context;
\r
616 case SERVICE_CONTROL_INTERROGATE:
\r
617 /* We always keep the service status up-to-date so this is a no-op. */
\r
620 case SERVICE_CONTROL_SHUTDOWN:
\r
621 case SERVICE_CONTROL_STOP:
\r
622 log_service_control(service->name, control, true);
\r
624 We MUST acknowledge the stop request promptly but we're committed to
\r
625 waiting for the application to exit. Spawn a new thread to wait
\r
626 while we acknowledge the request.
\r
628 if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
\r
629 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
632 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
633 to complete in time in this thread.
\r
635 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
636 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
637 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
639 stop_service(service, 0, true, true);
\r
643 case SERVICE_CONTROL_CONTINUE:
\r
644 log_service_control(service->name, control, true);
\r
645 service->throttle = 0;
\r
646 if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
\r
648 if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
649 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
650 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
652 service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
653 service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
\r
654 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
\r
655 SetServiceStatus(service->status_handle, &service->status);
\r
658 case SERVICE_CONTROL_PAUSE:
\r
660 We don't accept pause messages but it isn't possible to register
\r
661 only for continue messages so we have to handle this case.
\r
663 log_service_control(service->name, control, false);
\r
664 return ERROR_CALL_NOT_IMPLEMENTED;
\r
667 /* Unknown control */
\r
668 log_service_control(service->name, control, false);
\r
669 return ERROR_CALL_NOT_IMPLEMENTED;
\r
672 /* Start the service */
\r
673 int start_service(nssm_service_t *service) {
\r
674 service->stopping = false;
\r
675 service->allow_restart = true;
\r
677 if (service->process_handle) return 0;
\r
679 /* Allocate a STARTUPINFO structure for a new process */
\r
681 ZeroMemory(&si, sizeof(si));
\r
682 si.cb = sizeof(si);
\r
684 /* Allocate a PROCESSINFO structure for the process */
\r
685 PROCESS_INFORMATION pi;
\r
686 ZeroMemory(&pi, sizeof(pi));
\r
688 /* Get startup parameters */
\r
689 int ret = get_parameters(service, &si);
\r
691 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
\r
692 return stop_service(service, 2, true, true);
\r
695 /* Launch executable with arguments */
\r
696 TCHAR cmd[CMD_LENGTH];
\r
697 if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {
\r
698 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);
\r
699 close_output_handles(&si);
\r
700 return stop_service(service, 2, true, true);
\r
703 throttle_restart(service);
\r
705 bool inherit_handles = false;
\r
706 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
707 unsigned long flags = 0;
\r
709 flags |= CREATE_UNICODE_ENVIRONMENT;
\r
711 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {
\r
712 unsigned long exitcode = 3;
\r
713 unsigned long error = GetLastError();
\r
714 if (error == ERROR_INVALID_PARAMETER && service->env) {
\r
715 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);
\r
716 if (test_environment(service->env)) exitcode = 4;
\r
718 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
\r
719 close_output_handles(&si);
\r
720 return stop_service(service, exitcode, true, true);
\r
722 service->process_handle = pi.hProcess;
\r
723 service->pid = pi.dwProcessId;
\r
725 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
727 close_output_handles(&si);
\r
730 Wait for a clean startup before changing the service status to RUNNING
\r
731 but be mindful of the fact that we are blocking the service control manager
\r
732 so abandon the wait before too much time has elapsed.
\r
734 unsigned long delay = service->throttle_delay;
\r
735 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
736 TCHAR delay_milliseconds[16];
\r
737 _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);
\r
738 TCHAR deadline_milliseconds[16];
\r
739 _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);
\r
740 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
741 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
743 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
745 /* Signal successful start */
\r
746 service->status.dwCurrentState = SERVICE_RUNNING;
\r
747 SetServiceStatus(service->status_handle, &service->status);
\r
749 /* Continue waiting for a clean startup. */
\r
750 if (deadline == WAIT_TIMEOUT) {
\r
751 if (service->throttle_delay > delay) {
\r
752 if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
\r
754 else service->throttle = 0;
\r
760 /* Stop the service */
\r
761 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
\r
762 service->allow_restart = false;
\r
763 if (service->wait_handle) {
\r
764 UnregisterWait(service->wait_handle);
\r
765 service->wait_handle = 0;
\r
768 if (default_action && ! exitcode && ! graceful) {
\r
769 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
773 /* Signal we are stopping */
\r
775 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
776 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
777 SetServiceStatus(service->status_handle, &service->status);
\r
780 /* Nothing to do if service isn't running */
\r
781 if (service->pid) {
\r
782 /* Shut down service */
\r
783 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
\r
784 kill_process(service, service->process_handle, service->pid, 0);
\r
786 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
788 end_service((void *) service, true);
\r
790 /* Signal we stopped */
\r
792 service->status.dwCurrentState = SERVICE_STOPPED;
\r
794 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
795 service->status.dwServiceSpecificExitCode = exitcode;
\r
798 service->status.dwWin32ExitCode = NO_ERROR;
\r
799 service->status.dwServiceSpecificExitCode = 0;
\r
801 SetServiceStatus(service->status_handle, &service->status);
\r
807 /* Callback function triggered when the server exits */
\r
808 void CALLBACK end_service(void *arg, unsigned char why) {
\r
809 nssm_service_t *service = (nssm_service_t *) arg;
\r
811 if (service->stopping) return;
\r
813 service->stopping = true;
\r
815 /* Check exit code */
\r
816 unsigned long exitcode = 0;
\r
818 if (service->process_handle) {
\r
819 GetExitCodeProcess(service->process_handle, &exitcode);
\r
820 if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);
\r
821 CloseHandle(service->process_handle);
\r
823 else GetSystemTimeAsFileTime(&service->exit_time);
\r
825 service->process_handle = 0;
\r
828 Log that the service ended BEFORE logging about killing the process
\r
829 tree. See below for the possible values of the why argument.
\r
832 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);
\r
833 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
\r
837 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
838 if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);
\r
842 The why argument is true if our wait timed out or false otherwise.
\r
843 Our wait is infinite so why will never be true when called by the system.
\r
844 If it is indeed true, assume we were called from stop_service() because
\r
845 this is a controlled shutdown, and don't take any restart action.
\r
848 if (! service->allow_restart) return;
\r
850 /* What action should we take? */
\r
851 int action = NSSM_EXIT_RESTART;
\r
852 TCHAR action_string[ACTION_LEN];
\r
853 bool default_action;
\r
854 if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
\r
855 for (int i = 0; exit_action_strings[i]; i++) {
\r
856 if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
864 /* Try to restart the service or return failure code to service manager */
\r
865 case NSSM_EXIT_RESTART:
\r
866 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
\r
867 while (monitor_service(service)) {
\r
868 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
\r
873 /* Do nothing, just like srvany would */
\r
874 case NSSM_EXIT_IGNORE:
\r
875 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
\r
879 /* Tell the service manager we are finished */
\r
880 case NSSM_EXIT_REALLY:
\r
881 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
\r
882 stop_service(service, exitcode, true, default_action);
\r
885 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
886 case NSSM_EXIT_UNCLEAN:
\r
887 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
\r
888 stop_service(service, exitcode, false, default_action);
\r
895 void throttle_restart(nssm_service_t *service) {
\r
896 /* This can't be a restart if the service is already running. */
\r
897 if (! service->throttle++) return;
\r
899 int ms = throttle_milliseconds(service->throttle);
\r
901 if (service->throttle > 7) service->throttle = 8;
\r
903 TCHAR threshold[8], milliseconds[8];
\r
904 _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);
\r
905 _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);
\r
906 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
\r
908 if (use_critical_section) EnterCriticalSection(&service->throttle_section);
\r
909 else if (service->throttle_timer) {
\r
910 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
911 service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
912 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
915 service->status.dwCurrentState = SERVICE_PAUSED;
\r
916 SetServiceStatus(service->status_handle, &service->status);
\r
918 if (use_critical_section) {
\r
919 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
920 LeaveCriticalSection(&service->throttle_section);
\r
923 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\r
929 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
930 the number of milliseconds we expect the operation to take, and optionally
\r
931 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
932 operation completing or dwCheckPoint increasing, the system will consider the
\r
933 service to be hung.
\r
935 However the system will consider the service to be hung after 30000
\r
936 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
937 changed. Therefore if we want to wait longer than that we must periodically
\r
938 increase dwCheckPoint.
\r
940 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
941 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
942 time dwCheckPoint is also increased.
\r
944 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
945 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
946 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
947 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
950 Only doing both these things will prevent the system from killing the service.
\r
952 Returns: 1 if the wait timed out.
\r
953 0 if the wait completed.
\r
956 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {
\r
957 unsigned long interval;
\r
958 unsigned long waithint;
\r
960 unsigned long waited;
\r
961 TCHAR interval_milliseconds[16];
\r
962 TCHAR timeout_milliseconds[16];
\r
963 TCHAR waited_milliseconds[16];
\r
964 TCHAR *function = function_name;
\r
966 /* Add brackets to function name. */
\r
967 size_t funclen = _tcslen(function_name) + 3;
\r
968 TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));
\r
970 if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;
\r
973 _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);
\r
975 waithint = service->status.dwWaitHint;
\r
977 while (waited < timeout) {
\r
978 interval = timeout - waited;
\r
979 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
981 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
982 service->status.dwWaitHint += interval;
\r
983 service->status.dwCheckPoint++;
\r
984 SetServiceStatus(service->status_handle, &service->status);
\r
987 _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);
\r
988 _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);
\r
989 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
992 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
993 case WAIT_OBJECT_0:
\r
1006 waited += interval;
\r
1010 if (func) HeapFree(GetProcessHeap(), 0, func);
\r