Skip to content

Commit

Permalink
RecoveryActionsOnNonCrashFailures
Browse files Browse the repository at this point in the history
  • Loading branch information
craig65535 committed Apr 15, 2023
1 parent 39c2d6a commit 8df3816
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 5 deletions.
4 changes: 4 additions & 0 deletions windows/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
64 changes: 59 additions & 5 deletions windows/svc/mgr/mgr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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,
},
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -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)
}
Expand Down
27 changes: 27 additions & 0 deletions windows/svc/mgr/recovery.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

0 comments on commit 8df3816

Please sign in to comment.