Read registry before each restart.
[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   size_t flagslen = 0;\r
46   size_t s = 0;\r
47   int i;\r
48   for (i = 2; i < argc; i++) flagslen += strlen(argv[i]) + 1;\r
49   if (! flagslen) flagslen = 1;\r
50 \r
51   flags = (char *) HeapAlloc(GetProcessHeap(), 0, flagslen);\r
52   if (! flags) {\r
53     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "flags", "pre_install_service()", 0);\r
54     return 2;\r
55   }\r
56   ZeroMemory(flags, flagslen);\r
57 \r
58   /*\r
59     This probably isn't UTF8-safe and should use std::string or something\r
60     but it's been broken for the best part of a decade and due for a rewrite\r
61     anyway so it'll do as a quick-'n'-dirty fix.  Note that we don't free\r
62     the flags buffer but as the program exits that isn't a big problem.\r
63   */\r
64   for (i = 2; i < argc; i++) {\r
65     size_t len = strlen(argv[i]);\r
66     memmove(flags + s, argv[i], len);\r
67     s += len;\r
68     if (i < argc - 1) flags[s++] = ' ';\r
69   }\r
70 \r
71   return install_service(argv[0], argv[1], flags);\r
72 }\r
73 \r
74 /* About to remove the service */\r
75 int pre_remove_service(int argc, char **argv) {\r
76   /* Show dialogue box if we didn't pass service name and "confirm" */\r
77   if (argc < 2) return nssm_gui(IDD_REMOVE, argv[0]);\r
78   if (str_equiv(argv[1], "confirm")) return remove_service(argv[0]);\r
79   fprintf(stderr, "To remove a service without confirmation: nssm remove <servicename> confirm\n");\r
80   return 100;\r
81 }\r
82 \r
83 /* Install the service */\r
84 int install_service(char *name, char *exe, char *flags) {\r
85   /* Open service manager */\r
86   SC_HANDLE services = open_service_manager();\r
87   if (! services) {\r
88     fprintf(stderr, "Error opening service manager!\n");\r
89     return 2;\r
90   }\r
91   \r
92   /* Get path of this program */\r
93   char path[MAX_PATH];\r
94   GetModuleFileName(0, path, MAX_PATH);\r
95 \r
96   /* Construct command */\r
97   char command[CMD_LENGTH];\r
98   size_t runlen = strlen(NSSM_RUN);\r
99   size_t pathlen = strlen(path);\r
100   if (pathlen + runlen + 2 >= 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\" %s", path, NSSM_RUN) < 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 = NSSM_RESET_THROTTLE_RESTART + 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   InitializeCriticalSection(&throttle_section);\r
212 \r
213   monitor_service();\r
214 }\r
215 \r
216 /* Make sure service recovery actions are taken where necessary */\r
217 void set_service_recovery(char *service_name) {\r
218   SC_HANDLE services = open_service_manager();\r
219   if (! services) return;\r
220 \r
221   SC_HANDLE service = OpenService(services, service_name, SC_MANAGER_ALL_ACCESS);\r
222   if (! service) return;\r
223   return;\r
224 \r
225   SERVICE_FAILURE_ACTIONS_FLAG flag;\r
226   ZeroMemory(&flag, sizeof(flag));\r
227   flag.fFailureActionsOnNonCrashFailures = true;\r
228 \r
229   /* This functionality was added in Vista so the call may fail */\r
230   ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag);\r
231 }\r
232 \r
233 int monitor_service() {\r
234   /* Set service status to started */\r
235   int ret = start_service();\r
236   if (ret) {\r
237     char code[16];\r
238     snprintf(code, sizeof(code), "%d", ret);\r
239     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, exe, service_name, ret, 0);\r
240     return ret;\r
241   }\r
242   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, exe, flags, service_name, dir, 0);\r
243 \r
244   /* Monitor service service */\r
245   if (! RegisterWaitForSingleObject(&wait_handle, process_handle, end_service, (void *) pid, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {\r
246     log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service_name, exe, error_string(GetLastError()), 0);\r
247   }\r
248 \r
249   return 0;\r
250 }\r
251 \r
252 /* Service control handler */\r
253 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {\r
254   switch (control) {\r
255     case SERVICE_CONTROL_SHUTDOWN:\r
256     case SERVICE_CONTROL_STOP:\r
257       stop_service(0, true, true);\r
258       return NO_ERROR;\r
259 \r
260     case SERVICE_CONTROL_CONTINUE:\r
261       throttle = 0;\r
262       WakeConditionVariable(&throttle_condition);\r
263       service_status.dwCurrentState = SERVICE_CONTINUE_PENDING;\r
264       service_status.dwWaitHint = throttle_milliseconds() + NSSM_WAITHINT_MARGIN;\r
265       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service_name, 0);\r
266       SetServiceStatus(service_handle, &service_status);\r
267       return NO_ERROR;\r
268 \r
269     case SERVICE_CONTROL_PAUSE:\r
270       /*\r
271         We don't accept pause messages but it isn't possible to register\r
272         only for continue messages so we have to handle this case.\r
273       */\r
274       return ERROR_CALL_NOT_IMPLEMENTED;\r
275   }\r
276 \r
277   /* Unknown control */\r
278   return ERROR_CALL_NOT_IMPLEMENTED;\r
279 }\r
280 \r
281 /* Start the service */\r
282 int start_service() {\r
283   stopping = false;\r
284 \r
285   if (process_handle) return 0;\r
286 \r
287   /* Allocate a STARTUPINFO structure for a new process */\r
288   STARTUPINFO si;\r
289   ZeroMemory(&si, sizeof(si));\r
290   si.cb = sizeof(si);\r
291 \r
292   /* Allocate a PROCESSINFO structure for the process */\r
293   PROCESS_INFORMATION pi;\r
294   ZeroMemory(&pi, sizeof(pi));\r
295 \r
296   /* Get startup parameters */\r
297   int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir));\r
298   if (ret) {\r
299     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0);\r
300     return stop_service(2, true, true);\r
301   }\r
302 \r
303   /* Launch executable with arguments */\r
304   char cmd[CMD_LENGTH];\r
305   if (_snprintf(cmd, sizeof(cmd), "%s %s", exe, flags) < 0) {\r
306     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);\r
307     return stop_service(2, true, true);\r
308   }\r
309 \r
310   throttle_restart();\r
311 \r
312   if (! CreateProcess(0, cmd, 0, 0, false, 0, 0, dir, &si, &pi)) {\r
313     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, error_string(GetLastError()), 0);\r
314     return stop_service(3, true, true);\r
315   }\r
316   process_handle = pi.hProcess;\r
317   pid = pi.dwProcessId;\r
318 \r
319   /* Signal successful start */\r
320   service_status.dwCurrentState = SERVICE_RUNNING;\r
321   SetServiceStatus(service_handle, &service_status);\r
322 \r
323   /* Wait for a clean startup. */\r
324   if (WaitForSingleObject(process_handle, NSSM_RESET_THROTTLE_RESTART) == WAIT_TIMEOUT) throttle = 0;\r
325 \r
326   return 0;\r
327 }\r
328 \r
329 /* Stop the service */\r
330 int stop_service(unsigned long exitcode, bool graceful, bool default_action) {\r
331   if (default_action && ! exitcode && ! graceful) {\r
332     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
333     graceful = true;\r
334   }\r
335 \r
336   /* Signal we are stopping */\r
337   if (graceful) {\r
338     service_status.dwCurrentState = SERVICE_STOP_PENDING;\r
339     service_status.dwWaitHint = NSSM_KILL_WINDOW_GRACE_PERIOD + NSSM_KILL_THREADS_GRACE_PERIOD + NSSM_WAITHINT_MARGIN;\r
340     SetServiceStatus(service_handle, &service_status);\r
341   }\r
342 \r
343   /* Nothing to do if server isn't running */\r
344   if (pid) {\r
345     /* Shut down server */\r
346     log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);\r
347     kill_process(service_name, process_handle, pid, 0);\r
348     process_handle = 0;\r
349   }\r
350   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);\r
351 \r
352   end_service((void *) pid, true);\r
353 \r
354   /* Signal we stopped */\r
355   if (graceful) {\r
356     service_status.dwCurrentState = SERVICE_STOPPED;\r
357     if (exitcode) {\r
358       service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;\r
359       service_status.dwServiceSpecificExitCode = exitcode;\r
360     }\r
361     else {\r
362       service_status.dwWin32ExitCode = NO_ERROR;\r
363       service_status.dwServiceSpecificExitCode = 0;\r
364     }\r
365     SetServiceStatus(service_handle, &service_status);\r
366   }\r
367 \r
368   return exitcode;\r
369 }\r
370 \r
371 /* Callback function triggered when the server exits */\r
372 void CALLBACK end_service(void *arg, unsigned char why) {\r
373   if (stopping) return;\r
374 \r
375   stopping = true;\r
376 \r
377   pid = (unsigned long) arg;\r
378 \r
379   /* Check exit code */\r
380   unsigned long exitcode = 0;\r
381   GetExitCodeProcess(process_handle, &exitcode);\r
382 \r
383   /* Clean up. */\r
384   kill_process_tree(service_name, pid, exitcode, pid);\r
385 \r
386   /*\r
387     The why argument is true if our wait timed out or false otherwise.\r
388     Our wait is infinite so why will never be true when called by the system.\r
389     If it is indeed true, assume we were called from stop_service() because\r
390     this is a controlled shutdown, and don't take any restart action.\r
391   */\r
392   if (why) return;\r
393 \r
394   char code[16];\r
395   _snprintf(code, sizeof(code), "%d", exitcode);\r
396   log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);\r
397 \r
398   /* What action should we take? */\r
399   int action = NSSM_EXIT_RESTART;\r
400   unsigned char action_string[ACTION_LEN];\r
401   bool default_action;\r
402   if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {\r
403     for (int i = 0; exit_action_strings[i]; i++) {\r
404       if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {\r
405         action = i;\r
406         break;\r
407       }\r
408     }\r
409   }\r
410 \r
411   process_handle = 0;\r
412   pid = 0;\r
413   switch (action) {\r
414     /* Try to restart the service or return failure code to service manager */\r
415     case NSSM_EXIT_RESTART:\r
416       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);\r
417       while (monitor_service()) {\r
418         log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);\r
419         Sleep(30000);\r
420       }\r
421     break;\r
422 \r
423     /* Do nothing, just like srvany would */\r
424     case NSSM_EXIT_IGNORE:\r
425       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);\r
426       Sleep(INFINITE);\r
427     break;\r
428 \r
429     /* Tell the service manager we are finished */\r
430     case NSSM_EXIT_REALLY:\r
431       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);\r
432       stop_service(exitcode, true, default_action);\r
433     break;\r
434 \r
435     /* Fake a crash so pre-Vista service managers will run recovery actions. */\r
436     case NSSM_EXIT_UNCLEAN:\r
437       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);\r
438       exit(stop_service(exitcode, false, default_action));\r
439     break;\r
440   }\r
441 }\r
442 \r
443 void throttle_restart() {\r
444   /* This can't be a restart if the service is already running. */\r
445   if (! throttle++) return;\r
446 \r
447   int ms = throttle_milliseconds();\r
448 \r
449   if (throttle > 7) throttle = 8;\r
450 \r
451   char threshold[8], milliseconds[8];\r
452   _snprintf(threshold, sizeof(threshold), "%d", NSSM_RESET_THROTTLE_RESTART);\r
453   _snprintf(milliseconds, sizeof(milliseconds), "%d", ms);\r
454   log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);\r
455 \r
456   EnterCriticalSection(&throttle_section);\r
457 \r
458   service_status.dwCurrentState = SERVICE_PAUSED;\r
459   SetServiceStatus(service_handle, &service_status);\r
460 \r
461   SleepConditionVariableCS(&throttle_condition, &throttle_section, ms);\r
462 \r
463   LeaveCriticalSection(&throttle_section);\r
464 }\r