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 static int grant_logon_as_service(const TCHAR *username) {
\r
41 if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;
\r
43 /* Open Policy object. */
\r
44 LSA_OBJECT_ATTRIBUTES attributes;
\r
45 ZeroMemory(&attributes, sizeof(attributes));
\r
49 NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, &policy);
\r
51 print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status)));
\r
55 /* Look up SID for the account. */
\r
56 LSA_UNICODE_STRING lsa_username;
\r
58 lsa_username.Buffer = (wchar_t *) username;
\r
59 lsa_username.Length = (unsigned short) _tcslen(username) * sizeof(TCHAR);
\r
60 lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR);
\r
63 mbstowcs_s(&buflen, NULL, 0, username, _TRUNCATE);
\r
64 lsa_username.MaximumLength = buflen * sizeof(wchar_t);
\r
65 lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t);
\r
66 lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength);
\r
67 if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, username, _TRUNCATE);
\r
70 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("grant_logon_as_service()"));
\r
75 LSA_REFERENCED_DOMAIN_LIST *translated_domains;
\r
76 LSA_TRANSLATED_SID *translated_sid;
\r
77 status = LsaLookupNames(policy, 1, &lsa_username, &translated_domains, &translated_sid);
\r
79 HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);
\r
82 LsaFreeMemory(translated_domains);
\r
83 LsaFreeMemory(translated_sid);
\r
85 print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status)));
\r
89 if (translated_sid->Use != SidTypeUser) {
\r
90 LsaFreeMemory(translated_domains);
\r
91 LsaFreeMemory(translated_sid);
\r
93 print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
\r
97 LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];
\r
98 if (! trust || ! IsValidSid(trust->Sid)) {
\r
99 LsaFreeMemory(translated_domains);
\r
100 LsaFreeMemory(translated_sid);
\r
102 print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);
\r
106 /* GetSidSubAuthority*() return pointers! */
\r
107 unsigned char *n = GetSidSubAuthorityCount(trust->Sid);
\r
109 /* Convert translated SID to SID. */
\r
110 SID *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));
\r
112 LsaFreeMemory(translated_domains);
\r
113 LsaFreeMemory(translated_sid);
\r
115 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("grant_logon_as_service"));
\r
119 unsigned long error;
\r
120 if (! InitializeSid(sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {
\r
121 error = GetLastError();
\r
122 HeapFree(GetProcessHeap(), 0, sid);
\r
123 LsaFreeMemory(translated_domains);
\r
124 LsaFreeMemory(translated_sid);
\r
126 print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));
\r
130 for (unsigned char i = 0; i <= *n; i++) {
\r
131 unsigned long *sub = GetSidSubAuthority(sid, i);
\r
132 if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);
\r
133 else *sub = translated_sid->RelativeId;
\r
136 LsaFreeMemory(translated_domains);
\r
137 LsaFreeMemory(translated_sid);
\r
139 /* Check if the SID has the "Log on as a service" right. */
\r
140 LSA_UNICODE_STRING lsa_right;
\r
141 lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;
\r
142 lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);
\r
143 lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);
\r
145 LSA_UNICODE_STRING *rights;
\r
146 unsigned long count = ~0;
\r
147 status = LsaEnumerateAccountRights(policy, sid, &rights, &count);
\r
150 If the account has no rights set LsaEnumerateAccountRights() will return
\r
151 STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.
\r
153 error = LsaNtStatusToWinError(status);
\r
154 if (error != ERROR_FILE_NOT_FOUND) {
\r
155 HeapFree(GetProcessHeap(), 0, sid);
\r
157 print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));
\r
162 for (unsigned long i = 0; i < count; i++) {
\r
163 if (rights[i].Length != lsa_right.Length) continue;
\r
164 if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;
\r
165 /* The SID has the right. */
\r
166 HeapFree(GetProcessHeap(), 0, sid);
\r
167 LsaFreeMemory(rights);
\r
171 LsaFreeMemory(rights);
\r
173 /* Add the right. */
\r
174 status = LsaAddAccountRights(policy, sid, &lsa_right, 1);
\r
175 HeapFree(GetProcessHeap(), 0, sid);
\r
178 print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));
\r
182 print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);
\r
186 /* Set default values which aren't zero. */
\r
187 void set_nssm_service_defaults(nssm_service_t *service) {
\r
188 if (! service) return;
\r
190 service->type = SERVICE_WIN32_OWN_PROCESS;
\r
191 service->stdin_sharing = NSSM_STDIN_SHARING;
\r
192 service->stdin_disposition = NSSM_STDIN_DISPOSITION;
\r
193 service->stdin_flags = NSSM_STDIN_FLAGS;
\r
194 service->stdout_sharing = NSSM_STDOUT_SHARING;
\r
195 service->stdout_disposition = NSSM_STDOUT_DISPOSITION;
\r
196 service->stdout_flags = NSSM_STDOUT_FLAGS;
\r
197 service->stderr_sharing = NSSM_STDERR_SHARING;
\r
198 service->stderr_disposition = NSSM_STDERR_DISPOSITION;
\r
199 service->stderr_flags = NSSM_STDERR_FLAGS;
\r
200 service->throttle_delay = NSSM_RESET_THROTTLE_RESTART;
\r
201 service->stop_method = ~0;
\r
202 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
203 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
204 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
207 /* Allocate and zero memory for a service. */
\r
208 nssm_service_t *alloc_nssm_service() {
\r
209 nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));
\r
210 if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("alloc_nssm_service()"), 0);
\r
214 /* Free memory for a service. */
\r
215 void cleanup_nssm_service(nssm_service_t *service) {
\r
216 if (! service) return;
\r
217 if (service->username) HeapFree(GetProcessHeap(), 0, service->username);
\r
218 if (service->password) {
\r
219 SecureZeroMemory(service->password, service->passwordlen);
\r
220 HeapFree(GetProcessHeap(), 0, service->password);
\r
222 if (service->env) HeapFree(GetProcessHeap(), 0, service->env);
\r
223 if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);
\r
224 if (service->handle) CloseServiceHandle(service->handle);
\r
225 if (service->process_handle) CloseHandle(service->process_handle);
\r
226 if (service->wait_handle) UnregisterWait(service->process_handle);
\r
227 if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);
\r
228 if (service->throttle_timer) CloseHandle(service->throttle_timer);
\r
229 HeapFree(GetProcessHeap(), 0, service);
\r
232 /* About to install the service */
\r
233 int pre_install_service(int argc, TCHAR **argv) {
\r
234 nssm_service_t *service = alloc_nssm_service();
\r
235 set_nssm_service_defaults(service);
\r
236 if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
238 /* Show the dialogue box if we didn't give the service name and path */
\r
239 if (argc < 2) return nssm_gui(IDD_INSTALL, service);
\r
242 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("service"), _T("pre_install_service()"));
\r
245 _sntprintf_s(service->exe, _countof(service->exe), _TRUNCATE, _T("%s"), argv[1]);
\r
247 /* Arguments are optional */
\r
248 size_t flagslen = 0;
\r
251 for (i = 2; i < argc; i++) flagslen += _tcslen(argv[i]) + 1;
\r
252 if (! flagslen) flagslen = 1;
\r
253 if (flagslen > _countof(service->flags)) {
\r
254 print_message(stderr, NSSM_MESSAGE_FLAGS_TOO_LONG);
\r
258 for (i = 2; i < argc; i++) {
\r
259 size_t len = _tcslen(argv[i]);
\r
260 memmove(service->flags + s, argv[i], len * sizeof(TCHAR));
\r
262 if (i < argc - 1) service->flags[s++] = _T(' ');
\r
265 /* Work out directory name */
\r
266 _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);
\r
267 strip_basename(service->dir);
\r
269 int ret = install_service(service);
\r
270 cleanup_nssm_service(service);
\r
274 /* About to edit the service. */
\r
275 int pre_edit_service(int argc, TCHAR **argv) {
\r
276 /* Require service name. */
\r
277 if (argc < 1) return usage(1);
\r
279 nssm_service_t *service = alloc_nssm_service();
\r
280 _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
282 /* Open service manager */
\r
283 SC_HANDLE services = open_service_manager();
\r
285 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
289 /* Try to open the service */
\r
290 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
291 if (! service->handle) {
\r
292 CloseServiceHandle(services);
\r
293 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
297 /* Get system details. */
\r
298 unsigned long bufsize;
\r
299 unsigned long error;
\r
300 QUERY_SERVICE_CONFIG *qsc;
\r
302 QueryServiceConfig(service->handle, 0, 0, &bufsize);
\r
303 error = GetLastError();
\r
304 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
305 qsc = (QUERY_SERVICE_CONFIG *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufsize);
\r
307 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("QUERY_SERVICE_CONFIG"), _T("pre_edit_service()"), 0);
\r
312 CloseHandle(service->handle);
\r
313 CloseServiceHandle(services);
\r
314 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service->name, error_string(error), 0);
\r
318 if (! QueryServiceConfig(service->handle, qsc, bufsize, &bufsize)) {
\r
319 HeapFree(GetProcessHeap(), 0, qsc);
\r
320 CloseHandle(service->handle);
\r
321 CloseServiceHandle(services);
\r
322 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service->name, error_string(GetLastError()), 0);
\r
326 service->type = qsc->dwServiceType;
\r
327 if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {
\r
328 HeapFree(GetProcessHeap(), 0, qsc);
\r
329 CloseHandle(service->handle);
\r
330 CloseServiceHandle(services);
\r
331 print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, _T("SERVICE_WIN32_OWN_PROCESS"), 0);
\r
335 switch (qsc->dwStartType) {
\r
336 case SERVICE_DEMAND_START: service->startup = NSSM_STARTUP_MANUAL; break;
\r
337 case SERVICE_DISABLED: service->startup = NSSM_STARTUP_DISABLED; break;
\r
338 default: service->startup = NSSM_STARTUP_AUTOMATIC;
\r
340 if (! str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) {
\r
341 size_t len = _tcslen(qsc->lpServiceStartName);
\r
342 service->username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(TCHAR));
\r
343 if (service->username) {
\r
344 memmove(service->username, qsc->lpServiceStartName, (len + 1) * sizeof(TCHAR));
\r
345 service->usernamelen = (unsigned long) len;
\r
348 HeapFree(GetProcessHeap(), 0, qsc);
\r
349 CloseHandle(service->handle);
\r
350 CloseServiceHandle(services);
\r
351 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("username"), _T("pre_edit_service()"));
\r
355 _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);
\r
357 /* Get the canonical service name. We open it case insensitively. */
\r
358 bufsize = _countof(service->name);
\r
359 GetServiceKeyName(services, service->displayname, service->name, &bufsize);
\r
361 /* Remember the executable in case it isn't NSSM. */
\r
362 _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);
\r
363 HeapFree(GetProcessHeap(), 0, qsc);
\r
365 /* Get extended system details. */
\r
366 if (service->startup == NSSM_STARTUP_AUTOMATIC) {
\r
367 QueryServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, 0, 0, &bufsize);
\r
368 error = GetLastError();
\r
369 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
370 SERVICE_DELAYED_AUTO_START_INFO *info = (SERVICE_DELAYED_AUTO_START_INFO *) HeapAlloc(GetProcessHeap(), 0, bufsize);
\r
372 CloseHandle(service->handle);
\r
373 CloseServiceHandle(services);
\r
374 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_DELAYED_AUTO_START_INFO"), _T("pre_edit_service()"));
\r
378 if (QueryServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (unsigned char *) info, bufsize, &bufsize)) {
\r
379 if (info->fDelayedAutostart) service->startup = NSSM_STARTUP_DELAYED;
\r
382 error = GetLastError();
\r
383 if (error != ERROR_INVALID_LEVEL) {
\r
384 CloseHandle(service->handle);
\r
385 CloseServiceHandle(services);
\r
386 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
391 else if (error != ERROR_INVALID_LEVEL) {
\r
392 CloseHandle(service->handle);
\r
393 CloseServiceHandle(services);
\r
394 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));
\r
399 QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);
\r
400 error = GetLastError();
\r
401 if (error == ERROR_INSUFFICIENT_BUFFER) {
\r
402 SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);
\r
403 if (! description) {
\r
404 CloseHandle(service->handle);
\r
405 CloseServiceHandle(services);
\r
406 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("pre_edit_service()"));
\r
410 if (QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {
\r
411 if (description->lpDescription) _sntprintf_s(service->description, _countof(service->description), _TRUNCATE, _T("%s"), description->lpDescription);
\r
412 HeapFree(GetProcessHeap(), 0, description);
\r
415 HeapFree(GetProcessHeap(), 0, description);
\r
416 CloseHandle(service->handle);
\r
417 CloseServiceHandle(services);
\r
418 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
423 CloseHandle(service->handle);
\r
424 CloseServiceHandle(services);
\r
425 print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));
\r
429 /* Get NSSM details. */
\r
430 get_parameters(service, 0);
\r
432 CloseServiceHandle(services);
\r
434 if (! service->exe[0]) {
\r
435 print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);
\r
436 service->native = true;
\r
439 nssm_gui(IDD_EDIT, service);
\r
444 /* About to remove the service */
\r
445 int pre_remove_service(int argc, TCHAR **argv) {
\r
446 nssm_service_t *service = alloc_nssm_service();
\r
447 set_nssm_service_defaults(service);
\r
448 if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);
\r
450 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
451 if (argc < 2) return nssm_gui(IDD_REMOVE, service);
\r
452 if (str_equiv(argv[1], _T("confirm"))) {
\r
453 int ret = remove_service(service);
\r
454 cleanup_nssm_service(service);
\r
457 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
461 /* Install the service */
\r
462 int install_service(nssm_service_t *service) {
\r
463 if (! service) return 1;
\r
465 /* Open service manager */
\r
466 SC_HANDLE services = open_service_manager();
\r
468 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
469 cleanup_nssm_service(service);
\r
473 /* Get path of this program */
\r
474 GetModuleFileName(0, service->image, _countof(service->image));
\r
476 /* Create the service - settings will be changed in edit_service() */
\r
477 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
478 if (! service->handle) {
\r
479 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
480 CloseServiceHandle(services);
\r
484 if (edit_service(service, false)) {
\r
485 DeleteService(service->handle);
\r
486 CloseServiceHandle(services);
\r
490 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);
\r
493 CloseServiceHandle(services);
\r
498 /* Edit the service. */
\r
499 int edit_service(nssm_service_t *service, bool editing) {
\r
500 if (! service) return 1;
\r
503 The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS
\r
504 and SERVICE_INTERACTIVE_PROCESS.
\r
506 service->type &= SERVICE_INTERACTIVE_PROCESS;
\r
507 service->type |= SERVICE_WIN32_OWN_PROCESS;
\r
509 /* Startup type. */
\r
510 unsigned long startup;
\r
511 switch (service->startup) {
\r
512 case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;
\r
513 case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;
\r
514 default: startup = SERVICE_AUTO_START;
\r
517 /* Display name. */
\r
518 if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);
\r
521 Username must be NULL if we aren't changing or an account name.
\r
522 We must explicitly user LOCALSYSTEM to change it when we are editing.
\r
523 Password must be NULL if we aren't changing, a password or "".
\r
524 Empty passwords are valid but we won't allow them in the GUI.
\r
526 TCHAR *username = 0;
\r
527 TCHAR *password = 0;
\r
528 if (service->usernamelen) {
\r
529 username = service->username;
\r
530 if (service->passwordlen) password = service->password;
\r
531 else password = _T("");
\r
533 else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;
\r
535 if (grant_logon_as_service(username)) {
\r
536 print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);
\r
540 if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {
\r
541 print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));
\r
545 if (service->description[0] || editing) {
\r
546 SERVICE_DESCRIPTION description;
\r
547 ZeroMemory(&description, sizeof(description));
\r
548 if (service->description[0]) description.lpDescription = service->description;
\r
549 else description.lpDescription = 0;
\r
550 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, &description)) {
\r
551 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service->name, error_string(GetLastError()), 0);
\r
555 SERVICE_DELAYED_AUTO_START_INFO delayed;
\r
556 ZeroMemory(&delayed, sizeof(delayed));
\r
557 if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;
\r
558 else delayed.fDelayedAutostart = 0;
\r
559 /* Delayed startup isn't supported until Vista. */
\r
560 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {
\r
561 unsigned long error = GetLastError();
\r
562 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
563 if (error != ERROR_INVALID_LEVEL) {
\r
564 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);
\r
568 /* Don't mess with parameters which aren't ours. */
\r
569 if (! service->native) {
\r
570 /* Now we need to put the parameters into the registry */
\r
571 if (create_parameters(service, editing)) {
\r
572 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
576 set_service_recovery(service);
\r
582 /* Remove the service */
\r
583 int remove_service(nssm_service_t *service) {
\r
584 if (! service) return 1;
\r
586 /* Open service manager */
\r
587 SC_HANDLE services = open_service_manager();
\r
589 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
593 /* Try to open the service */
\r
594 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
595 if (! service->handle) {
\r
596 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
597 CloseServiceHandle(services);
\r
601 /* Get the canonical service name. We open it case insensitively. */
\r
602 unsigned long bufsize = _countof(service->displayname);
\r
603 GetServiceDisplayName(services, service->name, service->displayname, &bufsize);
\r
604 bufsize = _countof(service->name);
\r
605 GetServiceKeyName(services, service->displayname, service->name, &bufsize);
\r
607 /* Try to delete the service */
\r
608 if (! DeleteService(service->handle)) {
\r
609 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
610 CloseServiceHandle(services);
\r
615 CloseServiceHandle(services);
\r
617 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);
\r
621 /* Service initialisation */
\r
622 void WINAPI service_main(unsigned long argc, TCHAR **argv) {
\r
623 nssm_service_t *service = alloc_nssm_service();
\r
624 if (! service) return;
\r
626 if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {
\r
627 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);
\r
631 /* We can use a condition variable in a critical section on Vista or later. */
\r
632 if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;
\r
633 else use_critical_section = false;
\r
635 /* Initialise status */
\r
636 ZeroMemory(&service->status, sizeof(service->status));
\r
637 service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
638 service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
639 service->status.dwWin32ExitCode = NO_ERROR;
\r
640 service->status.dwServiceSpecificExitCode = 0;
\r
641 service->status.dwCheckPoint = 0;
\r
642 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
644 /* Signal we AREN'T running the server */
\r
645 service->process_handle = 0;
\r
648 /* Register control handler */
\r
649 service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);
\r
650 if (! service->status_handle) {
\r
651 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
655 log_service_control(service->name, 0, true);
\r
657 service->status.dwCurrentState = SERVICE_START_PENDING;
\r
658 service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;
\r
659 SetServiceStatus(service->status_handle, &service->status);
\r
662 /* Try to create the exit action parameters; we don't care if it fails */
\r
663 create_exit_action(service->name, exit_action_strings[0], false);
\r
665 SC_HANDLE services = open_service_manager();
\r
667 service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);
\r
668 set_service_recovery(service);
\r
669 CloseServiceHandle(services);
\r
673 /* Used for signalling a resume if the service pauses when throttled. */
\r
674 if (use_critical_section) {
\r
675 InitializeCriticalSection(&service->throttle_section);
\r
676 service->throttle_section_initialised = true;
\r
679 service->throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
680 if (! service->throttle_timer) {
\r
681 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);
\r
685 monitor_service(service);
\r
688 /* Make sure service recovery actions are taken where necessary */
\r
689 void set_service_recovery(nssm_service_t *service) {
\r
690 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
691 ZeroMemory(&flag, sizeof(flag));
\r
692 flag.fFailureActionsOnNonCrashFailures = true;
\r
694 /* This functionality was added in Vista so the call may fail */
\r
695 if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
696 unsigned long error = GetLastError();
\r
697 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
698 if (error != ERROR_INVALID_LEVEL) {
\r
699 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);
\r
704 int monitor_service(nssm_service_t *service) {
\r
705 /* Set service status to started */
\r
706 int ret = start_service(service);
\r
709 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);
\r
710 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);
\r
713 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);
\r
715 /* Monitor service */
\r
716 if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
717 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);
\r
723 TCHAR *service_control_text(unsigned long control) {
\r
725 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
726 case 0: return _T("START");
\r
727 case SERVICE_CONTROL_STOP: return _T("STOP");
\r
728 case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");
\r
729 case SERVICE_CONTROL_PAUSE: return _T("PAUSE");
\r
730 case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");
\r
731 case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");
\r
736 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {
\r
737 TCHAR *text = service_control_text(control);
\r
738 unsigned long event;
\r
741 /* "0x" + 8 x hex + NULL */
\r
742 text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));
\r
744 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
747 if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {
\r
748 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);
\r
749 HeapFree(GetProcessHeap(), 0, text);
\r
753 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
755 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
756 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
758 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
760 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
761 HeapFree(GetProcessHeap(), 0, text);
\r
765 /* Service control handler */
\r
766 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
767 nssm_service_t *service = (nssm_service_t *) context;
\r
770 case SERVICE_CONTROL_INTERROGATE:
\r
771 /* We always keep the service status up-to-date so this is a no-op. */
\r
774 case SERVICE_CONTROL_SHUTDOWN:
\r
775 case SERVICE_CONTROL_STOP:
\r
776 log_service_control(service->name, control, true);
\r
778 We MUST acknowledge the stop request promptly but we're committed to
\r
779 waiting for the application to exit. Spawn a new thread to wait
\r
780 while we acknowledge the request.
\r
782 if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {
\r
783 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);
\r
786 We couldn't create a thread to tidy up so we'll have to force the tidyup
\r
787 to complete in time in this thread.
\r
789 service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;
\r
790 service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;
\r
791 service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;
\r
793 stop_service(service, 0, true, true);
\r
797 case SERVICE_CONTROL_CONTINUE:
\r
798 log_service_control(service->name, control, true);
\r
799 service->throttle = 0;
\r
800 if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);
\r
802 if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
803 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
804 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
806 service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
807 service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;
\r
808 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);
\r
809 SetServiceStatus(service->status_handle, &service->status);
\r
812 case SERVICE_CONTROL_PAUSE:
\r
814 We don't accept pause messages but it isn't possible to register
\r
815 only for continue messages so we have to handle this case.
\r
817 log_service_control(service->name, control, false);
\r
818 return ERROR_CALL_NOT_IMPLEMENTED;
\r
821 /* Unknown control */
\r
822 log_service_control(service->name, control, false);
\r
823 return ERROR_CALL_NOT_IMPLEMENTED;
\r
826 /* Start the service */
\r
827 int start_service(nssm_service_t *service) {
\r
828 service->stopping = false;
\r
829 service->allow_restart = true;
\r
831 if (service->process_handle) return 0;
\r
833 /* Allocate a STARTUPINFO structure for a new process */
\r
835 ZeroMemory(&si, sizeof(si));
\r
836 si.cb = sizeof(si);
\r
838 /* Allocate a PROCESSINFO structure for the process */
\r
839 PROCESS_INFORMATION pi;
\r
840 ZeroMemory(&pi, sizeof(pi));
\r
842 /* Get startup parameters */
\r
843 int ret = get_parameters(service, &si);
\r
845 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);
\r
846 return stop_service(service, 2, true, true);
\r
849 /* Launch executable with arguments */
\r
850 TCHAR cmd[CMD_LENGTH];
\r
851 if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {
\r
852 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);
\r
853 close_output_handles(&si);
\r
854 return stop_service(service, 2, true, true);
\r
857 throttle_restart(service);
\r
859 bool inherit_handles = false;
\r
860 if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;
\r
861 unsigned long flags = 0;
\r
863 flags |= CREATE_UNICODE_ENVIRONMENT;
\r
865 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {
\r
866 unsigned long exitcode = 3;
\r
867 unsigned long error = GetLastError();
\r
868 if (error == ERROR_INVALID_PARAMETER && service->env) {
\r
869 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);
\r
870 if (test_environment(service->env)) exitcode = 4;
\r
872 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);
\r
873 close_output_handles(&si);
\r
874 return stop_service(service, exitcode, true, true);
\r
876 service->process_handle = pi.hProcess;
\r
877 service->pid = pi.dwProcessId;
\r
879 if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));
\r
881 close_output_handles(&si);
\r
884 Wait for a clean startup before changing the service status to RUNNING
\r
885 but be mindful of the fact that we are blocking the service control manager
\r
886 so abandon the wait before too much time has elapsed.
\r
888 unsigned long delay = service->throttle_delay;
\r
889 if (delay > NSSM_SERVICE_STATUS_DEADLINE) {
\r
890 TCHAR delay_milliseconds[16];
\r
891 _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);
\r
892 TCHAR deadline_milliseconds[16];
\r
893 _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);
\r
894 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);
\r
895 delay = NSSM_SERVICE_STATUS_DEADLINE;
\r
897 unsigned long deadline = WaitForSingleObject(service->process_handle, delay);
\r
899 /* Signal successful start */
\r
900 service->status.dwCurrentState = SERVICE_RUNNING;
\r
901 SetServiceStatus(service->status_handle, &service->status);
\r
903 /* Continue waiting for a clean startup. */
\r
904 if (deadline == WAIT_TIMEOUT) {
\r
905 if (service->throttle_delay > delay) {
\r
906 if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;
\r
908 else service->throttle = 0;
\r
914 /* Stop the service */
\r
915 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {
\r
916 service->allow_restart = false;
\r
917 if (service->wait_handle) {
\r
918 UnregisterWait(service->wait_handle);
\r
919 service->wait_handle = 0;
\r
922 if (default_action && ! exitcode && ! graceful) {
\r
923 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
927 /* Signal we are stopping */
\r
929 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
930 service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
931 SetServiceStatus(service->status_handle, &service->status);
\r
934 /* Nothing to do if service isn't running */
\r
935 if (service->pid) {
\r
936 /* Shut down service */
\r
937 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);
\r
938 kill_process(service, service->process_handle, service->pid, 0);
\r
940 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);
\r
942 end_service((void *) service, true);
\r
944 /* Signal we stopped */
\r
946 service->status.dwCurrentState = SERVICE_STOPPED;
\r
948 service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
949 service->status.dwServiceSpecificExitCode = exitcode;
\r
952 service->status.dwWin32ExitCode = NO_ERROR;
\r
953 service->status.dwServiceSpecificExitCode = 0;
\r
955 SetServiceStatus(service->status_handle, &service->status);
\r
961 /* Callback function triggered when the server exits */
\r
962 void CALLBACK end_service(void *arg, unsigned char why) {
\r
963 nssm_service_t *service = (nssm_service_t *) arg;
\r
965 if (service->stopping) return;
\r
967 service->stopping = true;
\r
969 /* Check exit code */
\r
970 unsigned long exitcode = 0;
\r
972 if (service->process_handle) {
\r
973 GetExitCodeProcess(service->process_handle, &exitcode);
\r
974 if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);
\r
975 CloseHandle(service->process_handle);
\r
977 else GetSystemTimeAsFileTime(&service->exit_time);
\r
979 service->process_handle = 0;
\r
982 Log that the service ended BEFORE logging about killing the process
\r
983 tree. See below for the possible values of the why argument.
\r
986 _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);
\r
987 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);
\r
991 if (exitcode == STILL_ACTIVE) exitcode = 0;
\r
992 if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);
\r
996 The why argument is true if our wait timed out or false otherwise.
\r
997 Our wait is infinite so why will never be true when called by the system.
\r
998 If it is indeed true, assume we were called from stop_service() because
\r
999 this is a controlled shutdown, and don't take any restart action.
\r
1002 if (! service->allow_restart) return;
\r
1004 /* What action should we take? */
\r
1005 int action = NSSM_EXIT_RESTART;
\r
1006 TCHAR action_string[ACTION_LEN];
\r
1007 bool default_action;
\r
1008 if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {
\r
1009 for (int i = 0; exit_action_strings[i]; i++) {
\r
1010 if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
1018 /* Try to restart the service or return failure code to service manager */
\r
1019 case NSSM_EXIT_RESTART:
\r
1020 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);
\r
1021 while (monitor_service(service)) {
\r
1022 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);
\r
1027 /* Do nothing, just like srvany would */
\r
1028 case NSSM_EXIT_IGNORE:
\r
1029 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);
\r
1033 /* Tell the service manager we are finished */
\r
1034 case NSSM_EXIT_REALLY:
\r
1035 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);
\r
1036 stop_service(service, exitcode, true, default_action);
\r
1039 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
1040 case NSSM_EXIT_UNCLEAN:
\r
1041 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);
\r
1042 stop_service(service, exitcode, false, default_action);
\r
1049 void throttle_restart(nssm_service_t *service) {
\r
1050 /* This can't be a restart if the service is already running. */
\r
1051 if (! service->throttle++) return;
\r
1053 int ms = throttle_milliseconds(service->throttle);
\r
1055 if (service->throttle > 7) service->throttle = 8;
\r
1057 TCHAR threshold[8], milliseconds[8];
\r
1058 _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);
\r
1059 _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);
\r
1060 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);
\r
1062 if (use_critical_section) EnterCriticalSection(&service->throttle_section);
\r
1063 else if (service->throttle_timer) {
\r
1064 ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));
\r
1065 service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
1066 SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);
\r
1069 service->status.dwCurrentState = SERVICE_PAUSED;
\r
1070 SetServiceStatus(service->status_handle, &service->status);
\r
1072 if (use_critical_section) {
\r
1073 imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);
\r
1074 LeaveCriticalSection(&service->throttle_section);
\r
1077 if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);
\r
1083 When responding to a stop (or any other) request we need to set dwWaitHint to
\r
1084 the number of milliseconds we expect the operation to take, and optionally
\r
1085 increase dwCheckPoint. If dwWaitHint milliseconds elapses without the
\r
1086 operation completing or dwCheckPoint increasing, the system will consider the
\r
1087 service to be hung.
\r
1089 However the system will consider the service to be hung after 30000
\r
1090 milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not
\r
1091 changed. Therefore if we want to wait longer than that we must periodically
\r
1092 increase dwCheckPoint.
\r
1094 Furthermore, it will consider the service to be hung after 60000 milliseconds
\r
1095 regardless of the value of dwCheckPoint unless dwWaitHint is increased every
\r
1096 time dwCheckPoint is also increased.
\r
1098 Our strategy then is to retrieve the initial dwWaitHint and wait for
\r
1099 NSSM_SERVICE_STATUS_DEADLINE milliseconds. If the process is still running
\r
1100 and we haven't finished waiting we increment dwCheckPoint and add whichever is
\r
1101 smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to
\r
1104 Only doing both these things will prevent the system from killing the service.
\r
1106 Returns: 1 if the wait timed out.
\r
1107 0 if the wait completed.
\r
1110 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {
\r
1111 unsigned long interval;
\r
1112 unsigned long waithint;
\r
1113 unsigned long ret;
\r
1114 unsigned long waited;
\r
1115 TCHAR interval_milliseconds[16];
\r
1116 TCHAR timeout_milliseconds[16];
\r
1117 TCHAR waited_milliseconds[16];
\r
1118 TCHAR *function = function_name;
\r
1120 /* Add brackets to function name. */
\r
1121 size_t funclen = _tcslen(function_name) + 3;
\r
1122 TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));
\r
1124 if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;
\r
1127 _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);
\r
1129 waithint = service->status.dwWaitHint;
\r
1131 while (waited < timeout) {
\r
1132 interval = timeout - waited;
\r
1133 if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;
\r
1135 service->status.dwCurrentState = SERVICE_STOP_PENDING;
\r
1136 service->status.dwWaitHint += interval;
\r
1137 service->status.dwCheckPoint++;
\r
1138 SetServiceStatus(service->status_handle, &service->status);
\r
1141 _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);
\r
1142 _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);
\r
1143 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);
\r
1146 switch (WaitForSingleObject(service->process_handle, interval)) {
\r
1147 case WAIT_OBJECT_0:
\r
1151 case WAIT_TIMEOUT:
\r
1160 waited += interval;
\r
1164 if (func) HeapFree(GetProcessHeap(), 0, func);
\r