Allow service editing on the command line.
[nssm.git] / service.cpp
1 #include "nssm.h"\r
2 \r
3 /* This is explicitly a wide string. */\r
4 #define NSSM_LOGON_AS_SERVICE_RIGHT L"SeServiceLogonRight"\r
5 \r
6 bool is_admin;\r
7 bool use_critical_section;\r
8 \r
9 extern imports_t imports;\r
10 extern settings_t settings[];\r
11 \r
12 const TCHAR *exit_action_strings[] = { _T("Restart"), _T("Ignore"), _T("Exit"), _T("Suicide"), 0 };\r
13 const TCHAR *startup_strings[] = { _T("SERVICE_AUTO_START"), _T("SERVICE_DELAYED_AUTO_START"), _T("SERVICE_DEMAND_START"), _T("SERVICE_DISABLED"), 0 };\r
14 \r
15 static inline int throttle_milliseconds(unsigned long throttle) {\r
16   /* pow() operates on doubles. */\r
17   int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;\r
18   return ret * 1000;\r
19 }\r
20 \r
21 /*\r
22   Wrapper to be called in a new thread so that we can acknowledge a STOP\r
23   control immediately.\r
24 */\r
25 static unsigned long WINAPI shutdown_service(void *arg) {\r
26   return stop_service((nssm_service_t *) arg, 0, true, true);\r
27 }\r
28 \r
29 /* Connect to the service manager */\r
30 SC_HANDLE open_service_manager() {\r
31   SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);\r
32   if (! ret) {\r
33     if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);\r
34     return 0;\r
35   }\r
36 \r
37   return ret;\r
38 }\r
39 \r
40 QUERY_SERVICE_CONFIG *query_service_config(const TCHAR *service_name, SC_HANDLE service_handle) {\r
41   QUERY_SERVICE_CONFIG *qsc;\r
42   unsigned long bufsize;\r
43   unsigned long error;\r
44 \r
45   QueryServiceConfig(service_handle, 0, 0, &bufsize);\r
46   error = GetLastError();\r
47   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
48     qsc = (QUERY_SERVICE_CONFIG *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, bufsize);\r
49     if (! qsc) {\r
50       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("QUERY_SERVICE_CONFIG"), _T("query_service_config()"), 0);\r
51       return 0;\r
52     }\r
53   }\r
54   else {\r
55     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service_name, error_string(error), 0);\r
56     return 0;\r
57   }\r
58 \r
59   if (! QueryServiceConfig(service_handle, qsc, bufsize, &bufsize)) {\r
60     HeapFree(GetProcessHeap(), 0, qsc);\r
61     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG_FAILED, service_name, error_string(GetLastError()), 0);\r
62     return 0;\r
63   }\r
64 \r
65   return qsc;\r
66 }\r
67 \r
68 int set_service_description(const TCHAR *service_name, SC_HANDLE service_handle, TCHAR *buffer) {\r
69   SERVICE_DESCRIPTION description;\r
70   ZeroMemory(&description, sizeof(description));\r
71   /*\r
72     lpDescription must be NULL if we aren't changing, the new description\r
73     or "".\r
74   */\r
75   if (buffer && buffer[0]) description.lpDescription = buffer;\r
76   else description.lpDescription = _T("");\r
77 \r
78   if (ChangeServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, &description)) return 0;\r
79 \r
80   log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DESCRIPTION_FAILED, service_name, error_string(GetLastError()), 0);\r
81   return 1;\r
82 }\r
83 \r
84 int get_service_description(const TCHAR *service_name, SC_HANDLE service_handle, unsigned long len, TCHAR *buffer) {\r
85   if (! buffer) return 1;\r
86 \r
87   unsigned long bufsize;\r
88   QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, 0, 0, &bufsize);\r
89   unsigned long error = GetLastError();\r
90   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
91     SERVICE_DESCRIPTION *description = (SERVICE_DESCRIPTION *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
92     if (! description) {\r
93       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_CONFIG_DESCRIPTION"), _T("get_service_description()"));\r
94       return 2;\r
95     }\r
96 \r
97     if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DESCRIPTION, (unsigned char *) description, bufsize, &bufsize)) {\r
98       if (description->lpDescription) _sntprintf_s(buffer, len, _TRUNCATE, _T("%s"), description->lpDescription);\r
99       else ZeroMemory(buffer, len * sizeof(TCHAR));\r
100       HeapFree(GetProcessHeap(), 0, description);\r
101       return 0;\r
102     }\r
103     else {\r
104       HeapFree(GetProcessHeap(), 0, description);\r
105       print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));\r
106       return 3;\r
107     }\r
108   }\r
109   else {\r
110     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DESCRIPTION"), error_string(error));\r
111     return 4;\r
112   }\r
113 \r
114   return 0;\r
115 }\r
116 \r
117 int get_service_startup(const TCHAR *service_name, SC_HANDLE service_handle, const QUERY_SERVICE_CONFIG *qsc, unsigned long *startup) {\r
118   if (! qsc) return 1;\r
119 \r
120   switch (qsc->dwStartType) {\r
121     case SERVICE_DEMAND_START: *startup = NSSM_STARTUP_MANUAL; break;\r
122     case SERVICE_DISABLED: *startup = NSSM_STARTUP_DISABLED; break;\r
123     default: *startup = NSSM_STARTUP_AUTOMATIC;\r
124   }\r
125 \r
126   if (*startup != NSSM_STARTUP_AUTOMATIC) return 0;\r
127 \r
128   /* Check for delayed start. */\r
129   unsigned long bufsize;\r
130   unsigned long error;\r
131   QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, 0, 0, &bufsize);\r
132   error = GetLastError();\r
133   if (error == ERROR_INSUFFICIENT_BUFFER) {\r
134     SERVICE_DELAYED_AUTO_START_INFO *info = (SERVICE_DELAYED_AUTO_START_INFO *) HeapAlloc(GetProcessHeap(), 0, bufsize);\r
135     if (! info) {\r
136       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SERVICE_DELAYED_AUTO_START_INFO"), _T("get_service_startup()"));\r
137       return 2;\r
138     }\r
139 \r
140     if (QueryServiceConfig2(service_handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, (unsigned char *) info, bufsize, &bufsize)) {\r
141       if (info->fDelayedAutostart) *startup = NSSM_STARTUP_DELAYED;\r
142       HeapFree(GetProcessHeap(), 0, info);\r
143       return 0;\r
144     }\r
145     else {\r
146       error = GetLastError();\r
147       if (error != ERROR_INVALID_LEVEL) {\r
148         print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_CONFIG_DELAYED_AUTO_START_INFO"), error_string(error));\r
149         return 3;\r
150       }\r
151     }\r
152   }\r
153   else if (error != ERROR_INVALID_LEVEL) {\r
154     print_message(stderr, NSSM_MESSAGE_QUERYSERVICECONFIG2_FAILED, service_name, _T("SERVICE_DELAYED_AUTO_START_INFO"), error_string(error));\r
155     return 3;\r
156   }\r
157 \r
158   return 0;\r
159 }\r
160 \r
161 int get_service_username(const TCHAR *service_name, const QUERY_SERVICE_CONFIG *qsc, TCHAR **username, size_t *usernamelen) {\r
162   if (! username) return 1;\r
163   if (! usernamelen) return 1;\r
164 \r
165   *username = 0;\r
166   *usernamelen = 0;\r
167 \r
168   if (! qsc) return 1;\r
169 \r
170   if (str_equiv(qsc->lpServiceStartName, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;\r
171 \r
172   size_t len = _tcslen(qsc->lpServiceStartName);\r
173   *username = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, (len + 1) * sizeof(TCHAR));\r
174   if (! *username) {\r
175     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("username"), _T("get_service_username()"));\r
176     return 2;\r
177   }\r
178 \r
179   memmove(*username, qsc->lpServiceStartName, (len + 1) * sizeof(TCHAR));\r
180   *usernamelen = len;\r
181 \r
182   return 0;\r
183 }\r
184 \r
185 int grant_logon_as_service(const TCHAR *username) {\r
186   if (str_equiv(username, NSSM_LOCALSYSTEM_ACCOUNT)) return 0;\r
187 \r
188   /* Open Policy object. */\r
189   LSA_OBJECT_ATTRIBUTES attributes;\r
190   ZeroMemory(&attributes, sizeof(attributes));\r
191 \r
192   LSA_HANDLE policy;\r
193 \r
194   NTSTATUS status = LsaOpenPolicy(0, &attributes, POLICY_ALL_ACCESS, &policy);\r
195   if (status) {\r
196     print_message(stderr, NSSM_MESSAGE_LSAOPENPOLICY_FAILED, error_string(LsaNtStatusToWinError(status)));\r
197     return 1;\r
198   }\r
199 \r
200   /* Look up SID for the account. */\r
201   LSA_UNICODE_STRING lsa_username;\r
202 #ifdef UNICODE\r
203   lsa_username.Buffer = (wchar_t *) username;\r
204   lsa_username.Length = (unsigned short) _tcslen(username) * sizeof(TCHAR);\r
205   lsa_username.MaximumLength = lsa_username.Length + sizeof(TCHAR);\r
206 #else\r
207   size_t buflen;\r
208   mbstowcs_s(&buflen, NULL, 0, username, _TRUNCATE);\r
209   lsa_username.MaximumLength = buflen * sizeof(wchar_t);\r
210   lsa_username.Length = lsa_username.MaximumLength - sizeof(wchar_t);\r
211   lsa_username.Buffer = (wchar_t *) HeapAlloc(GetProcessHeap(), 0, lsa_username.MaximumLength);\r
212   if (lsa_username.Buffer) mbstowcs_s(&buflen, lsa_username.Buffer, lsa_username.MaximumLength, username, _TRUNCATE);\r
213   else {\r
214     LsaClose(policy);\r
215     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("LSA_UNICODE_STRING"), _T("grant_logon_as_service()"));\r
216     return 2;\r
217   }\r
218 #endif\r
219 \r
220   LSA_REFERENCED_DOMAIN_LIST *translated_domains;\r
221   LSA_TRANSLATED_SID *translated_sid;\r
222   status = LsaLookupNames(policy, 1, &lsa_username, &translated_domains, &translated_sid);\r
223 #ifndef UNICODE\r
224   HeapFree(GetProcessHeap(), 0, lsa_username.Buffer);\r
225 #endif\r
226   if (status) {\r
227     LsaFreeMemory(translated_domains);\r
228     LsaFreeMemory(translated_sid);\r
229     LsaClose(policy);\r
230     print_message(stderr, NSSM_MESSAGE_LSALOOKUPNAMES_FAILED, username, error_string(LsaNtStatusToWinError(status)));\r
231     return 3;\r
232   }\r
233 \r
234   if (translated_sid->Use != SidTypeUser) {\r
235     LsaFreeMemory(translated_domains);\r
236     LsaFreeMemory(translated_sid);\r
237     LsaClose(policy);\r
238     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
239     return 4;\r
240   }\r
241 \r
242   LSA_TRUST_INFORMATION *trust = &translated_domains->Domains[translated_sid->DomainIndex];\r
243   if (! trust || ! IsValidSid(trust->Sid)) {\r
244     LsaFreeMemory(translated_domains);\r
245     LsaFreeMemory(translated_sid);\r
246     LsaClose(policy);\r
247     print_message(stderr, NSSM_GUI_INVALID_USERNAME, username);\r
248     return 4;\r
249   }\r
250 \r
251   /* GetSidSubAuthority*() return pointers! */\r
252   unsigned char *n = GetSidSubAuthorityCount(trust->Sid);\r
253 \r
254   /* Convert translated SID to SID. */\r
255   SID *sid = (SID *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetSidLengthRequired(*n + 1));\r
256   if (! sid) {\r
257     LsaFreeMemory(translated_domains);\r
258     LsaFreeMemory(translated_sid);\r
259     LsaClose(policy);\r
260     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("SID"), _T("grant_logon_as_service"));\r
261     return 4;\r
262   }\r
263 \r
264   unsigned long error;\r
265   if (! InitializeSid(sid, GetSidIdentifierAuthority(trust->Sid), *n + 1)) {\r
266     error = GetLastError();\r
267     HeapFree(GetProcessHeap(), 0, sid);\r
268     LsaFreeMemory(translated_domains);\r
269     LsaFreeMemory(translated_sid);\r
270     LsaClose(policy);\r
271     print_message(stderr, NSSM_MESSAGE_INITIALIZESID_FAILED, username, error_string(error));\r
272     return 5;\r
273   }\r
274 \r
275   for (unsigned char i = 0; i <= *n; i++) {\r
276     unsigned long *sub = GetSidSubAuthority(sid, i);\r
277     if (i < *n) *sub = *GetSidSubAuthority(trust->Sid, i);\r
278     else *sub = translated_sid->RelativeId;\r
279   }\r
280 \r
281   LsaFreeMemory(translated_domains);\r
282   LsaFreeMemory(translated_sid);\r
283 \r
284   /* Check if the SID has the "Log on as a service" right. */\r
285   LSA_UNICODE_STRING lsa_right;\r
286   lsa_right.Buffer = NSSM_LOGON_AS_SERVICE_RIGHT;\r
287   lsa_right.Length = (unsigned short) wcslen(lsa_right.Buffer) * sizeof(wchar_t);\r
288   lsa_right.MaximumLength = lsa_right.Length + sizeof(wchar_t);\r
289 \r
290   LSA_UNICODE_STRING *rights;\r
291   unsigned long count = ~0;\r
292   status = LsaEnumerateAccountRights(policy, sid, &rights, &count);\r
293   if (status) {\r
294     /*\r
295       If the account has no rights set LsaEnumerateAccountRights() will return\r
296       STATUS_OBJECT_NAME_NOT_FOUND and set count to 0.\r
297     */\r
298     error = LsaNtStatusToWinError(status);\r
299     if (error != ERROR_FILE_NOT_FOUND) {\r
300       HeapFree(GetProcessHeap(), 0, sid);\r
301       LsaClose(policy);\r
302       print_message(stderr, NSSM_MESSAGE_LSAENUMERATEACCOUNTRIGHTS_FAILED, username, error_string(error));\r
303       return 4;\r
304     }\r
305   }\r
306 \r
307   for (unsigned long i = 0; i < count; i++) {\r
308     if (rights[i].Length != lsa_right.Length) continue;\r
309     if (_wcsnicmp(rights[i].Buffer, lsa_right.Buffer, lsa_right.MaximumLength)) continue;\r
310     /* The SID has the right. */\r
311     HeapFree(GetProcessHeap(), 0, sid);\r
312     LsaFreeMemory(rights);\r
313     LsaClose(policy);\r
314     return 0;\r
315   }\r
316   LsaFreeMemory(rights);\r
317 \r
318   /* Add the right. */\r
319   status = LsaAddAccountRights(policy, sid, &lsa_right, 1);\r
320   HeapFree(GetProcessHeap(), 0, sid);\r
321   LsaClose(policy);\r
322   if (status) {\r
323     print_message(stderr, NSSM_MESSAGE_LSAADDACCOUNTRIGHTS_FAILED, error_string(LsaNtStatusToWinError(status)));\r
324     return 5;\r
325   }\r
326 \r
327   print_message(stdout, NSSM_MESSAGE_GRANTED_LOGON_AS_SERVICE, username);\r
328   return 0;\r
329 }\r
330 \r
331 /* Set default values which aren't zero. */\r
332 void set_nssm_service_defaults(nssm_service_t *service) {\r
333   if (! service) return;\r
334 \r
335   service->type = SERVICE_WIN32_OWN_PROCESS;\r
336   service->stdin_sharing = NSSM_STDIN_SHARING;\r
337   service->stdin_disposition = NSSM_STDIN_DISPOSITION;\r
338   service->stdin_flags = NSSM_STDIN_FLAGS;\r
339   service->stdout_sharing = NSSM_STDOUT_SHARING;\r
340   service->stdout_disposition = NSSM_STDOUT_DISPOSITION;\r
341   service->stdout_flags = NSSM_STDOUT_FLAGS;\r
342   service->stderr_sharing = NSSM_STDERR_SHARING;\r
343   service->stderr_disposition = NSSM_STDERR_DISPOSITION;\r
344   service->stderr_flags = NSSM_STDERR_FLAGS;\r
345   service->throttle_delay = NSSM_RESET_THROTTLE_RESTART;\r
346   service->stop_method = ~0;\r
347   service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
348   service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
349   service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
350 }\r
351 \r
352 /* Allocate and zero memory for a service. */\r
353 nssm_service_t *alloc_nssm_service() {\r
354   nssm_service_t *service = (nssm_service_t *) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(nssm_service_t));\r
355   if (! service) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service"), _T("alloc_nssm_service()"), 0);\r
356   return service;\r
357 }\r
358 \r
359 /* Free memory for a service. */\r
360 void cleanup_nssm_service(nssm_service_t *service) {\r
361   if (! service) return;\r
362   if (service->username) HeapFree(GetProcessHeap(), 0, service->username);\r
363   if (service->password) {\r
364     SecureZeroMemory(service->password, service->passwordlen);\r
365     HeapFree(GetProcessHeap(), 0, service->password);\r
366   }\r
367   if (service->env) HeapFree(GetProcessHeap(), 0, service->env);\r
368   if (service->env_extra) HeapFree(GetProcessHeap(), 0, service->env_extra);\r
369   if (service->handle) CloseServiceHandle(service->handle);\r
370   if (service->process_handle) CloseHandle(service->process_handle);\r
371   if (service->wait_handle) UnregisterWait(service->process_handle);\r
372   if (service->throttle_section_initialised) DeleteCriticalSection(&service->throttle_section);\r
373   if (service->throttle_timer) CloseHandle(service->throttle_timer);\r
374   HeapFree(GetProcessHeap(), 0, service);\r
375 }\r
376 \r
377 /* About to install the service */\r
378 int pre_install_service(int argc, TCHAR **argv) {\r
379   nssm_service_t *service = alloc_nssm_service();\r
380   set_nssm_service_defaults(service);\r
381   if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
382 \r
383   /* Show the dialogue box if we didn't give the service name and path */\r
384   if (argc < 2) return nssm_gui(IDD_INSTALL, service);\r
385 \r
386   if (! service) {\r
387     print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("service"), _T("pre_install_service()"));\r
388     return 1;\r
389   }\r
390   _sntprintf_s(service->exe, _countof(service->exe), _TRUNCATE, _T("%s"), argv[1]);\r
391 \r
392   /* Arguments are optional */\r
393   size_t flagslen = 0;\r
394   size_t s = 0;\r
395   int i;\r
396   for (i = 2; i < argc; i++) flagslen += _tcslen(argv[i]) + 1;\r
397   if (! flagslen) flagslen = 1;\r
398   if (flagslen > _countof(service->flags)) {\r
399     print_message(stderr, NSSM_MESSAGE_FLAGS_TOO_LONG);\r
400     return 2;\r
401   }\r
402 \r
403   for (i = 2; i < argc; i++) {\r
404     size_t len = _tcslen(argv[i]);\r
405     memmove(service->flags + s, argv[i], len * sizeof(TCHAR));\r
406     s += len;\r
407     if (i < argc - 1) service->flags[s++] = _T(' ');\r
408   }\r
409 \r
410   /* Work out directory name */\r
411   _sntprintf_s(service->dir, _countof(service->dir), _TRUNCATE, _T("%s"), service->exe);\r
412   strip_basename(service->dir);\r
413 \r
414   int ret = install_service(service);\r
415   cleanup_nssm_service(service);\r
416   return ret;\r
417 }\r
418 \r
419 /* About to edit the service. */\r
420 int pre_edit_service(int argc, TCHAR **argv) {\r
421   /* Require service name. */\r
422   if (argc < 2) return usage(1);\r
423 \r
424   /* Are we editing on the command line? */\r
425   enum { MODE_EDITING, MODE_GETTING, MODE_SETTING, MODE_RESETTING } mode = MODE_EDITING;\r
426   const TCHAR *verb = argv[0];\r
427   const TCHAR *service_name = argv[1];\r
428   bool getting = false;\r
429   bool unsetting = false;\r
430 \r
431   /* Minimum number of arguments. */\r
432   int mandatory = 2;\r
433   /* Index of first value. */\r
434   int remainder = 3;\r
435   int i;\r
436   if (str_equiv(verb, _T("get"))) {\r
437     mandatory = 3;\r
438     mode = MODE_GETTING;\r
439   }\r
440   else if (str_equiv(verb, _T("set"))) {\r
441     mandatory = 4;\r
442     mode = MODE_SETTING;\r
443   }\r
444   else if (str_equiv(verb, _T("reset")) || str_equiv(verb, _T("unset"))) {\r
445     mandatory = 3;\r
446     mode = MODE_RESETTING;\r
447   }\r
448   if (argc < mandatory) return usage(1);\r
449 \r
450   const TCHAR *parameter = 0;\r
451   settings_t *setting = 0;\r
452   TCHAR *additional;\r
453 \r
454   /* Validate the parameter. */\r
455   if (mandatory > 2) {\r
456     bool additional_mandatory = false;\r
457 \r
458     parameter = argv[2];\r
459     for (i = 0; settings[i].name; i++) {\r
460       setting = &settings[i];\r
461       if (! str_equiv(setting->name, parameter)) continue;\r
462       if (((setting->additional & ADDITIONAL_GETTING) && mode == MODE_GETTING) || ((setting->additional & ADDITIONAL_SETTING) && mode == MODE_SETTING) || ((setting->additional & ADDITIONAL_RESETTING) && mode == MODE_RESETTING)) {\r
463         additional_mandatory = true;\r
464         mandatory++;\r
465       }\r
466       break;\r
467     }\r
468     if (! settings[i].name) {\r
469       print_message(stderr, NSSM_MESSAGE_INVALID_PARAMETER, parameter);\r
470       for (i = 0; settings[i].name; i++) _ftprintf(stderr, _T("%s\n"), settings[i].name);\r
471       return 1;\r
472     }\r
473     if (argc < mandatory) return usage(1);\r
474 \r
475     additional = 0;\r
476     if (additional_mandatory) {\r
477       additional = argv[3];\r
478       remainder = 4;\r
479     }\r
480     else additional = argv[remainder];\r
481   }\r
482 \r
483   nssm_service_t *service = alloc_nssm_service();\r
484   _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), service_name);\r
485 \r
486   /* Open service manager */\r
487   SC_HANDLE services = open_service_manager();\r
488   if (! services) {\r
489     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
490     return 2;\r
491   }\r
492 \r
493   /* Try to open the service */\r
494   service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
495   if (! service->handle) {\r
496     CloseServiceHandle(services);\r
497     print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);\r
498     return 3;\r
499   }\r
500 \r
501   /* Get system details. */\r
502   QUERY_SERVICE_CONFIG *qsc = query_service_config(service->name, service->handle);\r
503   if (! qsc) {\r
504     CloseHandle(service->handle);\r
505     CloseServiceHandle(services);\r
506     return 4;\r
507   }\r
508 \r
509   service->type = qsc->dwServiceType;\r
510   if (! (service->type & SERVICE_WIN32_OWN_PROCESS)) {\r
511     if (mode != MODE_GETTING) {\r
512       HeapFree(GetProcessHeap(), 0, qsc);\r
513       CloseHandle(service->handle);\r
514       CloseServiceHandle(services);\r
515       print_message(stderr, NSSM_MESSAGE_CANNOT_EDIT, service->name, NSSM_WIN32_OWN_PROCESS, 0);\r
516       return 3;\r
517     }\r
518   }\r
519 \r
520   if (get_service_startup(service->name, service->handle, qsc, &service->startup)) {\r
521     if (mode != MODE_GETTING) {\r
522       HeapFree(GetProcessHeap(), 0, qsc);\r
523       CloseHandle(service->handle);\r
524       CloseServiceHandle(services);\r
525       return 4;\r
526     }\r
527   }\r
528 \r
529   if (get_service_username(service->name, qsc, &service->username, &service->usernamelen)) {\r
530     if (mode != MODE_GETTING) {\r
531       HeapFree(GetProcessHeap(), 0, qsc);\r
532       CloseHandle(service->handle);\r
533       CloseServiceHandle(services);\r
534       return 5;\r
535     }\r
536   }\r
537 \r
538   _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), qsc->lpDisplayName);\r
539 \r
540   /* Get the canonical service name. We open it case insensitively. */\r
541   unsigned long bufsize = _countof(service->name);\r
542   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
543 \r
544   /* Remember the executable in case it isn't NSSM. */\r
545   _sntprintf_s(service->image, _countof(service->image), _TRUNCATE, _T("%s"), qsc->lpBinaryPathName);\r
546   HeapFree(GetProcessHeap(), 0, qsc);\r
547 \r
548   /* Get extended system details. */\r
549   if (get_service_description(service->name, service->handle, _countof(service->description), service->description)) {\r
550     if (mode != MODE_GETTING) {\r
551       CloseHandle(service->handle);\r
552       CloseServiceHandle(services);\r
553       return 6;\r
554     }\r
555   }\r
556 \r
557   /* Get NSSM details. */\r
558   get_parameters(service, 0);\r
559 \r
560   CloseServiceHandle(services);\r
561 \r
562   if (! service->exe[0]) {\r
563     service->native = true;\r
564     if (mode != MODE_GETTING) print_message(stderr, NSSM_MESSAGE_INVALID_SERVICE, service->name, NSSM, service->image);\r
565   }\r
566 \r
567   /* Editing with the GUI. */\r
568   if (mode == MODE_EDITING) {\r
569     nssm_gui(IDD_EDIT, service);\r
570     return 0;\r
571   }\r
572 \r
573   /* Trying to manage App* parameters for a non-NSSM service. */\r
574   if (! setting->native && service->native) {\r
575     CloseHandle(service->handle);\r
576     print_message(stderr, NSSM_MESSAGE_NATIVE_PARAMETER, setting->name, NSSM);\r
577     return 1;\r
578   }\r
579 \r
580   HKEY key;\r
581   value_t value;\r
582   int ret;\r
583 \r
584   if (mode == MODE_GETTING) {\r
585     if (! service->native) {\r
586       key = open_registry(service->name, KEY_READ);\r
587       if (! key) return 4;\r
588     }\r
589 \r
590     if (setting->native) ret = get_setting(service->name, service->handle, setting, &value, additional);\r
591     else ret = get_setting(service->name, key, setting, &value, additional);\r
592     if (ret < 0) {\r
593       CloseHandle(service->handle);\r
594       return 5;\r
595     }\r
596 \r
597     switch (setting->type) {\r
598       case REG_EXPAND_SZ:\r
599       case REG_MULTI_SZ:\r
600       case REG_SZ:\r
601         _tprintf(_T("%s\n"), value.string ? value.string : _T(""));\r
602         HeapFree(GetProcessHeap(), 0, value.string);\r
603         break;\r
604 \r
605       case REG_DWORD:\r
606         _tprintf(_T("%u\n"), value.numeric);\r
607         break;\r
608     }\r
609 \r
610     if (! service->native) RegCloseKey(key);\r
611     CloseHandle(service->handle);\r
612     return 0;\r
613   }\r
614 \r
615   /* Build the value. */\r
616   if (mode == MODE_RESETTING) {\r
617     /* Unset the parameter. */\r
618     value.string = 0;\r
619   }\r
620   else {\r
621     /* Set the parameter. */\r
622     size_t len = 0;\r
623     size_t delimiterlen = (setting->additional & ADDITIONAL_CRLF) ? 2 : 1;\r
624     for (i = remainder; i < argc; i++) len += _tcslen(argv[i]) + delimiterlen;\r
625     len++;\r
626 \r
627     value.string = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, len * sizeof(TCHAR));\r
628     if (! value.string) {\r
629       print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY, _T("value"), _T("edit_service()"));\r
630       CloseHandle(service->handle);\r
631       return 2;\r
632     }\r
633 \r
634     size_t s = 0;\r
635     for (i = remainder; i < argc; i++) {\r
636       size_t len = _tcslen(argv[i]);\r
637       memmove(value.string + s, argv[i], len * sizeof(TCHAR));\r
638       s += len;\r
639       if (i < argc - 1) {\r
640         if (setting->additional & ADDITIONAL_CRLF) {\r
641           value.string[s++] = _T('\r');\r
642           value.string[s++] = _T('\n');\r
643         }\r
644         else value.string[s++] = _T(' ');\r
645       }\r
646     }\r
647     value.string[s] = _T('\0');\r
648   }\r
649 \r
650   if (! service->native) {\r
651     key = open_registry(service->name, KEY_WRITE);\r
652     if (! key) {\r
653       if (value.string) HeapFree(GetProcessHeap(), 0, value.string);\r
654       return 4;\r
655     }\r
656   }\r
657 \r
658   if (setting->native) ret = set_setting(service->name, service->handle, setting, &value, additional);\r
659   else ret = set_setting(service->name, key, setting, &value, additional);\r
660   if (value.string) HeapFree(GetProcessHeap(), 0, value.string);\r
661   if (ret < 0) {\r
662     if (! service->native) RegCloseKey(key);\r
663     CloseHandle(service->handle);\r
664     return 6;\r
665   }\r
666 \r
667   if (! service->native) RegCloseKey(key);\r
668   CloseHandle(service->handle);\r
669 \r
670   return 0;\r
671 }\r
672 \r
673 /* About to remove the service */\r
674 int pre_remove_service(int argc, TCHAR **argv) {\r
675   nssm_service_t *service = alloc_nssm_service();\r
676   set_nssm_service_defaults(service);\r
677   if (argc) _sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]);\r
678 \r
679   /* Show dialogue box if we didn't pass service name and "confirm" */\r
680   if (argc < 2) return nssm_gui(IDD_REMOVE, service);\r
681   if (str_equiv(argv[1], _T("confirm"))) {\r
682     int ret = remove_service(service);\r
683     cleanup_nssm_service(service);\r
684     return ret;\r
685   }\r
686   print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);\r
687   return 100;\r
688 }\r
689 \r
690 /* Install the service */\r
691 int install_service(nssm_service_t *service) {\r
692   if (! service) return 1;\r
693 \r
694   /* Open service manager */\r
695   SC_HANDLE services = open_service_manager();\r
696   if (! services) {\r
697     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
698     cleanup_nssm_service(service);\r
699     return 2;\r
700   }\r
701 \r
702   /* Get path of this program */\r
703   GetModuleFileName(0, service->image, _countof(service->image));\r
704 \r
705   /* Create the service - settings will be changed in edit_service() */\r
706   service->handle = CreateService(services, service->name, service->name, SC_MANAGER_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, service->image, 0, 0, 0, 0, 0);\r
707   if (! service->handle) {\r
708     print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);\r
709     CloseServiceHandle(services);\r
710     return 5;\r
711   }\r
712 \r
713   if (edit_service(service, false)) {\r
714     DeleteService(service->handle);\r
715     CloseServiceHandle(services);\r
716     return 6;\r
717   }\r
718 \r
719   print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, service->name);\r
720 \r
721   /* Cleanup */\r
722   CloseServiceHandle(services);\r
723 \r
724   return 0;\r
725 }\r
726 \r
727 /* Edit the service. */\r
728 int edit_service(nssm_service_t *service, bool editing) {\r
729   if (! service) return 1;\r
730 \r
731   /*\r
732     The only two valid flags for service type are SERVICE_WIN32_OWN_PROCESS\r
733     and SERVICE_INTERACTIVE_PROCESS.\r
734   */\r
735   service->type &= SERVICE_INTERACTIVE_PROCESS;\r
736   service->type |= SERVICE_WIN32_OWN_PROCESS;\r
737 \r
738   /* Startup type. */\r
739   unsigned long startup;\r
740   switch (service->startup) {\r
741     case NSSM_STARTUP_MANUAL: startup = SERVICE_DEMAND_START; break;\r
742     case NSSM_STARTUP_DISABLED: startup = SERVICE_DISABLED; break;\r
743     default: startup = SERVICE_AUTO_START;\r
744   }\r
745 \r
746   /* Display name. */\r
747   if (! service->displayname[0]) _sntprintf_s(service->displayname, _countof(service->displayname), _TRUNCATE, _T("%s"), service->name);\r
748 \r
749   /*\r
750     Username must be NULL if we aren't changing or an account name.\r
751     We must explicitly use LOCALSYSTEM to change it when we are editing.\r
752     Password must be NULL if we aren't changing, a password or "".\r
753     Empty passwords are valid but we won't allow them in the GUI.\r
754   */\r
755   TCHAR *username = 0;\r
756   TCHAR *password = 0;\r
757   if (service->usernamelen) {\r
758     username = service->username;\r
759     if (service->passwordlen) password = service->password;\r
760     else password = _T("");\r
761   }\r
762   else if (editing) username = NSSM_LOCALSYSTEM_ACCOUNT;\r
763 \r
764   if (grant_logon_as_service(username)) {\r
765     print_message(stderr, NSSM_MESSAGE_GRANT_LOGON_AS_SERVICE_FAILED, username);\r
766     return 5;\r
767   }\r
768 \r
769   if (! ChangeServiceConfig(service->handle, service->type, startup, SERVICE_NO_CHANGE, 0, 0, 0, 0, username, password, service->displayname)) {\r
770     print_message(stderr, NSSM_MESSAGE_CHANGESERVICECONFIG_FAILED, error_string(GetLastError()));\r
771     return 5;\r
772   }\r
773 \r
774   if (service->description[0] || editing) {\r
775     set_service_description(service->name, service->handle, service->description);\r
776   }\r
777 \r
778   SERVICE_DELAYED_AUTO_START_INFO delayed;\r
779   ZeroMemory(&delayed, sizeof(delayed));\r
780   if (service->startup == NSSM_STARTUP_DELAYED) delayed.fDelayedAutostart = 1;\r
781   else delayed.fDelayedAutostart = 0;\r
782   /* Delayed startup isn't supported until Vista. */\r
783   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_DELAYED_AUTO_START_INFO, &delayed)) {\r
784     unsigned long error = GetLastError();\r
785     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
786     if (error != ERROR_INVALID_LEVEL) {\r
787       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_DELAYED_AUTO_START_INFO_FAILED, service->name, error_string(error), 0);\r
788     }\r
789   }\r
790 \r
791   /* Don't mess with parameters which aren't ours. */\r
792   if (! service->native) {\r
793     /* Now we need to put the parameters into the registry */\r
794     if (create_parameters(service, editing)) {\r
795       print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);\r
796       return 6;\r
797     }\r
798 \r
799     set_service_recovery(service);\r
800   }\r
801 \r
802   return 0;\r
803 }\r
804 \r
805 /* Remove the service */\r
806 int remove_service(nssm_service_t *service) {\r
807   if (! service) return 1;\r
808 \r
809   /* Open service manager */\r
810   SC_HANDLE services = open_service_manager();\r
811   if (! services) {\r
812     print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);\r
813     return 2;\r
814   }\r
815 \r
816   /* Try to open the service */\r
817   service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
818   if (! service->handle) {\r
819     print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);\r
820     CloseServiceHandle(services);\r
821     return 3;\r
822   }\r
823 \r
824   /* Get the canonical service name. We open it case insensitively. */\r
825   unsigned long bufsize = _countof(service->displayname);\r
826   GetServiceDisplayName(services, service->name, service->displayname, &bufsize);\r
827   bufsize = _countof(service->name);\r
828   GetServiceKeyName(services, service->displayname, service->name, &bufsize);\r
829 \r
830   /* Try to delete the service */\r
831   if (! DeleteService(service->handle)) {\r
832     print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);\r
833     CloseServiceHandle(services);\r
834     return 4;\r
835   }\r
836 \r
837   /* Cleanup */\r
838   CloseServiceHandle(services);\r
839 \r
840   print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, service->name);\r
841   return 0;\r
842 }\r
843 \r
844 /* Service initialisation */\r
845 void WINAPI service_main(unsigned long argc, TCHAR **argv) {\r
846   nssm_service_t *service = alloc_nssm_service();\r
847   if (! service) return;\r
848 \r
849   if (_sntprintf_s(service->name, _countof(service->name), _TRUNCATE, _T("%s"), argv[0]) < 0) {\r
850     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("service->name"), _T("service_main()"), 0);\r
851     return;\r
852   }\r
853 \r
854   /* We can use a condition variable in a critical section on Vista or later. */\r
855   if (imports.SleepConditionVariableCS && imports.WakeConditionVariable) use_critical_section = true;\r
856   else use_critical_section = false;\r
857 \r
858   /* Initialise status */\r
859   ZeroMemory(&service->status, sizeof(service->status));\r
860   service->status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
861   service->status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;\r
862   service->status.dwWin32ExitCode = NO_ERROR;\r
863   service->status.dwServiceSpecificExitCode = 0;\r
864   service->status.dwCheckPoint = 0;\r
865   service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
866 \r
867   /* Signal we AREN'T running the server */\r
868   service->process_handle = 0;\r
869   service->pid = 0;\r
870 \r
871   /* Register control handler */\r
872   service->status_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, (void *) service);\r
873   if (! service->status_handle) {\r
874     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);\r
875     return;\r
876   }\r
877 \r
878   log_service_control(service->name, 0, true);\r
879 \r
880   service->status.dwCurrentState = SERVICE_START_PENDING;\r
881   service->status.dwWaitHint = service->throttle_delay + NSSM_WAITHINT_MARGIN;\r
882   SetServiceStatus(service->status_handle, &service->status);\r
883 \r
884   if (is_admin) {\r
885     /* Try to create the exit action parameters; we don't care if it fails */\r
886     create_exit_action(service->name, exit_action_strings[0], false);\r
887 \r
888     SC_HANDLE services = open_service_manager();\r
889     if (services) {\r
890       service->handle = OpenService(services, service->name, SC_MANAGER_ALL_ACCESS);\r
891       set_service_recovery(service);\r
892       CloseServiceHandle(services);\r
893     }\r
894   }\r
895 \r
896   /* Used for signalling a resume if the service pauses when throttled. */\r
897   if (use_critical_section) {\r
898     InitializeCriticalSection(&service->throttle_section);\r
899     service->throttle_section_initialised = true;\r
900   }\r
901   else {\r
902     service->throttle_timer = CreateWaitableTimer(0, 1, 0);\r
903     if (! service->throttle_timer) {\r
904       log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service->name, error_string(GetLastError()), 0);\r
905     }\r
906   }\r
907 \r
908   monitor_service(service);\r
909 }\r
910 \r
911 /* Make sure service recovery actions are taken where necessary */\r
912 void set_service_recovery(nssm_service_t *service) {\r
913   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
914   ZeroMemory(&flag, sizeof(flag));\r
915   flag.fFailureActionsOnNonCrashFailures = true;\r
916 \r
917   /* This functionality was added in Vista so the call may fail */\r
918   if (! ChangeServiceConfig2(service->handle, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {\r
919     unsigned long error = GetLastError();\r
920     /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */\r
921     if (error != ERROR_INVALID_LEVEL) {\r
922       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_SERVICE_CONFIG_FAILURE_ACTIONS_FAILED, service->name, error_string(error), 0);\r
923     }\r
924   }\r
925 }\r
926 \r
927 int monitor_service(nssm_service_t *service) {\r
928   /* Set service status to started */\r
929   int ret = start_service(service);\r
930   if (ret) {\r
931     TCHAR code[16];\r
932     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%d"), ret);\r
933     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, service->exe, service->name, ret, 0);\r
934     return ret;\r
935   }\r
936   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, service->exe, service->flags, service->name, service->dir, 0);\r
937 \r
938   /* Monitor service */\r
939   if (! RegisterWaitForSingleObject(&service->wait_handle, service->process_handle, end_service, (void *) service, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
940     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service->name, service->exe, error_string(GetLastError()), 0);\r
941   }\r
942 \r
943   return 0;\r
944 }\r
945 \r
946 TCHAR *service_control_text(unsigned long control) {\r
947   switch (control) {\r
948     /* HACK: there is no SERVICE_CONTROL_START constant */\r
949     case 0: return _T("START");\r
950     case SERVICE_CONTROL_STOP: return _T("STOP");\r
951     case SERVICE_CONTROL_SHUTDOWN: return _T("SHUTDOWN");\r
952     case SERVICE_CONTROL_PAUSE: return _T("PAUSE");\r
953     case SERVICE_CONTROL_CONTINUE: return _T("CONTINUE");\r
954     case SERVICE_CONTROL_INTERROGATE: return _T("INTERROGATE");\r
955     default: return 0;\r
956   }\r
957 }\r
958 \r
959 void log_service_control(TCHAR *service_name, unsigned long control, bool handled) {\r
960   TCHAR *text = service_control_text(control);\r
961   unsigned long event;\r
962 \r
963   if (! text) {\r
964     /* "0x" + 8 x hex + NULL */\r
965     text = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, 11 * sizeof(TCHAR));\r
966     if (! text) {\r
967       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
968       return;\r
969     }\r
970     if (_sntprintf_s(text, 11, _TRUNCATE, _T("0x%08x"), control) < 0) {\r
971       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("control code"), _T("log_service_control()"), 0);\r
972       HeapFree(GetProcessHeap(), 0, text);\r
973       return;\r
974     }\r
975 \r
976     event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;\r
977   }\r
978   else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;\r
979   else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;\r
980 \r
981   log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);\r
982 \r
983   if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {\r
984     HeapFree(GetProcessHeap(), 0, text);\r
985   }\r
986 }\r
987 \r
988 /* Service control handler */\r
989 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
990   nssm_service_t *service = (nssm_service_t *) context;\r
991 \r
992   switch (control) {\r
993     case SERVICE_CONTROL_INTERROGATE:\r
994       /* We always keep the service status up-to-date so this is a no-op. */\r
995       return NO_ERROR;\r
996 \r
997     case SERVICE_CONTROL_SHUTDOWN:\r
998     case SERVICE_CONTROL_STOP:\r
999       log_service_control(service->name, control, true);\r
1000       /*\r
1001         We MUST acknowledge the stop request promptly but we're committed to\r
1002         waiting for the application to exit.  Spawn a new thread to wait\r
1003         while we acknowledge the request.\r
1004       */\r
1005       if (! CreateThread(NULL, 0, shutdown_service, context, 0, NULL)) {\r
1006         log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATETHREAD_FAILED, error_string(GetLastError()), 0);\r
1007 \r
1008         /*\r
1009           We couldn't create a thread to tidy up so we'll have to force the tidyup\r
1010           to complete in time in this thread.\r
1011         */\r
1012         service->kill_console_delay = NSSM_KILL_CONSOLE_GRACE_PERIOD;\r
1013         service->kill_window_delay = NSSM_KILL_WINDOW_GRACE_PERIOD;\r
1014         service->kill_threads_delay = NSSM_KILL_THREADS_GRACE_PERIOD;\r
1015 \r
1016         stop_service(service, 0, true, true);\r
1017       }\r
1018       return NO_ERROR;\r
1019 \r
1020     case SERVICE_CONTROL_CONTINUE:\r
1021       log_service_control(service->name, control, true);\r
1022       service->throttle = 0;\r
1023       if (use_critical_section) imports.WakeConditionVariable(&service->throttle_condition);\r
1024       else {\r
1025         if (! service->throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
1026         ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1027         SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1028       }\r
1029       service->status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
1030       service->status.dwWaitHint = throttle_milliseconds(service->throttle) + NSSM_WAITHINT_MARGIN;\r
1031       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service->name, 0);\r
1032       SetServiceStatus(service->status_handle, &service->status);\r
1033       return NO_ERROR;\r
1034 \r
1035     case SERVICE_CONTROL_PAUSE:\r
1036       /*\r
1037         We don't accept pause messages but it isn't possible to register\r
1038         only for continue messages so we have to handle this case.\r
1039       */\r
1040       log_service_control(service->name, control, false);\r
1041       return ERROR_CALL_NOT_IMPLEMENTED;\r
1042   }\r
1043 \r
1044   /* Unknown control */\r
1045   log_service_control(service->name, control, false);\r
1046   return ERROR_CALL_NOT_IMPLEMENTED;\r
1047 }\r
1048 \r
1049 /* Start the service */\r
1050 int start_service(nssm_service_t *service) {\r
1051   service->stopping = false;\r
1052   service->allow_restart = true;\r
1053 \r
1054   if (service->process_handle) return 0;\r
1055 \r
1056   /* Allocate a STARTUPINFO structure for a new process */\r
1057   STARTUPINFO si;\r
1058   ZeroMemory(&si, sizeof(si));\r
1059   si.cb = sizeof(si);\r
1060 \r
1061   /* Allocate a PROCESSINFO structure for the process */\r
1062   PROCESS_INFORMATION pi;\r
1063   ZeroMemory(&pi, sizeof(pi));\r
1064 \r
1065   /* Get startup parameters */\r
1066   int ret = get_parameters(service, &si);\r
1067   if (ret) {\r
1068     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service->name, 0);\r
1069     return stop_service(service, 2, true, true);\r
1070   }\r
1071 \r
1072   /* Launch executable with arguments */\r
1073   TCHAR cmd[CMD_LENGTH];\r
1074   if (_sntprintf_s(cmd, _countof(cmd), _TRUNCATE, _T("\"%s\" %s"), service->exe, service->flags) < 0) {\r
1075     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, _T("command line"), _T("start_service"), 0);\r
1076     close_output_handles(&si);\r
1077     return stop_service(service, 2, true, true);\r
1078   }\r
1079 \r
1080   throttle_restart(service);\r
1081 \r
1082   bool inherit_handles = false;\r
1083   if (si.dwFlags & STARTF_USESTDHANDLES) inherit_handles = true;\r
1084   unsigned long flags = 0;\r
1085 #ifdef UNICODE\r
1086   flags |= CREATE_UNICODE_ENVIRONMENT;\r
1087 #endif\r
1088   if (! CreateProcess(0, cmd, 0, 0, inherit_handles, flags, service->env, service->dir, &si, &pi)) {\r
1089     unsigned long exitcode = 3;\r
1090     unsigned long error = GetLastError();\r
1091     if (error == ERROR_INVALID_PARAMETER && service->env) {\r
1092       log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service->name, service->exe, NSSM_REG_ENV, 0);\r
1093       if (test_environment(service->env)) exitcode = 4;\r
1094     }\r
1095     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service->name, service->exe, error_string(error), 0);\r
1096     close_output_handles(&si);\r
1097     return stop_service(service, exitcode, true, true);\r
1098   }\r
1099   service->process_handle = pi.hProcess;\r
1100   service->pid = pi.dwProcessId;\r
1101 \r
1102   if (get_process_creation_time(service->process_handle, &service->creation_time)) ZeroMemory(&service->creation_time, sizeof(service->creation_time));\r
1103 \r
1104   close_output_handles(&si);\r
1105 \r
1106   /*\r
1107     Wait for a clean startup before changing the service status to RUNNING\r
1108     but be mindful of the fact that we are blocking the service control manager\r
1109     so abandon the wait before too much time has elapsed.\r
1110   */\r
1111   unsigned long delay = service->throttle_delay;\r
1112   if (delay > NSSM_SERVICE_STATUS_DEADLINE) {\r
1113     TCHAR delay_milliseconds[16];\r
1114     _sntprintf_s(delay_milliseconds, _countof(delay_milliseconds), _TRUNCATE, _T("%lu"), delay);\r
1115     TCHAR deadline_milliseconds[16];\r
1116     _sntprintf_s(deadline_milliseconds, _countof(deadline_milliseconds), _TRUNCATE, _T("%lu"), NSSM_SERVICE_STATUS_DEADLINE);\r
1117     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_STARTUP_DELAY_TOO_LONG, service->name, delay_milliseconds, NSSM, deadline_milliseconds, 0);\r
1118     delay = NSSM_SERVICE_STATUS_DEADLINE;\r
1119   }\r
1120   unsigned long deadline = WaitForSingleObject(service->process_handle, delay);\r
1121 \r
1122   /* Signal successful start */\r
1123   service->status.dwCurrentState = SERVICE_RUNNING;\r
1124   SetServiceStatus(service->status_handle, &service->status);\r
1125 \r
1126   /* Continue waiting for a clean startup. */\r
1127   if (deadline == WAIT_TIMEOUT) {\r
1128     if (service->throttle_delay > delay) {\r
1129       if (WaitForSingleObject(service->process_handle, service->throttle_delay - delay) == WAIT_TIMEOUT) service->throttle = 0;\r
1130     }\r
1131     else service->throttle = 0;\r
1132   }\r
1133 \r
1134   return 0;\r
1135 }\r
1136 \r
1137 /* Stop the service */\r
1138 int stop_service(nssm_service_t *service, unsigned long exitcode, bool graceful, bool default_action) {\r
1139   service->allow_restart = false;\r
1140   if (service->wait_handle) {\r
1141     UnregisterWait(service->wait_handle);\r
1142     service->wait_handle = 0;\r
1143   }\r
1144 \r
1145   if (default_action && ! exitcode && ! graceful) {\r
1146     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
1147     graceful = true;\r
1148   }\r
1149 \r
1150   /* Signal we are stopping */\r
1151   if (graceful) {\r
1152     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1153     service->status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
1154     SetServiceStatus(service->status_handle, &service->status);\r
1155   }\r
1156 \r
1157   /* Nothing to do if service isn't running */\r
1158   if (service->pid) {\r
1159     /* Shut down service */\r
1160     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service->name, service->exe, 0);\r
1161     kill_process(service, service->process_handle, service->pid, 0);\r
1162   }\r
1163   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service->name, service->exe, 0);\r
1164 \r
1165   end_service((void *) service, true);\r
1166 \r
1167   /* Signal we stopped */\r
1168   if (graceful) {\r
1169     service->status.dwCurrentState = SERVICE_STOPPED;\r
1170     if (exitcode) {\r
1171       service->status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
1172       service->status.dwServiceSpecificExitCode = exitcode;\r
1173     }\r
1174     else {\r
1175       service->status.dwWin32ExitCode = NO_ERROR;\r
1176       service->status.dwServiceSpecificExitCode = 0;\r
1177     }\r
1178     SetServiceStatus(service->status_handle, &service->status);\r
1179   }\r
1180 \r
1181   return exitcode;\r
1182 }\r
1183 \r
1184 /* Callback function triggered when the server exits */\r
1185 void CALLBACK end_service(void *arg, unsigned char why) {\r
1186   nssm_service_t *service = (nssm_service_t *) arg;\r
1187 \r
1188   if (service->stopping) return;\r
1189 \r
1190   service->stopping = true;\r
1191 \r
1192   /* Check exit code */\r
1193   unsigned long exitcode = 0;\r
1194   TCHAR code[16];\r
1195   if (service->process_handle) {\r
1196     GetExitCodeProcess(service->process_handle, &exitcode);\r
1197     if (exitcode == STILL_ACTIVE || get_process_exit_time(service->process_handle, &service->exit_time)) GetSystemTimeAsFileTime(&service->exit_time);\r
1198     CloseHandle(service->process_handle);\r
1199   }\r
1200   else GetSystemTimeAsFileTime(&service->exit_time);\r
1201 \r
1202   service->process_handle = 0;\r
1203 \r
1204   /*\r
1205     Log that the service ended BEFORE logging about killing the process\r
1206     tree.  See below for the possible values of the why argument.\r
1207   */\r
1208   if (! why) {\r
1209     _sntprintf_s(code, _countof(code), _TRUNCATE, _T("%lu"), exitcode);\r
1210     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, service->exe, service->name, code, 0);\r
1211   }\r
1212 \r
1213   /* Clean up. */\r
1214   if (exitcode == STILL_ACTIVE) exitcode = 0;\r
1215   if (service->pid) kill_process_tree(service, service->pid, exitcode, service->pid);\r
1216   service->pid = 0;\r
1217 \r
1218   /*\r
1219     The why argument is true if our wait timed out or false otherwise.\r
1220     Our wait is infinite so why will never be true when called by the system.\r
1221     If it is indeed true, assume we were called from stop_service() because\r
1222     this is a controlled shutdown, and don't take any restart action.\r
1223   */\r
1224   if (why) return;\r
1225   if (! service->allow_restart) return;\r
1226 \r
1227   /* What action should we take? */\r
1228   int action = NSSM_EXIT_RESTART;\r
1229   TCHAR action_string[ACTION_LEN];\r
1230   bool default_action;\r
1231   if (! get_exit_action(service->name, &exitcode, action_string, &default_action)) {\r
1232     for (int i = 0; exit_action_strings[i]; i++) {\r
1233       if (! _tcsnicmp((const TCHAR *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
1234         action = i;\r
1235         break;\r
1236       }\r
1237     }\r
1238   }\r
1239 \r
1240   switch (action) {\r
1241     /* Try to restart the service or return failure code to service manager */\r
1242     case NSSM_EXIT_RESTART:\r
1243       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service->name, code, exit_action_strings[action], service->exe, 0);\r
1244       while (monitor_service(service)) {\r
1245         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, service->exe, service->name, 0);\r
1246         Sleep(30000);\r
1247       }\r
1248     break;\r
1249 \r
1250     /* Do nothing, just like srvany would */\r
1251     case NSSM_EXIT_IGNORE:\r
1252       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service->name, code, exit_action_strings[action], service->exe, 0);\r
1253       Sleep(INFINITE);\r
1254     break;\r
1255 \r
1256     /* Tell the service manager we are finished */\r
1257     case NSSM_EXIT_REALLY:\r
1258       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service->name, code, exit_action_strings[action], 0);\r
1259       stop_service(service, exitcode, true, default_action);\r
1260     break;\r
1261 \r
1262     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
1263     case NSSM_EXIT_UNCLEAN:\r
1264       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service->name, code, exit_action_strings[action], 0);\r
1265       stop_service(service, exitcode, false, default_action);\r
1266       free_imports();\r
1267       exit(exitcode);\r
1268     break;\r
1269   }\r
1270 }\r
1271 \r
1272 void throttle_restart(nssm_service_t *service) {\r
1273   /* This can't be a restart if the service is already running. */\r
1274   if (! service->throttle++) return;\r
1275 \r
1276   int ms = throttle_milliseconds(service->throttle);\r
1277 \r
1278   if (service->throttle > 7) service->throttle = 8;\r
1279 \r
1280   TCHAR threshold[8], milliseconds[8];\r
1281   _sntprintf_s(threshold, _countof(threshold), _TRUNCATE, _T("%lu"), service->throttle_delay);\r
1282   _sntprintf_s(milliseconds, _countof(milliseconds), _TRUNCATE, _T("%lu"), ms);\r
1283   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service->name, threshold, milliseconds, 0);\r
1284 \r
1285   if (use_critical_section) EnterCriticalSection(&service->throttle_section);\r
1286   else if (service->throttle_timer) {\r
1287     ZeroMemory(&service->throttle_duetime, sizeof(service->throttle_duetime));\r
1288     service->throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
1289     SetWaitableTimer(service->throttle_timer, &service->throttle_duetime, 0, 0, 0, 0);\r
1290   }\r
1291 \r
1292   service->status.dwCurrentState = SERVICE_PAUSED;\r
1293   SetServiceStatus(service->status_handle, &service->status);\r
1294 \r
1295   if (use_critical_section) {\r
1296     imports.SleepConditionVariableCS(&service->throttle_condition, &service->throttle_section, ms);\r
1297     LeaveCriticalSection(&service->throttle_section);\r
1298   }\r
1299   else {\r
1300     if (service->throttle_timer) WaitForSingleObject(service->throttle_timer, INFINITE);\r
1301     else Sleep(ms);\r
1302   }\r
1303 }\r
1304 \r
1305 /*\r
1306   When responding to a stop (or any other) request we need to set dwWaitHint to\r
1307   the number of milliseconds we expect the operation to take, and optionally\r
1308   increase dwCheckPoint.  If dwWaitHint milliseconds elapses without the\r
1309   operation completing or dwCheckPoint increasing, the system will consider the\r
1310   service to be hung.\r
1311 \r
1312   However the system will consider the service to be hung after 30000\r
1313   milliseconds regardless of the value of dwWaitHint if dwCheckPoint has not\r
1314   changed.  Therefore if we want to wait longer than that we must periodically\r
1315   increase dwCheckPoint.\r
1316 \r
1317   Furthermore, it will consider the service to be hung after 60000 milliseconds\r
1318   regardless of the value of dwCheckPoint unless dwWaitHint is increased every\r
1319   time dwCheckPoint is also increased.\r
1320 \r
1321   Our strategy then is to retrieve the initial dwWaitHint and wait for\r
1322   NSSM_SERVICE_STATUS_DEADLINE milliseconds.  If the process is still running\r
1323   and we haven't finished waiting we increment dwCheckPoint and add whichever is\r
1324   smaller of NSSM_SERVICE_STATUS_DEADLINE or the remaining timeout to\r
1325   dwWaitHint.\r
1326 \r
1327   Only doing both these things will prevent the system from killing the service.\r
1328 \r
1329   Returns: 1 if the wait timed out.\r
1330            0 if the wait completed.\r
1331           -1 on error.\r
1332 */\r
1333 int await_shutdown(nssm_service_t *service, TCHAR *function_name, unsigned long timeout) {\r
1334   unsigned long interval;\r
1335   unsigned long waithint;\r
1336   unsigned long ret;\r
1337   unsigned long waited;\r
1338   TCHAR interval_milliseconds[16];\r
1339   TCHAR timeout_milliseconds[16];\r
1340   TCHAR waited_milliseconds[16];\r
1341   TCHAR *function = function_name;\r
1342 \r
1343   /* Add brackets to function name. */\r
1344   size_t funclen = _tcslen(function_name) + 3;\r
1345   TCHAR *func = (TCHAR *) HeapAlloc(GetProcessHeap(), 0, funclen * sizeof(TCHAR));\r
1346   if (func) {\r
1347     if (_sntprintf_s(func, funclen, _TRUNCATE, _T("%s()"), function_name) > -1) function = func;\r
1348   }\r
1349 \r
1350   _sntprintf_s(timeout_milliseconds, _countof(timeout_milliseconds), _TRUNCATE, _T("%lu"), timeout);\r
1351 \r
1352   waithint = service->status.dwWaitHint;\r
1353   waited = 0;\r
1354   while (waited < timeout) {\r
1355     interval = timeout - waited;\r
1356     if (interval > NSSM_SERVICE_STATUS_DEADLINE) interval = NSSM_SERVICE_STATUS_DEADLINE;\r
1357 \r
1358     service->status.dwCurrentState = SERVICE_STOP_PENDING;\r
1359     service->status.dwWaitHint += interval;\r
1360     service->status.dwCheckPoint++;\r
1361     SetServiceStatus(service->status_handle, &service->status);\r
1362 \r
1363     if (waited) {\r
1364       _sntprintf_s(waited_milliseconds, _countof(waited_milliseconds), _TRUNCATE, _T("%lu"), waited);\r
1365       _sntprintf_s(interval_milliseconds, _countof(interval_milliseconds), _TRUNCATE, _T("%lu"), interval);\r
1366       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_AWAITING_SHUTDOWN, function, service->name, waited_milliseconds, interval_milliseconds, timeout_milliseconds, 0);\r
1367     }\r
1368 \r
1369     switch (WaitForSingleObject(service->process_handle, interval)) {\r
1370       case WAIT_OBJECT_0:\r
1371         ret = 0;\r
1372         goto awaited;\r
1373 \r
1374       case WAIT_TIMEOUT:\r
1375         ret = 1;\r
1376       break;\r
1377 \r
1378       default:\r
1379         ret = -1;\r
1380         goto awaited;\r
1381     }\r
1382 \r
1383     waited += interval;\r
1384   }\r
1385 \r
1386 awaited:\r
1387   if (func) HeapFree(GetProcessHeap(), 0, func);\r
1388 \r
1389   return ret;\r
1390 }\r