4ec51333f4806ac54cc0ee91c3cab28d26815a2c
[nssm.git] / service.cpp
1 #include "nssm.h"\r
2 \r
3 /* This is explicitly a wide string. */\r
4 #define NSSM_LOGON_AS_SERVICE_RIGHT L"SeServiceLogonRight"\r
5 \r
6 bool is_admin;\r
7 bool use_critical_section;\r
8 \r
9 extern imports_t imports;\r
10 extern settings_t settings[];\r
11 \r
12 const TCHAR *exit_action_strings[] = { _T("Restart"), _T("Ignore"), _T("Exit"), _T("Suicide"), 0 };\r
13 const TCHAR *startup_strings[] = { _T("SERVICE_AUTO_START"), _T("SERVICE_DELAYED_AUTO_START"), _T("SERVICE_DEMAND_START"), _T("SERVICE_DISABLED"), 0 };\r
14 \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
18   return ret * 1000;\r
19 }\r
20 \r
21 /*\r
22   Wrapper to be called in a new thread so that we can acknowledge a STOP\r
23   control immediately.\r
24 */\r
25 static unsigned long WINAPI shutdown_service(void *arg) {\r
26   return stop_service((nssm_service_t *) arg, 0, true, true);\r
27 }\r
28 \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
32   if (! ret) {\r
33     if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);\r
34     return 0;\r
35   }\r
36 \r
37   return ret;\r
38 }\r
39 \r
40 /* Open a service by name or display name. */\r
41 SC_HANDLE open_service(SC_HANDLE services, TCHAR *service_name, TCHAR *canonical_name, unsigned long canonical_namelen) {\r
42   SC_HANDLE service_handle = OpenService(services, service_name, SERVICE_ALL_ACCESS);\r
43   if (service_handle) {\r
44     if (canonical_name && canonical_name != service_name) {\r
45       if (_sntprintf_s(canonical_name, canonical_namelen, _TRUNCATE, _T("%s"), service_name) < 0) {\r
46         print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canonical_name"), _T("open_service()"));\r
47         return 0;\r
48       }\r
49     }\r
50     return service_handle;\r
51   }\r
52 \r
53   unsigned long error = GetLastError();\r
54   if (error != ERROR_SERVICE_DOES_NOT_EXIST) {\r
55     print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED, error_string(GetLastError()));\r
56     return 0;\r
57   }\r
58 \r
59   /* We can't look for a display name because there's no buffer to store it. */\r
60   if (! canonical_name) {\r
61     print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED, error_string(GetLastError()));\r
62     return 0;\r
63   }\r
64 \r
65   unsigned long bufsize, required, count, i;\r
66   unsigned long resume = 0;\r
67   EnumServicesStatus(services, SERVICE_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_KERNEL_DRIVER | SERVICE_WIN32, SERVICE_STATE_ALL, 0, 0, &required, &count, &resume);\r
68   error = GetLastError();\r
69   if (error != ERROR_MORE_DATA) {\r
70     print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));\r
71     return 0;\r
72   }\r
73 \r
74   ENUM_SERVICE_STATUS *status = (ENUM_SERVICE_STATUS *) HeapAlloc(GetProcessHeap(), 0, required);\r
75   if (! status) {\r
76     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("ENUM_SERVICE_STATUS"), _T("open_service()"));\r
77     return 0;\r
78   }\r
79 \r
80   bufsize = required;\r
81   while (true) {\r
82     /*\r
83       EnumServicesStatus() returns:\r
84       1 when it retrieved data and there's no more data to come.\r
85       0 and sets last error to ERROR_MORE_DATA when it retrieved data and\r
86         there's more data to come.\r
87       0 and sets last error to something else on error.\r
88     */\r
89     int ret = EnumServicesStatus(services, SERVICE_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_KERNEL_DRIVER | SERVICE_WIN32, SERVICE_STATE_ALL, status, bufsize, &required, &count, &resume);\r
90     if (! ret) {\r
91       error = GetLastError();\r
92       if (error != ERROR_MORE_DATA) {\r
93         HeapFree(GetProcessHeap(), 0, status);\r
94         print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));\r
95         return 0;\r
96       }\r
97     }\r
98 \r
99     for (i = 0; i < count; i++) {\r
100       if (str_equiv(status[i].lpDisplayName, service_name)) {\r
101         if (_sntprintf_s(canonical_name, canonical_namelen, _TRUNCATE, _T("%s"), status[i].lpServiceName) < 0) {\r
102           HeapFree(GetProcessHeap(), 0, status);\r
103           print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canonical_name"), _T("open_service()"));\r
104           return 0;\r
105         }\r
106 \r
107         HeapFree(GetProcessHeap(), 0, status);\r
108         return open_service(services, canonical_name, 0, 0);\r
109       }\r
110     }\r
111 \r
112     if (ret) break;\r
113   }\r
114 \r
115   /* Recurse so we can get an error message. */\r
116   return open_service(services, service_name, 0, 0);\r
117 }\r
118 \r
119 QUERY_SERVICE_CONFIG *query_service_config(const TCHAR *service_name, SC_HANDLE service_handle) {\r
120   QUERY_SERVICE_CONFIG *qsc;\r
121   unsigned long bufsize;\r
122   unsigned long error;\r
123 \r
124   QueryServiceConfig(service_handle, 0, 0, &bufsize);\r
125   error = GetLastError();\r
126   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
127     qsc = (QUERY_SERVICE_CONFIG *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufsize);\r
128     if (! qsc) {\r
129       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("QUERY_SERVICE_CONFIG"), _T("query_service_config()"), 0);\r
130       return 0;\r
131     }\r
132   }\r
133   else {\r
134     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service_name, error_string(error), 0);\r
135     return 0;\r
136   }\r
137 \r
138   if (! QueryServiceConfig(service_handle, qsc, bufsize, &bufsize)) {\r
139     HeapFree(GetProcessHeap(), 0, qsc);\r
140     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service_name, error_string(GetLastError()), 0);\r
141     return 0;\r
142   }\r
143 \r
144   return qsc;\r
145 }\r
146 \r
147 int set_service_description(const TCHAR *service_name, SC_HANDLE service_handle, TCHAR *buffer) {\r
148   SERVICE_DESCRIPTION description;\r
149   ZeroMemory(&description, sizeof(description));\r
150   /*\r
151     lpDescription must be NULL if we aren't changing, the new description\r
152     or "".\r
153   */\r
154   if (buffer && buffer[0]) description.lpDescription = buffer;\r
155   else description.lpDescription = _T("");\r
156 \r
157   if (ChangeServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, &description)) return 0;\r
158 \r
159   log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service_name, error_string(GetLastError()), 0);\r
160   return 1;\r
161 }\r
162 \r
163 int get_service_description(const TCHAR *service_name, SC_HANDLE service_handle, unsigned long len, TCHAR *buffer) {\r
164   if (! buffer) return 1;\r
165 \r
166   unsigned long bufsize;\r
167   QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);\r
168   unsigned long error = GetLastError();\r
169   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
170     SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
171     if (! description) {\r
172       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("get_service_description()"));\r
173       return 2;\r
174     }\r
175 \r
176     if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {\r
177       if (description->lpDescription) _sntprintf_s(buffer, len, _TRUNCATE, _T("%s"), description->lpDescription);\r
178       else ZeroMemory(buffer, len * sizeof(TCHAR));\r
179       HeapFree(GetProcessHeap(), 0, description);\r
180       return 0;\r
181     }\r
182     else {\r
183       HeapFree(GetProcessHeap(), 0, description);\r
184       print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));\r
185       return 3;\r
186     }\r
187   }\r
188   else {\r
189     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));\r
190     return 4;\r
191   }\r
192 \r
193   return 0;\r
194 }\r
195 \r
196 int get_service_startup(const TCHAR *service_name, SC_HANDLE service_handle, const QUERY_SERVICE_CONFIG *qsc, unsigned long *startup) {\r
197   if (! qsc) return 1;\r
198 \r
199   switch (qsc->dwStartType) {\r
200     case SERVICE_DEMAND_START: *startup = NSSM_STARTUP_MANUAL; break;\r
201     case SERVICE_DISABLED: *startup = NSSM_STARTUP_DISABLED; break;\r
202     default: *startup = NSSM_STARTUP_AUTOMATIC;\r
203   }\r
204 \r
205   if (*startup != NSSM_STARTUP_AUTOMATIC) return 0;\r
206 \r
207   /* Check for delayed start. */\r
208   unsigned long bufsize;\r
209   unsigned long error;\r
210   QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, 0, 0, &bufsize);\r
211   error = GetLastError();\r
212   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
213     SERVICE_DELAYED_AUTO_START_INFO *info = (SERVICE_DELAYED_AUTO_START_INFO *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
214     if (! info) {\r
215       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_DELAYED_AUTO_START_INFO"), _T("get_service_startup()"));\r
216       return 2;\r
217     }\r
218 \r
219     if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (unsigned char *) info, bufsize, &bufsize)) {\r
220       if (info->fDelayedAutostart) *startup = NSSM_STARTUP_DELAYED;\r
221       HeapFree(GetProcessHeap(), 0, info);\r
222       return 0;\r
223     }\r
224     else {\r
225       error = GetLastError();\r
226       if (error != ERROR_INVALID_LEVEL) {\r
227         print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));\r
228         return 3;\r
229       }\r
230     }\r
231   }\r
232   else if (error != ERROR_INVALID_LEVEL) {\r
233     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));\r
234     return 3;\r
235   }\r
236 \r
237   return 0;\r
238 }\r
239 \r
240 int get_service_username(const TCHAR *service_name, const QUERY_SERVICE_CONFIG *qsc, TCHAR **username, size_t *usernamelen) {\r
241   if (! username) return 1;\r
242   if (! usernamelen) return 1;\r
243 \r
244   *username = 0;\r
245   *usernamelen = 0;\r
246 \r
247   if (! qsc) return 1;\r
248 \r
249   if (str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;\r
250 \r
251   size_t len = _tcslen(qsc->lpServiceStartName);\r
252   *username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(TCHAR));\r
253   if (! *username) {\r
254     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("username"), _T("get_service_username()"));\r
255     return 2;\r
256   }\r
257 \r
258   memmove(*username, qsc->lpServiceStartName, (len + 1) * sizeof(TCHAR));\r
259   *usernamelen = len;\r
260 \r
261   return 0;\r
262 }\r
263 \r
264 int grant_logon_as_service(const TCHAR *username) {\r
265   if (! username) return 0;\r
266   if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;\r
267 \r
268   /* Open Policy object. */\r
269   LSA_OBJECT_ATTRIBUTES attributes;\r
270   ZeroMemory(&attributes, sizeof(attributes));\r
271 \r
272   LSA_HANDLE policy;\r
273 \r
274   NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, &policy);\r
275   if (status) {\r
276     print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status)));\r
277     return 1;\r
278   }\r
279 \r
280   /* Look up SID for the account. */\r
281   LSA_UNICODE_STRING lsa_username;\r
282 #ifdef UNICODE\r
283   lsa_username.Buffer = (wchar_t *) username;\r
284   lsa_username.Length = (unsigned short) _tcslen(username) * sizeof(TCHAR);\r
285   lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR);\r
286 #else\r
287   size_t buflen;\r
288   mbstowcs_s(&buflen, NULL, 0, username, _TRUNCATE);\r
289   lsa_username.MaximumLength = (unsigned short) buflen * sizeof(wchar_t);\r
290   lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t);\r
291   lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength);\r
292   if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, username, _TRUNCATE);\r
293   else {\r
294     LsaClose(policy);\r
295     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("grant_logon_as_service()"));\r
296     return 2;\r
297   }\r
298 #endif\r
299 \r
300   LSA_REFERENCED_DOMAIN_LIST *translated_domains;\r
301   LSA_TRANSLATED_SID *translated_sid;\r
302   status = LsaLookupNames(policy, 1, &lsa_username, &translated_domains, &translated_sid);\r
303 #ifndef UNICODE\r
304   HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);\r
305 #endif\r
306   if (status) {\r
307     LsaFreeMemory(translated_domains);\r
308     LsaFreeMemory(translated_sid);\r
309     LsaClose(policy);\r
310     print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status)));\r
311     return 3;\r
312   }\r
313 \r
314   if (translated_sid->Use != SidTypeUser) {\r
315     LsaFreeMemory(translated_domains);\r
316     LsaFreeMemory(translated_sid);\r
317     LsaClose(policy);\r
318     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
319     return 4;\r
320   }\r
321 \r
322   LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];\r
323   if (! trust || ! IsValidSid(trust->Sid)) {\r
324     LsaFreeMemory(translated_domains);\r
325     LsaFreeMemory(translated_sid);\r
326     LsaClose(policy);\r
327     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
328     return 4;\r
329   }\r
330 \r
331   /* GetSidSubAuthority*() return pointers! */\r
332   unsigned char *n = GetSidSubAuthorityCount(trust->Sid);\r
333 \r
334   /* Convert translated SID to SID. */\r
335   SID *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));\r
336   if (! sid) {\r
337     LsaFreeMemory(translated_domains);\r
338     LsaFreeMemory(translated_sid);\r
339     LsaClose(policy);\r
340     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("grant_logon_as_service"));\r
341     return 4;\r
342   }\r
343 \r
344   unsigned long error;\r
345   if (! InitializeSid(sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {\r
346     error = GetLastError();\r
347     HeapFree(GetProcessHeap(), 0, sid);\r
348     LsaFreeMemory(translated_domains);\r
349     LsaFreeMemory(translated_sid);\r
350     LsaClose(policy);\r
351     print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));\r
352     return 5;\r
353   }\r
354 \r
355   for (unsigned char i = 0; i <= *n; i++) {\r
356     unsigned long *sub = GetSidSubAuthority(sid, i);\r
357     if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);\r
358     else *sub = translated_sid->RelativeId;\r
359   }\r
360 \r
361   LsaFreeMemory(translated_domains);\r
362   LsaFreeMemory(translated_sid);\r
363 \r
364   /* Check if the SID has the "Log on as a service" right. */\r
365   LSA_UNICODE_STRING lsa_right;\r
366   lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;\r
367   lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);\r
368   lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);\r
369 \r
370   LSA_UNICODE_STRING *rights;\r
371   unsigned long count = ~0;\r
372   status = LsaEnumerateAccountRights(policy, sid, &rights, &count);\r
373   if (status) {\r
374     /*\r
375       If the account has no rights set LsaEnumerateAccountRights() will return\r
376       STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.\r
377     */\r
378     error = LsaNtStatusToWinError(status);\r
379     if (error != ERROR_FILE_NOT_FOUND) {\r
380       HeapFree(GetProcessHeap(), 0, sid);\r
381       LsaClose(policy);\r
382       print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));\r
383       return 4;\r
384     }\r
385   }\r
386 \r
387   for (unsigned long i = 0; i < count; i++) {\r
388     if (rights[i].Length != lsa_right.Length) continue;\r
389     if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;\r
390     /* The SID has the right. */\r
391     HeapFree(GetProcessHeap(), 0, sid);\r
392     LsaFreeMemory(rights);\r
393     LsaClose(policy);\r
394     return 0;\r
395   }\r
396   LsaFreeMemory(rights);\r
397 \r
398   /* Add the right. */\r
399   status = LsaAddAccountRights(policy, sid, &lsa_right, 1);\r
400   HeapFree(GetProcessHeap(), 0, sid);\r
401   LsaClose(policy);\r
402   if (status) {\r
403     print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));\r
404     return 5;\r
405   }\r
406 \r
407   print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);\r
408   return 0;\r
409 }\r
410 \r
411 /* Set default values which aren't zero. */\r
412 void set_nssm_service_defaults(nssm_service_t *service) {\r
413   if (! service) return;\r
414 \r
415   service->type = SERVICE_WIN32_OWN_PROCESS;\r
416   service->stdin_sharing = NSSM_STDIN_SHARING;\r
417   service->stdin_disposition = NSSM_STDIN_DISPOSITION;\r
418   service->stdin_flags = NSSM_STDIN_FLAGS;\r
419   service->stdout_sharing = NSSM_STDOUT_SHARING;\r
420   service->stdout_disposition = NSSM_STDOUT_DISPOSITION;\r
421   service->stdout_flags = NSSM_STDOUT_FLAGS;\r
422   service->stderr_sharing = NSSM_STDERR_SHARING;\r
423   service->stderr_disposition = NSSM_STDERR_DISPOSITION;\r
424   service->stderr_flags = NSSM_STDERR_FLAGS;\r
425   service->throttle_delay = NSSM_RESET_THROTTLE_RESTART;\r
426   service->stop_method = ~0;\r
427   service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
428   service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
429   service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
430 }\r
431 \r
432 /* Allocate and zero memory for a service. */\r
433 nssm_service_t *alloc_nssm_service() {\r
434   nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));\r
435   if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("alloc_nssm_service()"), 0);\r
436   return service;\r
437 }\r
438 \r
439 /* Free memory for a service. */\r
440 void cleanup_nssm_service(nssm_service_t *service) {\r
441   if (! service) return;\r
442   if (service->username) HeapFree(GetProcessHeap(), 0, service->username);\r
443   if (service->password) {\r
444     SecureZeroMemory(service->password, service->passwordlen);\r
445     HeapFree(GetProcessHeap(), 0, service->password);\r
446   }\r
447   if (service->env) HeapFree(GetProcessHeap(), 0, service->env);\r
448   if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);\r
449   if (service->handle) CloseServiceHandle(service->handle);\r
450   if (service->process_handle) CloseHandle(service->process_handle);\r
451   if (service->wait_handle) UnregisterWait(service->process_handle);\r
452   if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);\r
453   if (service->throttle_timer) CloseHandle(service->throttle_timer);\r
454   HeapFree(GetProcessHeap(), 0, service);\r
455 }\r
456 \r
457 /* About to install the service */\r
458 int pre_install_service(int argc, TCHAR **argv) {\r
459   nssm_service_t *service = alloc_nssm_service();\r
460   set_nssm_service_defaults(service);\r
461   if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
462 \r
463   /* Show the dialogue box if we didn't give the service name and path */\r
464   if (argc < 2) return nssm_gui(IDD_INSTALL, service);\r
465 \r
466   if (! service) {\r
467     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("service"), _T("pre_install_service()"));\r
468     return 1;\r
469   }\r
470   _sntprintf_s(service->exe, _countof(service->exe), _TRUNCATE, _T("%s"), argv[1]);\r
471 \r
472   /* Arguments are optional */\r
473   size_t flagslen = 0;\r
474   size_t s = 0;\r
475   int i;\r
476   for (i = 2; i < argc; i++) flagslen += _tcslen(argv[i]) + 1;\r
477   if (! flagslen) flagslen = 1;\r
478   if (flagslen > _countof(service->flags)) {\r
479     print_message(stderr, NSSM_MESSAGE_FLAGS_TOO_LONG);\r
480     return 2;\r
481   }\r
482 \r
483   for (i = 2; i < argc; i++) {\r
484     size_t len = _tcslen(argv[i]);\r
485     memmove(service->flags + s, argv[i], len * sizeof(TCHAR));\r
486     s += len;\r
487     if (i < argc - 1) service->flags[s++] = _T(' ');\r
488   }\r
489 \r
490   /* Work out directory name */\r
491   _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);\r
492   strip_basename(service->dir);\r
493 \r
494   int ret = install_service(service);\r
495   cleanup_nssm_service(service);\r
496   return ret;\r
497 }\r
498 \r
499 /* About to edit the service. */\r
500 int pre_edit_service(int argc, TCHAR **argv) {\r
501   /* Require service name. */\r
502   if (argc < 2) return usage(1);\r
503 \r
504   /* Are we editing on the command line? */\r
505   enum { MODE_EDITING, MODE_GETTING, MODE_SETTING, MODE_RESETTING } mode = MODE_EDITING;\r
506   const TCHAR *verb = argv[0];\r
507   const TCHAR *service_name = argv[1];\r
508   bool getting = false;\r
509   bool unsetting = false;\r
510 \r
511   /* Minimum number of arguments. */\r
512   int mandatory = 2;\r
513   /* Index of first value. */\r
514   int remainder = 3;\r
515   int i;\r
516   if (str_equiv(verb, _T("get"))) {\r
517     mandatory = 3;\r
518     mode = MODE_GETTING;\r
519   }\r
520   else if (str_equiv(verb, _T("set"))) {\r
521     mandatory = 4;\r
522     mode = MODE_SETTING;\r
523   }\r
524   else if (str_equiv(verb, _T("reset")) || str_equiv(verb, _T("unset"))) {\r
525     mandatory = 3;\r
526     mode = MODE_RESETTING;\r
527   }\r
528   if (argc < mandatory) return usage(1);\r
529 \r
530   const TCHAR *parameter = 0;\r
531   settings_t *setting = 0;\r
532   TCHAR *additional;\r
533 \r
534   /* Validate the parameter. */\r
535   if (mandatory > 2) {\r
536     bool additional_mandatory = false;\r
537 \r
538     parameter = argv[2];\r
539     for (i = 0; settings[i].name; i++) {\r
540       setting = &settings[i];\r
541       if (! str_equiv(setting->name, parameter)) continue;\r
542       if (((setting->additional & ADDITIONAL_GETTING) && mode == MODE_GETTING) || ((setting->additional & ADDITIONAL_SETTING) && mode == MODE_SETTING) || ((setting->additional & ADDITIONAL_RESETTING) && mode == MODE_RESETTING)) {\r
543         additional_mandatory = true;\r
544         mandatory++;\r
545       }\r
546       break;\r
547     }\r
548     if (! settings[i].name) {\r
549       print_message(stderr, NSSM_MESSAGE_INVALID_PARAMETER, parameter);\r
550       for (i = 0; settings[i].name; i++) _ftprintf(stderr, _T("%s\n"), settings[i].name);\r
551       return 1;\r
552     }\r
553     if (argc < mandatory) return usage(1);\r
554 \r
555     additional = 0;\r
556     if (additional_mandatory) {\r
557       additional = argv[3];\r
558       remainder = 4;\r
559     }\r
560     else additional = argv[remainder];\r
561   }\r
562 \r
563   nssm_service_t *service = alloc_nssm_service();\r
564   _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), service_name);\r
565 \r
566   /* Open service manager */\r
567   SC_HANDLE services = open_service_manager();\r
568   if (! services) {\r
569     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
570     return 2;\r
571   }\r
572 \r
573   /* Try to open the service */\r
574   service->handle = open_service(services, service->name, service->name, _countof(service->name));\r
575   if (! service->handle) {\r
576     CloseServiceHandle(services);\r
577     return 3;\r
578   }\r
579 \r
580   /* Get system details. */\r
581   QUERY_SERVICE_CONFIG *qsc = query_service_config(service->name, service->handle);\r
582   if (! qsc) {\r
583     CloseHandle(service->handle);\r
584     CloseServiceHandle(services);\r
585     return 4;\r
586   }\r
587 \r
588   service->type = qsc->dwServiceType;\r
589   if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {\r
590     if (mode != MODE_GETTING) {\r
591       HeapFree(GetProcessHeap(), 0, qsc);\r
592       CloseHandle(service->handle);\r
593       CloseServiceHandle(services);\r
594       print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, NSSM_WIN32_OWN_PROCESS, 0);\r
595       return 3;\r
596     }\r
597   }\r
598 \r
599   if (get_service_startup(service->name, service->handle, qsc, &service->startup)) {\r
600     if (mode != MODE_GETTING) {\r
601       HeapFree(GetProcessHeap(), 0, qsc);\r
602       CloseHandle(service->handle);\r
603       CloseServiceHandle(services);\r
604       return 4;\r
605     }\r
606   }\r
607 \r
608   if (get_service_username(service->name, qsc, &service->username, &service->usernamelen)) {\r
609     if (mode != MODE_GETTING) {\r
610       HeapFree(GetProcessHeap(), 0, qsc);\r
611       CloseHandle(service->handle);\r
612       CloseServiceHandle(services);\r
613       return 5;\r
614     }\r
615   }\r
616 \r
617   _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);\r
618 \r
619   /* Get the canonical service name. We open it case insensitively. */\r
620   unsigned long bufsize = _countof(service->name);\r
621   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
622 \r
623   /* Remember the executable in case it isn't NSSM. */\r
624   _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);\r
625   HeapFree(GetProcessHeap(), 0, qsc);\r
626 \r
627   /* Get extended system details. */\r
628   if (get_service_description(service->name, service->handle, _countof(service->description), service->description)) {\r
629     if (mode != MODE_GETTING) {\r
630       CloseHandle(service->handle);\r
631       CloseServiceHandle(services);\r
632       return 6;\r
633     }\r
634   }\r
635 \r
636   /* Get NSSM details. */\r
637   get_parameters(service, 0);\r
638 \r
639   CloseServiceHandle(services);\r
640 \r
641   if (! service->exe[0]) {\r
642     service->native = true;\r
643     if (mode != MODE_GETTING) print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);\r
644   }\r
645 \r
646   /* Editing with the GUI. */\r
647   if (mode == MODE_EDITING) {\r
648     nssm_gui(IDD_EDIT, service);\r
649     return 0;\r
650   }\r
651 \r
652   /* Trying to manage App* parameters for a non-NSSM service. */\r
653   if (! setting->native && service->native) {\r
654     CloseHandle(service->handle);\r
655     print_message(stderr, NSSM_MESSAGE_NATIVE_PARAMETER, setting->name, NSSM);\r
656     return 1;\r
657   }\r
658 \r
659   HKEY key;\r
660   value_t value;\r
661   int ret;\r
662 \r
663   if (mode == MODE_GETTING) {\r
664     if (! service->native) {\r
665       key = open_registry(service->name, KEY_READ);\r
666       if (! key) return 4;\r
667     }\r
668 \r
669     if (setting->native) ret = get_setting(service->name, service->handle, setting, &value, additional);\r
670     else ret = get_setting(service->name, key, setting, &value, additional);\r
671     if (ret < 0) {\r
672       CloseHandle(service->handle);\r
673       return 5;\r
674     }\r
675 \r
676     switch (setting->type) {\r
677       case REG_EXPAND_SZ:\r
678       case REG_MULTI_SZ:\r
679       case REG_SZ:\r
680         _tprintf(_T("%s\n"), value.string ? value.string : _T(""));\r
681         HeapFree(GetProcessHeap(), 0, value.string);\r
682         break;\r
683 \r
684       case REG_DWORD:\r
685         _tprintf(_T("%u\n"), value.numeric);\r
686         break;\r
687     }\r
688 \r
689     if (! service->native) RegCloseKey(key);\r
690     CloseHandle(service->handle);\r
691     return 0;\r
692   }\r
693 \r
694   /* Build the value. */\r
695   if (mode == MODE_RESETTING) {\r
696     /* Unset the parameter. */\r
697     value.string = 0;\r
698   }\r
699   else {\r
700     /* Set the parameter. */\r
701     size_t len = 0;\r
702     size_t delimiterlen = (setting->additional & ADDITIONAL_CRLF) ? 2 : 1;\r
703     for (i = remainder; i < argc; i++) len += _tcslen(argv[i]) + delimiterlen;\r
704     len++;\r
705 \r
706     value.string = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));\r
707     if (! value.string) {\r
708       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("value"), _T("edit_service()"));\r
709       CloseHandle(service->handle);\r
710       return 2;\r
711     }\r
712 \r
713     size_t s = 0;\r
714     for (i = remainder; i < argc; i++) {\r
715       size_t len = _tcslen(argv[i]);\r
716       memmove(value.string + s, argv[i], len * sizeof(TCHAR));\r
717       s += len;\r
718       if (i < argc - 1) {\r
719         if (setting->additional & ADDITIONAL_CRLF) {\r
720           value.string[s++] = _T('\r');\r
721           value.string[s++] = _T('\n');\r
722         }\r
723         else value.string[s++] = _T(' ');\r
724       }\r
725     }\r
726     value.string[s] = _T('\0');\r
727   }\r
728 \r
729   if (! service->native) {\r
730     key = open_registry(service->name, KEY_WRITE);\r
731     if (! key) {\r
732       if (value.string) HeapFree(GetProcessHeap(), 0, value.string);\r
733       return 4;\r
734     }\r
735   }\r
736 \r
737   if (setting->native) ret = set_setting(service->name, service->handle, setting, &value, additional);\r
738   else ret = set_setting(service->name, key, setting, &value, additional);\r
739   if (value.string) HeapFree(GetProcessHeap(), 0, value.string);\r
740   if (ret < 0) {\r
741     if (! service->native) RegCloseKey(key);\r
742     CloseHandle(service->handle);\r
743     return 6;\r
744   }\r
745 \r
746   if (! service->native) RegCloseKey(key);\r
747   CloseHandle(service->handle);\r
748 \r
749   return 0;\r
750 }\r
751 \r
752 /* About to remove the service */\r
753 int pre_remove_service(int argc, TCHAR **argv) {\r
754   nssm_service_t *service = alloc_nssm_service();\r
755   set_nssm_service_defaults(service);\r
756   if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
757 \r
758   /* Show dialogue box if we didn't pass service name and "confirm" */\r
759   if (argc < 2) return nssm_gui(IDD_REMOVE, service);\r
760   if (str_equiv(argv[1], _T("confirm"))) {\r
761     int ret = remove_service(service);\r
762     cleanup_nssm_service(service);\r
763     return ret;\r
764   }\r
765   print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);\r
766   return 100;\r
767 }\r
768 \r
769 /* Install the service */\r
770 int install_service(nssm_service_t *service) {\r
771   if (! service) return 1;\r
772 \r
773   /* Open service manager */\r
774   SC_HANDLE services = open_service_manager();\r
775   if (! services) {\r
776     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
777     cleanup_nssm_service(service);\r
778     return 2;\r
779   }\r
780 \r
781   /* Get path of this program */\r
782   GetModuleFileName(0, service->image, _countof(service->image));\r
783 \r
784   /* Create the service - settings will be changed in edit_service() */\r
785   service->handle = CreateService(services, service->name, service->name, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, service->image, 0, 0, 0, 0, 0);\r
786   if (! service->handle) {\r
787     print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED, error_string(GetLastError()));\r
788     CloseServiceHandle(services);\r
789     return 5;\r
790   }\r
791 \r
792   if (edit_service(service, false)) {\r
793     DeleteService(service->handle);\r
794     CloseServiceHandle(services);\r
795     return 6;\r
796   }\r
797 \r
798   print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);\r
799 \r
800   /* Cleanup */\r
801   CloseServiceHandle(services);\r
802 \r
803   return 0;\r
804 }\r
805 \r
806 /* Edit the service. */\r
807 int edit_service(nssm_service_t *service, bool editing) {\r
808   if (! service) return 1;\r
809 \r
810   /*\r
811     The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS\r
812     and SERVICE_INTERACTIVE_PROCESS.\r
813   */\r
814   service->type &= SERVICE_INTERACTIVE_PROCESS;\r
815   service->type |= SERVICE_WIN32_OWN_PROCESS;\r
816 \r
817   /* Startup type. */\r
818   unsigned long startup;\r
819   switch (service->startup) {\r
820     case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;\r
821     case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;\r
822     default: startup = SERVICE_AUTO_START;\r
823   }\r
824 \r
825   /* Display name. */\r
826   if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);\r
827 \r
828   /*\r
829     Username must be NULL if we aren't changing or an account name.\r
830     We must explicitly use LOCALSYSTEM to change it when we are editing.\r
831     Password must be NULL if we aren't changing, a password or "".\r
832     Empty passwords are valid but we won't allow them in the GUI.\r
833   */\r
834   TCHAR *username = 0;\r
835   TCHAR *password = 0;\r
836   if (service->usernamelen) {\r
837     username = service->username;\r
838     if (service->passwordlen) password = service->password;\r
839     else password = _T("");\r
840   }\r
841   else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;\r
842 \r
843   if (grant_logon_as_service(username)) {\r
844     print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);\r
845     return 5;\r
846   }\r
847 \r
848   if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {\r
849     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));\r
850     return 5;\r
851   }\r
852 \r
853   if (service->description[0] || editing) {\r
854     set_service_description(service->name, service->handle, service->description);\r
855   }\r
856 \r
857   SERVICE_DELAYED_AUTO_START_INFO delayed;\r
858   ZeroMemory(&delayed, sizeof(delayed));\r
859   if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;\r
860   else delayed.fDelayedAutostart = 0;\r
861   /* Delayed startup isn't supported until Vista. */\r
862   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {\r
863     unsigned long error = GetLastError();\r
864     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
865     if (error != ERROR_INVALID_LEVEL) {\r
866       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);\r
867     }\r
868   }\r
869 \r
870   /* Don't mess with parameters which aren't ours. */\r
871   if (! service->native) {\r
872     /* Now we need to put the parameters into the registry */\r
873     if (create_parameters(service, editing)) {\r
874       print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);\r
875       return 6;\r
876     }\r
877 \r
878     set_service_recovery(service);\r
879   }\r
880 \r
881   return 0;\r
882 }\r
883 \r
884 /* Control a service. */\r
885 int control_service(unsigned long control, int argc, TCHAR **argv) {\r
886   if (argc < 1) return usage(1);\r
887   TCHAR *service_name = argv[0];\r
888   TCHAR canonical_name[SERVICE_NAME_LENGTH];\r
889 \r
890   SC_HANDLE services = open_service_manager();\r
891   if (! services) {\r
892     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
893     return 2;\r
894   }\r
895 \r
896   SC_HANDLE service_handle = open_service(services, service_name, canonical_name, _countof(canonical_name));\r
897   if (! service_handle) {\r
898     CloseServiceHandle(services);\r
899     return 3;\r
900   }\r
901 \r
902   int ret;\r
903   unsigned long error;\r
904   SERVICE_STATUS service_status;\r
905   if (control == 0) {\r
906     ret = StartService(service_handle, (unsigned long) argc, (const TCHAR **) argv);\r
907     error = GetLastError();\r
908     CloseHandle(service_handle);\r
909     CloseServiceHandle(services);\r
910 \r
911     if (error == ERROR_IO_PENDING) {\r
912       /*\r
913         Older versions of Windows return immediately with ERROR_IO_PENDING\r
914         indicate that the operation is still in progress.  Newer versions\r
915         will return it if there really is a delay.  As far as we're\r
916         concerned the operation is a success.  We don't claim to offer a\r
917         fully-feature service control method; it's just a quick 'n' dirty\r
918         interface.\r
919 \r
920         In the future we may identify and handle this situation properly.\r
921       */\r
922       ret = 1;\r
923       error = ERROR_SUCCESS;\r
924     }\r
925 \r
926     if (ret) {\r
927       _tprintf(_T("%s: %s"), canonical_name, error_string(error));\r
928       return 0;\r
929     }\r
930     else {\r
931       _ftprintf(stderr, _T("%s: %s"), canonical_name, error_string(error));\r
932       return 1;\r
933     }\r
934   }\r
935   else if (control == SERVICE_CONTROL_INTERROGATE) {\r
936     /*\r
937       We could actually send an INTERROGATE control but that won't return\r
938       any information if the service is stopped and we don't care about\r
939       the extra details it might give us in any case.  So we'll fake it.\r
940     */\r
941     ret = QueryServiceStatus(service_handle, &service_status);\r
942     error = GetLastError();\r
943 \r
944     if (ret) {\r
945       switch (service_status.dwCurrentState) {\r
946         case SERVICE_STOPPED: _tprintf(_T("SERVICE_STOPPED\n")); break;\r
947         case SERVICE_START_PENDING: _tprintf(_T("SERVICE_START_PENDING\n")); break;\r
948         case SERVICE_STOP_PENDING: _tprintf(_T("SERVICE_STOP_PENDING\n")); break;\r
949         case SERVICE_RUNNING: _tprintf(_T("SERVICE_RUNNING\n")); break;\r
950         case SERVICE_CONTINUE_PENDING: _tprintf(_T("SERVICE_CONTINUE_PENDING\n")); break;\r
951         case SERVICE_PAUSE_PENDING: _tprintf(_T("SERVICE_PAUSE_PENDING\n")); break;\r
952         case SERVICE_PAUSED: _tprintf(_T("SERVICE_PAUSED\n")); break;\r
953         default: _tprintf(_T("?\n")); return 1;\r
954       }\r
955       return 0;\r
956     }\r
957     else {\r
958       _ftprintf(stderr, _T("%s: %s\n"), canonical_name, error_string(error));\r
959       return 1;\r
960     }\r
961   }\r
962   else {\r
963     ret = ControlService(service_handle, control, &service_status);\r
964     error = GetLastError();\r
965     CloseHandle(service_handle);\r
966     CloseServiceHandle(services);\r
967 \r
968     if (error == ERROR_IO_PENDING) {\r
969       ret = 1;\r
970       error = ERROR_SUCCESS;\r
971     }\r
972 \r
973     if (ret) {\r
974       _tprintf(_T("%s: %s"), canonical_name, error_string(error));\r
975       return 0;\r
976     }\r
977     else {\r
978       _ftprintf(stderr, _T("%s: %s"), canonical_name, error_string(error));\r
979       return 1;\r
980     }\r
981   }\r
982 }\r
983 \r
984 /* Remove the service */\r
985 int remove_service(nssm_service_t *service) {\r
986   if (! service) return 1;\r
987 \r
988   /* Open service manager */\r
989   SC_HANDLE services = open_service_manager();\r
990   if (! services) {\r
991     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
992     return 2;\r
993   }\r
994 \r
995   /* Try to open the service */\r
996   service->handle = open_service(services, service->name, service->name, _countof(service->name));\r
997   if (! service->handle) {\r
998     CloseServiceHandle(services);\r
999     return 3;\r
1000   }\r
1001 \r
1002   /* Get the canonical service name. We open it case insensitively. */\r
1003   unsigned long bufsize = _countof(service->displayname);\r
1004   GetServiceDisplayName(services, service->name, service->displayname, &bufsize);\r
1005   bufsize = _countof(service->name);\r
1006   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
1007 \r
1008   /* Try to delete the service */\r
1009   if (! DeleteService(service->handle)) {\r
1010     print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);\r
1011     CloseServiceHandle(services);\r
1012     return 4;\r
1013   }\r
1014 \r
1015   /* Cleanup */\r
1016   CloseServiceHandle(services);\r
1017 \r
1018   print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);\r
1019   return 0;\r
1020 }\r
1021 \r
1022 /* Service initialisation */\r
1023 void WINAPI service_main(unsigned long argc, TCHAR **argv) {\r
1024   nssm_service_t *service = alloc_nssm_service();\r
1025   if (! service) return;\r
1026 \r
1027   if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {\r
1028     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);\r
1029     return;\r
1030   }\r
1031 \r
1032   /* We can use a condition variable in a critical section on Vista or later. */\r
1033   if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;\r
1034   else use_critical_section = false;\r
1035 \r
1036   /* Initialise status */\r
1037   ZeroMemory(&service->status, sizeof(service->status));\r
1038   service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
1039   service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;\r
1040   service->status.dwWin32ExitCode = NO_ERROR;\r
1041   service->status.dwServiceSpecificExitCode = 0;\r
1042   service->status.dwCheckPoint = 0;\r
1043   service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
1044 \r
1045   /* Signal we AREN'T running the server */\r
1046   service->process_handle = 0;\r
1047   service->pid = 0;\r
1048 \r
1049   /* Register control handler */\r
1050   service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);\r
1051   if (! service->status_handle) {\r
1052     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);\r
1053     return;\r
1054   }\r
1055 \r
1056   log_service_control(service->name, 0, true);\r
1057 \r
1058   service->status.dwCurrentState = SERVICE_START_PENDING;\r
1059   service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;\r
1060   SetServiceStatus(service->status_handle, &service->status);\r
1061 \r
1062   if (is_admin) {\r
1063     /* Try to create the exit action parameters; we don't care if it fails */\r
1064     create_exit_action(service->name, exit_action_strings[0], false);\r
1065 \r
1066     SC_HANDLE services = open_service_manager();\r
1067     if (services) {\r
1068       service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
1069       set_service_recovery(service);\r
1070       CloseServiceHandle(services);\r
1071     }\r
1072   }\r
1073 \r
1074   /* Used for signalling a resume if the service pauses when throttled. */\r
1075   if (use_critical_section) {\r
1076     InitializeCriticalSection(&service->throttle_section);\r
1077     service->throttle_section_initialised = true;\r
1078   }\r
1079   else {\r
1080     service->throttle_timer = CreateWaitableTimer(0, 1, 0);\r
1081     if (! service->throttle_timer) {\r
1082       log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);\r
1083     }\r
1084   }\r
1085 \r
1086   monitor_service(service);\r
1087 }\r
1088 \r
1089 /* Make sure service recovery actions are taken where necessary */\r
1090 void set_service_recovery(nssm_service_t *service) {\r
1091   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
1092   ZeroMemory(&flag, sizeof(flag));\r
1093   flag.fFailureActionsOnNonCrashFailures = true;\r
1094 \r
1095   /* This functionality was added in Vista so the call may fail */\r
1096   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {\r
1097     unsigned long error = GetLastError();\r
1098     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
1099     if (error != ERROR_INVALID_LEVEL) {\r
1100       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);\r
1101     }\r
1102   }\r
1103 }\r
1104 \r
1105 int monitor_service(nssm_service_t *service) {\r
1106   /* Set service status to started */\r
1107   int ret = start_service(service);\r
1108   if (ret) {\r
1109     TCHAR code[16];\r
1110     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);\r
1111     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);\r
1112     return ret;\r
1113   }\r
1114   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);\r
1115 \r
1116   /* Monitor service */\r
1117   if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
1118     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);\r
1119   }\r
1120 \r
1121   return 0;\r
1122 }\r
1123 \r
1124 TCHAR *service_control_text(unsigned long control) {\r
1125   switch (control) {\r
1126     /* HACK: there is no SERVICE_CONTROL_START constant */\r
1127     case 0: return _T("START");\r
1128     case SERVICE_CONTROL_STOP: return _T("STOP");\r
1129     case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");\r
1130     case SERVICE_CONTROL_PAUSE: return _T("PAUSE");\r
1131     case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");\r
1132     case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");\r
1133     default: return 0;\r
1134   }\r
1135 }\r
1136 \r
1137 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {\r
1138   TCHAR *text = service_control_text(control);\r
1139   unsigned long event;\r
1140 \r
1141   if (! text) {\r
1142     /* "0x" + 8 x hex + NULL */\r
1143     text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));\r
1144     if (! text) {\r
1145       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
1146       return;\r
1147     }\r
1148     if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {\r
1149       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
1150       HeapFree(GetProcessHeap(), 0, text);\r
1151       return;\r
1152     }\r
1153 \r
1154     event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;\r
1155   }\r
1156   else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;\r
1157   else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;\r
1158 \r
1159   log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);\r
1160 \r
1161   if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {\r
1162     HeapFree(GetProcessHeap(), 0, text);\r
1163   }\r
1164 }\r
1165 \r
1166 /* Service control handler */\r
1167 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
1168   nssm_service_t *service = (nssm_service_t *) context;\r
1169 \r
1170   switch (control) {\r
1171     case SERVICE_CONTROL_INTERROGATE:\r
1172       /* We always keep the service status up-to-date so this is a no-op. */\r
1173       return NO_ERROR;\r
1174 \r
1175     case SERVICE_CONTROL_SHUTDOWN:\r
1176     case SERVICE_CONTROL_STOP:\r
1177       log_service_control(service->name, control, true);\r
1178       /*\r
1179         We MUST acknowledge the stop request promptly but we're committed to\r
1180         waiting for the application to exit.  Spawn a new thread to wait\r
1181         while we acknowledge the request.\r
1182       */\r
1183       if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {\r
1184         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
1185 \r
1186         /*\r
1187           We couldn't create a thread to tidy up so we'll have to force the tidyup\r
1188           to complete in time in this thread.\r
1189         */\r
1190         service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
1191         service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
1192         service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
1193 \r
1194         stop_service(service, 0, true, true);\r
1195       }\r
1196       return NO_ERROR;\r
1197 \r
1198     case SERVICE_CONTROL_CONTINUE:\r
1199       log_service_control(service->name, control, true);\r
1200       service->throttle = 0;\r
1201       if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);\r
1202       else {\r
1203         if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
1204         ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1205         SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1206       }\r
1207       service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
1208       service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;\r
1209       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);\r
1210       SetServiceStatus(service->status_handle, &service->status);\r
1211       return NO_ERROR;\r
1212 \r
1213     case SERVICE_CONTROL_PAUSE:\r
1214       /*\r
1215         We don't accept pause messages but it isn't possible to register\r
1216         only for continue messages so we have to handle this case.\r
1217       */\r
1218       log_service_control(service->name, control, false);\r
1219       return ERROR_CALL_NOT_IMPLEMENTED;\r
1220   }\r
1221 \r
1222   /* Unknown control */\r
1223   log_service_control(service->name, control, false);\r
1224   return ERROR_CALL_NOT_IMPLEMENTED;\r
1225 }\r
1226 \r
1227 /* Start the service */\r
1228 int start_service(nssm_service_t *service) {\r
1229   service->stopping = false;\r
1230   service->allow_restart = true;\r
1231 \r
1232   if (service->process_handle) return 0;\r
1233 \r
1234   /* Allocate a STARTUPINFO structure for a new process */\r
1235   STARTUPINFO si;\r
1236   ZeroMemory(&si, sizeof(si));\r
1237   si.cb = sizeof(si);\r
1238 \r
1239   /* Allocate a PROCESSINFO structure for the process */\r
1240   PROCESS_INFORMATION pi;\r
1241   ZeroMemory(&pi, sizeof(pi));\r
1242 \r
1243   /* Get startup parameters */\r
1244   int ret = get_parameters(service, &si);\r
1245   if (ret) {\r
1246     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);\r
1247     return stop_service(service, 2, true, true);\r
1248   }\r
1249 \r
1250   /* Launch executable with arguments */\r
1251   TCHAR cmd[CMD_LENGTH];\r
1252   if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {\r
1253     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);\r
1254     close_output_handles(&si);\r
1255     return stop_service(service, 2, true, true);\r
1256   }\r
1257 \r
1258   throttle_restart(service);\r
1259 \r
1260   bool inherit_handles = false;\r
1261   if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
1262   unsigned long flags = 0;\r
1263 #ifdef UNICODE\r
1264   flags |= CREATE_UNICODE_ENVIRONMENT;\r
1265 #endif\r
1266   if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {\r
1267     unsigned long exitcode = 3;\r
1268     unsigned long error = GetLastError();\r
1269     if (error == ERROR_INVALID_PARAMETER && service->env) {\r
1270       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);\r
1271       if (test_environment(service->env)) exitcode = 4;\r
1272     }\r
1273     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
1274     close_output_handles(&si);\r
1275     return stop_service(service, exitcode, true, true);\r
1276   }\r
1277   service->process_handle = pi.hProcess;\r
1278   service->pid = pi.dwProcessId;\r
1279 \r
1280   if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
1281 \r
1282   close_output_handles(&si);\r
1283 \r
1284   /*\r
1285     Wait for a clean startup before changing the service status to RUNNING\r
1286     but be mindful of the fact that we are blocking the service control manager\r
1287     so abandon the wait before too much time has elapsed.\r
1288   */\r
1289   unsigned long delay = service->throttle_delay;\r
1290   if (delay > NSSM_SERVICE_STATUS_DEADLINE) {\r
1291     TCHAR delay_milliseconds[16];\r
1292     _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);\r
1293     TCHAR deadline_milliseconds[16];\r
1294     _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);\r
1295     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);\r
1296     delay = NSSM_SERVICE_STATUS_DEADLINE;\r
1297   }\r
1298   unsigned long deadline = WaitForSingleObject(service->process_handle, delay);\r
1299 \r
1300   /* Signal successful start */\r
1301   service->status.dwCurrentState = SERVICE_RUNNING;\r
1302   SetServiceStatus(service->status_handle, &service->status);\r
1303 \r
1304   /* Continue waiting for a clean startup. */\r
1305   if (deadline == WAIT_TIMEOUT) {\r
1306     if (service->throttle_delay > delay) {\r
1307       if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;\r
1308     }\r
1309     else service->throttle = 0;\r
1310   }\r
1311 \r
1312   return 0;\r
1313 }\r
1314 \r
1315 /* Stop the service */\r
1316 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {\r
1317   service->allow_restart = false;\r
1318   if (service->wait_handle) {\r
1319     UnregisterWait(service->wait_handle);\r
1320     service->wait_handle = 0;\r
1321   }\r
1322 \r
1323   if (default_action && ! exitcode && ! graceful) {\r
1324     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
1325     graceful = true;\r
1326   }\r
1327 \r
1328   /* Signal we are stopping */\r
1329   if (graceful) {\r
1330     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1331     service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
1332     SetServiceStatus(service->status_handle, &service->status);\r
1333   }\r
1334 \r
1335   /* Nothing to do if service isn't running */\r
1336   if (service->pid) {\r
1337     /* Shut down service */\r
1338     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);\r
1339     kill_process(service, service->process_handle, service->pid, 0);\r
1340   }\r
1341   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);\r
1342 \r
1343   end_service((void *) service, true);\r
1344 \r
1345   /* Signal we stopped */\r
1346   if (graceful) {\r
1347     service->status.dwCurrentState = SERVICE_STOPPED;\r
1348     if (exitcode) {\r
1349       service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
1350       service->status.dwServiceSpecificExitCode = exitcode;\r
1351     }\r
1352     else {\r
1353       service->status.dwWin32ExitCode = NO_ERROR;\r
1354       service->status.dwServiceSpecificExitCode = 0;\r
1355     }\r
1356     SetServiceStatus(service->status_handle, &service->status);\r
1357   }\r
1358 \r
1359   return exitcode;\r
1360 }\r
1361 \r
1362 /* Callback function triggered when the server exits */\r
1363 void CALLBACK end_service(void *arg, unsigned char why) {\r
1364   nssm_service_t *service = (nssm_service_t *) arg;\r
1365 \r
1366   if (service->stopping) return;\r
1367 \r
1368   service->stopping = true;\r
1369 \r
1370   /* Check exit code */\r
1371   unsigned long exitcode = 0;\r
1372   TCHAR code[16];\r
1373   if (service->process_handle) {\r
1374     GetExitCodeProcess(service->process_handle, &exitcode);\r
1375     if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);\r
1376     CloseHandle(service->process_handle);\r
1377   }\r
1378   else GetSystemTimeAsFileTime(&service->exit_time);\r
1379 \r
1380   service->process_handle = 0;\r
1381 \r
1382   /*\r
1383     Log that the service ended BEFORE logging about killing the process\r
1384     tree.  See below for the possible values of the why argument.\r
1385   */\r
1386   if (! why) {\r
1387     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);\r
1388     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);\r
1389   }\r
1390 \r
1391   /* Clean up. */\r
1392   if (exitcode == STILL_ACTIVE) exitcode = 0;\r
1393   if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);\r
1394   service->pid = 0;\r
1395 \r
1396   /*\r
1397     The why argument is true if our wait timed out or false otherwise.\r
1398     Our wait is infinite so why will never be true when called by the system.\r
1399     If it is indeed true, assume we were called from stop_service() because\r
1400     this is a controlled shutdown, and don't take any restart action.\r
1401   */\r
1402   if (why) return;\r
1403   if (! service->allow_restart) return;\r
1404 \r
1405   /* What action should we take? */\r
1406   int action = NSSM_EXIT_RESTART;\r
1407   TCHAR action_string[ACTION_LEN];\r
1408   bool default_action;\r
1409   if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {\r
1410     for (int i = 0; exit_action_strings[i]; i++) {\r
1411       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
1412         action = i;\r
1413         break;\r
1414       }\r
1415     }\r
1416   }\r
1417 \r
1418   switch (action) {\r
1419     /* Try to restart the service or return failure code to service manager */\r
1420     case NSSM_EXIT_RESTART:\r
1421       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);\r
1422       while (monitor_service(service)) {\r
1423         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);\r
1424         Sleep(30000);\r
1425       }\r
1426     break;\r
1427 \r
1428     /* Do nothing, just like srvany would */\r
1429     case NSSM_EXIT_IGNORE:\r
1430       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
1431       Sleep(INFINITE);\r
1432     break;\r
1433 \r
1434     /* Tell the service manager we are finished */\r
1435     case NSSM_EXIT_REALLY:\r
1436       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);\r
1437       stop_service(service, exitcode, true, default_action);\r
1438     break;\r
1439 \r
1440     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
1441     case NSSM_EXIT_UNCLEAN:\r
1442       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
1443       stop_service(service, exitcode, false, default_action);\r
1444       free_imports();\r
1445       exit(exitcode);\r
1446     break;\r
1447   }\r
1448 }\r
1449 \r
1450 void throttle_restart(nssm_service_t *service) {\r
1451   /* This can't be a restart if the service is already running. */\r
1452   if (! service->throttle++) return;\r
1453 \r
1454   int ms = throttle_milliseconds(service->throttle);\r
1455 \r
1456   if (service->throttle > 7) service->throttle = 8;\r
1457 \r
1458   TCHAR threshold[8], milliseconds[8];\r
1459   _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);\r
1460   _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
1461   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);\r
1462 \r
1463   if (use_critical_section) EnterCriticalSection(&service->throttle_section);\r
1464   else if (service->throttle_timer) {\r
1465     ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1466     service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
1467     SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1468   }\r
1469 \r
1470   service->status.dwCurrentState = SERVICE_PAUSED;\r
1471   SetServiceStatus(service->status_handle, &service->status);\r
1472 \r
1473   if (use_critical_section) {\r
1474     imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);\r
1475     LeaveCriticalSection(&service->throttle_section);\r
1476   }\r
1477   else {\r
1478     if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);\r
1479     else Sleep(ms);\r
1480   }\r
1481 }\r
1482 \r
1483 /*\r
1484   When responding to a stop (or any other) request we need to set dwWaitHint to\r
1485   the number of milliseconds we expect the operation to take, and optionally\r
1486   increase dwCheckPoint.  If dwWaitHint milliseconds elapses without the\r
1487   operation completing or dwCheckPoint increasing, the system will consider the\r
1488   service to be hung.\r
1489 \r
1490   However the system will consider the service to be hung after 30000\r
1491   milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not\r
1492   changed.  Therefore if we want to wait longer than that we must periodically\r
1493   increase dwCheckPoint.\r
1494 \r
1495   Furthermore, it will consider the service to be hung after 60000 milliseconds\r
1496   regardless of the value of dwCheckPoint unless dwWaitHint is increased every\r
1497   time dwCheckPoint is also increased.\r
1498 \r
1499   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
1500   NSSM_SERVICE_STATUS_DEADLINE milliseconds.  If the process is still running\r
1501   and we haven't finished waiting we increment dwCheckPoint and add whichever is\r
1502   smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to\r
1503   dwWaitHint.\r
1504 \r
1505   Only doing both these things will prevent the system from killing the service.\r
1506 \r
1507   Returns: 1 if the wait timed out.\r
1508            0 if the wait completed.\r
1509           -1 on error.\r
1510 */\r
1511 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {\r
1512   unsigned long interval;\r
1513   unsigned long waithint;\r
1514   unsigned long ret;\r
1515   unsigned long waited;\r
1516   TCHAR interval_milliseconds[16];\r
1517   TCHAR timeout_milliseconds[16];\r
1518   TCHAR waited_milliseconds[16];\r
1519   TCHAR *function = function_name;\r
1520 \r
1521   /* Add brackets to function name. */\r
1522   size_t funclen = _tcslen(function_name) + 3;\r
1523   TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));\r
1524   if (func) {\r
1525     if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;\r
1526   }\r
1527 \r
1528   _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);\r
1529 \r
1530   waithint = service->status.dwWaitHint;\r
1531   waited = 0;\r
1532   while (waited < timeout) {\r
1533     interval = timeout - waited;\r
1534     if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
1535 \r
1536     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1537     service->status.dwWaitHint += interval;\r
1538     service->status.dwCheckPoint++;\r
1539     SetServiceStatus(service->status_handle, &service->status);\r
1540 \r
1541     if (waited) {\r
1542       _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);\r
1543       _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);\r
1544       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
1545     }\r
1546 \r
1547     switch (WaitForSingleObject(service->process_handle, interval)) {\r
1548       case WAIT_OBJECT_0:\r
1549         ret = 0;\r
1550         goto awaited;\r
1551 \r
1552       case WAIT_TIMEOUT:\r
1553         ret = 1;\r
1554       break;\r
1555 \r
1556       default:\r
1557         ret = -1;\r
1558         goto awaited;\r
1559     }\r
1560 \r
1561     waited += interval;\r
1562   }\r
1563 \r
1564 awaited:\r
1565   if (func) HeapFree(GetProcessHeap(), 0, func);\r
1566 \r
1567   return ret;\r
1568 }\r