Added service_status_text().
[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 \r
705     additional = 0;\r
706     if (additional_mandatory) {\r
707       if (argc < mandatory) {\r
708         print_message(stderr, NSSM_MESSAGE_MISSING_SUBPARAMETER, parameter);\r
709         return 1;\r
710       }\r
711       additional = argv[3];\r
712       remainder = 4;\r
713     }\r
714     else {\r
715       additional = argv[remainder];\r
716       if (argc < mandatory) return usage(1);\r
717     }\r
718   }\r
719 \r
720   nssm_service_t *service = alloc_nssm_service();\r
721   _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), service_name);\r
722 \r
723   /* Open service manager */\r
724   SC_HANDLE services = open_service_manager();\r
725   if (! services) {\r
726     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
727     return 2;\r
728   }\r
729 \r
730   /* Try to open the service */\r
731   service->handle = open_service(services, service->name, service->name, _countof(service->name));\r
732   if (! service->handle) {\r
733     CloseServiceHandle(services);\r
734     return 3;\r
735   }\r
736 \r
737   /* Get system details. */\r
738   QUERY_SERVICE_CONFIG *qsc = query_service_config(service->name, service->handle);\r
739   if (! qsc) {\r
740     CloseHandle(service->handle);\r
741     CloseServiceHandle(services);\r
742     return 4;\r
743   }\r
744 \r
745   service->type = qsc->dwServiceType;\r
746   if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {\r
747     if (mode != MODE_GETTING) {\r
748       HeapFree(GetProcessHeap(), 0, qsc);\r
749       CloseHandle(service->handle);\r
750       CloseServiceHandle(services);\r
751       print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, NSSM_WIN32_OWN_PROCESS, 0);\r
752       return 3;\r
753     }\r
754   }\r
755 \r
756   if (get_service_startup(service->name, service->handle, qsc, &service->startup)) {\r
757     if (mode != MODE_GETTING) {\r
758       HeapFree(GetProcessHeap(), 0, qsc);\r
759       CloseHandle(service->handle);\r
760       CloseServiceHandle(services);\r
761       return 4;\r
762     }\r
763   }\r
764 \r
765   if (get_service_username(service->name, qsc, &service->username, &service->usernamelen)) {\r
766     if (mode != MODE_GETTING) {\r
767       HeapFree(GetProcessHeap(), 0, qsc);\r
768       CloseHandle(service->handle);\r
769       CloseServiceHandle(services);\r
770       return 5;\r
771     }\r
772   }\r
773 \r
774   _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);\r
775 \r
776   /* Get the canonical service name. We open it case insensitively. */\r
777   unsigned long bufsize = _countof(service->name);\r
778   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
779 \r
780   /* Remember the executable in case it isn't NSSM. */\r
781   _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);\r
782   HeapFree(GetProcessHeap(), 0, qsc);\r
783 \r
784   /* Get extended system details. */\r
785   if (get_service_description(service->name, service->handle, _countof(service->description), service->description)) {\r
786     if (mode != MODE_GETTING) {\r
787       CloseHandle(service->handle);\r
788       CloseServiceHandle(services);\r
789       return 6;\r
790     }\r
791   }\r
792 \r
793   /* Get NSSM details. */\r
794   get_parameters(service, 0);\r
795 \r
796   CloseServiceHandle(services);\r
797 \r
798   if (! service->exe[0]) {\r
799     service->native = true;\r
800     if (mode != MODE_GETTING) print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);\r
801   }\r
802 \r
803   /* Editing with the GUI. */\r
804   if (mode == MODE_EDITING) {\r
805     nssm_gui(IDD_EDIT, service);\r
806     return 0;\r
807   }\r
808 \r
809   /* Trying to manage App* parameters for a non-NSSM service. */\r
810   if (! setting->native && service->native) {\r
811     CloseHandle(service->handle);\r
812     print_message(stderr, NSSM_MESSAGE_NATIVE_PARAMETER, setting->name, NSSM);\r
813     return 1;\r
814   }\r
815 \r
816   HKEY key;\r
817   value_t value;\r
818   int ret;\r
819 \r
820   if (mode == MODE_GETTING) {\r
821     if (! service->native) {\r
822       key = open_registry(service->name, KEY_READ);\r
823       if (! key) return 4;\r
824     }\r
825 \r
826     if (setting->native) ret = get_setting(service->name, service->handle, setting, &value, additional);\r
827     else ret = get_setting(service->name, key, setting, &value, additional);\r
828     if (ret < 0) {\r
829       CloseHandle(service->handle);\r
830       return 5;\r
831     }\r
832 \r
833     switch (setting->type) {\r
834       case REG_EXPAND_SZ:\r
835       case REG_MULTI_SZ:\r
836       case REG_SZ:\r
837         _tprintf(_T("%s\n"), value.string ? value.string : _T(""));\r
838         HeapFree(GetProcessHeap(), 0, value.string);\r
839         break;\r
840 \r
841       case REG_DWORD:\r
842         _tprintf(_T("%u\n"), value.numeric);\r
843         break;\r
844     }\r
845 \r
846     if (! service->native) RegCloseKey(key);\r
847     CloseHandle(service->handle);\r
848     return 0;\r
849   }\r
850 \r
851   /* Build the value. */\r
852   if (mode == MODE_RESETTING) {\r
853     /* Unset the parameter. */\r
854     value.string = 0;\r
855   }\r
856   else {\r
857     /* Set the parameter. */\r
858     size_t len = 0;\r
859     size_t delimiterlen = (setting->additional & ADDITIONAL_CRLF) ? 2 : 1;\r
860     for (i = remainder; i < argc; i++) len += _tcslen(argv[i]) + delimiterlen;\r
861     len++;\r
862 \r
863     value.string = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));\r
864     if (! value.string) {\r
865       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("value"), _T("edit_service()"));\r
866       CloseHandle(service->handle);\r
867       return 2;\r
868     }\r
869 \r
870     size_t s = 0;\r
871     for (i = remainder; i < argc; i++) {\r
872       size_t len = _tcslen(argv[i]);\r
873       memmove(value.string + s, argv[i], len * sizeof(TCHAR));\r
874       s += len;\r
875       if (i < argc - 1) {\r
876         if (setting->additional & ADDITIONAL_CRLF) {\r
877           value.string[s++] = _T('\r');\r
878           value.string[s++] = _T('\n');\r
879         }\r
880         else value.string[s++] = _T(' ');\r
881       }\r
882     }\r
883     value.string[s] = _T('\0');\r
884   }\r
885 \r
886   if (! service->native) {\r
887     key = open_registry(service->name, KEY_WRITE);\r
888     if (! key) {\r
889       if (value.string) HeapFree(GetProcessHeap(), 0, value.string);\r
890       return 4;\r
891     }\r
892   }\r
893 \r
894   if (setting->native) ret = set_setting(service->name, service->handle, setting, &value, additional);\r
895   else ret = set_setting(service->name, key, setting, &value, additional);\r
896   if (value.string) HeapFree(GetProcessHeap(), 0, value.string);\r
897   if (ret < 0) {\r
898     if (! service->native) RegCloseKey(key);\r
899     CloseHandle(service->handle);\r
900     return 6;\r
901   }\r
902 \r
903   if (! service->native) RegCloseKey(key);\r
904   CloseHandle(service->handle);\r
905 \r
906   return 0;\r
907 }\r
908 \r
909 /* About to remove the service */\r
910 int pre_remove_service(int argc, TCHAR **argv) {\r
911   nssm_service_t *service = alloc_nssm_service();\r
912   set_nssm_service_defaults(service);\r
913   if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
914 \r
915   /* Show dialogue box if we didn't pass service name and "confirm" */\r
916   if (argc < 2) return nssm_gui(IDD_REMOVE, service);\r
917   if (str_equiv(argv[1], _T("confirm"))) {\r
918     int ret = remove_service(service);\r
919     cleanup_nssm_service(service);\r
920     return ret;\r
921   }\r
922   print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);\r
923   return 100;\r
924 }\r
925 \r
926 /* Install the service */\r
927 int install_service(nssm_service_t *service) {\r
928   if (! service) return 1;\r
929 \r
930   /* Open service manager */\r
931   SC_HANDLE services = open_service_manager();\r
932   if (! services) {\r
933     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
934     cleanup_nssm_service(service);\r
935     return 2;\r
936   }\r
937 \r
938   /* Get path of this program */\r
939   GetModuleFileName(0, service->image, _countof(service->image));\r
940 \r
941   /* Create the service - settings will be changed in edit_service() */\r
942   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
943   if (! service->handle) {\r
944     print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED, error_string(GetLastError()));\r
945     CloseServiceHandle(services);\r
946     return 5;\r
947   }\r
948 \r
949   if (edit_service(service, false)) {\r
950     DeleteService(service->handle);\r
951     CloseServiceHandle(services);\r
952     return 6;\r
953   }\r
954 \r
955   print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);\r
956 \r
957   /* Cleanup */\r
958   CloseServiceHandle(services);\r
959 \r
960   return 0;\r
961 }\r
962 \r
963 /* Edit the service. */\r
964 int edit_service(nssm_service_t *service, bool editing) {\r
965   if (! service) return 1;\r
966 \r
967   /*\r
968     The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS\r
969     and SERVICE_INTERACTIVE_PROCESS.\r
970   */\r
971   service->type &= SERVICE_INTERACTIVE_PROCESS;\r
972   service->type |= SERVICE_WIN32_OWN_PROCESS;\r
973 \r
974   /* Startup type. */\r
975   unsigned long startup;\r
976   switch (service->startup) {\r
977     case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;\r
978     case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;\r
979     default: startup = SERVICE_AUTO_START;\r
980   }\r
981 \r
982   /* Display name. */\r
983   if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);\r
984 \r
985   /*\r
986     Username must be NULL if we aren't changing or an account name.\r
987     We must explicitly use LOCALSYSTEM to change it when we are editing.\r
988     Password must be NULL if we aren't changing, a password or "".\r
989     Empty passwords are valid but we won't allow them in the GUI.\r
990   */\r
991   TCHAR *username = 0;\r
992   TCHAR *password = 0;\r
993   if (service->usernamelen) {\r
994     username = service->username;\r
995     if (service->passwordlen) password = service->password;\r
996     else password = _T("");\r
997   }\r
998   else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;\r
999 \r
1000   if (grant_logon_as_service(username)) {\r
1001     print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);\r
1002     return 5;\r
1003   }\r
1004 \r
1005   if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {\r
1006     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));\r
1007     return 5;\r
1008   }\r
1009 \r
1010   if (service->description[0] || editing) {\r
1011     set_service_description(service->name, service->handle, service->description);\r
1012   }\r
1013 \r
1014   SERVICE_DELAYED_AUTO_START_INFO delayed;\r
1015   ZeroMemory(&delayed, sizeof(delayed));\r
1016   if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;\r
1017   else delayed.fDelayedAutostart = 0;\r
1018   /* Delayed startup isn't supported until Vista. */\r
1019   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {\r
1020     unsigned long error = GetLastError();\r
1021     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
1022     if (error != ERROR_INVALID_LEVEL) {\r
1023       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);\r
1024     }\r
1025   }\r
1026 \r
1027   /* Don't mess with parameters which aren't ours. */\r
1028   if (! service->native) {\r
1029     /* Now we need to put the parameters into the registry */\r
1030     if (create_parameters(service, editing)) {\r
1031       print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);\r
1032       return 6;\r
1033     }\r
1034 \r
1035     set_service_recovery(service);\r
1036   }\r
1037 \r
1038   return 0;\r
1039 }\r
1040 \r
1041 /* Control a service. */\r
1042 int control_service(unsigned long control, int argc, TCHAR **argv) {\r
1043   if (argc < 1) return usage(1);\r
1044   TCHAR *service_name = argv[0];\r
1045   TCHAR canonical_name[SERVICE_NAME_LENGTH];\r
1046 \r
1047   SC_HANDLE services = open_service_manager();\r
1048   if (! services) {\r
1049     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
1050     return 2;\r
1051   }\r
1052 \r
1053   SC_HANDLE service_handle = open_service(services, service_name, canonical_name, _countof(canonical_name));\r
1054   if (! service_handle) {\r
1055     CloseServiceHandle(services);\r
1056     return 3;\r
1057   }\r
1058 \r
1059   int ret;\r
1060   unsigned long error;\r
1061   SERVICE_STATUS service_status;\r
1062   if (control == NSSM_SERVICE_CONTROL_START) {\r
1063     ret = StartService(service_handle, (unsigned long) argc, (const TCHAR **) argv);\r
1064     error = GetLastError();\r
1065     CloseHandle(service_handle);\r
1066     CloseServiceHandle(services);\r
1067 \r
1068     if (error == ERROR_IO_PENDING) {\r
1069       /*\r
1070         Older versions of Windows return immediately with ERROR_IO_PENDING\r
1071         indicate that the operation is still in progress.  Newer versions\r
1072         will return it if there really is a delay.  As far as we're\r
1073         concerned the operation is a success.  We don't claim to offer a\r
1074         fully-feature service control method; it's just a quick 'n' dirty\r
1075         interface.\r
1076 \r
1077         In the future we may identify and handle this situation properly.\r
1078       */\r
1079       ret = 1;\r
1080       error = ERROR_SUCCESS;\r
1081     }\r
1082 \r
1083     if (ret) {\r
1084       _tprintf(_T("%s: %s"), canonical_name, error_string(error));\r
1085       return 0;\r
1086     }\r
1087     else {\r
1088       _ftprintf(stderr, _T("%s: %s"), canonical_name, error_string(error));\r
1089       return 1;\r
1090     }\r
1091   }\r
1092   else if (control == SERVICE_CONTROL_INTERROGATE) {\r
1093     /*\r
1094       We could actually send an INTERROGATE control but that won't return\r
1095       any information if the service is stopped and we don't care about\r
1096       the extra details it might give us in any case.  So we'll fake it.\r
1097     */\r
1098     ret = QueryServiceStatus(service_handle, &service_status);\r
1099     error = GetLastError();\r
1100 \r
1101     if (ret) {\r
1102       switch (service_status.dwCurrentState) {\r
1103         case SERVICE_STOPPED: _tprintf(_T("SERVICE_STOPPED\n")); break;\r
1104         case SERVICE_START_PENDING: _tprintf(_T("SERVICE_START_PENDING\n")); break;\r
1105         case SERVICE_STOP_PENDING: _tprintf(_T("SERVICE_STOP_PENDING\n")); break;\r
1106         case SERVICE_RUNNING: _tprintf(_T("SERVICE_RUNNING\n")); break;\r
1107         case SERVICE_CONTINUE_PENDING: _tprintf(_T("SERVICE_CONTINUE_PENDING\n")); break;\r
1108         case SERVICE_PAUSE_PENDING: _tprintf(_T("SERVICE_PAUSE_PENDING\n")); break;\r
1109         case SERVICE_PAUSED: _tprintf(_T("SERVICE_PAUSED\n")); break;\r
1110         default: _tprintf(_T("?\n")); return 1;\r
1111       }\r
1112       return 0;\r
1113     }\r
1114     else {\r
1115       _ftprintf(stderr, _T("%s: %s\n"), canonical_name, error_string(error));\r
1116       return 1;\r
1117     }\r
1118   }\r
1119   else {\r
1120     ret = ControlService(service_handle, control, &service_status);\r
1121     error = GetLastError();\r
1122     CloseHandle(service_handle);\r
1123     CloseServiceHandle(services);\r
1124 \r
1125     if (error == ERROR_IO_PENDING) {\r
1126       ret = 1;\r
1127       error = ERROR_SUCCESS;\r
1128     }\r
1129 \r
1130     if (ret) {\r
1131       _tprintf(_T("%s: %s"), canonical_name, error_string(error));\r
1132       return 0;\r
1133     }\r
1134     else {\r
1135       _ftprintf(stderr, _T("%s: %s"), canonical_name, error_string(error));\r
1136       return 1;\r
1137     }\r
1138   }\r
1139 }\r
1140 \r
1141 /* Remove the service */\r
1142 int remove_service(nssm_service_t *service) {\r
1143   if (! service) return 1;\r
1144 \r
1145   /* Open service manager */\r
1146   SC_HANDLE services = open_service_manager();\r
1147   if (! services) {\r
1148     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
1149     return 2;\r
1150   }\r
1151 \r
1152   /* Try to open the service */\r
1153   service->handle = open_service(services, service->name, service->name, _countof(service->name));\r
1154   if (! service->handle) {\r
1155     CloseServiceHandle(services);\r
1156     return 3;\r
1157   }\r
1158 \r
1159   /* Get the canonical service name. We open it case insensitively. */\r
1160   unsigned long bufsize = _countof(service->displayname);\r
1161   GetServiceDisplayName(services, service->name, service->displayname, &bufsize);\r
1162   bufsize = _countof(service->name);\r
1163   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
1164 \r
1165   /* Try to delete the service */\r
1166   if (! DeleteService(service->handle)) {\r
1167     print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);\r
1168     CloseServiceHandle(services);\r
1169     return 4;\r
1170   }\r
1171 \r
1172   /* Cleanup */\r
1173   CloseServiceHandle(services);\r
1174 \r
1175   print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);\r
1176   return 0;\r
1177 }\r
1178 \r
1179 /* Service initialisation */\r
1180 void WINAPI service_main(unsigned long argc, TCHAR **argv) {\r
1181   nssm_service_t *service = alloc_nssm_service();\r
1182   if (! service) return;\r
1183 \r
1184   if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {\r
1185     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);\r
1186     return;\r
1187   }\r
1188 \r
1189   /* We can use a condition variable in a critical section on Vista or later. */\r
1190   if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;\r
1191   else use_critical_section = false;\r
1192 \r
1193   /* Initialise status */\r
1194   ZeroMemory(&service->status, sizeof(service->status));\r
1195   service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
1196   service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;\r
1197   service->status.dwWin32ExitCode = NO_ERROR;\r
1198   service->status.dwServiceSpecificExitCode = 0;\r
1199   service->status.dwCheckPoint = 0;\r
1200   service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
1201 \r
1202   /* Signal we AREN'T running the server */\r
1203   service->process_handle = 0;\r
1204   service->pid = 0;\r
1205 \r
1206   /* Register control handler */\r
1207   service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);\r
1208   if (! service->status_handle) {\r
1209     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);\r
1210     return;\r
1211   }\r
1212 \r
1213   log_service_control(service->name, 0, true);\r
1214 \r
1215   service->status.dwCurrentState = SERVICE_START_PENDING;\r
1216   service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;\r
1217   SetServiceStatus(service->status_handle, &service->status);\r
1218 \r
1219   if (is_admin) {\r
1220     /* Try to create the exit action parameters; we don't care if it fails */\r
1221     create_exit_action(service->name, exit_action_strings[0], false);\r
1222 \r
1223     SC_HANDLE services = open_service_manager();\r
1224     if (services) {\r
1225       service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
1226       set_service_recovery(service);\r
1227       CloseServiceHandle(services);\r
1228     }\r
1229   }\r
1230 \r
1231   /* Used for signalling a resume if the service pauses when throttled. */\r
1232   if (use_critical_section) {\r
1233     InitializeCriticalSection(&service->throttle_section);\r
1234     service->throttle_section_initialised = true;\r
1235   }\r
1236   else {\r
1237     service->throttle_timer = CreateWaitableTimer(0, 1, 0);\r
1238     if (! service->throttle_timer) {\r
1239       log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);\r
1240     }\r
1241   }\r
1242 \r
1243   monitor_service(service);\r
1244 }\r
1245 \r
1246 /* Make sure service recovery actions are taken where necessary */\r
1247 void set_service_recovery(nssm_service_t *service) {\r
1248   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
1249   ZeroMemory(&flag, sizeof(flag));\r
1250   flag.fFailureActionsOnNonCrashFailures = true;\r
1251 \r
1252   /* This functionality was added in Vista so the call may fail */\r
1253   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {\r
1254     unsigned long error = GetLastError();\r
1255     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
1256     if (error != ERROR_INVALID_LEVEL) {\r
1257       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);\r
1258     }\r
1259   }\r
1260 }\r
1261 \r
1262 int monitor_service(nssm_service_t *service) {\r
1263   /* Set service status to started */\r
1264   int ret = start_service(service);\r
1265   if (ret) {\r
1266     TCHAR code[16];\r
1267     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);\r
1268     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);\r
1269     return ret;\r
1270   }\r
1271   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);\r
1272 \r
1273   /* Monitor service */\r
1274   if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
1275     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);\r
1276   }\r
1277 \r
1278   return 0;\r
1279 }\r
1280 \r
1281 TCHAR *service_control_text(unsigned long control) {\r
1282   switch (control) {\r
1283     /* HACK: there is no SERVICE_CONTROL_START constant */\r
1284     case NSSM_SERVICE_CONTROL_START: return _T("START");\r
1285     case SERVICE_CONTROL_STOP: return _T("STOP");\r
1286     case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");\r
1287     case SERVICE_CONTROL_PAUSE: return _T("PAUSE");\r
1288     case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");\r
1289     case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");\r
1290     case NSSM_SERVICE_CONTROL_ROTATE: return _T("ROTATE");\r
1291     default: return 0;\r
1292   }\r
1293 }\r
1294 \r
1295 TCHAR *service_status_text(unsigned long status) {\r
1296   switch (status) {\r
1297     case SERVICE_STOPPED: return _T("SERVICE_STOPPED");\r
1298     case SERVICE_START_PENDING: return _T("SERVICE_START_PENDING");\r
1299     case SERVICE_STOP_PENDING: return _T("SERVICE_STOP_PENDING");\r
1300     case SERVICE_RUNNING: return _T("SERVICE_RUNNING");\r
1301     case SERVICE_CONTINUE_PENDING: return _T("SERVICE_CONTINUE_PENDING");\r
1302     case SERVICE_PAUSE_PENDING: return _T("SERVICE_PAUSE_PENDING");\r
1303     case SERVICE_PAUSED: return _T("SERVICE_PAUSED");\r
1304     default: return 0;\r
1305   }\r
1306 }\r
1307 \r
1308 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {\r
1309   TCHAR *text = service_control_text(control);\r
1310   unsigned long event;\r
1311 \r
1312   if (! text) {\r
1313     /* "0x" + 8 x hex + NULL */\r
1314     text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));\r
1315     if (! text) {\r
1316       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
1317       return;\r
1318     }\r
1319     if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {\r
1320       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
1321       HeapFree(GetProcessHeap(), 0, text);\r
1322       return;\r
1323     }\r
1324 \r
1325     event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;\r
1326   }\r
1327   else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;\r
1328   else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;\r
1329 \r
1330   log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);\r
1331 \r
1332   if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {\r
1333     HeapFree(GetProcessHeap(), 0, text);\r
1334   }\r
1335 }\r
1336 \r
1337 /* Service control handler */\r
1338 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
1339   nssm_service_t *service = (nssm_service_t *) context;\r
1340 \r
1341   switch (control) {\r
1342     case SERVICE_CONTROL_INTERROGATE:\r
1343       /* We always keep the service status up-to-date so this is a no-op. */\r
1344       return NO_ERROR;\r
1345 \r
1346     case SERVICE_CONTROL_SHUTDOWN:\r
1347     case SERVICE_CONTROL_STOP:\r
1348       log_service_control(service->name, control, true);\r
1349       /*\r
1350         We MUST acknowledge the stop request promptly but we're committed to\r
1351         waiting for the application to exit.  Spawn a new thread to wait\r
1352         while we acknowledge the request.\r
1353       */\r
1354       if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {\r
1355         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
1356 \r
1357         /*\r
1358           We couldn't create a thread to tidy up so we'll have to force the tidyup\r
1359           to complete in time in this thread.\r
1360         */\r
1361         service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
1362         service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
1363         service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
1364 \r
1365         stop_service(service, 0, true, true);\r
1366       }\r
1367       return NO_ERROR;\r
1368 \r
1369     case SERVICE_CONTROL_CONTINUE:\r
1370       log_service_control(service->name, control, true);\r
1371       service->throttle = 0;\r
1372       if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);\r
1373       else {\r
1374         if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
1375         ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1376         SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1377       }\r
1378       service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
1379       service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;\r
1380       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);\r
1381       SetServiceStatus(service->status_handle, &service->status);\r
1382       return NO_ERROR;\r
1383 \r
1384     case SERVICE_CONTROL_PAUSE:\r
1385       /*\r
1386         We don't accept pause messages but it isn't possible to register\r
1387         only for continue messages so we have to handle this case.\r
1388       */\r
1389       log_service_control(service->name, control, false);\r
1390       return ERROR_CALL_NOT_IMPLEMENTED;\r
1391 \r
1392     case NSSM_SERVICE_CONTROL_ROTATE:\r
1393       log_service_control(service->name, control, true);\r
1394       if (service->rotate_stdout_online) service->rotate_stdout_online = NSSM_ROTATE_ONLINE_ASAP;\r
1395       if (service->rotate_stdout_online) service->rotate_stderr_online = NSSM_ROTATE_ONLINE_ASAP;\r
1396       return NO_ERROR;\r
1397   }\r
1398 \r
1399   /* Unknown control */\r
1400   log_service_control(service->name, control, false);\r
1401   return ERROR_CALL_NOT_IMPLEMENTED;\r
1402 }\r
1403 \r
1404 /* Start the service */\r
1405 int start_service(nssm_service_t *service) {\r
1406   service->stopping = false;\r
1407   service->allow_restart = true;\r
1408 \r
1409   if (service->process_handle) return 0;\r
1410 \r
1411   /* Allocate a STARTUPINFO structure for a new process */\r
1412   STARTUPINFO si;\r
1413   ZeroMemory(&si, sizeof(si));\r
1414   si.cb = sizeof(si);\r
1415 \r
1416   /* Allocate a PROCESSINFO structure for the process */\r
1417   PROCESS_INFORMATION pi;\r
1418   ZeroMemory(&pi, sizeof(pi));\r
1419 \r
1420   /* Get startup parameters */\r
1421   int ret = get_parameters(service, &si);\r
1422   if (ret) {\r
1423     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);\r
1424     return stop_service(service, 2, true, true);\r
1425   }\r
1426 \r
1427   /* Launch executable with arguments */\r
1428   TCHAR cmd[CMD_LENGTH];\r
1429   if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {\r
1430     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);\r
1431     close_output_handles(&si);\r
1432     return stop_service(service, 2, true, true);\r
1433   }\r
1434 \r
1435   throttle_restart(service);\r
1436 \r
1437   bool inherit_handles = false;\r
1438   if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
1439   unsigned long flags = service->priority & priority_mask();\r
1440   if (service->affinity) flags |= CREATE_SUSPENDED;\r
1441 #ifdef UNICODE\r
1442   flags |= CREATE_UNICODE_ENVIRONMENT;\r
1443 #endif\r
1444   if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {\r
1445     unsigned long exitcode = 3;\r
1446     unsigned long error = GetLastError();\r
1447     if (error == ERROR_INVALID_PARAMETER && service->env) {\r
1448       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);\r
1449       if (test_environment(service->env)) exitcode = 4;\r
1450     }\r
1451     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
1452     close_output_handles(&si);\r
1453     return stop_service(service, exitcode, true, true);\r
1454   }\r
1455   service->process_handle = pi.hProcess;\r
1456   service->pid = pi.dwProcessId;\r
1457 \r
1458   if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
1459 \r
1460   close_output_handles(&si, ! service->rotate_stdout_online, ! service->rotate_stderr_online);\r
1461 \r
1462   if (service->affinity) {\r
1463     /*\r
1464       We are explicitly storing service->affinity as a 64-bit unsigned integer\r
1465       so that we can parse it regardless of whether we're running in 32-bit\r
1466       or 64-bit mode.  The arguments to SetProcessAffinityMask(), however, are\r
1467       defined as type DWORD_PTR and hence limited to 32 bits on a 32-bit system\r
1468       (or when running the 32-bit NSSM).\r
1469 \r
1470       The result is a lot of seemingly-unnecessary casting throughout the code\r
1471       and potentially confusion when we actually try to start the service.\r
1472       Having said that, however, it's unlikely that we're actually going to\r
1473       run in 32-bit mode on a system which has more than 32 CPUs so the\r
1474       likelihood of seeing a confusing situation is somewhat diminished.\r
1475     */\r
1476     DWORD_PTR affinity, system_affinity;\r
1477 \r
1478     if (GetProcessAffinityMask(service->process_handle, &affinity, &system_affinity)) affinity = service->affinity & system_affinity;\r
1479     else {\r
1480       affinity = (DWORD_PTR) service->affinity;\r
1481       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
1482     }\r
1483 \r
1484     if (! SetProcessAffinityMask(service->process_handle, affinity)) {\r
1485       log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
1486     }\r
1487 \r
1488     ResumeThread(pi.hThread);\r
1489   }\r
1490 \r
1491   /*\r
1492     Wait for a clean startup before changing the service status to RUNNING\r
1493     but be mindful of the fact that we are blocking the service control manager\r
1494     so abandon the wait before too much time has elapsed.\r
1495   */\r
1496   unsigned long delay = service->throttle_delay;\r
1497   if (delay > NSSM_SERVICE_STATUS_DEADLINE) {\r
1498     TCHAR delay_milliseconds[16];\r
1499     _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);\r
1500     TCHAR deadline_milliseconds[16];\r
1501     _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);\r
1502     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);\r
1503     delay = NSSM_SERVICE_STATUS_DEADLINE;\r
1504   }\r
1505   unsigned long deadline = WaitForSingleObject(service->process_handle, delay);\r
1506 \r
1507   /* Signal successful start */\r
1508   service->status.dwCurrentState = SERVICE_RUNNING;\r
1509   SetServiceStatus(service->status_handle, &service->status);\r
1510 \r
1511   /* Continue waiting for a clean startup. */\r
1512   if (deadline == WAIT_TIMEOUT) {\r
1513     if (service->throttle_delay > delay) {\r
1514       if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;\r
1515     }\r
1516     else service->throttle = 0;\r
1517   }\r
1518 \r
1519   /* Ensure the restart delay is always applied. */\r
1520   if (service->restart_delay && ! service->throttle) service->throttle++;\r
1521 \r
1522   return 0;\r
1523 }\r
1524 \r
1525 /* Stop the service */\r
1526 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {\r
1527   service->allow_restart = false;\r
1528   if (service->wait_handle) {\r
1529     UnregisterWait(service->wait_handle);\r
1530     service->wait_handle = 0;\r
1531   }\r
1532 \r
1533   if (default_action && ! exitcode && ! graceful) {\r
1534     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
1535     graceful = true;\r
1536   }\r
1537 \r
1538   /* Signal we are stopping */\r
1539   if (graceful) {\r
1540     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1541     service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
1542     SetServiceStatus(service->status_handle, &service->status);\r
1543   }\r
1544 \r
1545   /* Nothing to do if service isn't running */\r
1546   if (service->pid) {\r
1547     /* Shut down service */\r
1548     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);\r
1549     kill_process(service, service->process_handle, service->pid, 0);\r
1550   }\r
1551   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);\r
1552 \r
1553   end_service((void *) service, true);\r
1554 \r
1555   /* Signal we stopped */\r
1556   if (graceful) {\r
1557     service->status.dwCurrentState = SERVICE_STOPPED;\r
1558     if (exitcode) {\r
1559       service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
1560       service->status.dwServiceSpecificExitCode = exitcode;\r
1561     }\r
1562     else {\r
1563       service->status.dwWin32ExitCode = NO_ERROR;\r
1564       service->status.dwServiceSpecificExitCode = 0;\r
1565     }\r
1566     SetServiceStatus(service->status_handle, &service->status);\r
1567   }\r
1568 \r
1569   return exitcode;\r
1570 }\r
1571 \r
1572 /* Callback function triggered when the server exits */\r
1573 void CALLBACK end_service(void *arg, unsigned char why) {\r
1574   nssm_service_t *service = (nssm_service_t *) arg;\r
1575 \r
1576   if (service->stopping) return;\r
1577 \r
1578   service->stopping = true;\r
1579 \r
1580   /* Check exit code */\r
1581   unsigned long exitcode = 0;\r
1582   TCHAR code[16];\r
1583   if (service->process_handle) {\r
1584     GetExitCodeProcess(service->process_handle, &exitcode);\r
1585     if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);\r
1586     CloseHandle(service->process_handle);\r
1587   }\r
1588   else GetSystemTimeAsFileTime(&service->exit_time);\r
1589 \r
1590   service->process_handle = 0;\r
1591 \r
1592   /*\r
1593     Log that the service ended BEFORE logging about killing the process\r
1594     tree.  See below for the possible values of the why argument.\r
1595   */\r
1596   if (! why) {\r
1597     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);\r
1598     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);\r
1599   }\r
1600 \r
1601   /* Clean up. */\r
1602   if (exitcode == STILL_ACTIVE) exitcode = 0;\r
1603   if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);\r
1604   service->pid = 0;\r
1605 \r
1606   /*\r
1607     The why argument is true if our wait timed out or false otherwise.\r
1608     Our wait is infinite so why will never be true when called by the system.\r
1609     If it is indeed true, assume we were called from stop_service() because\r
1610     this is a controlled shutdown, and don't take any restart action.\r
1611   */\r
1612   if (why) return;\r
1613   if (! service->allow_restart) return;\r
1614 \r
1615   /* What action should we take? */\r
1616   int action = NSSM_EXIT_RESTART;\r
1617   TCHAR action_string[ACTION_LEN];\r
1618   bool default_action;\r
1619   if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {\r
1620     for (int i = 0; exit_action_strings[i]; i++) {\r
1621       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
1622         action = i;\r
1623         break;\r
1624       }\r
1625     }\r
1626   }\r
1627 \r
1628   switch (action) {\r
1629     /* Try to restart the service or return failure code to service manager */\r
1630     case NSSM_EXIT_RESTART:\r
1631       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);\r
1632       while (monitor_service(service)) {\r
1633         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);\r
1634         Sleep(30000);\r
1635       }\r
1636     break;\r
1637 \r
1638     /* Do nothing, just like srvany would */\r
1639     case NSSM_EXIT_IGNORE:\r
1640       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
1641       Sleep(INFINITE);\r
1642     break;\r
1643 \r
1644     /* Tell the service manager we are finished */\r
1645     case NSSM_EXIT_REALLY:\r
1646       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);\r
1647       stop_service(service, exitcode, true, default_action);\r
1648     break;\r
1649 \r
1650     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
1651     case NSSM_EXIT_UNCLEAN:\r
1652       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
1653       stop_service(service, exitcode, false, default_action);\r
1654       free_imports();\r
1655       exit(exitcode);\r
1656     break;\r
1657   }\r
1658 }\r
1659 \r
1660 void throttle_restart(nssm_service_t *service) {\r
1661   /* This can't be a restart if the service is already running. */\r
1662   if (! service->throttle++) return;\r
1663 \r
1664   unsigned long ms;\r
1665   unsigned long throttle_ms = throttle_milliseconds(service->throttle);\r
1666   TCHAR threshold[8], milliseconds[8];\r
1667 \r
1668   if (service->restart_delay > throttle_ms) ms = service->restart_delay;\r
1669   else ms = throttle_ms;\r
1670 \r
1671   if (service->throttle > 7) service->throttle = 8;\r
1672 \r
1673   _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
1674 \r
1675   if (service->throttle == 1 && service->restart_delay > throttle_ms) log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESTART_DELAY, service->name, milliseconds, 0);\r
1676   else {\r
1677     _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);\r
1678     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);\r
1679   }\r
1680 \r
1681   if (use_critical_section) EnterCriticalSection(&service->throttle_section);\r
1682   else if (service->throttle_timer) {\r
1683     ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1684     service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
1685     SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1686   }\r
1687 \r
1688   service->status.dwCurrentState = SERVICE_PAUSED;\r
1689   SetServiceStatus(service->status_handle, &service->status);\r
1690 \r
1691   if (use_critical_section) {\r
1692     imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);\r
1693     LeaveCriticalSection(&service->throttle_section);\r
1694   }\r
1695   else {\r
1696     if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);\r
1697     else Sleep(ms);\r
1698   }\r
1699 }\r
1700 \r
1701 /*\r
1702   When responding to a stop (or any other) request we need to set dwWaitHint to\r
1703   the number of milliseconds we expect the operation to take, and optionally\r
1704   increase dwCheckPoint.  If dwWaitHint milliseconds elapses without the\r
1705   operation completing or dwCheckPoint increasing, the system will consider the\r
1706   service to be hung.\r
1707 \r
1708   However the system will consider the service to be hung after 30000\r
1709   milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not\r
1710   changed.  Therefore if we want to wait longer than that we must periodically\r
1711   increase dwCheckPoint.\r
1712 \r
1713   Furthermore, it will consider the service to be hung after 60000 milliseconds\r
1714   regardless of the value of dwCheckPoint unless dwWaitHint is increased every\r
1715   time dwCheckPoint is also increased.\r
1716 \r
1717   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
1718   NSSM_SERVICE_STATUS_DEADLINE milliseconds.  If the process is still running\r
1719   and we haven't finished waiting we increment dwCheckPoint and add whichever is\r
1720   smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to\r
1721   dwWaitHint.\r
1722 \r
1723   Only doing both these things will prevent the system from killing the service.\r
1724 \r
1725   Returns: 1 if the wait timed out.\r
1726            0 if the wait completed.\r
1727           -1 on error.\r
1728 */\r
1729 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {\r
1730   unsigned long interval;\r
1731   unsigned long waithint;\r
1732   unsigned long ret;\r
1733   unsigned long waited;\r
1734   TCHAR interval_milliseconds[16];\r
1735   TCHAR timeout_milliseconds[16];\r
1736   TCHAR waited_milliseconds[16];\r
1737   TCHAR *function = function_name;\r
1738 \r
1739   /* Add brackets to function name. */\r
1740   size_t funclen = _tcslen(function_name) + 3;\r
1741   TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));\r
1742   if (func) {\r
1743     if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;\r
1744   }\r
1745 \r
1746   _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);\r
1747 \r
1748   waithint = service->status.dwWaitHint;\r
1749   waited = 0;\r
1750   while (waited < timeout) {\r
1751     interval = timeout - waited;\r
1752     if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
1753 \r
1754     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1755     service->status.dwWaitHint += interval;\r
1756     service->status.dwCheckPoint++;\r
1757     SetServiceStatus(service->status_handle, &service->status);\r
1758 \r
1759     if (waited) {\r
1760       _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);\r
1761       _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);\r
1762       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
1763     }\r
1764 \r
1765     switch (WaitForSingleObject(service->process_handle, interval)) {\r
1766       case WAIT_OBJECT_0:\r
1767         ret = 0;\r
1768         goto awaited;\r
1769 \r
1770       case WAIT_TIMEOUT:\r
1771         ret = 1;\r
1772       break;\r
1773 \r
1774       default:\r
1775         ret = -1;\r
1776         goto awaited;\r
1777     }\r
1778 \r
1779     waited += interval;\r
1780   }\r
1781 \r
1782 awaited:\r
1783   if (func) HeapFree(GetProcessHeap(), 0, func);\r
1784 \r
1785   return ret;\r
1786 }\r