Added get_debug_token().
[nssm.git] / service.cpp
1 #include "nssm.h"\r
2 \r
3 bool is_admin;\r
4 bool use_critical_section;\r
5 \r
6 extern imports_t imports;\r
7 extern settings_t settings[];\r
8 \r
9 const TCHAR *exit_action_strings[] = { _T("Restart"), _T("Ignore"), _T("Exit"), _T("Suicide"), 0 };\r
10 const TCHAR *startup_strings[] = { _T("SERVICE_AUTO_START"), _T("SERVICE_DELAYED_AUTO_START"), _T("SERVICE_DEMAND_START"), _T("SERVICE_DISABLED"), 0 };\r
11 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
12 \r
13 static hook_thread_t hook_threads = { NULL, 0 };\r
14 \r
15 typedef struct {\r
16   int first;\r
17   int last;\r
18 } list_t;\r
19 \r
20 /*\r
21   Check the status in response to a control.\r
22   Returns:  1 if the status is expected, eg STOP following CONTROL_STOP.\r
23             0 if the status is desired, eg STOPPED following CONTROL_STOP.\r
24            -1 if the status is undesired, eg STOPPED following CONTROL_START.\r
25 */\r
26 static inline int service_control_response(unsigned long control, unsigned long status) {\r
27   switch (control) {\r
28     case NSSM_SERVICE_CONTROL_START:\r
29       switch (status) {\r
30         case SERVICE_START_PENDING:\r
31           return 1;\r
32 \r
33         case SERVICE_RUNNING:\r
34           return 0;\r
35 \r
36         default:\r
37           return -1;\r
38       }\r
39 \r
40     case SERVICE_CONTROL_STOP:\r
41     case SERVICE_CONTROL_SHUTDOWN:\r
42       switch (status) {\r
43         case SERVICE_RUNNING:\r
44         case SERVICE_STOP_PENDING:\r
45           return 1;\r
46 \r
47         case SERVICE_STOPPED:\r
48           return 0;\r
49 \r
50         default:\r
51           return -1;\r
52       }\r
53 \r
54     case SERVICE_CONTROL_PAUSE:\r
55       switch (status) {\r
56         case SERVICE_PAUSE_PENDING:\r
57           return 1;\r
58 \r
59         case SERVICE_PAUSED:\r
60           return 0;\r
61 \r
62         default:\r
63           return -1;\r
64       }\r
65 \r
66     case SERVICE_CONTROL_CONTINUE:\r
67       switch (status) {\r
68         case SERVICE_CONTINUE_PENDING:\r
69           return 1;\r
70 \r
71         case SERVICE_RUNNING:\r
72           return 0;\r
73 \r
74         default:\r
75           return -1;\r
76       }\r
77 \r
78     case SERVICE_CONTROL_INTERROGATE:\r
79     case NSSM_SERVICE_CONTROL_ROTATE:\r
80       return 0;\r
81   }\r
82 \r
83   return 0;\r
84 }\r
85 \r
86 static inline int await_service_control_response(unsigned long control, SC_HANDLE service_handle, SERVICE_STATUS *service_status, unsigned long initial_status) {\r
87   int tries = 0;\r
88   unsigned long checkpoint = 0;\r
89   unsigned long waithint = 0;\r
90   while (QueryServiceStatus(service_handle, service_status)) {\r
91     int response = service_control_response(control, service_status->dwCurrentState);\r
92     /* Alas we can't WaitForSingleObject() on an SC_HANDLE. */\r
93     if (! response) return response;\r
94     if (response > 0 || service_status->dwCurrentState == initial_status) {\r
95       if (service_status->dwCheckPoint != checkpoint || service_status->dwWaitHint != waithint) tries = 0;\r
96       checkpoint = service_status->dwCheckPoint;\r
97       waithint = service_status->dwWaitHint;\r
98       if (++tries > 10) tries = 10;\r
99       Sleep(50 * tries);\r
100     }\r
101     else return response;\r
102   }\r
103   return -1;\r
104 }\r
105 \r
106 static inline void wait_for_hooks(nssm_service_t *service, bool notify) {\r
107   SERVICE_STATUS_HANDLE status_handle;\r
108   SERVICE_STATUS *status;\r
109 \r
110   /* On a clean shutdown we need to keep the service's status up-to-date. */\r
111   if (notify) {\r
112     status_handle = service->status_handle;\r
113     status = &service->status;\r
114   }\r
115   else {\r
116     status_handle = NULL;\r
117     status = NULL;\r
118   }\r
119 \r
120   EnterCriticalSection(&service->hook_section);\r
121   await_hook_threads(&hook_threads, status_handle, status, NSSM_HOOK_THREAD_DEADLINE);\r
122   LeaveCriticalSection(&service->hook_section);\r
123 }\r
124 \r
125 int affinity_mask_to_string(__int64 mask, TCHAR **string) {\r
126   if (! string) return 1;\r
127   if (! mask) {\r
128     *string = 0;\r
129     return 0;\r
130   }\r
131 \r
132   __int64 i, n;\r
133 \r
134   /* SetProcessAffinityMask() accepts a mask of up to 64 processors. */\r
135   list_t set[64];\r
136   for (n = 0; n < _countof(set); n++) set[n].first = set[n].last = -1;\r
137 \r
138   for (i = 0, n = 0; i < _countof(set); i++) {\r
139     if (mask & (1LL << i)) {\r
140       if (set[n].first == -1) set[n].first = set[n].last = (int) i;\r
141       else if (set[n].last == (int) i - 1) set[n].last = (int) i;\r
142       else {\r
143         n++;\r
144         set[n].first = set[n].last = (int) i;\r
145       }\r
146     }\r
147   }\r
148 \r
149   /* Worst case is 2x2 characters for first and last CPU plus - and/or , */\r
150   size_t len = (size_t) (n + 1) * 6;\r
151   *string = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, len * sizeof(TCHAR));\r
152   if (! string) return 2;\r
153 \r
154   size_t s = 0;\r
155   int ret;\r
156   for (i = 0; i <= n; i++) {\r
157     if (i) (*string)[s++] = _T(',');\r
158     ret = _sntprintf_s(*string + s, 3, _TRUNCATE, _T("%u"), set[i].first);\r
159     if (ret < 0) {\r
160       HeapFree(GetProcessHeap(), 0, *string);\r
161       *string = 0;\r
162       return 3;\r
163     }\r
164     else s += ret;\r
165     if (set[i].last != set[i].first) {\r
166       ret =_sntprintf_s(*string + s, 4, _TRUNCATE, _T("%c%u"), (set[i].last == set[i].first + 1) ? _T(',') : _T('-'), set[i].last);\r
167       if (ret < 0) {\r
168         HeapFree(GetProcessHeap(), 0, *string);\r
169         *string = 0;\r
170         return 4;\r
171       }\r
172       else s += ret;\r
173     }\r
174   }\r
175 \r
176   return 0;\r
177 }\r
178 \r
179 int affinity_string_to_mask(TCHAR *string, __int64 *mask) {\r
180   if (! mask) return 1;\r
181 \r
182   *mask = 0LL;\r
183   if (! string) return 0;\r
184 \r
185   list_t set[64];\r
186 \r
187   TCHAR *s = string;\r
188   TCHAR *end;\r
189   int ret;\r
190   int i;\r
191   int n = 0;\r
192   unsigned long number;\r
193 \r
194   for (n = 0; n < _countof(set); n++) set[n].first = set[n].last = -1;\r
195   n = 0;\r
196 \r
197   while (*s) {\r
198     ret = str_number(s, &number, &end);\r
199     s = end;\r
200     if (ret == 0 || ret == 2) {\r
201       if (number >= _countof(set)) return 2;\r
202       set[n].first = set[n].last = (int) number;\r
203 \r
204       switch (*s) {\r
205         case 0:\r
206           break;\r
207 \r
208         case _T(','):\r
209           n++;\r
210           s++;\r
211           break;\r
212 \r
213         case _T('-'):\r
214           if (! *(++s)) return 3;\r
215           ret = str_number(s, &number, &end);\r
216           if (ret == 0 || ret == 2) {\r
217             s = end;\r
218             if (! *s || *s == _T(',')) {\r
219               set[n].last = (int) number;\r
220               if (! *s) break;\r
221               n++;\r
222               s++;\r
223             }\r
224             else return 3;\r
225           }\r
226           else return 3;\r
227           break;\r
228 \r
229         default:\r
230           return 3;\r
231       }\r
232     }\r
233     else return 4;\r
234   }\r
235 \r
236   for (i = 0; i <= n; i++) {\r
237     for (int j = set[i].first; j <= set[i].last; j++) (__int64) *mask |= (1LL << (__int64) j);\r
238   }\r
239 \r
240   return 0;\r
241 }\r
242 \r
243 unsigned long priority_mask() {\r
244  return REALTIME_PRIORITY_CLASS | HIGH_PRIORITY_CLASS | ABOVE_NORMAL_PRIORITY_CLASS | NORMAL_PRIORITY_CLASS | BELOW_NORMAL_PRIORITY_CLASS | IDLE_PRIORITY_CLASS;\r
245 }\r
246 \r
247 int priority_constant_to_index(unsigned long constant) {\r
248   switch (constant & priority_mask()) {\r
249     case REALTIME_PRIORITY_CLASS: return NSSM_REALTIME_PRIORITY;\r
250     case HIGH_PRIORITY_CLASS: return NSSM_HIGH_PRIORITY;\r
251     case ABOVE_NORMAL_PRIORITY_CLASS: return NSSM_ABOVE_NORMAL_PRIORITY;\r
252     case BELOW_NORMAL_PRIORITY_CLASS: return NSSM_BELOW_NORMAL_PRIORITY;\r
253     case IDLE_PRIORITY_CLASS: return NSSM_IDLE_PRIORITY;\r
254   }\r
255   return NSSM_NORMAL_PRIORITY;\r
256 }\r
257 \r
258 unsigned long priority_index_to_constant(int index) {\r
259   switch (index) {\r
260     case NSSM_REALTIME_PRIORITY: return REALTIME_PRIORITY_CLASS;\r
261     case NSSM_HIGH_PRIORITY: return HIGH_PRIORITY_CLASS;\r
262     case NSSM_ABOVE_NORMAL_PRIORITY: return ABOVE_NORMAL_PRIORITY_CLASS;\r
263     case NSSM_BELOW_NORMAL_PRIORITY: return BELOW_NORMAL_PRIORITY_CLASS;\r
264     case NSSM_IDLE_PRIORITY: return IDLE_PRIORITY_CLASS;\r
265   }\r
266   return NORMAL_PRIORITY_CLASS;\r
267 }\r
268 \r
269 static inline unsigned long throttle_milliseconds(unsigned long throttle) {\r
270   if (throttle > 7) throttle = 8;\r
271   /* pow() operates on doubles. */\r
272   unsigned long ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;\r
273   return ret * 1000;\r
274 }\r
275 \r
276 void set_service_environment(nssm_service_t *service) {\r
277   if (! service) return;\r
278 \r
279   /*\r
280     We have to duplicate the block because this function will be called\r
281     multiple times between registry reads.\r
282   */\r
283   if (service->env) duplicate_environment_strings(service->env);\r
284   if (! service->env_extra) return;\r
285   TCHAR *env_extra = copy_environment_block(service->env_extra);\r
286   if (! env_extra) return;\r
287 \r
288   set_environment_block(env_extra);\r
289   HeapFree(GetProcessHeap(), 0, env_extra);\r
290 }\r
291 \r
292 void unset_service_environment(nssm_service_t *service) {\r
293   if (! service) return;\r
294   duplicate_environment_strings(service->initial_env);\r
295 }\r
296 \r
297 /*\r
298   Wrapper to be called in a new thread so that we can acknowledge a STOP\r
299   control immediately.\r
300 */\r
301 static unsigned long WINAPI shutdown_service(void *arg) {\r
302   return stop_service((nssm_service_t *) arg, 0, true, true);\r
303 }\r
304 \r
305 /*\r
306  Wrapper to be called in a new thread so that we can acknowledge start\r
307  immediately.\r
308 */\r
309 static unsigned long WINAPI launch_service(void *arg) {\r
310   return monitor_service((nssm_service_t *) arg);\r
311 }\r
312 \r
313 /* Connect to the service manager */\r
314 SC_HANDLE open_service_manager(unsigned long access) {\r
315   SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, access);\r
316   if (! ret) {\r
317     if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);\r
318     return 0;\r
319   }\r
320 \r
321   return ret;\r
322 }\r
323 \r
324 /* Open a service by name or display name. */\r
325 SC_HANDLE open_service(SC_HANDLE services, TCHAR *service_name, unsigned long access, TCHAR *canonical_name, unsigned long canonical_namelen) {\r
326   SC_HANDLE service_handle = OpenService(services, service_name, access);\r
327   if (service_handle) {\r
328     if (canonical_name && canonical_name != service_name) {\r
329       TCHAR displayname[SERVICE_NAME_LENGTH];\r
330       unsigned long displayname_len = (unsigned long) _countof(displayname);\r
331       GetServiceDisplayName(services, service_name, displayname, &displayname_len);\r
332       unsigned long keyname_len = canonical_namelen;\r
333       GetServiceKeyName(services, displayname, canonical_name, &keyname_len);\r
334     }\r
335     return service_handle;\r
336   }\r
337 \r
338   unsigned long error = GetLastError();\r
339   if (error != ERROR_SERVICE_DOES_NOT_EXIST) {\r
340     print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED, error_string(GetLastError()));\r
341     return 0;\r
342   }\r
343 \r
344   /* We can't look for a display name because there's no buffer to store it. */\r
345   if (! canonical_name) {\r
346     print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED, error_string(GetLastError()));\r
347     return 0;\r
348   }\r
349 \r
350   unsigned long bufsize, required, count, i;\r
351   unsigned long resume = 0;\r
352   EnumServicesStatus(services, SERVICE_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_KERNEL_DRIVER | SERVICE_WIN32, SERVICE_STATE_ALL, 0, 0, &required, &count, &resume);\r
353   error = GetLastError();\r
354   if (error != ERROR_MORE_DATA) {\r
355     print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));\r
356     return 0;\r
357   }\r
358 \r
359   ENUM_SERVICE_STATUS *status = (ENUM_SERVICE_STATUS *) HeapAlloc(GetProcessHeap(), 0, required);\r
360   if (! status) {\r
361     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("ENUM_SERVICE_STATUS"), _T("open_service()"));\r
362     return 0;\r
363   }\r
364 \r
365   bufsize = required;\r
366   while (true) {\r
367     /*\r
368       EnumServicesStatus() returns:\r
369       1 when it retrieved data and there's no more data to come.\r
370       0 and sets last error to ERROR_MORE_DATA when it retrieved data and\r
371         there's more data to come.\r
372       0 and sets last error to something else on error.\r
373     */\r
374     int ret = EnumServicesStatus(services, SERVICE_DRIVER | SERVICE_FILE_SYSTEM_DRIVER | SERVICE_KERNEL_DRIVER | SERVICE_WIN32, SERVICE_STATE_ALL, status, bufsize, &required, &count, &resume);\r
375     if (! ret) {\r
376       error = GetLastError();\r
377       if (error != ERROR_MORE_DATA) {\r
378         HeapFree(GetProcessHeap(), 0, status);\r
379         print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));\r
380         return 0;\r
381       }\r
382     }\r
383 \r
384     for (i = 0; i < count; i++) {\r
385       if (str_equiv(status[i].lpDisplayName, service_name)) {\r
386         if (_sntprintf_s(canonical_name, canonical_namelen, _TRUNCATE, _T("%s"), status[i].lpServiceName) < 0) {\r
387           HeapFree(GetProcessHeap(), 0, status);\r
388           print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canonical_name"), _T("open_service()"));\r
389           return 0;\r
390         }\r
391 \r
392         HeapFree(GetProcessHeap(), 0, status);\r
393         return open_service(services, canonical_name, access, 0, 0);\r
394       }\r
395     }\r
396 \r
397     if (ret) break;\r
398   }\r
399 \r
400   /* Recurse so we can get an error message. */\r
401   return open_service(services, service_name, access, 0, 0);\r
402 }\r
403 \r
404 QUERY_SERVICE_CONFIG *query_service_config(const TCHAR *service_name, SC_HANDLE service_handle) {\r
405   QUERY_SERVICE_CONFIG *qsc;\r
406   unsigned long bufsize;\r
407   unsigned long error;\r
408 \r
409   QueryServiceConfig(service_handle, 0, 0, &bufsize);\r
410   error = GetLastError();\r
411   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
412     qsc = (QUERY_SERVICE_CONFIG *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufsize);\r
413     if (! qsc) {\r
414       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("QUERY_SERVICE_CONFIG"), _T("query_service_config()"), 0);\r
415       return 0;\r
416     }\r
417   }\r
418   else {\r
419     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service_name, error_string(error), 0);\r
420     return 0;\r
421   }\r
422 \r
423   if (! QueryServiceConfig(service_handle, qsc, bufsize, &bufsize)) {\r
424     HeapFree(GetProcessHeap(), 0, qsc);\r
425     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service_name, error_string(GetLastError()), 0);\r
426     return 0;\r
427   }\r
428 \r
429   return qsc;\r
430 }\r
431 \r
432 /* WILL NOT allocate a new string if the identifier is already present. */\r
433 int prepend_service_group_identifier(TCHAR *group, TCHAR **canon) {\r
434   if (! group || ! group[0] || group[0] == SC_GROUP_IDENTIFIER) {\r
435     *canon = group;\r
436     return 0;\r
437   }\r
438 \r
439   size_t len = _tcslen(group) + 1;\r
440   *canon = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(TCHAR));\r
441   if (! *canon) {\r
442     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("canon"), _T("prepend_service_group_identifier()"));\r
443     return 1;\r
444   }\r
445 \r
446   TCHAR *s = *canon;\r
447   *s++ = SC_GROUP_IDENTIFIER;\r
448   memmove(s, group, len * sizeof(TCHAR));\r
449   (*canon)[len] = _T('\0');\r
450 \r
451   return 0;\r
452 }\r
453 \r
454 int append_to_dependencies(TCHAR *dependencies, unsigned long dependencieslen, TCHAR *string, TCHAR **newdependencies, unsigned long *newlen, int type) {\r
455   *newlen = 0;\r
456 \r
457   TCHAR *canon = 0;\r
458   if (type == DEPENDENCY_GROUPS) {\r
459     if (prepend_service_group_identifier(string, &canon)) return 1;\r
460   }\r
461   else canon = string;\r
462   int ret = append_to_double_null(dependencies, dependencieslen, newdependencies, newlen, canon, 0, false);\r
463   if (canon && canon != string) HeapFree(GetProcessHeap(), 0, canon);\r
464 \r
465   return ret;\r
466 }\r
467 \r
468 int remove_from_dependencies(TCHAR *dependencies, unsigned long dependencieslen, TCHAR *string, TCHAR **newdependencies, unsigned long *newlen, int type) {\r
469   *newlen = 0;\r
470 \r
471   TCHAR *canon = 0;\r
472   if (type == DEPENDENCY_GROUPS) {\r
473     if (prepend_service_group_identifier(string, &canon)) return 1;\r
474   }\r
475   else canon = string;\r
476   int ret = remove_from_double_null(dependencies, dependencieslen, newdependencies, newlen, canon, 0, false);\r
477   if (canon && canon != string) HeapFree(GetProcessHeap(), 0, canon);\r
478 \r
479   return ret;\r
480 }\r
481 \r
482 int set_service_dependencies(const TCHAR *service_name, SC_HANDLE service_handle, TCHAR *buffer) {\r
483   TCHAR *dependencies = _T("");\r
484   unsigned long num_dependencies = 0;\r
485 \r
486   if (buffer && buffer[0]) {\r
487     SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);\r
488     if (! services) {\r
489       print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
490       return 1;\r
491     }\r
492 \r
493     /*\r
494       Count the dependencies then allocate a buffer big enough for their\r
495       canonical names, ie n * SERVICE_NAME_LENGTH.\r
496     */\r
497     TCHAR *s;\r
498     TCHAR *groups = 0;\r
499     for (s = buffer; *s; s++) {\r
500       num_dependencies++;\r
501       if (*s == SC_GROUP_IDENTIFIER) groups = s;\r
502       while (*s) s++;\r
503     }\r
504 \r
505     /* At least one dependency is a group so we need to verify them. */\r
506     if (groups) {\r
507       HKEY key;\r
508       if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, NSSM_REGISTRY_GROUPS, 0, KEY_READ, &key)) {\r
509         _ftprintf(stderr, _T("%s: %s\n"), NSSM_REGISTRY_GROUPS, error_string(GetLastError()));\r
510         return 2;\r
511       }\r
512 \r
513       unsigned long type;\r
514       unsigned long groupslen;\r
515       unsigned long ret = RegQueryValueEx(key, NSSM_REG_GROUPS, 0, &type, NULL, &groupslen);\r
516       if (ret == ERROR_SUCCESS) {\r
517         groups = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, groupslen);\r
518         if (! groups) {\r
519           print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("groups"), _T("set_service_dependencies()"));\r
520           return 3;\r
521         }\r
522 \r
523         ret = RegQueryValueEx(key, NSSM_REG_GROUPS, 0, &type, (unsigned char *) groups, &groupslen);\r
524         if (ret != ERROR_SUCCESS) {\r
525           _ftprintf(stderr, _T("%s\\%s: %s"), NSSM_REGISTRY_GROUPS, NSSM_REG_GROUPS, error_string(GetLastError()));\r
526           HeapFree(GetProcessHeap(), 0, groups);\r
527           RegCloseKey(key);\r
528           return 4;\r
529         }\r
530       }\r
531       else if (ret != ERROR_FILE_NOT_FOUND) {\r
532         _ftprintf(stderr, _T("%s\\%s: %s"), NSSM_REGISTRY_GROUPS, NSSM_REG_GROUPS, error_string(GetLastError()));\r
533         RegCloseKey(key);\r
534         return 4;\r
535       }\r
536 \r
537       RegCloseKey(key);\r
538 \r
539     }\r
540 \r
541     unsigned long dependencieslen = (num_dependencies * SERVICE_NAME_LENGTH) + 2;\r
542     dependencies = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, dependencieslen * sizeof(TCHAR));\r
543     size_t i = 0;\r
544 \r
545     TCHAR dependency[SERVICE_NAME_LENGTH];\r
546     for (s = buffer; *s; s++) {\r
547       /* Group? */\r
548       if (*s == SC_GROUP_IDENTIFIER) {\r
549         TCHAR *group = s + 1;\r
550 \r
551         bool ok = false;\r
552         if (*group) {\r
553           for (TCHAR *g = groups; *g; g++) {\r
554             if (str_equiv(g, group)) {\r
555               ok = true;\r
556               /* Set canonical name. */\r
557               memmove(group, g, _tcslen(g) * sizeof(TCHAR));\r
558               break;\r
559             }\r
560 \r
561             while (*g) g++;\r
562           }\r
563         }\r
564 \r
565         if (ok) _sntprintf_s(dependency, _countof(dependency), _TRUNCATE, _T("%s"), s);\r
566         else {\r
567           HeapFree(GetProcessHeap(), 0, dependencies);\r
568           if (groups) HeapFree(GetProcessHeap(), 0, groups);\r
569           _ftprintf(stderr, _T("%s: %s"), s, error_string(ERROR_SERVICE_DEPENDENCY_DELETED));\r
570           return 5;\r
571         }\r
572       }\r
573       else {\r
574         SC_HANDLE dependency_handle = open_service(services, s, SERVICE_QUERY_STATUS, dependency, _countof(dependency));\r
575         if (! dependency_handle) {\r
576           HeapFree(GetProcessHeap(), 0, dependencies);\r
577           if (groups) HeapFree(GetProcessHeap(), 0, groups);\r
578           CloseServiceHandle(services);\r
579           _ftprintf(stderr, _T("%s: %s"), s, error_string(ERROR_SERVICE_DEPENDENCY_DELETED));\r
580           return 5;\r
581         }\r
582       }\r
583 \r
584       size_t len = _tcslen(dependency) + 1;\r
585       memmove(dependencies + i, dependency, len * sizeof(TCHAR));\r
586       i += len;\r
587 \r
588       while (*s) s++;\r
589     }\r
590 \r
591     if (groups) HeapFree(GetProcessHeap(), 0, groups);\r
592     CloseServiceHandle(services);\r
593   }\r
594 \r
595   if (! ChangeServiceConfig(service_handle, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, SERVICE_NO_CHANGE, 0, 0, 0, dependencies, 0, 0, 0)) {\r
596     if (num_dependencies) HeapFree(GetProcessHeap(), 0, dependencies);\r
597     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));\r
598     return -1;\r
599   }\r
600 \r
601   if (num_dependencies) HeapFree(GetProcessHeap(), 0, dependencies);\r
602   return 0;\r
603 }\r
604 \r
605 int get_service_dependencies(const TCHAR *service_name, SC_HANDLE service_handle, TCHAR **buffer, unsigned long *bufsize, int type) {\r
606   if (! buffer) return 1;\r
607   if (! bufsize) return 2;\r
608 \r
609   *buffer = 0;\r
610   *bufsize = 0;\r
611 \r
612   QUERY_SERVICE_CONFIG *qsc = query_service_config(service_name, service_handle);\r
613   if (! qsc) return 3;\r
614 \r
615   if (! qsc->lpDependencies || ! qsc->lpDependencies[0]) {\r
616     HeapFree(GetProcessHeap(), 0, qsc);\r
617     return 0;\r
618   }\r
619 \r
620   /* lpDependencies is doubly NULL terminated. */\r
621   while (qsc->lpDependencies[*bufsize]) {\r
622     while (qsc->lpDependencies[*bufsize]) ++*bufsize;\r
623     ++*bufsize;\r
624   }\r
625 \r
626   *bufsize += 2;\r
627 \r
628   *buffer = (TCHAR *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, *bufsize * sizeof(TCHAR));\r
629   if (! *buffer) {\r
630     *bufsize = 0;\r
631     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("lpDependencies"), _T("get_service_dependencies()"));\r
632     HeapFree(GetProcessHeap(), 0, qsc);\r
633     return 4;\r
634   }\r
635 \r
636   if (type == DEPENDENCY_ALL) memmove(*buffer, qsc->lpDependencies, *bufsize * sizeof(TCHAR));\r
637   else {\r
638     TCHAR *s;\r
639     size_t i = 0;\r
640     *bufsize = 0;\r
641     for (s = qsc->lpDependencies; *s; s++) {\r
642       /* Only copy the appropriate type of dependency. */\r
643       if ((*s == SC_GROUP_IDENTIFIER && type & DEPENDENCY_GROUPS) || (*s != SC_GROUP_IDENTIFIER && type & DEPENDENCY_SERVICES)) {\r
644         size_t len = _tcslen(s) + 1;\r
645         *bufsize += (unsigned long) len;\r
646         memmove(*buffer + i, s, len * sizeof(TCHAR));\r
647         i += len;\r
648       }\r
649 \r
650       while (*s) s++;\r
651     }\r
652     ++*bufsize;\r
653   }\r
654 \r
655   HeapFree(GetProcessHeap(), 0, qsc);\r
656 \r
657   if (! *buffer[0]) {\r
658     HeapFree(GetProcessHeap(), 0, *buffer);\r
659     *buffer = 0;\r
660     *bufsize = 0;\r
661   }\r
662 \r
663   return 0;\r
664 }\r
665 \r
666 int get_service_dependencies(const TCHAR *service_name, SC_HANDLE service_handle, TCHAR **buffer, unsigned long *bufsize) {\r
667   return get_service_dependencies(service_name, service_handle, buffer, bufsize, DEPENDENCY_ALL);\r
668 }\r
669 \r
670 int set_service_description(const TCHAR *service_name, SC_HANDLE service_handle, TCHAR *buffer) {\r
671   SERVICE_DESCRIPTION description;\r
672   ZeroMemory(&description, sizeof(description));\r
673   /*\r
674     lpDescription must be NULL if we aren't changing, the new description\r
675     or "".\r
676   */\r
677   if (buffer && buffer[0]) description.lpDescription = buffer;\r
678   else description.lpDescription = _T("");\r
679 \r
680   if (ChangeServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, &description)) return 0;\r
681 \r
682   log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service_name, error_string(GetLastError()), 0);\r
683   return 1;\r
684 }\r
685 \r
686 int get_service_description(const TCHAR *service_name, SC_HANDLE service_handle, unsigned long len, TCHAR *buffer) {\r
687   if (! buffer) return 1;\r
688 \r
689   unsigned long bufsize;\r
690   QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);\r
691   unsigned long error = GetLastError();\r
692   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
693     SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
694     if (! description) {\r
695       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("get_service_description()"));\r
696       return 2;\r
697     }\r
698 \r
699     if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {\r
700       if (description->lpDescription) _sntprintf_s(buffer, len, _TRUNCATE, _T("%s"), description->lpDescription);\r
701       else ZeroMemory(buffer, len * sizeof(TCHAR));\r
702       HeapFree(GetProcessHeap(), 0, description);\r
703       return 0;\r
704     }\r
705     else {\r
706       HeapFree(GetProcessHeap(), 0, description);\r
707       print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));\r
708       return 3;\r
709     }\r
710   }\r
711   else {\r
712     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));\r
713     return 4;\r
714   }\r
715 }\r
716 \r
717 int get_service_startup(const TCHAR *service_name, SC_HANDLE service_handle, const QUERY_SERVICE_CONFIG *qsc, unsigned long *startup) {\r
718   if (! qsc) return 1;\r
719 \r
720   switch (qsc->dwStartType) {\r
721     case SERVICE_DEMAND_START: *startup = NSSM_STARTUP_MANUAL; break;\r
722     case SERVICE_DISABLED: *startup = NSSM_STARTUP_DISABLED; break;\r
723     default: *startup = NSSM_STARTUP_AUTOMATIC;\r
724   }\r
725 \r
726   if (*startup != NSSM_STARTUP_AUTOMATIC) return 0;\r
727 \r
728   /* Check for delayed start. */\r
729   unsigned long bufsize;\r
730   unsigned long error;\r
731   QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, 0, 0, &bufsize);\r
732   error = GetLastError();\r
733   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
734     SERVICE_DELAYED_AUTO_START_INFO *info = (SERVICE_DELAYED_AUTO_START_INFO *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
735     if (! info) {\r
736       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_DELAYED_AUTO_START_INFO"), _T("get_service_startup()"));\r
737       return 2;\r
738     }\r
739 \r
740     if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (unsigned char *) info, bufsize, &bufsize)) {\r
741       if (info->fDelayedAutostart) *startup = NSSM_STARTUP_DELAYED;\r
742       HeapFree(GetProcessHeap(), 0, info);\r
743       return 0;\r
744     }\r
745     else {\r
746       error = GetLastError();\r
747       if (error != ERROR_INVALID_LEVEL) {\r
748         print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));\r
749         return 3;\r
750       }\r
751     }\r
752   }\r
753   else if (error != ERROR_INVALID_LEVEL) {\r
754     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));\r
755     return 3;\r
756   }\r
757 \r
758   return 0;\r
759 }\r
760 \r
761 int get_service_username(const TCHAR *service_name, const QUERY_SERVICE_CONFIG *qsc, TCHAR **username, size_t *usernamelen) {\r
762   if (! username) return 1;\r
763   if (! usernamelen) return 1;\r
764 \r
765   *username = 0;\r
766   *usernamelen = 0;\r
767 \r
768   if (! qsc) return 1;\r
769 \r
770   if (qsc->lpServiceStartName[0]) {\r
771     if (is_localsystem(qsc->lpServiceStartName)) return 0;\r
772 \r
773     size_t len = _tcslen(qsc->lpServiceStartName);\r
774     *username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(TCHAR));\r
775     if (! *username) {\r
776       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("username"), _T("get_service_username()"));\r
777       return 2;\r
778     }\r
779 \r
780     memmove(*username, qsc->lpServiceStartName, (len + 1) * sizeof(TCHAR));\r
781     *usernamelen = len;\r
782   }\r
783 \r
784   return 0;\r
785 }\r
786 \r
787 /* Set default values which aren't zero. */\r
788 void set_nssm_service_defaults(nssm_service_t *service) {\r
789   if (! service) return;\r
790 \r
791   service->type = SERVICE_WIN32_OWN_PROCESS;\r
792   service->priority = NORMAL_PRIORITY_CLASS;\r
793   service->stdin_sharing = NSSM_STDIN_SHARING;\r
794   service->stdin_disposition = NSSM_STDIN_DISPOSITION;\r
795   service->stdin_flags = NSSM_STDIN_FLAGS;\r
796   service->stdout_sharing = NSSM_STDOUT_SHARING;\r
797   service->stdout_disposition = NSSM_STDOUT_DISPOSITION;\r
798   service->stdout_flags = NSSM_STDOUT_FLAGS;\r
799   service->stderr_sharing = NSSM_STDERR_SHARING;\r
800   service->stderr_disposition = NSSM_STDERR_DISPOSITION;\r
801   service->stderr_flags = NSSM_STDERR_FLAGS;\r
802   service->throttle_delay = NSSM_RESET_THROTTLE_RESTART;\r
803   service->stop_method = ~0;\r
804   service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
805   service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
806   service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
807   service->kill_process_tree = 1;\r
808 }\r
809 \r
810 /* Allocate and zero memory for a service. */\r
811 nssm_service_t *alloc_nssm_service() {\r
812   nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));\r
813   if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("alloc_nssm_service()"), 0);\r
814   return service;\r
815 }\r
816 \r
817 /* Free memory for a service. */\r
818 void cleanup_nssm_service(nssm_service_t *service) {\r
819   if (! service) return;\r
820   if (service->username) HeapFree(GetProcessHeap(), 0, service->username);\r
821   if (service->password) {\r
822     SecureZeroMemory(service->password, service->passwordlen * sizeof(TCHAR));\r
823     HeapFree(GetProcessHeap(), 0, service->password);\r
824   }\r
825   if (service->dependencies) HeapFree(GetProcessHeap(), 0, service->dependencies);\r
826   if (service->env) HeapFree(GetProcessHeap(), 0, service->env);\r
827   if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);\r
828   if (service->handle) CloseServiceHandle(service->handle);\r
829   if (service->process_handle) CloseHandle(service->process_handle);\r
830   if (service->wait_handle) UnregisterWait(service->wait_handle);\r
831   if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);\r
832   if (service->throttle_timer) CloseHandle(service->throttle_timer);\r
833   if (service->hook_section_initialised) DeleteCriticalSection(&service->hook_section);\r
834   if (service->initial_env) HeapFree(GetProcessHeap(), 0, service->initial_env);\r
835   HeapFree(GetProcessHeap(), 0, service);\r
836 }\r
837 \r
838 /* About to install the service */\r
839 int pre_install_service(int argc, TCHAR **argv) {\r
840   nssm_service_t *service = alloc_nssm_service();\r
841   set_nssm_service_defaults(service);\r
842   if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
843 \r
844   /* Show the dialogue box if we didn't give the service name and path */\r
845   if (argc < 2) return nssm_gui(IDD_INSTALL, service);\r
846 \r
847   if (! service) {\r
848     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("service"), _T("pre_install_service()"));\r
849     return 1;\r
850   }\r
851   _sntprintf_s(service->exe, _countof(service->exe), _TRUNCATE, _T("%s"), argv[1]);\r
852 \r
853   /* Arguments are optional */\r
854   size_t flagslen = 0;\r
855   size_t s = 0;\r
856   int i;\r
857   for (i = 2; i < argc; i++) flagslen += _tcslen(argv[i]) + 1;\r
858   if (! flagslen) flagslen = 1;\r
859   if (flagslen > _countof(service->flags)) {\r
860     print_message(stderr, NSSM_MESSAGE_FLAGS_TOO_LONG);\r
861     return 2;\r
862   }\r
863 \r
864   for (i = 2; i < argc; i++) {\r
865     size_t len = _tcslen(argv[i]);\r
866     memmove(service->flags + s, argv[i], len * sizeof(TCHAR));\r
867     s += len;\r
868     if (i < argc - 1) service->flags[s++] = _T(' ');\r
869   }\r
870 \r
871   /* Work out directory name */\r
872   _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);\r
873   strip_basename(service->dir);\r
874 \r
875   int ret = install_service(service);\r
876   cleanup_nssm_service(service);\r
877   return ret;\r
878 }\r
879 \r
880 /* About to edit the service. */\r
881 int pre_edit_service(int argc, TCHAR **argv) {\r
882   /* Require service name. */\r
883   if (argc < 2) return usage(1);\r
884 \r
885   /* Are we editing on the command line? */\r
886   enum { MODE_EDITING, MODE_GETTING, MODE_SETTING, MODE_RESETTING, MODE_DUMPING } mode = MODE_EDITING;\r
887   const TCHAR *verb = argv[0];\r
888   const TCHAR *service_name = argv[1];\r
889   bool getting = false;\r
890   bool unsetting = false;\r
891 \r
892   /* Minimum number of arguments. */\r
893   int mandatory = 2;\r
894   /* Index of first value. */\r
895   int remainder = 3;\r
896   int i;\r
897   if (str_equiv(verb, _T("get"))) {\r
898     mandatory = 3;\r
899     mode = MODE_GETTING;\r
900   }\r
901   else if (str_equiv(verb, _T("set"))) {\r
902     mandatory = 4;\r
903     mode = MODE_SETTING;\r
904   }\r
905   else if (str_equiv(verb, _T("reset")) || str_equiv(verb, _T("unset"))) {\r
906     mandatory = 3;\r
907     mode = MODE_RESETTING;\r
908   }\r
909   else if (str_equiv(verb, _T("dump"))) {\r
910     mandatory = 1;\r
911     remainder = 2;\r
912     mode = MODE_DUMPING;\r
913   }\r
914   if (argc < mandatory) return usage(1);\r
915 \r
916   const TCHAR *parameter = 0;\r
917   settings_t *setting = 0;\r
918   TCHAR *additional;\r
919 \r
920   /* Validate the parameter. */\r
921   if (mandatory > 2) {\r
922     bool additional_mandatory = false;\r
923 \r
924     parameter = argv[2];\r
925     for (i = 0; settings[i].name; i++) {\r
926       setting = &settings[i];\r
927       if (! str_equiv(setting->name, parameter)) continue;\r
928       if (((setting->additional & ADDITIONAL_GETTING) && mode == MODE_GETTING) || ((setting->additional & ADDITIONAL_SETTING) && mode == MODE_SETTING) || ((setting->additional & ADDITIONAL_RESETTING) && mode == MODE_RESETTING)) {\r
929         additional_mandatory = true;\r
930         mandatory++;\r
931       }\r
932       break;\r
933     }\r
934     if (! settings[i].name) {\r
935       print_message(stderr, NSSM_MESSAGE_INVALID_PARAMETER, parameter);\r
936       for (i = 0; settings[i].name; i++) _ftprintf(stderr, _T("%s\n"), settings[i].name);\r
937       return 1;\r
938     }\r
939 \r
940     additional = 0;\r
941     if (additional_mandatory) {\r
942       if (argc < mandatory) {\r
943         print_message(stderr, NSSM_MESSAGE_MISSING_SUBPARAMETER, parameter);\r
944         return 1;\r
945       }\r
946       additional = argv[3];\r
947       remainder = 4;\r
948     }\r
949     else if (str_equiv(setting->name, NSSM_NATIVE_OBJECTNAME) && mode == MODE_SETTING) {\r
950       additional = argv[3];\r
951       remainder = 4;\r
952     }\r
953     else {\r
954       additional = argv[remainder];\r
955       if (argc < mandatory) return usage(1);\r
956     }\r
957   }\r
958 \r
959   nssm_service_t *service = alloc_nssm_service();\r
960   _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), service_name);\r
961 \r
962   /* Open service manager */\r
963   SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);\r
964   if (! services) {\r
965     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
966     return 2;\r
967   }\r
968 \r
969   /* Try to open the service */\r
970   unsigned long access = SERVICE_QUERY_CONFIG;\r
971   if (mode != MODE_GETTING) access |= SERVICE_CHANGE_CONFIG;\r
972   service->handle = open_service(services, service->name, access, service->name, _countof(service->name));\r
973   if (! service->handle) {\r
974     CloseServiceHandle(services);\r
975     return 3;\r
976   }\r
977 \r
978   /* Get system details. */\r
979   QUERY_SERVICE_CONFIG *qsc = query_service_config(service->name, service->handle);\r
980   if (! qsc) {\r
981     CloseServiceHandle(service->handle);\r
982     CloseServiceHandle(services);\r
983     return 4;\r
984   }\r
985 \r
986   service->type = qsc->dwServiceType;\r
987   if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {\r
988     if (mode != MODE_GETTING && mode != MODE_DUMPING) {\r
989       HeapFree(GetProcessHeap(), 0, qsc);\r
990       CloseServiceHandle(service->handle);\r
991       CloseServiceHandle(services);\r
992       print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, NSSM_WIN32_OWN_PROCESS, 0);\r
993       return 3;\r
994     }\r
995   }\r
996 \r
997   if (get_service_startup(service->name, service->handle, qsc, &service->startup)) {\r
998     if (mode != MODE_GETTING && mode != MODE_DUMPING) {\r
999       HeapFree(GetProcessHeap(), 0, qsc);\r
1000       CloseServiceHandle(service->handle);\r
1001       CloseServiceHandle(services);\r
1002       return 4;\r
1003     }\r
1004   }\r
1005 \r
1006   if (get_service_username(service->name, qsc, &service->username, &service->usernamelen)) {\r
1007     if (mode != MODE_GETTING && mode != MODE_DUMPING) {\r
1008       HeapFree(GetProcessHeap(), 0, qsc);\r
1009       CloseServiceHandle(service->handle);\r
1010       CloseServiceHandle(services);\r
1011       return 5;\r
1012     }\r
1013   }\r
1014 \r
1015   _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);\r
1016 \r
1017   /* Get the canonical service name. We open it case insensitively. */\r
1018   unsigned long bufsize = _countof(service->name);\r
1019   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
1020 \r
1021   /* Remember the executable in case it isn't NSSM. */\r
1022   _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);\r
1023   HeapFree(GetProcessHeap(), 0, qsc);\r
1024 \r
1025   /* Get extended system details. */\r
1026   if (get_service_description(service->name, service->handle, _countof(service->description), service->description)) {\r
1027     if (mode != MODE_GETTING && mode != MODE_DUMPING) {\r
1028       CloseServiceHandle(service->handle);\r
1029       CloseServiceHandle(services);\r
1030       return 6;\r
1031     }\r
1032   }\r
1033 \r
1034   if (get_service_dependencies(service->name, service->handle, &service->dependencies, &service->dependencieslen)) {\r
1035     if (mode != MODE_GETTING && mode != MODE_DUMPING) {\r
1036       CloseServiceHandle(service->handle);\r
1037       CloseServiceHandle(services);\r
1038       return 7;\r
1039     }\r
1040   }\r
1041 \r
1042   /* Get NSSM details. */\r
1043   get_parameters(service, 0);\r
1044 \r
1045   CloseServiceHandle(services);\r
1046 \r
1047   if (! service->exe[0]) {\r
1048     service->native = true;\r
1049     if (mode != MODE_GETTING && mode != MODE_DUMPING) print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);\r
1050   }\r
1051 \r
1052   /* Editing with the GUI. */\r
1053   if (mode == MODE_EDITING) {\r
1054     nssm_gui(IDD_EDIT, service);\r
1055     return 0;\r
1056   }\r
1057 \r
1058   HKEY key;\r
1059   value_t value;\r
1060   int ret;\r
1061 \r
1062   if (mode == MODE_DUMPING) {\r
1063     TCHAR *service_name = service->name;\r
1064     if (argc > remainder) service_name = argv[remainder];\r
1065     if (service->native) key = 0;\r
1066     else {\r
1067       key = open_registry(service->name, KEY_READ);\r
1068       if (! key) return 4;\r
1069     }\r
1070 \r
1071     TCHAR quoted_service_name[SERVICE_NAME_LENGTH * 2];\r
1072     TCHAR quoted_exe[EXE_LENGTH * 2];\r
1073     TCHAR quoted_nssm[EXE_LENGTH * 2];\r
1074     if (quote(service_name, quoted_service_name, _countof(quoted_service_name))) return 5;\r
1075     if (quote(service->exe, quoted_exe, _countof(quoted_exe))) return 6;\r
1076     if (quote(nssm_exe(), quoted_nssm, _countof(quoted_nssm))) return 6;\r
1077     _tprintf(_T("%s install %s %s\n"), quoted_nssm, quoted_service_name, quoted_exe);\r
1078 \r
1079     ret = 0;\r
1080     for (i = 0; settings[i].name; i++) {\r
1081       setting = &settings[i];\r
1082       if (! setting->native && service->native) continue;\r
1083       if (dump_setting(service_name, key, service->handle, setting)) ret++;\r
1084     }\r
1085 \r
1086     if (! service->native) RegCloseKey(key);\r
1087     CloseServiceHandle(service->handle);\r
1088 \r
1089     if (ret) return 1;\r
1090     return 0;\r
1091   }\r
1092 \r
1093   /* Trying to manage App* parameters for a non-NSSM service. */\r
1094   if (! setting->native && service->native) {\r
1095     CloseServiceHandle(service->handle);\r
1096     print_message(stderr, NSSM_MESSAGE_NATIVE_PARAMETER, setting->name, NSSM);\r
1097     return 1;\r
1098   }\r
1099 \r
1100   if (mode == MODE_GETTING) {\r
1101     if (! service->native) {\r
1102       key = open_registry(service->name, KEY_READ);\r
1103       if (! key) return 4;\r
1104     }\r
1105 \r
1106     if (setting->native) ret = get_setting(service->name, service->handle, setting, &value, additional);\r
1107     else ret = get_setting(service->name, key, setting, &value, additional);\r
1108     if (ret < 0) {\r
1109       CloseServiceHandle(service->handle);\r
1110       return 5;\r
1111     }\r
1112 \r
1113     switch (setting->type) {\r
1114       case REG_EXPAND_SZ:\r
1115       case REG_MULTI_SZ:\r
1116       case REG_SZ:\r
1117         _tprintf(_T("%s\n"), value.string ? value.string : _T(""));\r
1118         HeapFree(GetProcessHeap(), 0, value.string);\r
1119         break;\r
1120 \r
1121       case REG_DWORD:\r
1122         _tprintf(_T("%lu\n"), value.numeric);\r
1123         break;\r
1124     }\r
1125 \r
1126     if (! service->native) RegCloseKey(key);\r
1127     CloseServiceHandle(service->handle);\r
1128     return 0;\r
1129   }\r
1130 \r
1131   /* Build the value. */\r
1132   if (mode == MODE_RESETTING) {\r
1133     /* Unset the parameter. */\r
1134     value.string = 0;\r
1135   }\r
1136   else if (remainder == argc) {\r
1137     value.string = 0;\r
1138   }\r
1139   else {\r
1140     /* Set the parameter. */\r
1141     size_t len = 0;\r
1142     size_t delimiterlen = (setting->additional & ADDITIONAL_CRLF) ? 2 : 1;\r
1143     for (i = remainder; i < argc; i++) len += _tcslen(argv[i]) + delimiterlen;\r
1144     len++;\r
1145 \r
1146     value.string = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));\r
1147     if (! value.string) {\r
1148       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("value"), _T("edit_service()"));\r
1149       CloseServiceHandle(service->handle);\r
1150       return 2;\r
1151     }\r
1152 \r
1153     size_t s = 0;\r
1154     for (i = remainder; i < argc; i++) {\r
1155       size_t len = _tcslen(argv[i]);\r
1156       memmove(value.string + s, argv[i], len * sizeof(TCHAR));\r
1157       s += len;\r
1158       if (i < argc - 1) {\r
1159         if (setting->additional & ADDITIONAL_CRLF) {\r
1160           value.string[s++] = _T('\r');\r
1161           value.string[s++] = _T('\n');\r
1162         }\r
1163         else value.string[s++] = _T(' ');\r
1164       }\r
1165     }\r
1166     value.string[s] = _T('\0');\r
1167   }\r
1168 \r
1169   if (! service->native) {\r
1170     key = open_registry(service->name, KEY_READ | KEY_WRITE);\r
1171     if (! key) {\r
1172       if (value.string) HeapFree(GetProcessHeap(), 0, value.string);\r
1173       return 4;\r
1174     }\r
1175   }\r
1176 \r
1177   if (setting->native) ret = set_setting(service->name, service->handle, setting, &value, additional);\r
1178   else ret = set_setting(service->name, key, setting, &value, additional);\r
1179   if (value.string) HeapFree(GetProcessHeap(), 0, value.string);\r
1180   if (ret < 0) {\r
1181     if (! service->native) RegCloseKey(key);\r
1182     CloseServiceHandle(service->handle);\r
1183     return 6;\r
1184   }\r
1185 \r
1186   if (! service->native) RegCloseKey(key);\r
1187   CloseServiceHandle(service->handle);\r
1188 \r
1189   return 0;\r
1190 }\r
1191 \r
1192 /* About to remove the service */\r
1193 int pre_remove_service(int argc, TCHAR **argv) {\r
1194   nssm_service_t *service = alloc_nssm_service();\r
1195   set_nssm_service_defaults(service);\r
1196   if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
1197 \r
1198   /* Show dialogue box if we didn't pass service name and "confirm" */\r
1199   if (argc < 2) return nssm_gui(IDD_REMOVE, service);\r
1200   if (str_equiv(argv[1], _T("confirm"))) {\r
1201     int ret = remove_service(service);\r
1202     cleanup_nssm_service(service);\r
1203     return ret;\r
1204   }\r
1205   print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);\r
1206   return 100;\r
1207 }\r
1208 \r
1209 /* Install the service */\r
1210 int install_service(nssm_service_t *service) {\r
1211   if (! service) return 1;\r
1212 \r
1213   /* Open service manager */\r
1214   SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE);\r
1215   if (! services) {\r
1216     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
1217     cleanup_nssm_service(service);\r
1218     return 2;\r
1219   }\r
1220 \r
1221   /* Get path of this program */\r
1222   _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), nssm_imagepath());\r
1223 \r
1224   /* Create the service - settings will be changed in edit_service() */\r
1225   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
1226   if (! service->handle) {\r
1227     print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED, error_string(GetLastError()));\r
1228     CloseServiceHandle(services);\r
1229     return 5;\r
1230   }\r
1231 \r
1232   if (edit_service(service, false)) {\r
1233     DeleteService(service->handle);\r
1234     CloseServiceHandle(services);\r
1235     return 6;\r
1236   }\r
1237 \r
1238   print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);\r
1239 \r
1240   /* Cleanup */\r
1241   CloseServiceHandle(services);\r
1242 \r
1243   return 0;\r
1244 }\r
1245 \r
1246 /* Edit the service. */\r
1247 int edit_service(nssm_service_t *service, bool editing) {\r
1248   if (! service) return 1;\r
1249 \r
1250   /*\r
1251     The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS\r
1252     and SERVICE_INTERACTIVE_PROCESS.\r
1253   */\r
1254   service->type &= SERVICE_INTERACTIVE_PROCESS;\r
1255   service->type |= SERVICE_WIN32_OWN_PROCESS;\r
1256 \r
1257   /* Startup type. */\r
1258   unsigned long startup;\r
1259   switch (service->startup) {\r
1260     case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;\r
1261     case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;\r
1262     default: startup = SERVICE_AUTO_START;\r
1263   }\r
1264 \r
1265   /* Display name. */\r
1266   if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);\r
1267 \r
1268   /*\r
1269     Username must be NULL if we aren't changing or an account name.\r
1270     We must explicitly use LOCALSYSTEM to change it when we are editing.\r
1271     Password must be NULL if we aren't changing, a password or "".\r
1272     Empty passwords are valid but we won't allow them in the GUI.\r
1273   */\r
1274   TCHAR *username = 0;\r
1275   TCHAR *canon = 0;\r
1276   TCHAR *password = 0;\r
1277   if (service->usernamelen) {\r
1278     username = service->username;\r
1279     if (canonicalise_username(username, &canon)) return 5;\r
1280     if (service->passwordlen) password = service->password;\r
1281   }\r
1282   else if (editing) username = canon = NSSM_LOCALSYSTEM_ACCOUNT;\r
1283 \r
1284   if (well_known_username(canon)) password = _T("");\r
1285   else {\r
1286     if (grant_logon_as_service(canon)) {\r
1287       if (canon != username) HeapFree(GetProcessHeap(), 0, canon);\r
1288       print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);\r
1289       return 5;\r
1290     }\r
1291   }\r
1292 \r
1293   TCHAR *dependencies = _T("");\r
1294   if (service->dependencieslen) dependencies = 0; /* Change later. */\r
1295 \r
1296   if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, dependencies, canon, password, service->displayname)) {\r
1297     if (canon != username) HeapFree(GetProcessHeap(), 0, canon);\r
1298     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));\r
1299     return 5;\r
1300   }\r
1301   if (canon != username) HeapFree(GetProcessHeap(), 0, canon);\r
1302 \r
1303   if (service->dependencieslen) {\r
1304     if (set_service_dependencies(service->name, service->handle, service->dependencies)) return 5;\r
1305   }\r
1306 \r
1307   if (service->description[0] || editing) {\r
1308     set_service_description(service->name, service->handle, service->description);\r
1309   }\r
1310 \r
1311   SERVICE_DELAYED_AUTO_START_INFO delayed;\r
1312   ZeroMemory(&delayed, sizeof(delayed));\r
1313   if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;\r
1314   else delayed.fDelayedAutostart = 0;\r
1315   /* Delayed startup isn't supported until Vista. */\r
1316   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {\r
1317     unsigned long error = GetLastError();\r
1318     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
1319     if (error != ERROR_INVALID_LEVEL) {\r
1320       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);\r
1321     }\r
1322   }\r
1323 \r
1324   /* Don't mess with parameters which aren't ours. */\r
1325   if (! service->native) {\r
1326     /* Now we need to put the parameters into the registry */\r
1327     if (create_parameters(service, editing)) {\r
1328       print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);\r
1329       return 6;\r
1330     }\r
1331 \r
1332     set_service_recovery(service);\r
1333   }\r
1334 \r
1335   return 0;\r
1336 }\r
1337 \r
1338 /* Control a service. */\r
1339 int control_service(unsigned long control, int argc, TCHAR **argv) {\r
1340   if (argc < 1) return usage(1);\r
1341   TCHAR *service_name = argv[0];\r
1342   TCHAR canonical_name[SERVICE_NAME_LENGTH];\r
1343 \r
1344   SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);\r
1345   if (! services) {\r
1346     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
1347     return 2;\r
1348   }\r
1349 \r
1350   unsigned long access = SERVICE_QUERY_STATUS;\r
1351   switch (control) {\r
1352     case NSSM_SERVICE_CONTROL_START:\r
1353       access |= SERVICE_START;\r
1354     break;\r
1355 \r
1356     case SERVICE_CONTROL_CONTINUE:\r
1357     case SERVICE_CONTROL_PAUSE:\r
1358       access |= SERVICE_PAUSE_CONTINUE;\r
1359       break;\r
1360 \r
1361     case SERVICE_CONTROL_STOP:\r
1362       access |= SERVICE_STOP;\r
1363       break;\r
1364 \r
1365     case NSSM_SERVICE_CONTROL_ROTATE:\r
1366       access |= SERVICE_USER_DEFINED_CONTROL;\r
1367       break;\r
1368   }\r
1369 \r
1370   SC_HANDLE service_handle = open_service(services, service_name, access, canonical_name, _countof(canonical_name));\r
1371   if (! service_handle) {\r
1372     CloseServiceHandle(services);\r
1373     return 3;\r
1374   }\r
1375 \r
1376   int ret;\r
1377   unsigned long error;\r
1378   SERVICE_STATUS service_status;\r
1379   if (control == NSSM_SERVICE_CONTROL_START) {\r
1380     unsigned long initial_status = SERVICE_STOPPED;\r
1381     ret = StartService(service_handle, (unsigned long) argc, (const TCHAR **) argv);\r
1382     error = GetLastError();\r
1383     CloseServiceHandle(services);\r
1384 \r
1385     if (error == ERROR_IO_PENDING) {\r
1386       /*\r
1387         Older versions of Windows return immediately with ERROR_IO_PENDING\r
1388         indicate that the operation is still in progress.  Newer versions\r
1389         will return it if there really is a delay.\r
1390       */\r
1391       ret = 1;\r
1392       error = ERROR_SUCCESS;\r
1393     }\r
1394 \r
1395     if (ret) {\r
1396       int response = await_service_control_response(control, service_handle, &service_status, initial_status);\r
1397       CloseServiceHandle(service_handle);\r
1398 \r
1399       if (response) {\r
1400         print_message(stderr, NSSM_MESSAGE_BAD_CONTROL_RESPONSE, canonical_name, service_status_text(service_status.dwCurrentState), service_control_text(control));\r
1401         return 1;\r
1402       }\r
1403       else _tprintf(_T("%s: %s: %s"), canonical_name, service_control_text(control), error_string(error));\r
1404       return 0;\r
1405     }\r
1406     else {\r
1407       CloseServiceHandle(service_handle);\r
1408       _ftprintf(stderr, _T("%s: %s: %s"), canonical_name, service_control_text(control), error_string(error));\r
1409       return 1;\r
1410     }\r
1411   }\r
1412   else if (control == SERVICE_CONTROL_INTERROGATE) {\r
1413     /*\r
1414       We could actually send an INTERROGATE control but that won't return\r
1415       any information if the service is stopped and we don't care about\r
1416       the extra details it might give us in any case.  So we'll fake it.\r
1417     */\r
1418     ret = QueryServiceStatus(service_handle, &service_status);\r
1419     error = GetLastError();\r
1420 \r
1421     if (ret) {\r
1422       _tprintf(_T("%s\n"), service_status_text(service_status.dwCurrentState));\r
1423       return 0;\r
1424     }\r
1425     else {\r
1426       _ftprintf(stderr, _T("%s: %s\n"), canonical_name, error_string(error));\r
1427       return 1;\r
1428     }\r
1429   }\r
1430   else {\r
1431     ret = ControlService(service_handle, control, &service_status);\r
1432     unsigned long initial_status = service_status.dwCurrentState;\r
1433     error = GetLastError();\r
1434     CloseServiceHandle(services);\r
1435 \r
1436     if (error == ERROR_IO_PENDING) {\r
1437       ret = 1;\r
1438       error = ERROR_SUCCESS;\r
1439     }\r
1440 \r
1441     if (ret) {\r
1442       int response = await_service_control_response(control, service_handle, &service_status, initial_status);\r
1443       CloseServiceHandle(service_handle);\r
1444 \r
1445       if (response) {\r
1446         print_message(stderr, NSSM_MESSAGE_BAD_CONTROL_RESPONSE, canonical_name, service_status_text(service_status.dwCurrentState), service_control_text(control));\r
1447         return 1;\r
1448       }\r
1449       else _tprintf(_T("%s: %s: %s"), canonical_name, service_control_text(control), error_string(error));\r
1450       return 0;\r
1451     }\r
1452     else {\r
1453       CloseServiceHandle(service_handle);\r
1454       _ftprintf(stderr, _T("%s: %s: %s"), canonical_name, service_control_text(control), error_string(error));\r
1455       if (error == ERROR_SERVICE_NOT_ACTIVE) {\r
1456         if (control == SERVICE_CONTROL_SHUTDOWN || control == SERVICE_CONTROL_STOP) return 0;\r
1457       }\r
1458       return 1;\r
1459     }\r
1460   }\r
1461 }\r
1462 \r
1463 /* Remove the service */\r
1464 int remove_service(nssm_service_t *service) {\r
1465   if (! service) return 1;\r
1466 \r
1467   /* Open service manager */\r
1468   SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);\r
1469   if (! services) {\r
1470     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
1471     return 2;\r
1472   }\r
1473 \r
1474   /* Try to open the service */\r
1475   service->handle = open_service(services, service->name, DELETE, service->name, _countof(service->name));\r
1476   if (! service->handle) {\r
1477     CloseServiceHandle(services);\r
1478     return 3;\r
1479   }\r
1480 \r
1481   /* Get the canonical service name. We open it case insensitively. */\r
1482   unsigned long bufsize = _countof(service->displayname);\r
1483   GetServiceDisplayName(services, service->name, service->displayname, &bufsize);\r
1484   bufsize = _countof(service->name);\r
1485   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
1486 \r
1487   /* Try to delete the service */\r
1488   if (! DeleteService(service->handle)) {\r
1489     print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);\r
1490     CloseServiceHandle(services);\r
1491     return 4;\r
1492   }\r
1493 \r
1494   /* Cleanup */\r
1495   CloseServiceHandle(services);\r
1496 \r
1497   print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);\r
1498   return 0;\r
1499 }\r
1500 \r
1501 /* Service initialisation */\r
1502 void WINAPI service_main(unsigned long argc, TCHAR **argv) {\r
1503   nssm_service_t *service = alloc_nssm_service();\r
1504   if (! service) return;\r
1505 \r
1506   if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {\r
1507     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);\r
1508     return;\r
1509   }\r
1510 \r
1511   /* We can use a condition variable in a critical section on Vista or later. */\r
1512   if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;\r
1513   else use_critical_section = false;\r
1514 \r
1515   /* Initialise status */\r
1516   ZeroMemory(&service->status, sizeof(service->status));\r
1517   service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
1518   service->status.dwControlsAccepted = 0;\r
1519   service->status.dwWin32ExitCode = NO_ERROR;\r
1520   service->status.dwServiceSpecificExitCode = 0;\r
1521   service->status.dwCheckPoint = 0;\r
1522   service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
1523 \r
1524   /* Signal we AREN'T running the server */\r
1525   service->process_handle = 0;\r
1526   service->pid = 0;\r
1527 \r
1528   /* Register control handler */\r
1529   service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);\r
1530   if (! service->status_handle) {\r
1531     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);\r
1532     return;\r
1533   }\r
1534 \r
1535   log_service_control(service->name, 0, true);\r
1536 \r
1537   service->status.dwCurrentState = SERVICE_START_PENDING;\r
1538   service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;\r
1539   SetServiceStatus(service->status_handle, &service->status);\r
1540 \r
1541   if (is_admin) {\r
1542     /* Try to create the exit action parameters; we don't care if it fails */\r
1543     create_exit_action(service->name, exit_action_strings[0], false);\r
1544 \r
1545     SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT);\r
1546     if (services) {\r
1547       service->handle = open_service(services, service->name, SERVICE_CHANGE_CONFIG, 0, 0);\r
1548       set_service_recovery(service);\r
1549 \r
1550       /* Remember our display name. */\r
1551       unsigned long displayname_len = _countof(service->displayname);\r
1552       GetServiceDisplayName(services, service->name, service->displayname, &displayname_len);\r
1553 \r
1554       CloseServiceHandle(services);\r
1555     }\r
1556   }\r
1557 \r
1558   /* Used for signalling a resume if the service pauses when throttled. */\r
1559   if (use_critical_section) {\r
1560     InitializeCriticalSection(&service->throttle_section);\r
1561     service->throttle_section_initialised = true;\r
1562   }\r
1563   else {\r
1564     service->throttle_timer = CreateWaitableTimer(0, 1, 0);\r
1565     if (! service->throttle_timer) {\r
1566       log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);\r
1567     }\r
1568   }\r
1569 \r
1570   /* Critical section for hooks. */\r
1571   InitializeCriticalSection(&service->hook_section);\r
1572   service->hook_section_initialised = true;\r
1573 \r
1574   /* Remember our initial environment. */\r
1575   service->initial_env = copy_environment();\r
1576 \r
1577   /* Remember our creation time. */\r
1578   if (get_process_creation_time(GetCurrentProcess(), &service->nssm_creation_time)) ZeroMemory(&service->nssm_creation_time, sizeof(service->nssm_creation_time));\r
1579 \r
1580   service->allow_restart = true;\r
1581   if (! CreateThread(NULL, 0, launch_service, (void *) service, 0, NULL)) {\r
1582     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
1583     stop_service(service, 0, true, true);\r
1584   }\r
1585 }\r
1586 \r
1587 /* Make sure service recovery actions are taken where necessary */\r
1588 void set_service_recovery(nssm_service_t *service) {\r
1589   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
1590   ZeroMemory(&flag, sizeof(flag));\r
1591   flag.fFailureActionsOnNonCrashFailures = true;\r
1592 \r
1593   /* This functionality was added in Vista so the call may fail */\r
1594   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {\r
1595     unsigned long error = GetLastError();\r
1596     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
1597     if (error != ERROR_INVALID_LEVEL) {\r
1598       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);\r
1599     }\r
1600   }\r
1601 }\r
1602 \r
1603 int monitor_service(nssm_service_t *service) {\r
1604   /* Set service status to started */\r
1605   int ret = start_service(service);\r
1606   if (ret) {\r
1607     TCHAR code[16];\r
1608     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);\r
1609     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);\r
1610     return ret;\r
1611   }\r
1612   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);\r
1613 \r
1614   /* Monitor service */\r
1615   if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
1616     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);\r
1617   }\r
1618 \r
1619   return 0;\r
1620 }\r
1621 \r
1622 TCHAR *service_control_text(unsigned long control) {\r
1623   switch (control) {\r
1624     /* HACK: there is no SERVICE_CONTROL_START constant */\r
1625     case NSSM_SERVICE_CONTROL_START: return _T("START");\r
1626     case SERVICE_CONTROL_STOP: return _T("STOP");\r
1627     case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");\r
1628     case SERVICE_CONTROL_PAUSE: return _T("PAUSE");\r
1629     case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");\r
1630     case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");\r
1631     case NSSM_SERVICE_CONTROL_ROTATE: return _T("ROTATE");\r
1632     case SERVICE_CONTROL_POWEREVENT: return _T("POWEREVENT");\r
1633     default: return 0;\r
1634   }\r
1635 }\r
1636 \r
1637 TCHAR *service_status_text(unsigned long status) {\r
1638   switch (status) {\r
1639     case SERVICE_STOPPED: return _T("SERVICE_STOPPED");\r
1640     case SERVICE_START_PENDING: return _T("SERVICE_START_PENDING");\r
1641     case SERVICE_STOP_PENDING: return _T("SERVICE_STOP_PENDING");\r
1642     case SERVICE_RUNNING: return _T("SERVICE_RUNNING");\r
1643     case SERVICE_CONTINUE_PENDING: return _T("SERVICE_CONTINUE_PENDING");\r
1644     case SERVICE_PAUSE_PENDING: return _T("SERVICE_PAUSE_PENDING");\r
1645     case SERVICE_PAUSED: return _T("SERVICE_PAUSED");\r
1646     default: return 0;\r
1647   }\r
1648 }\r
1649 \r
1650 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {\r
1651   TCHAR *text = service_control_text(control);\r
1652   unsigned long event;\r
1653 \r
1654   if (! text) {\r
1655     /* "0x" + 8 x hex + NULL */\r
1656     text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));\r
1657     if (! text) {\r
1658       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
1659       return;\r
1660     }\r
1661     if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {\r
1662       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
1663       HeapFree(GetProcessHeap(), 0, text);\r
1664       return;\r
1665     }\r
1666 \r
1667     event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;\r
1668   }\r
1669   else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;\r
1670   else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;\r
1671 \r
1672   log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);\r
1673 \r
1674   if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {\r
1675     HeapFree(GetProcessHeap(), 0, text);\r
1676   }\r
1677 }\r
1678 \r
1679 /* Service control handler */\r
1680 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
1681   nssm_service_t *service = (nssm_service_t *) context;\r
1682 \r
1683   switch (control) {\r
1684     case SERVICE_CONTROL_INTERROGATE:\r
1685       /* We always keep the service status up-to-date so this is a no-op. */\r
1686       return NO_ERROR;\r
1687 \r
1688     case SERVICE_CONTROL_SHUTDOWN:\r
1689     case SERVICE_CONTROL_STOP:\r
1690       service->last_control = control;\r
1691       log_service_control(service->name, control, true);\r
1692 \r
1693       /* Immediately block further controls. */\r
1694       service->allow_restart = false;\r
1695       service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1696       service->status.dwControlsAccepted = 0;\r
1697       SetServiceStatus(service->status_handle, &service->status);\r
1698 \r
1699       /* Pre-stop hook. */\r
1700       nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_STOP, NSSM_HOOK_ACTION_PRE, &control, NSSM_SERVICE_STATUS_DEADLINE, false);\r
1701 \r
1702       /*\r
1703         We MUST acknowledge the stop request promptly but we're committed to\r
1704         waiting for the application to exit.  Spawn a new thread to wait\r
1705         while we acknowledge the request.\r
1706       */\r
1707       if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {\r
1708         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
1709 \r
1710         /*\r
1711           We couldn't create a thread to tidy up so we'll have to force the tidyup\r
1712           to complete in time in this thread.\r
1713         */\r
1714         service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
1715         service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
1716         service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
1717 \r
1718         stop_service(service, 0, true, true);\r
1719       }\r
1720       return NO_ERROR;\r
1721 \r
1722     case SERVICE_CONTROL_CONTINUE:\r
1723       service->last_control = control;\r
1724       log_service_control(service->name, control, true);\r
1725       service->throttle = 0;\r
1726       if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);\r
1727       else {\r
1728         if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
1729         ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1730         SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1731       }\r
1732       /* We can't continue if the application is running! */\r
1733       if (! service->process_handle) service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
1734       service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;\r
1735       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);\r
1736       SetServiceStatus(service->status_handle, &service->status);\r
1737       return NO_ERROR;\r
1738 \r
1739     case SERVICE_CONTROL_PAUSE:\r
1740       /*\r
1741         We don't accept pause messages but it isn't possible to register\r
1742         only for continue messages so we have to handle this case.\r
1743       */\r
1744       log_service_control(service->name, control, false);\r
1745       return ERROR_CALL_NOT_IMPLEMENTED;\r
1746 \r
1747     case NSSM_SERVICE_CONTROL_ROTATE:\r
1748       service->last_control = control;\r
1749       log_service_control(service->name, control, true);\r
1750       (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_ROTATE, NSSM_HOOK_ACTION_PRE, &control, NSSM_HOOK_DEADLINE, false);\r
1751       if (service->rotate_stdout_online == NSSM_ROTATE_ONLINE) service->rotate_stdout_online = NSSM_ROTATE_ONLINE_ASAP;\r
1752       if (service->rotate_stderr_online == NSSM_ROTATE_ONLINE) service->rotate_stderr_online = NSSM_ROTATE_ONLINE_ASAP;\r
1753       (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_ROTATE, NSSM_HOOK_ACTION_POST, &control);\r
1754       return NO_ERROR;\r
1755 \r
1756     case SERVICE_CONTROL_POWEREVENT:\r
1757       /* Resume from suspend. */\r
1758       if (event == PBT_APMRESUMEAUTOMATIC) {\r
1759         service->last_control = control;\r
1760         log_service_control(service->name, control, true);\r
1761         (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_POWER, NSSM_HOOK_ACTION_RESUME, &control);\r
1762         return NO_ERROR;\r
1763       }\r
1764 \r
1765       /* Battery low or changed to A/C power or something. */\r
1766       if (event == PBT_APMPOWERSTATUSCHANGE) {\r
1767         service->last_control = control;\r
1768         log_service_control(service->name, control, true);\r
1769         (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_POWER, NSSM_HOOK_ACTION_CHANGE, &control);\r
1770         return NO_ERROR;\r
1771       }\r
1772       log_service_control(service->name, control, false);\r
1773       return NO_ERROR;\r
1774   }\r
1775 \r
1776   /* Unknown control */\r
1777   log_service_control(service->name, control, false);\r
1778   return ERROR_CALL_NOT_IMPLEMENTED;\r
1779 }\r
1780 \r
1781 /* Start the service */\r
1782 int start_service(nssm_service_t *service) {\r
1783   service->stopping = false;\r
1784 \r
1785   if (service->process_handle) return 0;\r
1786   service->start_requested_count++;\r
1787 \r
1788   /* Allocate a STARTUPINFO structure for a new process */\r
1789   STARTUPINFO si;\r
1790   ZeroMemory(&si, sizeof(si));\r
1791   si.cb = sizeof(si);\r
1792 \r
1793   /* Allocate a PROCESSINFO structure for the process */\r
1794   PROCESS_INFORMATION pi;\r
1795   ZeroMemory(&pi, sizeof(pi));\r
1796 \r
1797   /* Get startup parameters */\r
1798   int ret = get_parameters(service, &si);\r
1799   if (ret) {\r
1800     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);\r
1801     unset_service_environment(service);\r
1802     return stop_service(service, 2, true, true);\r
1803   }\r
1804 \r
1805   /* Launch executable with arguments */\r
1806   TCHAR cmd[CMD_LENGTH];\r
1807   if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {\r
1808     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);\r
1809     unset_service_environment(service);\r
1810     return stop_service(service, 2, true, true);\r
1811   }\r
1812 \r
1813   throttle_restart(service);\r
1814 \r
1815   service->status.dwCurrentState = SERVICE_START_PENDING;\r
1816   service->status.dwControlsAccepted = SERVICE_ACCEPT_POWEREVENT | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;\r
1817   SetServiceStatus(service->status_handle, &service->status);\r
1818 \r
1819   unsigned long control = NSSM_SERVICE_CONTROL_START;\r
1820 \r
1821   /* Did another thread receive a stop control? */\r
1822   if (service->allow_restart) {\r
1823     /* Set up I/O redirection. */\r
1824     if (get_output_handles(service, &si)) {\r
1825       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);\r
1826       if (! service->no_console) FreeConsole();\r
1827       close_output_handles(&si);\r
1828       unset_service_environment(service);\r
1829       return stop_service(service, 4, true, true);\r
1830     }\r
1831 \r
1832     /* Pre-start hook. May need I/O to have been redirected already. */\r
1833     if (nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE, &control, NSSM_SERVICE_STATUS_DEADLINE, false) == NSSM_HOOK_STATUS_ABORT) {\r
1834       TCHAR code[16];\r
1835       _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), NSSM_HOOK_STATUS_ABORT);\r
1836       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PRESTART_HOOK_ABORT, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE, service->name, code, 0);\r
1837       unset_service_environment(service);\r
1838       return stop_service(service, 5, true, true);\r
1839     }\r
1840 \r
1841     /* The pre-start hook will have cleaned the environment. */\r
1842     set_service_environment(service);\r
1843 \r
1844     bool inherit_handles = false;\r
1845     if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
1846     unsigned long flags = service->priority & priority_mask();\r
1847     if (service->affinity) flags |= CREATE_SUSPENDED;\r
1848     if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, 0, service->dir, &si, &pi)) {\r
1849       unsigned long exitcode = 3;\r
1850       unsigned long error = GetLastError();\r
1851       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
1852       close_output_handles(&si);\r
1853       unset_service_environment(service);\r
1854       return stop_service(service, exitcode, true, true);\r
1855     }\r
1856     service->start_count++;\r
1857     service->process_handle = pi.hProcess;\r
1858     service->pid = pi.dwProcessId;\r
1859 \r
1860     if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
1861 \r
1862     close_output_handles(&si);\r
1863 \r
1864     if (! service->no_console) FreeConsole();\r
1865 \r
1866     if (service->affinity) {\r
1867       /*\r
1868         We are explicitly storing service->affinity as a 64-bit unsigned integer\r
1869         so that we can parse it regardless of whether we're running in 32-bit\r
1870         or 64-bit mode.  The arguments to SetProcessAffinityMask(), however, are\r
1871         defined as type DWORD_PTR and hence limited to 32 bits on a 32-bit system\r
1872         (or when running the 32-bit NSSM).\r
1873 \r
1874         The result is a lot of seemingly-unnecessary casting throughout the code\r
1875         and potentially confusion when we actually try to start the service.\r
1876         Having said that, however, it's unlikely that we're actually going to\r
1877         run in 32-bit mode on a system which has more than 32 CPUs so the\r
1878         likelihood of seeing a confusing situation is somewhat diminished.\r
1879       */\r
1880       DWORD_PTR affinity, system_affinity;\r
1881 \r
1882       if (GetProcessAffinityMask(service->process_handle, &affinity, &system_affinity)) affinity = service->affinity & system_affinity;\r
1883       else {\r
1884         affinity = (DWORD_PTR) service->affinity;\r
1885         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
1886       }\r
1887 \r
1888       if (! SetProcessAffinityMask(service->process_handle, affinity)) {\r
1889         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
1890       }\r
1891 \r
1892       ResumeThread(pi.hThread);\r
1893     }\r
1894   }\r
1895 \r
1896   /* Restore our environment. */\r
1897   unset_service_environment(service);\r
1898 \r
1899   /*\r
1900     Wait for a clean startup before changing the service status to RUNNING\r
1901     but be mindful of the fact that we are blocking the service control manager\r
1902     so abandon the wait before too much time has elapsed.\r
1903   */\r
1904   if (await_single_handle(service->status_handle, &service->status, service->process_handle, service->name, _T("start_service"), service->throttle_delay) == 1) service->throttle = 0;\r
1905 \r
1906   /* Did another thread receive a stop control? */\r
1907   if (! service->allow_restart) return 0;\r
1908 \r
1909   /* Signal successful start */\r
1910   service->status.dwCurrentState = SERVICE_RUNNING;\r
1911   service->status.dwControlsAccepted &= ~SERVICE_ACCEPT_PAUSE_CONTINUE;\r
1912   SetServiceStatus(service->status_handle, &service->status);\r
1913 \r
1914   /* Post-start hook. */\r
1915   if (! service->throttle) {\r
1916     (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_POST, &control);\r
1917   }\r
1918 \r
1919   /* Ensure the restart delay is always applied. */\r
1920   if (service->restart_delay && ! service->throttle) service->throttle++;\r
1921 \r
1922   return 0;\r
1923 }\r
1924 \r
1925 /* Stop the service */\r
1926 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {\r
1927   service->allow_restart = false;\r
1928   if (service->wait_handle) {\r
1929     UnregisterWait(service->wait_handle);\r
1930     service->wait_handle = 0;\r
1931   }\r
1932 \r
1933   service->rotate_stdout_online = service->rotate_stderr_online = NSSM_ROTATE_OFFLINE;\r
1934 \r
1935   if (default_action && ! exitcode && ! graceful) {\r
1936     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
1937     graceful = true;\r
1938   }\r
1939 \r
1940   /* Signal we are stopping */\r
1941   if (graceful) {\r
1942     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1943     service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
1944     SetServiceStatus(service->status_handle, &service->status);\r
1945   }\r
1946 \r
1947   /* Nothing to do if service isn't running */\r
1948   if (service->pid) {\r
1949     /* Shut down service */\r
1950     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);\r
1951     kill_t k;\r
1952     service_kill_t(service, &k);\r
1953     k.exitcode = 0;\r
1954     kill_process(&k);\r
1955   }\r
1956   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);\r
1957 \r
1958   end_service((void *) service, true);\r
1959 \r
1960   /* Signal we stopped */\r
1961   if (graceful) {\r
1962     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1963     wait_for_hooks(service, true);\r
1964     service->status.dwCurrentState = SERVICE_STOPPED;\r
1965     if (exitcode) {\r
1966       service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
1967       service->status.dwServiceSpecificExitCode = exitcode;\r
1968     }\r
1969     else {\r
1970       service->status.dwWin32ExitCode = NO_ERROR;\r
1971       service->status.dwServiceSpecificExitCode = 0;\r
1972     }\r
1973     SetServiceStatus(service->status_handle, &service->status);\r
1974   }\r
1975 \r
1976   return exitcode;\r
1977 }\r
1978 \r
1979 /* Callback function triggered when the server exits */\r
1980 void CALLBACK end_service(void *arg, unsigned char why) {\r
1981   nssm_service_t *service = (nssm_service_t *) arg;\r
1982 \r
1983   if (service->stopping) return;\r
1984 \r
1985   service->stopping = true;\r
1986 \r
1987   service->rotate_stdout_online = service->rotate_stderr_online = NSSM_ROTATE_OFFLINE;\r
1988 \r
1989   /* Use now as a dummy exit time. */\r
1990   GetSystemTimeAsFileTime(&service->exit_time);\r
1991 \r
1992   /* Check exit code */\r
1993   unsigned long exitcode = 0;\r
1994   TCHAR code[16];\r
1995   if (service->process_handle) {\r
1996     GetExitCodeProcess(service->process_handle, &exitcode);\r
1997     service->exitcode = exitcode;\r
1998     /* Check real exit time. */\r
1999     if (exitcode != STILL_ACTIVE) get_process_exit_time(service->process_handle, &service->exit_time);\r
2000     CloseHandle(service->process_handle);\r
2001   }\r
2002 \r
2003   service->process_handle = 0;\r
2004 \r
2005   /*\r
2006     Log that the service ended BEFORE logging about killing the process\r
2007     tree.  See below for the possible values of the why argument.\r
2008   */\r
2009   if (! why) {\r
2010     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);\r
2011     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);\r
2012   }\r
2013 \r
2014   /* Clean up. */\r
2015   if (exitcode == STILL_ACTIVE) exitcode = 0;\r
2016   if (service->pid && service->kill_process_tree) {\r
2017     kill_t k;\r
2018     service_kill_t(service, &k);\r
2019     kill_process_tree(&k, service->pid);\r
2020   }\r
2021   service->pid = 0;\r
2022 \r
2023   /* Exit hook. */\r
2024   service->exit_count++;\r
2025   (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_EXIT, NSSM_HOOK_ACTION_POST, NULL, NSSM_HOOK_DEADLINE, true);\r
2026 \r
2027   /*\r
2028     The why argument is true if our wait timed out or false otherwise.\r
2029     Our wait is infinite so why will never be true when called by the system.\r
2030     If it is indeed true, assume we were called from stop_service() because\r
2031     this is a controlled shutdown, and don't take any restart action.\r
2032   */\r
2033   if (why) return;\r
2034   if (! service->allow_restart) return;\r
2035 \r
2036   /* What action should we take? */\r
2037   int action = NSSM_EXIT_RESTART;\r
2038   TCHAR action_string[ACTION_LEN];\r
2039   bool default_action;\r
2040   if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {\r
2041     for (int i = 0; exit_action_strings[i]; i++) {\r
2042       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
2043         action = i;\r
2044         break;\r
2045       }\r
2046     }\r
2047   }\r
2048 \r
2049   switch (action) {\r
2050     /* Try to restart the service or return failure code to service manager */\r
2051     case NSSM_EXIT_RESTART:\r
2052       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);\r
2053       while (monitor_service(service)) {\r
2054         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);\r
2055         Sleep(30000);\r
2056       }\r
2057     break;\r
2058 \r
2059     /* Do nothing, just like srvany would */\r
2060     case NSSM_EXIT_IGNORE:\r
2061       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
2062       wait_for_hooks(service, false);\r
2063       Sleep(INFINITE);\r
2064     break;\r
2065 \r
2066     /* Tell the service manager we are finished */\r
2067     case NSSM_EXIT_REALLY:\r
2068       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);\r
2069       stop_service(service, exitcode, true, default_action);\r
2070     break;\r
2071 \r
2072     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
2073     case NSSM_EXIT_UNCLEAN:\r
2074       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
2075       stop_service(service, exitcode, false, default_action);\r
2076       wait_for_hooks(service, false);\r
2077       free_imports();\r
2078       exit(exitcode);\r
2079   }\r
2080 }\r
2081 \r
2082 void throttle_restart(nssm_service_t *service) {\r
2083   /* This can't be a restart if the service is already running. */\r
2084   if (! service->throttle++) return;\r
2085 \r
2086   unsigned long ms;\r
2087   unsigned long throttle_ms = throttle_milliseconds(service->throttle);\r
2088   TCHAR threshold[8], milliseconds[8];\r
2089 \r
2090   if (service->restart_delay > throttle_ms) ms = service->restart_delay;\r
2091   else ms = throttle_ms;\r
2092 \r
2093   _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
2094 \r
2095   if (service->throttle == 1 && service->restart_delay > throttle_ms) log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESTART_DELAY, service->name, milliseconds, 0);\r
2096   else {\r
2097     _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);\r
2098     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);\r
2099   }\r
2100 \r
2101   if (use_critical_section) EnterCriticalSection(&service->throttle_section);\r
2102   else if (service->throttle_timer) {\r
2103     ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
2104     service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
2105     SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
2106   }\r
2107 \r
2108   service->status.dwCurrentState = SERVICE_PAUSED;\r
2109   service->status.dwControlsAccepted |= SERVICE_ACCEPT_PAUSE_CONTINUE;\r
2110   SetServiceStatus(service->status_handle, &service->status);\r
2111 \r
2112   if (use_critical_section) {\r
2113     imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);\r
2114     LeaveCriticalSection(&service->throttle_section);\r
2115   }\r
2116   else {\r
2117     if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);\r
2118     else Sleep(ms);\r
2119   }\r
2120 }\r
2121 \r
2122 /*\r
2123   When responding to a stop (or any other) request we need to set dwWaitHint to\r
2124   the number of milliseconds we expect the operation to take, and optionally\r
2125   increase dwCheckPoint.  If dwWaitHint milliseconds elapses without the\r
2126   operation completing or dwCheckPoint increasing, the system will consider the\r
2127   service to be hung.\r
2128 \r
2129   However the system will consider the service to be hung after 30000\r
2130   milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not\r
2131   changed.  Therefore if we want to wait longer than that we must periodically\r
2132   increase dwCheckPoint.\r
2133 \r
2134   Furthermore, it will consider the service to be hung after 60000 milliseconds\r
2135   regardless of the value of dwCheckPoint unless dwWaitHint is increased every\r
2136   time dwCheckPoint is also increased.\r
2137 \r
2138   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
2139   NSSM_SERVICE_STATUS_DEADLINE milliseconds.  If the process is still running\r
2140   and we haven't finished waiting we increment dwCheckPoint and add whichever is\r
2141   smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to\r
2142   dwWaitHint.\r
2143 \r
2144   Only doing both these things will prevent the system from killing the service.\r
2145 \r
2146   If the status_handle and service_status arguments are omitted, this function\r
2147   will not try to update the service manager but it will still log to the\r
2148   event log that it is waiting for a handle.\r
2149 \r
2150   Returns: 1 if the wait timed out.\r
2151            0 if the wait completed.\r
2152           -1 on error.\r
2153 */\r
2154 int await_single_handle(SERVICE_STATUS_HANDLE status_handle, SERVICE_STATUS *status, HANDLE handle, TCHAR *name, TCHAR *function_name, unsigned long timeout) {\r
2155   unsigned long interval;\r
2156   unsigned long ret;\r
2157   unsigned long waited;\r
2158   TCHAR interval_milliseconds[16];\r
2159   TCHAR timeout_milliseconds[16];\r
2160   TCHAR waited_milliseconds[16];\r
2161   TCHAR *function = function_name;\r
2162 \r
2163   /* Add brackets to function name. */\r
2164   size_t funclen = _tcslen(function_name) + 3;\r
2165   TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));\r
2166   if (func) {\r
2167     if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;\r
2168   }\r
2169 \r
2170   _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);\r
2171 \r
2172   waited = 0;\r
2173   while (waited < timeout) {\r
2174     interval = timeout - waited;\r
2175     if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
2176 \r
2177     if (status) {\r
2178       status->dwWaitHint += interval;\r
2179       status->dwCheckPoint++;\r
2180       SetServiceStatus(status_handle, status);\r
2181     }\r
2182 \r
2183     if (waited) {\r
2184       _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);\r
2185       _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);\r
2186       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SINGLE_HANDLE, function, name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
2187     }\r
2188 \r
2189     switch (WaitForSingleObject(handle, interval)) {\r
2190       case WAIT_OBJECT_0:\r
2191         ret = 0;\r
2192         goto awaited;\r
2193 \r
2194       case WAIT_TIMEOUT:\r
2195         ret = 1;\r
2196         break;\r
2197 \r
2198       default:\r
2199         ret = -1;\r
2200         goto awaited;\r
2201     }\r
2202 \r
2203     waited += interval;\r
2204   }\r
2205 \r
2206 awaited:\r
2207   if (func) HeapFree(GetProcessHeap(), 0, func);\r
2208 \r
2209   return ret;\r
2210 }\r
2211 \r
2212 int list_nssm_services() {\r
2213   /* Open service manager. */\r
2214   SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);\r
2215   if (! services) {\r
2216     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
2217     return 1;\r
2218   }\r
2219 \r
2220   unsigned long bufsize, required, count, i;\r
2221   unsigned long resume = 0;\r
2222   EnumServicesStatus(services, SERVICE_WIN32, SERVICE_STATE_ALL, 0, 0, &required, &count, &resume);\r
2223   unsigned long error = GetLastError();\r
2224   if (error != ERROR_MORE_DATA) {\r
2225     print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));\r
2226     return 2;\r
2227   }\r
2228 \r
2229   ENUM_SERVICE_STATUS *status = (ENUM_SERVICE_STATUS *) HeapAlloc(GetProcessHeap(), 0, required);\r
2230   if (! status) {\r
2231     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("ENUM_SERVICE_STATUS"), _T("list_nssm_services()"));\r
2232     return 3;\r
2233   }\r
2234 \r
2235   bufsize = required;\r
2236   while (true) {\r
2237     int ret = EnumServicesStatus(services, SERVICE_WIN32, SERVICE_STATE_ALL, status, bufsize, &required, &count, &resume);\r
2238     if (! ret) {\r
2239       error = GetLastError();\r
2240       if (error != ERROR_MORE_DATA) {\r
2241         HeapFree(GetProcessHeap(), 0, status);\r
2242         print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));\r
2243         return 4;\r
2244       }\r
2245     }\r
2246 \r
2247     for (i = 0; i < count; i++) {\r
2248       /* Try to get the service parameters. */\r
2249       nssm_service_t *service = alloc_nssm_service();\r
2250       if (! service) {\r
2251         HeapFree(GetProcessHeap(), 0, status);\r
2252         print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("nssm_service_t"), _T("list_nssm_services()"));\r
2253         return 5;\r
2254       }\r
2255       _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), status[i].lpServiceName);\r
2256 \r
2257       get_parameters(service, 0);\r
2258       /* We manage the service if we have an Application. */\r
2259       if (service->exe[0]) _tprintf(_T("%s\n"), service->name);\r
2260 \r
2261       cleanup_nssm_service(service);\r
2262     }\r
2263 \r
2264     if (ret) break;\r
2265   }\r
2266 \r
2267   HeapFree(GetProcessHeap(), 0, status);\r
2268   return 0;\r
2269 }\r