3 /* This is explicitly a wide string. */
\r
4 #define NSSM_LOGON_AS_SERVICE_RIGHT L"SeServiceLogonRight"
\r
6 extern const TCHAR *exit_action_strings[];
\r
9 bool use_critical_section;
\r
11 extern imports_t imports;
\r
13 const TCHAR *exit_action_strings[] = { _T("Restart"), _T("Ignore"), _T("Exit"), _T("Suicide"), 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 static 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 < 1) return usage(1);
\r
424 nssm_service_t *service = alloc_nssm_service();
\r
425 _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
427 /* Open service manager */
\r
428 SC_HANDLE services = open_service_manager();
\r
430 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
434 /* Try to open the service */
\r
435 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
436 if (! service->handle) {
\r
437 CloseServiceHandle(services);
\r
438 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
442 /* Get system details. */
\r
443 unsigned long bufsize;
\r
444 QUERY_SERVICE_CONFIG *qsc = query_service_config(service->name, service->handle);
\r
446 CloseHandle(service->handle);
\r
447 CloseServiceHandle(services);
\r
451 service->type = qsc->dwServiceType;
\r
452 if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {
\r
453 HeapFree(GetProcessHeap(), 0, qsc);
\r
454 CloseHandle(service->handle);
\r
455 CloseServiceHandle(services);
\r
456 print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, _T("SERVICE_WIN32_OWN_PROCESS"), 0);
\r
460 if (get_service_startup(service->name, service->handle, qsc, &service->startup)) {
\r
461 HeapFree(GetProcessHeap(), 0, qsc);
\r
462 CloseHandle(service->handle);
\r
463 CloseServiceHandle(services);
\r
467 if (get_service_username(service->name, qsc, &service->username, &service->usernamelen)) {
\r
468 HeapFree(GetProcessHeap(), 0, qsc);
\r
469 CloseHandle(service->handle);
\r
470 CloseServiceHandle(services);
\r
474 _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);
\r
476 /* Get the canonical service name. We open it case insensitively. */
\r
477 bufsize = _countof(service->name);
\r
478 GetServiceKeyName(services, service->displayname, service->name, &bufsize);
\r
480 /* Remember the executable in case it isn't NSSM. */
\r
481 _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);
\r
482 HeapFree(GetProcessHeap(), 0, qsc);
\r
484 /* Get extended system details. */
\r
485 if (get_service_description(service->name, service->handle, _countof(service->description), service->description)) {
\r
486 CloseHandle(service->handle);
\r
487 CloseServiceHandle(services);
\r
491 /* Get NSSM details. */
\r
492 get_parameters(service, 0);
\r
494 CloseServiceHandle(services);
\r
496 if (! service->exe[0]) {
\r
497 print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);
\r
498 service->native = true;
\r
501 nssm_gui(IDD_EDIT, service);
\r
506 /* About to remove the service */
\r
507 int pre_remove_service(int argc, TCHAR **argv) {
\r
508 nssm_service_t *service = alloc_nssm_service();
\r
509 set_nssm_service_defaults(service);
\r
510 if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
512 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
513 if (argc < 2) return nssm_gui(IDD_REMOVE, service);
\r
514 if (str_equiv(argv[1], _T("confirm"))) {
\r
515 int ret = remove_service(service);
\r
516 cleanup_nssm_service(service);
\r
519 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
523 /* Install the service */
\r
524 int install_service(nssm_service_t *service) {
\r
525 if (! service) return 1;
\r
527 /* Open service manager */
\r
528 SC_HANDLE services = open_service_manager();
\r
530 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
531 cleanup_nssm_service(service);
\r
535 /* Get path of this program */
\r
536 GetModuleFileName(0, service->image, _countof(service->image));
\r
538 /* Create the service - settings will be changed in edit_service() */
\r
539 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
540 if (! service->handle) {
\r
541 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
542 CloseServiceHandle(services);
\r
546 if (edit_service(service, false)) {
\r
547 DeleteService(service->handle);
\r
548 CloseServiceHandle(services);
\r
552 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
555 CloseServiceHandle(services);
\r
560 /* Edit the service. */
\r
561 int edit_service(nssm_service_t *service, bool editing) {
\r
562 if (! service) return 1;
\r
565 The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS
\r
566 and SERVICE_INTERACTIVE_PROCESS.
\r
568 service->type &= SERVICE_INTERACTIVE_PROCESS;
\r
569 service->type |= SERVICE_WIN32_OWN_PROCESS;
\r
571 /* Startup type. */
\r
572 unsigned long startup;
\r
573 switch (service->startup) {
\r
574 case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;
\r
575 case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;
\r
576 default: startup = SERVICE_AUTO_START;
\r
579 /* Display name. */
\r
580 if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);
\r
583 Username must be NULL if we aren't changing or an account name.
\r
584 We must explicitly user LOCALSYSTEM to change it when we are editing.
\r
585 Password must be NULL if we aren't changing, a password or "".
\r
586 Empty passwords are valid but we won't allow them in the GUI.
\r
588 TCHAR *username = 0;
\r
589 TCHAR *password = 0;
\r
590 if (service->usernamelen) {
\r
591 username = service->username;
\r
592 if (service->passwordlen) password = service->password;
\r
593 else password = _T("");
\r
595 else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;
\r
597 if (grant_logon_as_service(username)) {
\r
598 print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);
\r
602 if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {
\r
603 print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
\r
607 if (service->description[0] || editing) {
\r
608 set_service_description(service->name, service->handle, service->description);
\r
611 SERVICE_DELAYED_AUTO_START_INFO delayed;
\r
612 ZeroMemory(&delayed, sizeof(delayed));
\r
613 if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;
\r
614 else delayed.fDelayedAutostart = 0;
\r
615 /* Delayed startup isn't supported until Vista. */
\r
616 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {
\r
617 unsigned long error = GetLastError();
\r
618 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
619 if (error != ERROR_INVALID_LEVEL) {
\r
620 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);
\r
624 /* Don't mess with parameters which aren't ours. */
\r
625 if (! service->native) {
\r
626 /* Now we need to put the parameters into the registry */
\r
627 if (create_parameters(service, editing)) {
\r
628 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
632 set_service_recovery(service);
\r
638 /* Remove the service */
\r
639 int remove_service(nssm_service_t *service) {
\r
640 if (! service) return 1;
\r
642 /* Open service manager */
\r
643 SC_HANDLE services = open_service_manager();
\r
645 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
649 /* Try to open the service */
\r
650 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
651 if (! service->handle) {
\r
652 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
653 CloseServiceHandle(services);
\r
657 /* Get the canonical service name. We open it case insensitively. */
\r
658 unsigned long bufsize = _countof(service->displayname);
\r
659 GetServiceDisplayName(services, service->name, service->displayname, &bufsize);
\r
660 bufsize = _countof(service->name);
\r
661 GetServiceKeyName(services, service->displayname, service->name, &bufsize);
\r
663 /* Try to delete the service */
\r
664 if (! DeleteService(service->handle)) {
\r
665 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
666 CloseServiceHandle(services);
\r
671 CloseServiceHandle(services);
\r
673 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\r
677 /* Service initialisation */
\r
678 void WINAPI service_main(unsigned long argc, TCHAR **argv) {
\r
679 nssm_service_t *service = alloc_nssm_service();
\r
680 if (! service) return;
\r
682 if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {
\r
683 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);
\r
687 /* We can use a condition variable in a critical section on Vista or later. */
\r
688 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
689 else use_critical_section = false;
\r
691 /* Initialise status */
\r
692 ZeroMemory(&service->status, sizeof(service->status));
\r
693 service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
694 service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
695 service->status.dwWin32ExitCode = NO_ERROR;
\r
696 service->status.dwServiceSpecificExitCode = 0;
\r
697 service->status.dwCheckPoint = 0;
\r
698 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
700 /* Signal we AREN'T running the server */
\r
701 service->process_handle = 0;
\r
704 /* Register control handler */
\r
705 service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
\r
706 if (! service->status_handle) {
\r
707 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
711 log_service_control(service->name, 0, true);
\r
713 service->status.dwCurrentState = SERVICE_START_PENDING;
\r
714 service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
\r
715 SetServiceStatus(service->status_handle, &service->status);
\r
718 /* Try to create the exit action parameters; we don't care if it fails */
\r
719 create_exit_action(service->name, exit_action_strings[0], false);
\r
721 SC_HANDLE services = open_service_manager();
\r
723 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
724 set_service_recovery(service);
\r
725 CloseServiceHandle(services);
\r
729 /* Used for signalling a resume if the service pauses when throttled. */
\r
730 if (use_critical_section) {
\r
731 InitializeCriticalSection(&service->throttle_section);
\r
732 service->throttle_section_initialised = true;
\r
735 service->throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
736 if (! service->throttle_timer) {
\r
737 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
\r
741 monitor_service(service);
\r
744 /* Make sure service recovery actions are taken where necessary */
\r
745 void set_service_recovery(nssm_service_t *service) {
\r
746 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
747 ZeroMemory(&flag, sizeof(flag));
\r
748 flag.fFailureActionsOnNonCrashFailures = true;
\r
750 /* This functionality was added in Vista so the call may fail */
\r
751 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
752 unsigned long error = GetLastError();
\r
753 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
754 if (error != ERROR_INVALID_LEVEL) {
\r
755 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);
\r
760 int monitor_service(nssm_service_t *service) {
\r
761 /* Set service status to started */
\r
762 int ret = start_service(service);
\r
765 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);
\r
766 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
\r
769 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\r
771 /* Monitor service */
\r
772 if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
773 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
\r
779 TCHAR *service_control_text(unsigned long control) {
\r
781 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
782 case 0: return _T("START");
\r
783 case SERVICE_CONTROL_STOP: return _T("STOP");
\r
784 case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");
\r
785 case SERVICE_CONTROL_PAUSE: return _T("PAUSE");
\r
786 case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");
\r
787 case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");
\r
792 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {
\r
793 TCHAR *text = service_control_text(control);
\r
794 unsigned long event;
\r
797 /* "0x" + 8 x hex + NULL */
\r
798 text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));
\r
800 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
803 if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {
\r
804 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
805 HeapFree(GetProcessHeap(), 0, text);
\r
809 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
811 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
812 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
814 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
816 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
817 HeapFree(GetProcessHeap(), 0, text);
\r
821 /* Service control handler */
\r
822 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
823 nssm_service_t *service = (nssm_service_t *) context;
\r
826 case SERVICE_CONTROL_INTERROGATE:
\r
827 /* We always keep the service status up-to-date so this is a no-op. */
\r
830 case SERVICE_CONTROL_SHUTDOWN:
\r
831 case SERVICE_CONTROL_STOP:
\r
832 log_service_control(service->name, control, true);
\r
834 We MUST acknowledge the stop request promptly but we're committed to
\r
835 waiting for the application to exit. Spawn a new thread to wait
\r
836 while we acknowledge the request.
\r
838 if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
\r
839 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
842 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
843 to complete in time in this thread.
\r
845 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
846 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
847 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
849 stop_service(service, 0, true, true);
\r
853 case SERVICE_CONTROL_CONTINUE:
\r
854 log_service_control(service->name, control, true);
\r
855 service->throttle = 0;
\r
856 if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
\r
858 if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
859 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
860 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
862 service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
863 service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
\r
864 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
\r
865 SetServiceStatus(service->status_handle, &service->status);
\r
868 case SERVICE_CONTROL_PAUSE:
\r
870 We don't accept pause messages but it isn't possible to register
\r
871 only for continue messages so we have to handle this case.
\r
873 log_service_control(service->name, control, false);
\r
874 return ERROR_CALL_NOT_IMPLEMENTED;
\r
877 /* Unknown control */
\r
878 log_service_control(service->name, control, false);
\r
879 return ERROR_CALL_NOT_IMPLEMENTED;
\r
882 /* Start the service */
\r
883 int start_service(nssm_service_t *service) {
\r
884 service->stopping = false;
\r
885 service->allow_restart = true;
\r
887 if (service->process_handle) return 0;
\r
889 /* Allocate a STARTUPINFO structure for a new process */
\r
891 ZeroMemory(&si, sizeof(si));
\r
892 si.cb = sizeof(si);
\r
894 /* Allocate a PROCESSINFO structure for the process */
\r
895 PROCESS_INFORMATION pi;
\r
896 ZeroMemory(&pi, sizeof(pi));
\r
898 /* Get startup parameters */
\r
899 int ret = get_parameters(service, &si);
\r
901 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
\r
902 return stop_service(service, 2, true, true);
\r
905 /* Launch executable with arguments */
\r
906 TCHAR cmd[CMD_LENGTH];
\r
907 if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {
\r
908 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);
\r
909 close_output_handles(&si);
\r
910 return stop_service(service, 2, true, true);
\r
913 throttle_restart(service);
\r
915 bool inherit_handles = false;
\r
916 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
917 unsigned long flags = 0;
\r
919 flags |= CREATE_UNICODE_ENVIRONMENT;
\r
921 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {
\r
922 unsigned long exitcode = 3;
\r
923 unsigned long error = GetLastError();
\r
924 if (error == ERROR_INVALID_PARAMETER && service->env) {
\r
925 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);
\r
926 if (test_environment(service->env)) exitcode = 4;
\r
928 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
\r
929 close_output_handles(&si);
\r
930 return stop_service(service, exitcode, true, true);
\r
932 service->process_handle = pi.hProcess;
\r
933 service->pid = pi.dwProcessId;
\r
935 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
937 close_output_handles(&si);
\r
940 Wait for a clean startup before changing the service status to RUNNING
\r
941 but be mindful of the fact that we are blocking the service control manager
\r
942 so abandon the wait before too much time has elapsed.
\r
944 unsigned long delay = service->throttle_delay;
\r
945 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
946 TCHAR delay_milliseconds[16];
\r
947 _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);
\r
948 TCHAR deadline_milliseconds[16];
\r
949 _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);
\r
950 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
951 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
953 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
955 /* Signal successful start */
\r
956 service->status.dwCurrentState = SERVICE_RUNNING;
\r
957 SetServiceStatus(service->status_handle, &service->status);
\r
959 /* Continue waiting for a clean startup. */
\r
960 if (deadline == WAIT_TIMEOUT) {
\r
961 if (service->throttle_delay > delay) {
\r
962 if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
\r
964 else service->throttle = 0;
\r
970 /* Stop the service */
\r
971 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
\r
972 service->allow_restart = false;
\r
973 if (service->wait_handle) {
\r
974 UnregisterWait(service->wait_handle);
\r
975 service->wait_handle = 0;
\r
978 if (default_action && ! exitcode && ! graceful) {
\r
979 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
983 /* Signal we are stopping */
\r
985 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
986 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
987 SetServiceStatus(service->status_handle, &service->status);
\r
990 /* Nothing to do if service isn't running */
\r
991 if (service->pid) {
\r
992 /* Shut down service */
\r
993 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
\r
994 kill_process(service, service->process_handle, service->pid, 0);
\r
996 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
998 end_service((void *) service, true);
\r
1000 /* Signal we stopped */
\r
1002 service->status.dwCurrentState = SERVICE_STOPPED;
\r
1004 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
1005 service->status.dwServiceSpecificExitCode = exitcode;
\r
1008 service->status.dwWin32ExitCode = NO_ERROR;
\r
1009 service->status.dwServiceSpecificExitCode = 0;
\r
1011 SetServiceStatus(service->status_handle, &service->status);
\r
1017 /* Callback function triggered when the server exits */
\r
1018 void CALLBACK end_service(void *arg, unsigned char why) {
\r
1019 nssm_service_t *service = (nssm_service_t *) arg;
\r
1021 if (service->stopping) return;
\r
1023 service->stopping = true;
\r
1025 /* Check exit code */
\r
1026 unsigned long exitcode = 0;
\r
1028 if (service->process_handle) {
\r
1029 GetExitCodeProcess(service->process_handle, &exitcode);
\r
1030 if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);
\r
1031 CloseHandle(service->process_handle);
\r
1033 else GetSystemTimeAsFileTime(&service->exit_time);
\r
1035 service->process_handle = 0;
\r
1038 Log that the service ended BEFORE logging about killing the process
\r
1039 tree. See below for the possible values of the why argument.
\r
1042 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);
\r
1043 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
\r
1047 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
1048 if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);
\r
1052 The why argument is true if our wait timed out or false otherwise.
\r
1053 Our wait is infinite so why will never be true when called by the system.
\r
1054 If it is indeed true, assume we were called from stop_service() because
\r
1055 this is a controlled shutdown, and don't take any restart action.
\r
1058 if (! service->allow_restart) return;
\r
1060 /* What action should we take? */
\r
1061 int action = NSSM_EXIT_RESTART;
\r
1062 TCHAR action_string[ACTION_LEN];
\r
1063 bool default_action;
\r
1064 if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
\r
1065 for (int i = 0; exit_action_strings[i]; i++) {
\r
1066 if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
1074 /* Try to restart the service or return failure code to service manager */
\r
1075 case NSSM_EXIT_RESTART:
\r
1076 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
\r
1077 while (monitor_service(service)) {
\r
1078 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
\r
1083 /* Do nothing, just like srvany would */
\r
1084 case NSSM_EXIT_IGNORE:
\r
1085 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
\r
1089 /* Tell the service manager we are finished */
\r
1090 case NSSM_EXIT_REALLY:
\r
1091 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
\r
1092 stop_service(service, exitcode, true, default_action);
\r
1095 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
1096 case NSSM_EXIT_UNCLEAN:
\r
1097 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
\r
1098 stop_service(service, exitcode, false, default_action);
\r
1105 void throttle_restart(nssm_service_t *service) {
\r
1106 /* This can't be a restart if the service is already running. */
\r
1107 if (! service->throttle++) return;
\r
1109 int ms = throttle_milliseconds(service->throttle);
\r
1111 if (service->throttle > 7) service->throttle = 8;
\r
1113 TCHAR threshold[8], milliseconds[8];
\r
1114 _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);
\r
1115 _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);
\r
1116 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
\r
1118 if (use_critical_section) EnterCriticalSection(&service->throttle_section);
\r
1119 else if (service->throttle_timer) {
\r
1120 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
1121 service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
1122 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
1125 service->status.dwCurrentState = SERVICE_PAUSED;
\r
1126 SetServiceStatus(service->status_handle, &service->status);
\r
1128 if (use_critical_section) {
\r
1129 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
1130 LeaveCriticalSection(&service->throttle_section);
\r
1133 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\r
1139 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
1140 the number of milliseconds we expect the operation to take, and optionally
\r
1141 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
1142 operation completing or dwCheckPoint increasing, the system will consider the
\r
1143 service to be hung.
\r
1145 However the system will consider the service to be hung after 30000
\r
1146 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
1147 changed. Therefore if we want to wait longer than that we must periodically
\r
1148 increase dwCheckPoint.
\r
1150 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
1151 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
1152 time dwCheckPoint is also increased.
\r
1154 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
1155 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
1156 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
1157 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
1160 Only doing both these things will prevent the system from killing the service.
\r
1162 Returns: 1 if the wait timed out.
\r
1163 0 if the wait completed.
\r
1166 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {
\r
1167 unsigned long interval;
\r
1168 unsigned long waithint;
\r
1169 unsigned long ret;
\r
1170 unsigned long waited;
\r
1171 TCHAR interval_milliseconds[16];
\r
1172 TCHAR timeout_milliseconds[16];
\r
1173 TCHAR waited_milliseconds[16];
\r
1174 TCHAR *function = function_name;
\r
1176 /* Add brackets to function name. */
\r
1177 size_t funclen = _tcslen(function_name) + 3;
\r
1178 TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));
\r
1180 if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;
\r
1183 _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);
\r
1185 waithint = service->status.dwWaitHint;
\r
1187 while (waited < timeout) {
\r
1188 interval = timeout - waited;
\r
1189 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
1191 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
1192 service->status.dwWaitHint += interval;
\r
1193 service->status.dwCheckPoint++;
\r
1194 SetServiceStatus(service->status_handle, &service->status);
\r
1197 _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);
\r
1198 _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);
\r
1199 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
1202 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
1203 case WAIT_OBJECT_0:
\r
1207 case WAIT_TIMEOUT:
\r
1216 waited += interval;
\r
1220 if (func) HeapFree(GetProcessHeap(), 0, func);
\r