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