From 8df38161980d1d79b2a43272eb5905e0e41f53de Mon Sep 17 00:00:00 2001 From: Craig Davison Date: Sat, 15 Apr 2023 16:22:10 -0600 Subject: [PATCH] RecoveryActionsOnNonCrashFailures --- windows/service.go | 4 +++ windows/svc/mgr/mgr_test.go | 64 ++++++++++++++++++++++++++++++++++--- windows/svc/mgr/recovery.go | 27 ++++++++++++++++ 3 files changed, 90 insertions(+), 5 deletions(-) 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 bc763f06..5a25b4a4 100644 --- a/windows/svc/mgr/mgr_test.go +++ b/windows/svc/mgr/mgr_test.go @@ -109,7 +109,7 @@ func testRecoveryActions(t *testing.T, s *mgr.Service, should []mgr.RecoveryActi if len(should) != len(is) { t.Errorf("recovery action mismatch: contains %v actions, but should have %v", len(is), len(should)) } - for i, _ := range is { + for i := range is { if should[i].Type != is[i].Type { t.Errorf("recovery action mismatch: Type is %v, but should have %v", is[i].Type, should[i].Type) } @@ -131,19 +131,19 @@ func testResetPeriod(t *testing.T, s *mgr.Service, should uint32) { func testSetRecoveryActions(t *testing.T, s *mgr.Service) { r := []mgr.RecoveryAction{ - mgr.RecoveryAction{ + { Type: mgr.NoAction, Delay: 60000 * time.Millisecond, }, - mgr.RecoveryAction{ + { Type: mgr.ServiceRestart, Delay: 4 * time.Minute, }, - mgr.RecoveryAction{ + { Type: mgr.ServiceRestart, Delay: time.Minute, }, - mgr.RecoveryAction{ + { Type: mgr.RunCommand, Delay: 4000 * time.Millisecond, }, @@ -208,6 +208,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 remove(t *testing.T, s *mgr.Service) { err := s.Delete() if err != nil { @@ -292,6 +343,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) remove(t, s) } 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 +}