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