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 /* Remember the executable in case it isn't NSSM. */
\r
209 _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);
\r
210 HeapFree(GetProcessHeap(), 0, qsc);
\r
212 /* Get extended system details. */
\r
213 if (service->startup == NSSM_STARTUP_AUTOMATIC) {
\r
214 QueryServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, 0, 0, &bufsize);
\r
215 error = GetLastError();
\r
216 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
217 SERVICE_DELAYED_AUTO_START_INFO *info = (SERVICE_DELAYED_AUTO_START_INFO *) HeapAlloc(GetProcessHeap(), 0, bufsize);
\r
219 CloseHandle(service->handle);
\r
220 CloseServiceHandle(services);
\r
221 print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, _T("SERVICE_DELAYED_AUTO_START_INFO"), _T("pre_edit_service()"));
\r
225 if (QueryServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (unsigned char *) info, bufsize, &bufsize)) {
\r
226 if (info->fDelayedAutostart) service->startup = NSSM_STARTUP_DELAYED;
\r
229 error = GetLastError();
\r
230 if (error != ERROR_INVALID_LEVEL) {
\r
231 CloseHandle(service->handle);
\r
232 CloseServiceHandle(services);
\r
233 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
238 else if (error != ERROR_INVALID_LEVEL) {
\r
239 CloseHandle(service->handle);
\r
240 CloseServiceHandle(services);
\r
241 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));
\r
246 QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);
\r
247 error = GetLastError();
\r
248 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
249 SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);
\r
250 if (! description) {
\r
251 CloseHandle(service->handle);
\r
252 CloseServiceHandle(services);
\r
253 print_message(stderr, NSSM_EVENT_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("pre_edit_service()"));
\r
257 if (QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {
\r
258 if (description->lpDescription) _sntprintf_s(service->description, _countof(service->description), _TRUNCATE, _T("%s"), description->lpDescription);
\r
259 HeapFree(GetProcessHeap(), 0, description);
\r
262 HeapFree(GetProcessHeap(), 0, description);
\r
263 CloseHandle(service->handle);
\r
264 CloseServiceHandle(services);
\r
265 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
270 CloseHandle(service->handle);
\r
271 CloseServiceHandle(services);
\r
272 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
276 /* Get NSSM details. */
\r
277 get_parameters(service, 0);
\r
279 CloseServiceHandle(services);
\r
281 if (! service->exe[0]) {
\r
282 print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);
\r
283 service->native = true;
\r
286 nssm_gui(IDD_EDIT, service);
\r
291 /* About to remove the service */
\r
292 int pre_remove_service(int argc, TCHAR **argv) {
\r
293 nssm_service_t *service = alloc_nssm_service();
\r
294 set_nssm_service_defaults(service);
\r
295 if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
297 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
298 if (argc < 2) return nssm_gui(IDD_REMOVE, service);
\r
299 if (str_equiv(argv[1], _T("confirm"))) {
\r
300 int ret = remove_service(service);
\r
301 cleanup_nssm_service(service);
\r
304 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
308 /* Install the service */
\r
309 int install_service(nssm_service_t *service) {
\r
310 if (! service) return 1;
\r
312 /* Open service manager */
\r
313 SC_HANDLE services = open_service_manager();
\r
315 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
316 cleanup_nssm_service(service);
\r
320 /* Get path of this program */
\r
321 GetModuleFileName(0, service->image, _countof(service->image));
\r
323 /* Create the service - settings will be changed in edit_service() */
\r
324 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
325 if (! service->handle) {
\r
326 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
327 CloseServiceHandle(services);
\r
331 if (edit_service(service, false)) {
\r
332 DeleteService(service->handle);
\r
333 CloseServiceHandle(services);
\r
337 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
340 CloseServiceHandle(services);
\r
345 /* Edit the service. */
\r
346 int edit_service(nssm_service_t *service, bool editing) {
\r
347 if (! service) return 1;
\r
350 The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS
\r
351 and SERVICE_INTERACTIVE_PROCESS.
\r
353 service->type &= SERVICE_INTERACTIVE_PROCESS;
\r
354 service->type |= SERVICE_WIN32_OWN_PROCESS;
\r
356 /* Startup type. */
\r
357 unsigned long startup;
\r
358 switch (service->startup) {
\r
359 case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;
\r
360 case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;
\r
361 default: startup = SERVICE_AUTO_START;
\r
364 /* Display name. */
\r
365 if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);
\r
368 Username must be NULL if we aren't changing or an account name.
\r
369 We must explicitly user LOCALSYSTEM to change it when we are editing.
\r
370 Password must be NULL if we aren't changing, a password or "".
\r
371 Empty passwords are valid but we won't allow them in the GUI.
\r
373 TCHAR *username = 0;
\r
374 TCHAR *password = 0;
\r
375 if (service->usernamelen) {
\r
376 username = service->username;
\r
377 if (service->passwordlen) password = service->password;
\r
378 else password = _T("");
\r
380 else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;
\r
382 if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {
\r
383 print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
\r
387 if (service->description[0] || editing) {
\r
388 SERVICE_DESCRIPTION description;
\r
389 ZeroMemory(&description, sizeof(description));
\r
390 if (service->description[0]) description.lpDescription = service->description;
\r
391 else description.lpDescription = 0;
\r
392 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, &description)) {
\r
393 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service->name, error_string(GetLastError()), 0);
\r
397 SERVICE_DELAYED_AUTO_START_INFO delayed;
\r
398 ZeroMemory(&delayed, sizeof(delayed));
\r
399 if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;
\r
400 else delayed.fDelayedAutostart = 0;
\r
401 /* Delayed startup isn't supported until Vista. */
\r
402 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {
\r
403 unsigned long error = GetLastError();
\r
404 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
405 if (error != ERROR_INVALID_LEVEL) {
\r
406 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);
\r
410 /* Don't mess with parameters which aren't ours. */
\r
411 if (! service->native) {
\r
412 /* Now we need to put the parameters into the registry */
\r
413 if (create_parameters(service, editing)) {
\r
414 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
418 set_service_recovery(service);
\r
424 /* Remove the service */
\r
425 int remove_service(nssm_service_t *service) {
\r
426 if (! service) return 1;
\r
428 /* Open service manager */
\r
429 SC_HANDLE services = open_service_manager();
\r
431 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
435 /* Try to open the service */
\r
436 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
437 if (! service->handle) {
\r
438 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
439 CloseServiceHandle(services);
\r
443 /* Try to delete the service */
\r
444 if (! DeleteService(service->handle)) {
\r
445 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
446 CloseServiceHandle(services);
\r
451 CloseServiceHandle(services);
\r
453 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\r
457 /* Service initialisation */
\r
458 void WINAPI service_main(unsigned long argc, TCHAR **argv) {
\r
459 nssm_service_t *service = alloc_nssm_service();
\r
460 if (! service) return;
\r
462 if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {
\r
463 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);
\r
467 /* We can use a condition variable in a critical section on Vista or later. */
\r
468 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
469 else use_critical_section = false;
\r
471 /* Initialise status */
\r
472 ZeroMemory(&service->status, sizeof(service->status));
\r
473 service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
474 service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
475 service->status.dwWin32ExitCode = NO_ERROR;
\r
476 service->status.dwServiceSpecificExitCode = 0;
\r
477 service->status.dwCheckPoint = 0;
\r
478 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
480 /* Signal we AREN'T running the server */
\r
481 service->process_handle = 0;
\r
484 /* Register control handler */
\r
485 service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
\r
486 if (! service->status_handle) {
\r
487 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
491 log_service_control(service->name, 0, true);
\r
493 service->status.dwCurrentState = SERVICE_START_PENDING;
\r
494 service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
\r
495 SetServiceStatus(service->status_handle, &service->status);
\r
498 /* Try to create the exit action parameters; we don't care if it fails */
\r
499 create_exit_action(service->name, exit_action_strings[0], false);
\r
501 SC_HANDLE services = open_service_manager();
\r
503 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
504 set_service_recovery(service);
\r
505 CloseServiceHandle(services);
\r
509 /* Used for signalling a resume if the service pauses when throttled. */
\r
510 if (use_critical_section) {
\r
511 InitializeCriticalSection(&service->throttle_section);
\r
512 service->throttle_section_initialised = true;
\r
515 service->throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
516 if (! service->throttle_timer) {
\r
517 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
\r
521 monitor_service(service);
\r
524 /* Make sure service recovery actions are taken where necessary */
\r
525 void set_service_recovery(nssm_service_t *service) {
\r
526 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
527 ZeroMemory(&flag, sizeof(flag));
\r
528 flag.fFailureActionsOnNonCrashFailures = true;
\r
530 /* This functionality was added in Vista so the call may fail */
\r
531 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
532 unsigned long error = GetLastError();
\r
533 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
534 if (error != ERROR_INVALID_LEVEL) {
\r
535 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);
\r
540 int monitor_service(nssm_service_t *service) {
\r
541 /* Set service status to started */
\r
542 int ret = start_service(service);
\r
545 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);
\r
546 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
\r
549 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\r
551 /* Monitor service */
\r
552 if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
553 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
\r
559 TCHAR *service_control_text(unsigned long control) {
\r
561 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
562 case 0: return _T("START");
\r
563 case SERVICE_CONTROL_STOP: return _T("STOP");
\r
564 case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");
\r
565 case SERVICE_CONTROL_PAUSE: return _T("PAUSE");
\r
566 case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");
\r
567 case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");
\r
572 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {
\r
573 TCHAR *text = service_control_text(control);
\r
574 unsigned long event;
\r
577 /* "0x" + 8 x hex + NULL */
\r
578 text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));
\r
580 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
583 if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {
\r
584 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
585 HeapFree(GetProcessHeap(), 0, text);
\r
589 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
591 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
592 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
594 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
596 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
597 HeapFree(GetProcessHeap(), 0, text);
\r
601 /* Service control handler */
\r
602 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
603 nssm_service_t *service = (nssm_service_t *) context;
\r
606 case SERVICE_CONTROL_INTERROGATE:
\r
607 /* We always keep the service status up-to-date so this is a no-op. */
\r
610 case SERVICE_CONTROL_SHUTDOWN:
\r
611 case SERVICE_CONTROL_STOP:
\r
612 log_service_control(service->name, control, true);
\r
614 We MUST acknowledge the stop request promptly but we're committed to
\r
615 waiting for the application to exit. Spawn a new thread to wait
\r
616 while we acknowledge the request.
\r
618 if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
\r
619 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
622 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
623 to complete in time in this thread.
\r
625 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
626 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
627 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
629 stop_service(service, 0, true, true);
\r
633 case SERVICE_CONTROL_CONTINUE:
\r
634 log_service_control(service->name, control, true);
\r
635 service->throttle = 0;
\r
636 if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
\r
638 if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
639 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
640 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
642 service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
643 service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
\r
644 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
\r
645 SetServiceStatus(service->status_handle, &service->status);
\r
648 case SERVICE_CONTROL_PAUSE:
\r
650 We don't accept pause messages but it isn't possible to register
\r
651 only for continue messages so we have to handle this case.
\r
653 log_service_control(service->name, control, false);
\r
654 return ERROR_CALL_NOT_IMPLEMENTED;
\r
657 /* Unknown control */
\r
658 log_service_control(service->name, control, false);
\r
659 return ERROR_CALL_NOT_IMPLEMENTED;
\r
662 /* Start the service */
\r
663 int start_service(nssm_service_t *service) {
\r
664 service->stopping = false;
\r
665 service->allow_restart = true;
\r
667 if (service->process_handle) return 0;
\r
669 /* Allocate a STARTUPINFO structure for a new process */
\r
671 ZeroMemory(&si, sizeof(si));
\r
672 si.cb = sizeof(si);
\r
674 /* Allocate a PROCESSINFO structure for the process */
\r
675 PROCESS_INFORMATION pi;
\r
676 ZeroMemory(&pi, sizeof(pi));
\r
678 /* Get startup parameters */
\r
679 int ret = get_parameters(service, &si);
\r
681 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
\r
682 return stop_service(service, 2, true, true);
\r
685 /* Launch executable with arguments */
\r
686 TCHAR cmd[CMD_LENGTH];
\r
687 if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {
\r
688 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);
\r
689 close_output_handles(&si);
\r
690 return stop_service(service, 2, true, true);
\r
693 throttle_restart(service);
\r
695 bool inherit_handles = false;
\r
696 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
697 unsigned long flags = 0;
\r
699 flags |= CREATE_UNICODE_ENVIRONMENT;
\r
701 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {
\r
702 unsigned long exitcode = 3;
\r
703 unsigned long error = GetLastError();
\r
704 if (error == ERROR_INVALID_PARAMETER && service->env) {
\r
705 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);
\r
706 if (test_environment(service->env)) exitcode = 4;
\r
708 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
\r
709 close_output_handles(&si);
\r
710 return stop_service(service, exitcode, true, true);
\r
712 service->process_handle = pi.hProcess;
\r
713 service->pid = pi.dwProcessId;
\r
715 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
717 close_output_handles(&si);
\r
720 Wait for a clean startup before changing the service status to RUNNING
\r
721 but be mindful of the fact that we are blocking the service control manager
\r
722 so abandon the wait before too much time has elapsed.
\r
724 unsigned long delay = service->throttle_delay;
\r
725 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
726 TCHAR delay_milliseconds[16];
\r
727 _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);
\r
728 TCHAR deadline_milliseconds[16];
\r
729 _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);
\r
730 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
731 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
733 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
735 /* Signal successful start */
\r
736 service->status.dwCurrentState = SERVICE_RUNNING;
\r
737 SetServiceStatus(service->status_handle, &service->status);
\r
739 /* Continue waiting for a clean startup. */
\r
740 if (deadline == WAIT_TIMEOUT) {
\r
741 if (service->throttle_delay > delay) {
\r
742 if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
\r
744 else service->throttle = 0;
\r
750 /* Stop the service */
\r
751 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
\r
752 service->allow_restart = false;
\r
753 if (service->wait_handle) {
\r
754 UnregisterWait(service->wait_handle);
\r
755 service->wait_handle = 0;
\r
758 if (default_action && ! exitcode && ! graceful) {
\r
759 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
763 /* Signal we are stopping */
\r
765 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
766 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
767 SetServiceStatus(service->status_handle, &service->status);
\r
770 /* Nothing to do if service isn't running */
\r
771 if (service->pid) {
\r
772 /* Shut down service */
\r
773 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
\r
774 kill_process(service, service->process_handle, service->pid, 0);
\r
776 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
778 end_service((void *) service, true);
\r
780 /* Signal we stopped */
\r
782 service->status.dwCurrentState = SERVICE_STOPPED;
\r
784 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
785 service->status.dwServiceSpecificExitCode = exitcode;
\r
788 service->status.dwWin32ExitCode = NO_ERROR;
\r
789 service->status.dwServiceSpecificExitCode = 0;
\r
791 SetServiceStatus(service->status_handle, &service->status);
\r
797 /* Callback function triggered when the server exits */
\r
798 void CALLBACK end_service(void *arg, unsigned char why) {
\r
799 nssm_service_t *service = (nssm_service_t *) arg;
\r
801 if (service->stopping) return;
\r
803 service->stopping = true;
\r
805 /* Check exit code */
\r
806 unsigned long exitcode = 0;
\r
808 if (service->process_handle) {
\r
809 GetExitCodeProcess(service->process_handle, &exitcode);
\r
810 if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);
\r
811 CloseHandle(service->process_handle);
\r
813 else GetSystemTimeAsFileTime(&service->exit_time);
\r
815 service->process_handle = 0;
\r
818 Log that the service ended BEFORE logging about killing the process
\r
819 tree. See below for the possible values of the why argument.
\r
822 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);
\r
823 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
\r
827 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
828 if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);
\r
832 The why argument is true if our wait timed out or false otherwise.
\r
833 Our wait is infinite so why will never be true when called by the system.
\r
834 If it is indeed true, assume we were called from stop_service() because
\r
835 this is a controlled shutdown, and don't take any restart action.
\r
838 if (! service->allow_restart) return;
\r
840 /* What action should we take? */
\r
841 int action = NSSM_EXIT_RESTART;
\r
842 TCHAR action_string[ACTION_LEN];
\r
843 bool default_action;
\r
844 if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
\r
845 for (int i = 0; exit_action_strings[i]; i++) {
\r
846 if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
854 /* Try to restart the service or return failure code to service manager */
\r
855 case NSSM_EXIT_RESTART:
\r
856 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
\r
857 while (monitor_service(service)) {
\r
858 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
\r
863 /* Do nothing, just like srvany would */
\r
864 case NSSM_EXIT_IGNORE:
\r
865 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
\r
869 /* Tell the service manager we are finished */
\r
870 case NSSM_EXIT_REALLY:
\r
871 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
\r
872 stop_service(service, exitcode, true, default_action);
\r
875 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
876 case NSSM_EXIT_UNCLEAN:
\r
877 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
\r
878 stop_service(service, exitcode, false, default_action);
\r
885 void throttle_restart(nssm_service_t *service) {
\r
886 /* This can't be a restart if the service is already running. */
\r
887 if (! service->throttle++) return;
\r
889 int ms = throttle_milliseconds(service->throttle);
\r
891 if (service->throttle > 7) service->throttle = 8;
\r
893 TCHAR threshold[8], milliseconds[8];
\r
894 _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);
\r
895 _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);
\r
896 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
\r
898 if (use_critical_section) EnterCriticalSection(&service->throttle_section);
\r
899 else if (service->throttle_timer) {
\r
900 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
901 service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
902 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
905 service->status.dwCurrentState = SERVICE_PAUSED;
\r
906 SetServiceStatus(service->status_handle, &service->status);
\r
908 if (use_critical_section) {
\r
909 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
910 LeaveCriticalSection(&service->throttle_section);
\r
913 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\r
919 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
920 the number of milliseconds we expect the operation to take, and optionally
\r
921 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
922 operation completing or dwCheckPoint increasing, the system will consider the
\r
923 service to be hung.
\r
925 However the system will consider the service to be hung after 30000
\r
926 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
927 changed. Therefore if we want to wait longer than that we must periodically
\r
928 increase dwCheckPoint.
\r
930 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
931 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
932 time dwCheckPoint is also increased.
\r
934 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
935 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
936 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
937 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
940 Only doing both these things will prevent the system from killing the service.
\r
942 Returns: 1 if the wait timed out.
\r
943 0 if the wait completed.
\r
946 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {
\r
947 unsigned long interval;
\r
948 unsigned long waithint;
\r
950 unsigned long waited;
\r
951 TCHAR interval_milliseconds[16];
\r
952 TCHAR timeout_milliseconds[16];
\r
953 TCHAR waited_milliseconds[16];
\r
954 TCHAR *function = function_name;
\r
956 /* Add brackets to function name. */
\r
957 size_t funclen = _tcslen(function_name) + 3;
\r
958 TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));
\r
960 if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;
\r
963 _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);
\r
965 waithint = service->status.dwWaitHint;
\r
967 while (waited < timeout) {
\r
968 interval = timeout - waited;
\r
969 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
971 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
972 service->status.dwWaitHint += interval;
\r
973 service->status.dwCheckPoint++;
\r
974 SetServiceStatus(service->status_handle, &service->status);
\r
977 _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);
\r
978 _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);
\r
979 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
982 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
983 case WAIT_OBJECT_0:
\r
996 waited += interval;
\r
1000 if (func) HeapFree(GetProcessHeap(), 0, func);
\r