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