3 SERVICE_STATUS service_status;
\r
4 SERVICE_STATUS_HANDLE service_handle;
\r
5 HANDLE process_handle;
\r
8 static char service_name[SERVICE_NAME_LENGTH];
\r
9 char exe[EXE_LENGTH];
\r
10 char flags[CMD_LENGTH];
\r
13 CRITICAL_SECTION throttle_section;
\r
14 CONDITION_VARIABLE throttle_condition;
\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
19 static unsigned long throttle;
\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
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
31 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);
\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
43 /* Arguments are optional */
\r
45 if (argc == 2) flags = "";
\r
46 else flags = argv[2];
\r
48 return install_service(argv[0], argv[1], flags);
\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
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
65 fprintf(stderr, "Error opening service manager!\n");
\r
69 /* Get path of this program */
\r
70 char path[MAX_PATH];
\r
71 GetModuleFileName(0, path, MAX_PATH);
\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
81 if (snprintf(command, sizeof(command), "\"%s\" %s", path, NSSM_RUN) < 0) {
\r
82 fprintf(stderr, "Out of memory for ImagePath!\n");
\r
86 /* Work out directory name */
\r
87 size_t len = strlen(exe);
\r
89 for (i = len; i && exe[i] != '\\' && exe[i] != '/'; i--);
\r
91 memmove(dir, exe, i);
\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
97 fprintf(stderr, "Error creating service!\n");
\r
98 CloseServiceHandle(services);
\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
111 CloseServiceHandle(service);
\r
112 CloseServiceHandle(services);
\r
114 printf("Service \"%s\" installed successfully!\n", name);
\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
123 fprintf(stderr, "Error opening service manager!\n");
\r
127 /* Try to open the service */
\r
128 SC_HANDLE service = OpenService(services, name, SC_MANAGER_ALL_ACCESS);
\r
130 fprintf(stderr, "Can't open service!");
\r
131 CloseServiceHandle(services);
\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
144 CloseServiceHandle(service);
\r
145 CloseServiceHandle(services);
\r
147 printf("Service \"%s\" removed successfully!\n", name);
\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
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
167 /* Signal we AREN'T running the server */
\r
168 process_handle = 0;
\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
178 /* Get startup parameters */
\r
179 int ret = get_parameters(argv[0], exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir));
\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
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
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
196 set_service_recovery(service_name);
\r
198 /* Used for signalling a resume if the service pauses when throttled. */
\r
199 InitializeCriticalSection(&throttle_section);
\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
209 SC_HANDLE service = OpenService(services, service_name, SC_MANAGER_ALL_ACCESS);
\r
210 if (! service) return;
\r
213 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
214 ZeroMemory(&flag, sizeof(flag));
\r
215 flag.fFailureActionsOnNonCrashFailures = true;
\r
217 /* This functionality was added in Vista so the call may fail */
\r
218 ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag);
\r
221 int monitor_service() {
\r
222 /* Set service status to started */
\r
223 int ret = start_service();
\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
230 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, exe, flags, service_name, dir, 0);
\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
240 /* Service control handler */
\r
241 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
243 case SERVICE_CONTROL_SHUTDOWN:
\r
244 case SERVICE_CONTROL_STOP:
\r
245 stop_service(0, true, true);
\r
248 case SERVICE_CONTROL_CONTINUE:
\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
257 case SERVICE_CONTROL_PAUSE:
\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
262 return ERROR_CALL_NOT_IMPLEMENTED;
\r
265 /* Unknown control */
\r
266 return ERROR_CALL_NOT_IMPLEMENTED;
\r
269 /* Start the service */
\r
270 int start_service() {
\r
273 if (process_handle) return 0;
\r
275 /* Allocate a STARTUPINFO structure for a new process */
\r
277 ZeroMemory(&si, sizeof(si));
\r
278 si.cb = sizeof(si);
\r
280 /* Allocate a PROCESSINFO structure for the process */
\r
281 PROCESS_INFORMATION pi;
\r
282 ZeroMemory(&pi, sizeof(pi));
\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
291 throttle_restart();
\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
297 process_handle = pi.hProcess;
\r
298 pid = pi.dwProcessId;
\r
300 /* Signal successful start */
\r
301 service_status.dwCurrentState = SERVICE_RUNNING;
\r
302 SetServiceStatus(service_handle, &service_status);
\r
304 /* Wait for a clean startup. */
\r
305 if (WaitForSingleObject(process_handle, NSSM_RESET_THROTTLE_RESTART) == WAIT_TIMEOUT) throttle = 0;
\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
317 /* Signal we are stopping */
\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
324 /* Nothing to do if server isn't running */
\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
331 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);
\r
333 end_service((void *) pid, true);
\r
335 /* Signal we stopped */
\r
337 service_status.dwCurrentState = SERVICE_STOPPED;
\r
339 service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
340 service_status.dwServiceSpecificExitCode = exitcode;
\r
343 service_status.dwWin32ExitCode = NO_ERROR;
\r
344 service_status.dwServiceSpecificExitCode = 0;
\r
346 SetServiceStatus(service_handle, &service_status);
\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
358 pid = (unsigned long) arg;
\r
360 /* Check exit code */
\r
361 unsigned long exitcode = 0;
\r
362 GetExitCodeProcess(process_handle, &exitcode);
\r
365 kill_process_tree(service_name, pid, exitcode, pid);
\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
376 _snprintf(code, sizeof(code), "%d", exitcode);
\r
377 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);
\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
392 process_handle = 0;
\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
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
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
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
424 void throttle_restart() {
\r
425 /* This can't be a restart if the service is already running. */
\r
426 if (! throttle++) return;
\r
428 int ms = throttle_milliseconds();
\r
430 if (throttle > 7) throttle = 8;
\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
437 EnterCriticalSection(&throttle_section);
\r
439 service_status.dwCurrentState = SERVICE_PAUSED;
\r
440 SetServiceStatus(service_handle, &service_status);
\r
442 SleepConditionVariableCS(&throttle_condition, &throttle_section, ms);
\r
444 LeaveCriticalSection(&throttle_section);
\r