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