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