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