cccfa2b55d161578d74dd117dd229447be78e88c
[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 extern const TCHAR *exit_action_strings[];\r
7 \r
8 bool is_admin;\r
9 bool use_critical_section;\r
10 \r
11 extern imports_t imports;\r
12 \r
13 const TCHAR *exit_action_strings[] = { _T("Restart"), _T("Ignore"), _T("Exit"), _T("Suicide"), 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 QUERY_SERVICE_CONFIG *query_service_config(const TCHAR *service_name, SC_HANDLE service_handle) {\r
41   QUERY_SERVICE_CONFIG *qsc;\r
42   unsigned long bufsize;\r
43   unsigned long error;\r
44 \r
45   QueryServiceConfig(service_handle, 0, 0, &bufsize);\r
46   error = GetLastError();\r
47   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
48     qsc = (QUERY_SERVICE_CONFIG *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufsize);\r
49     if (! qsc) {\r
50       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("QUERY_SERVICE_CONFIG"), _T("query_service_config()"), 0);\r
51       return 0;\r
52     }\r
53   }\r
54   else {\r
55     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service_name, error_string(error), 0);\r
56     return 0;\r
57   }\r
58 \r
59   if (! QueryServiceConfig(service_handle, qsc, bufsize, &bufsize)) {\r
60     HeapFree(GetProcessHeap(), 0, qsc);\r
61     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service_name, error_string(GetLastError()), 0);\r
62     return 0;\r
63   }\r
64 \r
65   return qsc;\r
66 }\r
67 \r
68 int get_service_description(const TCHAR *service_name, SC_HANDLE service_handle, unsigned long len, TCHAR *buffer) {\r
69   if (! buffer) return 1;\r
70 \r
71   unsigned long bufsize;\r
72   QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);\r
73   unsigned long error = GetLastError();\r
74   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
75     SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
76     if (! description) {\r
77       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("get_service_description()"));\r
78       return 2;\r
79     }\r
80 \r
81     if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {\r
82       if (description->lpDescription) _sntprintf_s(buffer, len, _TRUNCATE, _T("%s"), description->lpDescription);\r
83       else ZeroMemory(buffer, len * sizeof(TCHAR));\r
84       HeapFree(GetProcessHeap(), 0, description);\r
85       return 0;\r
86     }\r
87     else {\r
88       HeapFree(GetProcessHeap(), 0, description);\r
89       print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));\r
90       return 3;\r
91     }\r
92   }\r
93   else {\r
94     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));\r
95     return 4;\r
96   }\r
97 \r
98   return 0;\r
99 }\r
100 \r
101 int get_service_startup(const TCHAR *service_name, SC_HANDLE service_handle, const QUERY_SERVICE_CONFIG *qsc, unsigned long *startup) {\r
102   if (! qsc) return 1;\r
103 \r
104   switch (qsc->dwStartType) {\r
105     case SERVICE_DEMAND_START: *startup = NSSM_STARTUP_MANUAL; break;\r
106     case SERVICE_DISABLED: *startup = NSSM_STARTUP_DISABLED; break;\r
107     default: *startup = NSSM_STARTUP_AUTOMATIC;\r
108   }\r
109 \r
110   if (*startup != NSSM_STARTUP_AUTOMATIC) return 0;\r
111 \r
112   /* Check for delayed start. */\r
113   unsigned long bufsize;\r
114   unsigned long error;\r
115   QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, 0, 0, &bufsize);\r
116   error = GetLastError();\r
117   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
118     SERVICE_DELAYED_AUTO_START_INFO *info = (SERVICE_DELAYED_AUTO_START_INFO *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
119     if (! info) {\r
120       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_DELAYED_AUTO_START_INFO"), _T("get_service_startup()"));\r
121       return 2;\r
122     }\r
123 \r
124     if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (unsigned char *) info, bufsize, &bufsize)) {\r
125       if (info->fDelayedAutostart) *startup = NSSM_STARTUP_DELAYED;\r
126       HeapFree(GetProcessHeap(), 0, info);\r
127       return 0;\r
128     }\r
129     else {\r
130       error = GetLastError();\r
131       if (error != ERROR_INVALID_LEVEL) {\r
132         print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));\r
133         return 3;\r
134       }\r
135     }\r
136   }\r
137   else if (error != ERROR_INVALID_LEVEL) {\r
138     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));\r
139     return 3;\r
140   }\r
141 \r
142   return 0;\r
143 }\r
144 \r
145 int get_service_username(const TCHAR *service_name, const QUERY_SERVICE_CONFIG *qsc, TCHAR **username, size_t *usernamelen) {\r
146   if (! username) return 1;\r
147   if (! usernamelen) return 1;\r
148 \r
149   *username = 0;\r
150   *usernamelen = 0;\r
151 \r
152   if (! qsc) return 1;\r
153 \r
154   if (str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;\r
155 \r
156   size_t len = _tcslen(qsc->lpServiceStartName);\r
157   *username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(TCHAR));\r
158   if (! *username) {\r
159     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("username"), _T("get_service_username()"));\r
160     return 2;\r
161   }\r
162 \r
163   memmove(*username, qsc->lpServiceStartName, (len + 1) * sizeof(TCHAR));\r
164   *usernamelen = len;\r
165 \r
166   return 0;\r
167 }\r
168 \r
169 static int grant_logon_as_service(const TCHAR *username) {\r
170   if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;\r
171 \r
172   /* Open Policy object. */\r
173   LSA_OBJECT_ATTRIBUTES attributes;\r
174   ZeroMemory(&attributes, sizeof(attributes));\r
175 \r
176   LSA_HANDLE policy;\r
177 \r
178   NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, &policy);\r
179   if (status) {\r
180     print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status)));\r
181     return 1;\r
182   }\r
183 \r
184   /* Look up SID for the account. */\r
185   LSA_UNICODE_STRING lsa_username;\r
186 #ifdef UNICODE\r
187   lsa_username.Buffer = (wchar_t *) username;\r
188   lsa_username.Length = (unsigned short) _tcslen(username) * sizeof(TCHAR);\r
189   lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR);\r
190 #else\r
191   size_t buflen;\r
192   mbstowcs_s(&buflen, NULL, 0, username, _TRUNCATE);\r
193   lsa_username.MaximumLength = buflen * sizeof(wchar_t);\r
194   lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t);\r
195   lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength);\r
196   if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, username, _TRUNCATE);\r
197   else {\r
198     LsaClose(policy);\r
199     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("grant_logon_as_service()"));\r
200     return 2;\r
201   }\r
202 #endif\r
203 \r
204   LSA_REFERENCED_DOMAIN_LIST *translated_domains;\r
205   LSA_TRANSLATED_SID *translated_sid;\r
206   status = LsaLookupNames(policy, 1, &lsa_username, &translated_domains, &translated_sid);\r
207 #ifndef UNICODE\r
208   HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);\r
209 #endif\r
210   if (status) {\r
211     LsaFreeMemory(translated_domains);\r
212     LsaFreeMemory(translated_sid);\r
213     LsaClose(policy);\r
214     print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status)));\r
215     return 3;\r
216   }\r
217 \r
218   if (translated_sid->Use != SidTypeUser) {\r
219     LsaFreeMemory(translated_domains);\r
220     LsaFreeMemory(translated_sid);\r
221     LsaClose(policy);\r
222     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
223     return 4;\r
224   }\r
225 \r
226   LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];\r
227   if (! trust || ! IsValidSid(trust->Sid)) {\r
228     LsaFreeMemory(translated_domains);\r
229     LsaFreeMemory(translated_sid);\r
230     LsaClose(policy);\r
231     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
232     return 4;\r
233   }\r
234 \r
235   /* GetSidSubAuthority*() return pointers! */\r
236   unsigned char *n = GetSidSubAuthorityCount(trust->Sid);\r
237 \r
238   /* Convert translated SID to SID. */\r
239   SID *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));\r
240   if (! sid) {\r
241     LsaFreeMemory(translated_domains);\r
242     LsaFreeMemory(translated_sid);\r
243     LsaClose(policy);\r
244     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("grant_logon_as_service"));\r
245     return 4;\r
246   }\r
247 \r
248   unsigned long error;\r
249   if (! InitializeSid(sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {\r
250     error = GetLastError();\r
251     HeapFree(GetProcessHeap(), 0, sid);\r
252     LsaFreeMemory(translated_domains);\r
253     LsaFreeMemory(translated_sid);\r
254     LsaClose(policy);\r
255     print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));\r
256     return 5;\r
257   }\r
258 \r
259   for (unsigned char i = 0; i <= *n; i++) {\r
260     unsigned long *sub = GetSidSubAuthority(sid, i);\r
261     if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);\r
262     else *sub = translated_sid->RelativeId;\r
263   }\r
264 \r
265   LsaFreeMemory(translated_domains);\r
266   LsaFreeMemory(translated_sid);\r
267 \r
268   /* Check if the SID has the "Log on as a service" right. */\r
269   LSA_UNICODE_STRING lsa_right;\r
270   lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;\r
271   lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);\r
272   lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);\r
273 \r
274   LSA_UNICODE_STRING *rights;\r
275   unsigned long count = ~0;\r
276   status = LsaEnumerateAccountRights(policy, sid, &rights, &count);\r
277   if (status) {\r
278     /*\r
279       If the account has no rights set LsaEnumerateAccountRights() will return\r
280       STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.\r
281     */\r
282     error = LsaNtStatusToWinError(status);\r
283     if (error != ERROR_FILE_NOT_FOUND) {\r
284       HeapFree(GetProcessHeap(), 0, sid);\r
285       LsaClose(policy);\r
286       print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));\r
287       return 4;\r
288     }\r
289   }\r
290 \r
291   for (unsigned long i = 0; i < count; i++) {\r
292     if (rights[i].Length != lsa_right.Length) continue;\r
293     if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;\r
294     /* The SID has the right. */\r
295     HeapFree(GetProcessHeap(), 0, sid);\r
296     LsaFreeMemory(rights);\r
297     LsaClose(policy);\r
298     return 0;\r
299   }\r
300   LsaFreeMemory(rights);\r
301 \r
302   /* Add the right. */\r
303   status = LsaAddAccountRights(policy, sid, &lsa_right, 1);\r
304   HeapFree(GetProcessHeap(), 0, sid);\r
305   LsaClose(policy);\r
306   if (status) {\r
307     print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));\r
308     return 5;\r
309   }\r
310 \r
311   print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);\r
312   return 0;\r
313 }\r
314 \r
315 /* Set default values which aren't zero. */\r
316 void set_nssm_service_defaults(nssm_service_t *service) {\r
317   if (! service) return;\r
318 \r
319   service->type = SERVICE_WIN32_OWN_PROCESS;\r
320   service->stdin_sharing = NSSM_STDIN_SHARING;\r
321   service->stdin_disposition = NSSM_STDIN_DISPOSITION;\r
322   service->stdin_flags = NSSM_STDIN_FLAGS;\r
323   service->stdout_sharing = NSSM_STDOUT_SHARING;\r
324   service->stdout_disposition = NSSM_STDOUT_DISPOSITION;\r
325   service->stdout_flags = NSSM_STDOUT_FLAGS;\r
326   service->stderr_sharing = NSSM_STDERR_SHARING;\r
327   service->stderr_disposition = NSSM_STDERR_DISPOSITION;\r
328   service->stderr_flags = NSSM_STDERR_FLAGS;\r
329   service->throttle_delay = NSSM_RESET_THROTTLE_RESTART;\r
330   service->stop_method = ~0;\r
331   service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
332   service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
333   service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
334 }\r
335 \r
336 /* Allocate and zero memory for a service. */\r
337 nssm_service_t *alloc_nssm_service() {\r
338   nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));\r
339   if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("alloc_nssm_service()"), 0);\r
340   return service;\r
341 }\r
342 \r
343 /* Free memory for a service. */\r
344 void cleanup_nssm_service(nssm_service_t *service) {\r
345   if (! service) return;\r
346   if (service->username) HeapFree(GetProcessHeap(), 0, service->username);\r
347   if (service->password) {\r
348     SecureZeroMemory(service->password, service->passwordlen);\r
349     HeapFree(GetProcessHeap(), 0, service->password);\r
350   }\r
351   if (service->env) HeapFree(GetProcessHeap(), 0, service->env);\r
352   if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);\r
353   if (service->handle) CloseServiceHandle(service->handle);\r
354   if (service->process_handle) CloseHandle(service->process_handle);\r
355   if (service->wait_handle) UnregisterWait(service->process_handle);\r
356   if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);\r
357   if (service->throttle_timer) CloseHandle(service->throttle_timer);\r
358   HeapFree(GetProcessHeap(), 0, service);\r
359 }\r
360 \r
361 /* About to install the service */\r
362 int pre_install_service(int argc, TCHAR **argv) {\r
363   nssm_service_t *service = alloc_nssm_service();\r
364   set_nssm_service_defaults(service);\r
365   if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
366 \r
367   /* Show the dialogue box if we didn't give the service name and path */\r
368   if (argc < 2) return nssm_gui(IDD_INSTALL, service);\r
369 \r
370   if (! service) {\r
371     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("service"), _T("pre_install_service()"));\r
372     return 1;\r
373   }\r
374   _sntprintf_s(service->exe, _countof(service->exe), _TRUNCATE, _T("%s"), argv[1]);\r
375 \r
376   /* Arguments are optional */\r
377   size_t flagslen = 0;\r
378   size_t s = 0;\r
379   int i;\r
380   for (i = 2; i < argc; i++) flagslen += _tcslen(argv[i]) + 1;\r
381   if (! flagslen) flagslen = 1;\r
382   if (flagslen > _countof(service->flags)) {\r
383     print_message(stderr, NSSM_MESSAGE_FLAGS_TOO_LONG);\r
384     return 2;\r
385   }\r
386 \r
387   for (i = 2; i < argc; i++) {\r
388     size_t len = _tcslen(argv[i]);\r
389     memmove(service->flags + s, argv[i], len * sizeof(TCHAR));\r
390     s += len;\r
391     if (i < argc - 1) service->flags[s++] = _T(' ');\r
392   }\r
393 \r
394   /* Work out directory name */\r
395   _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);\r
396   strip_basename(service->dir);\r
397 \r
398   int ret = install_service(service);\r
399   cleanup_nssm_service(service);\r
400   return ret;\r
401 }\r
402 \r
403 /* About to edit the service. */\r
404 int pre_edit_service(int argc, TCHAR **argv) {\r
405   /* Require service name. */\r
406   if (argc < 1) return usage(1);\r
407 \r
408   nssm_service_t *service = alloc_nssm_service();\r
409   _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
410 \r
411   /* Open service manager */\r
412   SC_HANDLE services = open_service_manager();\r
413   if (! services) {\r
414     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
415     return 2;\r
416   }\r
417 \r
418   /* Try to open the service */\r
419   service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
420   if (! service->handle) {\r
421     CloseServiceHandle(services);\r
422     print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);\r
423     return 3;\r
424   }\r
425 \r
426   /* Get system details. */\r
427   unsigned long bufsize;\r
428   QUERY_SERVICE_CONFIG *qsc = query_service_config(service->name, service->handle);\r
429   if (! qsc) {\r
430     CloseHandle(service->handle);\r
431     CloseServiceHandle(services);\r
432     return 4;\r
433   }\r
434 \r
435   service->type = qsc->dwServiceType;\r
436   if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {\r
437     HeapFree(GetProcessHeap(), 0, qsc);\r
438     CloseHandle(service->handle);\r
439     CloseServiceHandle(services);\r
440     print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, _T("SERVICE_WIN32_OWN_PROCESS"), 0);\r
441     return 3;\r
442   }\r
443 \r
444   if (get_service_startup(service->name, service->handle, qsc, &service->startup)) {\r
445     HeapFree(GetProcessHeap(), 0, qsc);\r
446     CloseHandle(service->handle);\r
447     CloseServiceHandle(services);\r
448     return 4;\r
449   }\r
450 \r
451   if (get_service_username(service->name, qsc, &service->username, &service->usernamelen)) {\r
452     HeapFree(GetProcessHeap(), 0, qsc);\r
453     CloseHandle(service->handle);\r
454     CloseServiceHandle(services);\r
455     return 5;\r
456   }\r
457 \r
458   _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);\r
459 \r
460   /* Get the canonical service name. We open it case insensitively. */\r
461   bufsize = _countof(service->name);\r
462   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
463 \r
464   /* Remember the executable in case it isn't NSSM. */\r
465   _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);\r
466   HeapFree(GetProcessHeap(), 0, qsc);\r
467 \r
468   /* Get extended system details. */\r
469   if (get_service_description(service->name, service->handle, _countof(service->description), service->description)) {\r
470     CloseHandle(service->handle);\r
471     CloseServiceHandle(services);\r
472     return 6;\r
473   }\r
474 \r
475   /* Get NSSM details. */\r
476   get_parameters(service, 0);\r
477 \r
478   CloseServiceHandle(services);\r
479 \r
480   if (! service->exe[0]) {\r
481     print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);\r
482     service->native = true;\r
483   }\r
484 \r
485   nssm_gui(IDD_EDIT, service);\r
486 \r
487   return 0;\r
488 }\r
489 \r
490 /* About to remove the service */\r
491 int pre_remove_service(int argc, TCHAR **argv) {\r
492   nssm_service_t *service = alloc_nssm_service();\r
493   set_nssm_service_defaults(service);\r
494   if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
495 \r
496   /* Show dialogue box if we didn't pass service name and "confirm" */\r
497   if (argc < 2) return nssm_gui(IDD_REMOVE, service);\r
498   if (str_equiv(argv[1], _T("confirm"))) {\r
499     int ret = remove_service(service);\r
500     cleanup_nssm_service(service);\r
501     return ret;\r
502   }\r
503   print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);\r
504   return 100;\r
505 }\r
506 \r
507 /* Install the service */\r
508 int install_service(nssm_service_t *service) {\r
509   if (! service) return 1;\r
510 \r
511   /* Open service manager */\r
512   SC_HANDLE services = open_service_manager();\r
513   if (! services) {\r
514     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
515     cleanup_nssm_service(service);\r
516     return 2;\r
517   }\r
518 \r
519   /* Get path of this program */\r
520   GetModuleFileName(0, service->image, _countof(service->image));\r
521 \r
522   /* Create the service - settings will be changed in edit_service() */\r
523   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
524   if (! service->handle) {\r
525     print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);\r
526     CloseServiceHandle(services);\r
527     return 5;\r
528   }\r
529 \r
530   if (edit_service(service, false)) {\r
531     DeleteService(service->handle);\r
532     CloseServiceHandle(services);\r
533     return 6;\r
534   }\r
535 \r
536   print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);\r
537 \r
538   /* Cleanup */\r
539   CloseServiceHandle(services);\r
540 \r
541   return 0;\r
542 }\r
543 \r
544 /* Edit the service. */\r
545 int edit_service(nssm_service_t *service, bool editing) {\r
546   if (! service) return 1;\r
547 \r
548   /*\r
549     The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS\r
550     and SERVICE_INTERACTIVE_PROCESS.\r
551   */\r
552   service->type &= SERVICE_INTERACTIVE_PROCESS;\r
553   service->type |= SERVICE_WIN32_OWN_PROCESS;\r
554 \r
555   /* Startup type. */\r
556   unsigned long startup;\r
557   switch (service->startup) {\r
558     case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;\r
559     case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;\r
560     default: startup = SERVICE_AUTO_START;\r
561   }\r
562 \r
563   /* Display name. */\r
564   if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);\r
565 \r
566   /*\r
567     Username must be NULL if we aren't changing or an account name.\r
568     We must explicitly user LOCALSYSTEM to change it when we are editing.\r
569     Password must be NULL if we aren't changing, a password or "".\r
570     Empty passwords are valid but we won't allow them in the GUI.\r
571   */\r
572   TCHAR *username = 0;\r
573   TCHAR *password = 0;\r
574   if (service->usernamelen) {\r
575     username = service->username;\r
576     if (service->passwordlen) password = service->password;\r
577     else password = _T("");\r
578   }\r
579   else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;\r
580 \r
581   if (grant_logon_as_service(username)) {\r
582     print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);\r
583     return 5;\r
584   }\r
585 \r
586   if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {\r
587     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));\r
588     return 5;\r
589   }\r
590 \r
591   if (service->description[0] || editing) {\r
592     SERVICE_DESCRIPTION description;\r
593     ZeroMemory(&description, sizeof(description));\r
594     if (service->description[0]) description.lpDescription = service->description;\r
595     else description.lpDescription = 0;\r
596     if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, &description)) {\r
597       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service->name, error_string(GetLastError()), 0);\r
598     }\r
599   }\r
600 \r
601   SERVICE_DELAYED_AUTO_START_INFO delayed;\r
602   ZeroMemory(&delayed, sizeof(delayed));\r
603   if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;\r
604   else delayed.fDelayedAutostart = 0;\r
605   /* Delayed startup isn't supported until Vista. */\r
606   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {\r
607     unsigned long error = GetLastError();\r
608     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
609     if (error != ERROR_INVALID_LEVEL) {\r
610       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);\r
611     }\r
612   }\r
613 \r
614   /* Don't mess with parameters which aren't ours. */\r
615   if (! service->native) {\r
616     /* Now we need to put the parameters into the registry */\r
617     if (create_parameters(service, editing)) {\r
618       print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);\r
619       return 6;\r
620     }\r
621 \r
622     set_service_recovery(service);\r
623   }\r
624 \r
625   return 0;\r
626 }\r
627 \r
628 /* Remove the service */\r
629 int remove_service(nssm_service_t *service) {\r
630   if (! service) return 1;\r
631 \r
632   /* Open service manager */\r
633   SC_HANDLE services = open_service_manager();\r
634   if (! services) {\r
635     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
636     return 2;\r
637   }\r
638 \r
639   /* Try to open the service */\r
640   service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
641   if (! service->handle) {\r
642     print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);\r
643     CloseServiceHandle(services);\r
644     return 3;\r
645   }\r
646 \r
647   /* Get the canonical service name. We open it case insensitively. */\r
648   unsigned long bufsize = _countof(service->displayname);\r
649   GetServiceDisplayName(services, service->name, service->displayname, &bufsize);\r
650   bufsize = _countof(service->name);\r
651   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
652 \r
653   /* Try to delete the service */\r
654   if (! DeleteService(service->handle)) {\r
655     print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);\r
656     CloseServiceHandle(services);\r
657     return 4;\r
658   }\r
659 \r
660   /* Cleanup */\r
661   CloseServiceHandle(services);\r
662 \r
663   print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);\r
664   return 0;\r
665 }\r
666 \r
667 /* Service initialisation */\r
668 void WINAPI service_main(unsigned long argc, TCHAR **argv) {\r
669   nssm_service_t *service = alloc_nssm_service();\r
670   if (! service) return;\r
671 \r
672   if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {\r
673     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);\r
674     return;\r
675   }\r
676 \r
677   /* We can use a condition variable in a critical section on Vista or later. */\r
678   if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;\r
679   else use_critical_section = false;\r
680 \r
681   /* Initialise status */\r
682   ZeroMemory(&service->status, sizeof(service->status));\r
683   service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
684   service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;\r
685   service->status.dwWin32ExitCode = NO_ERROR;\r
686   service->status.dwServiceSpecificExitCode = 0;\r
687   service->status.dwCheckPoint = 0;\r
688   service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
689 \r
690   /* Signal we AREN'T running the server */\r
691   service->process_handle = 0;\r
692   service->pid = 0;\r
693 \r
694   /* Register control handler */\r
695   service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);\r
696   if (! service->status_handle) {\r
697     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);\r
698     return;\r
699   }\r
700 \r
701   log_service_control(service->name, 0, true);\r
702 \r
703   service->status.dwCurrentState = SERVICE_START_PENDING;\r
704   service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;\r
705   SetServiceStatus(service->status_handle, &service->status);\r
706 \r
707   if (is_admin) {\r
708     /* Try to create the exit action parameters; we don't care if it fails */\r
709     create_exit_action(service->name, exit_action_strings[0], false);\r
710 \r
711     SC_HANDLE services = open_service_manager();\r
712     if (services) {\r
713       service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
714       set_service_recovery(service);\r
715       CloseServiceHandle(services);\r
716     }\r
717   }\r
718 \r
719   /* Used for signalling a resume if the service pauses when throttled. */\r
720   if (use_critical_section) {\r
721     InitializeCriticalSection(&service->throttle_section);\r
722     service->throttle_section_initialised = true;\r
723   }\r
724   else {\r
725     service->throttle_timer = CreateWaitableTimer(0, 1, 0);\r
726     if (! service->throttle_timer) {\r
727       log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);\r
728     }\r
729   }\r
730 \r
731   monitor_service(service);\r
732 }\r
733 \r
734 /* Make sure service recovery actions are taken where necessary */\r
735 void set_service_recovery(nssm_service_t *service) {\r
736   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
737   ZeroMemory(&flag, sizeof(flag));\r
738   flag.fFailureActionsOnNonCrashFailures = true;\r
739 \r
740   /* This functionality was added in Vista so the call may fail */\r
741   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {\r
742     unsigned long error = GetLastError();\r
743     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
744     if (error != ERROR_INVALID_LEVEL) {\r
745       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);\r
746     }\r
747   }\r
748 }\r
749 \r
750 int monitor_service(nssm_service_t *service) {\r
751   /* Set service status to started */\r
752   int ret = start_service(service);\r
753   if (ret) {\r
754     TCHAR code[16];\r
755     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);\r
756     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);\r
757     return ret;\r
758   }\r
759   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);\r
760 \r
761   /* Monitor service */\r
762   if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
763     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);\r
764   }\r
765 \r
766   return 0;\r
767 }\r
768 \r
769 TCHAR *service_control_text(unsigned long control) {\r
770   switch (control) {\r
771     /* HACK: there is no SERVICE_CONTROL_START constant */\r
772     case 0: return _T("START");\r
773     case SERVICE_CONTROL_STOP: return _T("STOP");\r
774     case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");\r
775     case SERVICE_CONTROL_PAUSE: return _T("PAUSE");\r
776     case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");\r
777     case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");\r
778     default: return 0;\r
779   }\r
780 }\r
781 \r
782 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {\r
783   TCHAR *text = service_control_text(control);\r
784   unsigned long event;\r
785 \r
786   if (! text) {\r
787     /* "0x" + 8 x hex + NULL */\r
788     text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));\r
789     if (! text) {\r
790       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
791       return;\r
792     }\r
793     if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {\r
794       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
795       HeapFree(GetProcessHeap(), 0, text);\r
796       return;\r
797     }\r
798 \r
799     event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;\r
800   }\r
801   else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;\r
802   else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;\r
803 \r
804   log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);\r
805 \r
806   if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {\r
807     HeapFree(GetProcessHeap(), 0, text);\r
808   }\r
809 }\r
810 \r
811 /* Service control handler */\r
812 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
813   nssm_service_t *service = (nssm_service_t *) context;\r
814 \r
815   switch (control) {\r
816     case SERVICE_CONTROL_INTERROGATE:\r
817       /* We always keep the service status up-to-date so this is a no-op. */\r
818       return NO_ERROR;\r
819 \r
820     case SERVICE_CONTROL_SHUTDOWN:\r
821     case SERVICE_CONTROL_STOP:\r
822       log_service_control(service->name, control, true);\r
823       /*\r
824         We MUST acknowledge the stop request promptly but we're committed to\r
825         waiting for the application to exit.  Spawn a new thread to wait\r
826         while we acknowledge the request.\r
827       */\r
828       if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {\r
829         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
830 \r
831         /*\r
832           We couldn't create a thread to tidy up so we'll have to force the tidyup\r
833           to complete in time in this thread.\r
834         */\r
835         service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
836         service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
837         service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
838 \r
839         stop_service(service, 0, true, true);\r
840       }\r
841       return NO_ERROR;\r
842 \r
843     case SERVICE_CONTROL_CONTINUE:\r
844       log_service_control(service->name, control, true);\r
845       service->throttle = 0;\r
846       if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);\r
847       else {\r
848         if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
849         ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
850         SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
851       }\r
852       service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
853       service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;\r
854       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);\r
855       SetServiceStatus(service->status_handle, &service->status);\r
856       return NO_ERROR;\r
857 \r
858     case SERVICE_CONTROL_PAUSE:\r
859       /*\r
860         We don't accept pause messages but it isn't possible to register\r
861         only for continue messages so we have to handle this case.\r
862       */\r
863       log_service_control(service->name, control, false);\r
864       return ERROR_CALL_NOT_IMPLEMENTED;\r
865   }\r
866 \r
867   /* Unknown control */\r
868   log_service_control(service->name, control, false);\r
869   return ERROR_CALL_NOT_IMPLEMENTED;\r
870 }\r
871 \r
872 /* Start the service */\r
873 int start_service(nssm_service_t *service) {\r
874   service->stopping = false;\r
875   service->allow_restart = true;\r
876 \r
877   if (service->process_handle) return 0;\r
878 \r
879   /* Allocate a STARTUPINFO structure for a new process */\r
880   STARTUPINFO si;\r
881   ZeroMemory(&si, sizeof(si));\r
882   si.cb = sizeof(si);\r
883 \r
884   /* Allocate a PROCESSINFO structure for the process */\r
885   PROCESS_INFORMATION pi;\r
886   ZeroMemory(&pi, sizeof(pi));\r
887 \r
888   /* Get startup parameters */\r
889   int ret = get_parameters(service, &si);\r
890   if (ret) {\r
891     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);\r
892     return stop_service(service, 2, true, true);\r
893   }\r
894 \r
895   /* Launch executable with arguments */\r
896   TCHAR cmd[CMD_LENGTH];\r
897   if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {\r
898     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);\r
899     close_output_handles(&si);\r
900     return stop_service(service, 2, true, true);\r
901   }\r
902 \r
903   throttle_restart(service);\r
904 \r
905   bool inherit_handles = false;\r
906   if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
907   unsigned long flags = 0;\r
908 #ifdef UNICODE\r
909   flags |= CREATE_UNICODE_ENVIRONMENT;\r
910 #endif\r
911   if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {\r
912     unsigned long exitcode = 3;\r
913     unsigned long error = GetLastError();\r
914     if (error == ERROR_INVALID_PARAMETER && service->env) {\r
915       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);\r
916       if (test_environment(service->env)) exitcode = 4;\r
917     }\r
918     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
919     close_output_handles(&si);\r
920     return stop_service(service, exitcode, true, true);\r
921   }\r
922   service->process_handle = pi.hProcess;\r
923   service->pid = pi.dwProcessId;\r
924 \r
925   if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
926 \r
927   close_output_handles(&si);\r
928 \r
929   /*\r
930     Wait for a clean startup before changing the service status to RUNNING\r
931     but be mindful of the fact that we are blocking the service control manager\r
932     so abandon the wait before too much time has elapsed.\r
933   */\r
934   unsigned long delay = service->throttle_delay;\r
935   if (delay > NSSM_SERVICE_STATUS_DEADLINE) {\r
936     TCHAR delay_milliseconds[16];\r
937     _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);\r
938     TCHAR deadline_milliseconds[16];\r
939     _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);\r
940     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);\r
941     delay = NSSM_SERVICE_STATUS_DEADLINE;\r
942   }\r
943   unsigned long deadline = WaitForSingleObject(service->process_handle, delay);\r
944 \r
945   /* Signal successful start */\r
946   service->status.dwCurrentState = SERVICE_RUNNING;\r
947   SetServiceStatus(service->status_handle, &service->status);\r
948 \r
949   /* Continue waiting for a clean startup. */\r
950   if (deadline == WAIT_TIMEOUT) {\r
951     if (service->throttle_delay > delay) {\r
952       if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;\r
953     }\r
954     else service->throttle = 0;\r
955   }\r
956 \r
957   return 0;\r
958 }\r
959 \r
960 /* Stop the service */\r
961 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {\r
962   service->allow_restart = false;\r
963   if (service->wait_handle) {\r
964     UnregisterWait(service->wait_handle);\r
965     service->wait_handle = 0;\r
966   }\r
967 \r
968   if (default_action && ! exitcode && ! graceful) {\r
969     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
970     graceful = true;\r
971   }\r
972 \r
973   /* Signal we are stopping */\r
974   if (graceful) {\r
975     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
976     service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
977     SetServiceStatus(service->status_handle, &service->status);\r
978   }\r
979 \r
980   /* Nothing to do if service isn't running */\r
981   if (service->pid) {\r
982     /* Shut down service */\r
983     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);\r
984     kill_process(service, service->process_handle, service->pid, 0);\r
985   }\r
986   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);\r
987 \r
988   end_service((void *) service, true);\r
989 \r
990   /* Signal we stopped */\r
991   if (graceful) {\r
992     service->status.dwCurrentState = SERVICE_STOPPED;\r
993     if (exitcode) {\r
994       service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
995       service->status.dwServiceSpecificExitCode = exitcode;\r
996     }\r
997     else {\r
998       service->status.dwWin32ExitCode = NO_ERROR;\r
999       service->status.dwServiceSpecificExitCode = 0;\r
1000     }\r
1001     SetServiceStatus(service->status_handle, &service->status);\r
1002   }\r
1003 \r
1004   return exitcode;\r
1005 }\r
1006 \r
1007 /* Callback function triggered when the server exits */\r
1008 void CALLBACK end_service(void *arg, unsigned char why) {\r
1009   nssm_service_t *service = (nssm_service_t *) arg;\r
1010 \r
1011   if (service->stopping) return;\r
1012 \r
1013   service->stopping = true;\r
1014 \r
1015   /* Check exit code */\r
1016   unsigned long exitcode = 0;\r
1017   TCHAR code[16];\r
1018   if (service->process_handle) {\r
1019     GetExitCodeProcess(service->process_handle, &exitcode);\r
1020     if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);\r
1021     CloseHandle(service->process_handle);\r
1022   }\r
1023   else GetSystemTimeAsFileTime(&service->exit_time);\r
1024 \r
1025   service->process_handle = 0;\r
1026 \r
1027   /*\r
1028     Log that the service ended BEFORE logging about killing the process\r
1029     tree.  See below for the possible values of the why argument.\r
1030   */\r
1031   if (! why) {\r
1032     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);\r
1033     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);\r
1034   }\r
1035 \r
1036   /* Clean up. */\r
1037   if (exitcode == STILL_ACTIVE) exitcode = 0;\r
1038   if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);\r
1039   service->pid = 0;\r
1040 \r
1041   /*\r
1042     The why argument is true if our wait timed out or false otherwise.\r
1043     Our wait is infinite so why will never be true when called by the system.\r
1044     If it is indeed true, assume we were called from stop_service() because\r
1045     this is a controlled shutdown, and don't take any restart action.\r
1046   */\r
1047   if (why) return;\r
1048   if (! service->allow_restart) return;\r
1049 \r
1050   /* What action should we take? */\r
1051   int action = NSSM_EXIT_RESTART;\r
1052   TCHAR action_string[ACTION_LEN];\r
1053   bool default_action;\r
1054   if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {\r
1055     for (int i = 0; exit_action_strings[i]; i++) {\r
1056       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
1057         action = i;\r
1058         break;\r
1059       }\r
1060     }\r
1061   }\r
1062 \r
1063   switch (action) {\r
1064     /* Try to restart the service or return failure code to service manager */\r
1065     case NSSM_EXIT_RESTART:\r
1066       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);\r
1067       while (monitor_service(service)) {\r
1068         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);\r
1069         Sleep(30000);\r
1070       }\r
1071     break;\r
1072 \r
1073     /* Do nothing, just like srvany would */\r
1074     case NSSM_EXIT_IGNORE:\r
1075       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
1076       Sleep(INFINITE);\r
1077     break;\r
1078 \r
1079     /* Tell the service manager we are finished */\r
1080     case NSSM_EXIT_REALLY:\r
1081       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);\r
1082       stop_service(service, exitcode, true, default_action);\r
1083     break;\r
1084 \r
1085     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
1086     case NSSM_EXIT_UNCLEAN:\r
1087       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
1088       stop_service(service, exitcode, false, default_action);\r
1089       free_imports();\r
1090       exit(exitcode);\r
1091     break;\r
1092   }\r
1093 }\r
1094 \r
1095 void throttle_restart(nssm_service_t *service) {\r
1096   /* This can't be a restart if the service is already running. */\r
1097   if (! service->throttle++) return;\r
1098 \r
1099   int ms = throttle_milliseconds(service->throttle);\r
1100 \r
1101   if (service->throttle > 7) service->throttle = 8;\r
1102 \r
1103   TCHAR threshold[8], milliseconds[8];\r
1104   _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);\r
1105   _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
1106   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);\r
1107 \r
1108   if (use_critical_section) EnterCriticalSection(&service->throttle_section);\r
1109   else if (service->throttle_timer) {\r
1110     ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1111     service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
1112     SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1113   }\r
1114 \r
1115   service->status.dwCurrentState = SERVICE_PAUSED;\r
1116   SetServiceStatus(service->status_handle, &service->status);\r
1117 \r
1118   if (use_critical_section) {\r
1119     imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);\r
1120     LeaveCriticalSection(&service->throttle_section);\r
1121   }\r
1122   else {\r
1123     if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);\r
1124     else Sleep(ms);\r
1125   }\r
1126 }\r
1127 \r
1128 /*\r
1129   When responding to a stop (or any other) request we need to set dwWaitHint to\r
1130   the number of milliseconds we expect the operation to take, and optionally\r
1131   increase dwCheckPoint.  If dwWaitHint milliseconds elapses without the\r
1132   operation completing or dwCheckPoint increasing, the system will consider the\r
1133   service to be hung.\r
1134 \r
1135   However the system will consider the service to be hung after 30000\r
1136   milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not\r
1137   changed.  Therefore if we want to wait longer than that we must periodically\r
1138   increase dwCheckPoint.\r
1139 \r
1140   Furthermore, it will consider the service to be hung after 60000 milliseconds\r
1141   regardless of the value of dwCheckPoint unless dwWaitHint is increased every\r
1142   time dwCheckPoint is also increased.\r
1143 \r
1144   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
1145   NSSM_SERVICE_STATUS_DEADLINE milliseconds.  If the process is still running\r
1146   and we haven't finished waiting we increment dwCheckPoint and add whichever is\r
1147   smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to\r
1148   dwWaitHint.\r
1149 \r
1150   Only doing both these things will prevent the system from killing the service.\r
1151 \r
1152   Returns: 1 if the wait timed out.\r
1153            0 if the wait completed.\r
1154           -1 on error.\r
1155 */\r
1156 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {\r
1157   unsigned long interval;\r
1158   unsigned long waithint;\r
1159   unsigned long ret;\r
1160   unsigned long waited;\r
1161   TCHAR interval_milliseconds[16];\r
1162   TCHAR timeout_milliseconds[16];\r
1163   TCHAR waited_milliseconds[16];\r
1164   TCHAR *function = function_name;\r
1165 \r
1166   /* Add brackets to function name. */\r
1167   size_t funclen = _tcslen(function_name) + 3;\r
1168   TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));\r
1169   if (func) {\r
1170     if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;\r
1171   }\r
1172 \r
1173   _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);\r
1174 \r
1175   waithint = service->status.dwWaitHint;\r
1176   waited = 0;\r
1177   while (waited < timeout) {\r
1178     interval = timeout - waited;\r
1179     if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
1180 \r
1181     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1182     service->status.dwWaitHint += interval;\r
1183     service->status.dwCheckPoint++;\r
1184     SetServiceStatus(service->status_handle, &service->status);\r
1185 \r
1186     if (waited) {\r
1187       _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);\r
1188       _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);\r
1189       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
1190     }\r
1191 \r
1192     switch (WaitForSingleObject(service->process_handle, interval)) {\r
1193       case WAIT_OBJECT_0:\r
1194         ret = 0;\r
1195         goto awaited;\r
1196 \r
1197       case WAIT_TIMEOUT:\r
1198         ret = 1;\r
1199       break;\r
1200 \r
1201       default:\r
1202         ret = -1;\r
1203         goto awaited;\r
1204     }\r
1205 \r
1206     waited += interval;\r
1207   }\r
1208 \r
1209 awaited:\r
1210   if (func) HeapFree(GetProcessHeap(), 0, func);\r
1211 \r
1212   return ret;\r
1213 }\r