diff --git a/windows/service.go b/windows/service.go index c964b684..c44a1b96 100644 --- a/windows/service.go +++ b/windows/service.go @@ -218,6 +218,10 @@ type SERVICE_FAILURE_ACTIONS struct { Actions *SC_ACTION } +type SERVICE_FAILURE_ACTIONS_FLAG struct { + FailureActionsOnNonCrashFailures int32 +} + type SC_ACTION struct { Type uint32 Delay uint32 diff --git a/windows/svc/mgr/mgr_test.go b/windows/svc/mgr/mgr_test.go index 10d23107..285dc10d 100644 --- a/windows/svc/mgr/mgr_test.go +++ b/windows/svc/mgr/mgr_test.go @@ -209,6 +209,57 @@ func testRecoveryCommand(t *testing.T, s *mgr.Service, should string) { } } +func testRecoveryActionsOnNonCrashFailures(t *testing.T, s *mgr.Service, should bool) { + err := s.SetRecoveryActionsOnNonCrashFailures(should) + if err != nil { + t.Fatalf("SetRecoveryActionsOnNonCrashFailures failed: %v", err) + } + is, err := s.RecoveryActionsOnNonCrashFailures() + if err != nil { + t.Fatalf("RecoveryActionsOnNonCrashFailures failed: %v", err) + } + if should != is { + t.Errorf("RecoveryActionsOnNonCrashFailures mismatch: flag is %v, but should have %v", is, should) + } +} + +func testMultipleRecoverySettings(t *testing.T, s *mgr.Service, rebootMsgShould, recoveryCmdShould string, actionsFlagShould bool) { + err := s.SetRebootMessage(rebootMsgShould) + if err != nil { + t.Fatalf("SetRebootMessage failed: %v", err) + } + err = s.SetRecoveryActionsOnNonCrashFailures(actionsFlagShould) + if err != nil { + t.Fatalf("SetRecoveryActionsOnNonCrashFailures failed: %v", err) + } + err = s.SetRecoveryCommand(recoveryCmdShould) + if err != nil { + t.Fatalf("SetRecoveryCommand failed: %v", err) + } + + rebootMsgIs, err := s.RebootMessage() + if err != nil { + t.Fatalf("RebootMessage failed: %v", err) + } + if rebootMsgShould != rebootMsgIs { + t.Errorf("reboot message mismatch: message is %q, but should have %q", rebootMsgIs, rebootMsgShould) + } + recoveryCommandIs, err := s.RecoveryCommand() + if err != nil { + t.Fatalf("RecoveryCommand failed: %v", err) + } + if recoveryCmdShould != recoveryCommandIs { + t.Errorf("recovery command mismatch: command is %q, but should have %q", recoveryCommandIs, recoveryCmdShould) + } + actionsFlagIs, err := s.RecoveryActionsOnNonCrashFailures() + if err != nil { + t.Fatalf("RecoveryActionsOnNonCrashFailures failed: %v", err) + } + if actionsFlagShould != actionsFlagIs { + t.Errorf("RecoveryActionsOnNonCrashFailures mismatch: flag is %v, but should have %v", actionsFlagIs, actionsFlagShould) + } +} + func testControl(t *testing.T, s *mgr.Service, c svc.Cmd, expectedErr error, expectedStatus svc.Status) { status, err := s.Control(c) if err != expectedErr { @@ -305,6 +356,9 @@ func TestMyService(t *testing.T) { testRebootMessage(t, s, "") // delete reboot message testRecoveryCommand(t, s, fmt.Sprintf("sc query %s", name)) testRecoveryCommand(t, s, "") // delete recovery command + testRecoveryActionsOnNonCrashFailures(t, s, true) + testRecoveryActionsOnNonCrashFailures(t, s, false) + testMultipleRecoverySettings(t, s, fmt.Sprintf("%s failed", name), fmt.Sprintf("sc query %s", name), true) expectedStatus := svc.Status{ State: svc.Stopped, diff --git a/windows/svc/mgr/recovery.go b/windows/svc/mgr/recovery.go index 2e042dd6..32145199 100644 --- a/windows/svc/mgr/recovery.go +++ b/windows/svc/mgr/recovery.go @@ -140,3 +140,30 @@ func (s *Service) RecoveryCommand() (string, error) { p := (*windows.SERVICE_FAILURE_ACTIONS)(unsafe.Pointer(&b[0])) return windows.UTF16PtrToString(p.Command), nil } + +// SetRecoveryActionsOnNonCrashFailures sets the failure actions flag. If the +// flag is set to false, recovery actions will only be performed if the service +// terminates without reporting a status of SERVICE_STOPPED. If the flag is set +// to true, recovery actions are also perfomed if the service stops with a +// nonzero exit code. +func (s *Service) SetRecoveryActionsOnNonCrashFailures(flag bool) error { + var setting windows.SERVICE_FAILURE_ACTIONS_FLAG + if flag { + setting.FailureActionsOnNonCrashFailures = 1 + } + return windows.ChangeServiceConfig2(s.Handle, windows.SERVICE_CONFIG_FAILURE_ACTIONS_FLAG, (*byte)(unsafe.Pointer(&setting))) +} + +// RecoveryActionsOnNonCrashFailures returns the current value of the failure +// actions flag. If the flag is set to false, recovery actions will only be +// performed if the service terminates without reporting a status of +// SERVICE_STOPPED. If the flag is set to true, recovery actions are also +// perfomed if the service stops with a nonzero exit code. +func (s *Service) RecoveryActionsOnNonCrashFailures() (bool, error) { + b, err := s.queryServiceConfig2(windows.SERVICE_CONFIG_FAILURE_ACTIONS_FLAG) + if err != nil { + return false, err + } + p := (*windows.SERVICE_FAILURE_ACTIONS_FLAG)(unsafe.Pointer(&b[0])) + return p.FailureActionsOnNonCrashFailures != 0, nil +}