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