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