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
391 error = GetLastError();
\r
392 if (error != ERROR_INVALID_LEVEL) {
\r
393 CloseHandle(service->handle);
\r
394 CloseServiceHandle(services);
\r
395 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
400 else if (error != ERROR_INVALID_LEVEL) {
\r
401 CloseHandle(service->handle);
\r
402 CloseServiceHandle(services);
\r
403 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));
\r
408 QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);
\r
409 error = GetLastError();
\r
410 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
411 SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);
\r
412 if (! description) {
\r
413 CloseHandle(service->handle);
\r
414 CloseServiceHandle(services);
\r
415 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("pre_edit_service()"));
\r
419 if (QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {
\r
420 if (description->lpDescription) _sntprintf_s(service->description, _countof(service->description), _TRUNCATE, _T("%s"), description->lpDescription);
\r
421 HeapFree(GetProcessHeap(), 0, description);
\r
424 HeapFree(GetProcessHeap(), 0, description);
\r
425 CloseHandle(service->handle);
\r
426 CloseServiceHandle(services);
\r
427 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
432 CloseHandle(service->handle);
\r
433 CloseServiceHandle(services);
\r
434 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
438 /* Get NSSM details. */
\r
439 get_parameters(service, 0);
\r
441 CloseServiceHandle(services);
\r
443 if (! service->exe[0]) {
\r
444 print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);
\r
445 service->native = true;
\r
448 nssm_gui(IDD_EDIT, service);
\r
453 /* About to remove the service */
\r
454 int pre_remove_service(int argc, TCHAR **argv) {
\r
455 nssm_service_t *service = alloc_nssm_service();
\r
456 set_nssm_service_defaults(service);
\r
457 if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
459 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
460 if (argc < 2) return nssm_gui(IDD_REMOVE, service);
\r
461 if (str_equiv(argv[1], _T("confirm"))) {
\r
462 int ret = remove_service(service);
\r
463 cleanup_nssm_service(service);
\r
466 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
470 /* Install the service */
\r
471 int install_service(nssm_service_t *service) {
\r
472 if (! service) return 1;
\r
474 /* Open service manager */
\r
475 SC_HANDLE services = open_service_manager();
\r
477 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
478 cleanup_nssm_service(service);
\r
482 /* Get path of this program */
\r
483 GetModuleFileName(0, service->image, _countof(service->image));
\r
485 /* Create the service - settings will be changed in edit_service() */
\r
486 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
487 if (! service->handle) {
\r
488 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
489 CloseServiceHandle(services);
\r
493 if (edit_service(service, false)) {
\r
494 DeleteService(service->handle);
\r
495 CloseServiceHandle(services);
\r
499 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
502 CloseServiceHandle(services);
\r
507 /* Edit the service. */
\r
508 int edit_service(nssm_service_t *service, bool editing) {
\r
509 if (! service) return 1;
\r
512 The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS
\r
513 and SERVICE_INTERACTIVE_PROCESS.
\r
515 service->type &= SERVICE_INTERACTIVE_PROCESS;
\r
516 service->type |= SERVICE_WIN32_OWN_PROCESS;
\r
518 /* Startup type. */
\r
519 unsigned long startup;
\r
520 switch (service->startup) {
\r
521 case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;
\r
522 case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;
\r
523 default: startup = SERVICE_AUTO_START;
\r
526 /* Display name. */
\r
527 if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);
\r
530 Username must be NULL if we aren't changing or an account name.
\r
531 We must explicitly user LOCALSYSTEM to change it when we are editing.
\r
532 Password must be NULL if we aren't changing, a password or "".
\r
533 Empty passwords are valid but we won't allow them in the GUI.
\r
535 TCHAR *username = 0;
\r
536 TCHAR *password = 0;
\r
537 if (service->usernamelen) {
\r
538 username = service->username;
\r
539 if (service->passwordlen) password = service->password;
\r
540 else password = _T("");
\r
542 else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;
\r
544 if (grant_logon_as_service(username)) {
\r
545 print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);
\r
549 if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {
\r
550 print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
\r
554 if (service->description[0] || editing) {
\r
555 SERVICE_DESCRIPTION description;
\r
556 ZeroMemory(&description, sizeof(description));
\r
557 if (service->description[0]) description.lpDescription = service->description;
\r
558 else description.lpDescription = 0;
\r
559 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, &description)) {
\r
560 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service->name, error_string(GetLastError()), 0);
\r
564 SERVICE_DELAYED_AUTO_START_INFO delayed;
\r
565 ZeroMemory(&delayed, sizeof(delayed));
\r
566 if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;
\r
567 else delayed.fDelayedAutostart = 0;
\r
568 /* Delayed startup isn't supported until Vista. */
\r
569 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {
\r
570 unsigned long error = GetLastError();
\r
571 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
572 if (error != ERROR_INVALID_LEVEL) {
\r
573 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);
\r
577 /* Don't mess with parameters which aren't ours. */
\r
578 if (! service->native) {
\r
579 /* Now we need to put the parameters into the registry */
\r
580 if (create_parameters(service, editing)) {
\r
581 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
585 set_service_recovery(service);
\r
591 /* Remove the service */
\r
592 int remove_service(nssm_service_t *service) {
\r
593 if (! service) return 1;
\r
595 /* Open service manager */
\r
596 SC_HANDLE services = open_service_manager();
\r
598 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
602 /* Try to open the service */
\r
603 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
604 if (! service->handle) {
\r
605 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
606 CloseServiceHandle(services);
\r
610 /* Get the canonical service name. We open it case insensitively. */
\r
611 unsigned long bufsize = _countof(service->displayname);
\r
612 GetServiceDisplayName(services, service->name, service->displayname, &bufsize);
\r
613 bufsize = _countof(service->name);
\r
614 GetServiceKeyName(services, service->displayname, service->name, &bufsize);
\r
616 /* Try to delete the service */
\r
617 if (! DeleteService(service->handle)) {
\r
618 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
619 CloseServiceHandle(services);
\r
624 CloseServiceHandle(services);
\r
626 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\r
630 /* Service initialisation */
\r
631 void WINAPI service_main(unsigned long argc, TCHAR **argv) {
\r
632 nssm_service_t *service = alloc_nssm_service();
\r
633 if (! service) return;
\r
635 if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {
\r
636 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);
\r
640 /* We can use a condition variable in a critical section on Vista or later. */
\r
641 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
642 else use_critical_section = false;
\r
644 /* Initialise status */
\r
645 ZeroMemory(&service->status, sizeof(service->status));
\r
646 service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
647 service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
648 service->status.dwWin32ExitCode = NO_ERROR;
\r
649 service->status.dwServiceSpecificExitCode = 0;
\r
650 service->status.dwCheckPoint = 0;
\r
651 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
653 /* Signal we AREN'T running the server */
\r
654 service->process_handle = 0;
\r
657 /* Register control handler */
\r
658 service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
\r
659 if (! service->status_handle) {
\r
660 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
664 log_service_control(service->name, 0, true);
\r
666 service->status.dwCurrentState = SERVICE_START_PENDING;
\r
667 service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
\r
668 SetServiceStatus(service->status_handle, &service->status);
\r
671 /* Try to create the exit action parameters; we don't care if it fails */
\r
672 create_exit_action(service->name, exit_action_strings[0], false);
\r
674 SC_HANDLE services = open_service_manager();
\r
676 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
677 set_service_recovery(service);
\r
678 CloseServiceHandle(services);
\r
682 /* Used for signalling a resume if the service pauses when throttled. */
\r
683 if (use_critical_section) {
\r
684 InitializeCriticalSection(&service->throttle_section);
\r
685 service->throttle_section_initialised = true;
\r
688 service->throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
689 if (! service->throttle_timer) {
\r
690 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
\r
694 monitor_service(service);
\r
697 /* Make sure service recovery actions are taken where necessary */
\r
698 void set_service_recovery(nssm_service_t *service) {
\r
699 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
700 ZeroMemory(&flag, sizeof(flag));
\r
701 flag.fFailureActionsOnNonCrashFailures = true;
\r
703 /* This functionality was added in Vista so the call may fail */
\r
704 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
705 unsigned long error = GetLastError();
\r
706 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
707 if (error != ERROR_INVALID_LEVEL) {
\r
708 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);
\r
713 int monitor_service(nssm_service_t *service) {
\r
714 /* Set service status to started */
\r
715 int ret = start_service(service);
\r
718 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);
\r
719 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
\r
722 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\r
724 /* Monitor service */
\r
725 if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
726 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
\r
732 TCHAR *service_control_text(unsigned long control) {
\r
734 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
735 case 0: return _T("START");
\r
736 case SERVICE_CONTROL_STOP: return _T("STOP");
\r
737 case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");
\r
738 case SERVICE_CONTROL_PAUSE: return _T("PAUSE");
\r
739 case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");
\r
740 case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");
\r
745 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {
\r
746 TCHAR *text = service_control_text(control);
\r
747 unsigned long event;
\r
750 /* "0x" + 8 x hex + NULL */
\r
751 text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));
\r
753 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
756 if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {
\r
757 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
758 HeapFree(GetProcessHeap(), 0, text);
\r
762 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
764 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
765 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
767 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
769 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
770 HeapFree(GetProcessHeap(), 0, text);
\r
774 /* Service control handler */
\r
775 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
776 nssm_service_t *service = (nssm_service_t *) context;
\r
779 case SERVICE_CONTROL_INTERROGATE:
\r
780 /* We always keep the service status up-to-date so this is a no-op. */
\r
783 case SERVICE_CONTROL_SHUTDOWN:
\r
784 case SERVICE_CONTROL_STOP:
\r
785 log_service_control(service->name, control, true);
\r
787 We MUST acknowledge the stop request promptly but we're committed to
\r
788 waiting for the application to exit. Spawn a new thread to wait
\r
789 while we acknowledge the request.
\r
791 if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
\r
792 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
795 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
796 to complete in time in this thread.
\r
798 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
799 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
800 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
802 stop_service(service, 0, true, true);
\r
806 case SERVICE_CONTROL_CONTINUE:
\r
807 log_service_control(service->name, control, true);
\r
808 service->throttle = 0;
\r
809 if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
\r
811 if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
812 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
813 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
815 service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
816 service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
\r
817 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
\r
818 SetServiceStatus(service->status_handle, &service->status);
\r
821 case SERVICE_CONTROL_PAUSE:
\r
823 We don't accept pause messages but it isn't possible to register
\r
824 only for continue messages so we have to handle this case.
\r
826 log_service_control(service->name, control, false);
\r
827 return ERROR_CALL_NOT_IMPLEMENTED;
\r
830 /* Unknown control */
\r
831 log_service_control(service->name, control, false);
\r
832 return ERROR_CALL_NOT_IMPLEMENTED;
\r
835 /* Start the service */
\r
836 int start_service(nssm_service_t *service) {
\r
837 service->stopping = false;
\r
838 service->allow_restart = true;
\r
840 if (service->process_handle) return 0;
\r
842 /* Allocate a STARTUPINFO structure for a new process */
\r
844 ZeroMemory(&si, sizeof(si));
\r
845 si.cb = sizeof(si);
\r
847 /* Allocate a PROCESSINFO structure for the process */
\r
848 PROCESS_INFORMATION pi;
\r
849 ZeroMemory(&pi, sizeof(pi));
\r
851 /* Get startup parameters */
\r
852 int ret = get_parameters(service, &si);
\r
854 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
\r
855 return stop_service(service, 2, true, true);
\r
858 /* Launch executable with arguments */
\r
859 TCHAR cmd[CMD_LENGTH];
\r
860 if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {
\r
861 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);
\r
862 close_output_handles(&si);
\r
863 return stop_service(service, 2, true, true);
\r
866 throttle_restart(service);
\r
868 bool inherit_handles = false;
\r
869 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
870 unsigned long flags = 0;
\r
872 flags |= CREATE_UNICODE_ENVIRONMENT;
\r
874 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {
\r
875 unsigned long exitcode = 3;
\r
876 unsigned long error = GetLastError();
\r
877 if (error == ERROR_INVALID_PARAMETER && service->env) {
\r
878 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);
\r
879 if (test_environment(service->env)) exitcode = 4;
\r
881 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
\r
882 close_output_handles(&si);
\r
883 return stop_service(service, exitcode, true, true);
\r
885 service->process_handle = pi.hProcess;
\r
886 service->pid = pi.dwProcessId;
\r
888 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
890 close_output_handles(&si);
\r
893 Wait for a clean startup before changing the service status to RUNNING
\r
894 but be mindful of the fact that we are blocking the service control manager
\r
895 so abandon the wait before too much time has elapsed.
\r
897 unsigned long delay = service->throttle_delay;
\r
898 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
899 TCHAR delay_milliseconds[16];
\r
900 _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);
\r
901 TCHAR deadline_milliseconds[16];
\r
902 _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);
\r
903 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
904 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
906 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
908 /* Signal successful start */
\r
909 service->status.dwCurrentState = SERVICE_RUNNING;
\r
910 SetServiceStatus(service->status_handle, &service->status);
\r
912 /* Continue waiting for a clean startup. */
\r
913 if (deadline == WAIT_TIMEOUT) {
\r
914 if (service->throttle_delay > delay) {
\r
915 if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
\r
917 else service->throttle = 0;
\r
923 /* Stop the service */
\r
924 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
\r
925 service->allow_restart = false;
\r
926 if (service->wait_handle) {
\r
927 UnregisterWait(service->wait_handle);
\r
928 service->wait_handle = 0;
\r
931 if (default_action && ! exitcode && ! graceful) {
\r
932 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
936 /* Signal we are stopping */
\r
938 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
939 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
940 SetServiceStatus(service->status_handle, &service->status);
\r
943 /* Nothing to do if service isn't running */
\r
944 if (service->pid) {
\r
945 /* Shut down service */
\r
946 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
\r
947 kill_process(service, service->process_handle, service->pid, 0);
\r
949 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
951 end_service((void *) service, true);
\r
953 /* Signal we stopped */
\r
955 service->status.dwCurrentState = SERVICE_STOPPED;
\r
957 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
958 service->status.dwServiceSpecificExitCode = exitcode;
\r
961 service->status.dwWin32ExitCode = NO_ERROR;
\r
962 service->status.dwServiceSpecificExitCode = 0;
\r
964 SetServiceStatus(service->status_handle, &service->status);
\r
970 /* Callback function triggered when the server exits */
\r
971 void CALLBACK end_service(void *arg, unsigned char why) {
\r
972 nssm_service_t *service = (nssm_service_t *) arg;
\r
974 if (service->stopping) return;
\r
976 service->stopping = true;
\r
978 /* Check exit code */
\r
979 unsigned long exitcode = 0;
\r
981 if (service->process_handle) {
\r
982 GetExitCodeProcess(service->process_handle, &exitcode);
\r
983 if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);
\r
984 CloseHandle(service->process_handle);
\r
986 else GetSystemTimeAsFileTime(&service->exit_time);
\r
988 service->process_handle = 0;
\r
991 Log that the service ended BEFORE logging about killing the process
\r
992 tree. See below for the possible values of the why argument.
\r
995 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);
\r
996 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
\r
1000 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
1001 if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);
\r
1005 The why argument is true if our wait timed out or false otherwise.
\r
1006 Our wait is infinite so why will never be true when called by the system.
\r
1007 If it is indeed true, assume we were called from stop_service() because
\r
1008 this is a controlled shutdown, and don't take any restart action.
\r
1011 if (! service->allow_restart) return;
\r
1013 /* What action should we take? */
\r
1014 int action = NSSM_EXIT_RESTART;
\r
1015 TCHAR action_string[ACTION_LEN];
\r
1016 bool default_action;
\r
1017 if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
\r
1018 for (int i = 0; exit_action_strings[i]; i++) {
\r
1019 if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
1027 /* Try to restart the service or return failure code to service manager */
\r
1028 case NSSM_EXIT_RESTART:
\r
1029 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
\r
1030 while (monitor_service(service)) {
\r
1031 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
\r
1036 /* Do nothing, just like srvany would */
\r
1037 case NSSM_EXIT_IGNORE:
\r
1038 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
\r
1042 /* Tell the service manager we are finished */
\r
1043 case NSSM_EXIT_REALLY:
\r
1044 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
\r
1045 stop_service(service, exitcode, true, default_action);
\r
1048 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
1049 case NSSM_EXIT_UNCLEAN:
\r
1050 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
\r
1051 stop_service(service, exitcode, false, default_action);
\r
1058 void throttle_restart(nssm_service_t *service) {
\r
1059 /* This can't be a restart if the service is already running. */
\r
1060 if (! service->throttle++) return;
\r
1062 int ms = throttle_milliseconds(service->throttle);
\r
1064 if (service->throttle > 7) service->throttle = 8;
\r
1066 TCHAR threshold[8], milliseconds[8];
\r
1067 _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);
\r
1068 _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);
\r
1069 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
\r
1071 if (use_critical_section) EnterCriticalSection(&service->throttle_section);
\r
1072 else if (service->throttle_timer) {
\r
1073 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
1074 service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
1075 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
1078 service->status.dwCurrentState = SERVICE_PAUSED;
\r
1079 SetServiceStatus(service->status_handle, &service->status);
\r
1081 if (use_critical_section) {
\r
1082 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
1083 LeaveCriticalSection(&service->throttle_section);
\r
1086 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\r
1092 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
1093 the number of milliseconds we expect the operation to take, and optionally
\r
1094 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
1095 operation completing or dwCheckPoint increasing, the system will consider the
\r
1096 service to be hung.
\r
1098 However the system will consider the service to be hung after 30000
\r
1099 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
1100 changed. Therefore if we want to wait longer than that we must periodically
\r
1101 increase dwCheckPoint.
\r
1103 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
1104 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
1105 time dwCheckPoint is also increased.
\r
1107 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
1108 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
1109 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
1110 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
1113 Only doing both these things will prevent the system from killing the service.
\r
1115 Returns: 1 if the wait timed out.
\r
1116 0 if the wait completed.
\r
1119 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {
\r
1120 unsigned long interval;
\r
1121 unsigned long waithint;
\r
1122 unsigned long ret;
\r
1123 unsigned long waited;
\r
1124 TCHAR interval_milliseconds[16];
\r
1125 TCHAR timeout_milliseconds[16];
\r
1126 TCHAR waited_milliseconds[16];
\r
1127 TCHAR *function = function_name;
\r
1129 /* Add brackets to function name. */
\r
1130 size_t funclen = _tcslen(function_name) + 3;
\r
1131 TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));
\r
1133 if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;
\r
1136 _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);
\r
1138 waithint = service->status.dwWaitHint;
\r
1140 while (waited < timeout) {
\r
1141 interval = timeout - waited;
\r
1142 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
1144 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
1145 service->status.dwWaitHint += interval;
\r
1146 service->status.dwCheckPoint++;
\r
1147 SetServiceStatus(service->status_handle, &service->status);
\r
1150 _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);
\r
1151 _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);
\r
1152 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
1155 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
1156 case WAIT_OBJECT_0:
\r
1160 case WAIT_TIMEOUT:
\r
1169 waited += interval;
\r
1173 if (func) HeapFree(GetProcessHeap(), 0, func);
\r