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