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