72e0192e10cd4264a457ccc9c2271c2dae57572d
[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 bool is_admin;\r
7 bool use_critical_section;\r
8 \r
9 extern imports_t imports;\r
10 extern settings_t settings[];\r
11 \r
12 const TCHAR *exit_action_strings[] = { _T("Restart"), _T("Ignore"), _T("Exit"), _T("Suicide"), 0 };\r
13 const TCHAR *startup_strings[] = { _T("SERVICE_AUTO_START"), _T("SERVICE_DELAYED_AUTO_START"), _T("SERVICE_DEMAND_START"), _T("SERVICE_DISABLED"), 0 };\r
14 const TCHAR *priority_strings[] = { _T("REALTIME_PRIORITY_CLASS"), _T("HIGH_PRIORITY_CLASS"), _T("ABOVE_NORMAL_PRIORITY_CLASS"), _T("NORMAL_PRIORITY_CLASS"), _T("BELOW_NORMAL_PRIORITY_CLASS"), _T("IDLE_PRIORITY_CLASS"), 0 };\r
15 \r
16 inline unsigned long priority_mask() {\r
17  return REALTIME_PRIORITY_CLASS | HIGH_PRIORITY_CLASS | ABOVE_NORMAL_PRIORITY_CLASS | NORMAL_PRIORITY_CLASS | BELOW_NORMAL_PRIORITY_CLASS | IDLE_PRIORITY_CLASS;\r
18 }\r
19 \r
20 int priority_constant_to_index(unsigned long constant) {\r
21   switch (constant & priority_mask()) {\r
22     case REALTIME_PRIORITY_CLASS: return NSSM_REALTIME_PRIORITY;\r
23     case HIGH_PRIORITY_CLASS: return NSSM_HIGH_PRIORITY;\r
24     case ABOVE_NORMAL_PRIORITY_CLASS: return NSSM_ABOVE_NORMAL_PRIORITY;\r
25     case BELOW_NORMAL_PRIORITY_CLASS: return NSSM_BELOW_NORMAL_PRIORITY;\r
26     case IDLE_PRIORITY_CLASS: return NSSM_IDLE_PRIORITY;\r
27   }\r
28   return NSSM_NORMAL_PRIORITY;\r
29 }\r
30 \r
31 unsigned long priority_index_to_constant(int index) {\r
32   switch (index) {\r
33     case NSSM_REALTIME_PRIORITY: return REALTIME_PRIORITY_CLASS;\r
34     case NSSM_HIGH_PRIORITY: return HIGH_PRIORITY_CLASS;\r
35     case NSSM_ABOVE_NORMAL_PRIORITY: return ABOVE_NORMAL_PRIORITY_CLASS;\r
36     case NSSM_BELOW_NORMAL_PRIORITY: return BELOW_NORMAL_PRIORITY_CLASS;\r
37     case NSSM_IDLE_PRIORITY: return IDLE_PRIORITY_CLASS;\r
38   }\r
39   return NORMAL_PRIORITY_CLASS;\r
40 }\r
41 \r
42 static inline int throttle_milliseconds(unsigned long throttle) {\r
43   /* pow() operates on doubles. */\r
44   int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;\r
45   return ret * 1000;\r
46 }\r
47 \r
48 /*\r
49   Wrapper to be called in a new thread so that we can acknowledge a STOP\r
50   control immediately.\r
51 */\r
52 static unsigned long WINAPI shutdown_service(void *arg) {\r
53   return stop_service((nssm_service_t *) arg, 0, true, true);\r
54 }\r
55 \r
56 /* Connect to the service manager */\r
57 SC_HANDLE open_service_manager() {\r
58   SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);\r
59   if (! ret) {\r
60     if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);\r
61     return 0;\r
62   }\r
63 \r
64   return ret;\r
65 }\r
66 \r
67 /* Open a service by name or display name. */\r
68 SC_HANDLE open_service(SC_HANDLE services, TCHAR *service_name, TCHAR *canonical_name, unsigned long canonical_namelen) {\r
69   SC_HANDLE service_handle = OpenService(services, service_name, SERVICE_ALL_ACCESS);\r
70   if (service_handle) {\r
71     if (canonical_name && canonical_name != service_name) {\r
72       if (_sntprintf_s(canonical_name, canonical_namelen, _TRUNCATE, _T("%s"), service_name) < 0) {\r
73         print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canonical_name"), _T("open_service()"));\r
74         return 0;\r
75       }\r
76     }\r
77     return service_handle;\r
78   }\r
79 \r
80   unsigned long error = GetLastError();\r
81   if (error != ERROR_SERVICE_DOES_NOT_EXIST) {\r
82     print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED, error_string(GetLastError()));\r
83     return 0;\r
84   }\r
85 \r
86   /* We can't look for a display name because there's no buffer to store it. */\r
87   if (! canonical_name) {\r
88     print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED, error_string(GetLastError()));\r
89     return 0;\r
90   }\r
91 \r
92   unsigned long bufsize, required, count, i;\r
93   unsigned long resume = 0;\r
94   EnumServicesStatus(services, SERVICE_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_KERNEL_DRIVER | SERVICE_WIN32, SERVICE_STATE_ALL, 0, 0, &required, &count, &resume);\r
95   error = GetLastError();\r
96   if (error != ERROR_MORE_DATA) {\r
97     print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));\r
98     return 0;\r
99   }\r
100 \r
101   ENUM_SERVICE_STATUS *status = (ENUM_SERVICE_STATUS *) HeapAlloc(GetProcessHeap(), 0, required);\r
102   if (! status) {\r
103     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("ENUM_SERVICE_STATUS"), _T("open_service()"));\r
104     return 0;\r
105   }\r
106 \r
107   bufsize = required;\r
108   while (true) {\r
109     /*\r
110       EnumServicesStatus() returns:\r
111       1 when it retrieved data and there's no more data to come.\r
112       0 and sets last error to ERROR_MORE_DATA when it retrieved data and\r
113         there's more data to come.\r
114       0 and sets last error to something else on error.\r
115     */\r
116     int ret = EnumServicesStatus(services, SERVICE_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_KERNEL_DRIVER | SERVICE_WIN32, SERVICE_STATE_ALL, status, bufsize, &required, &count, &resume);\r
117     if (! ret) {\r
118       error = GetLastError();\r
119       if (error != ERROR_MORE_DATA) {\r
120         HeapFree(GetProcessHeap(), 0, status);\r
121         print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));\r
122         return 0;\r
123       }\r
124     }\r
125 \r
126     for (i = 0; i < count; i++) {\r
127       if (str_equiv(status[i].lpDisplayName, service_name)) {\r
128         if (_sntprintf_s(canonical_name, canonical_namelen, _TRUNCATE, _T("%s"), status[i].lpServiceName) < 0) {\r
129           HeapFree(GetProcessHeap(), 0, status);\r
130           print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canonical_name"), _T("open_service()"));\r
131           return 0;\r
132         }\r
133 \r
134         HeapFree(GetProcessHeap(), 0, status);\r
135         return open_service(services, canonical_name, 0, 0);\r
136       }\r
137     }\r
138 \r
139     if (ret) break;\r
140   }\r
141 \r
142   /* Recurse so we can get an error message. */\r
143   return open_service(services, service_name, 0, 0);\r
144 }\r
145 \r
146 QUERY_SERVICE_CONFIG *query_service_config(const TCHAR *service_name, SC_HANDLE service_handle) {\r
147   QUERY_SERVICE_CONFIG *qsc;\r
148   unsigned long bufsize;\r
149   unsigned long error;\r
150 \r
151   QueryServiceConfig(service_handle, 0, 0, &bufsize);\r
152   error = GetLastError();\r
153   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
154     qsc = (QUERY_SERVICE_CONFIG *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufsize);\r
155     if (! qsc) {\r
156       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("QUERY_SERVICE_CONFIG"), _T("query_service_config()"), 0);\r
157       return 0;\r
158     }\r
159   }\r
160   else {\r
161     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service_name, error_string(error), 0);\r
162     return 0;\r
163   }\r
164 \r
165   if (! QueryServiceConfig(service_handle, qsc, bufsize, &bufsize)) {\r
166     HeapFree(GetProcessHeap(), 0, qsc);\r
167     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service_name, error_string(GetLastError()), 0);\r
168     return 0;\r
169   }\r
170 \r
171   return qsc;\r
172 }\r
173 \r
174 int set_service_description(const TCHAR *service_name, SC_HANDLE service_handle, TCHAR *buffer) {\r
175   SERVICE_DESCRIPTION description;\r
176   ZeroMemory(&description, sizeof(description));\r
177   /*\r
178     lpDescription must be NULL if we aren't changing, the new description\r
179     or "".\r
180   */\r
181   if (buffer && buffer[0]) description.lpDescription = buffer;\r
182   else description.lpDescription = _T("");\r
183 \r
184   if (ChangeServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, &description)) return 0;\r
185 \r
186   log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service_name, error_string(GetLastError()), 0);\r
187   return 1;\r
188 }\r
189 \r
190 int get_service_description(const TCHAR *service_name, SC_HANDLE service_handle, unsigned long len, TCHAR *buffer) {\r
191   if (! buffer) return 1;\r
192 \r
193   unsigned long bufsize;\r
194   QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);\r
195   unsigned long error = GetLastError();\r
196   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
197     SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
198     if (! description) {\r
199       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("get_service_description()"));\r
200       return 2;\r
201     }\r
202 \r
203     if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {\r
204       if (description->lpDescription) _sntprintf_s(buffer, len, _TRUNCATE, _T("%s"), description->lpDescription);\r
205       else ZeroMemory(buffer, len * sizeof(TCHAR));\r
206       HeapFree(GetProcessHeap(), 0, description);\r
207       return 0;\r
208     }\r
209     else {\r
210       HeapFree(GetProcessHeap(), 0, description);\r
211       print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));\r
212       return 3;\r
213     }\r
214   }\r
215   else {\r
216     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));\r
217     return 4;\r
218   }\r
219 \r
220   return 0;\r
221 }\r
222 \r
223 int get_service_startup(const TCHAR *service_name, SC_HANDLE service_handle, const QUERY_SERVICE_CONFIG *qsc, unsigned long *startup) {\r
224   if (! qsc) return 1;\r
225 \r
226   switch (qsc->dwStartType) {\r
227     case SERVICE_DEMAND_START: *startup = NSSM_STARTUP_MANUAL; break;\r
228     case SERVICE_DISABLED: *startup = NSSM_STARTUP_DISABLED; break;\r
229     default: *startup = NSSM_STARTUP_AUTOMATIC;\r
230   }\r
231 \r
232   if (*startup != NSSM_STARTUP_AUTOMATIC) return 0;\r
233 \r
234   /* Check for delayed start. */\r
235   unsigned long bufsize;\r
236   unsigned long error;\r
237   QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, 0, 0, &bufsize);\r
238   error = GetLastError();\r
239   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
240     SERVICE_DELAYED_AUTO_START_INFO *info = (SERVICE_DELAYED_AUTO_START_INFO *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
241     if (! info) {\r
242       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_DELAYED_AUTO_START_INFO"), _T("get_service_startup()"));\r
243       return 2;\r
244     }\r
245 \r
246     if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (unsigned char *) info, bufsize, &bufsize)) {\r
247       if (info->fDelayedAutostart) *startup = NSSM_STARTUP_DELAYED;\r
248       HeapFree(GetProcessHeap(), 0, info);\r
249       return 0;\r
250     }\r
251     else {\r
252       error = GetLastError();\r
253       if (error != ERROR_INVALID_LEVEL) {\r
254         print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));\r
255         return 3;\r
256       }\r
257     }\r
258   }\r
259   else if (error != ERROR_INVALID_LEVEL) {\r
260     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));\r
261     return 3;\r
262   }\r
263 \r
264   return 0;\r
265 }\r
266 \r
267 int get_service_username(const TCHAR *service_name, const QUERY_SERVICE_CONFIG *qsc, TCHAR **username, size_t *usernamelen) {\r
268   if (! username) return 1;\r
269   if (! usernamelen) return 1;\r
270 \r
271   *username = 0;\r
272   *usernamelen = 0;\r
273 \r
274   if (! qsc) return 1;\r
275 \r
276   if (str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;\r
277 \r
278   size_t len = _tcslen(qsc->lpServiceStartName);\r
279   *username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(TCHAR));\r
280   if (! *username) {\r
281     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("username"), _T("get_service_username()"));\r
282     return 2;\r
283   }\r
284 \r
285   memmove(*username, qsc->lpServiceStartName, (len + 1) * sizeof(TCHAR));\r
286   *usernamelen = len;\r
287 \r
288   return 0;\r
289 }\r
290 \r
291 int grant_logon_as_service(const TCHAR *username) {\r
292   if (! username) return 0;\r
293   if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;\r
294 \r
295   /* Open Policy object. */\r
296   LSA_OBJECT_ATTRIBUTES attributes;\r
297   ZeroMemory(&attributes, sizeof(attributes));\r
298 \r
299   LSA_HANDLE policy;\r
300 \r
301   NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, &policy);\r
302   if (status) {\r
303     print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status)));\r
304     return 1;\r
305   }\r
306 \r
307   /* Look up SID for the account. */\r
308   LSA_UNICODE_STRING lsa_username;\r
309 #ifdef UNICODE\r
310   lsa_username.Buffer = (wchar_t *) username;\r
311   lsa_username.Length = (unsigned short) _tcslen(username) * sizeof(TCHAR);\r
312   lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR);\r
313 #else\r
314   size_t buflen;\r
315   mbstowcs_s(&buflen, NULL, 0, username, _TRUNCATE);\r
316   lsa_username.MaximumLength = (unsigned short) buflen * sizeof(wchar_t);\r
317   lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t);\r
318   lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength);\r
319   if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, username, _TRUNCATE);\r
320   else {\r
321     LsaClose(policy);\r
322     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("grant_logon_as_service()"));\r
323     return 2;\r
324   }\r
325 #endif\r
326 \r
327   LSA_REFERENCED_DOMAIN_LIST *translated_domains;\r
328   LSA_TRANSLATED_SID *translated_sid;\r
329   status = LsaLookupNames(policy, 1, &lsa_username, &translated_domains, &translated_sid);\r
330 #ifndef UNICODE\r
331   HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);\r
332 #endif\r
333   if (status) {\r
334     LsaFreeMemory(translated_domains);\r
335     LsaFreeMemory(translated_sid);\r
336     LsaClose(policy);\r
337     print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status)));\r
338     return 3;\r
339   }\r
340 \r
341   if (translated_sid->Use != SidTypeUser) {\r
342     LsaFreeMemory(translated_domains);\r
343     LsaFreeMemory(translated_sid);\r
344     LsaClose(policy);\r
345     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
346     return 4;\r
347   }\r
348 \r
349   LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];\r
350   if (! trust || ! IsValidSid(trust->Sid)) {\r
351     LsaFreeMemory(translated_domains);\r
352     LsaFreeMemory(translated_sid);\r
353     LsaClose(policy);\r
354     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
355     return 4;\r
356   }\r
357 \r
358   /* GetSidSubAuthority*() return pointers! */\r
359   unsigned char *n = GetSidSubAuthorityCount(trust->Sid);\r
360 \r
361   /* Convert translated SID to SID. */\r
362   SID *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));\r
363   if (! sid) {\r
364     LsaFreeMemory(translated_domains);\r
365     LsaFreeMemory(translated_sid);\r
366     LsaClose(policy);\r
367     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("grant_logon_as_service"));\r
368     return 4;\r
369   }\r
370 \r
371   unsigned long error;\r
372   if (! InitializeSid(sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {\r
373     error = GetLastError();\r
374     HeapFree(GetProcessHeap(), 0, sid);\r
375     LsaFreeMemory(translated_domains);\r
376     LsaFreeMemory(translated_sid);\r
377     LsaClose(policy);\r
378     print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));\r
379     return 5;\r
380   }\r
381 \r
382   for (unsigned char i = 0; i <= *n; i++) {\r
383     unsigned long *sub = GetSidSubAuthority(sid, i);\r
384     if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);\r
385     else *sub = translated_sid->RelativeId;\r
386   }\r
387 \r
388   LsaFreeMemory(translated_domains);\r
389   LsaFreeMemory(translated_sid);\r
390 \r
391   /* Check if the SID has the "Log on as a service" right. */\r
392   LSA_UNICODE_STRING lsa_right;\r
393   lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;\r
394   lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);\r
395   lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);\r
396 \r
397   LSA_UNICODE_STRING *rights;\r
398   unsigned long count = ~0;\r
399   status = LsaEnumerateAccountRights(policy, sid, &rights, &count);\r
400   if (status) {\r
401     /*\r
402       If the account has no rights set LsaEnumerateAccountRights() will return\r
403       STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.\r
404     */\r
405     error = LsaNtStatusToWinError(status);\r
406     if (error != ERROR_FILE_NOT_FOUND) {\r
407       HeapFree(GetProcessHeap(), 0, sid);\r
408       LsaClose(policy);\r
409       print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));\r
410       return 4;\r
411     }\r
412   }\r
413 \r
414   for (unsigned long i = 0; i < count; i++) {\r
415     if (rights[i].Length != lsa_right.Length) continue;\r
416     if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;\r
417     /* The SID has the right. */\r
418     HeapFree(GetProcessHeap(), 0, sid);\r
419     LsaFreeMemory(rights);\r
420     LsaClose(policy);\r
421     return 0;\r
422   }\r
423   LsaFreeMemory(rights);\r
424 \r
425   /* Add the right. */\r
426   status = LsaAddAccountRights(policy, sid, &lsa_right, 1);\r
427   HeapFree(GetProcessHeap(), 0, sid);\r
428   LsaClose(policy);\r
429   if (status) {\r
430     print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));\r
431     return 5;\r
432   }\r
433 \r
434   print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);\r
435   return 0;\r
436 }\r
437 \r
438 /* Set default values which aren't zero. */\r
439 void set_nssm_service_defaults(nssm_service_t *service) {\r
440   if (! service) return;\r
441 \r
442   service->type = SERVICE_WIN32_OWN_PROCESS;\r
443   service->priority = NORMAL_PRIORITY_CLASS;\r
444   service->stdin_sharing = NSSM_STDIN_SHARING;\r
445   service->stdin_disposition = NSSM_STDIN_DISPOSITION;\r
446   service->stdin_flags = NSSM_STDIN_FLAGS;\r
447   service->stdout_sharing = NSSM_STDOUT_SHARING;\r
448   service->stdout_disposition = NSSM_STDOUT_DISPOSITION;\r
449   service->stdout_flags = NSSM_STDOUT_FLAGS;\r
450   service->stderr_sharing = NSSM_STDERR_SHARING;\r
451   service->stderr_disposition = NSSM_STDERR_DISPOSITION;\r
452   service->stderr_flags = NSSM_STDERR_FLAGS;\r
453   service->throttle_delay = NSSM_RESET_THROTTLE_RESTART;\r
454   service->stop_method = ~0;\r
455   service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
456   service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
457   service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
458 }\r
459 \r
460 /* Allocate and zero memory for a service. */\r
461 nssm_service_t *alloc_nssm_service() {\r
462   nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));\r
463   if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("alloc_nssm_service()"), 0);\r
464   return service;\r
465 }\r
466 \r
467 /* Free memory for a service. */\r
468 void cleanup_nssm_service(nssm_service_t *service) {\r
469   if (! service) return;\r
470   if (service->username) HeapFree(GetProcessHeap(), 0, service->username);\r
471   if (service->password) {\r
472     SecureZeroMemory(service->password, service->passwordlen);\r
473     HeapFree(GetProcessHeap(), 0, service->password);\r
474   }\r
475   if (service->env) HeapFree(GetProcessHeap(), 0, service->env);\r
476   if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);\r
477   if (service->handle) CloseServiceHandle(service->handle);\r
478   if (service->process_handle) CloseHandle(service->process_handle);\r
479   if (service->wait_handle) UnregisterWait(service->process_handle);\r
480   if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);\r
481   if (service->throttle_timer) CloseHandle(service->throttle_timer);\r
482   HeapFree(GetProcessHeap(), 0, service);\r
483 }\r
484 \r
485 /* About to install the service */\r
486 int pre_install_service(int argc, TCHAR **argv) {\r
487   nssm_service_t *service = alloc_nssm_service();\r
488   set_nssm_service_defaults(service);\r
489   if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
490 \r
491   /* Show the dialogue box if we didn't give the service name and path */\r
492   if (argc < 2) return nssm_gui(IDD_INSTALL, service);\r
493 \r
494   if (! service) {\r
495     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("service"), _T("pre_install_service()"));\r
496     return 1;\r
497   }\r
498   _sntprintf_s(service->exe, _countof(service->exe), _TRUNCATE, _T("%s"), argv[1]);\r
499 \r
500   /* Arguments are optional */\r
501   size_t flagslen = 0;\r
502   size_t s = 0;\r
503   int i;\r
504   for (i = 2; i < argc; i++) flagslen += _tcslen(argv[i]) + 1;\r
505   if (! flagslen) flagslen = 1;\r
506   if (flagslen > _countof(service->flags)) {\r
507     print_message(stderr, NSSM_MESSAGE_FLAGS_TOO_LONG);\r
508     return 2;\r
509   }\r
510 \r
511   for (i = 2; i < argc; i++) {\r
512     size_t len = _tcslen(argv[i]);\r
513     memmove(service->flags + s, argv[i], len * sizeof(TCHAR));\r
514     s += len;\r
515     if (i < argc - 1) service->flags[s++] = _T(' ');\r
516   }\r
517 \r
518   /* Work out directory name */\r
519   _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);\r
520   strip_basename(service->dir);\r
521 \r
522   int ret = install_service(service);\r
523   cleanup_nssm_service(service);\r
524   return ret;\r
525 }\r
526 \r
527 /* About to edit the service. */\r
528 int pre_edit_service(int argc, TCHAR **argv) {\r
529   /* Require service name. */\r
530   if (argc < 2) return usage(1);\r
531 \r
532   /* Are we editing on the command line? */\r
533   enum { MODE_EDITING, MODE_GETTING, MODE_SETTING, MODE_RESETTING } mode = MODE_EDITING;\r
534   const TCHAR *verb = argv[0];\r
535   const TCHAR *service_name = argv[1];\r
536   bool getting = false;\r
537   bool unsetting = false;\r
538 \r
539   /* Minimum number of arguments. */\r
540   int mandatory = 2;\r
541   /* Index of first value. */\r
542   int remainder = 3;\r
543   int i;\r
544   if (str_equiv(verb, _T("get"))) {\r
545     mandatory = 3;\r
546     mode = MODE_GETTING;\r
547   }\r
548   else if (str_equiv(verb, _T("set"))) {\r
549     mandatory = 4;\r
550     mode = MODE_SETTING;\r
551   }\r
552   else if (str_equiv(verb, _T("reset")) || str_equiv(verb, _T("unset"))) {\r
553     mandatory = 3;\r
554     mode = MODE_RESETTING;\r
555   }\r
556   if (argc < mandatory) return usage(1);\r
557 \r
558   const TCHAR *parameter = 0;\r
559   settings_t *setting = 0;\r
560   TCHAR *additional;\r
561 \r
562   /* Validate the parameter. */\r
563   if (mandatory > 2) {\r
564     bool additional_mandatory = false;\r
565 \r
566     parameter = argv[2];\r
567     for (i = 0; settings[i].name; i++) {\r
568       setting = &settings[i];\r
569       if (! str_equiv(setting->name, parameter)) continue;\r
570       if (((setting->additional & ADDITIONAL_GETTING) && mode == MODE_GETTING) || ((setting->additional & ADDITIONAL_SETTING) && mode == MODE_SETTING) || ((setting->additional & ADDITIONAL_RESETTING) && mode == MODE_RESETTING)) {\r
571         additional_mandatory = true;\r
572         mandatory++;\r
573       }\r
574       break;\r
575     }\r
576     if (! settings[i].name) {\r
577       print_message(stderr, NSSM_MESSAGE_INVALID_PARAMETER, parameter);\r
578       for (i = 0; settings[i].name; i++) _ftprintf(stderr, _T("%s\n"), settings[i].name);\r
579       return 1;\r
580     }\r
581     if (argc < mandatory) return usage(1);\r
582 \r
583     additional = 0;\r
584     if (additional_mandatory) {\r
585       additional = argv[3];\r
586       remainder = 4;\r
587     }\r
588     else additional = argv[remainder];\r
589   }\r
590 \r
591   nssm_service_t *service = alloc_nssm_service();\r
592   _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), service_name);\r
593 \r
594   /* Open service manager */\r
595   SC_HANDLE services = open_service_manager();\r
596   if (! services) {\r
597     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
598     return 2;\r
599   }\r
600 \r
601   /* Try to open the service */\r
602   service->handle = open_service(services, service->name, service->name, _countof(service->name));\r
603   if (! service->handle) {\r
604     CloseServiceHandle(services);\r
605     return 3;\r
606   }\r
607 \r
608   /* Get system details. */\r
609   QUERY_SERVICE_CONFIG *qsc = query_service_config(service->name, service->handle);\r
610   if (! qsc) {\r
611     CloseHandle(service->handle);\r
612     CloseServiceHandle(services);\r
613     return 4;\r
614   }\r
615 \r
616   service->type = qsc->dwServiceType;\r
617   if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {\r
618     if (mode != MODE_GETTING) {\r
619       HeapFree(GetProcessHeap(), 0, qsc);\r
620       CloseHandle(service->handle);\r
621       CloseServiceHandle(services);\r
622       print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, NSSM_WIN32_OWN_PROCESS, 0);\r
623       return 3;\r
624     }\r
625   }\r
626 \r
627   if (get_service_startup(service->name, service->handle, qsc, &service->startup)) {\r
628     if (mode != MODE_GETTING) {\r
629       HeapFree(GetProcessHeap(), 0, qsc);\r
630       CloseHandle(service->handle);\r
631       CloseServiceHandle(services);\r
632       return 4;\r
633     }\r
634   }\r
635 \r
636   if (get_service_username(service->name, qsc, &service->username, &service->usernamelen)) {\r
637     if (mode != MODE_GETTING) {\r
638       HeapFree(GetProcessHeap(), 0, qsc);\r
639       CloseHandle(service->handle);\r
640       CloseServiceHandle(services);\r
641       return 5;\r
642     }\r
643   }\r
644 \r
645   _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);\r
646 \r
647   /* Get the canonical service name. We open it case insensitively. */\r
648   unsigned long bufsize = _countof(service->name);\r
649   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
650 \r
651   /* Remember the executable in case it isn't NSSM. */\r
652   _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);\r
653   HeapFree(GetProcessHeap(), 0, qsc);\r
654 \r
655   /* Get extended system details. */\r
656   if (get_service_description(service->name, service->handle, _countof(service->description), service->description)) {\r
657     if (mode != MODE_GETTING) {\r
658       CloseHandle(service->handle);\r
659       CloseServiceHandle(services);\r
660       return 6;\r
661     }\r
662   }\r
663 \r
664   /* Get NSSM details. */\r
665   get_parameters(service, 0);\r
666 \r
667   CloseServiceHandle(services);\r
668 \r
669   if (! service->exe[0]) {\r
670     service->native = true;\r
671     if (mode != MODE_GETTING) print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);\r
672   }\r
673 \r
674   /* Editing with the GUI. */\r
675   if (mode == MODE_EDITING) {\r
676     nssm_gui(IDD_EDIT, service);\r
677     return 0;\r
678   }\r
679 \r
680   /* Trying to manage App* parameters for a non-NSSM service. */\r
681   if (! setting->native && service->native) {\r
682     CloseHandle(service->handle);\r
683     print_message(stderr, NSSM_MESSAGE_NATIVE_PARAMETER, setting->name, NSSM);\r
684     return 1;\r
685   }\r
686 \r
687   HKEY key;\r
688   value_t value;\r
689   int ret;\r
690 \r
691   if (mode == MODE_GETTING) {\r
692     if (! service->native) {\r
693       key = open_registry(service->name, KEY_READ);\r
694       if (! key) return 4;\r
695     }\r
696 \r
697     if (setting->native) ret = get_setting(service->name, service->handle, setting, &value, additional);\r
698     else ret = get_setting(service->name, key, setting, &value, additional);\r
699     if (ret < 0) {\r
700       CloseHandle(service->handle);\r
701       return 5;\r
702     }\r
703 \r
704     switch (setting->type) {\r
705       case REG_EXPAND_SZ:\r
706       case REG_MULTI_SZ:\r
707       case REG_SZ:\r
708         _tprintf(_T("%s\n"), value.string ? value.string : _T(""));\r
709         HeapFree(GetProcessHeap(), 0, value.string);\r
710         break;\r
711 \r
712       case REG_DWORD:\r
713         _tprintf(_T("%u\n"), value.numeric);\r
714         break;\r
715     }\r
716 \r
717     if (! service->native) RegCloseKey(key);\r
718     CloseHandle(service->handle);\r
719     return 0;\r
720   }\r
721 \r
722   /* Build the value. */\r
723   if (mode == MODE_RESETTING) {\r
724     /* Unset the parameter. */\r
725     value.string = 0;\r
726   }\r
727   else {\r
728     /* Set the parameter. */\r
729     size_t len = 0;\r
730     size_t delimiterlen = (setting->additional & ADDITIONAL_CRLF) ? 2 : 1;\r
731     for (i = remainder; i < argc; i++) len += _tcslen(argv[i]) + delimiterlen;\r
732     len++;\r
733 \r
734     value.string = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));\r
735     if (! value.string) {\r
736       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("value"), _T("edit_service()"));\r
737       CloseHandle(service->handle);\r
738       return 2;\r
739     }\r
740 \r
741     size_t s = 0;\r
742     for (i = remainder; i < argc; i++) {\r
743       size_t len = _tcslen(argv[i]);\r
744       memmove(value.string + s, argv[i], len * sizeof(TCHAR));\r
745       s += len;\r
746       if (i < argc - 1) {\r
747         if (setting->additional & ADDITIONAL_CRLF) {\r
748           value.string[s++] = _T('\r');\r
749           value.string[s++] = _T('\n');\r
750         }\r
751         else value.string[s++] = _T(' ');\r
752       }\r
753     }\r
754     value.string[s] = _T('\0');\r
755   }\r
756 \r
757   if (! service->native) {\r
758     key = open_registry(service->name, KEY_WRITE);\r
759     if (! key) {\r
760       if (value.string) HeapFree(GetProcessHeap(), 0, value.string);\r
761       return 4;\r
762     }\r
763   }\r
764 \r
765   if (setting->native) ret = set_setting(service->name, service->handle, setting, &value, additional);\r
766   else ret = set_setting(service->name, key, setting, &value, additional);\r
767   if (value.string) HeapFree(GetProcessHeap(), 0, value.string);\r
768   if (ret < 0) {\r
769     if (! service->native) RegCloseKey(key);\r
770     CloseHandle(service->handle);\r
771     return 6;\r
772   }\r
773 \r
774   if (! service->native) RegCloseKey(key);\r
775   CloseHandle(service->handle);\r
776 \r
777   return 0;\r
778 }\r
779 \r
780 /* About to remove the service */\r
781 int pre_remove_service(int argc, TCHAR **argv) {\r
782   nssm_service_t *service = alloc_nssm_service();\r
783   set_nssm_service_defaults(service);\r
784   if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
785 \r
786   /* Show dialogue box if we didn't pass service name and "confirm" */\r
787   if (argc < 2) return nssm_gui(IDD_REMOVE, service);\r
788   if (str_equiv(argv[1], _T("confirm"))) {\r
789     int ret = remove_service(service);\r
790     cleanup_nssm_service(service);\r
791     return ret;\r
792   }\r
793   print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);\r
794   return 100;\r
795 }\r
796 \r
797 /* Install the service */\r
798 int install_service(nssm_service_t *service) {\r
799   if (! service) return 1;\r
800 \r
801   /* Open service manager */\r
802   SC_HANDLE services = open_service_manager();\r
803   if (! services) {\r
804     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
805     cleanup_nssm_service(service);\r
806     return 2;\r
807   }\r
808 \r
809   /* Get path of this program */\r
810   GetModuleFileName(0, service->image, _countof(service->image));\r
811 \r
812   /* Create the service - settings will be changed in edit_service() */\r
813   service->handle = CreateService(services, service->name, service->name, SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, service->image, 0, 0, 0, 0, 0);\r
814   if (! service->handle) {\r
815     print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED, error_string(GetLastError()));\r
816     CloseServiceHandle(services);\r
817     return 5;\r
818   }\r
819 \r
820   if (edit_service(service, false)) {\r
821     DeleteService(service->handle);\r
822     CloseServiceHandle(services);\r
823     return 6;\r
824   }\r
825 \r
826   print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);\r
827 \r
828   /* Cleanup */\r
829   CloseServiceHandle(services);\r
830 \r
831   return 0;\r
832 }\r
833 \r
834 /* Edit the service. */\r
835 int edit_service(nssm_service_t *service, bool editing) {\r
836   if (! service) return 1;\r
837 \r
838   /*\r
839     The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS\r
840     and SERVICE_INTERACTIVE_PROCESS.\r
841   */\r
842   service->type &= SERVICE_INTERACTIVE_PROCESS;\r
843   service->type |= SERVICE_WIN32_OWN_PROCESS;\r
844 \r
845   /* Startup type. */\r
846   unsigned long startup;\r
847   switch (service->startup) {\r
848     case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;\r
849     case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;\r
850     default: startup = SERVICE_AUTO_START;\r
851   }\r
852 \r
853   /* Display name. */\r
854   if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);\r
855 \r
856   /*\r
857     Username must be NULL if we aren't changing or an account name.\r
858     We must explicitly use LOCALSYSTEM to change it when we are editing.\r
859     Password must be NULL if we aren't changing, a password or "".\r
860     Empty passwords are valid but we won't allow them in the GUI.\r
861   */\r
862   TCHAR *username = 0;\r
863   TCHAR *password = 0;\r
864   if (service->usernamelen) {\r
865     username = service->username;\r
866     if (service->passwordlen) password = service->password;\r
867     else password = _T("");\r
868   }\r
869   else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;\r
870 \r
871   if (grant_logon_as_service(username)) {\r
872     print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);\r
873     return 5;\r
874   }\r
875 \r
876   if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {\r
877     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));\r
878     return 5;\r
879   }\r
880 \r
881   if (service->description[0] || editing) {\r
882     set_service_description(service->name, service->handle, service->description);\r
883   }\r
884 \r
885   SERVICE_DELAYED_AUTO_START_INFO delayed;\r
886   ZeroMemory(&delayed, sizeof(delayed));\r
887   if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;\r
888   else delayed.fDelayedAutostart = 0;\r
889   /* Delayed startup isn't supported until Vista. */\r
890   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {\r
891     unsigned long error = GetLastError();\r
892     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
893     if (error != ERROR_INVALID_LEVEL) {\r
894       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);\r
895     }\r
896   }\r
897 \r
898   /* Don't mess with parameters which aren't ours. */\r
899   if (! service->native) {\r
900     /* Now we need to put the parameters into the registry */\r
901     if (create_parameters(service, editing)) {\r
902       print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);\r
903       return 6;\r
904     }\r
905 \r
906     set_service_recovery(service);\r
907   }\r
908 \r
909   return 0;\r
910 }\r
911 \r
912 /* Control a service. */\r
913 int control_service(unsigned long control, int argc, TCHAR **argv) {\r
914   if (argc < 1) return usage(1);\r
915   TCHAR *service_name = argv[0];\r
916   TCHAR canonical_name[SERVICE_NAME_LENGTH];\r
917 \r
918   SC_HANDLE services = open_service_manager();\r
919   if (! services) {\r
920     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
921     return 2;\r
922   }\r
923 \r
924   SC_HANDLE service_handle = open_service(services, service_name, canonical_name, _countof(canonical_name));\r
925   if (! service_handle) {\r
926     CloseServiceHandle(services);\r
927     return 3;\r
928   }\r
929 \r
930   int ret;\r
931   unsigned long error;\r
932   SERVICE_STATUS service_status;\r
933   if (control == 0) {\r
934     ret = StartService(service_handle, (unsigned long) argc, (const TCHAR **) argv);\r
935     error = GetLastError();\r
936     CloseHandle(service_handle);\r
937     CloseServiceHandle(services);\r
938 \r
939     if (error == ERROR_IO_PENDING) {\r
940       /*\r
941         Older versions of Windows return immediately with ERROR_IO_PENDING\r
942         indicate that the operation is still in progress.  Newer versions\r
943         will return it if there really is a delay.  As far as we're\r
944         concerned the operation is a success.  We don't claim to offer a\r
945         fully-feature service control method; it's just a quick 'n' dirty\r
946         interface.\r
947 \r
948         In the future we may identify and handle this situation properly.\r
949       */\r
950       ret = 1;\r
951       error = ERROR_SUCCESS;\r
952     }\r
953 \r
954     if (ret) {\r
955       _tprintf(_T("%s: %s"), canonical_name, error_string(error));\r
956       return 0;\r
957     }\r
958     else {\r
959       _ftprintf(stderr, _T("%s: %s"), canonical_name, error_string(error));\r
960       return 1;\r
961     }\r
962   }\r
963   else if (control == SERVICE_CONTROL_INTERROGATE) {\r
964     /*\r
965       We could actually send an INTERROGATE control but that won't return\r
966       any information if the service is stopped and we don't care about\r
967       the extra details it might give us in any case.  So we'll fake it.\r
968     */\r
969     ret = QueryServiceStatus(service_handle, &service_status);\r
970     error = GetLastError();\r
971 \r
972     if (ret) {\r
973       switch (service_status.dwCurrentState) {\r
974         case SERVICE_STOPPED: _tprintf(_T("SERVICE_STOPPED\n")); break;\r
975         case SERVICE_START_PENDING: _tprintf(_T("SERVICE_START_PENDING\n")); break;\r
976         case SERVICE_STOP_PENDING: _tprintf(_T("SERVICE_STOP_PENDING\n")); break;\r
977         case SERVICE_RUNNING: _tprintf(_T("SERVICE_RUNNING\n")); break;\r
978         case SERVICE_CONTINUE_PENDING: _tprintf(_T("SERVICE_CONTINUE_PENDING\n")); break;\r
979         case SERVICE_PAUSE_PENDING: _tprintf(_T("SERVICE_PAUSE_PENDING\n")); break;\r
980         case SERVICE_PAUSED: _tprintf(_T("SERVICE_PAUSED\n")); break;\r
981         default: _tprintf(_T("?\n")); return 1;\r
982       }\r
983       return 0;\r
984     }\r
985     else {\r
986       _ftprintf(stderr, _T("%s: %s\n"), canonical_name, error_string(error));\r
987       return 1;\r
988     }\r
989   }\r
990   else {\r
991     ret = ControlService(service_handle, control, &service_status);\r
992     error = GetLastError();\r
993     CloseHandle(service_handle);\r
994     CloseServiceHandle(services);\r
995 \r
996     if (error == ERROR_IO_PENDING) {\r
997       ret = 1;\r
998       error = ERROR_SUCCESS;\r
999     }\r
1000 \r
1001     if (ret) {\r
1002       _tprintf(_T("%s: %s"), canonical_name, error_string(error));\r
1003       return 0;\r
1004     }\r
1005     else {\r
1006       _ftprintf(stderr, _T("%s: %s"), canonical_name, error_string(error));\r
1007       return 1;\r
1008     }\r
1009   }\r
1010 }\r
1011 \r
1012 /* Remove the service */\r
1013 int remove_service(nssm_service_t *service) {\r
1014   if (! service) return 1;\r
1015 \r
1016   /* Open service manager */\r
1017   SC_HANDLE services = open_service_manager();\r
1018   if (! services) {\r
1019     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
1020     return 2;\r
1021   }\r
1022 \r
1023   /* Try to open the service */\r
1024   service->handle = open_service(services, service->name, service->name, _countof(service->name));\r
1025   if (! service->handle) {\r
1026     CloseServiceHandle(services);\r
1027     return 3;\r
1028   }\r
1029 \r
1030   /* Get the canonical service name. We open it case insensitively. */\r
1031   unsigned long bufsize = _countof(service->displayname);\r
1032   GetServiceDisplayName(services, service->name, service->displayname, &bufsize);\r
1033   bufsize = _countof(service->name);\r
1034   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
1035 \r
1036   /* Try to delete the service */\r
1037   if (! DeleteService(service->handle)) {\r
1038     print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);\r
1039     CloseServiceHandle(services);\r
1040     return 4;\r
1041   }\r
1042 \r
1043   /* Cleanup */\r
1044   CloseServiceHandle(services);\r
1045 \r
1046   print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);\r
1047   return 0;\r
1048 }\r
1049 \r
1050 /* Service initialisation */\r
1051 void WINAPI service_main(unsigned long argc, TCHAR **argv) {\r
1052   nssm_service_t *service = alloc_nssm_service();\r
1053   if (! service) return;\r
1054 \r
1055   if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {\r
1056     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);\r
1057     return;\r
1058   }\r
1059 \r
1060   /* We can use a condition variable in a critical section on Vista or later. */\r
1061   if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;\r
1062   else use_critical_section = false;\r
1063 \r
1064   /* Initialise status */\r
1065   ZeroMemory(&service->status, sizeof(service->status));\r
1066   service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
1067   service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;\r
1068   service->status.dwWin32ExitCode = NO_ERROR;\r
1069   service->status.dwServiceSpecificExitCode = 0;\r
1070   service->status.dwCheckPoint = 0;\r
1071   service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
1072 \r
1073   /* Signal we AREN'T running the server */\r
1074   service->process_handle = 0;\r
1075   service->pid = 0;\r
1076 \r
1077   /* Register control handler */\r
1078   service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);\r
1079   if (! service->status_handle) {\r
1080     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);\r
1081     return;\r
1082   }\r
1083 \r
1084   log_service_control(service->name, 0, true);\r
1085 \r
1086   service->status.dwCurrentState = SERVICE_START_PENDING;\r
1087   service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;\r
1088   SetServiceStatus(service->status_handle, &service->status);\r
1089 \r
1090   if (is_admin) {\r
1091     /* Try to create the exit action parameters; we don't care if it fails */\r
1092     create_exit_action(service->name, exit_action_strings[0], false);\r
1093 \r
1094     SC_HANDLE services = open_service_manager();\r
1095     if (services) {\r
1096       service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
1097       set_service_recovery(service);\r
1098       CloseServiceHandle(services);\r
1099     }\r
1100   }\r
1101 \r
1102   /* Used for signalling a resume if the service pauses when throttled. */\r
1103   if (use_critical_section) {\r
1104     InitializeCriticalSection(&service->throttle_section);\r
1105     service->throttle_section_initialised = true;\r
1106   }\r
1107   else {\r
1108     service->throttle_timer = CreateWaitableTimer(0, 1, 0);\r
1109     if (! service->throttle_timer) {\r
1110       log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);\r
1111     }\r
1112   }\r
1113 \r
1114   monitor_service(service);\r
1115 }\r
1116 \r
1117 /* Make sure service recovery actions are taken where necessary */\r
1118 void set_service_recovery(nssm_service_t *service) {\r
1119   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
1120   ZeroMemory(&flag, sizeof(flag));\r
1121   flag.fFailureActionsOnNonCrashFailures = true;\r
1122 \r
1123   /* This functionality was added in Vista so the call may fail */\r
1124   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {\r
1125     unsigned long error = GetLastError();\r
1126     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
1127     if (error != ERROR_INVALID_LEVEL) {\r
1128       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);\r
1129     }\r
1130   }\r
1131 }\r
1132 \r
1133 int monitor_service(nssm_service_t *service) {\r
1134   /* Set service status to started */\r
1135   int ret = start_service(service);\r
1136   if (ret) {\r
1137     TCHAR code[16];\r
1138     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);\r
1139     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);\r
1140     return ret;\r
1141   }\r
1142   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);\r
1143 \r
1144   /* Monitor service */\r
1145   if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
1146     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);\r
1147   }\r
1148 \r
1149   return 0;\r
1150 }\r
1151 \r
1152 TCHAR *service_control_text(unsigned long control) {\r
1153   switch (control) {\r
1154     /* HACK: there is no SERVICE_CONTROL_START constant */\r
1155     case 0: return _T("START");\r
1156     case SERVICE_CONTROL_STOP: return _T("STOP");\r
1157     case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");\r
1158     case SERVICE_CONTROL_PAUSE: return _T("PAUSE");\r
1159     case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");\r
1160     case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");\r
1161     default: return 0;\r
1162   }\r
1163 }\r
1164 \r
1165 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {\r
1166   TCHAR *text = service_control_text(control);\r
1167   unsigned long event;\r
1168 \r
1169   if (! text) {\r
1170     /* "0x" + 8 x hex + NULL */\r
1171     text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));\r
1172     if (! text) {\r
1173       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
1174       return;\r
1175     }\r
1176     if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {\r
1177       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
1178       HeapFree(GetProcessHeap(), 0, text);\r
1179       return;\r
1180     }\r
1181 \r
1182     event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;\r
1183   }\r
1184   else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;\r
1185   else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;\r
1186 \r
1187   log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);\r
1188 \r
1189   if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {\r
1190     HeapFree(GetProcessHeap(), 0, text);\r
1191   }\r
1192 }\r
1193 \r
1194 /* Service control handler */\r
1195 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
1196   nssm_service_t *service = (nssm_service_t *) context;\r
1197 \r
1198   switch (control) {\r
1199     case SERVICE_CONTROL_INTERROGATE:\r
1200       /* We always keep the service status up-to-date so this is a no-op. */\r
1201       return NO_ERROR;\r
1202 \r
1203     case SERVICE_CONTROL_SHUTDOWN:\r
1204     case SERVICE_CONTROL_STOP:\r
1205       log_service_control(service->name, control, true);\r
1206       /*\r
1207         We MUST acknowledge the stop request promptly but we're committed to\r
1208         waiting for the application to exit.  Spawn a new thread to wait\r
1209         while we acknowledge the request.\r
1210       */\r
1211       if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {\r
1212         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
1213 \r
1214         /*\r
1215           We couldn't create a thread to tidy up so we'll have to force the tidyup\r
1216           to complete in time in this thread.\r
1217         */\r
1218         service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
1219         service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
1220         service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
1221 \r
1222         stop_service(service, 0, true, true);\r
1223       }\r
1224       return NO_ERROR;\r
1225 \r
1226     case SERVICE_CONTROL_CONTINUE:\r
1227       log_service_control(service->name, control, true);\r
1228       service->throttle = 0;\r
1229       if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);\r
1230       else {\r
1231         if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
1232         ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1233         SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1234       }\r
1235       service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
1236       service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;\r
1237       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);\r
1238       SetServiceStatus(service->status_handle, &service->status);\r
1239       return NO_ERROR;\r
1240 \r
1241     case SERVICE_CONTROL_PAUSE:\r
1242       /*\r
1243         We don't accept pause messages but it isn't possible to register\r
1244         only for continue messages so we have to handle this case.\r
1245       */\r
1246       log_service_control(service->name, control, false);\r
1247       return ERROR_CALL_NOT_IMPLEMENTED;\r
1248   }\r
1249 \r
1250   /* Unknown control */\r
1251   log_service_control(service->name, control, false);\r
1252   return ERROR_CALL_NOT_IMPLEMENTED;\r
1253 }\r
1254 \r
1255 /* Start the service */\r
1256 int start_service(nssm_service_t *service) {\r
1257   service->stopping = false;\r
1258   service->allow_restart = true;\r
1259 \r
1260   if (service->process_handle) return 0;\r
1261 \r
1262   /* Allocate a STARTUPINFO structure for a new process */\r
1263   STARTUPINFO si;\r
1264   ZeroMemory(&si, sizeof(si));\r
1265   si.cb = sizeof(si);\r
1266 \r
1267   /* Allocate a PROCESSINFO structure for the process */\r
1268   PROCESS_INFORMATION pi;\r
1269   ZeroMemory(&pi, sizeof(pi));\r
1270 \r
1271   /* Get startup parameters */\r
1272   int ret = get_parameters(service, &si);\r
1273   if (ret) {\r
1274     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);\r
1275     return stop_service(service, 2, true, true);\r
1276   }\r
1277 \r
1278   /* Launch executable with arguments */\r
1279   TCHAR cmd[CMD_LENGTH];\r
1280   if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {\r
1281     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);\r
1282     close_output_handles(&si);\r
1283     return stop_service(service, 2, true, true);\r
1284   }\r
1285 \r
1286   throttle_restart(service);\r
1287 \r
1288   bool inherit_handles = false;\r
1289   if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
1290   unsigned long flags = service->priority & priority_mask();\r
1291 #ifdef UNICODE\r
1292   flags |= CREATE_UNICODE_ENVIRONMENT;\r
1293 #endif\r
1294   if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {\r
1295     unsigned long exitcode = 3;\r
1296     unsigned long error = GetLastError();\r
1297     if (error == ERROR_INVALID_PARAMETER && service->env) {\r
1298       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);\r
1299       if (test_environment(service->env)) exitcode = 4;\r
1300     }\r
1301     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
1302     close_output_handles(&si);\r
1303     return stop_service(service, exitcode, true, true);\r
1304   }\r
1305   service->process_handle = pi.hProcess;\r
1306   service->pid = pi.dwProcessId;\r
1307 \r
1308   if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
1309 \r
1310   close_output_handles(&si);\r
1311 \r
1312   /*\r
1313     Wait for a clean startup before changing the service status to RUNNING\r
1314     but be mindful of the fact that we are blocking the service control manager\r
1315     so abandon the wait before too much time has elapsed.\r
1316   */\r
1317   unsigned long delay = service->throttle_delay;\r
1318   if (delay > NSSM_SERVICE_STATUS_DEADLINE) {\r
1319     TCHAR delay_milliseconds[16];\r
1320     _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);\r
1321     TCHAR deadline_milliseconds[16];\r
1322     _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);\r
1323     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);\r
1324     delay = NSSM_SERVICE_STATUS_DEADLINE;\r
1325   }\r
1326   unsigned long deadline = WaitForSingleObject(service->process_handle, delay);\r
1327 \r
1328   /* Signal successful start */\r
1329   service->status.dwCurrentState = SERVICE_RUNNING;\r
1330   SetServiceStatus(service->status_handle, &service->status);\r
1331 \r
1332   /* Continue waiting for a clean startup. */\r
1333   if (deadline == WAIT_TIMEOUT) {\r
1334     if (service->throttle_delay > delay) {\r
1335       if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;\r
1336     }\r
1337     else service->throttle = 0;\r
1338   }\r
1339 \r
1340   return 0;\r
1341 }\r
1342 \r
1343 /* Stop the service */\r
1344 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {\r
1345   service->allow_restart = false;\r
1346   if (service->wait_handle) {\r
1347     UnregisterWait(service->wait_handle);\r
1348     service->wait_handle = 0;\r
1349   }\r
1350 \r
1351   if (default_action && ! exitcode && ! graceful) {\r
1352     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
1353     graceful = true;\r
1354   }\r
1355 \r
1356   /* Signal we are stopping */\r
1357   if (graceful) {\r
1358     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1359     service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
1360     SetServiceStatus(service->status_handle, &service->status);\r
1361   }\r
1362 \r
1363   /* Nothing to do if service isn't running */\r
1364   if (service->pid) {\r
1365     /* Shut down service */\r
1366     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);\r
1367     kill_process(service, service->process_handle, service->pid, 0);\r
1368   }\r
1369   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);\r
1370 \r
1371   end_service((void *) service, true);\r
1372 \r
1373   /* Signal we stopped */\r
1374   if (graceful) {\r
1375     service->status.dwCurrentState = SERVICE_STOPPED;\r
1376     if (exitcode) {\r
1377       service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
1378       service->status.dwServiceSpecificExitCode = exitcode;\r
1379     }\r
1380     else {\r
1381       service->status.dwWin32ExitCode = NO_ERROR;\r
1382       service->status.dwServiceSpecificExitCode = 0;\r
1383     }\r
1384     SetServiceStatus(service->status_handle, &service->status);\r
1385   }\r
1386 \r
1387   return exitcode;\r
1388 }\r
1389 \r
1390 /* Callback function triggered when the server exits */\r
1391 void CALLBACK end_service(void *arg, unsigned char why) {\r
1392   nssm_service_t *service = (nssm_service_t *) arg;\r
1393 \r
1394   if (service->stopping) return;\r
1395 \r
1396   service->stopping = true;\r
1397 \r
1398   /* Check exit code */\r
1399   unsigned long exitcode = 0;\r
1400   TCHAR code[16];\r
1401   if (service->process_handle) {\r
1402     GetExitCodeProcess(service->process_handle, &exitcode);\r
1403     if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);\r
1404     CloseHandle(service->process_handle);\r
1405   }\r
1406   else GetSystemTimeAsFileTime(&service->exit_time);\r
1407 \r
1408   service->process_handle = 0;\r
1409 \r
1410   /*\r
1411     Log that the service ended BEFORE logging about killing the process\r
1412     tree.  See below for the possible values of the why argument.\r
1413   */\r
1414   if (! why) {\r
1415     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);\r
1416     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);\r
1417   }\r
1418 \r
1419   /* Clean up. */\r
1420   if (exitcode == STILL_ACTIVE) exitcode = 0;\r
1421   if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);\r
1422   service->pid = 0;\r
1423 \r
1424   /*\r
1425     The why argument is true if our wait timed out or false otherwise.\r
1426     Our wait is infinite so why will never be true when called by the system.\r
1427     If it is indeed true, assume we were called from stop_service() because\r
1428     this is a controlled shutdown, and don't take any restart action.\r
1429   */\r
1430   if (why) return;\r
1431   if (! service->allow_restart) return;\r
1432 \r
1433   /* What action should we take? */\r
1434   int action = NSSM_EXIT_RESTART;\r
1435   TCHAR action_string[ACTION_LEN];\r
1436   bool default_action;\r
1437   if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {\r
1438     for (int i = 0; exit_action_strings[i]; i++) {\r
1439       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
1440         action = i;\r
1441         break;\r
1442       }\r
1443     }\r
1444   }\r
1445 \r
1446   switch (action) {\r
1447     /* Try to restart the service or return failure code to service manager */\r
1448     case NSSM_EXIT_RESTART:\r
1449       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);\r
1450       while (monitor_service(service)) {\r
1451         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);\r
1452         Sleep(30000);\r
1453       }\r
1454     break;\r
1455 \r
1456     /* Do nothing, just like srvany would */\r
1457     case NSSM_EXIT_IGNORE:\r
1458       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
1459       Sleep(INFINITE);\r
1460     break;\r
1461 \r
1462     /* Tell the service manager we are finished */\r
1463     case NSSM_EXIT_REALLY:\r
1464       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);\r
1465       stop_service(service, exitcode, true, default_action);\r
1466     break;\r
1467 \r
1468     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
1469     case NSSM_EXIT_UNCLEAN:\r
1470       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
1471       stop_service(service, exitcode, false, default_action);\r
1472       free_imports();\r
1473       exit(exitcode);\r
1474     break;\r
1475   }\r
1476 }\r
1477 \r
1478 void throttle_restart(nssm_service_t *service) {\r
1479   /* This can't be a restart if the service is already running. */\r
1480   if (! service->throttle++) return;\r
1481 \r
1482   int ms = throttle_milliseconds(service->throttle);\r
1483 \r
1484   if (service->throttle > 7) service->throttle = 8;\r
1485 \r
1486   TCHAR threshold[8], milliseconds[8];\r
1487   _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);\r
1488   _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
1489   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);\r
1490 \r
1491   if (use_critical_section) EnterCriticalSection(&service->throttle_section);\r
1492   else if (service->throttle_timer) {\r
1493     ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1494     service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
1495     SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1496   }\r
1497 \r
1498   service->status.dwCurrentState = SERVICE_PAUSED;\r
1499   SetServiceStatus(service->status_handle, &service->status);\r
1500 \r
1501   if (use_critical_section) {\r
1502     imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);\r
1503     LeaveCriticalSection(&service->throttle_section);\r
1504   }\r
1505   else {\r
1506     if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);\r
1507     else Sleep(ms);\r
1508   }\r
1509 }\r
1510 \r
1511 /*\r
1512   When responding to a stop (or any other) request we need to set dwWaitHint to\r
1513   the number of milliseconds we expect the operation to take, and optionally\r
1514   increase dwCheckPoint.  If dwWaitHint milliseconds elapses without the\r
1515   operation completing or dwCheckPoint increasing, the system will consider the\r
1516   service to be hung.\r
1517 \r
1518   However the system will consider the service to be hung after 30000\r
1519   milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not\r
1520   changed.  Therefore if we want to wait longer than that we must periodically\r
1521   increase dwCheckPoint.\r
1522 \r
1523   Furthermore, it will consider the service to be hung after 60000 milliseconds\r
1524   regardless of the value of dwCheckPoint unless dwWaitHint is increased every\r
1525   time dwCheckPoint is also increased.\r
1526 \r
1527   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
1528   NSSM_SERVICE_STATUS_DEADLINE milliseconds.  If the process is still running\r
1529   and we haven't finished waiting we increment dwCheckPoint and add whichever is\r
1530   smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to\r
1531   dwWaitHint.\r
1532 \r
1533   Only doing both these things will prevent the system from killing the service.\r
1534 \r
1535   Returns: 1 if the wait timed out.\r
1536            0 if the wait completed.\r
1537           -1 on error.\r
1538 */\r
1539 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {\r
1540   unsigned long interval;\r
1541   unsigned long waithint;\r
1542   unsigned long ret;\r
1543   unsigned long waited;\r
1544   TCHAR interval_milliseconds[16];\r
1545   TCHAR timeout_milliseconds[16];\r
1546   TCHAR waited_milliseconds[16];\r
1547   TCHAR *function = function_name;\r
1548 \r
1549   /* Add brackets to function name. */\r
1550   size_t funclen = _tcslen(function_name) + 3;\r
1551   TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));\r
1552   if (func) {\r
1553     if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;\r
1554   }\r
1555 \r
1556   _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);\r
1557 \r
1558   waithint = service->status.dwWaitHint;\r
1559   waited = 0;\r
1560   while (waited < timeout) {\r
1561     interval = timeout - waited;\r
1562     if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
1563 \r
1564     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1565     service->status.dwWaitHint += interval;\r
1566     service->status.dwCheckPoint++;\r
1567     SetServiceStatus(service->status_handle, &service->status);\r
1568 \r
1569     if (waited) {\r
1570       _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);\r
1571       _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);\r
1572       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
1573     }\r
1574 \r
1575     switch (WaitForSingleObject(service->process_handle, interval)) {\r
1576       case WAIT_OBJECT_0:\r
1577         ret = 0;\r
1578         goto awaited;\r
1579 \r
1580       case WAIT_TIMEOUT:\r
1581         ret = 1;\r
1582       break;\r
1583 \r
1584       default:\r
1585         ret = -1;\r
1586         goto awaited;\r
1587     }\r
1588 \r
1589     waited += interval;\r
1590   }\r
1591 \r
1592 awaited:\r
1593   if (func) HeapFree(GetProcessHeap(), 0, func);\r
1594 \r
1595   return ret;\r
1596 }\r