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