3 /* This is explicitly a wide string. */
\r
4 #define NSSM_LOGON_AS_SERVICE_RIGHT L"SeServiceLogonRight"
\r
7 bool use_critical_section;
\r
9 extern imports_t imports;
\r
10 extern settings_t settings[];
\r
12 const TCHAR *exit_action_strings[] = { _T("Restart"), _T("Ignore"), _T("Exit"), _T("Suicide"), 0 };
\r
13 const TCHAR *startup_strings[] = { _T("SERVICE_AUTO_START"), _T("SERVICE_DELAYED_AUTO_START"), _T("SERVICE_DEMAND_START"), _T("SERVICE_DISABLED"), 0 };
\r
15 static inline int throttle_milliseconds(unsigned long throttle) {
\r
16 /* pow() operates on doubles. */
\r
17 int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;
\r
22 Wrapper to be called in a new thread so that we can acknowledge a STOP
\r
23 control immediately.
\r
25 static unsigned long WINAPI shutdown_service(void *arg) {
\r
26 return stop_service((nssm_service_t *) arg, 0, true, true);
\r
29 /* Connect to the service manager */
\r
30 SC_HANDLE open_service_manager() {
\r
31 SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
\r
33 if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);
\r
40 QUERY_SERVICE_CONFIG *query_service_config(const TCHAR *service_name, SC_HANDLE service_handle) {
\r
41 QUERY_SERVICE_CONFIG *qsc;
\r
42 unsigned long bufsize;
\r
43 unsigned long error;
\r
45 QueryServiceConfig(service_handle, 0, 0, &bufsize);
\r
46 error = GetLastError();
\r
47 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
48 qsc = (QUERY_SERVICE_CONFIG *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufsize);
\r
50 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("QUERY_SERVICE_CONFIG"), _T("query_service_config()"), 0);
\r
55 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service_name, error_string(error), 0);
\r
59 if (! QueryServiceConfig(service_handle, qsc, bufsize, &bufsize)) {
\r
60 HeapFree(GetProcessHeap(), 0, qsc);
\r
61 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service_name, error_string(GetLastError()), 0);
\r
68 int set_service_description(const TCHAR *service_name, SC_HANDLE service_handle, TCHAR *buffer) {
\r
69 SERVICE_DESCRIPTION description;
\r
70 ZeroMemory(&description, sizeof(description));
\r
72 lpDescription must be NULL if we aren't changing, the new description
\r
75 if (buffer && buffer[0]) description.lpDescription = buffer;
\r
76 else description.lpDescription = _T("");
\r
78 if (ChangeServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, &description)) return 0;
\r
80 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service_name, error_string(GetLastError()), 0);
\r
84 int get_service_description(const TCHAR *service_name, SC_HANDLE service_handle, unsigned long len, TCHAR *buffer) {
\r
85 if (! buffer) return 1;
\r
87 unsigned long bufsize;
\r
88 QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);
\r
89 unsigned long error = GetLastError();
\r
90 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
91 SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);
\r
92 if (! description) {
\r
93 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("get_service_description()"));
\r
97 if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {
\r
98 if (description->lpDescription) _sntprintf_s(buffer, len, _TRUNCATE, _T("%s"), description->lpDescription);
\r
99 else ZeroMemory(buffer, len * sizeof(TCHAR));
\r
100 HeapFree(GetProcessHeap(), 0, description);
\r
104 HeapFree(GetProcessHeap(), 0, description);
\r
105 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));
\r
110 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));
\r
117 int get_service_startup(const TCHAR *service_name, SC_HANDLE service_handle, const QUERY_SERVICE_CONFIG *qsc, unsigned long *startup) {
\r
118 if (! qsc) return 1;
\r
120 switch (qsc->dwStartType) {
\r
121 case SERVICE_DEMAND_START: *startup = NSSM_STARTUP_MANUAL; break;
\r
122 case SERVICE_DISABLED: *startup = NSSM_STARTUP_DISABLED; break;
\r
123 default: *startup = NSSM_STARTUP_AUTOMATIC;
\r
126 if (*startup != NSSM_STARTUP_AUTOMATIC) return 0;
\r
128 /* Check for delayed start. */
\r
129 unsigned long bufsize;
\r
130 unsigned long error;
\r
131 QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, 0, 0, &bufsize);
\r
132 error = GetLastError();
\r
133 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
134 SERVICE_DELAYED_AUTO_START_INFO *info = (SERVICE_DELAYED_AUTO_START_INFO *) HeapAlloc(GetProcessHeap(), 0, bufsize);
\r
136 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_DELAYED_AUTO_START_INFO"), _T("get_service_startup()"));
\r
140 if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (unsigned char *) info, bufsize, &bufsize)) {
\r
141 if (info->fDelayedAutostart) *startup = NSSM_STARTUP_DELAYED;
\r
142 HeapFree(GetProcessHeap(), 0, info);
\r
146 error = GetLastError();
\r
147 if (error != ERROR_INVALID_LEVEL) {
\r
148 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
153 else if (error != ERROR_INVALID_LEVEL) {
\r
154 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));
\r
161 int get_service_username(const TCHAR *service_name, const QUERY_SERVICE_CONFIG *qsc, TCHAR **username, size_t *usernamelen) {
\r
162 if (! username) return 1;
\r
163 if (! usernamelen) return 1;
\r
168 if (! qsc) return 1;
\r
170 if (str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;
\r
172 size_t len = _tcslen(qsc->lpServiceStartName);
\r
173 *username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(TCHAR));
\r
175 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("username"), _T("get_service_username()"));
\r
179 memmove(*username, qsc->lpServiceStartName, (len + 1) * sizeof(TCHAR));
\r
180 *usernamelen = len;
\r
185 int grant_logon_as_service(const TCHAR *username) {
\r
186 if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;
\r
188 /* Open Policy object. */
\r
189 LSA_OBJECT_ATTRIBUTES attributes;
\r
190 ZeroMemory(&attributes, sizeof(attributes));
\r
194 NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, &policy);
\r
196 print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status)));
\r
200 /* Look up SID for the account. */
\r
201 LSA_UNICODE_STRING lsa_username;
\r
203 lsa_username.Buffer = (wchar_t *) username;
\r
204 lsa_username.Length = (unsigned short) _tcslen(username) * sizeof(TCHAR);
\r
205 lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR);
\r
208 mbstowcs_s(&buflen, NULL, 0, username, _TRUNCATE);
\r
209 lsa_username.MaximumLength = buflen * sizeof(wchar_t);
\r
210 lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t);
\r
211 lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength);
\r
212 if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, username, _TRUNCATE);
\r
215 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("grant_logon_as_service()"));
\r
220 LSA_REFERENCED_DOMAIN_LIST *translated_domains;
\r
221 LSA_TRANSLATED_SID *translated_sid;
\r
222 status = LsaLookupNames(policy, 1, &lsa_username, &translated_domains, &translated_sid);
\r
224 HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);
\r
227 LsaFreeMemory(translated_domains);
\r
228 LsaFreeMemory(translated_sid);
\r
230 print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status)));
\r
234 if (translated_sid->Use != SidTypeUser) {
\r
235 LsaFreeMemory(translated_domains);
\r
236 LsaFreeMemory(translated_sid);
\r
238 print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
\r
242 LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];
\r
243 if (! trust || ! IsValidSid(trust->Sid)) {
\r
244 LsaFreeMemory(translated_domains);
\r
245 LsaFreeMemory(translated_sid);
\r
247 print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
\r
251 /* GetSidSubAuthority*() return pointers! */
\r
252 unsigned char *n = GetSidSubAuthorityCount(trust->Sid);
\r
254 /* Convert translated SID to SID. */
\r
255 SID *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));
\r
257 LsaFreeMemory(translated_domains);
\r
258 LsaFreeMemory(translated_sid);
\r
260 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("grant_logon_as_service"));
\r
264 unsigned long error;
\r
265 if (! InitializeSid(sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {
\r
266 error = GetLastError();
\r
267 HeapFree(GetProcessHeap(), 0, sid);
\r
268 LsaFreeMemory(translated_domains);
\r
269 LsaFreeMemory(translated_sid);
\r
271 print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));
\r
275 for (unsigned char i = 0; i <= *n; i++) {
\r
276 unsigned long *sub = GetSidSubAuthority(sid, i);
\r
277 if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);
\r
278 else *sub = translated_sid->RelativeId;
\r
281 LsaFreeMemory(translated_domains);
\r
282 LsaFreeMemory(translated_sid);
\r
284 /* Check if the SID has the "Log on as a service" right. */
\r
285 LSA_UNICODE_STRING lsa_right;
\r
286 lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;
\r
287 lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);
\r
288 lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);
\r
290 LSA_UNICODE_STRING *rights;
\r
291 unsigned long count = ~0;
\r
292 status = LsaEnumerateAccountRights(policy, sid, &rights, &count);
\r
295 If the account has no rights set LsaEnumerateAccountRights() will return
\r
296 STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.
\r
298 error = LsaNtStatusToWinError(status);
\r
299 if (error != ERROR_FILE_NOT_FOUND) {
\r
300 HeapFree(GetProcessHeap(), 0, sid);
\r
302 print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));
\r
307 for (unsigned long i = 0; i < count; i++) {
\r
308 if (rights[i].Length != lsa_right.Length) continue;
\r
309 if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;
\r
310 /* The SID has the right. */
\r
311 HeapFree(GetProcessHeap(), 0, sid);
\r
312 LsaFreeMemory(rights);
\r
316 LsaFreeMemory(rights);
\r
318 /* Add the right. */
\r
319 status = LsaAddAccountRights(policy, sid, &lsa_right, 1);
\r
320 HeapFree(GetProcessHeap(), 0, sid);
\r
323 print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));
\r
327 print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);
\r
331 /* Set default values which aren't zero. */
\r
332 void set_nssm_service_defaults(nssm_service_t *service) {
\r
333 if (! service) return;
\r
335 service->type = SERVICE_WIN32_OWN_PROCESS;
\r
336 service->stdin_sharing = NSSM_STDIN_SHARING;
\r
337 service->stdin_disposition = NSSM_STDIN_DISPOSITION;
\r
338 service->stdin_flags = NSSM_STDIN_FLAGS;
\r
339 service->stdout_sharing = NSSM_STDOUT_SHARING;
\r
340 service->stdout_disposition = NSSM_STDOUT_DISPOSITION;
\r
341 service->stdout_flags = NSSM_STDOUT_FLAGS;
\r
342 service->stderr_sharing = NSSM_STDERR_SHARING;
\r
343 service->stderr_disposition = NSSM_STDERR_DISPOSITION;
\r
344 service->stderr_flags = NSSM_STDERR_FLAGS;
\r
345 service->throttle_delay = NSSM_RESET_THROTTLE_RESTART;
\r
346 service->stop_method = ~0;
\r
347 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
348 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
349 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
352 /* Allocate and zero memory for a service. */
\r
353 nssm_service_t *alloc_nssm_service() {
\r
354 nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));
\r
355 if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("alloc_nssm_service()"), 0);
\r
359 /* Free memory for a service. */
\r
360 void cleanup_nssm_service(nssm_service_t *service) {
\r
361 if (! service) return;
\r
362 if (service->username) HeapFree(GetProcessHeap(), 0, service->username);
\r
363 if (service->password) {
\r
364 SecureZeroMemory(service->password, service->passwordlen);
\r
365 HeapFree(GetProcessHeap(), 0, service->password);
\r
367 if (service->env) HeapFree(GetProcessHeap(), 0, service->env);
\r
368 if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);
\r
369 if (service->handle) CloseServiceHandle(service->handle);
\r
370 if (service->process_handle) CloseHandle(service->process_handle);
\r
371 if (service->wait_handle) UnregisterWait(service->process_handle);
\r
372 if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);
\r
373 if (service->throttle_timer) CloseHandle(service->throttle_timer);
\r
374 HeapFree(GetProcessHeap(), 0, service);
\r
377 /* About to install the service */
\r
378 int pre_install_service(int argc, TCHAR **argv) {
\r
379 nssm_service_t *service = alloc_nssm_service();
\r
380 set_nssm_service_defaults(service);
\r
381 if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
383 /* Show the dialogue box if we didn't give the service name and path */
\r
384 if (argc < 2) return nssm_gui(IDD_INSTALL, service);
\r
387 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("service"), _T("pre_install_service()"));
\r
390 _sntprintf_s(service->exe, _countof(service->exe), _TRUNCATE, _T("%s"), argv[1]);
\r
392 /* Arguments are optional */
\r
393 size_t flagslen = 0;
\r
396 for (i = 2; i < argc; i++) flagslen += _tcslen(argv[i]) + 1;
\r
397 if (! flagslen) flagslen = 1;
\r
398 if (flagslen > _countof(service->flags)) {
\r
399 print_message(stderr, NSSM_MESSAGE_FLAGS_TOO_LONG);
\r
403 for (i = 2; i < argc; i++) {
\r
404 size_t len = _tcslen(argv[i]);
\r
405 memmove(service->flags + s, argv[i], len * sizeof(TCHAR));
\r
407 if (i < argc - 1) service->flags[s++] = _T(' ');
\r
410 /* Work out directory name */
\r
411 _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);
\r
412 strip_basename(service->dir);
\r
414 int ret = install_service(service);
\r
415 cleanup_nssm_service(service);
\r
419 /* About to edit the service. */
\r
420 int pre_edit_service(int argc, TCHAR **argv) {
\r
421 /* Require service name. */
\r
422 if (argc < 2) return usage(1);
\r
424 /* Are we editing on the command line? */
\r
425 enum { MODE_EDITING, MODE_GETTING, MODE_SETTING, MODE_RESETTING } mode = MODE_EDITING;
\r
426 const TCHAR *verb = argv[0];
\r
427 const TCHAR *service_name = argv[1];
\r
428 bool getting = false;
\r
429 bool unsetting = false;
\r
431 /* Minimum number of arguments. */
\r
433 /* Index of first value. */
\r
436 if (str_equiv(verb, _T("get"))) {
\r
438 mode = MODE_GETTING;
\r
440 else if (str_equiv(verb, _T("set"))) {
\r
442 mode = MODE_SETTING;
\r
444 else if (str_equiv(verb, _T("reset")) || str_equiv(verb, _T("unset"))) {
\r
446 mode = MODE_RESETTING;
\r
448 if (argc < mandatory) return usage(1);
\r
450 const TCHAR *parameter = 0;
\r
451 settings_t *setting = 0;
\r
454 /* Validate the parameter. */
\r
455 if (mandatory > 2) {
\r
456 bool additional_mandatory = false;
\r
458 parameter = argv[2];
\r
459 for (i = 0; settings[i].name; i++) {
\r
460 setting = &settings[i];
\r
461 if (! str_equiv(setting->name, parameter)) continue;
\r
462 if (((setting->additional & ADDITIONAL_GETTING) && mode == MODE_GETTING) || ((setting->additional & ADDITIONAL_SETTING) && mode == MODE_SETTING) || ((setting->additional & ADDITIONAL_RESETTING) && mode == MODE_RESETTING)) {
\r
463 additional_mandatory = true;
\r
468 if (! settings[i].name) {
\r
469 print_message(stderr, NSSM_MESSAGE_INVALID_PARAMETER, parameter);
\r
470 for (i = 0; settings[i].name; i++) _ftprintf(stderr, _T("%s\n"), settings[i].name);
\r
473 if (argc < mandatory) return usage(1);
\r
476 if (additional_mandatory) {
\r
477 additional = argv[3];
\r
480 else additional = argv[remainder];
\r
483 nssm_service_t *service = alloc_nssm_service();
\r
484 _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), service_name);
\r
486 /* Open service manager */
\r
487 SC_HANDLE services = open_service_manager();
\r
489 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
493 /* Try to open the service */
\r
494 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
495 if (! service->handle) {
\r
496 CloseServiceHandle(services);
\r
497 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
501 /* Get system details. */
\r
502 QUERY_SERVICE_CONFIG *qsc = query_service_config(service->name, service->handle);
\r
504 CloseHandle(service->handle);
\r
505 CloseServiceHandle(services);
\r
509 service->type = qsc->dwServiceType;
\r
510 if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {
\r
511 if (mode != MODE_GETTING) {
\r
512 HeapFree(GetProcessHeap(), 0, qsc);
\r
513 CloseHandle(service->handle);
\r
514 CloseServiceHandle(services);
\r
515 print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, NSSM_WIN32_OWN_PROCESS, 0);
\r
520 if (get_service_startup(service->name, service->handle, qsc, &service->startup)) {
\r
521 if (mode != MODE_GETTING) {
\r
522 HeapFree(GetProcessHeap(), 0, qsc);
\r
523 CloseHandle(service->handle);
\r
524 CloseServiceHandle(services);
\r
529 if (get_service_username(service->name, qsc, &service->username, &service->usernamelen)) {
\r
530 if (mode != MODE_GETTING) {
\r
531 HeapFree(GetProcessHeap(), 0, qsc);
\r
532 CloseHandle(service->handle);
\r
533 CloseServiceHandle(services);
\r
538 _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);
\r
540 /* Get the canonical service name. We open it case insensitively. */
\r
541 unsigned long bufsize = _countof(service->name);
\r
542 GetServiceKeyName(services, service->displayname, service->name, &bufsize);
\r
544 /* Remember the executable in case it isn't NSSM. */
\r
545 _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);
\r
546 HeapFree(GetProcessHeap(), 0, qsc);
\r
548 /* Get extended system details. */
\r
549 if (get_service_description(service->name, service->handle, _countof(service->description), service->description)) {
\r
550 if (mode != MODE_GETTING) {
\r
551 CloseHandle(service->handle);
\r
552 CloseServiceHandle(services);
\r
557 /* Get NSSM details. */
\r
558 get_parameters(service, 0);
\r
560 CloseServiceHandle(services);
\r
562 if (! service->exe[0]) {
\r
563 service->native = true;
\r
564 if (mode != MODE_GETTING) print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);
\r
567 /* Editing with the GUI. */
\r
568 if (mode == MODE_EDITING) {
\r
569 nssm_gui(IDD_EDIT, service);
\r
573 /* Trying to manage App* parameters for a non-NSSM service. */
\r
574 if (! setting->native && service->native) {
\r
575 CloseHandle(service->handle);
\r
576 print_message(stderr, NSSM_MESSAGE_NATIVE_PARAMETER, setting->name, NSSM);
\r
584 if (mode == MODE_GETTING) {
\r
585 if (! service->native) {
\r
586 key = open_registry(service->name, KEY_READ);
\r
587 if (! key) return 4;
\r
590 if (setting->native) ret = get_setting(service->name, service->handle, setting, &value, additional);
\r
591 else ret = get_setting(service->name, key, setting, &value, additional);
\r
593 CloseHandle(service->handle);
\r
597 switch (setting->type) {
\r
598 case REG_EXPAND_SZ:
\r
601 _tprintf(_T("%s\n"), value.string ? value.string : _T(""));
\r
602 HeapFree(GetProcessHeap(), 0, value.string);
\r
606 _tprintf(_T("%u\n"), value.numeric);
\r
610 if (! service->native) RegCloseKey(key);
\r
611 CloseHandle(service->handle);
\r
615 /* Build the value. */
\r
616 if (mode == MODE_RESETTING) {
\r
617 /* Unset the parameter. */
\r
621 /* Set the parameter. */
\r
623 size_t delimiterlen = (setting->additional & ADDITIONAL_CRLF) ? 2 : 1;
\r
624 for (i = remainder; i < argc; i++) len += _tcslen(argv[i]) + delimiterlen;
\r
627 value.string = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));
\r
628 if (! value.string) {
\r
629 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("value"), _T("edit_service()"));
\r
630 CloseHandle(service->handle);
\r
635 for (i = remainder; i < argc; i++) {
\r
636 size_t len = _tcslen(argv[i]);
\r
637 memmove(value.string + s, argv[i], len * sizeof(TCHAR));
\r
639 if (i < argc - 1) {
\r
640 if (setting->additional & ADDITIONAL_CRLF) {
\r
641 value.string[s++] = _T('\r');
\r
642 value.string[s++] = _T('\n');
\r
644 else value.string[s++] = _T(' ');
\r
647 value.string[s] = _T('\0');
\r
650 if (! service->native) {
\r
651 key = open_registry(service->name, KEY_WRITE);
\r
653 if (value.string) HeapFree(GetProcessHeap(), 0, value.string);
\r
658 if (setting->native) ret = set_setting(service->name, service->handle, setting, &value, additional);
\r
659 else ret = set_setting(service->name, key, setting, &value, additional);
\r
660 if (value.string) HeapFree(GetProcessHeap(), 0, value.string);
\r
662 if (! service->native) RegCloseKey(key);
\r
663 CloseHandle(service->handle);
\r
667 if (! service->native) RegCloseKey(key);
\r
668 CloseHandle(service->handle);
\r
673 /* About to remove the service */
\r
674 int pre_remove_service(int argc, TCHAR **argv) {
\r
675 nssm_service_t *service = alloc_nssm_service();
\r
676 set_nssm_service_defaults(service);
\r
677 if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
679 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
680 if (argc < 2) return nssm_gui(IDD_REMOVE, service);
\r
681 if (str_equiv(argv[1], _T("confirm"))) {
\r
682 int ret = remove_service(service);
\r
683 cleanup_nssm_service(service);
\r
686 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
690 /* Install the service */
\r
691 int install_service(nssm_service_t *service) {
\r
692 if (! service) return 1;
\r
694 /* Open service manager */
\r
695 SC_HANDLE services = open_service_manager();
\r
697 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
698 cleanup_nssm_service(service);
\r
702 /* Get path of this program */
\r
703 GetModuleFileName(0, service->image, _countof(service->image));
\r
705 /* Create the service - settings will be changed in edit_service() */
\r
706 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
707 if (! service->handle) {
\r
708 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
709 CloseServiceHandle(services);
\r
713 if (edit_service(service, false)) {
\r
714 DeleteService(service->handle);
\r
715 CloseServiceHandle(services);
\r
719 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
722 CloseServiceHandle(services);
\r
727 /* Edit the service. */
\r
728 int edit_service(nssm_service_t *service, bool editing) {
\r
729 if (! service) return 1;
\r
732 The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS
\r
733 and SERVICE_INTERACTIVE_PROCESS.
\r
735 service->type &= SERVICE_INTERACTIVE_PROCESS;
\r
736 service->type |= SERVICE_WIN32_OWN_PROCESS;
\r
738 /* Startup type. */
\r
739 unsigned long startup;
\r
740 switch (service->startup) {
\r
741 case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;
\r
742 case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;
\r
743 default: startup = SERVICE_AUTO_START;
\r
746 /* Display name. */
\r
747 if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);
\r
750 Username must be NULL if we aren't changing or an account name.
\r
751 We must explicitly use LOCALSYSTEM to change it when we are editing.
\r
752 Password must be NULL if we aren't changing, a password or "".
\r
753 Empty passwords are valid but we won't allow them in the GUI.
\r
755 TCHAR *username = 0;
\r
756 TCHAR *password = 0;
\r
757 if (service->usernamelen) {
\r
758 username = service->username;
\r
759 if (service->passwordlen) password = service->password;
\r
760 else password = _T("");
\r
762 else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;
\r
764 if (grant_logon_as_service(username)) {
\r
765 print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);
\r
769 if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {
\r
770 print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
\r
774 if (service->description[0] || editing) {
\r
775 set_service_description(service->name, service->handle, service->description);
\r
778 SERVICE_DELAYED_AUTO_START_INFO delayed;
\r
779 ZeroMemory(&delayed, sizeof(delayed));
\r
780 if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;
\r
781 else delayed.fDelayedAutostart = 0;
\r
782 /* Delayed startup isn't supported until Vista. */
\r
783 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {
\r
784 unsigned long error = GetLastError();
\r
785 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
786 if (error != ERROR_INVALID_LEVEL) {
\r
787 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);
\r
791 /* Don't mess with parameters which aren't ours. */
\r
792 if (! service->native) {
\r
793 /* Now we need to put the parameters into the registry */
\r
794 if (create_parameters(service, editing)) {
\r
795 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
799 set_service_recovery(service);
\r
805 /* Remove the service */
\r
806 int remove_service(nssm_service_t *service) {
\r
807 if (! service) return 1;
\r
809 /* Open service manager */
\r
810 SC_HANDLE services = open_service_manager();
\r
812 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
816 /* Try to open the service */
\r
817 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
818 if (! service->handle) {
\r
819 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
820 CloseServiceHandle(services);
\r
824 /* Get the canonical service name. We open it case insensitively. */
\r
825 unsigned long bufsize = _countof(service->displayname);
\r
826 GetServiceDisplayName(services, service->name, service->displayname, &bufsize);
\r
827 bufsize = _countof(service->name);
\r
828 GetServiceKeyName(services, service->displayname, service->name, &bufsize);
\r
830 /* Try to delete the service */
\r
831 if (! DeleteService(service->handle)) {
\r
832 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
833 CloseServiceHandle(services);
\r
838 CloseServiceHandle(services);
\r
840 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\r
844 /* Service initialisation */
\r
845 void WINAPI service_main(unsigned long argc, TCHAR **argv) {
\r
846 nssm_service_t *service = alloc_nssm_service();
\r
847 if (! service) return;
\r
849 if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {
\r
850 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);
\r
854 /* We can use a condition variable in a critical section on Vista or later. */
\r
855 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
856 else use_critical_section = false;
\r
858 /* Initialise status */
\r
859 ZeroMemory(&service->status, sizeof(service->status));
\r
860 service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
861 service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
862 service->status.dwWin32ExitCode = NO_ERROR;
\r
863 service->status.dwServiceSpecificExitCode = 0;
\r
864 service->status.dwCheckPoint = 0;
\r
865 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
867 /* Signal we AREN'T running the server */
\r
868 service->process_handle = 0;
\r
871 /* Register control handler */
\r
872 service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
\r
873 if (! service->status_handle) {
\r
874 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
878 log_service_control(service->name, 0, true);
\r
880 service->status.dwCurrentState = SERVICE_START_PENDING;
\r
881 service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
\r
882 SetServiceStatus(service->status_handle, &service->status);
\r
885 /* Try to create the exit action parameters; we don't care if it fails */
\r
886 create_exit_action(service->name, exit_action_strings[0], false);
\r
888 SC_HANDLE services = open_service_manager();
\r
890 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
891 set_service_recovery(service);
\r
892 CloseServiceHandle(services);
\r
896 /* Used for signalling a resume if the service pauses when throttled. */
\r
897 if (use_critical_section) {
\r
898 InitializeCriticalSection(&service->throttle_section);
\r
899 service->throttle_section_initialised = true;
\r
902 service->throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
903 if (! service->throttle_timer) {
\r
904 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
\r
908 monitor_service(service);
\r
911 /* Make sure service recovery actions are taken where necessary */
\r
912 void set_service_recovery(nssm_service_t *service) {
\r
913 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
914 ZeroMemory(&flag, sizeof(flag));
\r
915 flag.fFailureActionsOnNonCrashFailures = true;
\r
917 /* This functionality was added in Vista so the call may fail */
\r
918 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
919 unsigned long error = GetLastError();
\r
920 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
921 if (error != ERROR_INVALID_LEVEL) {
\r
922 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);
\r
927 int monitor_service(nssm_service_t *service) {
\r
928 /* Set service status to started */
\r
929 int ret = start_service(service);
\r
932 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);
\r
933 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
\r
936 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\r
938 /* Monitor service */
\r
939 if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
940 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
\r
946 TCHAR *service_control_text(unsigned long control) {
\r
948 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
949 case 0: return _T("START");
\r
950 case SERVICE_CONTROL_STOP: return _T("STOP");
\r
951 case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");
\r
952 case SERVICE_CONTROL_PAUSE: return _T("PAUSE");
\r
953 case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");
\r
954 case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");
\r
959 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {
\r
960 TCHAR *text = service_control_text(control);
\r
961 unsigned long event;
\r
964 /* "0x" + 8 x hex + NULL */
\r
965 text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));
\r
967 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
970 if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {
\r
971 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
972 HeapFree(GetProcessHeap(), 0, text);
\r
976 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
978 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
979 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
981 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
983 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
984 HeapFree(GetProcessHeap(), 0, text);
\r
988 /* Service control handler */
\r
989 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
990 nssm_service_t *service = (nssm_service_t *) context;
\r
993 case SERVICE_CONTROL_INTERROGATE:
\r
994 /* We always keep the service status up-to-date so this is a no-op. */
\r
997 case SERVICE_CONTROL_SHUTDOWN:
\r
998 case SERVICE_CONTROL_STOP:
\r
999 log_service_control(service->name, control, true);
\r
1001 We MUST acknowledge the stop request promptly but we're committed to
\r
1002 waiting for the application to exit. Spawn a new thread to wait
\r
1003 while we acknowledge the request.
\r
1005 if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
\r
1006 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
1009 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
1010 to complete in time in this thread.
\r
1012 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
1013 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
1014 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
1016 stop_service(service, 0, true, true);
\r
1020 case SERVICE_CONTROL_CONTINUE:
\r
1021 log_service_control(service->name, control, true);
\r
1022 service->throttle = 0;
\r
1023 if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
\r
1025 if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
1026 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
1027 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
1029 service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
1030 service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
\r
1031 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
\r
1032 SetServiceStatus(service->status_handle, &service->status);
\r
1035 case SERVICE_CONTROL_PAUSE:
\r
1037 We don't accept pause messages but it isn't possible to register
\r
1038 only for continue messages so we have to handle this case.
\r
1040 log_service_control(service->name, control, false);
\r
1041 return ERROR_CALL_NOT_IMPLEMENTED;
\r
1044 /* Unknown control */
\r
1045 log_service_control(service->name, control, false);
\r
1046 return ERROR_CALL_NOT_IMPLEMENTED;
\r
1049 /* Start the service */
\r
1050 int start_service(nssm_service_t *service) {
\r
1051 service->stopping = false;
\r
1052 service->allow_restart = true;
\r
1054 if (service->process_handle) return 0;
\r
1056 /* Allocate a STARTUPINFO structure for a new process */
\r
1058 ZeroMemory(&si, sizeof(si));
\r
1059 si.cb = sizeof(si);
\r
1061 /* Allocate a PROCESSINFO structure for the process */
\r
1062 PROCESS_INFORMATION pi;
\r
1063 ZeroMemory(&pi, sizeof(pi));
\r
1065 /* Get startup parameters */
\r
1066 int ret = get_parameters(service, &si);
\r
1068 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
\r
1069 return stop_service(service, 2, true, true);
\r
1072 /* Launch executable with arguments */
\r
1073 TCHAR cmd[CMD_LENGTH];
\r
1074 if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {
\r
1075 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);
\r
1076 close_output_handles(&si);
\r
1077 return stop_service(service, 2, true, true);
\r
1080 throttle_restart(service);
\r
1082 bool inherit_handles = false;
\r
1083 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
1084 unsigned long flags = 0;
\r
1086 flags |= CREATE_UNICODE_ENVIRONMENT;
\r
1088 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {
\r
1089 unsigned long exitcode = 3;
\r
1090 unsigned long error = GetLastError();
\r
1091 if (error == ERROR_INVALID_PARAMETER && service->env) {
\r
1092 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);
\r
1093 if (test_environment(service->env)) exitcode = 4;
\r
1095 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
\r
1096 close_output_handles(&si);
\r
1097 return stop_service(service, exitcode, true, true);
\r
1099 service->process_handle = pi.hProcess;
\r
1100 service->pid = pi.dwProcessId;
\r
1102 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
1104 close_output_handles(&si);
\r
1107 Wait for a clean startup before changing the service status to RUNNING
\r
1108 but be mindful of the fact that we are blocking the service control manager
\r
1109 so abandon the wait before too much time has elapsed.
\r
1111 unsigned long delay = service->throttle_delay;
\r
1112 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
1113 TCHAR delay_milliseconds[16];
\r
1114 _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);
\r
1115 TCHAR deadline_milliseconds[16];
\r
1116 _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);
\r
1117 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
1118 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
1120 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
1122 /* Signal successful start */
\r
1123 service->status.dwCurrentState = SERVICE_RUNNING;
\r
1124 SetServiceStatus(service->status_handle, &service->status);
\r
1126 /* Continue waiting for a clean startup. */
\r
1127 if (deadline == WAIT_TIMEOUT) {
\r
1128 if (service->throttle_delay > delay) {
\r
1129 if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
\r
1131 else service->throttle = 0;
\r
1137 /* Stop the service */
\r
1138 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
\r
1139 service->allow_restart = false;
\r
1140 if (service->wait_handle) {
\r
1141 UnregisterWait(service->wait_handle);
\r
1142 service->wait_handle = 0;
\r
1145 if (default_action && ! exitcode && ! graceful) {
\r
1146 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
1150 /* Signal we are stopping */
\r
1152 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
1153 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
1154 SetServiceStatus(service->status_handle, &service->status);
\r
1157 /* Nothing to do if service isn't running */
\r
1158 if (service->pid) {
\r
1159 /* Shut down service */
\r
1160 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
\r
1161 kill_process(service, service->process_handle, service->pid, 0);
\r
1163 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
1165 end_service((void *) service, true);
\r
1167 /* Signal we stopped */
\r
1169 service->status.dwCurrentState = SERVICE_STOPPED;
\r
1171 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
1172 service->status.dwServiceSpecificExitCode = exitcode;
\r
1175 service->status.dwWin32ExitCode = NO_ERROR;
\r
1176 service->status.dwServiceSpecificExitCode = 0;
\r
1178 SetServiceStatus(service->status_handle, &service->status);
\r
1184 /* Callback function triggered when the server exits */
\r
1185 void CALLBACK end_service(void *arg, unsigned char why) {
\r
1186 nssm_service_t *service = (nssm_service_t *) arg;
\r
1188 if (service->stopping) return;
\r
1190 service->stopping = true;
\r
1192 /* Check exit code */
\r
1193 unsigned long exitcode = 0;
\r
1195 if (service->process_handle) {
\r
1196 GetExitCodeProcess(service->process_handle, &exitcode);
\r
1197 if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);
\r
1198 CloseHandle(service->process_handle);
\r
1200 else GetSystemTimeAsFileTime(&service->exit_time);
\r
1202 service->process_handle = 0;
\r
1205 Log that the service ended BEFORE logging about killing the process
\r
1206 tree. See below for the possible values of the why argument.
\r
1209 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);
\r
1210 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
\r
1214 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
1215 if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);
\r
1219 The why argument is true if our wait timed out or false otherwise.
\r
1220 Our wait is infinite so why will never be true when called by the system.
\r
1221 If it is indeed true, assume we were called from stop_service() because
\r
1222 this is a controlled shutdown, and don't take any restart action.
\r
1225 if (! service->allow_restart) return;
\r
1227 /* What action should we take? */
\r
1228 int action = NSSM_EXIT_RESTART;
\r
1229 TCHAR action_string[ACTION_LEN];
\r
1230 bool default_action;
\r
1231 if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
\r
1232 for (int i = 0; exit_action_strings[i]; i++) {
\r
1233 if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
1241 /* Try to restart the service or return failure code to service manager */
\r
1242 case NSSM_EXIT_RESTART:
\r
1243 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
\r
1244 while (monitor_service(service)) {
\r
1245 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
\r
1250 /* Do nothing, just like srvany would */
\r
1251 case NSSM_EXIT_IGNORE:
\r
1252 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
\r
1256 /* Tell the service manager we are finished */
\r
1257 case NSSM_EXIT_REALLY:
\r
1258 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
\r
1259 stop_service(service, exitcode, true, default_action);
\r
1262 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
1263 case NSSM_EXIT_UNCLEAN:
\r
1264 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
\r
1265 stop_service(service, exitcode, false, default_action);
\r
1272 void throttle_restart(nssm_service_t *service) {
\r
1273 /* This can't be a restart if the service is already running. */
\r
1274 if (! service->throttle++) return;
\r
1276 int ms = throttle_milliseconds(service->throttle);
\r
1278 if (service->throttle > 7) service->throttle = 8;
\r
1280 TCHAR threshold[8], milliseconds[8];
\r
1281 _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);
\r
1282 _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);
\r
1283 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
\r
1285 if (use_critical_section) EnterCriticalSection(&service->throttle_section);
\r
1286 else if (service->throttle_timer) {
\r
1287 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
1288 service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
1289 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
1292 service->status.dwCurrentState = SERVICE_PAUSED;
\r
1293 SetServiceStatus(service->status_handle, &service->status);
\r
1295 if (use_critical_section) {
\r
1296 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
1297 LeaveCriticalSection(&service->throttle_section);
\r
1300 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\r
1306 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
1307 the number of milliseconds we expect the operation to take, and optionally
\r
1308 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
1309 operation completing or dwCheckPoint increasing, the system will consider the
\r
1310 service to be hung.
\r
1312 However the system will consider the service to be hung after 30000
\r
1313 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
1314 changed. Therefore if we want to wait longer than that we must periodically
\r
1315 increase dwCheckPoint.
\r
1317 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
1318 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
1319 time dwCheckPoint is also increased.
\r
1321 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
1322 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
1323 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
1324 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
1327 Only doing both these things will prevent the system from killing the service.
\r
1329 Returns: 1 if the wait timed out.
\r
1330 0 if the wait completed.
\r
1333 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {
\r
1334 unsigned long interval;
\r
1335 unsigned long waithint;
\r
1336 unsigned long ret;
\r
1337 unsigned long waited;
\r
1338 TCHAR interval_milliseconds[16];
\r
1339 TCHAR timeout_milliseconds[16];
\r
1340 TCHAR waited_milliseconds[16];
\r
1341 TCHAR *function = function_name;
\r
1343 /* Add brackets to function name. */
\r
1344 size_t funclen = _tcslen(function_name) + 3;
\r
1345 TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));
\r
1347 if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;
\r
1350 _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);
\r
1352 waithint = service->status.dwWaitHint;
\r
1354 while (waited < timeout) {
\r
1355 interval = timeout - waited;
\r
1356 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
1358 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
1359 service->status.dwWaitHint += interval;
\r
1360 service->status.dwCheckPoint++;
\r
1361 SetServiceStatus(service->status_handle, &service->status);
\r
1364 _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);
\r
1365 _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);
\r
1366 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
1369 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
1370 case WAIT_OBJECT_0:
\r
1374 case WAIT_TIMEOUT:
\r
1383 waited += interval;
\r
1387 if (func) HeapFree(GetProcessHeap(), 0, func);
\r