Skip to content

Commit

Permalink
windows/svc/mgr: add Service.RecoveryActionsOnNonCrashFailures
Browse files Browse the repository at this point in the history
Fixes golang/go#59016

Change-Id: I5e16f61ea2fc384052565342c6517687562260a1
GitHub-Last-Rev: 3626b01
GitHub-Pull-Request: #157
Reviewed-on: https://go-review.googlesource.com/c/sys/+/484896
Run-TryBot: Bryan Mills <bcmills@google.com>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
Reviewed-by: Bryan Mills <bcmills@google.com>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
  • Loading branch information
craig65535 authored and alexbrainman committed Jun 24, 2023
1 parent e0c3b6e commit 2b751dd
Show file tree
Hide file tree
Showing 3 changed files with 85 additions and 0 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
54 changes: 54 additions & 0 deletions windows/svc/mgr/mgr_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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,
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 2b751dd

Please sign in to comment.