Added open_service().
[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 (ret) {\r
911       _tprintf(_T("%s: %s"), canonical_name, error_string(error));\r
912       return 0;\r
913     }\r
914     else {\r
915       _ftprintf(stderr, _T("%s: %s"), canonical_name, error_string(error));\r
916       return 1;\r
917     }\r
918   }\r
919   else if (control == SERVICE_CONTROL_INTERROGATE) {\r
920     /*\r
921       We could actually send an INTERROGATE control but that won't return\r
922       any information if the service is stopped and we don't care about\r
923       the extra details it might give us in any case.  So we'll fake it.\r
924     */\r
925     ret = QueryServiceStatus(service_handle, &service_status);\r
926     error = GetLastError();\r
927 \r
928     if (ret) {\r
929       switch (service_status.dwCurrentState) {\r
930         case SERVICE_STOPPED: _tprintf(_T("SERVICE_STOPPED\n")); break;\r
931         case SERVICE_START_PENDING: _tprintf(_T("SERVICE_START_PENDING\n")); break;\r
932         case SERVICE_STOP_PENDING: _tprintf(_T("SERVICE_STOP_PENDING\n")); break;\r
933         case SERVICE_RUNNING: _tprintf(_T("SERVICE_RUNNING\n")); break;\r
934         case SERVICE_CONTINUE_PENDING: _tprintf(_T("SERVICE_CONTINUE_PENDING\n")); break;\r
935         case SERVICE_PAUSE_PENDING: _tprintf(_T("SERVICE_PAUSE_PENDING\n")); break;\r
936         case SERVICE_PAUSED: _tprintf(_T("SERVICE_PAUSED\n")); break;\r
937         default: _tprintf(_T("?\n")); return 1;\r
938       }\r
939       return 0;\r
940     }\r
941     else {\r
942       _ftprintf(stderr, _T("%s: %s\n"), canonical_name, error_string(error));\r
943       return 1;\r
944     }\r
945   }\r
946   else {\r
947     ret = ControlService(service_handle, control, &service_status);\r
948     error = GetLastError();\r
949     CloseHandle(service_handle);\r
950     CloseServiceHandle(services);\r
951 \r
952     if (ret) {\r
953       _tprintf(_T("%s: %s"), canonical_name, error_string(error));\r
954       return 0;\r
955     }\r
956     else {\r
957       _ftprintf(stderr, _T("%s: %s"), canonical_name, error_string(error));\r
958       return 1;\r
959     }\r
960   }\r
961 }\r
962 \r
963 /* Remove the service */\r
964 int remove_service(nssm_service_t *service) {\r
965   if (! service) return 1;\r
966 \r
967   /* Open service manager */\r
968   SC_HANDLE services = open_service_manager();\r
969   if (! services) {\r
970     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
971     return 2;\r
972   }\r
973 \r
974   /* Try to open the service */\r
975   service->handle = open_service(services, service->name, service->name, _countof(service->name));\r
976   if (! service->handle) {\r
977     CloseServiceHandle(services);\r
978     return 3;\r
979   }\r
980 \r
981   /* Get the canonical service name. We open it case insensitively. */\r
982   unsigned long bufsize = _countof(service->displayname);\r
983   GetServiceDisplayName(services, service->name, service->displayname, &bufsize);\r
984   bufsize = _countof(service->name);\r
985   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
986 \r
987   /* Try to delete the service */\r
988   if (! DeleteService(service->handle)) {\r
989     print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);\r
990     CloseServiceHandle(services);\r
991     return 4;\r
992   }\r
993 \r
994   /* Cleanup */\r
995   CloseServiceHandle(services);\r
996 \r
997   print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);\r
998   return 0;\r
999 }\r
1000 \r
1001 /* Service initialisation */\r
1002 void WINAPI service_main(unsigned long argc, TCHAR **argv) {\r
1003   nssm_service_t *service = alloc_nssm_service();\r
1004   if (! service) return;\r
1005 \r
1006   if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {\r
1007     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);\r
1008     return;\r
1009   }\r
1010 \r
1011   /* We can use a condition variable in a critical section on Vista or later. */\r
1012   if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;\r
1013   else use_critical_section = false;\r
1014 \r
1015   /* Initialise status */\r
1016   ZeroMemory(&service->status, sizeof(service->status));\r
1017   service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
1018   service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;\r
1019   service->status.dwWin32ExitCode = NO_ERROR;\r
1020   service->status.dwServiceSpecificExitCode = 0;\r
1021   service->status.dwCheckPoint = 0;\r
1022   service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
1023 \r
1024   /* Signal we AREN'T running the server */\r
1025   service->process_handle = 0;\r
1026   service->pid = 0;\r
1027 \r
1028   /* Register control handler */\r
1029   service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);\r
1030   if (! service->status_handle) {\r
1031     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);\r
1032     return;\r
1033   }\r
1034 \r
1035   log_service_control(service->name, 0, true);\r
1036 \r
1037   service->status.dwCurrentState = SERVICE_START_PENDING;\r
1038   service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;\r
1039   SetServiceStatus(service->status_handle, &service->status);\r
1040 \r
1041   if (is_admin) {\r
1042     /* Try to create the exit action parameters; we don't care if it fails */\r
1043     create_exit_action(service->name, exit_action_strings[0], false);\r
1044 \r
1045     SC_HANDLE services = open_service_manager();\r
1046     if (services) {\r
1047       service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
1048       set_service_recovery(service);\r
1049       CloseServiceHandle(services);\r
1050     }\r
1051   }\r
1052 \r
1053   /* Used for signalling a resume if the service pauses when throttled. */\r
1054   if (use_critical_section) {\r
1055     InitializeCriticalSection(&service->throttle_section);\r
1056     service->throttle_section_initialised = true;\r
1057   }\r
1058   else {\r
1059     service->throttle_timer = CreateWaitableTimer(0, 1, 0);\r
1060     if (! service->throttle_timer) {\r
1061       log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);\r
1062     }\r
1063   }\r
1064 \r
1065   monitor_service(service);\r
1066 }\r
1067 \r
1068 /* Make sure service recovery actions are taken where necessary */\r
1069 void set_service_recovery(nssm_service_t *service) {\r
1070   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
1071   ZeroMemory(&flag, sizeof(flag));\r
1072   flag.fFailureActionsOnNonCrashFailures = true;\r
1073 \r
1074   /* This functionality was added in Vista so the call may fail */\r
1075   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {\r
1076     unsigned long error = GetLastError();\r
1077     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
1078     if (error != ERROR_INVALID_LEVEL) {\r
1079       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);\r
1080     }\r
1081   }\r
1082 }\r
1083 \r
1084 int monitor_service(nssm_service_t *service) {\r
1085   /* Set service status to started */\r
1086   int ret = start_service(service);\r
1087   if (ret) {\r
1088     TCHAR code[16];\r
1089     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);\r
1090     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);\r
1091     return ret;\r
1092   }\r
1093   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);\r
1094 \r
1095   /* Monitor service */\r
1096   if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
1097     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);\r
1098   }\r
1099 \r
1100   return 0;\r
1101 }\r
1102 \r
1103 TCHAR *service_control_text(unsigned long control) {\r
1104   switch (control) {\r
1105     /* HACK: there is no SERVICE_CONTROL_START constant */\r
1106     case 0: return _T("START");\r
1107     case SERVICE_CONTROL_STOP: return _T("STOP");\r
1108     case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");\r
1109     case SERVICE_CONTROL_PAUSE: return _T("PAUSE");\r
1110     case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");\r
1111     case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");\r
1112     default: return 0;\r
1113   }\r
1114 }\r
1115 \r
1116 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {\r
1117   TCHAR *text = service_control_text(control);\r
1118   unsigned long event;\r
1119 \r
1120   if (! text) {\r
1121     /* "0x" + 8 x hex + NULL */\r
1122     text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));\r
1123     if (! text) {\r
1124       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
1125       return;\r
1126     }\r
1127     if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {\r
1128       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
1129       HeapFree(GetProcessHeap(), 0, text);\r
1130       return;\r
1131     }\r
1132 \r
1133     event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;\r
1134   }\r
1135   else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;\r
1136   else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;\r
1137 \r
1138   log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);\r
1139 \r
1140   if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {\r
1141     HeapFree(GetProcessHeap(), 0, text);\r
1142   }\r
1143 }\r
1144 \r
1145 /* Service control handler */\r
1146 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
1147   nssm_service_t *service = (nssm_service_t *) context;\r
1148 \r
1149   switch (control) {\r
1150     case SERVICE_CONTROL_INTERROGATE:\r
1151       /* We always keep the service status up-to-date so this is a no-op. */\r
1152       return NO_ERROR;\r
1153 \r
1154     case SERVICE_CONTROL_SHUTDOWN:\r
1155     case SERVICE_CONTROL_STOP:\r
1156       log_service_control(service->name, control, true);\r
1157       /*\r
1158         We MUST acknowledge the stop request promptly but we're committed to\r
1159         waiting for the application to exit.  Spawn a new thread to wait\r
1160         while we acknowledge the request.\r
1161       */\r
1162       if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {\r
1163         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
1164 \r
1165         /*\r
1166           We couldn't create a thread to tidy up so we'll have to force the tidyup\r
1167           to complete in time in this thread.\r
1168         */\r
1169         service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
1170         service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
1171         service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
1172 \r
1173         stop_service(service, 0, true, true);\r
1174       }\r
1175       return NO_ERROR;\r
1176 \r
1177     case SERVICE_CONTROL_CONTINUE:\r
1178       log_service_control(service->name, control, true);\r
1179       service->throttle = 0;\r
1180       if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);\r
1181       else {\r
1182         if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
1183         ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1184         SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1185       }\r
1186       service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
1187       service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;\r
1188       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);\r
1189       SetServiceStatus(service->status_handle, &service->status);\r
1190       return NO_ERROR;\r
1191 \r
1192     case SERVICE_CONTROL_PAUSE:\r
1193       /*\r
1194         We don't accept pause messages but it isn't possible to register\r
1195         only for continue messages so we have to handle this case.\r
1196       */\r
1197       log_service_control(service->name, control, false);\r
1198       return ERROR_CALL_NOT_IMPLEMENTED;\r
1199   }\r
1200 \r
1201   /* Unknown control */\r
1202   log_service_control(service->name, control, false);\r
1203   return ERROR_CALL_NOT_IMPLEMENTED;\r
1204 }\r
1205 \r
1206 /* Start the service */\r
1207 int start_service(nssm_service_t *service) {\r
1208   service->stopping = false;\r
1209   service->allow_restart = true;\r
1210 \r
1211   if (service->process_handle) return 0;\r
1212 \r
1213   /* Allocate a STARTUPINFO structure for a new process */\r
1214   STARTUPINFO si;\r
1215   ZeroMemory(&si, sizeof(si));\r
1216   si.cb = sizeof(si);\r
1217 \r
1218   /* Allocate a PROCESSINFO structure for the process */\r
1219   PROCESS_INFORMATION pi;\r
1220   ZeroMemory(&pi, sizeof(pi));\r
1221 \r
1222   /* Get startup parameters */\r
1223   int ret = get_parameters(service, &si);\r
1224   if (ret) {\r
1225     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);\r
1226     return stop_service(service, 2, true, true);\r
1227   }\r
1228 \r
1229   /* Launch executable with arguments */\r
1230   TCHAR cmd[CMD_LENGTH];\r
1231   if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {\r
1232     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);\r
1233     close_output_handles(&si);\r
1234     return stop_service(service, 2, true, true);\r
1235   }\r
1236 \r
1237   throttle_restart(service);\r
1238 \r
1239   bool inherit_handles = false;\r
1240   if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
1241   unsigned long flags = 0;\r
1242 #ifdef UNICODE\r
1243   flags |= CREATE_UNICODE_ENVIRONMENT;\r
1244 #endif\r
1245   if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {\r
1246     unsigned long exitcode = 3;\r
1247     unsigned long error = GetLastError();\r
1248     if (error == ERROR_INVALID_PARAMETER && service->env) {\r
1249       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);\r
1250       if (test_environment(service->env)) exitcode = 4;\r
1251     }\r
1252     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
1253     close_output_handles(&si);\r
1254     return stop_service(service, exitcode, true, true);\r
1255   }\r
1256   service->process_handle = pi.hProcess;\r
1257   service->pid = pi.dwProcessId;\r
1258 \r
1259   if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
1260 \r
1261   close_output_handles(&si);\r
1262 \r
1263   /*\r
1264     Wait for a clean startup before changing the service status to RUNNING\r
1265     but be mindful of the fact that we are blocking the service control manager\r
1266     so abandon the wait before too much time has elapsed.\r
1267   */\r
1268   unsigned long delay = service->throttle_delay;\r
1269   if (delay > NSSM_SERVICE_STATUS_DEADLINE) {\r
1270     TCHAR delay_milliseconds[16];\r
1271     _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);\r
1272     TCHAR deadline_milliseconds[16];\r
1273     _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);\r
1274     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);\r
1275     delay = NSSM_SERVICE_STATUS_DEADLINE;\r
1276   }\r
1277   unsigned long deadline = WaitForSingleObject(service->process_handle, delay);\r
1278 \r
1279   /* Signal successful start */\r
1280   service->status.dwCurrentState = SERVICE_RUNNING;\r
1281   SetServiceStatus(service->status_handle, &service->status);\r
1282 \r
1283   /* Continue waiting for a clean startup. */\r
1284   if (deadline == WAIT_TIMEOUT) {\r
1285     if (service->throttle_delay > delay) {\r
1286       if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;\r
1287     }\r
1288     else service->throttle = 0;\r
1289   }\r
1290 \r
1291   return 0;\r
1292 }\r
1293 \r
1294 /* Stop the service */\r
1295 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {\r
1296   service->allow_restart = false;\r
1297   if (service->wait_handle) {\r
1298     UnregisterWait(service->wait_handle);\r
1299     service->wait_handle = 0;\r
1300   }\r
1301 \r
1302   if (default_action && ! exitcode && ! graceful) {\r
1303     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
1304     graceful = true;\r
1305   }\r
1306 \r
1307   /* Signal we are stopping */\r
1308   if (graceful) {\r
1309     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1310     service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
1311     SetServiceStatus(service->status_handle, &service->status);\r
1312   }\r
1313 \r
1314   /* Nothing to do if service isn't running */\r
1315   if (service->pid) {\r
1316     /* Shut down service */\r
1317     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);\r
1318     kill_process(service, service->process_handle, service->pid, 0);\r
1319   }\r
1320   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);\r
1321 \r
1322   end_service((void *) service, true);\r
1323 \r
1324   /* Signal we stopped */\r
1325   if (graceful) {\r
1326     service->status.dwCurrentState = SERVICE_STOPPED;\r
1327     if (exitcode) {\r
1328       service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
1329       service->status.dwServiceSpecificExitCode = exitcode;\r
1330     }\r
1331     else {\r
1332       service->status.dwWin32ExitCode = NO_ERROR;\r
1333       service->status.dwServiceSpecificExitCode = 0;\r
1334     }\r
1335     SetServiceStatus(service->status_handle, &service->status);\r
1336   }\r
1337 \r
1338   return exitcode;\r
1339 }\r
1340 \r
1341 /* Callback function triggered when the server exits */\r
1342 void CALLBACK end_service(void *arg, unsigned char why) {\r
1343   nssm_service_t *service = (nssm_service_t *) arg;\r
1344 \r
1345   if (service->stopping) return;\r
1346 \r
1347   service->stopping = true;\r
1348 \r
1349   /* Check exit code */\r
1350   unsigned long exitcode = 0;\r
1351   TCHAR code[16];\r
1352   if (service->process_handle) {\r
1353     GetExitCodeProcess(service->process_handle, &exitcode);\r
1354     if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);\r
1355     CloseHandle(service->process_handle);\r
1356   }\r
1357   else GetSystemTimeAsFileTime(&service->exit_time);\r
1358 \r
1359   service->process_handle = 0;\r
1360 \r
1361   /*\r
1362     Log that the service ended BEFORE logging about killing the process\r
1363     tree.  See below for the possible values of the why argument.\r
1364   */\r
1365   if (! why) {\r
1366     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);\r
1367     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);\r
1368   }\r
1369 \r
1370   /* Clean up. */\r
1371   if (exitcode == STILL_ACTIVE) exitcode = 0;\r
1372   if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);\r
1373   service->pid = 0;\r
1374 \r
1375   /*\r
1376     The why argument is true if our wait timed out or false otherwise.\r
1377     Our wait is infinite so why will never be true when called by the system.\r
1378     If it is indeed true, assume we were called from stop_service() because\r
1379     this is a controlled shutdown, and don't take any restart action.\r
1380   */\r
1381   if (why) return;\r
1382   if (! service->allow_restart) return;\r
1383 \r
1384   /* What action should we take? */\r
1385   int action = NSSM_EXIT_RESTART;\r
1386   TCHAR action_string[ACTION_LEN];\r
1387   bool default_action;\r
1388   if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {\r
1389     for (int i = 0; exit_action_strings[i]; i++) {\r
1390       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
1391         action = i;\r
1392         break;\r
1393       }\r
1394     }\r
1395   }\r
1396 \r
1397   switch (action) {\r
1398     /* Try to restart the service or return failure code to service manager */\r
1399     case NSSM_EXIT_RESTART:\r
1400       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);\r
1401       while (monitor_service(service)) {\r
1402         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);\r
1403         Sleep(30000);\r
1404       }\r
1405     break;\r
1406 \r
1407     /* Do nothing, just like srvany would */\r
1408     case NSSM_EXIT_IGNORE:\r
1409       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
1410       Sleep(INFINITE);\r
1411     break;\r
1412 \r
1413     /* Tell the service manager we are finished */\r
1414     case NSSM_EXIT_REALLY:\r
1415       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);\r
1416       stop_service(service, exitcode, true, default_action);\r
1417     break;\r
1418 \r
1419     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
1420     case NSSM_EXIT_UNCLEAN:\r
1421       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
1422       stop_service(service, exitcode, false, default_action);\r
1423       free_imports();\r
1424       exit(exitcode);\r
1425     break;\r
1426   }\r
1427 }\r
1428 \r
1429 void throttle_restart(nssm_service_t *service) {\r
1430   /* This can't be a restart if the service is already running. */\r
1431   if (! service->throttle++) return;\r
1432 \r
1433   int ms = throttle_milliseconds(service->throttle);\r
1434 \r
1435   if (service->throttle > 7) service->throttle = 8;\r
1436 \r
1437   TCHAR threshold[8], milliseconds[8];\r
1438   _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);\r
1439   _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
1440   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);\r
1441 \r
1442   if (use_critical_section) EnterCriticalSection(&service->throttle_section);\r
1443   else if (service->throttle_timer) {\r
1444     ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1445     service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
1446     SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1447   }\r
1448 \r
1449   service->status.dwCurrentState = SERVICE_PAUSED;\r
1450   SetServiceStatus(service->status_handle, &service->status);\r
1451 \r
1452   if (use_critical_section) {\r
1453     imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);\r
1454     LeaveCriticalSection(&service->throttle_section);\r
1455   }\r
1456   else {\r
1457     if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);\r
1458     else Sleep(ms);\r
1459   }\r
1460 }\r
1461 \r
1462 /*\r
1463   When responding to a stop (or any other) request we need to set dwWaitHint to\r
1464   the number of milliseconds we expect the operation to take, and optionally\r
1465   increase dwCheckPoint.  If dwWaitHint milliseconds elapses without the\r
1466   operation completing or dwCheckPoint increasing, the system will consider the\r
1467   service to be hung.\r
1468 \r
1469   However the system will consider the service to be hung after 30000\r
1470   milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not\r
1471   changed.  Therefore if we want to wait longer than that we must periodically\r
1472   increase dwCheckPoint.\r
1473 \r
1474   Furthermore, it will consider the service to be hung after 60000 milliseconds\r
1475   regardless of the value of dwCheckPoint unless dwWaitHint is increased every\r
1476   time dwCheckPoint is also increased.\r
1477 \r
1478   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
1479   NSSM_SERVICE_STATUS_DEADLINE milliseconds.  If the process is still running\r
1480   and we haven't finished waiting we increment dwCheckPoint and add whichever is\r
1481   smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to\r
1482   dwWaitHint.\r
1483 \r
1484   Only doing both these things will prevent the system from killing the service.\r
1485 \r
1486   Returns: 1 if the wait timed out.\r
1487            0 if the wait completed.\r
1488           -1 on error.\r
1489 */\r
1490 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {\r
1491   unsigned long interval;\r
1492   unsigned long waithint;\r
1493   unsigned long ret;\r
1494   unsigned long waited;\r
1495   TCHAR interval_milliseconds[16];\r
1496   TCHAR timeout_milliseconds[16];\r
1497   TCHAR waited_milliseconds[16];\r
1498   TCHAR *function = function_name;\r
1499 \r
1500   /* Add brackets to function name. */\r
1501   size_t funclen = _tcslen(function_name) + 3;\r
1502   TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));\r
1503   if (func) {\r
1504     if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;\r
1505   }\r
1506 \r
1507   _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);\r
1508 \r
1509   waithint = service->status.dwWaitHint;\r
1510   waited = 0;\r
1511   while (waited < timeout) {\r
1512     interval = timeout - waited;\r
1513     if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
1514 \r
1515     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1516     service->status.dwWaitHint += interval;\r
1517     service->status.dwCheckPoint++;\r
1518     SetServiceStatus(service->status_handle, &service->status);\r
1519 \r
1520     if (waited) {\r
1521       _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);\r
1522       _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);\r
1523       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
1524     }\r
1525 \r
1526     switch (WaitForSingleObject(service->process_handle, interval)) {\r
1527       case WAIT_OBJECT_0:\r
1528         ret = 0;\r
1529         goto awaited;\r
1530 \r
1531       case WAIT_TIMEOUT:\r
1532         ret = 1;\r
1533       break;\r
1534 \r
1535       default:\r
1536         ret = -1;\r
1537         goto awaited;\r
1538     }\r
1539 \r
1540     waited += interval;\r
1541   }\r
1542 \r
1543 awaited:\r
1544   if (func) HeapFree(GetProcessHeap(), 0, func);\r
1545 \r
1546   return ret;\r
1547 }\r