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