Ensure systems recovery actions can happen.
authorIain Patterson <me@iain.cx>
Thu, 23 Sep 2010 14:27:14 +0000 (15:27 +0100)
committerIain Patterson <me@iain.cx>
Thu, 23 Sep 2010 15:32:57 +0000 (16:32 +0100)
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.

messages.mc
service.cpp
service.h

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