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 get_service_description(const TCHAR *service_name, SC_HANDLE service_handle, unsigned long len, TCHAR *buffer) {
\r
69 if (! buffer) return 1;
\r
71 unsigned long bufsize;
\r
72 QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);
\r
73 unsigned long error = GetLastError();
\r
74 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
75 SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);
\r
76 if (! description) {
\r
77 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("get_service_description()"));
\r
81 if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {
\r
82 if (description->lpDescription) _sntprintf_s(buffer, len, _TRUNCATE, _T("%s"), description->lpDescription);
\r
83 else ZeroMemory(buffer, len * sizeof(TCHAR));
\r
84 HeapFree(GetProcessHeap(), 0, description);
\r
88 HeapFree(GetProcessHeap(), 0, description);
\r
89 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));
\r
94 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));
\r
101 int get_service_startup(const TCHAR *service_name, SC_HANDLE service_handle, const QUERY_SERVICE_CONFIG *qsc, unsigned long *startup) {
\r
102 if (! qsc) return 1;
\r
104 switch (qsc->dwStartType) {
\r
105 case SERVICE_DEMAND_START: *startup = NSSM_STARTUP_MANUAL; break;
\r
106 case SERVICE_DISABLED: *startup = NSSM_STARTUP_DISABLED; break;
\r
107 default: *startup = NSSM_STARTUP_AUTOMATIC;
\r
110 if (*startup != NSSM_STARTUP_AUTOMATIC) return 0;
\r
112 /* Check for delayed start. */
\r
113 unsigned long bufsize;
\r
114 unsigned long error;
\r
115 QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, 0, 0, &bufsize);
\r
116 error = GetLastError();
\r
117 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
118 SERVICE_DELAYED_AUTO_START_INFO *info = (SERVICE_DELAYED_AUTO_START_INFO *) HeapAlloc(GetProcessHeap(), 0, bufsize);
\r
120 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_DELAYED_AUTO_START_INFO"), _T("get_service_startup()"));
\r
124 if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (unsigned char *) info, bufsize, &bufsize)) {
\r
125 if (info->fDelayedAutostart) *startup = NSSM_STARTUP_DELAYED;
\r
126 HeapFree(GetProcessHeap(), 0, info);
\r
130 error = GetLastError();
\r
131 if (error != ERROR_INVALID_LEVEL) {
\r
132 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
137 else if (error != ERROR_INVALID_LEVEL) {
\r
138 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));
\r
145 static int grant_logon_as_service(const TCHAR *username) {
\r
146 if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;
\r
148 /* Open Policy object. */
\r
149 LSA_OBJECT_ATTRIBUTES attributes;
\r
150 ZeroMemory(&attributes, sizeof(attributes));
\r
154 NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, &policy);
\r
156 print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status)));
\r
160 /* Look up SID for the account. */
\r
161 LSA_UNICODE_STRING lsa_username;
\r
163 lsa_username.Buffer = (wchar_t *) username;
\r
164 lsa_username.Length = (unsigned short) _tcslen(username) * sizeof(TCHAR);
\r
165 lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR);
\r
168 mbstowcs_s(&buflen, NULL, 0, username, _TRUNCATE);
\r
169 lsa_username.MaximumLength = buflen * sizeof(wchar_t);
\r
170 lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t);
\r
171 lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength);
\r
172 if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, username, _TRUNCATE);
\r
175 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("grant_logon_as_service()"));
\r
180 LSA_REFERENCED_DOMAIN_LIST *translated_domains;
\r
181 LSA_TRANSLATED_SID *translated_sid;
\r
182 status = LsaLookupNames(policy, 1, &lsa_username, &translated_domains, &translated_sid);
\r
184 HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);
\r
187 LsaFreeMemory(translated_domains);
\r
188 LsaFreeMemory(translated_sid);
\r
190 print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status)));
\r
194 if (translated_sid->Use != SidTypeUser) {
\r
195 LsaFreeMemory(translated_domains);
\r
196 LsaFreeMemory(translated_sid);
\r
198 print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
\r
202 LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];
\r
203 if (! trust || ! IsValidSid(trust->Sid)) {
\r
204 LsaFreeMemory(translated_domains);
\r
205 LsaFreeMemory(translated_sid);
\r
207 print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
\r
211 /* GetSidSubAuthority*() return pointers! */
\r
212 unsigned char *n = GetSidSubAuthorityCount(trust->Sid);
\r
214 /* Convert translated SID to SID. */
\r
215 SID *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));
\r
217 LsaFreeMemory(translated_domains);
\r
218 LsaFreeMemory(translated_sid);
\r
220 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("grant_logon_as_service"));
\r
224 unsigned long error;
\r
225 if (! InitializeSid(sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {
\r
226 error = GetLastError();
\r
227 HeapFree(GetProcessHeap(), 0, sid);
\r
228 LsaFreeMemory(translated_domains);
\r
229 LsaFreeMemory(translated_sid);
\r
231 print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));
\r
235 for (unsigned char i = 0; i <= *n; i++) {
\r
236 unsigned long *sub = GetSidSubAuthority(sid, i);
\r
237 if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);
\r
238 else *sub = translated_sid->RelativeId;
\r
241 LsaFreeMemory(translated_domains);
\r
242 LsaFreeMemory(translated_sid);
\r
244 /* Check if the SID has the "Log on as a service" right. */
\r
245 LSA_UNICODE_STRING lsa_right;
\r
246 lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;
\r
247 lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);
\r
248 lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);
\r
250 LSA_UNICODE_STRING *rights;
\r
251 unsigned long count = ~0;
\r
252 status = LsaEnumerateAccountRights(policy, sid, &rights, &count);
\r
255 If the account has no rights set LsaEnumerateAccountRights() will return
\r
256 STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.
\r
258 error = LsaNtStatusToWinError(status);
\r
259 if (error != ERROR_FILE_NOT_FOUND) {
\r
260 HeapFree(GetProcessHeap(), 0, sid);
\r
262 print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));
\r
267 for (unsigned long i = 0; i < count; i++) {
\r
268 if (rights[i].Length != lsa_right.Length) continue;
\r
269 if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;
\r
270 /* The SID has the right. */
\r
271 HeapFree(GetProcessHeap(), 0, sid);
\r
272 LsaFreeMemory(rights);
\r
276 LsaFreeMemory(rights);
\r
278 /* Add the right. */
\r
279 status = LsaAddAccountRights(policy, sid, &lsa_right, 1);
\r
280 HeapFree(GetProcessHeap(), 0, sid);
\r
283 print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));
\r
287 print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);
\r
291 /* Set default values which aren't zero. */
\r
292 void set_nssm_service_defaults(nssm_service_t *service) {
\r
293 if (! service) return;
\r
295 service->type = SERVICE_WIN32_OWN_PROCESS;
\r
296 service->stdin_sharing = NSSM_STDIN_SHARING;
\r
297 service->stdin_disposition = NSSM_STDIN_DISPOSITION;
\r
298 service->stdin_flags = NSSM_STDIN_FLAGS;
\r
299 service->stdout_sharing = NSSM_STDOUT_SHARING;
\r
300 service->stdout_disposition = NSSM_STDOUT_DISPOSITION;
\r
301 service->stdout_flags = NSSM_STDOUT_FLAGS;
\r
302 service->stderr_sharing = NSSM_STDERR_SHARING;
\r
303 service->stderr_disposition = NSSM_STDERR_DISPOSITION;
\r
304 service->stderr_flags = NSSM_STDERR_FLAGS;
\r
305 service->throttle_delay = NSSM_RESET_THROTTLE_RESTART;
\r
306 service->stop_method = ~0;
\r
307 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
308 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
309 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
312 /* Allocate and zero memory for a service. */
\r
313 nssm_service_t *alloc_nssm_service() {
\r
314 nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));
\r
315 if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("alloc_nssm_service()"), 0);
\r
319 /* Free memory for a service. */
\r
320 void cleanup_nssm_service(nssm_service_t *service) {
\r
321 if (! service) return;
\r
322 if (service->username) HeapFree(GetProcessHeap(), 0, service->username);
\r
323 if (service->password) {
\r
324 SecureZeroMemory(service->password, service->passwordlen);
\r
325 HeapFree(GetProcessHeap(), 0, service->password);
\r
327 if (service->env) HeapFree(GetProcessHeap(), 0, service->env);
\r
328 if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);
\r
329 if (service->handle) CloseServiceHandle(service->handle);
\r
330 if (service->process_handle) CloseHandle(service->process_handle);
\r
331 if (service->wait_handle) UnregisterWait(service->process_handle);
\r
332 if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);
\r
333 if (service->throttle_timer) CloseHandle(service->throttle_timer);
\r
334 HeapFree(GetProcessHeap(), 0, service);
\r
337 /* About to install the service */
\r
338 int pre_install_service(int argc, TCHAR **argv) {
\r
339 nssm_service_t *service = alloc_nssm_service();
\r
340 set_nssm_service_defaults(service);
\r
341 if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
343 /* Show the dialogue box if we didn't give the service name and path */
\r
344 if (argc < 2) return nssm_gui(IDD_INSTALL, service);
\r
347 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("service"), _T("pre_install_service()"));
\r
350 _sntprintf_s(service->exe, _countof(service->exe), _TRUNCATE, _T("%s"), argv[1]);
\r
352 /* Arguments are optional */
\r
353 size_t flagslen = 0;
\r
356 for (i = 2; i < argc; i++) flagslen += _tcslen(argv[i]) + 1;
\r
357 if (! flagslen) flagslen = 1;
\r
358 if (flagslen > _countof(service->flags)) {
\r
359 print_message(stderr, NSSM_MESSAGE_FLAGS_TOO_LONG);
\r
363 for (i = 2; i < argc; i++) {
\r
364 size_t len = _tcslen(argv[i]);
\r
365 memmove(service->flags + s, argv[i], len * sizeof(TCHAR));
\r
367 if (i < argc - 1) service->flags[s++] = _T(' ');
\r
370 /* Work out directory name */
\r
371 _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);
\r
372 strip_basename(service->dir);
\r
374 int ret = install_service(service);
\r
375 cleanup_nssm_service(service);
\r
379 /* About to edit the service. */
\r
380 int pre_edit_service(int argc, TCHAR **argv) {
\r
381 /* Require service name. */
\r
382 if (argc < 1) return usage(1);
\r
384 nssm_service_t *service = alloc_nssm_service();
\r
385 _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
387 /* Open service manager */
\r
388 SC_HANDLE services = open_service_manager();
\r
390 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
394 /* Try to open the service */
\r
395 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
396 if (! service->handle) {
\r
397 CloseServiceHandle(services);
\r
398 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
402 /* Get system details. */
\r
403 unsigned long bufsize;
\r
404 QUERY_SERVICE_CONFIG *qsc = query_service_config(service->name, service->handle);
\r
406 CloseHandle(service->handle);
\r
407 CloseServiceHandle(services);
\r
411 service->type = qsc->dwServiceType;
\r
412 if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {
\r
413 HeapFree(GetProcessHeap(), 0, qsc);
\r
414 CloseHandle(service->handle);
\r
415 CloseServiceHandle(services);
\r
416 print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, _T("SERVICE_WIN32_OWN_PROCESS"), 0);
\r
420 if (get_service_startup(service->name, service->handle, qsc, &service->startup)) {
\r
421 HeapFree(GetProcessHeap(), 0, qsc);
\r
422 CloseHandle(service->handle);
\r
423 CloseServiceHandle(services);
\r
427 if (! str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) {
\r
428 size_t len = _tcslen(qsc->lpServiceStartName);
\r
429 service->username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(TCHAR));
\r
430 if (service->username) {
\r
431 memmove(service->username, qsc->lpServiceStartName, (len + 1) * sizeof(TCHAR));
\r
432 service->usernamelen = (unsigned long) len;
\r
435 HeapFree(GetProcessHeap(), 0, qsc);
\r
436 CloseHandle(service->handle);
\r
437 CloseServiceHandle(services);
\r
438 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("username"), _T("pre_edit_service()"));
\r
442 _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);
\r
444 /* Get the canonical service name. We open it case insensitively. */
\r
445 bufsize = _countof(service->name);
\r
446 GetServiceKeyName(services, service->displayname, service->name, &bufsize);
\r
448 /* Remember the executable in case it isn't NSSM. */
\r
449 _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);
\r
450 HeapFree(GetProcessHeap(), 0, qsc);
\r
452 /* Get extended system details. */
\r
453 if (get_service_description(service->name, service->handle, _countof(service->description), service->description)) {
\r
454 CloseHandle(service->handle);
\r
455 CloseServiceHandle(services);
\r
459 /* Get NSSM details. */
\r
460 get_parameters(service, 0);
\r
462 CloseServiceHandle(services);
\r
464 if (! service->exe[0]) {
\r
465 print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);
\r
466 service->native = true;
\r
469 nssm_gui(IDD_EDIT, service);
\r
474 /* About to remove the service */
\r
475 int pre_remove_service(int argc, TCHAR **argv) {
\r
476 nssm_service_t *service = alloc_nssm_service();
\r
477 set_nssm_service_defaults(service);
\r
478 if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
480 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
481 if (argc < 2) return nssm_gui(IDD_REMOVE, service);
\r
482 if (str_equiv(argv[1], _T("confirm"))) {
\r
483 int ret = remove_service(service);
\r
484 cleanup_nssm_service(service);
\r
487 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
491 /* Install the service */
\r
492 int install_service(nssm_service_t *service) {
\r
493 if (! service) return 1;
\r
495 /* Open service manager */
\r
496 SC_HANDLE services = open_service_manager();
\r
498 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
499 cleanup_nssm_service(service);
\r
503 /* Get path of this program */
\r
504 GetModuleFileName(0, service->image, _countof(service->image));
\r
506 /* Create the service - settings will be changed in edit_service() */
\r
507 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
508 if (! service->handle) {
\r
509 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
510 CloseServiceHandle(services);
\r
514 if (edit_service(service, false)) {
\r
515 DeleteService(service->handle);
\r
516 CloseServiceHandle(services);
\r
520 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
523 CloseServiceHandle(services);
\r
528 /* Edit the service. */
\r
529 int edit_service(nssm_service_t *service, bool editing) {
\r
530 if (! service) return 1;
\r
533 The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS
\r
534 and SERVICE_INTERACTIVE_PROCESS.
\r
536 service->type &= SERVICE_INTERACTIVE_PROCESS;
\r
537 service->type |= SERVICE_WIN32_OWN_PROCESS;
\r
539 /* Startup type. */
\r
540 unsigned long startup;
\r
541 switch (service->startup) {
\r
542 case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;
\r
543 case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;
\r
544 default: startup = SERVICE_AUTO_START;
\r
547 /* Display name. */
\r
548 if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);
\r
551 Username must be NULL if we aren't changing or an account name.
\r
552 We must explicitly user LOCALSYSTEM to change it when we are editing.
\r
553 Password must be NULL if we aren't changing, a password or "".
\r
554 Empty passwords are valid but we won't allow them in the GUI.
\r
556 TCHAR *username = 0;
\r
557 TCHAR *password = 0;
\r
558 if (service->usernamelen) {
\r
559 username = service->username;
\r
560 if (service->passwordlen) password = service->password;
\r
561 else password = _T("");
\r
563 else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;
\r
565 if (grant_logon_as_service(username)) {
\r
566 print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);
\r
570 if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {
\r
571 print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
\r
575 if (service->description[0] || editing) {
\r
576 SERVICE_DESCRIPTION description;
\r
577 ZeroMemory(&description, sizeof(description));
\r
578 if (service->description[0]) description.lpDescription = service->description;
\r
579 else description.lpDescription = 0;
\r
580 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, &description)) {
\r
581 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service->name, error_string(GetLastError()), 0);
\r
585 SERVICE_DELAYED_AUTO_START_INFO delayed;
\r
586 ZeroMemory(&delayed, sizeof(delayed));
\r
587 if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;
\r
588 else delayed.fDelayedAutostart = 0;
\r
589 /* Delayed startup isn't supported until Vista. */
\r
590 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {
\r
591 unsigned long error = GetLastError();
\r
592 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
593 if (error != ERROR_INVALID_LEVEL) {
\r
594 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);
\r
598 /* Don't mess with parameters which aren't ours. */
\r
599 if (! service->native) {
\r
600 /* Now we need to put the parameters into the registry */
\r
601 if (create_parameters(service, editing)) {
\r
602 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
606 set_service_recovery(service);
\r
612 /* Remove the service */
\r
613 int remove_service(nssm_service_t *service) {
\r
614 if (! service) return 1;
\r
616 /* Open service manager */
\r
617 SC_HANDLE services = open_service_manager();
\r
619 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
623 /* Try to open the service */
\r
624 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
625 if (! service->handle) {
\r
626 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
627 CloseServiceHandle(services);
\r
631 /* Get the canonical service name. We open it case insensitively. */
\r
632 unsigned long bufsize = _countof(service->displayname);
\r
633 GetServiceDisplayName(services, service->name, service->displayname, &bufsize);
\r
634 bufsize = _countof(service->name);
\r
635 GetServiceKeyName(services, service->displayname, service->name, &bufsize);
\r
637 /* Try to delete the service */
\r
638 if (! DeleteService(service->handle)) {
\r
639 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
640 CloseServiceHandle(services);
\r
645 CloseServiceHandle(services);
\r
647 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\r
651 /* Service initialisation */
\r
652 void WINAPI service_main(unsigned long argc, TCHAR **argv) {
\r
653 nssm_service_t *service = alloc_nssm_service();
\r
654 if (! service) return;
\r
656 if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {
\r
657 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);
\r
661 /* We can use a condition variable in a critical section on Vista or later. */
\r
662 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
663 else use_critical_section = false;
\r
665 /* Initialise status */
\r
666 ZeroMemory(&service->status, sizeof(service->status));
\r
667 service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
668 service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
669 service->status.dwWin32ExitCode = NO_ERROR;
\r
670 service->status.dwServiceSpecificExitCode = 0;
\r
671 service->status.dwCheckPoint = 0;
\r
672 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
674 /* Signal we AREN'T running the server */
\r
675 service->process_handle = 0;
\r
678 /* Register control handler */
\r
679 service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
\r
680 if (! service->status_handle) {
\r
681 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
685 log_service_control(service->name, 0, true);
\r
687 service->status.dwCurrentState = SERVICE_START_PENDING;
\r
688 service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
\r
689 SetServiceStatus(service->status_handle, &service->status);
\r
692 /* Try to create the exit action parameters; we don't care if it fails */
\r
693 create_exit_action(service->name, exit_action_strings[0], false);
\r
695 SC_HANDLE services = open_service_manager();
\r
697 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
698 set_service_recovery(service);
\r
699 CloseServiceHandle(services);
\r
703 /* Used for signalling a resume if the service pauses when throttled. */
\r
704 if (use_critical_section) {
\r
705 InitializeCriticalSection(&service->throttle_section);
\r
706 service->throttle_section_initialised = true;
\r
709 service->throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
710 if (! service->throttle_timer) {
\r
711 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
\r
715 monitor_service(service);
\r
718 /* Make sure service recovery actions are taken where necessary */
\r
719 void set_service_recovery(nssm_service_t *service) {
\r
720 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
721 ZeroMemory(&flag, sizeof(flag));
\r
722 flag.fFailureActionsOnNonCrashFailures = true;
\r
724 /* This functionality was added in Vista so the call may fail */
\r
725 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
726 unsigned long error = GetLastError();
\r
727 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
728 if (error != ERROR_INVALID_LEVEL) {
\r
729 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);
\r
734 int monitor_service(nssm_service_t *service) {
\r
735 /* Set service status to started */
\r
736 int ret = start_service(service);
\r
739 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);
\r
740 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
\r
743 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\r
745 /* Monitor service */
\r
746 if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
747 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
\r
753 TCHAR *service_control_text(unsigned long control) {
\r
755 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
756 case 0: return _T("START");
\r
757 case SERVICE_CONTROL_STOP: return _T("STOP");
\r
758 case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");
\r
759 case SERVICE_CONTROL_PAUSE: return _T("PAUSE");
\r
760 case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");
\r
761 case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");
\r
766 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {
\r
767 TCHAR *text = service_control_text(control);
\r
768 unsigned long event;
\r
771 /* "0x" + 8 x hex + NULL */
\r
772 text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));
\r
774 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
777 if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {
\r
778 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
779 HeapFree(GetProcessHeap(), 0, text);
\r
783 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
785 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
786 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
788 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
790 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
791 HeapFree(GetProcessHeap(), 0, text);
\r
795 /* Service control handler */
\r
796 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
797 nssm_service_t *service = (nssm_service_t *) context;
\r
800 case SERVICE_CONTROL_INTERROGATE:
\r
801 /* We always keep the service status up-to-date so this is a no-op. */
\r
804 case SERVICE_CONTROL_SHUTDOWN:
\r
805 case SERVICE_CONTROL_STOP:
\r
806 log_service_control(service->name, control, true);
\r
808 We MUST acknowledge the stop request promptly but we're committed to
\r
809 waiting for the application to exit. Spawn a new thread to wait
\r
810 while we acknowledge the request.
\r
812 if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
\r
813 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
816 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
817 to complete in time in this thread.
\r
819 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
820 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
821 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
823 stop_service(service, 0, true, true);
\r
827 case SERVICE_CONTROL_CONTINUE:
\r
828 log_service_control(service->name, control, true);
\r
829 service->throttle = 0;
\r
830 if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
\r
832 if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
833 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
834 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
836 service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
837 service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
\r
838 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
\r
839 SetServiceStatus(service->status_handle, &service->status);
\r
842 case SERVICE_CONTROL_PAUSE:
\r
844 We don't accept pause messages but it isn't possible to register
\r
845 only for continue messages so we have to handle this case.
\r
847 log_service_control(service->name, control, false);
\r
848 return ERROR_CALL_NOT_IMPLEMENTED;
\r
851 /* Unknown control */
\r
852 log_service_control(service->name, control, false);
\r
853 return ERROR_CALL_NOT_IMPLEMENTED;
\r
856 /* Start the service */
\r
857 int start_service(nssm_service_t *service) {
\r
858 service->stopping = false;
\r
859 service->allow_restart = true;
\r
861 if (service->process_handle) return 0;
\r
863 /* Allocate a STARTUPINFO structure for a new process */
\r
865 ZeroMemory(&si, sizeof(si));
\r
866 si.cb = sizeof(si);
\r
868 /* Allocate a PROCESSINFO structure for the process */
\r
869 PROCESS_INFORMATION pi;
\r
870 ZeroMemory(&pi, sizeof(pi));
\r
872 /* Get startup parameters */
\r
873 int ret = get_parameters(service, &si);
\r
875 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
\r
876 return stop_service(service, 2, true, true);
\r
879 /* Launch executable with arguments */
\r
880 TCHAR cmd[CMD_LENGTH];
\r
881 if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {
\r
882 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);
\r
883 close_output_handles(&si);
\r
884 return stop_service(service, 2, true, true);
\r
887 throttle_restart(service);
\r
889 bool inherit_handles = false;
\r
890 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
891 unsigned long flags = 0;
\r
893 flags |= CREATE_UNICODE_ENVIRONMENT;
\r
895 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {
\r
896 unsigned long exitcode = 3;
\r
897 unsigned long error = GetLastError();
\r
898 if (error == ERROR_INVALID_PARAMETER && service->env) {
\r
899 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);
\r
900 if (test_environment(service->env)) exitcode = 4;
\r
902 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
\r
903 close_output_handles(&si);
\r
904 return stop_service(service, exitcode, true, true);
\r
906 service->process_handle = pi.hProcess;
\r
907 service->pid = pi.dwProcessId;
\r
909 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
911 close_output_handles(&si);
\r
914 Wait for a clean startup before changing the service status to RUNNING
\r
915 but be mindful of the fact that we are blocking the service control manager
\r
916 so abandon the wait before too much time has elapsed.
\r
918 unsigned long delay = service->throttle_delay;
\r
919 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
920 TCHAR delay_milliseconds[16];
\r
921 _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);
\r
922 TCHAR deadline_milliseconds[16];
\r
923 _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);
\r
924 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
925 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
927 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
929 /* Signal successful start */
\r
930 service->status.dwCurrentState = SERVICE_RUNNING;
\r
931 SetServiceStatus(service->status_handle, &service->status);
\r
933 /* Continue waiting for a clean startup. */
\r
934 if (deadline == WAIT_TIMEOUT) {
\r
935 if (service->throttle_delay > delay) {
\r
936 if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
\r
938 else service->throttle = 0;
\r
944 /* Stop the service */
\r
945 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
\r
946 service->allow_restart = false;
\r
947 if (service->wait_handle) {
\r
948 UnregisterWait(service->wait_handle);
\r
949 service->wait_handle = 0;
\r
952 if (default_action && ! exitcode && ! graceful) {
\r
953 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
957 /* Signal we are stopping */
\r
959 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
960 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
961 SetServiceStatus(service->status_handle, &service->status);
\r
964 /* Nothing to do if service isn't running */
\r
965 if (service->pid) {
\r
966 /* Shut down service */
\r
967 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
\r
968 kill_process(service, service->process_handle, service->pid, 0);
\r
970 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
972 end_service((void *) service, true);
\r
974 /* Signal we stopped */
\r
976 service->status.dwCurrentState = SERVICE_STOPPED;
\r
978 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
979 service->status.dwServiceSpecificExitCode = exitcode;
\r
982 service->status.dwWin32ExitCode = NO_ERROR;
\r
983 service->status.dwServiceSpecificExitCode = 0;
\r
985 SetServiceStatus(service->status_handle, &service->status);
\r
991 /* Callback function triggered when the server exits */
\r
992 void CALLBACK end_service(void *arg, unsigned char why) {
\r
993 nssm_service_t *service = (nssm_service_t *) arg;
\r
995 if (service->stopping) return;
\r
997 service->stopping = true;
\r
999 /* Check exit code */
\r
1000 unsigned long exitcode = 0;
\r
1002 if (service->process_handle) {
\r
1003 GetExitCodeProcess(service->process_handle, &exitcode);
\r
1004 if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);
\r
1005 CloseHandle(service->process_handle);
\r
1007 else GetSystemTimeAsFileTime(&service->exit_time);
\r
1009 service->process_handle = 0;
\r
1012 Log that the service ended BEFORE logging about killing the process
\r
1013 tree. See below for the possible values of the why argument.
\r
1016 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);
\r
1017 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
\r
1021 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
1022 if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);
\r
1026 The why argument is true if our wait timed out or false otherwise.
\r
1027 Our wait is infinite so why will never be true when called by the system.
\r
1028 If it is indeed true, assume we were called from stop_service() because
\r
1029 this is a controlled shutdown, and don't take any restart action.
\r
1032 if (! service->allow_restart) return;
\r
1034 /* What action should we take? */
\r
1035 int action = NSSM_EXIT_RESTART;
\r
1036 TCHAR action_string[ACTION_LEN];
\r
1037 bool default_action;
\r
1038 if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
\r
1039 for (int i = 0; exit_action_strings[i]; i++) {
\r
1040 if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
1048 /* Try to restart the service or return failure code to service manager */
\r
1049 case NSSM_EXIT_RESTART:
\r
1050 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
\r
1051 while (monitor_service(service)) {
\r
1052 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
\r
1057 /* Do nothing, just like srvany would */
\r
1058 case NSSM_EXIT_IGNORE:
\r
1059 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
\r
1063 /* Tell the service manager we are finished */
\r
1064 case NSSM_EXIT_REALLY:
\r
1065 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
\r
1066 stop_service(service, exitcode, true, default_action);
\r
1069 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
1070 case NSSM_EXIT_UNCLEAN:
\r
1071 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
\r
1072 stop_service(service, exitcode, false, default_action);
\r
1079 void throttle_restart(nssm_service_t *service) {
\r
1080 /* This can't be a restart if the service is already running. */
\r
1081 if (! service->throttle++) return;
\r
1083 int ms = throttle_milliseconds(service->throttle);
\r
1085 if (service->throttle > 7) service->throttle = 8;
\r
1087 TCHAR threshold[8], milliseconds[8];
\r
1088 _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);
\r
1089 _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);
\r
1090 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
\r
1092 if (use_critical_section) EnterCriticalSection(&service->throttle_section);
\r
1093 else if (service->throttle_timer) {
\r
1094 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
1095 service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
1096 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
1099 service->status.dwCurrentState = SERVICE_PAUSED;
\r
1100 SetServiceStatus(service->status_handle, &service->status);
\r
1102 if (use_critical_section) {
\r
1103 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
1104 LeaveCriticalSection(&service->throttle_section);
\r
1107 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\r
1113 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
1114 the number of milliseconds we expect the operation to take, and optionally
\r
1115 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
1116 operation completing or dwCheckPoint increasing, the system will consider the
\r
1117 service to be hung.
\r
1119 However the system will consider the service to be hung after 30000
\r
1120 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
1121 changed. Therefore if we want to wait longer than that we must periodically
\r
1122 increase dwCheckPoint.
\r
1124 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
1125 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
1126 time dwCheckPoint is also increased.
\r
1128 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
1129 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
1130 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
1131 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
1134 Only doing both these things will prevent the system from killing the service.
\r
1136 Returns: 1 if the wait timed out.
\r
1137 0 if the wait completed.
\r
1140 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {
\r
1141 unsigned long interval;
\r
1142 unsigned long waithint;
\r
1143 unsigned long ret;
\r
1144 unsigned long waited;
\r
1145 TCHAR interval_milliseconds[16];
\r
1146 TCHAR timeout_milliseconds[16];
\r
1147 TCHAR waited_milliseconds[16];
\r
1148 TCHAR *function = function_name;
\r
1150 /* Add brackets to function name. */
\r
1151 size_t funclen = _tcslen(function_name) + 3;
\r
1152 TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));
\r
1154 if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;
\r
1157 _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);
\r
1159 waithint = service->status.dwWaitHint;
\r
1161 while (waited < timeout) {
\r
1162 interval = timeout - waited;
\r
1163 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
1165 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
1166 service->status.dwWaitHint += interval;
\r
1167 service->status.dwCheckPoint++;
\r
1168 SetServiceStatus(service->status_handle, &service->status);
\r
1171 _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);
\r
1172 _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);
\r
1173 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
1176 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
1177 case WAIT_OBJECT_0:
\r
1181 case WAIT_TIMEOUT:
\r
1190 waited += interval;
\r
1194 if (func) HeapFree(GetProcessHeap(), 0, func);
\r