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 static int grant_logon_as_service(const TCHAR *username) {
\r
69 if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;
\r
71 /* Open Policy object. */
\r
72 LSA_OBJECT_ATTRIBUTES attributes;
\r
73 ZeroMemory(&attributes, sizeof(attributes));
\r
77 NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, &policy);
\r
79 print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status)));
\r
83 /* Look up SID for the account. */
\r
84 LSA_UNICODE_STRING lsa_username;
\r
86 lsa_username.Buffer = (wchar_t *) username;
\r
87 lsa_username.Length = (unsigned short) _tcslen(username) * sizeof(TCHAR);
\r
88 lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR);
\r
91 mbstowcs_s(&buflen, NULL, 0, username, _TRUNCATE);
\r
92 lsa_username.MaximumLength = buflen * sizeof(wchar_t);
\r
93 lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t);
\r
94 lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength);
\r
95 if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, username, _TRUNCATE);
\r
98 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("grant_logon_as_service()"));
\r
103 LSA_REFERENCED_DOMAIN_LIST *translated_domains;
\r
104 LSA_TRANSLATED_SID *translated_sid;
\r
105 status = LsaLookupNames(policy, 1, &lsa_username, &translated_domains, &translated_sid);
\r
107 HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);
\r
110 LsaFreeMemory(translated_domains);
\r
111 LsaFreeMemory(translated_sid);
\r
113 print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status)));
\r
117 if (translated_sid->Use != SidTypeUser) {
\r
118 LsaFreeMemory(translated_domains);
\r
119 LsaFreeMemory(translated_sid);
\r
121 print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
\r
125 LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];
\r
126 if (! trust || ! IsValidSid(trust->Sid)) {
\r
127 LsaFreeMemory(translated_domains);
\r
128 LsaFreeMemory(translated_sid);
\r
130 print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
\r
134 /* GetSidSubAuthority*() return pointers! */
\r
135 unsigned char *n = GetSidSubAuthorityCount(trust->Sid);
\r
137 /* Convert translated SID to SID. */
\r
138 SID *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));
\r
140 LsaFreeMemory(translated_domains);
\r
141 LsaFreeMemory(translated_sid);
\r
143 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("grant_logon_as_service"));
\r
147 unsigned long error;
\r
148 if (! InitializeSid(sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {
\r
149 error = GetLastError();
\r
150 HeapFree(GetProcessHeap(), 0, sid);
\r
151 LsaFreeMemory(translated_domains);
\r
152 LsaFreeMemory(translated_sid);
\r
154 print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));
\r
158 for (unsigned char i = 0; i <= *n; i++) {
\r
159 unsigned long *sub = GetSidSubAuthority(sid, i);
\r
160 if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);
\r
161 else *sub = translated_sid->RelativeId;
\r
164 LsaFreeMemory(translated_domains);
\r
165 LsaFreeMemory(translated_sid);
\r
167 /* Check if the SID has the "Log on as a service" right. */
\r
168 LSA_UNICODE_STRING lsa_right;
\r
169 lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;
\r
170 lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);
\r
171 lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);
\r
173 LSA_UNICODE_STRING *rights;
\r
174 unsigned long count = ~0;
\r
175 status = LsaEnumerateAccountRights(policy, sid, &rights, &count);
\r
178 If the account has no rights set LsaEnumerateAccountRights() will return
\r
179 STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.
\r
181 error = LsaNtStatusToWinError(status);
\r
182 if (error != ERROR_FILE_NOT_FOUND) {
\r
183 HeapFree(GetProcessHeap(), 0, sid);
\r
185 print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));
\r
190 for (unsigned long i = 0; i < count; i++) {
\r
191 if (rights[i].Length != lsa_right.Length) continue;
\r
192 if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;
\r
193 /* The SID has the right. */
\r
194 HeapFree(GetProcessHeap(), 0, sid);
\r
195 LsaFreeMemory(rights);
\r
199 LsaFreeMemory(rights);
\r
201 /* Add the right. */
\r
202 status = LsaAddAccountRights(policy, sid, &lsa_right, 1);
\r
203 HeapFree(GetProcessHeap(), 0, sid);
\r
206 print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));
\r
210 print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);
\r
214 /* Set default values which aren't zero. */
\r
215 void set_nssm_service_defaults(nssm_service_t *service) {
\r
216 if (! service) return;
\r
218 service->type = SERVICE_WIN32_OWN_PROCESS;
\r
219 service->stdin_sharing = NSSM_STDIN_SHARING;
\r
220 service->stdin_disposition = NSSM_STDIN_DISPOSITION;
\r
221 service->stdin_flags = NSSM_STDIN_FLAGS;
\r
222 service->stdout_sharing = NSSM_STDOUT_SHARING;
\r
223 service->stdout_disposition = NSSM_STDOUT_DISPOSITION;
\r
224 service->stdout_flags = NSSM_STDOUT_FLAGS;
\r
225 service->stderr_sharing = NSSM_STDERR_SHARING;
\r
226 service->stderr_disposition = NSSM_STDERR_DISPOSITION;
\r
227 service->stderr_flags = NSSM_STDERR_FLAGS;
\r
228 service->throttle_delay = NSSM_RESET_THROTTLE_RESTART;
\r
229 service->stop_method = ~0;
\r
230 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
231 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
232 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
235 /* Allocate and zero memory for a service. */
\r
236 nssm_service_t *alloc_nssm_service() {
\r
237 nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));
\r
238 if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("alloc_nssm_service()"), 0);
\r
242 /* Free memory for a service. */
\r
243 void cleanup_nssm_service(nssm_service_t *service) {
\r
244 if (! service) return;
\r
245 if (service->username) HeapFree(GetProcessHeap(), 0, service->username);
\r
246 if (service->password) {
\r
247 SecureZeroMemory(service->password, service->passwordlen);
\r
248 HeapFree(GetProcessHeap(), 0, service->password);
\r
250 if (service->env) HeapFree(GetProcessHeap(), 0, service->env);
\r
251 if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);
\r
252 if (service->handle) CloseServiceHandle(service->handle);
\r
253 if (service->process_handle) CloseHandle(service->process_handle);
\r
254 if (service->wait_handle) UnregisterWait(service->process_handle);
\r
255 if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);
\r
256 if (service->throttle_timer) CloseHandle(service->throttle_timer);
\r
257 HeapFree(GetProcessHeap(), 0, service);
\r
260 /* About to install the service */
\r
261 int pre_install_service(int argc, TCHAR **argv) {
\r
262 nssm_service_t *service = alloc_nssm_service();
\r
263 set_nssm_service_defaults(service);
\r
264 if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
266 /* Show the dialogue box if we didn't give the service name and path */
\r
267 if (argc < 2) return nssm_gui(IDD_INSTALL, service);
\r
270 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("service"), _T("pre_install_service()"));
\r
273 _sntprintf_s(service->exe, _countof(service->exe), _TRUNCATE, _T("%s"), argv[1]);
\r
275 /* Arguments are optional */
\r
276 size_t flagslen = 0;
\r
279 for (i = 2; i < argc; i++) flagslen += _tcslen(argv[i]) + 1;
\r
280 if (! flagslen) flagslen = 1;
\r
281 if (flagslen > _countof(service->flags)) {
\r
282 print_message(stderr, NSSM_MESSAGE_FLAGS_TOO_LONG);
\r
286 for (i = 2; i < argc; i++) {
\r
287 size_t len = _tcslen(argv[i]);
\r
288 memmove(service->flags + s, argv[i], len * sizeof(TCHAR));
\r
290 if (i < argc - 1) service->flags[s++] = _T(' ');
\r
293 /* Work out directory name */
\r
294 _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);
\r
295 strip_basename(service->dir);
\r
297 int ret = install_service(service);
\r
298 cleanup_nssm_service(service);
\r
302 /* About to edit the service. */
\r
303 int pre_edit_service(int argc, TCHAR **argv) {
\r
304 /* Require service name. */
\r
305 if (argc < 1) return usage(1);
\r
307 nssm_service_t *service = alloc_nssm_service();
\r
308 _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
310 /* Open service manager */
\r
311 SC_HANDLE services = open_service_manager();
\r
313 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
317 /* Try to open the service */
\r
318 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
319 if (! service->handle) {
\r
320 CloseServiceHandle(services);
\r
321 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
325 /* Get system details. */
\r
326 unsigned long bufsize;
\r
327 unsigned long error;
\r
328 QUERY_SERVICE_CONFIG *qsc = query_service_config(service->name, service->handle);
\r
330 CloseHandle(service->handle);
\r
331 CloseServiceHandle(services);
\r
335 service->type = qsc->dwServiceType;
\r
336 if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {
\r
337 HeapFree(GetProcessHeap(), 0, qsc);
\r
338 CloseHandle(service->handle);
\r
339 CloseServiceHandle(services);
\r
340 print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, _T("SERVICE_WIN32_OWN_PROCESS"), 0);
\r
344 switch (qsc->dwStartType) {
\r
345 case SERVICE_DEMAND_START: service->startup = NSSM_STARTUP_MANUAL; break;
\r
346 case SERVICE_DISABLED: service->startup = NSSM_STARTUP_DISABLED; break;
\r
347 default: service->startup = NSSM_STARTUP_AUTOMATIC;
\r
349 if (! str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) {
\r
350 size_t len = _tcslen(qsc->lpServiceStartName);
\r
351 service->username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(TCHAR));
\r
352 if (service->username) {
\r
353 memmove(service->username, qsc->lpServiceStartName, (len + 1) * sizeof(TCHAR));
\r
354 service->usernamelen = (unsigned long) len;
\r
357 HeapFree(GetProcessHeap(), 0, qsc);
\r
358 CloseHandle(service->handle);
\r
359 CloseServiceHandle(services);
\r
360 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("username"), _T("pre_edit_service()"));
\r
364 _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);
\r
366 /* Get the canonical service name. We open it case insensitively. */
\r
367 bufsize = _countof(service->name);
\r
368 GetServiceKeyName(services, service->displayname, service->name, &bufsize);
\r
370 /* Remember the executable in case it isn't NSSM. */
\r
371 _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);
\r
372 HeapFree(GetProcessHeap(), 0, qsc);
\r
374 /* Get extended system details. */
\r
375 if (service->startup == NSSM_STARTUP_AUTOMATIC) {
\r
376 QueryServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, 0, 0, &bufsize);
\r
377 error = GetLastError();
\r
378 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
379 SERVICE_DELAYED_AUTO_START_INFO *info = (SERVICE_DELAYED_AUTO_START_INFO *) HeapAlloc(GetProcessHeap(), 0, bufsize);
\r
381 CloseHandle(service->handle);
\r
382 CloseServiceHandle(services);
\r
383 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_DELAYED_AUTO_START_INFO"), _T("pre_edit_service()"));
\r
387 if (QueryServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (unsigned char *) info, bufsize, &bufsize)) {
\r
388 if (info->fDelayedAutostart) service->startup = NSSM_STARTUP_DELAYED;
\r
389 HeapFree(GetProcessHeap(), 0, info);
\r
392 error = GetLastError();
\r
393 if (error != ERROR_INVALID_LEVEL) {
\r
394 CloseHandle(service->handle);
\r
395 CloseServiceHandle(services);
\r
396 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
401 else if (error != ERROR_INVALID_LEVEL) {
\r
402 CloseHandle(service->handle);
\r
403 CloseServiceHandle(services);
\r
404 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));
\r
409 QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);
\r
410 error = GetLastError();
\r
411 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
412 SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);
\r
413 if (! description) {
\r
414 CloseHandle(service->handle);
\r
415 CloseServiceHandle(services);
\r
416 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("pre_edit_service()"));
\r
420 if (QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {
\r
421 if (description->lpDescription) _sntprintf_s(service->description, _countof(service->description), _TRUNCATE, _T("%s"), description->lpDescription);
\r
422 HeapFree(GetProcessHeap(), 0, description);
\r
425 HeapFree(GetProcessHeap(), 0, description);
\r
426 CloseHandle(service->handle);
\r
427 CloseServiceHandle(services);
\r
428 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
433 CloseHandle(service->handle);
\r
434 CloseServiceHandle(services);
\r
435 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
439 /* Get NSSM details. */
\r
440 get_parameters(service, 0);
\r
442 CloseServiceHandle(services);
\r
444 if (! service->exe[0]) {
\r
445 print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);
\r
446 service->native = true;
\r
449 nssm_gui(IDD_EDIT, service);
\r
454 /* About to remove the service */
\r
455 int pre_remove_service(int argc, TCHAR **argv) {
\r
456 nssm_service_t *service = alloc_nssm_service();
\r
457 set_nssm_service_defaults(service);
\r
458 if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
460 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
461 if (argc < 2) return nssm_gui(IDD_REMOVE, service);
\r
462 if (str_equiv(argv[1], _T("confirm"))) {
\r
463 int ret = remove_service(service);
\r
464 cleanup_nssm_service(service);
\r
467 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
471 /* Install the service */
\r
472 int install_service(nssm_service_t *service) {
\r
473 if (! service) return 1;
\r
475 /* Open service manager */
\r
476 SC_HANDLE services = open_service_manager();
\r
478 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
479 cleanup_nssm_service(service);
\r
483 /* Get path of this program */
\r
484 GetModuleFileName(0, service->image, _countof(service->image));
\r
486 /* Create the service - settings will be changed in edit_service() */
\r
487 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
488 if (! service->handle) {
\r
489 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
490 CloseServiceHandle(services);
\r
494 if (edit_service(service, false)) {
\r
495 DeleteService(service->handle);
\r
496 CloseServiceHandle(services);
\r
500 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
503 CloseServiceHandle(services);
\r
508 /* Edit the service. */
\r
509 int edit_service(nssm_service_t *service, bool editing) {
\r
510 if (! service) return 1;
\r
513 The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS
\r
514 and SERVICE_INTERACTIVE_PROCESS.
\r
516 service->type &= SERVICE_INTERACTIVE_PROCESS;
\r
517 service->type |= SERVICE_WIN32_OWN_PROCESS;
\r
519 /* Startup type. */
\r
520 unsigned long startup;
\r
521 switch (service->startup) {
\r
522 case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;
\r
523 case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;
\r
524 default: startup = SERVICE_AUTO_START;
\r
527 /* Display name. */
\r
528 if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);
\r
531 Username must be NULL if we aren't changing or an account name.
\r
532 We must explicitly user LOCALSYSTEM to change it when we are editing.
\r
533 Password must be NULL if we aren't changing, a password or "".
\r
534 Empty passwords are valid but we won't allow them in the GUI.
\r
536 TCHAR *username = 0;
\r
537 TCHAR *password = 0;
\r
538 if (service->usernamelen) {
\r
539 username = service->username;
\r
540 if (service->passwordlen) password = service->password;
\r
541 else password = _T("");
\r
543 else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;
\r
545 if (grant_logon_as_service(username)) {
\r
546 print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);
\r
550 if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {
\r
551 print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
\r
555 if (service->description[0] || editing) {
\r
556 SERVICE_DESCRIPTION description;
\r
557 ZeroMemory(&description, sizeof(description));
\r
558 if (service->description[0]) description.lpDescription = service->description;
\r
559 else description.lpDescription = 0;
\r
560 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, &description)) {
\r
561 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service->name, error_string(GetLastError()), 0);
\r
565 SERVICE_DELAYED_AUTO_START_INFO delayed;
\r
566 ZeroMemory(&delayed, sizeof(delayed));
\r
567 if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;
\r
568 else delayed.fDelayedAutostart = 0;
\r
569 /* Delayed startup isn't supported until Vista. */
\r
570 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {
\r
571 unsigned long error = GetLastError();
\r
572 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
573 if (error != ERROR_INVALID_LEVEL) {
\r
574 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);
\r
578 /* Don't mess with parameters which aren't ours. */
\r
579 if (! service->native) {
\r
580 /* Now we need to put the parameters into the registry */
\r
581 if (create_parameters(service, editing)) {
\r
582 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
586 set_service_recovery(service);
\r
592 /* Remove the service */
\r
593 int remove_service(nssm_service_t *service) {
\r
594 if (! service) return 1;
\r
596 /* Open service manager */
\r
597 SC_HANDLE services = open_service_manager();
\r
599 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
603 /* Try to open the service */
\r
604 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
605 if (! service->handle) {
\r
606 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
607 CloseServiceHandle(services);
\r
611 /* Get the canonical service name. We open it case insensitively. */
\r
612 unsigned long bufsize = _countof(service->displayname);
\r
613 GetServiceDisplayName(services, service->name, service->displayname, &bufsize);
\r
614 bufsize = _countof(service->name);
\r
615 GetServiceKeyName(services, service->displayname, service->name, &bufsize);
\r
617 /* Try to delete the service */
\r
618 if (! DeleteService(service->handle)) {
\r
619 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
620 CloseServiceHandle(services);
\r
625 CloseServiceHandle(services);
\r
627 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\r
631 /* Service initialisation */
\r
632 void WINAPI service_main(unsigned long argc, TCHAR **argv) {
\r
633 nssm_service_t *service = alloc_nssm_service();
\r
634 if (! service) return;
\r
636 if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {
\r
637 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);
\r
641 /* We can use a condition variable in a critical section on Vista or later. */
\r
642 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
643 else use_critical_section = false;
\r
645 /* Initialise status */
\r
646 ZeroMemory(&service->status, sizeof(service->status));
\r
647 service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
648 service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
649 service->status.dwWin32ExitCode = NO_ERROR;
\r
650 service->status.dwServiceSpecificExitCode = 0;
\r
651 service->status.dwCheckPoint = 0;
\r
652 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
654 /* Signal we AREN'T running the server */
\r
655 service->process_handle = 0;
\r
658 /* Register control handler */
\r
659 service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
\r
660 if (! service->status_handle) {
\r
661 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
665 log_service_control(service->name, 0, true);
\r
667 service->status.dwCurrentState = SERVICE_START_PENDING;
\r
668 service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
\r
669 SetServiceStatus(service->status_handle, &service->status);
\r
672 /* Try to create the exit action parameters; we don't care if it fails */
\r
673 create_exit_action(service->name, exit_action_strings[0], false);
\r
675 SC_HANDLE services = open_service_manager();
\r
677 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
678 set_service_recovery(service);
\r
679 CloseServiceHandle(services);
\r
683 /* Used for signalling a resume if the service pauses when throttled. */
\r
684 if (use_critical_section) {
\r
685 InitializeCriticalSection(&service->throttle_section);
\r
686 service->throttle_section_initialised = true;
\r
689 service->throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
690 if (! service->throttle_timer) {
\r
691 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
\r
695 monitor_service(service);
\r
698 /* Make sure service recovery actions are taken where necessary */
\r
699 void set_service_recovery(nssm_service_t *service) {
\r
700 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
701 ZeroMemory(&flag, sizeof(flag));
\r
702 flag.fFailureActionsOnNonCrashFailures = true;
\r
704 /* This functionality was added in Vista so the call may fail */
\r
705 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
706 unsigned long error = GetLastError();
\r
707 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
708 if (error != ERROR_INVALID_LEVEL) {
\r
709 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);
\r
714 int monitor_service(nssm_service_t *service) {
\r
715 /* Set service status to started */
\r
716 int ret = start_service(service);
\r
719 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);
\r
720 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
\r
723 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\r
725 /* Monitor service */
\r
726 if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
727 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
\r
733 TCHAR *service_control_text(unsigned long control) {
\r
735 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
736 case 0: return _T("START");
\r
737 case SERVICE_CONTROL_STOP: return _T("STOP");
\r
738 case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");
\r
739 case SERVICE_CONTROL_PAUSE: return _T("PAUSE");
\r
740 case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");
\r
741 case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");
\r
746 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {
\r
747 TCHAR *text = service_control_text(control);
\r
748 unsigned long event;
\r
751 /* "0x" + 8 x hex + NULL */
\r
752 text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));
\r
754 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
757 if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {
\r
758 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
759 HeapFree(GetProcessHeap(), 0, text);
\r
763 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
765 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
766 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
768 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
770 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
771 HeapFree(GetProcessHeap(), 0, text);
\r
775 /* Service control handler */
\r
776 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
777 nssm_service_t *service = (nssm_service_t *) context;
\r
780 case SERVICE_CONTROL_INTERROGATE:
\r
781 /* We always keep the service status up-to-date so this is a no-op. */
\r
784 case SERVICE_CONTROL_SHUTDOWN:
\r
785 case SERVICE_CONTROL_STOP:
\r
786 log_service_control(service->name, control, true);
\r
788 We MUST acknowledge the stop request promptly but we're committed to
\r
789 waiting for the application to exit. Spawn a new thread to wait
\r
790 while we acknowledge the request.
\r
792 if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
\r
793 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
796 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
797 to complete in time in this thread.
\r
799 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
800 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
801 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
803 stop_service(service, 0, true, true);
\r
807 case SERVICE_CONTROL_CONTINUE:
\r
808 log_service_control(service->name, control, true);
\r
809 service->throttle = 0;
\r
810 if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
\r
812 if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
813 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
814 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
816 service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
817 service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
\r
818 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
\r
819 SetServiceStatus(service->status_handle, &service->status);
\r
822 case SERVICE_CONTROL_PAUSE:
\r
824 We don't accept pause messages but it isn't possible to register
\r
825 only for continue messages so we have to handle this case.
\r
827 log_service_control(service->name, control, false);
\r
828 return ERROR_CALL_NOT_IMPLEMENTED;
\r
831 /* Unknown control */
\r
832 log_service_control(service->name, control, false);
\r
833 return ERROR_CALL_NOT_IMPLEMENTED;
\r
836 /* Start the service */
\r
837 int start_service(nssm_service_t *service) {
\r
838 service->stopping = false;
\r
839 service->allow_restart = true;
\r
841 if (service->process_handle) return 0;
\r
843 /* Allocate a STARTUPINFO structure for a new process */
\r
845 ZeroMemory(&si, sizeof(si));
\r
846 si.cb = sizeof(si);
\r
848 /* Allocate a PROCESSINFO structure for the process */
\r
849 PROCESS_INFORMATION pi;
\r
850 ZeroMemory(&pi, sizeof(pi));
\r
852 /* Get startup parameters */
\r
853 int ret = get_parameters(service, &si);
\r
855 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
\r
856 return stop_service(service, 2, true, true);
\r
859 /* Launch executable with arguments */
\r
860 TCHAR cmd[CMD_LENGTH];
\r
861 if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {
\r
862 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);
\r
863 close_output_handles(&si);
\r
864 return stop_service(service, 2, true, true);
\r
867 throttle_restart(service);
\r
869 bool inherit_handles = false;
\r
870 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
871 unsigned long flags = 0;
\r
873 flags |= CREATE_UNICODE_ENVIRONMENT;
\r
875 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {
\r
876 unsigned long exitcode = 3;
\r
877 unsigned long error = GetLastError();
\r
878 if (error == ERROR_INVALID_PARAMETER && service->env) {
\r
879 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);
\r
880 if (test_environment(service->env)) exitcode = 4;
\r
882 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
\r
883 close_output_handles(&si);
\r
884 return stop_service(service, exitcode, true, true);
\r
886 service->process_handle = pi.hProcess;
\r
887 service->pid = pi.dwProcessId;
\r
889 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
891 close_output_handles(&si);
\r
894 Wait for a clean startup before changing the service status to RUNNING
\r
895 but be mindful of the fact that we are blocking the service control manager
\r
896 so abandon the wait before too much time has elapsed.
\r
898 unsigned long delay = service->throttle_delay;
\r
899 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
900 TCHAR delay_milliseconds[16];
\r
901 _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);
\r
902 TCHAR deadline_milliseconds[16];
\r
903 _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);
\r
904 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
905 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
907 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
909 /* Signal successful start */
\r
910 service->status.dwCurrentState = SERVICE_RUNNING;
\r
911 SetServiceStatus(service->status_handle, &service->status);
\r
913 /* Continue waiting for a clean startup. */
\r
914 if (deadline == WAIT_TIMEOUT) {
\r
915 if (service->throttle_delay > delay) {
\r
916 if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
\r
918 else service->throttle = 0;
\r
924 /* Stop the service */
\r
925 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
\r
926 service->allow_restart = false;
\r
927 if (service->wait_handle) {
\r
928 UnregisterWait(service->wait_handle);
\r
929 service->wait_handle = 0;
\r
932 if (default_action && ! exitcode && ! graceful) {
\r
933 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
937 /* Signal we are stopping */
\r
939 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
940 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
941 SetServiceStatus(service->status_handle, &service->status);
\r
944 /* Nothing to do if service isn't running */
\r
945 if (service->pid) {
\r
946 /* Shut down service */
\r
947 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
\r
948 kill_process(service, service->process_handle, service->pid, 0);
\r
950 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
952 end_service((void *) service, true);
\r
954 /* Signal we stopped */
\r
956 service->status.dwCurrentState = SERVICE_STOPPED;
\r
958 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
959 service->status.dwServiceSpecificExitCode = exitcode;
\r
962 service->status.dwWin32ExitCode = NO_ERROR;
\r
963 service->status.dwServiceSpecificExitCode = 0;
\r
965 SetServiceStatus(service->status_handle, &service->status);
\r
971 /* Callback function triggered when the server exits */
\r
972 void CALLBACK end_service(void *arg, unsigned char why) {
\r
973 nssm_service_t *service = (nssm_service_t *) arg;
\r
975 if (service->stopping) return;
\r
977 service->stopping = true;
\r
979 /* Check exit code */
\r
980 unsigned long exitcode = 0;
\r
982 if (service->process_handle) {
\r
983 GetExitCodeProcess(service->process_handle, &exitcode);
\r
984 if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);
\r
985 CloseHandle(service->process_handle);
\r
987 else GetSystemTimeAsFileTime(&service->exit_time);
\r
989 service->process_handle = 0;
\r
992 Log that the service ended BEFORE logging about killing the process
\r
993 tree. See below for the possible values of the why argument.
\r
996 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);
\r
997 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
\r
1001 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
1002 if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);
\r
1006 The why argument is true if our wait timed out or false otherwise.
\r
1007 Our wait is infinite so why will never be true when called by the system.
\r
1008 If it is indeed true, assume we were called from stop_service() because
\r
1009 this is a controlled shutdown, and don't take any restart action.
\r
1012 if (! service->allow_restart) return;
\r
1014 /* What action should we take? */
\r
1015 int action = NSSM_EXIT_RESTART;
\r
1016 TCHAR action_string[ACTION_LEN];
\r
1017 bool default_action;
\r
1018 if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
\r
1019 for (int i = 0; exit_action_strings[i]; i++) {
\r
1020 if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
1028 /* Try to restart the service or return failure code to service manager */
\r
1029 case NSSM_EXIT_RESTART:
\r
1030 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
\r
1031 while (monitor_service(service)) {
\r
1032 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
\r
1037 /* Do nothing, just like srvany would */
\r
1038 case NSSM_EXIT_IGNORE:
\r
1039 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
\r
1043 /* Tell the service manager we are finished */
\r
1044 case NSSM_EXIT_REALLY:
\r
1045 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
\r
1046 stop_service(service, exitcode, true, default_action);
\r
1049 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
1050 case NSSM_EXIT_UNCLEAN:
\r
1051 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
\r
1052 stop_service(service, exitcode, false, default_action);
\r
1059 void throttle_restart(nssm_service_t *service) {
\r
1060 /* This can't be a restart if the service is already running. */
\r
1061 if (! service->throttle++) return;
\r
1063 int ms = throttle_milliseconds(service->throttle);
\r
1065 if (service->throttle > 7) service->throttle = 8;
\r
1067 TCHAR threshold[8], milliseconds[8];
\r
1068 _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);
\r
1069 _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);
\r
1070 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
\r
1072 if (use_critical_section) EnterCriticalSection(&service->throttle_section);
\r
1073 else if (service->throttle_timer) {
\r
1074 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
1075 service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
1076 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
1079 service->status.dwCurrentState = SERVICE_PAUSED;
\r
1080 SetServiceStatus(service->status_handle, &service->status);
\r
1082 if (use_critical_section) {
\r
1083 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
1084 LeaveCriticalSection(&service->throttle_section);
\r
1087 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\r
1093 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
1094 the number of milliseconds we expect the operation to take, and optionally
\r
1095 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
1096 operation completing or dwCheckPoint increasing, the system will consider the
\r
1097 service to be hung.
\r
1099 However the system will consider the service to be hung after 30000
\r
1100 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
1101 changed. Therefore if we want to wait longer than that we must periodically
\r
1102 increase dwCheckPoint.
\r
1104 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
1105 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
1106 time dwCheckPoint is also increased.
\r
1108 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
1109 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
1110 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
1111 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
1114 Only doing both these things will prevent the system from killing the service.
\r
1116 Returns: 1 if the wait timed out.
\r
1117 0 if the wait completed.
\r
1120 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {
\r
1121 unsigned long interval;
\r
1122 unsigned long waithint;
\r
1123 unsigned long ret;
\r
1124 unsigned long waited;
\r
1125 TCHAR interval_milliseconds[16];
\r
1126 TCHAR timeout_milliseconds[16];
\r
1127 TCHAR waited_milliseconds[16];
\r
1128 TCHAR *function = function_name;
\r
1130 /* Add brackets to function name. */
\r
1131 size_t funclen = _tcslen(function_name) + 3;
\r
1132 TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));
\r
1134 if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;
\r
1137 _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);
\r
1139 waithint = service->status.dwWaitHint;
\r
1141 while (waited < timeout) {
\r
1142 interval = timeout - waited;
\r
1143 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
1145 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
1146 service->status.dwWaitHint += interval;
\r
1147 service->status.dwCheckPoint++;
\r
1148 SetServiceStatus(service->status_handle, &service->status);
\r
1151 _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);
\r
1152 _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);
\r
1153 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
1156 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
1157 case WAIT_OBJECT_0:
\r
1161 case WAIT_TIMEOUT:
\r
1170 waited += interval;
\r
1174 if (func) HeapFree(GetProcessHeap(), 0, func);
\r