Allow listing all services.
[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   static volatile bool await_debugger = (argc > 1 && str_equiv(argv[1], _T("debug")));\r
1507   while (await_debugger) Sleep(1000);\r
1508 \r
1509   if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {\r
1510     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);\r
1511     return;\r
1512   }\r
1513 \r
1514   /* We can use a condition variable in a critical section on Vista or later. */\r
1515   if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;\r
1516   else use_critical_section = false;\r
1517 \r
1518   /* Initialise status */\r
1519   ZeroMemory(&service->status, sizeof(service->status));\r
1520   service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
1521   service->status.dwControlsAccepted = 0;\r
1522   service->status.dwWin32ExitCode = NO_ERROR;\r
1523   service->status.dwServiceSpecificExitCode = 0;\r
1524   service->status.dwCheckPoint = 0;\r
1525   service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
1526 \r
1527   /* Signal we AREN'T running the server */\r
1528   service->process_handle = 0;\r
1529   service->pid = 0;\r
1530 \r
1531   /* Register control handler */\r
1532   service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);\r
1533   if (! service->status_handle) {\r
1534     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);\r
1535     return;\r
1536   }\r
1537 \r
1538   log_service_control(service->name, 0, true);\r
1539 \r
1540   service->status.dwCurrentState = SERVICE_START_PENDING;\r
1541   service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;\r
1542   SetServiceStatus(service->status_handle, &service->status);\r
1543 \r
1544   if (is_admin) {\r
1545     /* Try to create the exit action parameters; we don't care if it fails */\r
1546     create_exit_action(service->name, exit_action_strings[0], false);\r
1547 \r
1548     SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT);\r
1549     if (services) {\r
1550       service->handle = open_service(services, service->name, SERVICE_CHANGE_CONFIG, 0, 0);\r
1551       set_service_recovery(service);\r
1552 \r
1553       /* Remember our display name. */\r
1554       unsigned long displayname_len = _countof(service->displayname);\r
1555       GetServiceDisplayName(services, service->name, service->displayname, &displayname_len);\r
1556 \r
1557       CloseServiceHandle(services);\r
1558     }\r
1559   }\r
1560 \r
1561   /* Used for signalling a resume if the service pauses when throttled. */\r
1562   if (use_critical_section) {\r
1563     InitializeCriticalSection(&service->throttle_section);\r
1564     service->throttle_section_initialised = true;\r
1565   }\r
1566   else {\r
1567     service->throttle_timer = CreateWaitableTimer(0, 1, 0);\r
1568     if (! service->throttle_timer) {\r
1569       log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);\r
1570     }\r
1571   }\r
1572 \r
1573   /* Critical section for hooks. */\r
1574   InitializeCriticalSection(&service->hook_section);\r
1575   service->hook_section_initialised = true;\r
1576 \r
1577   /* Remember our initial environment. */\r
1578   service->initial_env = copy_environment();\r
1579 \r
1580   /* Remember our creation time. */\r
1581   if (get_process_creation_time(GetCurrentProcess(), &service->nssm_creation_time)) ZeroMemory(&service->nssm_creation_time, sizeof(service->nssm_creation_time));\r
1582 \r
1583   service->allow_restart = true;\r
1584   if (! CreateThread(NULL, 0, launch_service, (void *) service, 0, NULL)) {\r
1585     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
1586     stop_service(service, 0, true, true);\r
1587   }\r
1588 }\r
1589 \r
1590 /* Make sure service recovery actions are taken where necessary */\r
1591 void set_service_recovery(nssm_service_t *service) {\r
1592   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
1593   ZeroMemory(&flag, sizeof(flag));\r
1594   flag.fFailureActionsOnNonCrashFailures = true;\r
1595 \r
1596   /* This functionality was added in Vista so the call may fail */\r
1597   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {\r
1598     unsigned long error = GetLastError();\r
1599     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
1600     if (error != ERROR_INVALID_LEVEL) {\r
1601       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);\r
1602     }\r
1603   }\r
1604 }\r
1605 \r
1606 int monitor_service(nssm_service_t *service) {\r
1607   /* Set service status to started */\r
1608   int ret = start_service(service);\r
1609   if (ret) {\r
1610     TCHAR code[16];\r
1611     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);\r
1612     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);\r
1613     return ret;\r
1614   }\r
1615   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);\r
1616 \r
1617   /* Monitor service */\r
1618   if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
1619     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);\r
1620   }\r
1621 \r
1622   return 0;\r
1623 }\r
1624 \r
1625 TCHAR *service_control_text(unsigned long control) {\r
1626   switch (control) {\r
1627     /* HACK: there is no SERVICE_CONTROL_START constant */\r
1628     case NSSM_SERVICE_CONTROL_START: return _T("START");\r
1629     case SERVICE_CONTROL_STOP: return _T("STOP");\r
1630     case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");\r
1631     case SERVICE_CONTROL_PAUSE: return _T("PAUSE");\r
1632     case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");\r
1633     case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");\r
1634     case NSSM_SERVICE_CONTROL_ROTATE: return _T("ROTATE");\r
1635     case SERVICE_CONTROL_POWEREVENT: return _T("POWEREVENT");\r
1636     default: return 0;\r
1637   }\r
1638 }\r
1639 \r
1640 TCHAR *service_status_text(unsigned long status) {\r
1641   switch (status) {\r
1642     case SERVICE_STOPPED: return _T("SERVICE_STOPPED");\r
1643     case SERVICE_START_PENDING: return _T("SERVICE_START_PENDING");\r
1644     case SERVICE_STOP_PENDING: return _T("SERVICE_STOP_PENDING");\r
1645     case SERVICE_RUNNING: return _T("SERVICE_RUNNING");\r
1646     case SERVICE_CONTINUE_PENDING: return _T("SERVICE_CONTINUE_PENDING");\r
1647     case SERVICE_PAUSE_PENDING: return _T("SERVICE_PAUSE_PENDING");\r
1648     case SERVICE_PAUSED: return _T("SERVICE_PAUSED");\r
1649     default: return 0;\r
1650   }\r
1651 }\r
1652 \r
1653 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {\r
1654   TCHAR *text = service_control_text(control);\r
1655   unsigned long event;\r
1656 \r
1657   if (! text) {\r
1658     /* "0x" + 8 x hex + NULL */\r
1659     text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));\r
1660     if (! text) {\r
1661       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
1662       return;\r
1663     }\r
1664     if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {\r
1665       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
1666       HeapFree(GetProcessHeap(), 0, text);\r
1667       return;\r
1668     }\r
1669 \r
1670     event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;\r
1671   }\r
1672   else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;\r
1673   else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;\r
1674 \r
1675   log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);\r
1676 \r
1677   if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {\r
1678     HeapFree(GetProcessHeap(), 0, text);\r
1679   }\r
1680 }\r
1681 \r
1682 /* Service control handler */\r
1683 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
1684   nssm_service_t *service = (nssm_service_t *) context;\r
1685 \r
1686   switch (control) {\r
1687     case SERVICE_CONTROL_INTERROGATE:\r
1688       /* We always keep the service status up-to-date so this is a no-op. */\r
1689       return NO_ERROR;\r
1690 \r
1691     case SERVICE_CONTROL_SHUTDOWN:\r
1692     case SERVICE_CONTROL_STOP:\r
1693       service->last_control = control;\r
1694       log_service_control(service->name, control, true);\r
1695 \r
1696       /* Immediately block further controls. */\r
1697       service->allow_restart = false;\r
1698       service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1699       service->status.dwControlsAccepted = 0;\r
1700       SetServiceStatus(service->status_handle, &service->status);\r
1701 \r
1702       /* Pre-stop hook. */\r
1703       nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_STOP, NSSM_HOOK_ACTION_PRE, &control, NSSM_SERVICE_STATUS_DEADLINE, false);\r
1704 \r
1705       /*\r
1706         We MUST acknowledge the stop request promptly but we're committed to\r
1707         waiting for the application to exit.  Spawn a new thread to wait\r
1708         while we acknowledge the request.\r
1709       */\r
1710       if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {\r
1711         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
1712 \r
1713         /*\r
1714           We couldn't create a thread to tidy up so we'll have to force the tidyup\r
1715           to complete in time in this thread.\r
1716         */\r
1717         service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
1718         service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
1719         service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
1720 \r
1721         stop_service(service, 0, true, true);\r
1722       }\r
1723       return NO_ERROR;\r
1724 \r
1725     case SERVICE_CONTROL_CONTINUE:\r
1726       service->last_control = control;\r
1727       log_service_control(service->name, control, true);\r
1728       service->throttle = 0;\r
1729       if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);\r
1730       else {\r
1731         if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
1732         ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1733         SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1734       }\r
1735       /* We can't continue if the application is running! */\r
1736       if (! service->process_handle) service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
1737       service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;\r
1738       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);\r
1739       SetServiceStatus(service->status_handle, &service->status);\r
1740       return NO_ERROR;\r
1741 \r
1742     case SERVICE_CONTROL_PAUSE:\r
1743       /*\r
1744         We don't accept pause messages but it isn't possible to register\r
1745         only for continue messages so we have to handle this case.\r
1746       */\r
1747       log_service_control(service->name, control, false);\r
1748       return ERROR_CALL_NOT_IMPLEMENTED;\r
1749 \r
1750     case NSSM_SERVICE_CONTROL_ROTATE:\r
1751       service->last_control = control;\r
1752       log_service_control(service->name, control, true);\r
1753       (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_ROTATE, NSSM_HOOK_ACTION_PRE, &control, NSSM_HOOK_DEADLINE, false);\r
1754       if (service->rotate_stdout_online == NSSM_ROTATE_ONLINE) service->rotate_stdout_online = NSSM_ROTATE_ONLINE_ASAP;\r
1755       if (service->rotate_stderr_online == NSSM_ROTATE_ONLINE) service->rotate_stderr_online = NSSM_ROTATE_ONLINE_ASAP;\r
1756       (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_ROTATE, NSSM_HOOK_ACTION_POST, &control);\r
1757       return NO_ERROR;\r
1758 \r
1759     case SERVICE_CONTROL_POWEREVENT:\r
1760       /* Resume from suspend. */\r
1761       if (event == PBT_APMRESUMEAUTOMATIC) {\r
1762         service->last_control = control;\r
1763         log_service_control(service->name, control, true);\r
1764         (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_POWER, NSSM_HOOK_ACTION_RESUME, &control);\r
1765         return NO_ERROR;\r
1766       }\r
1767 \r
1768       /* Battery low or changed to A/C power or something. */\r
1769       if (event == PBT_APMPOWERSTATUSCHANGE) {\r
1770         service->last_control = control;\r
1771         log_service_control(service->name, control, true);\r
1772         (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_POWER, NSSM_HOOK_ACTION_CHANGE, &control);\r
1773         return NO_ERROR;\r
1774       }\r
1775       log_service_control(service->name, control, false);\r
1776       return NO_ERROR;\r
1777   }\r
1778 \r
1779   /* Unknown control */\r
1780   log_service_control(service->name, control, false);\r
1781   return ERROR_CALL_NOT_IMPLEMENTED;\r
1782 }\r
1783 \r
1784 /* Start the service */\r
1785 int start_service(nssm_service_t *service) {\r
1786   service->stopping = false;\r
1787 \r
1788   if (service->process_handle) return 0;\r
1789   service->start_requested_count++;\r
1790 \r
1791   /* Allocate a STARTUPINFO structure for a new process */\r
1792   STARTUPINFO si;\r
1793   ZeroMemory(&si, sizeof(si));\r
1794   si.cb = sizeof(si);\r
1795 \r
1796   /* Allocate a PROCESSINFO structure for the process */\r
1797   PROCESS_INFORMATION pi;\r
1798   ZeroMemory(&pi, sizeof(pi));\r
1799 \r
1800   /* Get startup parameters */\r
1801   int ret = get_parameters(service, &si);\r
1802   if (ret) {\r
1803     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);\r
1804     unset_service_environment(service);\r
1805     return stop_service(service, 2, true, true);\r
1806   }\r
1807 \r
1808   /* Launch executable with arguments */\r
1809   TCHAR cmd[CMD_LENGTH];\r
1810   if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {\r
1811     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);\r
1812     unset_service_environment(service);\r
1813     return stop_service(service, 2, true, true);\r
1814   }\r
1815 \r
1816   throttle_restart(service);\r
1817 \r
1818   service->status.dwCurrentState = SERVICE_START_PENDING;\r
1819   service->status.dwControlsAccepted = SERVICE_ACCEPT_POWEREVENT | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;\r
1820   SetServiceStatus(service->status_handle, &service->status);\r
1821 \r
1822   unsigned long control = NSSM_SERVICE_CONTROL_START;\r
1823 \r
1824   /* Did another thread receive a stop control? */\r
1825   if (service->allow_restart) {\r
1826     /* Set up I/O redirection. */\r
1827     if (get_output_handles(service, &si)) {\r
1828       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);\r
1829       if (! service->no_console) FreeConsole();\r
1830       close_output_handles(&si);\r
1831       unset_service_environment(service);\r
1832       return stop_service(service, 4, true, true);\r
1833     }\r
1834 \r
1835     /* Pre-start hook. May need I/O to have been redirected already. */\r
1836     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
1837       TCHAR code[16];\r
1838       _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), NSSM_HOOK_STATUS_ABORT);\r
1839       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PRESTART_HOOK_ABORT, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE, service->name, code, 0);\r
1840       unset_service_environment(service);\r
1841       return stop_service(service, 5, true, true);\r
1842     }\r
1843 \r
1844     /* The pre-start hook will have cleaned the environment. */\r
1845     set_service_environment(service);\r
1846 \r
1847     bool inherit_handles = false;\r
1848     if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
1849     unsigned long flags = service->priority & priority_mask();\r
1850     if (service->affinity) flags |= CREATE_SUSPENDED;\r
1851     if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, 0, service->dir, &si, &pi)) {\r
1852       unsigned long exitcode = 3;\r
1853       unsigned long error = GetLastError();\r
1854       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
1855       close_output_handles(&si);\r
1856       unset_service_environment(service);\r
1857       return stop_service(service, exitcode, true, true);\r
1858     }\r
1859     service->start_count++;\r
1860     service->process_handle = pi.hProcess;\r
1861     service->pid = pi.dwProcessId;\r
1862 \r
1863     if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
1864 \r
1865     close_output_handles(&si);\r
1866 \r
1867     if (! service->no_console) FreeConsole();\r
1868 \r
1869     if (service->affinity) {\r
1870       /*\r
1871         We are explicitly storing service->affinity as a 64-bit unsigned integer\r
1872         so that we can parse it regardless of whether we're running in 32-bit\r
1873         or 64-bit mode.  The arguments to SetProcessAffinityMask(), however, are\r
1874         defined as type DWORD_PTR and hence limited to 32 bits on a 32-bit system\r
1875         (or when running the 32-bit NSSM).\r
1876 \r
1877         The result is a lot of seemingly-unnecessary casting throughout the code\r
1878         and potentially confusion when we actually try to start the service.\r
1879         Having said that, however, it's unlikely that we're actually going to\r
1880         run in 32-bit mode on a system which has more than 32 CPUs so the\r
1881         likelihood of seeing a confusing situation is somewhat diminished.\r
1882       */\r
1883       DWORD_PTR affinity, system_affinity;\r
1884 \r
1885       if (GetProcessAffinityMask(service->process_handle, &affinity, &system_affinity)) affinity = service->affinity & system_affinity;\r
1886       else {\r
1887         affinity = (DWORD_PTR) service->affinity;\r
1888         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
1889       }\r
1890 \r
1891       if (! SetProcessAffinityMask(service->process_handle, affinity)) {\r
1892         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
1893       }\r
1894 \r
1895       ResumeThread(pi.hThread);\r
1896     }\r
1897   }\r
1898 \r
1899   /* Restore our environment. */\r
1900   unset_service_environment(service);\r
1901 \r
1902   /*\r
1903     Wait for a clean startup before changing the service status to RUNNING\r
1904     but be mindful of the fact that we are blocking the service control manager\r
1905     so abandon the wait before too much time has elapsed.\r
1906   */\r
1907   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
1908 \r
1909   /* Did another thread receive a stop control? */\r
1910   if (! service->allow_restart) return 0;\r
1911 \r
1912   /* Signal successful start */\r
1913   service->status.dwCurrentState = SERVICE_RUNNING;\r
1914   service->status.dwControlsAccepted &= ~SERVICE_ACCEPT_PAUSE_CONTINUE;\r
1915   SetServiceStatus(service->status_handle, &service->status);\r
1916 \r
1917   /* Post-start hook. */\r
1918   if (! service->throttle) {\r
1919     (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_POST, &control);\r
1920   }\r
1921 \r
1922   /* Ensure the restart delay is always applied. */\r
1923   if (service->restart_delay && ! service->throttle) service->throttle++;\r
1924 \r
1925   return 0;\r
1926 }\r
1927 \r
1928 /* Stop the service */\r
1929 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {\r
1930   service->allow_restart = false;\r
1931   if (service->wait_handle) {\r
1932     UnregisterWait(service->wait_handle);\r
1933     service->wait_handle = 0;\r
1934   }\r
1935 \r
1936   service->rotate_stdout_online = service->rotate_stderr_online = NSSM_ROTATE_OFFLINE;\r
1937 \r
1938   if (default_action && ! exitcode && ! graceful) {\r
1939     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
1940     graceful = true;\r
1941   }\r
1942 \r
1943   /* Signal we are stopping */\r
1944   if (graceful) {\r
1945     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1946     service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
1947     SetServiceStatus(service->status_handle, &service->status);\r
1948   }\r
1949 \r
1950   /* Nothing to do if service isn't running */\r
1951   if (service->pid) {\r
1952     /* Shut down service */\r
1953     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);\r
1954     kill_t k;\r
1955     service_kill_t(service, &k);\r
1956     k.exitcode = 0;\r
1957     kill_process(&k);\r
1958   }\r
1959   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);\r
1960 \r
1961   end_service((void *) service, true);\r
1962 \r
1963   /* Signal we stopped */\r
1964   if (graceful) {\r
1965     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1966     wait_for_hooks(service, true);\r
1967     service->status.dwCurrentState = SERVICE_STOPPED;\r
1968     if (exitcode) {\r
1969       service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
1970       service->status.dwServiceSpecificExitCode = exitcode;\r
1971     }\r
1972     else {\r
1973       service->status.dwWin32ExitCode = NO_ERROR;\r
1974       service->status.dwServiceSpecificExitCode = 0;\r
1975     }\r
1976     SetServiceStatus(service->status_handle, &service->status);\r
1977   }\r
1978 \r
1979   return exitcode;\r
1980 }\r
1981 \r
1982 /* Callback function triggered when the server exits */\r
1983 void CALLBACK end_service(void *arg, unsigned char why) {\r
1984   nssm_service_t *service = (nssm_service_t *) arg;\r
1985 \r
1986   if (service->stopping) return;\r
1987 \r
1988   service->stopping = true;\r
1989 \r
1990   service->rotate_stdout_online = service->rotate_stderr_online = NSSM_ROTATE_OFFLINE;\r
1991 \r
1992   /* Use now as a dummy exit time. */\r
1993   GetSystemTimeAsFileTime(&service->exit_time);\r
1994 \r
1995   /* Check exit code */\r
1996   unsigned long exitcode = 0;\r
1997   TCHAR code[16];\r
1998   if (service->process_handle) {\r
1999     GetExitCodeProcess(service->process_handle, &exitcode);\r
2000     service->exitcode = exitcode;\r
2001     /* Check real exit time. */\r
2002     if (exitcode != STILL_ACTIVE) get_process_exit_time(service->process_handle, &service->exit_time);\r
2003     CloseHandle(service->process_handle);\r
2004   }\r
2005 \r
2006   service->process_handle = 0;\r
2007 \r
2008   /*\r
2009     Log that the service ended BEFORE logging about killing the process\r
2010     tree.  See below for the possible values of the why argument.\r
2011   */\r
2012   if (! why) {\r
2013     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);\r
2014     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);\r
2015   }\r
2016 \r
2017   /* Clean up. */\r
2018   if (exitcode == STILL_ACTIVE) exitcode = 0;\r
2019   if (service->pid && service->kill_process_tree) {\r
2020     kill_t k;\r
2021     service_kill_t(service, &k);\r
2022     kill_process_tree(&k, service->pid);\r
2023   }\r
2024   service->pid = 0;\r
2025 \r
2026   /* Exit hook. */\r
2027   service->exit_count++;\r
2028   (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_EXIT, NSSM_HOOK_ACTION_POST, NULL, NSSM_HOOK_DEADLINE, true);\r
2029 \r
2030   /*\r
2031     The why argument is true if our wait timed out or false otherwise.\r
2032     Our wait is infinite so why will never be true when called by the system.\r
2033     If it is indeed true, assume we were called from stop_service() because\r
2034     this is a controlled shutdown, and don't take any restart action.\r
2035   */\r
2036   if (why) return;\r
2037   if (! service->allow_restart) return;\r
2038 \r
2039   /* What action should we take? */\r
2040   int action = NSSM_EXIT_RESTART;\r
2041   TCHAR action_string[ACTION_LEN];\r
2042   bool default_action;\r
2043   if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {\r
2044     for (int i = 0; exit_action_strings[i]; i++) {\r
2045       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
2046         action = i;\r
2047         break;\r
2048       }\r
2049     }\r
2050   }\r
2051 \r
2052   switch (action) {\r
2053     /* Try to restart the service or return failure code to service manager */\r
2054     case NSSM_EXIT_RESTART:\r
2055       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);\r
2056       while (monitor_service(service)) {\r
2057         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);\r
2058         Sleep(30000);\r
2059       }\r
2060     break;\r
2061 \r
2062     /* Do nothing, just like srvany would */\r
2063     case NSSM_EXIT_IGNORE:\r
2064       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
2065       wait_for_hooks(service, false);\r
2066       Sleep(INFINITE);\r
2067     break;\r
2068 \r
2069     /* Tell the service manager we are finished */\r
2070     case NSSM_EXIT_REALLY:\r
2071       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);\r
2072       stop_service(service, exitcode, true, default_action);\r
2073     break;\r
2074 \r
2075     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
2076     case NSSM_EXIT_UNCLEAN:\r
2077       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
2078       stop_service(service, exitcode, false, default_action);\r
2079       wait_for_hooks(service, false);\r
2080       free_imports();\r
2081       exit(exitcode);\r
2082   }\r
2083 }\r
2084 \r
2085 void throttle_restart(nssm_service_t *service) {\r
2086   /* This can't be a restart if the service is already running. */\r
2087   if (! service->throttle++) return;\r
2088 \r
2089   unsigned long ms;\r
2090   unsigned long throttle_ms = throttle_milliseconds(service->throttle);\r
2091   TCHAR threshold[8], milliseconds[8];\r
2092 \r
2093   if (service->restart_delay > throttle_ms) ms = service->restart_delay;\r
2094   else ms = throttle_ms;\r
2095 \r
2096   _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
2097 \r
2098   if (service->throttle == 1 && service->restart_delay > throttle_ms) log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESTART_DELAY, service->name, milliseconds, 0);\r
2099   else {\r
2100     _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);\r
2101     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);\r
2102   }\r
2103 \r
2104   if (use_critical_section) EnterCriticalSection(&service->throttle_section);\r
2105   else if (service->throttle_timer) {\r
2106     ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
2107     service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
2108     SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
2109   }\r
2110 \r
2111   service->status.dwCurrentState = SERVICE_PAUSED;\r
2112   service->status.dwControlsAccepted |= SERVICE_ACCEPT_PAUSE_CONTINUE;\r
2113   SetServiceStatus(service->status_handle, &service->status);\r
2114 \r
2115   if (use_critical_section) {\r
2116     imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);\r
2117     LeaveCriticalSection(&service->throttle_section);\r
2118   }\r
2119   else {\r
2120     if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);\r
2121     else Sleep(ms);\r
2122   }\r
2123 }\r
2124 \r
2125 /*\r
2126   When responding to a stop (or any other) request we need to set dwWaitHint to\r
2127   the number of milliseconds we expect the operation to take, and optionally\r
2128   increase dwCheckPoint.  If dwWaitHint milliseconds elapses without the\r
2129   operation completing or dwCheckPoint increasing, the system will consider the\r
2130   service to be hung.\r
2131 \r
2132   However the system will consider the service to be hung after 30000\r
2133   milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not\r
2134   changed.  Therefore if we want to wait longer than that we must periodically\r
2135   increase dwCheckPoint.\r
2136 \r
2137   Furthermore, it will consider the service to be hung after 60000 milliseconds\r
2138   regardless of the value of dwCheckPoint unless dwWaitHint is increased every\r
2139   time dwCheckPoint is also increased.\r
2140 \r
2141   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
2142   NSSM_SERVICE_STATUS_DEADLINE milliseconds.  If the process is still running\r
2143   and we haven't finished waiting we increment dwCheckPoint and add whichever is\r
2144   smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to\r
2145   dwWaitHint.\r
2146 \r
2147   Only doing both these things will prevent the system from killing the service.\r
2148 \r
2149   If the status_handle and service_status arguments are omitted, this function\r
2150   will not try to update the service manager but it will still log to the\r
2151   event log that it is waiting for a handle.\r
2152 \r
2153   Returns: 1 if the wait timed out.\r
2154            0 if the wait completed.\r
2155           -1 on error.\r
2156 */\r
2157 int await_single_handle(SERVICE_STATUS_HANDLE status_handle, SERVICE_STATUS *status, HANDLE handle, TCHAR *name, TCHAR *function_name, unsigned long timeout) {\r
2158   unsigned long interval;\r
2159   unsigned long ret;\r
2160   unsigned long waited;\r
2161   TCHAR interval_milliseconds[16];\r
2162   TCHAR timeout_milliseconds[16];\r
2163   TCHAR waited_milliseconds[16];\r
2164   TCHAR *function = function_name;\r
2165 \r
2166   /* Add brackets to function name. */\r
2167   size_t funclen = _tcslen(function_name) + 3;\r
2168   TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));\r
2169   if (func) {\r
2170     if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;\r
2171   }\r
2172 \r
2173   _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);\r
2174 \r
2175   waited = 0;\r
2176   while (waited < timeout) {\r
2177     interval = timeout - waited;\r
2178     if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
2179 \r
2180     if (status) {\r
2181       status->dwWaitHint += interval;\r
2182       status->dwCheckPoint++;\r
2183       SetServiceStatus(status_handle, status);\r
2184     }\r
2185 \r
2186     if (waited) {\r
2187       _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);\r
2188       _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);\r
2189       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SINGLE_HANDLE, function, name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
2190     }\r
2191 \r
2192     switch (WaitForSingleObject(handle, interval)) {\r
2193       case WAIT_OBJECT_0:\r
2194         ret = 0;\r
2195         goto awaited;\r
2196 \r
2197       case WAIT_TIMEOUT:\r
2198         ret = 1;\r
2199         break;\r
2200 \r
2201       default:\r
2202         ret = -1;\r
2203         goto awaited;\r
2204     }\r
2205 \r
2206     waited += interval;\r
2207   }\r
2208 \r
2209 awaited:\r
2210   if (func) HeapFree(GetProcessHeap(), 0, func);\r
2211 \r
2212   return ret;\r
2213 }\r
2214 \r
2215 int list_nssm_services(int argc, TCHAR **argv) {\r
2216   bool including_native = (argc > 0 && str_equiv(argv[0], _T("all")));\r
2217 \r
2218   /* Open service manager. */\r
2219   SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);\r
2220   if (! services) {\r
2221     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
2222     return 1;\r
2223   }\r
2224 \r
2225   unsigned long bufsize, required, count, i;\r
2226   unsigned long resume = 0;\r
2227   EnumServicesStatus(services, SERVICE_WIN32, SERVICE_STATE_ALL, 0, 0, &required, &count, &resume);\r
2228   unsigned long error = GetLastError();\r
2229   if (error != ERROR_MORE_DATA) {\r
2230     print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));\r
2231     return 2;\r
2232   }\r
2233 \r
2234   ENUM_SERVICE_STATUS *status = (ENUM_SERVICE_STATUS *) HeapAlloc(GetProcessHeap(), 0, required);\r
2235   if (! status) {\r
2236     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("ENUM_SERVICE_STATUS"), _T("list_nssm_services()"));\r
2237     return 3;\r
2238   }\r
2239 \r
2240   bufsize = required;\r
2241   while (true) {\r
2242     int ret = EnumServicesStatus(services, SERVICE_WIN32, SERVICE_STATE_ALL, status, bufsize, &required, &count, &resume);\r
2243     if (! ret) {\r
2244       error = GetLastError();\r
2245       if (error != ERROR_MORE_DATA) {\r
2246         HeapFree(GetProcessHeap(), 0, status);\r
2247         print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));\r
2248         return 4;\r
2249       }\r
2250     }\r
2251 \r
2252     for (i = 0; i < count; i++) {\r
2253       /* Try to get the service parameters. */\r
2254       nssm_service_t *service = alloc_nssm_service();\r
2255       if (! service) {\r
2256         HeapFree(GetProcessHeap(), 0, status);\r
2257         print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("nssm_service_t"), _T("list_nssm_services()"));\r
2258         return 5;\r
2259       }\r
2260       _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), status[i].lpServiceName);\r
2261 \r
2262       get_parameters(service, 0);\r
2263       /* We manage the service if we have an Application. */\r
2264       if (including_native || service->exe[0]) _tprintf(_T("%s\n"), service->name);\r
2265 \r
2266       cleanup_nssm_service(service);\r
2267     }\r
2268 \r
2269     if (ret) break;\r
2270   }\r
2271 \r
2272   HeapFree(GetProcessHeap(), 0, status);\r
2273   return 0;\r
2274 }\r
2275 \r
2276 int service_process_tree(int argc, TCHAR **argv) {\r
2277   int errors = 0;\r
2278   if (argc < 1) return usage(1);\r
2279 \r
2280   SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT);\r
2281   if (! services) {\r
2282     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
2283     return 1;\r
2284   }\r
2285 \r
2286   /*\r
2287     We need SeDebugPrivilege to read the process tree.\r
2288     We ignore failure here so that an error will be printed later when we\r
2289     try to open a process handle.\r
2290   */\r
2291   HANDLE token = get_debug_token();\r
2292 \r
2293   TCHAR canonical_name[SERVICE_NAME_LENGTH];\r
2294   SERVICE_STATUS_PROCESS service_status;\r
2295   nssm_service_t *service;\r
2296   kill_t k;\r
2297 \r
2298   int i;\r
2299   for (i = 0; i < argc; i++) {\r
2300     TCHAR *service_name = argv[i];\r
2301     SC_HANDLE service_handle = open_service(services, service_name, SERVICE_QUERY_STATUS, canonical_name, _countof(canonical_name));\r
2302     if (! service_handle) {\r
2303       errors++;\r
2304       continue;\r
2305     }\r
2306 \r
2307     unsigned long size;\r
2308     int ret = QueryServiceStatusEx(service_handle, SC_STATUS_PROCESS_INFO, (LPBYTE) &service_status, sizeof(service_status), &size);\r
2309     long error = GetLastError();\r
2310     CloseServiceHandle(service_handle);\r
2311     if (! ret) {\r
2312       _ftprintf(stderr, _T("%s: %s\n"), canonical_name, error_string(error));\r
2313       errors++;\r
2314       continue;\r
2315     }\r
2316 \r
2317     ZeroMemory(&k, sizeof(k));\r
2318     k.pid = service_status.dwProcessId;\r
2319     if (! k.pid) continue;\r
2320 \r
2321     k.process_handle = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, k.pid);\r
2322     if (! k.process_handle) {\r
2323       _ftprintf(stderr, _T("%s: %lu: %s\n"), canonical_name, k.pid, error_string(GetLastError()));\r
2324       continue;\r
2325     }\r
2326 \r
2327     if (get_process_creation_time(k.process_handle, &k.creation_time)) continue;\r
2328     /* Dummy exit time so we can check processes' parents. */\r
2329     GetSystemTimeAsFileTime(&k.exit_time);\r
2330 \r
2331     service = alloc_nssm_service();\r
2332     if (! service) {\r
2333       errors++;\r
2334       continue;\r
2335     }\r
2336 \r
2337     _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), canonical_name);\r
2338     k.name = service->name;\r
2339     walk_process_tree(service, print_process, &k, k.pid);\r
2340 \r
2341     cleanup_nssm_service(service);\r
2342   }\r
2343 \r
2344   CloseServiceHandle(services);\r
2345   if (token != INVALID_HANDLE_VALUE) CloseHandle(token);\r
2346 \r
2347   return errors;\r
2348 }\r