Command to dump service configuration.
[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"))) mode = MODE_DUMPING;\r
910   if (argc < mandatory) return usage(1);\r
911 \r
912   const TCHAR *parameter = 0;\r
913   settings_t *setting = 0;\r
914   TCHAR *additional;\r
915 \r
916   /* Validate the parameter. */\r
917   if (mandatory > 2) {\r
918     bool additional_mandatory = false;\r
919 \r
920     parameter = argv[2];\r
921     for (i = 0; settings[i].name; i++) {\r
922       setting = &settings[i];\r
923       if (! str_equiv(setting->name, parameter)) continue;\r
924       if (((setting->additional & ADDITIONAL_GETTING) && mode == MODE_GETTING) || ((setting->additional & ADDITIONAL_SETTING) && mode == MODE_SETTING) || ((setting->additional & ADDITIONAL_RESETTING) && mode == MODE_RESETTING)) {\r
925         additional_mandatory = true;\r
926         mandatory++;\r
927       }\r
928       break;\r
929     }\r
930     if (! settings[i].name) {\r
931       print_message(stderr, NSSM_MESSAGE_INVALID_PARAMETER, parameter);\r
932       for (i = 0; settings[i].name; i++) _ftprintf(stderr, _T("%s\n"), settings[i].name);\r
933       return 1;\r
934     }\r
935 \r
936     additional = 0;\r
937     if (additional_mandatory) {\r
938       if (argc < mandatory) {\r
939         print_message(stderr, NSSM_MESSAGE_MISSING_SUBPARAMETER, parameter);\r
940         return 1;\r
941       }\r
942       additional = argv[3];\r
943       remainder = 4;\r
944     }\r
945     else if (str_equiv(setting->name, NSSM_NATIVE_OBJECTNAME) && mode == MODE_SETTING) {\r
946       additional = argv[3];\r
947       remainder = 4;\r
948     }\r
949     else {\r
950       additional = argv[remainder];\r
951       if (argc < mandatory) return usage(1);\r
952     }\r
953   }\r
954 \r
955   nssm_service_t *service = alloc_nssm_service();\r
956   _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), service_name);\r
957 \r
958   /* Open service manager */\r
959   SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);\r
960   if (! services) {\r
961     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
962     return 2;\r
963   }\r
964 \r
965   /* Try to open the service */\r
966   unsigned long access = SERVICE_QUERY_CONFIG;\r
967   if (mode != MODE_GETTING) access |= SERVICE_CHANGE_CONFIG;\r
968   service->handle = open_service(services, service->name, access, service->name, _countof(service->name));\r
969   if (! service->handle) {\r
970     CloseServiceHandle(services);\r
971     return 3;\r
972   }\r
973 \r
974   /* Get system details. */\r
975   QUERY_SERVICE_CONFIG *qsc = query_service_config(service->name, service->handle);\r
976   if (! qsc) {\r
977     CloseServiceHandle(service->handle);\r
978     CloseServiceHandle(services);\r
979     return 4;\r
980   }\r
981 \r
982   service->type = qsc->dwServiceType;\r
983   if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {\r
984     if (mode != MODE_GETTING && mode != MODE_DUMPING) {\r
985       HeapFree(GetProcessHeap(), 0, qsc);\r
986       CloseServiceHandle(service->handle);\r
987       CloseServiceHandle(services);\r
988       print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, NSSM_WIN32_OWN_PROCESS, 0);\r
989       return 3;\r
990     }\r
991   }\r
992 \r
993   if (get_service_startup(service->name, service->handle, qsc, &service->startup)) {\r
994     if (mode != MODE_GETTING && mode != MODE_DUMPING) {\r
995       HeapFree(GetProcessHeap(), 0, qsc);\r
996       CloseServiceHandle(service->handle);\r
997       CloseServiceHandle(services);\r
998       return 4;\r
999     }\r
1000   }\r
1001 \r
1002   if (get_service_username(service->name, qsc, &service->username, &service->usernamelen)) {\r
1003     if (mode != MODE_GETTING && mode != MODE_DUMPING) {\r
1004       HeapFree(GetProcessHeap(), 0, qsc);\r
1005       CloseServiceHandle(service->handle);\r
1006       CloseServiceHandle(services);\r
1007       return 5;\r
1008     }\r
1009   }\r
1010 \r
1011   _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);\r
1012 \r
1013   /* Get the canonical service name. We open it case insensitively. */\r
1014   unsigned long bufsize = _countof(service->name);\r
1015   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
1016 \r
1017   /* Remember the executable in case it isn't NSSM. */\r
1018   _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);\r
1019   HeapFree(GetProcessHeap(), 0, qsc);\r
1020 \r
1021   /* Get extended system details. */\r
1022   if (get_service_description(service->name, service->handle, _countof(service->description), service->description)) {\r
1023     if (mode != MODE_GETTING && mode != MODE_DUMPING) {\r
1024       CloseServiceHandle(service->handle);\r
1025       CloseServiceHandle(services);\r
1026       return 6;\r
1027     }\r
1028   }\r
1029 \r
1030   if (get_service_dependencies(service->name, service->handle, &service->dependencies, &service->dependencieslen)) {\r
1031     if (mode != MODE_GETTING && mode != MODE_DUMPING) {\r
1032       CloseServiceHandle(service->handle);\r
1033       CloseServiceHandle(services);\r
1034       return 7;\r
1035     }\r
1036   }\r
1037 \r
1038   /* Get NSSM details. */\r
1039   get_parameters(service, 0);\r
1040 \r
1041   CloseServiceHandle(services);\r
1042 \r
1043   if (! service->exe[0]) {\r
1044     service->native = true;\r
1045     if (mode != MODE_GETTING && mode != MODE_DUMPING) print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);\r
1046   }\r
1047 \r
1048   /* Editing with the GUI. */\r
1049   if (mode == MODE_EDITING) {\r
1050     nssm_gui(IDD_EDIT, service);\r
1051     return 0;\r
1052   }\r
1053 \r
1054   HKEY key;\r
1055   value_t value;\r
1056   int ret;\r
1057 \r
1058   if (mode == MODE_DUMPING) {\r
1059     if (service->native) key = 0;\r
1060     else {\r
1061       key = open_registry(service->name, KEY_READ);\r
1062       if (! key) return 4;\r
1063     }\r
1064 \r
1065     ret = 0;\r
1066     for (i = 0; settings[i].name; i++) {\r
1067       setting = &settings[i];\r
1068       if (! setting->native && service->native) continue;\r
1069       if (dump_setting(service->name, key, service->handle, setting)) ret++;\r
1070     }\r
1071 \r
1072     if (! service->native) RegCloseKey(key);\r
1073     CloseServiceHandle(service->handle);\r
1074 \r
1075     if (ret) return 1;\r
1076     return 0;\r
1077   }\r
1078 \r
1079   /* Trying to manage App* parameters for a non-NSSM service. */\r
1080   if (! setting->native && service->native) {\r
1081     CloseServiceHandle(service->handle);\r
1082     print_message(stderr, NSSM_MESSAGE_NATIVE_PARAMETER, setting->name, NSSM);\r
1083     return 1;\r
1084   }\r
1085 \r
1086   if (mode == MODE_GETTING) {\r
1087     if (! service->native) {\r
1088       key = open_registry(service->name, KEY_READ);\r
1089       if (! key) return 4;\r
1090     }\r
1091 \r
1092     if (setting->native) ret = get_setting(service->name, service->handle, setting, &value, additional);\r
1093     else ret = get_setting(service->name, key, setting, &value, additional);\r
1094     if (ret < 0) {\r
1095       CloseServiceHandle(service->handle);\r
1096       return 5;\r
1097     }\r
1098 \r
1099     switch (setting->type) {\r
1100       case REG_EXPAND_SZ:\r
1101       case REG_MULTI_SZ:\r
1102       case REG_SZ:\r
1103         _tprintf(_T("%s\n"), value.string ? value.string : _T(""));\r
1104         HeapFree(GetProcessHeap(), 0, value.string);\r
1105         break;\r
1106 \r
1107       case REG_DWORD:\r
1108         _tprintf(_T("%lu\n"), value.numeric);\r
1109         break;\r
1110     }\r
1111 \r
1112     if (! service->native) RegCloseKey(key);\r
1113     CloseServiceHandle(service->handle);\r
1114     return 0;\r
1115   }\r
1116 \r
1117   /* Build the value. */\r
1118   if (mode == MODE_RESETTING) {\r
1119     /* Unset the parameter. */\r
1120     value.string = 0;\r
1121   }\r
1122   else if (remainder == argc) {\r
1123     value.string = 0;\r
1124   }\r
1125   else {\r
1126     /* Set the parameter. */\r
1127     size_t len = 0;\r
1128     size_t delimiterlen = (setting->additional & ADDITIONAL_CRLF) ? 2 : 1;\r
1129     for (i = remainder; i < argc; i++) len += _tcslen(argv[i]) + delimiterlen;\r
1130     len++;\r
1131 \r
1132     value.string = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));\r
1133     if (! value.string) {\r
1134       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("value"), _T("edit_service()"));\r
1135       CloseServiceHandle(service->handle);\r
1136       return 2;\r
1137     }\r
1138 \r
1139     size_t s = 0;\r
1140     for (i = remainder; i < argc; i++) {\r
1141       size_t len = _tcslen(argv[i]);\r
1142       memmove(value.string + s, argv[i], len * sizeof(TCHAR));\r
1143       s += len;\r
1144       if (i < argc - 1) {\r
1145         if (setting->additional & ADDITIONAL_CRLF) {\r
1146           value.string[s++] = _T('\r');\r
1147           value.string[s++] = _T('\n');\r
1148         }\r
1149         else value.string[s++] = _T(' ');\r
1150       }\r
1151     }\r
1152     value.string[s] = _T('\0');\r
1153   }\r
1154 \r
1155   if (! service->native) {\r
1156     key = open_registry(service->name, KEY_READ | KEY_WRITE);\r
1157     if (! key) {\r
1158       if (value.string) HeapFree(GetProcessHeap(), 0, value.string);\r
1159       return 4;\r
1160     }\r
1161   }\r
1162 \r
1163   if (setting->native) ret = set_setting(service->name, service->handle, setting, &value, additional);\r
1164   else ret = set_setting(service->name, key, setting, &value, additional);\r
1165   if (value.string) HeapFree(GetProcessHeap(), 0, value.string);\r
1166   if (ret < 0) {\r
1167     if (! service->native) RegCloseKey(key);\r
1168     CloseServiceHandle(service->handle);\r
1169     return 6;\r
1170   }\r
1171 \r
1172   if (! service->native) RegCloseKey(key);\r
1173   CloseServiceHandle(service->handle);\r
1174 \r
1175   return 0;\r
1176 }\r
1177 \r
1178 /* About to remove the service */\r
1179 int pre_remove_service(int argc, TCHAR **argv) {\r
1180   nssm_service_t *service = alloc_nssm_service();\r
1181   set_nssm_service_defaults(service);\r
1182   if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
1183 \r
1184   /* Show dialogue box if we didn't pass service name and "confirm" */\r
1185   if (argc < 2) return nssm_gui(IDD_REMOVE, service);\r
1186   if (str_equiv(argv[1], _T("confirm"))) {\r
1187     int ret = remove_service(service);\r
1188     cleanup_nssm_service(service);\r
1189     return ret;\r
1190   }\r
1191   print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);\r
1192   return 100;\r
1193 }\r
1194 \r
1195 /* Install the service */\r
1196 int install_service(nssm_service_t *service) {\r
1197   if (! service) return 1;\r
1198 \r
1199   /* Open service manager */\r
1200   SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT | SC_MANAGER_CREATE_SERVICE);\r
1201   if (! services) {\r
1202     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
1203     cleanup_nssm_service(service);\r
1204     return 2;\r
1205   }\r
1206 \r
1207   /* Get path of this program */\r
1208   _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), nssm_imagepath());\r
1209 \r
1210   /* Create the service - settings will be changed in edit_service() */\r
1211   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
1212   if (! service->handle) {\r
1213     print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED, error_string(GetLastError()));\r
1214     CloseServiceHandle(services);\r
1215     return 5;\r
1216   }\r
1217 \r
1218   if (edit_service(service, false)) {\r
1219     DeleteService(service->handle);\r
1220     CloseServiceHandle(services);\r
1221     return 6;\r
1222   }\r
1223 \r
1224   print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);\r
1225 \r
1226   /* Cleanup */\r
1227   CloseServiceHandle(services);\r
1228 \r
1229   return 0;\r
1230 }\r
1231 \r
1232 /* Edit the service. */\r
1233 int edit_service(nssm_service_t *service, bool editing) {\r
1234   if (! service) return 1;\r
1235 \r
1236   /*\r
1237     The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS\r
1238     and SERVICE_INTERACTIVE_PROCESS.\r
1239   */\r
1240   service->type &= SERVICE_INTERACTIVE_PROCESS;\r
1241   service->type |= SERVICE_WIN32_OWN_PROCESS;\r
1242 \r
1243   /* Startup type. */\r
1244   unsigned long startup;\r
1245   switch (service->startup) {\r
1246     case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;\r
1247     case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;\r
1248     default: startup = SERVICE_AUTO_START;\r
1249   }\r
1250 \r
1251   /* Display name. */\r
1252   if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);\r
1253 \r
1254   /*\r
1255     Username must be NULL if we aren't changing or an account name.\r
1256     We must explicitly use LOCALSYSTEM to change it when we are editing.\r
1257     Password must be NULL if we aren't changing, a password or "".\r
1258     Empty passwords are valid but we won't allow them in the GUI.\r
1259   */\r
1260   TCHAR *username = 0;\r
1261   TCHAR *canon = 0;\r
1262   TCHAR *password = 0;\r
1263   if (service->usernamelen) {\r
1264     username = service->username;\r
1265     if (canonicalise_username(username, &canon)) return 5;\r
1266     if (service->passwordlen) password = service->password;\r
1267   }\r
1268   else if (editing) username = canon = NSSM_LOCALSYSTEM_ACCOUNT;\r
1269 \r
1270   if (well_known_username(canon)) password = _T("");\r
1271   else {\r
1272     if (grant_logon_as_service(canon)) {\r
1273       if (canon != username) HeapFree(GetProcessHeap(), 0, canon);\r
1274       print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);\r
1275       return 5;\r
1276     }\r
1277   }\r
1278 \r
1279   TCHAR *dependencies = _T("");\r
1280   if (service->dependencieslen) dependencies = 0; /* Change later. */\r
1281 \r
1282   if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, dependencies, canon, password, service->displayname)) {\r
1283     if (canon != username) HeapFree(GetProcessHeap(), 0, canon);\r
1284     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));\r
1285     return 5;\r
1286   }\r
1287   if (canon != username) HeapFree(GetProcessHeap(), 0, canon);\r
1288 \r
1289   if (service->dependencieslen) {\r
1290     if (set_service_dependencies(service->name, service->handle, service->dependencies)) return 5;\r
1291   }\r
1292 \r
1293   if (service->description[0] || editing) {\r
1294     set_service_description(service->name, service->handle, service->description);\r
1295   }\r
1296 \r
1297   SERVICE_DELAYED_AUTO_START_INFO delayed;\r
1298   ZeroMemory(&delayed, sizeof(delayed));\r
1299   if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;\r
1300   else delayed.fDelayedAutostart = 0;\r
1301   /* Delayed startup isn't supported until Vista. */\r
1302   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {\r
1303     unsigned long error = GetLastError();\r
1304     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
1305     if (error != ERROR_INVALID_LEVEL) {\r
1306       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);\r
1307     }\r
1308   }\r
1309 \r
1310   /* Don't mess with parameters which aren't ours. */\r
1311   if (! service->native) {\r
1312     /* Now we need to put the parameters into the registry */\r
1313     if (create_parameters(service, editing)) {\r
1314       print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);\r
1315       return 6;\r
1316     }\r
1317 \r
1318     set_service_recovery(service);\r
1319   }\r
1320 \r
1321   return 0;\r
1322 }\r
1323 \r
1324 /* Control a service. */\r
1325 int control_service(unsigned long control, int argc, TCHAR **argv) {\r
1326   if (argc < 1) return usage(1);\r
1327   TCHAR *service_name = argv[0];\r
1328   TCHAR canonical_name[SERVICE_NAME_LENGTH];\r
1329 \r
1330   SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);\r
1331   if (! services) {\r
1332     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
1333     return 2;\r
1334   }\r
1335 \r
1336   unsigned long access = SERVICE_QUERY_STATUS;\r
1337   switch (control) {\r
1338     case NSSM_SERVICE_CONTROL_START:\r
1339       access |= SERVICE_START;\r
1340     break;\r
1341 \r
1342     case SERVICE_CONTROL_CONTINUE:\r
1343     case SERVICE_CONTROL_PAUSE:\r
1344       access |= SERVICE_PAUSE_CONTINUE;\r
1345       break;\r
1346 \r
1347     case SERVICE_CONTROL_STOP:\r
1348       access |= SERVICE_STOP;\r
1349       break;\r
1350 \r
1351     case NSSM_SERVICE_CONTROL_ROTATE:\r
1352       access |= SERVICE_USER_DEFINED_CONTROL;\r
1353       break;\r
1354   }\r
1355 \r
1356   SC_HANDLE service_handle = open_service(services, service_name, access, canonical_name, _countof(canonical_name));\r
1357   if (! service_handle) {\r
1358     CloseServiceHandle(services);\r
1359     return 3;\r
1360   }\r
1361 \r
1362   int ret;\r
1363   unsigned long error;\r
1364   SERVICE_STATUS service_status;\r
1365   if (control == NSSM_SERVICE_CONTROL_START) {\r
1366     unsigned long initial_status = SERVICE_STOPPED;\r
1367     ret = StartService(service_handle, (unsigned long) argc, (const TCHAR **) argv);\r
1368     error = GetLastError();\r
1369     CloseServiceHandle(services);\r
1370 \r
1371     if (error == ERROR_IO_PENDING) {\r
1372       /*\r
1373         Older versions of Windows return immediately with ERROR_IO_PENDING\r
1374         indicate that the operation is still in progress.  Newer versions\r
1375         will return it if there really is a delay.\r
1376       */\r
1377       ret = 1;\r
1378       error = ERROR_SUCCESS;\r
1379     }\r
1380 \r
1381     if (ret) {\r
1382       int response = await_service_control_response(control, service_handle, &service_status, initial_status);\r
1383       CloseServiceHandle(service_handle);\r
1384 \r
1385       if (response) {\r
1386         print_message(stderr, NSSM_MESSAGE_BAD_CONTROL_RESPONSE, canonical_name, service_status_text(service_status.dwCurrentState), service_control_text(control));\r
1387         return 1;\r
1388       }\r
1389       else _tprintf(_T("%s: %s: %s"), canonical_name, service_control_text(control), error_string(error));\r
1390       return 0;\r
1391     }\r
1392     else {\r
1393       CloseServiceHandle(service_handle);\r
1394       _ftprintf(stderr, _T("%s: %s: %s"), canonical_name, service_control_text(control), error_string(error));\r
1395       return 1;\r
1396     }\r
1397   }\r
1398   else if (control == SERVICE_CONTROL_INTERROGATE) {\r
1399     /*\r
1400       We could actually send an INTERROGATE control but that won't return\r
1401       any information if the service is stopped and we don't care about\r
1402       the extra details it might give us in any case.  So we'll fake it.\r
1403     */\r
1404     ret = QueryServiceStatus(service_handle, &service_status);\r
1405     error = GetLastError();\r
1406 \r
1407     if (ret) {\r
1408       _tprintf(_T("%s\n"), service_status_text(service_status.dwCurrentState));\r
1409       return 0;\r
1410     }\r
1411     else {\r
1412       _ftprintf(stderr, _T("%s: %s\n"), canonical_name, error_string(error));\r
1413       return 1;\r
1414     }\r
1415   }\r
1416   else {\r
1417     ret = ControlService(service_handle, control, &service_status);\r
1418     unsigned long initial_status = service_status.dwCurrentState;\r
1419     error = GetLastError();\r
1420     CloseServiceHandle(services);\r
1421 \r
1422     if (error == ERROR_IO_PENDING) {\r
1423       ret = 1;\r
1424       error = ERROR_SUCCESS;\r
1425     }\r
1426 \r
1427     if (ret) {\r
1428       int response = await_service_control_response(control, service_handle, &service_status, initial_status);\r
1429       CloseServiceHandle(service_handle);\r
1430 \r
1431       if (response) {\r
1432         print_message(stderr, NSSM_MESSAGE_BAD_CONTROL_RESPONSE, canonical_name, service_status_text(service_status.dwCurrentState), service_control_text(control));\r
1433         return 1;\r
1434       }\r
1435       else _tprintf(_T("%s: %s: %s"), canonical_name, service_control_text(control), error_string(error));\r
1436       return 0;\r
1437     }\r
1438     else {\r
1439       CloseServiceHandle(service_handle);\r
1440       _ftprintf(stderr, _T("%s: %s: %s"), canonical_name, service_control_text(control), error_string(error));\r
1441       if (error == ERROR_SERVICE_NOT_ACTIVE) {\r
1442         if (control == SERVICE_CONTROL_SHUTDOWN || control == SERVICE_CONTROL_STOP) return 0;\r
1443       }\r
1444       return 1;\r
1445     }\r
1446   }\r
1447 }\r
1448 \r
1449 /* Remove the service */\r
1450 int remove_service(nssm_service_t *service) {\r
1451   if (! service) return 1;\r
1452 \r
1453   /* Open service manager */\r
1454   SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);\r
1455   if (! services) {\r
1456     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
1457     return 2;\r
1458   }\r
1459 \r
1460   /* Try to open the service */\r
1461   service->handle = open_service(services, service->name, DELETE, service->name, _countof(service->name));\r
1462   if (! service->handle) {\r
1463     CloseServiceHandle(services);\r
1464     return 3;\r
1465   }\r
1466 \r
1467   /* Get the canonical service name. We open it case insensitively. */\r
1468   unsigned long bufsize = _countof(service->displayname);\r
1469   GetServiceDisplayName(services, service->name, service->displayname, &bufsize);\r
1470   bufsize = _countof(service->name);\r
1471   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
1472 \r
1473   /* Try to delete the service */\r
1474   if (! DeleteService(service->handle)) {\r
1475     print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);\r
1476     CloseServiceHandle(services);\r
1477     return 4;\r
1478   }\r
1479 \r
1480   /* Cleanup */\r
1481   CloseServiceHandle(services);\r
1482 \r
1483   print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);\r
1484   return 0;\r
1485 }\r
1486 \r
1487 /* Service initialisation */\r
1488 void WINAPI service_main(unsigned long argc, TCHAR **argv) {\r
1489   nssm_service_t *service = alloc_nssm_service();\r
1490   if (! service) return;\r
1491 \r
1492   if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {\r
1493     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);\r
1494     return;\r
1495   }\r
1496 \r
1497   /* We can use a condition variable in a critical section on Vista or later. */\r
1498   if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;\r
1499   else use_critical_section = false;\r
1500 \r
1501   /* Initialise status */\r
1502   ZeroMemory(&service->status, sizeof(service->status));\r
1503   service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
1504   service->status.dwControlsAccepted = 0;\r
1505   service->status.dwWin32ExitCode = NO_ERROR;\r
1506   service->status.dwServiceSpecificExitCode = 0;\r
1507   service->status.dwCheckPoint = 0;\r
1508   service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
1509 \r
1510   /* Signal we AREN'T running the server */\r
1511   service->process_handle = 0;\r
1512   service->pid = 0;\r
1513 \r
1514   /* Register control handler */\r
1515   service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);\r
1516   if (! service->status_handle) {\r
1517     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);\r
1518     return;\r
1519   }\r
1520 \r
1521   log_service_control(service->name, 0, true);\r
1522 \r
1523   service->status.dwCurrentState = SERVICE_START_PENDING;\r
1524   service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;\r
1525   SetServiceStatus(service->status_handle, &service->status);\r
1526 \r
1527   if (is_admin) {\r
1528     /* Try to create the exit action parameters; we don't care if it fails */\r
1529     create_exit_action(service->name, exit_action_strings[0], false);\r
1530 \r
1531     SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT);\r
1532     if (services) {\r
1533       service->handle = open_service(services, service->name, SERVICE_CHANGE_CONFIG, 0, 0);\r
1534       set_service_recovery(service);\r
1535 \r
1536       /* Remember our display name. */\r
1537       unsigned long displayname_len = _countof(service->displayname);\r
1538       GetServiceDisplayName(services, service->name, service->displayname, &displayname_len);\r
1539 \r
1540       CloseServiceHandle(services);\r
1541     }\r
1542   }\r
1543 \r
1544   /* Used for signalling a resume if the service pauses when throttled. */\r
1545   if (use_critical_section) {\r
1546     InitializeCriticalSection(&service->throttle_section);\r
1547     service->throttle_section_initialised = true;\r
1548   }\r
1549   else {\r
1550     service->throttle_timer = CreateWaitableTimer(0, 1, 0);\r
1551     if (! service->throttle_timer) {\r
1552       log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);\r
1553     }\r
1554   }\r
1555 \r
1556   /* Critical section for hooks. */\r
1557   InitializeCriticalSection(&service->hook_section);\r
1558   service->hook_section_initialised = true;\r
1559 \r
1560   /* Remember our initial environment. */\r
1561   service->initial_env = copy_environment();\r
1562 \r
1563   /* Remember our creation time. */\r
1564   if (get_process_creation_time(GetCurrentProcess(), &service->nssm_creation_time)) ZeroMemory(&service->nssm_creation_time, sizeof(service->nssm_creation_time));\r
1565 \r
1566   service->allow_restart = true;\r
1567   if (! CreateThread(NULL, 0, launch_service, (void *) service, 0, NULL)) {\r
1568     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
1569     stop_service(service, 0, true, true);\r
1570   }\r
1571 }\r
1572 \r
1573 /* Make sure service recovery actions are taken where necessary */\r
1574 void set_service_recovery(nssm_service_t *service) {\r
1575   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
1576   ZeroMemory(&flag, sizeof(flag));\r
1577   flag.fFailureActionsOnNonCrashFailures = true;\r
1578 \r
1579   /* This functionality was added in Vista so the call may fail */\r
1580   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {\r
1581     unsigned long error = GetLastError();\r
1582     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
1583     if (error != ERROR_INVALID_LEVEL) {\r
1584       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);\r
1585     }\r
1586   }\r
1587 }\r
1588 \r
1589 int monitor_service(nssm_service_t *service) {\r
1590   /* Set service status to started */\r
1591   int ret = start_service(service);\r
1592   if (ret) {\r
1593     TCHAR code[16];\r
1594     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);\r
1595     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);\r
1596     return ret;\r
1597   }\r
1598   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);\r
1599 \r
1600   /* Monitor service */\r
1601   if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
1602     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);\r
1603   }\r
1604 \r
1605   return 0;\r
1606 }\r
1607 \r
1608 TCHAR *service_control_text(unsigned long control) {\r
1609   switch (control) {\r
1610     /* HACK: there is no SERVICE_CONTROL_START constant */\r
1611     case NSSM_SERVICE_CONTROL_START: return _T("START");\r
1612     case SERVICE_CONTROL_STOP: return _T("STOP");\r
1613     case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");\r
1614     case SERVICE_CONTROL_PAUSE: return _T("PAUSE");\r
1615     case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");\r
1616     case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");\r
1617     case NSSM_SERVICE_CONTROL_ROTATE: return _T("ROTATE");\r
1618     case SERVICE_CONTROL_POWEREVENT: return _T("POWEREVENT");\r
1619     default: return 0;\r
1620   }\r
1621 }\r
1622 \r
1623 TCHAR *service_status_text(unsigned long status) {\r
1624   switch (status) {\r
1625     case SERVICE_STOPPED: return _T("SERVICE_STOPPED");\r
1626     case SERVICE_START_PENDING: return _T("SERVICE_START_PENDING");\r
1627     case SERVICE_STOP_PENDING: return _T("SERVICE_STOP_PENDING");\r
1628     case SERVICE_RUNNING: return _T("SERVICE_RUNNING");\r
1629     case SERVICE_CONTINUE_PENDING: return _T("SERVICE_CONTINUE_PENDING");\r
1630     case SERVICE_PAUSE_PENDING: return _T("SERVICE_PAUSE_PENDING");\r
1631     case SERVICE_PAUSED: return _T("SERVICE_PAUSED");\r
1632     default: return 0;\r
1633   }\r
1634 }\r
1635 \r
1636 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {\r
1637   TCHAR *text = service_control_text(control);\r
1638   unsigned long event;\r
1639 \r
1640   if (! text) {\r
1641     /* "0x" + 8 x hex + NULL */\r
1642     text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));\r
1643     if (! text) {\r
1644       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
1645       return;\r
1646     }\r
1647     if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {\r
1648       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
1649       HeapFree(GetProcessHeap(), 0, text);\r
1650       return;\r
1651     }\r
1652 \r
1653     event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;\r
1654   }\r
1655   else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;\r
1656   else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;\r
1657 \r
1658   log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);\r
1659 \r
1660   if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {\r
1661     HeapFree(GetProcessHeap(), 0, text);\r
1662   }\r
1663 }\r
1664 \r
1665 /* Service control handler */\r
1666 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
1667   nssm_service_t *service = (nssm_service_t *) context;\r
1668 \r
1669   switch (control) {\r
1670     case SERVICE_CONTROL_INTERROGATE:\r
1671       /* We always keep the service status up-to-date so this is a no-op. */\r
1672       return NO_ERROR;\r
1673 \r
1674     case SERVICE_CONTROL_SHUTDOWN:\r
1675     case SERVICE_CONTROL_STOP:\r
1676       service->last_control = control;\r
1677       log_service_control(service->name, control, true);\r
1678 \r
1679       /* Immediately block further controls. */\r
1680       service->allow_restart = false;\r
1681       service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1682       service->status.dwControlsAccepted = 0;\r
1683       SetServiceStatus(service->status_handle, &service->status);\r
1684 \r
1685       /* Pre-stop hook. */\r
1686       nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_STOP, NSSM_HOOK_ACTION_PRE, &control, NSSM_SERVICE_STATUS_DEADLINE, false);\r
1687 \r
1688       /*\r
1689         We MUST acknowledge the stop request promptly but we're committed to\r
1690         waiting for the application to exit.  Spawn a new thread to wait\r
1691         while we acknowledge the request.\r
1692       */\r
1693       if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {\r
1694         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
1695 \r
1696         /*\r
1697           We couldn't create a thread to tidy up so we'll have to force the tidyup\r
1698           to complete in time in this thread.\r
1699         */\r
1700         service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
1701         service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
1702         service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
1703 \r
1704         stop_service(service, 0, true, true);\r
1705       }\r
1706       return NO_ERROR;\r
1707 \r
1708     case SERVICE_CONTROL_CONTINUE:\r
1709       service->last_control = control;\r
1710       log_service_control(service->name, control, true);\r
1711       service->throttle = 0;\r
1712       if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);\r
1713       else {\r
1714         if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
1715         ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1716         SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1717       }\r
1718       /* We can't continue if the application is running! */\r
1719       if (! service->process_handle) service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
1720       service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;\r
1721       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);\r
1722       SetServiceStatus(service->status_handle, &service->status);\r
1723       return NO_ERROR;\r
1724 \r
1725     case SERVICE_CONTROL_PAUSE:\r
1726       /*\r
1727         We don't accept pause messages but it isn't possible to register\r
1728         only for continue messages so we have to handle this case.\r
1729       */\r
1730       log_service_control(service->name, control, false);\r
1731       return ERROR_CALL_NOT_IMPLEMENTED;\r
1732 \r
1733     case NSSM_SERVICE_CONTROL_ROTATE:\r
1734       service->last_control = control;\r
1735       log_service_control(service->name, control, true);\r
1736       (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_ROTATE, NSSM_HOOK_ACTION_PRE, &control, NSSM_HOOK_DEADLINE, false);\r
1737       if (service->rotate_stdout_online == NSSM_ROTATE_ONLINE) service->rotate_stdout_online = NSSM_ROTATE_ONLINE_ASAP;\r
1738       if (service->rotate_stderr_online == NSSM_ROTATE_ONLINE) service->rotate_stderr_online = NSSM_ROTATE_ONLINE_ASAP;\r
1739       (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_ROTATE, NSSM_HOOK_ACTION_POST, &control);\r
1740       return NO_ERROR;\r
1741 \r
1742     case SERVICE_CONTROL_POWEREVENT:\r
1743       /* Resume from suspend. */\r
1744       if (event == PBT_APMRESUMEAUTOMATIC) {\r
1745         service->last_control = control;\r
1746         log_service_control(service->name, control, true);\r
1747         (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_POWER, NSSM_HOOK_ACTION_RESUME, &control);\r
1748         return NO_ERROR;\r
1749       }\r
1750 \r
1751       /* Battery low or changed to A/C power or something. */\r
1752       if (event == PBT_APMPOWERSTATUSCHANGE) {\r
1753         service->last_control = control;\r
1754         log_service_control(service->name, control, true);\r
1755         (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_POWER, NSSM_HOOK_ACTION_CHANGE, &control);\r
1756         return NO_ERROR;\r
1757       }\r
1758       log_service_control(service->name, control, false);\r
1759       return NO_ERROR;\r
1760   }\r
1761 \r
1762   /* Unknown control */\r
1763   log_service_control(service->name, control, false);\r
1764   return ERROR_CALL_NOT_IMPLEMENTED;\r
1765 }\r
1766 \r
1767 /* Start the service */\r
1768 int start_service(nssm_service_t *service) {\r
1769   service->stopping = false;\r
1770 \r
1771   if (service->process_handle) return 0;\r
1772   service->start_requested_count++;\r
1773 \r
1774   /* Allocate a STARTUPINFO structure for a new process */\r
1775   STARTUPINFO si;\r
1776   ZeroMemory(&si, sizeof(si));\r
1777   si.cb = sizeof(si);\r
1778 \r
1779   /* Allocate a PROCESSINFO structure for the process */\r
1780   PROCESS_INFORMATION pi;\r
1781   ZeroMemory(&pi, sizeof(pi));\r
1782 \r
1783   /* Get startup parameters */\r
1784   int ret = get_parameters(service, &si);\r
1785   if (ret) {\r
1786     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);\r
1787     unset_service_environment(service);\r
1788     return stop_service(service, 2, true, true);\r
1789   }\r
1790 \r
1791   /* Launch executable with arguments */\r
1792   TCHAR cmd[CMD_LENGTH];\r
1793   if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {\r
1794     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);\r
1795     unset_service_environment(service);\r
1796     return stop_service(service, 2, true, true);\r
1797   }\r
1798 \r
1799   throttle_restart(service);\r
1800 \r
1801   service->status.dwCurrentState = SERVICE_START_PENDING;\r
1802   service->status.dwControlsAccepted = SERVICE_ACCEPT_POWEREVENT | SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP;\r
1803   SetServiceStatus(service->status_handle, &service->status);\r
1804 \r
1805   unsigned long control = NSSM_SERVICE_CONTROL_START;\r
1806 \r
1807   /* Did another thread receive a stop control? */\r
1808   if (service->allow_restart) {\r
1809     /* Set up I/O redirection. */\r
1810     if (get_output_handles(service, &si)) {\r
1811       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_OUTPUT_HANDLES_FAILED, service->name, 0);\r
1812       if (! service->no_console) FreeConsole();\r
1813       close_output_handles(&si);\r
1814       unset_service_environment(service);\r
1815       return stop_service(service, 4, true, true);\r
1816     }\r
1817 \r
1818     /* Pre-start hook. May need I/O to have been redirected already. */\r
1819     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
1820       TCHAR code[16];\r
1821       _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), NSSM_HOOK_STATUS_ABORT);\r
1822       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_PRESTART_HOOK_ABORT, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_PRE, service->name, code, 0);\r
1823       unset_service_environment(service);\r
1824       return stop_service(service, 5, true, true);\r
1825     }\r
1826 \r
1827     /* The pre-start hook will have cleaned the environment. */\r
1828     set_service_environment(service);\r
1829 \r
1830     bool inherit_handles = false;\r
1831     if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
1832     unsigned long flags = service->priority & priority_mask();\r
1833     if (service->affinity) flags |= CREATE_SUSPENDED;\r
1834     if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, 0, service->dir, &si, &pi)) {\r
1835       unsigned long exitcode = 3;\r
1836       unsigned long error = GetLastError();\r
1837       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
1838       close_output_handles(&si);\r
1839       unset_service_environment(service);\r
1840       return stop_service(service, exitcode, true, true);\r
1841     }\r
1842     service->start_count++;\r
1843     service->process_handle = pi.hProcess;\r
1844     service->pid = pi.dwProcessId;\r
1845 \r
1846     if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
1847 \r
1848     close_output_handles(&si);\r
1849 \r
1850     if (! service->no_console) FreeConsole();\r
1851 \r
1852     if (service->affinity) {\r
1853       /*\r
1854         We are explicitly storing service->affinity as a 64-bit unsigned integer\r
1855         so that we can parse it regardless of whether we're running in 32-bit\r
1856         or 64-bit mode.  The arguments to SetProcessAffinityMask(), however, are\r
1857         defined as type DWORD_PTR and hence limited to 32 bits on a 32-bit system\r
1858         (or when running the 32-bit NSSM).\r
1859 \r
1860         The result is a lot of seemingly-unnecessary casting throughout the code\r
1861         and potentially confusion when we actually try to start the service.\r
1862         Having said that, however, it's unlikely that we're actually going to\r
1863         run in 32-bit mode on a system which has more than 32 CPUs so the\r
1864         likelihood of seeing a confusing situation is somewhat diminished.\r
1865       */\r
1866       DWORD_PTR affinity, system_affinity;\r
1867 \r
1868       if (GetProcessAffinityMask(service->process_handle, &affinity, &system_affinity)) affinity = service->affinity & system_affinity;\r
1869       else {\r
1870         affinity = (DWORD_PTR) service->affinity;\r
1871         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
1872       }\r
1873 \r
1874       if (! SetProcessAffinityMask(service->process_handle, affinity)) {\r
1875         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_SETPROCESSAFFINITYMASK_FAILED, service->name, error_string(GetLastError()), 0);\r
1876       }\r
1877 \r
1878       ResumeThread(pi.hThread);\r
1879     }\r
1880   }\r
1881 \r
1882   /* Restore our environment. */\r
1883   unset_service_environment(service);\r
1884 \r
1885   /*\r
1886     Wait for a clean startup before changing the service status to RUNNING\r
1887     but be mindful of the fact that we are blocking the service control manager\r
1888     so abandon the wait before too much time has elapsed.\r
1889   */\r
1890   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
1891 \r
1892   /* Did another thread receive a stop control? */\r
1893   if (! service->allow_restart) return 0;\r
1894 \r
1895   /* Signal successful start */\r
1896   service->status.dwCurrentState = SERVICE_RUNNING;\r
1897   service->status.dwControlsAccepted &= ~SERVICE_ACCEPT_PAUSE_CONTINUE;\r
1898   SetServiceStatus(service->status_handle, &service->status);\r
1899 \r
1900   /* Post-start hook. */\r
1901   if (! service->throttle) {\r
1902     (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_START, NSSM_HOOK_ACTION_POST, &control);\r
1903   }\r
1904 \r
1905   /* Ensure the restart delay is always applied. */\r
1906   if (service->restart_delay && ! service->throttle) service->throttle++;\r
1907 \r
1908   return 0;\r
1909 }\r
1910 \r
1911 /* Stop the service */\r
1912 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {\r
1913   service->allow_restart = false;\r
1914   if (service->wait_handle) {\r
1915     UnregisterWait(service->wait_handle);\r
1916     service->wait_handle = 0;\r
1917   }\r
1918 \r
1919   service->rotate_stdout_online = service->rotate_stderr_online = NSSM_ROTATE_OFFLINE;\r
1920 \r
1921   if (default_action && ! exitcode && ! graceful) {\r
1922     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
1923     graceful = true;\r
1924   }\r
1925 \r
1926   /* Signal we are stopping */\r
1927   if (graceful) {\r
1928     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1929     service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
1930     SetServiceStatus(service->status_handle, &service->status);\r
1931   }\r
1932 \r
1933   /* Nothing to do if service isn't running */\r
1934   if (service->pid) {\r
1935     /* Shut down service */\r
1936     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);\r
1937     kill_t k;\r
1938     service_kill_t(service, &k);\r
1939     k.exitcode = 0;\r
1940     kill_process(&k);\r
1941   }\r
1942   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);\r
1943 \r
1944   end_service((void *) service, true);\r
1945 \r
1946   /* Signal we stopped */\r
1947   if (graceful) {\r
1948     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1949     wait_for_hooks(service, true);\r
1950     service->status.dwCurrentState = SERVICE_STOPPED;\r
1951     if (exitcode) {\r
1952       service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
1953       service->status.dwServiceSpecificExitCode = exitcode;\r
1954     }\r
1955     else {\r
1956       service->status.dwWin32ExitCode = NO_ERROR;\r
1957       service->status.dwServiceSpecificExitCode = 0;\r
1958     }\r
1959     SetServiceStatus(service->status_handle, &service->status);\r
1960   }\r
1961 \r
1962   return exitcode;\r
1963 }\r
1964 \r
1965 /* Callback function triggered when the server exits */\r
1966 void CALLBACK end_service(void *arg, unsigned char why) {\r
1967   nssm_service_t *service = (nssm_service_t *) arg;\r
1968 \r
1969   if (service->stopping) return;\r
1970 \r
1971   service->stopping = true;\r
1972 \r
1973   service->rotate_stdout_online = service->rotate_stderr_online = NSSM_ROTATE_OFFLINE;\r
1974 \r
1975   /* Use now as a dummy exit time. */\r
1976   GetSystemTimeAsFileTime(&service->exit_time);\r
1977 \r
1978   /* Check exit code */\r
1979   unsigned long exitcode = 0;\r
1980   TCHAR code[16];\r
1981   if (service->process_handle) {\r
1982     GetExitCodeProcess(service->process_handle, &exitcode);\r
1983     service->exitcode = exitcode;\r
1984     /* Check real exit time. */\r
1985     if (exitcode != STILL_ACTIVE) get_process_exit_time(service->process_handle, &service->exit_time);\r
1986     CloseHandle(service->process_handle);\r
1987   }\r
1988 \r
1989   service->process_handle = 0;\r
1990 \r
1991   /*\r
1992     Log that the service ended BEFORE logging about killing the process\r
1993     tree.  See below for the possible values of the why argument.\r
1994   */\r
1995   if (! why) {\r
1996     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);\r
1997     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);\r
1998   }\r
1999 \r
2000   /* Clean up. */\r
2001   if (exitcode == STILL_ACTIVE) exitcode = 0;\r
2002   if (service->pid && service->kill_process_tree) {\r
2003     kill_t k;\r
2004     service_kill_t(service, &k);\r
2005     kill_process_tree(&k, service->pid);\r
2006   }\r
2007   service->pid = 0;\r
2008 \r
2009   /* Exit hook. */\r
2010   service->exit_count++;\r
2011   (void) nssm_hook(&hook_threads, service, NSSM_HOOK_EVENT_EXIT, NSSM_HOOK_ACTION_POST, NULL, NSSM_HOOK_DEADLINE, true);\r
2012 \r
2013   /*\r
2014     The why argument is true if our wait timed out or false otherwise.\r
2015     Our wait is infinite so why will never be true when called by the system.\r
2016     If it is indeed true, assume we were called from stop_service() because\r
2017     this is a controlled shutdown, and don't take any restart action.\r
2018   */\r
2019   if (why) return;\r
2020   if (! service->allow_restart) return;\r
2021 \r
2022   /* What action should we take? */\r
2023   int action = NSSM_EXIT_RESTART;\r
2024   TCHAR action_string[ACTION_LEN];\r
2025   bool default_action;\r
2026   if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {\r
2027     for (int i = 0; exit_action_strings[i]; i++) {\r
2028       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
2029         action = i;\r
2030         break;\r
2031       }\r
2032     }\r
2033   }\r
2034 \r
2035   switch (action) {\r
2036     /* Try to restart the service or return failure code to service manager */\r
2037     case NSSM_EXIT_RESTART:\r
2038       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);\r
2039       while (monitor_service(service)) {\r
2040         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);\r
2041         Sleep(30000);\r
2042       }\r
2043     break;\r
2044 \r
2045     /* Do nothing, just like srvany would */\r
2046     case NSSM_EXIT_IGNORE:\r
2047       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
2048       wait_for_hooks(service, false);\r
2049       Sleep(INFINITE);\r
2050     break;\r
2051 \r
2052     /* Tell the service manager we are finished */\r
2053     case NSSM_EXIT_REALLY:\r
2054       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);\r
2055       stop_service(service, exitcode, true, default_action);\r
2056     break;\r
2057 \r
2058     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
2059     case NSSM_EXIT_UNCLEAN:\r
2060       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
2061       stop_service(service, exitcode, false, default_action);\r
2062       wait_for_hooks(service, false);\r
2063       free_imports();\r
2064       exit(exitcode);\r
2065   }\r
2066 }\r
2067 \r
2068 void throttle_restart(nssm_service_t *service) {\r
2069   /* This can't be a restart if the service is already running. */\r
2070   if (! service->throttle++) return;\r
2071 \r
2072   unsigned long ms;\r
2073   unsigned long throttle_ms = throttle_milliseconds(service->throttle);\r
2074   TCHAR threshold[8], milliseconds[8];\r
2075 \r
2076   if (service->restart_delay > throttle_ms) ms = service->restart_delay;\r
2077   else ms = throttle_ms;\r
2078 \r
2079   _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
2080 \r
2081   if (service->throttle == 1 && service->restart_delay > throttle_ms) log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESTART_DELAY, service->name, milliseconds, 0);\r
2082   else {\r
2083     _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);\r
2084     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);\r
2085   }\r
2086 \r
2087   if (use_critical_section) EnterCriticalSection(&service->throttle_section);\r
2088   else if (service->throttle_timer) {\r
2089     ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
2090     service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
2091     SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
2092   }\r
2093 \r
2094   service->status.dwCurrentState = SERVICE_PAUSED;\r
2095   service->status.dwControlsAccepted |= SERVICE_ACCEPT_PAUSE_CONTINUE;\r
2096   SetServiceStatus(service->status_handle, &service->status);\r
2097 \r
2098   if (use_critical_section) {\r
2099     imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);\r
2100     LeaveCriticalSection(&service->throttle_section);\r
2101   }\r
2102   else {\r
2103     if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);\r
2104     else Sleep(ms);\r
2105   }\r
2106 }\r
2107 \r
2108 /*\r
2109   When responding to a stop (or any other) request we need to set dwWaitHint to\r
2110   the number of milliseconds we expect the operation to take, and optionally\r
2111   increase dwCheckPoint.  If dwWaitHint milliseconds elapses without the\r
2112   operation completing or dwCheckPoint increasing, the system will consider the\r
2113   service to be hung.\r
2114 \r
2115   However the system will consider the service to be hung after 30000\r
2116   milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not\r
2117   changed.  Therefore if we want to wait longer than that we must periodically\r
2118   increase dwCheckPoint.\r
2119 \r
2120   Furthermore, it will consider the service to be hung after 60000 milliseconds\r
2121   regardless of the value of dwCheckPoint unless dwWaitHint is increased every\r
2122   time dwCheckPoint is also increased.\r
2123 \r
2124   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
2125   NSSM_SERVICE_STATUS_DEADLINE milliseconds.  If the process is still running\r
2126   and we haven't finished waiting we increment dwCheckPoint and add whichever is\r
2127   smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to\r
2128   dwWaitHint.\r
2129 \r
2130   Only doing both these things will prevent the system from killing the service.\r
2131 \r
2132   If the status_handle and service_status arguments are omitted, this function\r
2133   will not try to update the service manager but it will still log to the\r
2134   event log that it is waiting for a handle.\r
2135 \r
2136   Returns: 1 if the wait timed out.\r
2137            0 if the wait completed.\r
2138           -1 on error.\r
2139 */\r
2140 int await_single_handle(SERVICE_STATUS_HANDLE status_handle, SERVICE_STATUS *status, HANDLE handle, TCHAR *name, TCHAR *function_name, unsigned long timeout) {\r
2141   unsigned long interval;\r
2142   unsigned long ret;\r
2143   unsigned long waited;\r
2144   TCHAR interval_milliseconds[16];\r
2145   TCHAR timeout_milliseconds[16];\r
2146   TCHAR waited_milliseconds[16];\r
2147   TCHAR *function = function_name;\r
2148 \r
2149   /* Add brackets to function name. */\r
2150   size_t funclen = _tcslen(function_name) + 3;\r
2151   TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));\r
2152   if (func) {\r
2153     if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;\r
2154   }\r
2155 \r
2156   _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);\r
2157 \r
2158   waited = 0;\r
2159   while (waited < timeout) {\r
2160     interval = timeout - waited;\r
2161     if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
2162 \r
2163     if (status) {\r
2164       status->dwWaitHint += interval;\r
2165       status->dwCheckPoint++;\r
2166       SetServiceStatus(status_handle, status);\r
2167     }\r
2168 \r
2169     if (waited) {\r
2170       _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);\r
2171       _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);\r
2172       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SINGLE_HANDLE, function, name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
2173     }\r
2174 \r
2175     switch (WaitForSingleObject(handle, interval)) {\r
2176       case WAIT_OBJECT_0:\r
2177         ret = 0;\r
2178         goto awaited;\r
2179 \r
2180       case WAIT_TIMEOUT:\r
2181         ret = 1;\r
2182         break;\r
2183 \r
2184       default:\r
2185         ret = -1;\r
2186         goto awaited;\r
2187     }\r
2188 \r
2189     waited += interval;\r
2190   }\r
2191 \r
2192 awaited:\r
2193   if (func) HeapFree(GetProcessHeap(), 0, func);\r
2194 \r
2195   return ret;\r
2196 }\r
2197 \r
2198 int list_nssm_services() {\r
2199   /* Open service manager. */\r
2200   SC_HANDLE services = open_service_manager(SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE);\r
2201   if (! services) {\r
2202     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
2203     return 1;\r
2204   }\r
2205 \r
2206   unsigned long bufsize, required, count, i;\r
2207   unsigned long resume = 0;\r
2208   EnumServicesStatus(services, SERVICE_WIN32, SERVICE_STATE_ALL, 0, 0, &required, &count, &resume);\r
2209   unsigned long error = GetLastError();\r
2210   if (error != ERROR_MORE_DATA) {\r
2211     print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));\r
2212     return 2;\r
2213   }\r
2214 \r
2215   ENUM_SERVICE_STATUS *status = (ENUM_SERVICE_STATUS *) HeapAlloc(GetProcessHeap(), 0, required);\r
2216   if (! status) {\r
2217     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("ENUM_SERVICE_STATUS"), _T("list_nssm_services()"));\r
2218     return 3;\r
2219   }\r
2220 \r
2221   bufsize = required;\r
2222   while (true) {\r
2223     int ret = EnumServicesStatus(services, SERVICE_WIN32, SERVICE_STATE_ALL, status, bufsize, &required, &count, &resume);\r
2224     if (! ret) {\r
2225       error = GetLastError();\r
2226       if (error != ERROR_MORE_DATA) {\r
2227         HeapFree(GetProcessHeap(), 0, status);\r
2228         print_message(stderr, NSSM_MESSAGE_ENUMSERVICESSTATUS_FAILED, error_string(GetLastError()));\r
2229         return 4;\r
2230       }\r
2231     }\r
2232 \r
2233     for (i = 0; i < count; i++) {\r
2234       /* Try to get the service parameters. */\r
2235       nssm_service_t *service = alloc_nssm_service();\r
2236       if (! service) {\r
2237         HeapFree(GetProcessHeap(), 0, status);\r
2238         print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("nssm_service_t"), _T("list_nssm_services()"));\r
2239         return 5;\r
2240       }\r
2241       _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), status[i].lpServiceName);\r
2242 \r
2243       get_parameters(service, 0);\r
2244       /* We manage the service if we have an Application. */\r
2245       if (service->exe[0]) _tprintf(_T("%s\n"), service->name);\r
2246 \r
2247       cleanup_nssm_service(service);\r
2248     }\r
2249 \r
2250     if (ret) break;\r
2251   }\r
2252 \r
2253   HeapFree(GetProcessHeap(), 0, status);\r
2254   return 0;\r
2255 }\r