abe745a532727866ef1c539fcab75d7e7e384b04
[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         HeapFree(GetProcessHeap(), 0, info);\r
390       }\r
391       else {\r
392         error = GetLastError();\r
393         if (error != ERROR_INVALID_LEVEL) {\r
394           CloseHandle(service->handle);\r
395           CloseServiceHandle(services);\r
396           print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));\r
397           return 5;\r
398         }\r
399       }\r
400     }\r
401     else if (error != ERROR_INVALID_LEVEL) {\r
402       CloseHandle(service->handle);\r
403       CloseServiceHandle(services);\r
404       print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));\r
405       return 5;\r
406     }\r
407   }\r
408 \r
409   QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);\r
410   error = GetLastError();\r
411   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
412     SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
413     if (! description) {\r
414       CloseHandle(service->handle);\r
415       CloseServiceHandle(services);\r
416       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("pre_edit_service()"));\r
417       return 6;\r
418     }\r
419 \r
420     if (QueryServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {\r
421      if (description->lpDescription) _sntprintf_s(service->description, _countof(service->description), _TRUNCATE, _T("%s"), description->lpDescription);\r
422       HeapFree(GetProcessHeap(), 0, description);\r
423     }\r
424     else {\r
425       HeapFree(GetProcessHeap(), 0, description);\r
426       CloseHandle(service->handle);\r
427       CloseServiceHandle(services);\r
428       print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));\r
429       return 6;\r
430     }\r
431   }\r
432   else {\r
433     CloseHandle(service->handle);\r
434     CloseServiceHandle(services);\r
435     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service->name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));\r
436     return 6;\r
437   }\r
438 \r
439   /* Get NSSM details. */\r
440   get_parameters(service, 0);\r
441 \r
442   CloseServiceHandle(services);\r
443 \r
444   if (! service->exe[0]) {\r
445     print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);\r
446     service->native = true;\r
447   }\r
448 \r
449   nssm_gui(IDD_EDIT, service);\r
450 \r
451   return 0;\r
452 }\r
453 \r
454 /* About to remove the service */\r
455 int pre_remove_service(int argc, TCHAR **argv) {\r
456   nssm_service_t *service = alloc_nssm_service();\r
457   set_nssm_service_defaults(service);\r
458   if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
459 \r
460   /* Show dialogue box if we didn't pass service name and "confirm" */\r
461   if (argc < 2) return nssm_gui(IDD_REMOVE, service);\r
462   if (str_equiv(argv[1], _T("confirm"))) {\r
463     int ret = remove_service(service);\r
464     cleanup_nssm_service(service);\r
465     return ret;\r
466   }\r
467   print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);\r
468   return 100;\r
469 }\r
470 \r
471 /* Install the service */\r
472 int install_service(nssm_service_t *service) {\r
473   if (! service) return 1;\r
474 \r
475   /* Open service manager */\r
476   SC_HANDLE services = open_service_manager();\r
477   if (! services) {\r
478     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
479     cleanup_nssm_service(service);\r
480     return 2;\r
481   }\r
482 \r
483   /* Get path of this program */\r
484   GetModuleFileName(0, service->image, _countof(service->image));\r
485 \r
486   /* Create the service - settings will be changed in edit_service() */\r
487   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
488   if (! service->handle) {\r
489     print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);\r
490     CloseServiceHandle(services);\r
491     return 5;\r
492   }\r
493 \r
494   if (edit_service(service, false)) {\r
495     DeleteService(service->handle);\r
496     CloseServiceHandle(services);\r
497     return 6;\r
498   }\r
499 \r
500   print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);\r
501 \r
502   /* Cleanup */\r
503   CloseServiceHandle(services);\r
504 \r
505   return 0;\r
506 }\r
507 \r
508 /* Edit the service. */\r
509 int edit_service(nssm_service_t *service, bool editing) {\r
510   if (! service) return 1;\r
511 \r
512   /*\r
513     The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS\r
514     and SERVICE_INTERACTIVE_PROCESS.\r
515   */\r
516   service->type &= SERVICE_INTERACTIVE_PROCESS;\r
517   service->type |= SERVICE_WIN32_OWN_PROCESS;\r
518 \r
519   /* Startup type. */\r
520   unsigned long startup;\r
521   switch (service->startup) {\r
522     case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;\r
523     case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;\r
524     default: startup = SERVICE_AUTO_START;\r
525   }\r
526 \r
527   /* Display name. */\r
528   if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);\r
529 \r
530   /*\r
531     Username must be NULL if we aren't changing or an account name.\r
532     We must explicitly user LOCALSYSTEM to change it when we are editing.\r
533     Password must be NULL if we aren't changing, a password or "".\r
534     Empty passwords are valid but we won't allow them in the GUI.\r
535   */\r
536   TCHAR *username = 0;\r
537   TCHAR *password = 0;\r
538   if (service->usernamelen) {\r
539     username = service->username;\r
540     if (service->passwordlen) password = service->password;\r
541     else password = _T("");\r
542   }\r
543   else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;\r
544 \r
545   if (grant_logon_as_service(username)) {\r
546     print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);\r
547     return 5;\r
548   }\r
549 \r
550   if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {\r
551     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));\r
552     return 5;\r
553   }\r
554 \r
555   if (service->description[0] || editing) {\r
556     SERVICE_DESCRIPTION description;\r
557     ZeroMemory(&description, sizeof(description));\r
558     if (service->description[0]) description.lpDescription = service->description;\r
559     else description.lpDescription = 0;\r
560     if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DESCRIPTION, &description)) {\r
561       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service->name, error_string(GetLastError()), 0);\r
562     }\r
563   }\r
564 \r
565   SERVICE_DELAYED_AUTO_START_INFO delayed;\r
566   ZeroMemory(&delayed, sizeof(delayed));\r
567   if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;\r
568   else delayed.fDelayedAutostart = 0;\r
569   /* Delayed startup isn't supported until Vista. */\r
570   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {\r
571     unsigned long error = GetLastError();\r
572     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
573     if (error != ERROR_INVALID_LEVEL) {\r
574       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);\r
575     }\r
576   }\r
577 \r
578   /* Don't mess with parameters which aren't ours. */\r
579   if (! service->native) {\r
580     /* Now we need to put the parameters into the registry */\r
581     if (create_parameters(service, editing)) {\r
582       print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);\r
583       return 6;\r
584     }\r
585 \r
586     set_service_recovery(service);\r
587   }\r
588 \r
589   return 0;\r
590 }\r
591 \r
592 /* Remove the service */\r
593 int remove_service(nssm_service_t *service) {\r
594   if (! service) return 1;\r
595 \r
596   /* Open service manager */\r
597   SC_HANDLE services = open_service_manager();\r
598   if (! services) {\r
599     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
600     return 2;\r
601   }\r
602 \r
603   /* Try to open the service */\r
604   service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
605   if (! service->handle) {\r
606     print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);\r
607     CloseServiceHandle(services);\r
608     return 3;\r
609   }\r
610 \r
611   /* Get the canonical service name. We open it case insensitively. */\r
612   unsigned long bufsize = _countof(service->displayname);\r
613   GetServiceDisplayName(services, service->name, service->displayname, &bufsize);\r
614   bufsize = _countof(service->name);\r
615   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
616 \r
617   /* Try to delete the service */\r
618   if (! DeleteService(service->handle)) {\r
619     print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);\r
620     CloseServiceHandle(services);\r
621     return 4;\r
622   }\r
623 \r
624   /* Cleanup */\r
625   CloseServiceHandle(services);\r
626 \r
627   print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);\r
628   return 0;\r
629 }\r
630 \r
631 /* Service initialisation */\r
632 void WINAPI service_main(unsigned long argc, TCHAR **argv) {\r
633   nssm_service_t *service = alloc_nssm_service();\r
634   if (! service) return;\r
635 \r
636   if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {\r
637     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);\r
638     return;\r
639   }\r
640 \r
641   /* We can use a condition variable in a critical section on Vista or later. */\r
642   if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;\r
643   else use_critical_section = false;\r
644 \r
645   /* Initialise status */\r
646   ZeroMemory(&service->status, sizeof(service->status));\r
647   service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
648   service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;\r
649   service->status.dwWin32ExitCode = NO_ERROR;\r
650   service->status.dwServiceSpecificExitCode = 0;\r
651   service->status.dwCheckPoint = 0;\r
652   service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
653 \r
654   /* Signal we AREN'T running the server */\r
655   service->process_handle = 0;\r
656   service->pid = 0;\r
657 \r
658   /* Register control handler */\r
659   service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);\r
660   if (! service->status_handle) {\r
661     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);\r
662     return;\r
663   }\r
664 \r
665   log_service_control(service->name, 0, true);\r
666 \r
667   service->status.dwCurrentState = SERVICE_START_PENDING;\r
668   service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;\r
669   SetServiceStatus(service->status_handle, &service->status);\r
670 \r
671   if (is_admin) {\r
672     /* Try to create the exit action parameters; we don't care if it fails */\r
673     create_exit_action(service->name, exit_action_strings[0], false);\r
674 \r
675     SC_HANDLE services = open_service_manager();\r
676     if (services) {\r
677       service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
678       set_service_recovery(service);\r
679       CloseServiceHandle(services);\r
680     }\r
681   }\r
682 \r
683   /* Used for signalling a resume if the service pauses when throttled. */\r
684   if (use_critical_section) {\r
685     InitializeCriticalSection(&service->throttle_section);\r
686     service->throttle_section_initialised = true;\r
687   }\r
688   else {\r
689     service->throttle_timer = CreateWaitableTimer(0, 1, 0);\r
690     if (! service->throttle_timer) {\r
691       log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);\r
692     }\r
693   }\r
694 \r
695   monitor_service(service);\r
696 }\r
697 \r
698 /* Make sure service recovery actions are taken where necessary */\r
699 void set_service_recovery(nssm_service_t *service) {\r
700   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
701   ZeroMemory(&flag, sizeof(flag));\r
702   flag.fFailureActionsOnNonCrashFailures = true;\r
703 \r
704   /* This functionality was added in Vista so the call may fail */\r
705   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {\r
706     unsigned long error = GetLastError();\r
707     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
708     if (error != ERROR_INVALID_LEVEL) {\r
709       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);\r
710     }\r
711   }\r
712 }\r
713 \r
714 int monitor_service(nssm_service_t *service) {\r
715   /* Set service status to started */\r
716   int ret = start_service(service);\r
717   if (ret) {\r
718     TCHAR code[16];\r
719     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);\r
720     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);\r
721     return ret;\r
722   }\r
723   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);\r
724 \r
725   /* Monitor service */\r
726   if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
727     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);\r
728   }\r
729 \r
730   return 0;\r
731 }\r
732 \r
733 TCHAR *service_control_text(unsigned long control) {\r
734   switch (control) {\r
735     /* HACK: there is no SERVICE_CONTROL_START constant */\r
736     case 0: return _T("START");\r
737     case SERVICE_CONTROL_STOP: return _T("STOP");\r
738     case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");\r
739     case SERVICE_CONTROL_PAUSE: return _T("PAUSE");\r
740     case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");\r
741     case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");\r
742     default: return 0;\r
743   }\r
744 }\r
745 \r
746 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {\r
747   TCHAR *text = service_control_text(control);\r
748   unsigned long event;\r
749 \r
750   if (! text) {\r
751     /* "0x" + 8 x hex + NULL */\r
752     text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));\r
753     if (! text) {\r
754       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
755       return;\r
756     }\r
757     if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {\r
758       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
759       HeapFree(GetProcessHeap(), 0, text);\r
760       return;\r
761     }\r
762 \r
763     event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;\r
764   }\r
765   else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;\r
766   else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;\r
767 \r
768   log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);\r
769 \r
770   if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {\r
771     HeapFree(GetProcessHeap(), 0, text);\r
772   }\r
773 }\r
774 \r
775 /* Service control handler */\r
776 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
777   nssm_service_t *service = (nssm_service_t *) context;\r
778 \r
779   switch (control) {\r
780     case SERVICE_CONTROL_INTERROGATE:\r
781       /* We always keep the service status up-to-date so this is a no-op. */\r
782       return NO_ERROR;\r
783 \r
784     case SERVICE_CONTROL_SHUTDOWN:\r
785     case SERVICE_CONTROL_STOP:\r
786       log_service_control(service->name, control, true);\r
787       /*\r
788         We MUST acknowledge the stop request promptly but we're committed to\r
789         waiting for the application to exit.  Spawn a new thread to wait\r
790         while we acknowledge the request.\r
791       */\r
792       if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {\r
793         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
794 \r
795         /*\r
796           We couldn't create a thread to tidy up so we'll have to force the tidyup\r
797           to complete in time in this thread.\r
798         */\r
799         service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
800         service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
801         service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
802 \r
803         stop_service(service, 0, true, true);\r
804       }\r
805       return NO_ERROR;\r
806 \r
807     case SERVICE_CONTROL_CONTINUE:\r
808       log_service_control(service->name, control, true);\r
809       service->throttle = 0;\r
810       if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);\r
811       else {\r
812         if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
813         ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
814         SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
815       }\r
816       service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
817       service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;\r
818       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);\r
819       SetServiceStatus(service->status_handle, &service->status);\r
820       return NO_ERROR;\r
821 \r
822     case SERVICE_CONTROL_PAUSE:\r
823       /*\r
824         We don't accept pause messages but it isn't possible to register\r
825         only for continue messages so we have to handle this case.\r
826       */\r
827       log_service_control(service->name, control, false);\r
828       return ERROR_CALL_NOT_IMPLEMENTED;\r
829   }\r
830 \r
831   /* Unknown control */\r
832   log_service_control(service->name, control, false);\r
833   return ERROR_CALL_NOT_IMPLEMENTED;\r
834 }\r
835 \r
836 /* Start the service */\r
837 int start_service(nssm_service_t *service) {\r
838   service->stopping = false;\r
839   service->allow_restart = true;\r
840 \r
841   if (service->process_handle) return 0;\r
842 \r
843   /* Allocate a STARTUPINFO structure for a new process */\r
844   STARTUPINFO si;\r
845   ZeroMemory(&si, sizeof(si));\r
846   si.cb = sizeof(si);\r
847 \r
848   /* Allocate a PROCESSINFO structure for the process */\r
849   PROCESS_INFORMATION pi;\r
850   ZeroMemory(&pi, sizeof(pi));\r
851 \r
852   /* Get startup parameters */\r
853   int ret = get_parameters(service, &si);\r
854   if (ret) {\r
855     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);\r
856     return stop_service(service, 2, true, true);\r
857   }\r
858 \r
859   /* Launch executable with arguments */\r
860   TCHAR cmd[CMD_LENGTH];\r
861   if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {\r
862     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);\r
863     close_output_handles(&si);\r
864     return stop_service(service, 2, true, true);\r
865   }\r
866 \r
867   throttle_restart(service);\r
868 \r
869   bool inherit_handles = false;\r
870   if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
871   unsigned long flags = 0;\r
872 #ifdef UNICODE\r
873   flags |= CREATE_UNICODE_ENVIRONMENT;\r
874 #endif\r
875   if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {\r
876     unsigned long exitcode = 3;\r
877     unsigned long error = GetLastError();\r
878     if (error == ERROR_INVALID_PARAMETER && service->env) {\r
879       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);\r
880       if (test_environment(service->env)) exitcode = 4;\r
881     }\r
882     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
883     close_output_handles(&si);\r
884     return stop_service(service, exitcode, true, true);\r
885   }\r
886   service->process_handle = pi.hProcess;\r
887   service->pid = pi.dwProcessId;\r
888 \r
889   if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
890 \r
891   close_output_handles(&si);\r
892 \r
893   /*\r
894     Wait for a clean startup before changing the service status to RUNNING\r
895     but be mindful of the fact that we are blocking the service control manager\r
896     so abandon the wait before too much time has elapsed.\r
897   */\r
898   unsigned long delay = service->throttle_delay;\r
899   if (delay > NSSM_SERVICE_STATUS_DEADLINE) {\r
900     TCHAR delay_milliseconds[16];\r
901     _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);\r
902     TCHAR deadline_milliseconds[16];\r
903     _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);\r
904     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);\r
905     delay = NSSM_SERVICE_STATUS_DEADLINE;\r
906   }\r
907   unsigned long deadline = WaitForSingleObject(service->process_handle, delay);\r
908 \r
909   /* Signal successful start */\r
910   service->status.dwCurrentState = SERVICE_RUNNING;\r
911   SetServiceStatus(service->status_handle, &service->status);\r
912 \r
913   /* Continue waiting for a clean startup. */\r
914   if (deadline == WAIT_TIMEOUT) {\r
915     if (service->throttle_delay > delay) {\r
916       if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;\r
917     }\r
918     else service->throttle = 0;\r
919   }\r
920 \r
921   return 0;\r
922 }\r
923 \r
924 /* Stop the service */\r
925 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {\r
926   service->allow_restart = false;\r
927   if (service->wait_handle) {\r
928     UnregisterWait(service->wait_handle);\r
929     service->wait_handle = 0;\r
930   }\r
931 \r
932   if (default_action && ! exitcode && ! graceful) {\r
933     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
934     graceful = true;\r
935   }\r
936 \r
937   /* Signal we are stopping */\r
938   if (graceful) {\r
939     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
940     service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
941     SetServiceStatus(service->status_handle, &service->status);\r
942   }\r
943 \r
944   /* Nothing to do if service isn't running */\r
945   if (service->pid) {\r
946     /* Shut down service */\r
947     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);\r
948     kill_process(service, service->process_handle, service->pid, 0);\r
949   }\r
950   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);\r
951 \r
952   end_service((void *) service, true);\r
953 \r
954   /* Signal we stopped */\r
955   if (graceful) {\r
956     service->status.dwCurrentState = SERVICE_STOPPED;\r
957     if (exitcode) {\r
958       service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
959       service->status.dwServiceSpecificExitCode = exitcode;\r
960     }\r
961     else {\r
962       service->status.dwWin32ExitCode = NO_ERROR;\r
963       service->status.dwServiceSpecificExitCode = 0;\r
964     }\r
965     SetServiceStatus(service->status_handle, &service->status);\r
966   }\r
967 \r
968   return exitcode;\r
969 }\r
970 \r
971 /* Callback function triggered when the server exits */\r
972 void CALLBACK end_service(void *arg, unsigned char why) {\r
973   nssm_service_t *service = (nssm_service_t *) arg;\r
974 \r
975   if (service->stopping) return;\r
976 \r
977   service->stopping = true;\r
978 \r
979   /* Check exit code */\r
980   unsigned long exitcode = 0;\r
981   TCHAR code[16];\r
982   if (service->process_handle) {\r
983     GetExitCodeProcess(service->process_handle, &exitcode);\r
984     if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);\r
985     CloseHandle(service->process_handle);\r
986   }\r
987   else GetSystemTimeAsFileTime(&service->exit_time);\r
988 \r
989   service->process_handle = 0;\r
990 \r
991   /*\r
992     Log that the service ended BEFORE logging about killing the process\r
993     tree.  See below for the possible values of the why argument.\r
994   */\r
995   if (! why) {\r
996     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);\r
997     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);\r
998   }\r
999 \r
1000   /* Clean up. */\r
1001   if (exitcode == STILL_ACTIVE) exitcode = 0;\r
1002   if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);\r
1003   service->pid = 0;\r
1004 \r
1005   /*\r
1006     The why argument is true if our wait timed out or false otherwise.\r
1007     Our wait is infinite so why will never be true when called by the system.\r
1008     If it is indeed true, assume we were called from stop_service() because\r
1009     this is a controlled shutdown, and don't take any restart action.\r
1010   */\r
1011   if (why) return;\r
1012   if (! service->allow_restart) return;\r
1013 \r
1014   /* What action should we take? */\r
1015   int action = NSSM_EXIT_RESTART;\r
1016   TCHAR action_string[ACTION_LEN];\r
1017   bool default_action;\r
1018   if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {\r
1019     for (int i = 0; exit_action_strings[i]; i++) {\r
1020       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
1021         action = i;\r
1022         break;\r
1023       }\r
1024     }\r
1025   }\r
1026 \r
1027   switch (action) {\r
1028     /* Try to restart the service or return failure code to service manager */\r
1029     case NSSM_EXIT_RESTART:\r
1030       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);\r
1031       while (monitor_service(service)) {\r
1032         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);\r
1033         Sleep(30000);\r
1034       }\r
1035     break;\r
1036 \r
1037     /* Do nothing, just like srvany would */\r
1038     case NSSM_EXIT_IGNORE:\r
1039       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
1040       Sleep(INFINITE);\r
1041     break;\r
1042 \r
1043     /* Tell the service manager we are finished */\r
1044     case NSSM_EXIT_REALLY:\r
1045       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);\r
1046       stop_service(service, exitcode, true, default_action);\r
1047     break;\r
1048 \r
1049     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
1050     case NSSM_EXIT_UNCLEAN:\r
1051       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
1052       stop_service(service, exitcode, false, default_action);\r
1053       free_imports();\r
1054       exit(exitcode);\r
1055     break;\r
1056   }\r
1057 }\r
1058 \r
1059 void throttle_restart(nssm_service_t *service) {\r
1060   /* This can't be a restart if the service is already running. */\r
1061   if (! service->throttle++) return;\r
1062 \r
1063   int ms = throttle_milliseconds(service->throttle);\r
1064 \r
1065   if (service->throttle > 7) service->throttle = 8;\r
1066 \r
1067   TCHAR threshold[8], milliseconds[8];\r
1068   _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);\r
1069   _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
1070   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);\r
1071 \r
1072   if (use_critical_section) EnterCriticalSection(&service->throttle_section);\r
1073   else if (service->throttle_timer) {\r
1074     ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1075     service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
1076     SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1077   }\r
1078 \r
1079   service->status.dwCurrentState = SERVICE_PAUSED;\r
1080   SetServiceStatus(service->status_handle, &service->status);\r
1081 \r
1082   if (use_critical_section) {\r
1083     imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);\r
1084     LeaveCriticalSection(&service->throttle_section);\r
1085   }\r
1086   else {\r
1087     if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);\r
1088     else Sleep(ms);\r
1089   }\r
1090 }\r
1091 \r
1092 /*\r
1093   When responding to a stop (or any other) request we need to set dwWaitHint to\r
1094   the number of milliseconds we expect the operation to take, and optionally\r
1095   increase dwCheckPoint.  If dwWaitHint milliseconds elapses without the\r
1096   operation completing or dwCheckPoint increasing, the system will consider the\r
1097   service to be hung.\r
1098 \r
1099   However the system will consider the service to be hung after 30000\r
1100   milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not\r
1101   changed.  Therefore if we want to wait longer than that we must periodically\r
1102   increase dwCheckPoint.\r
1103 \r
1104   Furthermore, it will consider the service to be hung after 60000 milliseconds\r
1105   regardless of the value of dwCheckPoint unless dwWaitHint is increased every\r
1106   time dwCheckPoint is also increased.\r
1107 \r
1108   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
1109   NSSM_SERVICE_STATUS_DEADLINE milliseconds.  If the process is still running\r
1110   and we haven't finished waiting we increment dwCheckPoint and add whichever is\r
1111   smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to\r
1112   dwWaitHint.\r
1113 \r
1114   Only doing both these things will prevent the system from killing the service.\r
1115 \r
1116   Returns: 1 if the wait timed out.\r
1117            0 if the wait completed.\r
1118           -1 on error.\r
1119 */\r
1120 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {\r
1121   unsigned long interval;\r
1122   unsigned long waithint;\r
1123   unsigned long ret;\r
1124   unsigned long waited;\r
1125   TCHAR interval_milliseconds[16];\r
1126   TCHAR timeout_milliseconds[16];\r
1127   TCHAR waited_milliseconds[16];\r
1128   TCHAR *function = function_name;\r
1129 \r
1130   /* Add brackets to function name. */\r
1131   size_t funclen = _tcslen(function_name) + 3;\r
1132   TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));\r
1133   if (func) {\r
1134     if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;\r
1135   }\r
1136 \r
1137   _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);\r
1138 \r
1139   waithint = service->status.dwWaitHint;\r
1140   waited = 0;\r
1141   while (waited < timeout) {\r
1142     interval = timeout - waited;\r
1143     if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
1144 \r
1145     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1146     service->status.dwWaitHint += interval;\r
1147     service->status.dwCheckPoint++;\r
1148     SetServiceStatus(service->status_handle, &service->status);\r
1149 \r
1150     if (waited) {\r
1151       _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);\r
1152       _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);\r
1153       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
1154     }\r
1155 \r
1156     switch (WaitForSingleObject(service->process_handle, interval)) {\r
1157       case WAIT_OBJECT_0:\r
1158         ret = 0;\r
1159         goto awaited;\r
1160 \r
1161       case WAIT_TIMEOUT:\r
1162         ret = 1;\r
1163       break;\r
1164 \r
1165       default:\r
1166         ret = -1;\r
1167         goto awaited;\r
1168     }\r
1169 \r
1170     waited += interval;\r
1171   }\r
1172 \r
1173 awaited:\r
1174   if (func) HeapFree(GetProcessHeap(), 0, func);\r
1175 \r
1176   return ret;\r
1177 }\r