Allow setting processor affinity.
[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 int throttle_milliseconds(unsigned long throttle) {\r
166   /* pow() operates on doubles. */\r
167   int 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     default: return 0;\r
1285   }\r
1286 }\r
1287 \r
1288 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {\r
1289   TCHAR *text = service_control_text(control);\r
1290   unsigned long event;\r
1291 \r
1292   if (! text) {\r
1293     /* "0x" + 8 x hex + NULL */\r
1294     text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));\r
1295     if (! text) {\r
1296       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
1297       return;\r
1298     }\r
1299     if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {\r
1300       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
1301       HeapFree(GetProcessHeap(), 0, text);\r
1302       return;\r
1303     }\r
1304 \r
1305     event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;\r
1306   }\r
1307   else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;\r
1308   else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;\r
1309 \r
1310   log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);\r
1311 \r
1312   if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {\r
1313     HeapFree(GetProcessHeap(), 0, text);\r
1314   }\r
1315 }\r
1316 \r
1317 /* Service control handler */\r
1318 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
1319   nssm_service_t *service = (nssm_service_t *) context;\r
1320 \r
1321   switch (control) {\r
1322     case SERVICE_CONTROL_INTERROGATE:\r
1323       /* We always keep the service status up-to-date so this is a no-op. */\r
1324       return NO_ERROR;\r
1325 \r
1326     case SERVICE_CONTROL_SHUTDOWN:\r
1327     case SERVICE_CONTROL_STOP:\r
1328       log_service_control(service->name, control, true);\r
1329       /*\r
1330         We MUST acknowledge the stop request promptly but we're committed to\r
1331         waiting for the application to exit.  Spawn a new thread to wait\r
1332         while we acknowledge the request.\r
1333       */\r
1334       if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {\r
1335         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
1336 \r
1337         /*\r
1338           We couldn't create a thread to tidy up so we'll have to force the tidyup\r
1339           to complete in time in this thread.\r
1340         */\r
1341         service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
1342         service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
1343         service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
1344 \r
1345         stop_service(service, 0, true, true);\r
1346       }\r
1347       return NO_ERROR;\r
1348 \r
1349     case SERVICE_CONTROL_CONTINUE:\r
1350       log_service_control(service->name, control, true);\r
1351       service->throttle = 0;\r
1352       if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);\r
1353       else {\r
1354         if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
1355         ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1356         SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1357       }\r
1358       service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
1359       service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;\r
1360       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);\r
1361       SetServiceStatus(service->status_handle, &service->status);\r
1362       return NO_ERROR;\r
1363 \r
1364     case SERVICE_CONTROL_PAUSE:\r
1365       /*\r
1366         We don't accept pause messages but it isn't possible to register\r
1367         only for continue messages so we have to handle this case.\r
1368       */\r
1369       log_service_control(service->name, control, false);\r
1370       return ERROR_CALL_NOT_IMPLEMENTED;\r
1371   }\r
1372 \r
1373   /* Unknown control */\r
1374   log_service_control(service->name, control, false);\r
1375   return ERROR_CALL_NOT_IMPLEMENTED;\r
1376 }\r
1377 \r
1378 /* Start the service */\r
1379 int start_service(nssm_service_t *service) {\r
1380   service->stopping = false;\r
1381   service->allow_restart = true;\r
1382 \r
1383   if (service->process_handle) return 0;\r
1384 \r
1385   /* Allocate a STARTUPINFO structure for a new process */\r
1386   STARTUPINFO si;\r
1387   ZeroMemory(&si, sizeof(si));\r
1388   si.cb = sizeof(si);\r
1389 \r
1390   /* Allocate a PROCESSINFO structure for the process */\r
1391   PROCESS_INFORMATION pi;\r
1392   ZeroMemory(&pi, sizeof(pi));\r
1393 \r
1394   /* Get startup parameters */\r
1395   int ret = get_parameters(service, &si);\r
1396   if (ret) {\r
1397     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);\r
1398     return stop_service(service, 2, true, true);\r
1399   }\r
1400 \r
1401   /* Launch executable with arguments */\r
1402   TCHAR cmd[CMD_LENGTH];\r
1403   if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {\r
1404     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);\r
1405     close_output_handles(&si);\r
1406     return stop_service(service, 2, true, true);\r
1407   }\r
1408 \r
1409   throttle_restart(service);\r
1410 \r
1411   bool inherit_handles = false;\r
1412   if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
1413   unsigned long flags = service->priority & priority_mask();\r
1414   if (service->affinity) flags |= CREATE_SUSPENDED;\r
1415 #ifdef UNICODE\r
1416   flags |= CREATE_UNICODE_ENVIRONMENT;\r
1417 #endif\r
1418   if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {\r
1419     unsigned long exitcode = 3;\r
1420     unsigned long error = GetLastError();\r
1421     if (error == ERROR_INVALID_PARAMETER && service->env) {\r
1422       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);\r
1423       if (test_environment(service->env)) exitcode = 4;\r
1424     }\r
1425     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
1426     close_output_handles(&si);\r
1427     return stop_service(service, exitcode, true, true);\r
1428   }\r
1429   service->process_handle = pi.hProcess;\r
1430   service->pid = pi.dwProcessId;\r
1431 \r
1432   if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
1433 \r
1434   close_output_handles(&si);\r
1435 \r
1436   if (service->affinity) {\r
1437     /*\r
1438       We are explicitly storing service->affinity as a 64-bit unsigned integer\r
1439       so that we can parse it regardless of whether we're running in 32-bit\r
1440       or 64-bit mode.  The arguments to SetProcessAffinityMask(), however, are\r
1441       defined as type DWORD_PTR and hence limited to 32 bits on a 32-bit system\r
1442       (or when running the 32-bit NSSM).\r
1443 \r
1444       The result is a lot of seemingly-unnecessary casting throughout the code\r
1445       and potentially confusion when we actually try to start the service.\r
1446       Having said that, however, it's unlikely that we're actually going to\r
1447       run in 32-bit mode on a system which has more than 32 CPUs so the\r
1448       likelihood of seeing a confusing situation is somewhat diminished.\r
1449     */\r
1450     DWORD_PTR affinity, system_affinity;\r
1451 \r
1452     if (GetProcessAffinityMask(service->process_handle, &affinity, &system_affinity)) affinity = service->affinity & system_affinity;\r
1453     else {\r
1454       affinity = (DWORD_PTR) service->affinity;\r
1455       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
1456     }\r
1457 \r
1458     if (! SetProcessAffinityMask(service->process_handle, affinity)) {\r
1459       log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
1460     }\r
1461 \r
1462     ResumeThread(pi.hThread);\r
1463   }\r
1464 \r
1465   /*\r
1466     Wait for a clean startup before changing the service status to RUNNING\r
1467     but be mindful of the fact that we are blocking the service control manager\r
1468     so abandon the wait before too much time has elapsed.\r
1469   */\r
1470   unsigned long delay = service->throttle_delay;\r
1471   if (delay > NSSM_SERVICE_STATUS_DEADLINE) {\r
1472     TCHAR delay_milliseconds[16];\r
1473     _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);\r
1474     TCHAR deadline_milliseconds[16];\r
1475     _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);\r
1476     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);\r
1477     delay = NSSM_SERVICE_STATUS_DEADLINE;\r
1478   }\r
1479   unsigned long deadline = WaitForSingleObject(service->process_handle, delay);\r
1480 \r
1481   /* Signal successful start */\r
1482   service->status.dwCurrentState = SERVICE_RUNNING;\r
1483   SetServiceStatus(service->status_handle, &service->status);\r
1484 \r
1485   /* Continue waiting for a clean startup. */\r
1486   if (deadline == WAIT_TIMEOUT) {\r
1487     if (service->throttle_delay > delay) {\r
1488       if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;\r
1489     }\r
1490     else service->throttle = 0;\r
1491   }\r
1492 \r
1493   return 0;\r
1494 }\r
1495 \r
1496 /* Stop the service */\r
1497 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {\r
1498   service->allow_restart = false;\r
1499   if (service->wait_handle) {\r
1500     UnregisterWait(service->wait_handle);\r
1501     service->wait_handle = 0;\r
1502   }\r
1503 \r
1504   if (default_action && ! exitcode && ! graceful) {\r
1505     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
1506     graceful = true;\r
1507   }\r
1508 \r
1509   /* Signal we are stopping */\r
1510   if (graceful) {\r
1511     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1512     service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
1513     SetServiceStatus(service->status_handle, &service->status);\r
1514   }\r
1515 \r
1516   /* Nothing to do if service isn't running */\r
1517   if (service->pid) {\r
1518     /* Shut down service */\r
1519     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);\r
1520     kill_process(service, service->process_handle, service->pid, 0);\r
1521   }\r
1522   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);\r
1523 \r
1524   end_service((void *) service, true);\r
1525 \r
1526   /* Signal we stopped */\r
1527   if (graceful) {\r
1528     service->status.dwCurrentState = SERVICE_STOPPED;\r
1529     if (exitcode) {\r
1530       service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
1531       service->status.dwServiceSpecificExitCode = exitcode;\r
1532     }\r
1533     else {\r
1534       service->status.dwWin32ExitCode = NO_ERROR;\r
1535       service->status.dwServiceSpecificExitCode = 0;\r
1536     }\r
1537     SetServiceStatus(service->status_handle, &service->status);\r
1538   }\r
1539 \r
1540   return exitcode;\r
1541 }\r
1542 \r
1543 /* Callback function triggered when the server exits */\r
1544 void CALLBACK end_service(void *arg, unsigned char why) {\r
1545   nssm_service_t *service = (nssm_service_t *) arg;\r
1546 \r
1547   if (service->stopping) return;\r
1548 \r
1549   service->stopping = true;\r
1550 \r
1551   /* Check exit code */\r
1552   unsigned long exitcode = 0;\r
1553   TCHAR code[16];\r
1554   if (service->process_handle) {\r
1555     GetExitCodeProcess(service->process_handle, &exitcode);\r
1556     if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);\r
1557     CloseHandle(service->process_handle);\r
1558   }\r
1559   else GetSystemTimeAsFileTime(&service->exit_time);\r
1560 \r
1561   service->process_handle = 0;\r
1562 \r
1563   /*\r
1564     Log that the service ended BEFORE logging about killing the process\r
1565     tree.  See below for the possible values of the why argument.\r
1566   */\r
1567   if (! why) {\r
1568     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);\r
1569     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);\r
1570   }\r
1571 \r
1572   /* Clean up. */\r
1573   if (exitcode == STILL_ACTIVE) exitcode = 0;\r
1574   if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);\r
1575   service->pid = 0;\r
1576 \r
1577   /*\r
1578     The why argument is true if our wait timed out or false otherwise.\r
1579     Our wait is infinite so why will never be true when called by the system.\r
1580     If it is indeed true, assume we were called from stop_service() because\r
1581     this is a controlled shutdown, and don't take any restart action.\r
1582   */\r
1583   if (why) return;\r
1584   if (! service->allow_restart) return;\r
1585 \r
1586   /* What action should we take? */\r
1587   int action = NSSM_EXIT_RESTART;\r
1588   TCHAR action_string[ACTION_LEN];\r
1589   bool default_action;\r
1590   if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {\r
1591     for (int i = 0; exit_action_strings[i]; i++) {\r
1592       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
1593         action = i;\r
1594         break;\r
1595       }\r
1596     }\r
1597   }\r
1598 \r
1599   switch (action) {\r
1600     /* Try to restart the service or return failure code to service manager */\r
1601     case NSSM_EXIT_RESTART:\r
1602       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);\r
1603       while (monitor_service(service)) {\r
1604         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);\r
1605         Sleep(30000);\r
1606       }\r
1607     break;\r
1608 \r
1609     /* Do nothing, just like srvany would */\r
1610     case NSSM_EXIT_IGNORE:\r
1611       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
1612       Sleep(INFINITE);\r
1613     break;\r
1614 \r
1615     /* Tell the service manager we are finished */\r
1616     case NSSM_EXIT_REALLY:\r
1617       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);\r
1618       stop_service(service, exitcode, true, default_action);\r
1619     break;\r
1620 \r
1621     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
1622     case NSSM_EXIT_UNCLEAN:\r
1623       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
1624       stop_service(service, exitcode, false, default_action);\r
1625       free_imports();\r
1626       exit(exitcode);\r
1627     break;\r
1628   }\r
1629 }\r
1630 \r
1631 void throttle_restart(nssm_service_t *service) {\r
1632   /* This can't be a restart if the service is already running. */\r
1633   if (! service->throttle++) return;\r
1634 \r
1635   int ms = throttle_milliseconds(service->throttle);\r
1636 \r
1637   if (service->throttle > 7) service->throttle = 8;\r
1638 \r
1639   TCHAR threshold[8], milliseconds[8];\r
1640   _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);\r
1641   _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
1642   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);\r
1643 \r
1644   if (use_critical_section) EnterCriticalSection(&service->throttle_section);\r
1645   else if (service->throttle_timer) {\r
1646     ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1647     service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
1648     SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1649   }\r
1650 \r
1651   service->status.dwCurrentState = SERVICE_PAUSED;\r
1652   SetServiceStatus(service->status_handle, &service->status);\r
1653 \r
1654   if (use_critical_section) {\r
1655     imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);\r
1656     LeaveCriticalSection(&service->throttle_section);\r
1657   }\r
1658   else {\r
1659     if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);\r
1660     else Sleep(ms);\r
1661   }\r
1662 }\r
1663 \r
1664 /*\r
1665   When responding to a stop (or any other) request we need to set dwWaitHint to\r
1666   the number of milliseconds we expect the operation to take, and optionally\r
1667   increase dwCheckPoint.  If dwWaitHint milliseconds elapses without the\r
1668   operation completing or dwCheckPoint increasing, the system will consider the\r
1669   service to be hung.\r
1670 \r
1671   However the system will consider the service to be hung after 30000\r
1672   milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not\r
1673   changed.  Therefore if we want to wait longer than that we must periodically\r
1674   increase dwCheckPoint.\r
1675 \r
1676   Furthermore, it will consider the service to be hung after 60000 milliseconds\r
1677   regardless of the value of dwCheckPoint unless dwWaitHint is increased every\r
1678   time dwCheckPoint is also increased.\r
1679 \r
1680   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
1681   NSSM_SERVICE_STATUS_DEADLINE milliseconds.  If the process is still running\r
1682   and we haven't finished waiting we increment dwCheckPoint and add whichever is\r
1683   smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to\r
1684   dwWaitHint.\r
1685 \r
1686   Only doing both these things will prevent the system from killing the service.\r
1687 \r
1688   Returns: 1 if the wait timed out.\r
1689            0 if the wait completed.\r
1690           -1 on error.\r
1691 */\r
1692 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {\r
1693   unsigned long interval;\r
1694   unsigned long waithint;\r
1695   unsigned long ret;\r
1696   unsigned long waited;\r
1697   TCHAR interval_milliseconds[16];\r
1698   TCHAR timeout_milliseconds[16];\r
1699   TCHAR waited_milliseconds[16];\r
1700   TCHAR *function = function_name;\r
1701 \r
1702   /* Add brackets to function name. */\r
1703   size_t funclen = _tcslen(function_name) + 3;\r
1704   TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));\r
1705   if (func) {\r
1706     if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;\r
1707   }\r
1708 \r
1709   _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);\r
1710 \r
1711   waithint = service->status.dwWaitHint;\r
1712   waited = 0;\r
1713   while (waited < timeout) {\r
1714     interval = timeout - waited;\r
1715     if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
1716 \r
1717     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1718     service->status.dwWaitHint += interval;\r
1719     service->status.dwCheckPoint++;\r
1720     SetServiceStatus(service->status_handle, &service->status);\r
1721 \r
1722     if (waited) {\r
1723       _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);\r
1724       _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);\r
1725       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
1726     }\r
1727 \r
1728     switch (WaitForSingleObject(service->process_handle, interval)) {\r
1729       case WAIT_OBJECT_0:\r
1730         ret = 0;\r
1731         goto awaited;\r
1732 \r
1733       case WAIT_TIMEOUT:\r
1734         ret = 1;\r
1735       break;\r
1736 \r
1737       default:\r
1738         ret = -1;\r
1739         goto awaited;\r
1740     }\r
1741 \r
1742     waited += interval;\r
1743   }\r
1744 \r
1745 awaited:\r
1746   if (func) HeapFree(GetProcessHeap(), 0, func);\r
1747 \r
1748   return ret;\r
1749 }\r