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
14 unsigned long throttle_delay;
\r
15 HANDLE throttle_timer;
\r
16 LARGE_INTEGER throttle_duetime;
\r
17 FILETIME creation_time;
\r
19 static enum { NSSM_EXIT_RESTART, NSSM_EXIT_IGNORE, NSSM_EXIT_REALLY, NSSM_EXIT_UNCLEAN } exit_actions;
\r
20 static const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", "Suicide", 0 };
\r
22 static unsigned long throttle;
\r
24 static inline int throttle_milliseconds() {
\r
25 /* pow() operates on doubles. */
\r
26 int ret = 1; for (unsigned long i = 1; i < throttle; i++) ret *= 2;
\r
30 /* Connect to the service manager */
\r
31 SC_HANDLE open_service_manager() {
\r
32 SC_HANDLE ret = OpenSCManager(0, SERVICES_ACTIVE_DATABASE, SC_MANAGER_ALL_ACCESS);
\r
34 if (is_admin) log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OPENSCMANAGER_FAILED, 0);
\r
41 /* About to install the service */
\r
42 int pre_install_service(int argc, char **argv) {
\r
43 /* Show the dialogue box if we didn't give the service name and path */
\r
44 if (argc < 2) return nssm_gui(IDD_INSTALL, argv[0]);
\r
46 /* Arguments are optional */
\r
48 size_t flagslen = 0;
\r
51 for (i = 2; i < argc; i++) flagslen += strlen(argv[i]) + 1;
\r
52 if (! flagslen) flagslen = 1;
\r
54 flags = (char *) HeapAlloc(GetProcessHeap(), 0, flagslen);
\r
56 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "flags", "pre_install_service()", 0);
\r
59 ZeroMemory(flags, flagslen);
\r
62 This probably isn't UTF8-safe and should use std::string or something
\r
63 but it's been broken for the best part of a decade and due for a rewrite
\r
64 anyway so it'll do as a quick-'n'-dirty fix. Note that we don't free
\r
65 the flags buffer but as the program exits that isn't a big problem.
\r
67 for (i = 2; i < argc; i++) {
\r
68 size_t len = strlen(argv[i]);
\r
69 memmove(flags + s, argv[i], len);
\r
71 if (i < argc - 1) flags[s++] = ' ';
\r
74 return install_service(argv[0], argv[1], flags);
\r
77 /* About to remove the service */
\r
78 int pre_remove_service(int argc, char **argv) {
\r
79 /* Show dialogue box if we didn't pass service name and "confirm" */
\r
80 if (argc < 2) return nssm_gui(IDD_REMOVE, argv[0]);
\r
81 if (str_equiv(argv[1], "confirm")) return remove_service(argv[0]);
\r
82 print_message(stderr, NSSM_MESSAGE_PRE_REMOVE_SERVICE);
\r
86 /* Install the service */
\r
87 int install_service(char *name, char *exe, char *flags) {
\r
88 /* Open service manager */
\r
89 SC_HANDLE services = open_service_manager();
\r
91 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
95 /* Get path of this program */
\r
96 char path[MAX_PATH];
\r
97 GetModuleFileName(0, path, MAX_PATH);
\r
99 /* Construct command */
\r
100 char command[CMD_LENGTH];
\r
101 size_t pathlen = strlen(path);
\r
102 if (pathlen + 1 >= VALUE_LENGTH) {
\r
103 print_message(stderr, NSSM_MESSAGE_PATH_TOO_LONG, NSSM);
\r
106 if (_snprintf(command, sizeof(command), "\"%s\"", path) < 0) {
\r
107 print_message(stderr, NSSM_MESSAGE_OUT_OF_MEMORY_FOR_IMAGEPATH);
\r
111 /* Work out directory name */
\r
112 size_t len = strlen(exe);
\r
114 for (i = len; i && exe[i] != '\\' && exe[i] != '/'; i--);
\r
115 char dir[MAX_PATH];
\r
116 memmove(dir, exe, i);
\r
119 /* Create the service */
\r
120 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
122 print_message(stderr, NSSM_MESSAGE_CREATESERVICE_FAILED);
\r
123 CloseServiceHandle(services);
\r
127 /* Now we need to put the parameters into the registry */
\r
128 if (create_parameters(name, exe, flags, dir)) {
\r
129 print_message(stderr, NSSM_MESSAGE_CREATE_PARAMETERS_FAILED);
\r
130 DeleteService(service);
\r
131 CloseServiceHandle(services);
\r
135 set_service_recovery(service, name);
\r
138 CloseServiceHandle(service);
\r
139 CloseServiceHandle(services);
\r
141 print_message(stdout, NSSM_MESSAGE_SERVICE_INSTALLED, name);
\r
145 /* Remove the service */
\r
146 int remove_service(char *name) {
\r
147 /* Open service manager */
\r
148 SC_HANDLE services = open_service_manager();
\r
150 print_message(stderr, NSSM_MESSAGE_OPEN_SERVICE_MANAGER_FAILED);
\r
154 /* Try to open the service */
\r
155 SC_HANDLE service = OpenService(services, name, SC_MANAGER_ALL_ACCESS);
\r
157 print_message(stderr, NSSM_MESSAGE_OPENSERVICE_FAILED);
\r
158 CloseServiceHandle(services);
\r
162 /* Try to delete the service */
\r
163 if (! DeleteService(service)) {
\r
164 print_message(stderr, NSSM_MESSAGE_DELETESERVICE_FAILED);
\r
165 CloseServiceHandle(service);
\r
166 CloseServiceHandle(services);
\r
171 CloseServiceHandle(service);
\r
172 CloseServiceHandle(services);
\r
174 print_message(stdout, NSSM_MESSAGE_SERVICE_REMOVED, name);
\r
178 /* Service initialisation */
\r
179 void WINAPI service_main(unsigned long argc, char **argv) {
\r
180 if (_snprintf(service_name, sizeof(service_name), "%s", argv[0]) < 0) {
\r
181 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "service_name", "service_main()", 0);
\r
185 /* Initialise status */
\r
186 ZeroMemory(&service_status, sizeof(service_status));
\r
187 service_status.dwServiceType = SERVICE_WIN32_OWN_PROCESS | SERVICE_INTERACTIVE_PROCESS;
\r
188 service_status.dwControlsAccepted = SERVICE_ACCEPT_SHUTDOWN | SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_PAUSE_CONTINUE;
\r
189 service_status.dwWin32ExitCode = NO_ERROR;
\r
190 service_status.dwServiceSpecificExitCode = 0;
\r
191 service_status.dwCheckPoint = 0;
\r
192 service_status.dwWaitHint = NSSM_WAITHINT_MARGIN;
\r
194 /* Signal we AREN'T running the server */
\r
195 process_handle = 0;
\r
198 /* Register control handler */
\r
199 service_handle = RegisterServiceCtrlHandlerEx(NSSM, service_control_handler, 0);
\r
200 if (! service_handle) {
\r
201 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_REGISTERSERVICECTRLHANDER_FAILED, error_string(GetLastError()), 0);
\r
205 log_service_control(service_name, 0, true);
\r
207 service_status.dwCurrentState = SERVICE_START_PENDING;
\r
208 service_status.dwWaitHint = throttle_delay + NSSM_WAITHINT_MARGIN;
\r
209 SetServiceStatus(service_handle, &service_status);
\r
212 /* Try to create the exit action parameters; we don't care if it fails */
\r
213 create_exit_action(argv[0], exit_action_strings[0]);
\r
215 set_service_recovery(0, service_name);
\r
218 /* Used for signalling a resume if the service pauses when throttled. */
\r
219 throttle_timer = CreateWaitableTimer(0, 1, 0);
\r
220 if (! throttle_timer) {
\r
221 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_CREATEWAITABLETIMER_FAILED, service_name, error_string(GetLastError()), 0);
\r
227 /* Make sure service recovery actions are taken where necessary */
\r
228 void set_service_recovery(SC_HANDLE service, char *service_name) {
\r
229 SC_HANDLE services = 0;
\r
232 services = open_service_manager();
\r
233 if (! services) return;
\r
235 service = OpenService(services, service_name, SC_MANAGER_ALL_ACCESS);
\r
236 if (! service) return;
\r
239 SERVICE_FAILURE_ACTIONS_FLAG flag;
\r
240 ZeroMemory(&flag, sizeof(flag));
\r
241 flag.fFailureActionsOnNonCrashFailures = true;
\r
243 /* This functionality was added in Vista so the call may fail */
\r
244 if (! ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag)) {
\r
245 unsigned long error = GetLastError();
\r
246 /* Pre-Vista we expect to fail with ERROR_INVALID_LEVEL */
\r
247 if (error != ERROR_INVALID_LEVEL) {
\r
248 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CHANGESERVICECONFIG2_FAILED, service_name, error_string(error), 0);
\r
253 CloseServiceHandle(service);
\r
254 CloseServiceHandle(services);
\r
258 int monitor_service() {
\r
259 /* Set service status to started */
\r
260 int ret = start_service();
\r
263 _snprintf(code, sizeof(code), "%d", ret);
\r
264 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_START_SERVICE_FAILED, exe, service_name, ret, 0);
\r
267 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_STARTED_SERVICE, exe, flags, service_name, dir, 0);
\r
269 /* Monitor service service */
\r
270 if (! RegisterWaitForSingleObject(&wait_handle, process_handle, end_service, (void *) pid, INFINITE, WT_EXECUTEONLYONCE | WT_EXECUTELONGFUNCTION)) {
\r
271 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_REGISTERWAITFORSINGLEOBJECT_FAILED, service_name, exe, error_string(GetLastError()), 0);
\r
277 char *service_control_text(unsigned long control) {
\r
279 /* HACK: there is no SERVICE_CONTROL_START constant */
\r
280 case 0: return "START";
\r
281 case SERVICE_CONTROL_STOP: return "STOP";
\r
282 case SERVICE_CONTROL_SHUTDOWN: return "SHUTDOWN";
\r
283 case SERVICE_CONTROL_PAUSE: return "PAUSE";
\r
284 case SERVICE_CONTROL_CONTINUE: return "CONTINUE";
\r
285 case SERVICE_CONTROL_INTERROGATE: return "INTERROGATE";
\r
290 void log_service_control(char *service_name, unsigned long control, bool handled) {
\r
291 char *text = service_control_text(control);
\r
292 unsigned long event;
\r
295 /* "0x" + 8 x hex + NULL */
\r
296 text = (char *) HeapAlloc(GetProcessHeap(), 0, 11);
\r
298 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control", 0);
\r
301 if (_snprintf(text, 11, "0x%08x", control) < 0) {
\r
302 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "control code", "log_service_control", 0);
\r
303 HeapFree(GetProcessHeap(), 0, text);
\r
307 event = NSSM_EVENT_SERVICE_CONTROL_UNKNOWN;
\r
309 else if (handled) event = NSSM_EVENT_SERVICE_CONTROL_HANDLED;
\r
310 else event = NSSM_EVENT_SERVICE_CONTROL_NOT_HANDLED;
\r
312 log_event(EVENTLOG_INFORMATION_TYPE, event, service_name, text, 0);
\r
314 if (event == NSSM_EVENT_SERVICE_CONTROL_UNKNOWN) {
\r
315 HeapFree(GetProcessHeap(), 0, text);
\r
319 /* Service control handler */
\r
320 unsigned long WINAPI service_control_handler(unsigned long control, unsigned long event, void *data, void *context) {
\r
322 case SERVICE_CONTROL_INTERROGATE:
\r
323 /* We always keep the service status up-to-date so this is a no-op. */
\r
326 case SERVICE_CONTROL_SHUTDOWN:
\r
327 case SERVICE_CONTROL_STOP:
\r
328 log_service_control(service_name, control, true);
\r
329 stop_service(0, true, true);
\r
332 case SERVICE_CONTROL_CONTINUE:
\r
333 log_service_control(service_name, control, true);
\r
334 if (! throttle_timer) return ERROR_CALL_NOT_IMPLEMENTED;
\r
336 ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));
\r
337 SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);
\r
338 service_status.dwCurrentState = SERVICE_CONTINUE_PENDING;
\r
339 service_status.dwWaitHint = throttle_milliseconds() + NSSM_WAITHINT_MARGIN;
\r
340 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_RESET_THROTTLE, service_name, 0);
\r
341 SetServiceStatus(service_handle, &service_status);
\r
344 case SERVICE_CONTROL_PAUSE:
\r
346 We don't accept pause messages but it isn't possible to register
\r
347 only for continue messages so we have to handle this case.
\r
349 log_service_control(service_name, control, false);
\r
350 return ERROR_CALL_NOT_IMPLEMENTED;
\r
353 /* Unknown control */
\r
354 log_service_control(service_name, control, false);
\r
355 return ERROR_CALL_NOT_IMPLEMENTED;
\r
358 /* Start the service */
\r
359 int start_service() {
\r
362 if (process_handle) return 0;
\r
364 /* Allocate a STARTUPINFO structure for a new process */
\r
366 ZeroMemory(&si, sizeof(si));
\r
367 si.cb = sizeof(si);
\r
369 /* Allocate a PROCESSINFO structure for the process */
\r
370 PROCESS_INFORMATION pi;
\r
371 ZeroMemory(&pi, sizeof(pi));
\r
373 /* Get startup parameters */
\r
375 int ret = get_parameters(service_name, exe, sizeof(exe), flags, sizeof(flags), dir, sizeof(dir), &env, &throttle_delay, &si);
\r
377 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_GET_PARAMETERS_FAILED, service_name, 0);
\r
378 return stop_service(2, true, true);
\r
381 /* Launch executable with arguments */
\r
382 char cmd[CMD_LENGTH];
\r
383 if (_snprintf(cmd, sizeof(cmd), "\"%s\" %s", exe, flags) < 0) {
\r
384 log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);
\r
385 close_output_handles(&si);
\r
386 return stop_service(2, true, true);
\r
389 throttle_restart();
\r
391 bool inherit_handles = (si.dwFlags & STARTF_USESTDHANDLES);
\r
392 if (! CreateProcess(0, cmd, 0, 0, inherit_handles, 0, env, dir, &si, &pi)) {
\r
393 unsigned long error = GetLastError();
\r
394 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
395 else log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, error_string(error), 0);
\r
396 close_output_handles(&si);
\r
397 return stop_service(3, true, true);
\r
399 process_handle = pi.hProcess;
\r
400 pid = pi.dwProcessId;
\r
402 if (get_process_creation_time(process_handle, &creation_time)) ZeroMemory(&creation_time, sizeof(creation_time));
\r
404 close_output_handles(&si);
\r
406 /* Signal successful start */
\r
407 service_status.dwCurrentState = SERVICE_RUNNING;
\r
408 SetServiceStatus(service_handle, &service_status);
\r
410 /* Wait for a clean startup. */
\r
411 if (WaitForSingleObject(process_handle, throttle_delay) == WAIT_TIMEOUT) throttle = 0;
\r
416 /* Stop the service */
\r
417 int stop_service(unsigned long exitcode, bool graceful, bool default_action) {
\r
418 if (default_action && ! exitcode && ! graceful) {
\r
419 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
423 /* Signal we are stopping */
\r
425 service_status.dwCurrentState = SERVICE_STOP_PENDING;
\r
426 service_status.dwWaitHint = NSSM_KILL_WINDOW_GRACE_PERIOD + NSSM_KILL_THREADS_GRACE_PERIOD + NSSM_WAITHINT_MARGIN;
\r
427 SetServiceStatus(service_handle, &service_status);
\r
430 /* Nothing to do if service isn't running */
\r
432 /* Shut down service */
\r
433 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_TERMINATEPROCESS, service_name, exe, 0);
\r
434 kill_process(service_name, process_handle, pid, 0);
\r
436 else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);
\r
438 end_service((void *) pid, true);
\r
440 /* Signal we stopped */
\r
442 service_status.dwCurrentState = SERVICE_STOPPED;
\r
444 service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
\r
445 service_status.dwServiceSpecificExitCode = exitcode;
\r
448 service_status.dwWin32ExitCode = NO_ERROR;
\r
449 service_status.dwServiceSpecificExitCode = 0;
\r
451 SetServiceStatus(service_handle, &service_status);
\r
457 /* Callback function triggered when the server exits */
\r
458 void CALLBACK end_service(void *arg, unsigned char why) {
\r
459 if (stopping) return;
\r
463 pid = (unsigned long) arg;
\r
465 /* Check exit code */
\r
466 unsigned long exitcode = 0;
\r
468 FILETIME exit_time;
\r
469 GetExitCodeProcess(process_handle, &exitcode);
\r
470 if (exitcode == STILL_ACTIVE || get_process_exit_time(process_handle, &exit_time)) GetSystemTimeAsFileTime(&exit_time);
\r
471 CloseHandle(process_handle);
\r
474 Log that the service ended BEFORE logging about killing the process
\r
475 tree. See below for the possible values of the why argument.
\r
478 _snprintf(code, sizeof(code), "%d", exitcode);
\r
479 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_ENDED_SERVICE, exe, service_name, code, 0);
\r
483 kill_process_tree(service_name, pid, exitcode, pid, &creation_time, &exit_time);
\r
486 The why argument is true if our wait timed out or false otherwise.
\r
487 Our wait is infinite so why will never be true when called by the system.
\r
488 If it is indeed true, assume we were called from stop_service() because
\r
489 this is a controlled shutdown, and don't take any restart action.
\r
493 /* What action should we take? */
\r
494 int action = NSSM_EXIT_RESTART;
\r
495 unsigned char action_string[ACTION_LEN];
\r
496 bool default_action;
\r
497 if (! get_exit_action(service_name, &exitcode, action_string, &default_action)) {
\r
498 for (int i = 0; exit_action_strings[i]; i++) {
\r
499 if (! _strnicmp((const char *) action_string, exit_action_strings[i], ACTION_LEN)) {
\r
506 process_handle = 0;
\r
509 /* Try to restart the service or return failure code to service manager */
\r
510 case NSSM_EXIT_RESTART:
\r
511 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_RESTART, service_name, code, exit_action_strings[action], exe, 0);
\r
512 while (monitor_service()) {
\r
513 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_RESTART_SERVICE_FAILED, exe, service_name, 0);
\r
518 /* Do nothing, just like srvany would */
\r
519 case NSSM_EXIT_IGNORE:
\r
520 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_IGNORE, service_name, code, exit_action_strings[action], exe, 0);
\r
524 /* Tell the service manager we are finished */
\r
525 case NSSM_EXIT_REALLY:
\r
526 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);
\r
527 stop_service(exitcode, true, default_action);
\r
530 /* Fake a crash so pre-Vista service managers will run recovery actions. */
\r
531 case NSSM_EXIT_UNCLEAN:
\r
532 log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);
\r
533 exit(stop_service(exitcode, false, default_action));
\r
538 void throttle_restart() {
\r
539 /* This can't be a restart if the service is already running. */
\r
540 if (! throttle++) return;
\r
542 int ms = throttle_milliseconds();
\r
544 if (throttle > 7) throttle = 8;
\r
546 char threshold[8], milliseconds[8];
\r
547 _snprintf(threshold, sizeof(threshold), "%d", throttle_delay);
\r
548 _snprintf(milliseconds, sizeof(milliseconds), "%d", ms);
\r
549 log_event(EVENTLOG_WARNING_TYPE, NSSM_EVENT_THROTTLED, service_name, threshold, milliseconds, 0);
\r
551 if (throttle_timer) {
\r
552 ZeroMemory(&throttle_duetime, sizeof(throttle_duetime));
\r
553 throttle_duetime.QuadPart = 0 - (ms * 10000LL);
\r
554 SetWaitableTimer(throttle_timer, &throttle_duetime, 0, 0, 0, 0);
\r
557 service_status.dwCurrentState = SERVICE_PAUSED;
\r
558 SetServiceStatus(service_handle, &service_status);
\r
560 if (throttle_timer) WaitForSingleObject(throttle_timer, INFINITE);
\r