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