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