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