Browse Source

Ensure systems recovery actions can happen.

In Windows versions earlier than Vista the service manager would only
consider a service failed (and hence eligible for recovery action) if
the service exited without setting its state to SERVICE_STOPPED, even if
it signalled an error exit code.
In Vista and later the service manager can be configured to treat a
graceful shutdown with error code as a failure but this is not the
default behaviour.

Try to configure the service manager to use the new behaviour when
starting the service so users who set AppExit to Exit can use recovery
actions as expected.

Also recognise the new AppExit option Suicide for use on pre-Vista
systems.  When AppExit is Suicide don't stop the service but exit
inelegantly, which should be seen as a failure.
Iain Patterson 15 years ago
parent
commit
a7cd825675
3 changed files with 57 additions and 19 deletions
  1. 8 0
      messages.mc
  2. 47 18
      service.cpp
  3. 2 1
      service.h

+ 8 - 0
messages.mc

@@ -147,3 +147,11 @@ Language = English
 Failed to write registry value %1:
 %2
 .
+
+MessageId = +1
+SymbolicName = NSSM_EVENT_EXIT_UNCLEAN
+Severity = Informational
+Language = English
+Service %1 action for exit code %2 is %3.
+Exiting.
+.

+ 47 - 18
service.cpp

@@ -9,8 +9,8 @@ char exe[EXE_LENGTH];
 char flags[CMD_LENGTH];
 char dir[MAX_PATH];
 
-static enum { NSSM_EXIT_RESTART, NSSM_EXIT_IGNORE, NSSM_EXIT_REALLY } exit_actions;
-static const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", 0 };
+static enum { NSSM_EXIT_RESTART, NSSM_EXIT_IGNORE, NSSM_EXIT_REALLY, NSSM_EXIT_UNCLEAN } exit_actions;
+static const char *exit_action_strings[] = { "Restart", "Ignore", "Exit", "Suicide", 0 };
 
 /* Connect to the service manager */
 SC_HANDLE open_service_manager() {
@@ -173,9 +173,28 @@ void WINAPI service_main(unsigned long argc, char **argv) {
   /* Try to create the exit action parameters; we don't care if it fails */
   create_exit_action(argv[0], exit_action_strings[0]);
 
+  set_service_recovery(service_name);
+
   monitor_service();
 }
 
+/* Make sure service recovery actions are taken where necessary */
+void set_service_recovery(char *service_name) {
+  SC_HANDLE services = open_service_manager();
+  if (! services) return;
+
+  SC_HANDLE service = OpenService(services, service_name, SC_MANAGER_ALL_ACCESS);
+  if (! service) return;
+  return;
+
+  SERVICE_FAILURE_ACTIONS_FLAG flag;
+  ZeroMemory(&flag, sizeof(flag));
+  flag.fFailureActionsOnNonCrashFailures = true;
+
+  /* This functionality was added in Vista so the call may fail */
+  ChangeServiceConfig2(service, SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, &flag);
+}
+
 int monitor_service() {
   /* Set service status to started */
   int ret = start_service();
@@ -200,7 +219,7 @@ unsigned long WINAPI service_control_handler(unsigned long control, unsigned lon
   switch (control) {
     case SERVICE_CONTROL_SHUTDOWN:
     case SERVICE_CONTROL_STOP:
-      stop_service(0);
+      stop_service(0, true);
       return NO_ERROR;
   }
 
@@ -225,11 +244,11 @@ int start_service() {
   char cmd[CMD_LENGTH];
   if (_snprintf(cmd, sizeof(cmd), "%s %s", exe, flags) < 0) {
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_OUT_OF_MEMORY, "command line", "start_service", 0);
-    return stop_service(2);
+    return stop_service(2, true);
   }
   if (! CreateProcess(0, cmd, 0, 0, 0, 0, 0, dir, &si, &pi)) {
     log_event(EVENTLOG_ERROR_TYPE, NSSM_EVENT_CREATEPROCESS_FAILED, service_name, exe, GetLastError(), 0);
-    return stop_service(3);
+    return stop_service(3, true);
   }
   pid = pi.hProcess;
 
@@ -241,10 +260,12 @@ int start_service() {
 }
 
 /* Stop the service */
-int stop_service(unsigned long exitcode) {
+int stop_service(unsigned long exitcode, bool graceful) {
   /* Signal we are stopping */
-  service_status.dwCurrentState = SERVICE_STOP_PENDING;
-  SetServiceStatus(service_handle, &service_status);
+  if (graceful) {
+    service_status.dwCurrentState = SERVICE_STOP_PENDING;
+    SetServiceStatus(service_handle, &service_status);
+  }
 
   /* Nothing to do if server isn't running */
   if (pid) {
@@ -256,16 +277,18 @@ int stop_service(unsigned long exitcode) {
   else log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_PROCESS_ALREADY_STOPPED, service_name, exe, 0);
 
   /* Signal we stopped */
-  service_status.dwCurrentState = SERVICE_STOPPED;
-  if (exitcode) {
-    service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
-    service_status.dwServiceSpecificExitCode = exitcode;
-  }
-  else {
-    service_status.dwWin32ExitCode = NO_ERROR;
-    service_status.dwServiceSpecificExitCode = 0;
+  if (graceful) {
+    service_status.dwCurrentState = SERVICE_STOPPED;
+    if (exitcode) {
+      service_status.dwWin32ExitCode = ERROR_SERVICE_SPECIFIC_ERROR;
+      service_status.dwServiceSpecificExitCode = exitcode;
+    }
+    else {
+      service_status.dwWin32ExitCode = NO_ERROR;
+      service_status.dwServiceSpecificExitCode = 0;
+    }
+    SetServiceStatus(service_handle, &service_status);
   }
-  SetServiceStatus(service_handle, &service_status);
 
   return exitcode;
 }
@@ -312,7 +335,13 @@ void CALLBACK end_service(void *arg, unsigned char why) {
     /* Tell the service manager we are finished */
     case NSSM_EXIT_REALLY:
       log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_REALLY, service_name, code, exit_action_strings[action], 0);
-      stop_service(ret);
+      stop_service(ret, true);
+    break;
+
+    /* Fake a crash so pre-Vista service managers will run recovery actions. */
+    case NSSM_EXIT_UNCLEAN:
+      log_event(EVENTLOG_INFORMATION_TYPE, NSSM_EVENT_EXIT_UNCLEAN, service_name, code, exit_action_strings[action], 0);
+      exit(stop_service(ret, false));
     break;
   }
 }

+ 2 - 1
service.h

@@ -11,9 +11,10 @@ int pre_install_service(int, char **);
 int pre_remove_service(int, char **);
 int install_service(char *, char *, char *);
 int remove_service(char *);
+void set_service_recovery(char *);
 int monitor_service();
 int start_service();
-int stop_service(unsigned long);
+int stop_service(unsigned long, bool);
 void CALLBACK end_service(void *, unsigned char);
 
 #endif