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