Support srvany's AppEnvironment registry value.
[nssm.git] / service.cpp
1 #include "nssm.h"\r
2 \r
3 SERVICE_STATUS service_status;\r
4 SERVICE_STATUS_HANDLE service_handle;\r
5 HANDLE process_handle;\r
6 HANDLE wait_handle;\r
7 unsigned long pid;\r
8 static char service_name[SERVICE_NAME_LENGTH];\r
9 char exe[EXE_LENGTH];\r
10 char flags[CMD_LENGTH];\r
11 char dir[MAX_PATH];\r
12 bool stopping;\r
13 unsigned long throttle_delay;\r
14 HANDLE throttle_timer;\r
15 LARGE_INTEGER throttle_duetime;\r
16 \r
17 static enum { NSSM_EXIT_RESTART, NSSM_EXIT_IGNORE, NSSM_EXIT_REALLY, NSSM_EXIT_UNCLEAN } exit_actions;\r
18 static const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", "Suicide", 0 };\r
19 \r
20 static unsigned long throttle;\r
21 \r
22 static inline int throttle_milliseconds() {\r
23   /* pow() operates on doubles. */\r
24   int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;\r
25   return ret * 1000;\r
26 }\r
27 \r
28 /* Connect to the service manager */\r
29 SC_HANDLE open_service_manager() {\r
30   SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);\r
31   if (! ret) {\r
32     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);\r
33     return 0;\r
34   }\r
35 \r
36   return ret;\r
37 }\r
38 \r
39 /* About to install the service */\r
40 int pre_install_service(int argc, char **argv) {\r
41   /* Show the dialogue box if we didn't give the */\r
42   if (argc < 2) return nssm_gui(IDD_INSTALL, argv[0]);\r
43 \r
44   /* Arguments are optional */\r
45   char *flags;\r
46   size_t flagslen = 0;\r
47   size_t s = 0;\r
48   int i;\r
49   for (i = 2; i < argc; i++) flagslen += strlen(argv[i]) + 1;\r
50   if (! flagslen) flagslen = 1;\r
51 \r
52   flags = (char *) HeapAlloc(GetProcessHeap(), 0, flagslen);\r
53   if (! flags) {\r
54     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "flags", "pre_install_service()", 0);\r
55     return 2;\r
56   }\r
57   ZeroMemory(flags, flagslen);\r
58 \r
59   /*\r
60     This probably isn't UTF8-safe and should use std::string or something\r
61     but it's been broken for the best part of a decade and due for a rewrite\r
62     anyway so it'll do as a quick-'n'-dirty fix.  Note that we don't free\r
63     the flags buffer but as the program exits that isn't a big problem.\r
64   */\r
65   for (i = 2; i < argc; i++) {\r
66     size_t len = strlen(argv[i]);\r
67     memmove(flags + s, argv[i], len);\r
68     s += len;\r
69     if (i < argc - 1) flags[s++] = ' ';\r
70   }\r
71 \r
72   return install_service(argv[0], argv[1], flags);\r
73 }\r
74 \r
75 /* About to remove the service */\r
76 int pre_remove_service(int argc, char **argv) {\r
77   /* Show dialogue box if we didn't pass service name and "confirm" */\r
78   if (argc < 2) return nssm_gui(IDD_REMOVE, argv[0]);\r
79   if (str_equiv(argv[1], "confirm")) return remove_service(argv[0]);\r
80   fprintf(stderr, "To remove a service without confirmation: nssm remove <servicename> confirm\n");\r
81   return 100;\r
82 }\r
83 \r
84 /* Install the service */\r
85 int install_service(char *name, char *exe, char *flags) {\r
86   /* Open service manager */\r
87   SC_HANDLE services = open_service_manager();\r
88   if (! services) {\r
89     fprintf(stderr, "Error opening service manager!\n");\r
90     return 2;\r
91   }\r
92   \r
93   /* Get path of this program */\r
94   char path[MAX_PATH];\r
95   GetModuleFileName(0, path, MAX_PATH);\r
96 \r
97   /* Construct command */\r
98   char command[CMD_LENGTH];\r
99   size_t runlen = strlen(NSSM_RUN);\r
100   size_t pathlen = strlen(path);\r
101   if (pathlen + runlen + 2 >= VALUE_LENGTH) {\r
102     fprintf(stderr, "The full path to " NSSM " is too long!\n");\r
103     return 3;\r
104   }\r
105   if (_snprintf(command, sizeof(command), "\"%s\" %s", path, NSSM_RUN) < 0) {\r
106     fprintf(stderr, "Out of memory for ImagePath!\n");\r
107     return 4;\r
108   }\r
109 \r
110   /* Work out directory name */\r
111   size_t len = strlen(exe);\r
112   size_t i;\r
113   for (i = len; i && exe[i] != '\\' && exe[i] != '/'; i--);\r
114   char dir[MAX_PATH];\r
115   memmove(dir, exe, i);\r
116   dir[i] = '\0';\r
117 \r
118   /* Create the service */\r
119   SC_HANDLE service = CreateService(services, name, name, SC_MANAGER_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS, SERVICE_AUTO_START, SERVICE_ERROR_NORMAL, command, 0, 0, 0, 0, 0);\r
120   if (! service) {\r
121     fprintf(stderr, "Error creating service!\n");\r
122     CloseServiceHandle(services);\r
123     return 5;\r
124   }\r
125 \r
126   /* Now we need to put the parameters into the registry */\r
127   if (create_parameters(name, exe, flags, dir)) {\r
128     fprintf(stderr, "Error setting startup parameters for the service!\n");\r
129     DeleteService(service);\r
130     CloseServiceHandle(services);\r
131     return 6;\r
132   }\r
133 \r
134   /* Cleanup */\r
135   CloseServiceHandle(service);\r
136   CloseServiceHandle(services);\r
137 \r
138   printf("Service \"%s\" installed successfully!\n", name);\r
139   return 0;\r
140 }\r
141 \r
142 /* Remove the service */\r
143 int remove_service(char *name) {\r
144   /* Open service manager */\r
145   SC_HANDLE services = open_service_manager();\r
146   if (! services) {\r
147     fprintf(stderr, "Error opening service manager!\n");\r
148     return 2;\r
149   }\r
150   \r
151   /* Try to open the service */\r
152   SC_HANDLE service = OpenService(services, name, SC_MANAGER_ALL_ACCESS);\r
153   if (! service) {\r
154     fprintf(stderr, "Can't open service!");\r
155     CloseServiceHandle(services);\r
156     return 3;\r
157   }\r
158 \r
159   /* Try to delete the service */\r
160   if (! DeleteService(service)) {\r
161     fprintf(stderr, "Error deleting service!\n");\r
162     CloseServiceHandle(service);\r
163     CloseServiceHandle(services);\r
164     return 4;\r
165   }\r
166 \r
167   /* Cleanup */\r
168   CloseServiceHandle(service);\r
169   CloseServiceHandle(services);\r
170 \r
171   printf("Service \"%s\" removed successfully!\n", name);\r
172   return 0;\r
173 }\r
174 \r
175 /* Service initialisation */\r
176 void WINAPI service_main(unsigned long argc, char **argv) {\r
177   if (_snprintf(service_name, sizeof(service_name), "%s", argv[0]) < 0) {\r
178     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service_name", "service_main()", 0);\r
179     return;\r
180   }\r
181 \r
182   /* Initialise status */\r
183   ZeroMemory(&service_status, sizeof(service_status));\r
184   service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;\r
185   service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;\r
186   service_status.dwWin32ExitCode = NO_ERROR;\r
187   service_status.dwServiceSpecificExitCode = 0;\r
188   service_status.dwCheckPoint = 0;\r
189   service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;\r
190 \r
191   /* Signal we AREN'T running the server */\r
192   process_handle = 0;\r
193   pid = 0;\r
194 \r
195   /* Register control handler */\r
196   service_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, 0);\r
197   if (! service_handle) {\r
198     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);\r
199     return;\r
200   }\r
201 \r
202   service_status.dwCurrentState = SERVICE_START_PENDING;\r
203   service_status.dwWaitHint = throttle_delay + NSSM_WAITHINT_MARGIN;\r
204   SetServiceStatus(service_handle, &service_status);\r
205 \r
206   /* Try to create the exit action parameters; we don't care if it fails */\r
207   create_exit_action(argv[0], exit_action_strings[0]);\r
208 \r
209   set_service_recovery(service_name);\r
210 \r
211   /* Used for signalling a resume if the service pauses when throttled. */\r
212   throttle_timer = CreateWaitableTimer(0, 1, 0);\r
213   if (! throttle_timer) {\r
214     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service_name, error_string(GetLastError()), 0);\r
215   }\r
216 \r
217   monitor_service();\r
218 }\r
219 \r
220 /* Make sure service recovery actions are taken where necessary */\r
221 void set_service_recovery(char *service_name) {\r
222   SC_HANDLE services = open_service_manager();\r
223   if (! services) return;\r
224 \r
225   SC_HANDLE service = OpenService(services, service_name, SC_MANAGER_ALL_ACCESS);\r
226   if (! service) return;\r
227   return;\r
228 \r
229   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
230   ZeroMemory(&flag, sizeof(flag));\r
231   flag.fFailureActionsOnNonCrashFailures = true;\r
232 \r
233   /* This functionality was added in Vista so the call may fail */\r
234   ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag);\r
235 }\r
236 \r
237 int monitor_service() {\r
238   /* Set service status to started */\r
239   int ret = start_service();\r
240   if (ret) {\r
241     char code[16];\r
242     _snprintf(code, sizeof(code), "%d", ret);\r
243     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, exe, service_name, ret, 0);\r
244     return ret;\r
245   }\r
246   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, exe, flags, service_name, dir, 0);\r
247 \r
248   /* Monitor service service */\r
249   if (! RegisterWaitForSingleObject(&wait_handle, process_handle, end_service, (void *) pid, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
250     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service_name, exe, error_string(GetLastError()), 0);\r
251   }\r
252 \r
253   return 0;\r
254 }\r
255 \r
256 /* Service control handler */\r
257 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
258   switch (control) {\r
259     case SERVICE_CONTROL_SHUTDOWN:\r
260     case SERVICE_CONTROL_STOP:\r
261       stop_service(0, true, true);\r
262       return NO_ERROR;\r
263 \r
264     case SERVICE_CONTROL_CONTINUE:\r
265       if (! throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;\r
266       throttle = 0;\r
267       ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
268       SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
269       service_status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
270       service_status.dwWaitHint = throttle_milliseconds() + NSSM_WAITHINT_MARGIN;\r
271       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service_name, 0);\r
272       SetServiceStatus(service_handle, &service_status);\r
273       return NO_ERROR;\r
274 \r
275     case SERVICE_CONTROL_PAUSE:\r
276       /*\r
277         We don't accept pause messages but it isn't possible to register\r
278         only for continue messages so we have to handle this case.\r
279       */\r
280       return ERROR_CALL_NOT_IMPLEMENTED;\r
281   }\r
282 \r
283   /* Unknown control */\r
284   return ERROR_CALL_NOT_IMPLEMENTED;\r
285 }\r
286 \r
287 /* Start the service */\r
288 int start_service() {\r
289   stopping = false;\r
290 \r
291   if (process_handle) return 0;\r
292 \r
293   /* Allocate a STARTUPINFO structure for a new process */\r
294   STARTUPINFO si;\r
295   ZeroMemory(&si, sizeof(si));\r
296   si.cb = sizeof(si);\r
297 \r
298   /* Allocate a PROCESSINFO structure for the process */\r
299   PROCESS_INFORMATION pi;\r
300   ZeroMemory(&pi, sizeof(pi));\r
301 \r
302   /* Get startup parameters */\r
303   char *env = 0;\r
304   int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay);\r
305   if (ret) {\r
306     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0);\r
307     return stop_service(2, true, true);\r
308   }\r
309 \r
310   /* Launch executable with arguments */\r
311   char cmd[CMD_LENGTH];\r
312   if (_snprintf(cmd, sizeof(cmd), "\"%s\" %s", exe, flags) < 0) {\r
313     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);\r
314     return stop_service(2, true, true);\r
315   }\r
316 \r
317   throttle_restart();\r
318 \r
319   if (! CreateProcess(0, cmd, 0, 0, false, 0, env, dir, &si, &pi)) {\r
320     unsigned long error = GetLastError();\r
321     if (error == ERROR_INVALID_PARAMETER && env) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service_name, exe, NSSM_REG_ENV, 0);\r
322     else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, error_string(error), 0);\r
323     return stop_service(3, true, true);\r
324   }\r
325   process_handle = pi.hProcess;\r
326   pid = pi.dwProcessId;\r
327 \r
328   /* Signal successful start */\r
329   service_status.dwCurrentState = SERVICE_RUNNING;\r
330   SetServiceStatus(service_handle, &service_status);\r
331 \r
332   /* Wait for a clean startup. */\r
333   if (WaitForSingleObject(process_handle, throttle_delay) == WAIT_TIMEOUT) throttle = 0;\r
334 \r
335   return 0;\r
336 }\r
337 \r
338 /* Stop the service */\r
339 int stop_service(unsigned long exitcode, bool graceful, bool default_action) {\r
340   if (default_action && ! exitcode && ! graceful) {\r
341     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_GRACEFUL_SUICIDE, service_name, 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
342     graceful = true;\r
343   }\r
344 \r
345   /* Signal we are stopping */\r
346   if (graceful) {\r
347     service_status.dwCurrentState = SERVICE_STOP_PENDING;\r
348     service_status.dwWaitHint = NSSM_KILL_WINDOW_GRACE_PERIOD + NSSM_KILL_THREADS_GRACE_PERIOD + NSSM_WAITHINT_MARGIN;\r
349     SetServiceStatus(service_handle, &service_status);\r
350   }\r
351 \r
352   /* Nothing to do if server isn't running */\r
353   if (pid) {\r
354     /* Shut down server */\r
355     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);\r
356     kill_process(service_name, process_handle, pid, 0);\r
357     process_handle = 0;\r
358   }\r
359   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);\r
360 \r
361   end_service((void *) pid, true);\r
362 \r
363   /* Signal we stopped */\r
364   if (graceful) {\r
365     service_status.dwCurrentState = SERVICE_STOPPED;\r
366     if (exitcode) {\r
367       service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
368       service_status.dwServiceSpecificExitCode = exitcode;\r
369     }\r
370     else {\r
371       service_status.dwWin32ExitCode = NO_ERROR;\r
372       service_status.dwServiceSpecificExitCode = 0;\r
373     }\r
374     SetServiceStatus(service_handle, &service_status);\r
375   }\r
376 \r
377   return exitcode;\r
378 }\r
379 \r
380 /* Callback function triggered when the server exits */\r
381 void CALLBACK end_service(void *arg, unsigned char why) {\r
382   if (stopping) return;\r
383 \r
384   stopping = true;\r
385 \r
386   pid = (unsigned long) arg;\r
387 \r
388   /* Check exit code */\r
389   unsigned long exitcode = 0;\r
390   GetExitCodeProcess(process_handle, &exitcode);\r
391 \r
392   /* Clean up. */\r
393   kill_process_tree(service_name, pid, exitcode, pid);\r
394 \r
395   /*\r
396     The why argument is true if our wait timed out or false otherwise.\r
397     Our wait is infinite so why will never be true when called by the system.\r
398     If it is indeed true, assume we were called from stop_service() because\r
399     this is a controlled shutdown, and don't take any restart action.\r
400   */\r
401   if (why) return;\r
402 \r
403   char code[16];\r
404   _snprintf(code, sizeof(code), "%d", exitcode);\r
405   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);\r
406 \r
407   /* What action should we take? */\r
408   int action = NSSM_EXIT_RESTART;\r
409   unsigned char action_string[ACTION_LEN];\r
410   bool default_action;\r
411   if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {\r
412     for (int i = 0; exit_action_strings[i]; i++) {\r
413       if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
414         action = i;\r
415         break;\r
416       }\r
417     }\r
418   }\r
419 \r
420   process_handle = 0;\r
421   pid = 0;\r
422   switch (action) {\r
423     /* Try to restart the service or return failure code to service manager */\r
424     case NSSM_EXIT_RESTART:\r
425       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);\r
426       while (monitor_service()) {\r
427         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);\r
428         Sleep(30000);\r
429       }\r
430     break;\r
431 \r
432     /* Do nothing, just like srvany would */\r
433     case NSSM_EXIT_IGNORE:\r
434       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);\r
435       Sleep(INFINITE);\r
436     break;\r
437 \r
438     /* Tell the service manager we are finished */\r
439     case NSSM_EXIT_REALLY:\r
440       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);\r
441       stop_service(exitcode, true, default_action);\r
442     break;\r
443 \r
444     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
445     case NSSM_EXIT_UNCLEAN:\r
446       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);\r
447       exit(stop_service(exitcode, false, default_action));\r
448     break;\r
449   }\r
450 }\r
451 \r
452 void throttle_restart() {\r
453   /* This can't be a restart if the service is already running. */\r
454   if (! throttle++) return;\r
455 \r
456   int ms = throttle_milliseconds();\r
457 \r
458   if (throttle > 7) throttle = 8;\r
459 \r
460   char threshold[8], milliseconds[8];\r
461   _snprintf(threshold, sizeof(threshold), "%d", throttle_delay);\r
462   _snprintf(milliseconds, sizeof(milliseconds), "%d", ms);\r
463   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);\r
464 \r
465   if (throttle_timer) {\r
466     ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));\r
467     throttle_duetime.QuadPart = 0 - (ms * 10000LL);\r
468     SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);\r
469   }\r
470 \r
471   service_status.dwCurrentState = SERVICE_PAUSED;\r
472   SetServiceStatus(service_handle, &service_status);\r
473 \r
474   if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);\r
475   else Sleep(ms);\r
476 }\r