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