4 SERVICE_STATUS service_status;
\r
5 SERVICE_STATUS_HANDLE service_handle;
\r
6 HANDLE process_handle;
\r
9 static char service_name[SERVICE_NAME_LENGTH];
\r
10 char exe[EXE_LENGTH];
\r
11 char flags[CMD_LENGTH];
\r
15 unsigned long throttle_delay;
\r
16 unsigned long stop_method;
\r
17 HANDLE throttle_timer;
\r
18 LARGE_INTEGER throttle_duetime;
\r
19 FILETIME creation_time;
\r
21 static enum { NSSM_EXIT_RESTART, NSSM_EXIT_IGNORE, NSSM_EXIT_REALLY, NSSM_EXIT_UNCLEAN } exit_actions;
\r
22 static const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", "Suicide", 0 };
\r
24 static unsigned long throttle;
\r
26 static inline int throttle_milliseconds() {
\r
27 /* pow() operates on doubles. */
\r
28 int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;
\r
32 /* Connect to the service manager */
\r
33 SC_HANDLE open_service_manager() {
\r
34 SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
\r
36 if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);
\r
43 /* About to install the service */
\r
44 int pre_install_service(int argc, char **argv) {
\r
45 /* Show the dialogue box if we didn't give the service name and path */
\r
46 if (argc < 2) return nssm_gui(IDD_INSTALL, argv[0]);
\r
48 /* Arguments are optional */
\r
50 size_t flagslen = 0;
\r
53 for (i = 2; i < argc; i++) flagslen += strlen(argv[i]) + 1;
\r
54 if (! flagslen) flagslen = 1;
\r
56 flags = (char *) HeapAlloc(GetProcessHeap(), 0, flagslen);
\r
58 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "flags", "pre_install_service()", 0);
\r
61 ZeroMemory(flags, flagslen);
\r
64 This probably isn't UTF8-safe and should use std::string or something
\r
65 but it's been broken for the best part of a decade and due for a rewrite
\r
66 anyway so it'll do as a quick-'n'-dirty fix. Note that we don't free
\r
67 the flags buffer but as the program exits that isn't a big problem.
\r
69 for (i = 2; i < argc; i++) {
\r
70 size_t len = strlen(argv[i]);
\r
71 memmove(flags + s, argv[i], len);
\r
73 if (i < argc - 1) flags[s++] = ' ';
\r
76 return install_service(argv[0], argv[1], flags);
\r
79 /* About to remove the service */
\r
80 int pre_remove_service(int argc, char **argv) {
\r
81 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
82 if (argc < 2) return nssm_gui(IDD_REMOVE, argv[0]);
\r
83 if (str_equiv(argv[1], "confirm")) return remove_service(argv[0]);
\r
84 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
88 /* Install the service */
\r
89 int install_service(char *name, char *exe, char *flags) {
\r
90 /* Open service manager */
\r
91 SC_HANDLE services = open_service_manager();
\r
93 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
97 /* Get path of this program */
\r
98 char path[MAX_PATH];
\r
99 GetModuleFileName(0, path, MAX_PATH);
\r
101 /* Construct command */
\r
102 char command[CMD_LENGTH];
\r
103 size_t pathlen = strlen(path);
\r
104 if (pathlen + 1 >= VALUE_LENGTH) {
\r
105 print_message(stderr, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);
\r
108 if (_snprintf(command, sizeof(command), "\"%s\"", path) < 0) {
\r
109 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY_FOR_IMAGEPATH);
\r
113 /* Work out directory name */
\r
114 size_t len = strlen(exe);
\r
116 for (i = len; i && exe[i] != '\\' && exe[i] != '/'; i--);
\r
117 char dir[MAX_PATH];
\r
118 memmove(dir, exe, i);
\r
121 /* Create the service */
\r
122 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
124 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
125 CloseServiceHandle(services);
\r
129 /* Now we need to put the parameters into the registry */
\r
130 if (create_parameters(name, exe, flags, dir)) {
\r
131 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
132 DeleteService(service);
\r
133 CloseServiceHandle(services);
\r
137 set_service_recovery(service, name);
\r
140 CloseServiceHandle(service);
\r
141 CloseServiceHandle(services);
\r
143 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, name);
\r
147 /* Remove the service */
\r
148 int remove_service(char *name) {
\r
149 /* Open service manager */
\r
150 SC_HANDLE services = open_service_manager();
\r
152 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
156 /* Try to open the service */
\r
157 SC_HANDLE service = OpenService(services, name, SC_MANAGER_ALL_ACCESS);
\r
159 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
160 CloseServiceHandle(services);
\r
164 /* Try to delete the service */
\r
165 if (! DeleteService(service)) {
\r
166 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
167 CloseServiceHandle(service);
\r
168 CloseServiceHandle(services);
\r
173 CloseServiceHandle(service);
\r
174 CloseServiceHandle(services);
\r
176 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, name);
\r
180 /* Service initialisation */
\r
181 void WINAPI service_main(unsigned long argc, char **argv) {
\r
182 if (_snprintf(service_name, sizeof(service_name), "%s", argv[0]) < 0) {
\r
183 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service_name", "service_main()", 0);
\r
187 /* Initialise status */
\r
188 ZeroMemory(&service_status, sizeof(service_status));
\r
189 service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
190 service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
191 service_status.dwWin32ExitCode = NO_ERROR;
\r
192 service_status.dwServiceSpecificExitCode = 0;
\r
193 service_status.dwCheckPoint = 0;
\r
194 service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
196 /* Signal we AREN'T running the server */
\r
197 process_handle = 0;
\r
200 /* Register control handler */
\r
201 service_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, 0);
\r
202 if (! service_handle) {
\r
203 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
207 log_service_control(service_name, 0, true);
\r
209 service_status.dwCurrentState = SERVICE_START_PENDING;
\r
210 service_status.dwWaitHint = throttle_delay + NSSM_WAITHINT_MARGIN;
\r
211 SetServiceStatus(service_handle, &service_status);
\r
214 /* Try to create the exit action parameters; we don't care if it fails */
\r
215 create_exit_action(argv[0], exit_action_strings[0]);
\r
217 set_service_recovery(0, service_name);
\r
220 /* Used for signalling a resume if the service pauses when throttled. */
\r
221 throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
222 if (! throttle_timer) {
\r
223 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service_name, error_string(GetLastError()), 0);
\r
229 /* Make sure service recovery actions are taken where necessary */
\r
230 void set_service_recovery(SC_HANDLE service, char *service_name) {
\r
231 SC_HANDLE services = 0;
\r
234 services = open_service_manager();
\r
235 if (! services) return;
\r
237 service = OpenService(services, service_name, SC_MANAGER_ALL_ACCESS);
\r
238 if (! service) return;
\r
241 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
242 ZeroMemory(&flag, sizeof(flag));
\r
243 flag.fFailureActionsOnNonCrashFailures = true;
\r
245 /* This functionality was added in Vista so the call may fail */
\r
246 if (! ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
247 unsigned long error = GetLastError();
\r
248 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
249 if (error != ERROR_INVALID_LEVEL) {
\r
250 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CHANGESERVICECONFIG2_FAILED, service_name, error_string(error), 0);
\r
255 CloseServiceHandle(service);
\r
256 CloseServiceHandle(services);
\r
260 int monitor_service() {
\r
261 /* Set service status to started */
\r
262 int ret = start_service();
\r
265 _snprintf(code, sizeof(code), "%d", ret);
\r
266 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, exe, service_name, ret, 0);
\r
269 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, exe, flags, service_name, dir, 0);
\r
271 /* Monitor service service */
\r
272 if (! RegisterWaitForSingleObject(&wait_handle, process_handle, end_service, (void *) pid, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
273 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service_name, exe, error_string(GetLastError()), 0);
\r
279 char *service_control_text(unsigned long control) {
\r
281 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
282 case 0: return "START";
\r
283 case SERVICE_CONTROL_STOP: return "STOP";
\r
284 case SERVICE_CONTROL_SHUTDOWN: return "SHUTDOWN";
\r
285 case SERVICE_CONTROL_PAUSE: return "PAUSE";
\r
286 case SERVICE_CONTROL_CONTINUE: return "CONTINUE";
\r
287 case SERVICE_CONTROL_INTERROGATE: return "INTERROGATE";
\r
292 void log_service_control(char *service_name, unsigned long control, bool handled) {
\r
293 char *text = service_control_text(control);
\r
294 unsigned long event;
\r
297 /* "0x" + 8 x hex + NULL */
\r
298 text = (char *) HeapAlloc(GetProcessHeap(), 0, 11);
\r
300 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control", 0);
\r
303 if (_snprintf(text, 11, "0x%08x", control) < 0) {
\r
304 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control", 0);
\r
305 HeapFree(GetProcessHeap(), 0, text);
\r
309 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
311 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
312 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
314 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
316 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
317 HeapFree(GetProcessHeap(), 0, text);
\r
321 /* Service control handler */
\r
322 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
324 case SERVICE_CONTROL_INTERROGATE:
\r
325 /* We always keep the service status up-to-date so this is a no-op. */
\r
328 case SERVICE_CONTROL_SHUTDOWN:
\r
329 case SERVICE_CONTROL_STOP:
\r
330 log_service_control(service_name, control, true);
\r
331 stop_service(0, true, true);
\r
334 case SERVICE_CONTROL_CONTINUE:
\r
335 log_service_control(service_name, control, true);
\r
336 if (! throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
338 ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));
\r
339 SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);
\r
340 service_status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
341 service_status.dwWaitHint = throttle_milliseconds() + NSSM_WAITHINT_MARGIN;
\r
342 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service_name, 0);
\r
343 SetServiceStatus(service_handle, &service_status);
\r
346 case SERVICE_CONTROL_PAUSE:
\r
348 We don't accept pause messages but it isn't possible to register
\r
349 only for continue messages so we have to handle this case.
\r
351 log_service_control(service_name, control, false);
\r
352 return ERROR_CALL_NOT_IMPLEMENTED;
\r
355 /* Unknown control */
\r
356 log_service_control(service_name, control, false);
\r
357 return ERROR_CALL_NOT_IMPLEMENTED;
\r
360 /* Start the service */
\r
361 int start_service() {
\r
363 allow_restart = true;
\r
365 if (process_handle) return 0;
\r
367 /* Allocate a STARTUPINFO structure for a new process */
\r
369 ZeroMemory(&si, sizeof(si));
\r
370 si.cb = sizeof(si);
\r
372 /* Allocate a PROCESSINFO structure for the process */
\r
373 PROCESS_INFORMATION pi;
\r
374 ZeroMemory(&pi, sizeof(pi));
\r
376 /* Get startup parameters */
\r
378 int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay, &stop_method, &si);
\r
380 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0);
\r
381 return stop_service(2, true, true);
\r
384 /* Launch executable with arguments */
\r
385 char cmd[CMD_LENGTH];
\r
386 if (_snprintf(cmd, sizeof(cmd), "\"%s\" %s", exe, flags) < 0) {
\r
387 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);
\r
388 close_output_handles(&si);
\r
389 return stop_service(2, true, true);
\r
392 throttle_restart();
\r
394 bool inherit_handles = (si.dwFlags & STARTF_USESTDHANDLES);
\r
395 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, 0, env, dir, &si, &pi)) {
\r
396 unsigned long error = GetLastError();
\r
397 if (error == ERROR_INVALID_PARAMETER && env) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED_INVALID_ENVIRONMENT, service_name, exe, NSSM_REG_ENV, 0);
\r
398 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, error_string(error), 0);
\r
399 close_output_handles(&si);
\r
400 return stop_service(3, true, true);
\r
402 process_handle = pi.hProcess;
\r
403 pid = pi.dwProcessId;
\r
405 if (get_process_creation_time(process_handle, &creation_time)) ZeroMemory(&creation_time, sizeof(creation_time));
\r
407 close_output_handles(&si);
\r
409 /* Wait for a clean startup. */
\r
410 if (WaitForSingleObject(process_handle, throttle_delay) == WAIT_TIMEOUT) throttle = 0;
\r
412 /* Signal successful start */
\r
413 service_status.dwCurrentState = SERVICE_RUNNING;
\r
414 SetServiceStatus(service_handle, &service_status);
\r
419 /* Stop the service */
\r
420 int stop_service(unsigned long exitcode, bool graceful, bool default_action) {
\r
421 allow_restart = false;
\r
422 if (wait_handle) UnregisterWait(wait_handle);
\r
424 if (default_action && ! exitcode && ! graceful) {
\r
425 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
429 /* Signal we are stopping */
\r
431 service_status.dwCurrentState = SERVICE_STOP_PENDING;
\r
432 service_status.dwWaitHint = NSSM_KILL_WINDOW_GRACE_PERIOD + NSSM_KILL_THREADS_GRACE_PERIOD + NSSM_WAITHINT_MARGIN;
\r
433 SetServiceStatus(service_handle, &service_status);
\r
436 /* Nothing to do if service isn't running */
\r
438 /* Shut down service */
\r
439 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);
\r
440 kill_process(service_name, stop_method, process_handle, pid, 0);
\r
442 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);
\r
444 end_service((void *) pid, true);
\r
446 /* Signal we stopped */
\r
448 service_status.dwCurrentState = SERVICE_STOPPED;
\r
450 service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
451 service_status.dwServiceSpecificExitCode = exitcode;
\r
454 service_status.dwWin32ExitCode = NO_ERROR;
\r
455 service_status.dwServiceSpecificExitCode = 0;
\r
457 SetServiceStatus(service_handle, &service_status);
\r
463 /* Callback function triggered when the server exits */
\r
464 void CALLBACK end_service(void *arg, unsigned char why) {
\r
465 if (stopping) return;
\r
469 pid = (unsigned long) arg;
\r
471 /* Check exit code */
\r
472 unsigned long exitcode = 0;
\r
474 FILETIME exit_time;
\r
475 GetExitCodeProcess(process_handle, &exitcode);
\r
476 if (exitcode == STILL_ACTIVE || get_process_exit_time(process_handle, &exit_time)) GetSystemTimeAsFileTime(&exit_time);
\r
477 CloseHandle(process_handle);
\r
480 Log that the service ended BEFORE logging about killing the process
\r
481 tree. See below for the possible values of the why argument.
\r
484 _snprintf(code, sizeof(code), "%d", exitcode);
\r
485 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);
\r
489 kill_process_tree(service_name, stop_method, pid, exitcode, pid, &creation_time, &exit_time);
\r
492 The why argument is true if our wait timed out or false otherwise.
\r
493 Our wait is infinite so why will never be true when called by the system.
\r
494 If it is indeed true, assume we were called from stop_service() because
\r
495 this is a controlled shutdown, and don't take any restart action.
\r
498 if (! allow_restart) return;
\r
500 /* What action should we take? */
\r
501 int action = NSSM_EXIT_RESTART;
\r
502 unsigned char action_string[ACTION_LEN];
\r
503 bool default_action;
\r
504 if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {
\r
505 for (int i = 0; exit_action_strings[i]; i++) {
\r
506 if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
513 process_handle = 0;
\r
516 /* Try to restart the service or return failure code to service manager */
\r
517 case NSSM_EXIT_RESTART:
\r
518 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);
\r
519 while (monitor_service()) {
\r
520 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);
\r
525 /* Do nothing, just like srvany would */
\r
526 case NSSM_EXIT_IGNORE:
\r
527 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);
\r
531 /* Tell the service manager we are finished */
\r
532 case NSSM_EXIT_REALLY:
\r
533 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);
\r
534 stop_service(exitcode, true, default_action);
\r
537 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
538 case NSSM_EXIT_UNCLEAN:
\r
539 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);
\r
540 exit(stop_service(exitcode, false, default_action));
\r
545 void throttle_restart() {
\r
546 /* This can't be a restart if the service is already running. */
\r
547 if (! throttle++) return;
\r
549 int ms = throttle_milliseconds();
\r
551 if (throttle > 7) throttle = 8;
\r
553 char threshold[8], milliseconds[8];
\r
554 _snprintf(threshold, sizeof(threshold), "%d", throttle_delay);
\r
555 _snprintf(milliseconds, sizeof(milliseconds), "%d", ms);
\r
556 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);
\r
558 if (throttle_timer) {
\r
559 ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));
\r
560 throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
561 SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);
\r
564 service_status.dwCurrentState = SERVICE_PAUSED;
\r
565 SetServiceStatus(service_handle, &service_status);
\r
567 if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);
\r