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