golang.org/x/sys@v0.20.1-0.20240517151509-673e0f94c16d/windows/svc/mgr/mgr_test.go (about)

     1  // Copyright 2012 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build windows
     6  
     7  package mgr_test
     8  
     9  import (
    10  	"fmt"
    11  	"os"
    12  	"path/filepath"
    13  	"sort"
    14  	"strings"
    15  	"syscall"
    16  	"testing"
    17  	"time"
    18  
    19  	"golang.org/x/sys/windows"
    20  	"golang.org/x/sys/windows/svc"
    21  	"golang.org/x/sys/windows/svc/mgr"
    22  )
    23  
    24  func TestOpenLanManServer(t *testing.T) {
    25  	m, err := mgr.Connect()
    26  	if err != nil {
    27  		if errno, ok := err.(syscall.Errno); ok && errno == syscall.ERROR_ACCESS_DENIED {
    28  			t.Skip("Skipping test: we don't have rights to manage services.")
    29  		}
    30  		t.Fatalf("SCM connection failed: %s", err)
    31  	}
    32  	defer m.Disconnect()
    33  
    34  	s, err := m.OpenService("LanmanServer")
    35  	if err != nil {
    36  		t.Fatalf("OpenService(lanmanserver) failed: %s", err)
    37  	}
    38  	defer s.Close()
    39  
    40  	_, err = s.Config()
    41  	if err != nil {
    42  		t.Fatalf("Config failed: %s", err)
    43  	}
    44  }
    45  
    46  func install(t *testing.T, m *mgr.Mgr, name, exepath string, c mgr.Config) {
    47  	// Sometimes it takes a while for the service to get
    48  	// removed after previous test run.
    49  	for i := 0; ; i++ {
    50  		s, err := m.OpenService(name)
    51  		if err != nil {
    52  			break
    53  		}
    54  		s.Close()
    55  
    56  		if i > 10 {
    57  			t.Fatalf("service %s already exists", name)
    58  		}
    59  		time.Sleep(300 * time.Millisecond)
    60  	}
    61  
    62  	s, err := m.CreateService(name, exepath, c)
    63  	if err != nil {
    64  		t.Fatalf("CreateService(%s) failed: %v", name, err)
    65  	}
    66  	defer s.Close()
    67  }
    68  
    69  func depString(d []string) string {
    70  	if len(d) == 0 {
    71  		return ""
    72  	}
    73  	for i := range d {
    74  		d[i] = strings.ToLower(d[i])
    75  	}
    76  	ss := sort.StringSlice(d)
    77  	ss.Sort()
    78  	return strings.Join([]string(ss), " ")
    79  }
    80  
    81  func testConfig(t *testing.T, s *mgr.Service, should mgr.Config) mgr.Config {
    82  	is, err := s.Config()
    83  	if err != nil {
    84  		t.Fatalf("Config failed: %s", err)
    85  	}
    86  	if should.DelayedAutoStart != is.DelayedAutoStart {
    87  		t.Fatalf("config mismatch: DelayedAutoStart is %v, but should have %v", is.DelayedAutoStart, should.DelayedAutoStart)
    88  	}
    89  	if should.DisplayName != is.DisplayName {
    90  		t.Fatalf("config mismatch: DisplayName is %q, but should have %q", is.DisplayName, should.DisplayName)
    91  	}
    92  	if should.StartType != is.StartType {
    93  		t.Fatalf("config mismatch: StartType is %v, but should have %v", is.StartType, should.StartType)
    94  	}
    95  	if should.Description != is.Description {
    96  		t.Fatalf("config mismatch: Description is %q, but should have %q", is.Description, should.Description)
    97  	}
    98  	if depString(should.Dependencies) != depString(is.Dependencies) {
    99  		t.Fatalf("config mismatch: Dependencies is %v, but should have %v", is.Dependencies, should.Dependencies)
   100  	}
   101  	return is
   102  }
   103  
   104  func testRecoveryActions(t *testing.T, s *mgr.Service, should []mgr.RecoveryAction) {
   105  	is, err := s.RecoveryActions()
   106  	if err != nil {
   107  		t.Fatalf("RecoveryActions failed: %s", err)
   108  	}
   109  	if len(should) != len(is) {
   110  		t.Errorf("recovery action mismatch: contains %v actions, but should have %v", len(is), len(should))
   111  	}
   112  	for i := range is {
   113  		if should[i].Type != is[i].Type {
   114  			t.Errorf("recovery action mismatch: Type is %v, but should have %v", is[i].Type, should[i].Type)
   115  		}
   116  		if should[i].Delay != is[i].Delay {
   117  			t.Errorf("recovery action mismatch: Delay is %v, but should have %v", is[i].Delay, should[i].Delay)
   118  		}
   119  	}
   120  }
   121  
   122  func testResetPeriod(t *testing.T, s *mgr.Service, should uint32) {
   123  	is, err := s.ResetPeriod()
   124  	if err != nil {
   125  		t.Fatalf("ResetPeriod failed: %s", err)
   126  	}
   127  	if should != is {
   128  		t.Errorf("reset period mismatch: reset period is %v, but should have %v", is, should)
   129  	}
   130  }
   131  
   132  func testSetRecoveryActions(t *testing.T, s *mgr.Service) {
   133  	r := []mgr.RecoveryAction{
   134  		{
   135  			Type:  mgr.NoAction,
   136  			Delay: 60000 * time.Millisecond,
   137  		},
   138  		{
   139  			Type:  mgr.ServiceRestart,
   140  			Delay: 4 * time.Minute,
   141  		},
   142  		{
   143  			Type:  mgr.ServiceRestart,
   144  			Delay: time.Minute,
   145  		},
   146  		{
   147  			Type:  mgr.RunCommand,
   148  			Delay: 4000 * time.Millisecond,
   149  		},
   150  	}
   151  
   152  	// 4 recovery actions with reset period
   153  	err := s.SetRecoveryActions(r, uint32(10000))
   154  	if err != nil {
   155  		t.Fatalf("SetRecoveryActions failed: %v", err)
   156  	}
   157  	testRecoveryActions(t, s, r)
   158  	testResetPeriod(t, s, uint32(10000))
   159  
   160  	// Infinite reset period
   161  	err = s.SetRecoveryActions(r, syscall.INFINITE)
   162  	if err != nil {
   163  		t.Fatalf("SetRecoveryActions failed: %v", err)
   164  	}
   165  	testRecoveryActions(t, s, r)
   166  	testResetPeriod(t, s, syscall.INFINITE)
   167  
   168  	// nil recovery actions
   169  	err = s.SetRecoveryActions(nil, 0)
   170  	if err.Error() != "recoveryActions cannot be nil" {
   171  		t.Fatalf("SetRecoveryActions failed with unexpected error message of %q", err)
   172  	}
   173  
   174  	// Delete all recovery actions and reset period
   175  	err = s.ResetRecoveryActions()
   176  	if err != nil {
   177  		t.Fatalf("ResetRecoveryActions failed: %v", err)
   178  	}
   179  	testRecoveryActions(t, s, nil)
   180  	testResetPeriod(t, s, 0)
   181  }
   182  
   183  func testRebootMessage(t *testing.T, s *mgr.Service, should string) {
   184  	err := s.SetRebootMessage(should)
   185  	if err != nil {
   186  		t.Fatalf("SetRebootMessage failed: %v", err)
   187  	}
   188  	is, err := s.RebootMessage()
   189  	if err != nil {
   190  		t.Fatalf("RebootMessage failed: %v", err)
   191  	}
   192  	if should != is {
   193  		t.Errorf("reboot message mismatch: message is %q, but should have %q", is, should)
   194  	}
   195  }
   196  
   197  func testRecoveryCommand(t *testing.T, s *mgr.Service, should string) {
   198  	err := s.SetRecoveryCommand(should)
   199  	if err != nil {
   200  		t.Fatalf("SetRecoveryCommand failed: %v", err)
   201  	}
   202  	is, err := s.RecoveryCommand()
   203  	if err != nil {
   204  		t.Fatalf("RecoveryCommand failed: %v", err)
   205  	}
   206  	if should != is {
   207  		t.Errorf("recovery command mismatch: command is %q, but should have %q", is, should)
   208  	}
   209  }
   210  
   211  func testRecoveryActionsOnNonCrashFailures(t *testing.T, s *mgr.Service, should bool) {
   212  	err := s.SetRecoveryActionsOnNonCrashFailures(should)
   213  	if err != nil {
   214  		t.Fatalf("SetRecoveryActionsOnNonCrashFailures failed: %v", err)
   215  	}
   216  	is, err := s.RecoveryActionsOnNonCrashFailures()
   217  	if err != nil {
   218  		t.Fatalf("RecoveryActionsOnNonCrashFailures failed: %v", err)
   219  	}
   220  	if should != is {
   221  		t.Errorf("RecoveryActionsOnNonCrashFailures mismatch: flag is %v, but should have %v", is, should)
   222  	}
   223  }
   224  
   225  func testMultipleRecoverySettings(t *testing.T, s *mgr.Service, rebootMsgShould, recoveryCmdShould string, actionsFlagShould bool) {
   226  	err := s.SetRebootMessage(rebootMsgShould)
   227  	if err != nil {
   228  		t.Fatalf("SetRebootMessage failed: %v", err)
   229  	}
   230  	err = s.SetRecoveryActionsOnNonCrashFailures(actionsFlagShould)
   231  	if err != nil {
   232  		t.Fatalf("SetRecoveryActionsOnNonCrashFailures failed: %v", err)
   233  	}
   234  	err = s.SetRecoveryCommand(recoveryCmdShould)
   235  	if err != nil {
   236  		t.Fatalf("SetRecoveryCommand failed: %v", err)
   237  	}
   238  
   239  	rebootMsgIs, err := s.RebootMessage()
   240  	if err != nil {
   241  		t.Fatalf("RebootMessage failed: %v", err)
   242  	}
   243  	if rebootMsgShould != rebootMsgIs {
   244  		t.Errorf("reboot message mismatch: message is %q, but should have %q", rebootMsgIs, rebootMsgShould)
   245  	}
   246  	recoveryCommandIs, err := s.RecoveryCommand()
   247  	if err != nil {
   248  		t.Fatalf("RecoveryCommand failed: %v", err)
   249  	}
   250  	if recoveryCmdShould != recoveryCommandIs {
   251  		t.Errorf("recovery command mismatch: command is %q, but should have %q", recoveryCommandIs, recoveryCmdShould)
   252  	}
   253  	actionsFlagIs, err := s.RecoveryActionsOnNonCrashFailures()
   254  	if err != nil {
   255  		t.Fatalf("RecoveryActionsOnNonCrashFailures failed: %v", err)
   256  	}
   257  	if actionsFlagShould != actionsFlagIs {
   258  		t.Errorf("RecoveryActionsOnNonCrashFailures mismatch: flag is %v, but should have %v", actionsFlagIs, actionsFlagShould)
   259  	}
   260  }
   261  
   262  func testControl(t *testing.T, s *mgr.Service, c svc.Cmd, expectedErr error, expectedStatus svc.Status) {
   263  	status, err := s.Control(c)
   264  	if err != expectedErr {
   265  		t.Fatalf("Unexpected return from s.Control: %v (expected %v)", err, expectedErr)
   266  	}
   267  	if expectedStatus != status {
   268  		t.Fatalf("Unexpected status from s.Control: %+v (expected %+v)", status, expectedStatus)
   269  	}
   270  }
   271  
   272  func remove(t *testing.T, s *mgr.Service) {
   273  	err := s.Delete()
   274  	if err != nil {
   275  		t.Fatalf("Delete failed: %s", err)
   276  	}
   277  }
   278  
   279  func TestMyService(t *testing.T) {
   280  	if os.Getenv("GO_BUILDER_NAME") == "" {
   281  		// Don't install services on arbitrary users' machines.
   282  		t.Skip("skipping test that modifies system services: GO_BUILDER_NAME not set")
   283  	}
   284  	if testing.Short() {
   285  		t.Skip("skipping test in short mode that modifies system services")
   286  	}
   287  
   288  	const name = "mgrtestservice"
   289  
   290  	m, err := mgr.Connect()
   291  	if err != nil {
   292  		t.Fatalf("SCM connection failed: %s", err)
   293  	}
   294  	defer m.Disconnect()
   295  
   296  	c := mgr.Config{
   297  		StartType:    mgr.StartDisabled,
   298  		DisplayName:  "x-sys mgr test service",
   299  		Description:  "x-sys mgr test service is just a test",
   300  		Dependencies: []string{"LanmanServer", "W32Time"},
   301  	}
   302  
   303  	exename := os.Args[0]
   304  	exepath, err := filepath.Abs(exename)
   305  	if err != nil {
   306  		t.Fatalf("filepath.Abs(%s) failed: %s", exename, err)
   307  	}
   308  
   309  	install(t, m, name, exepath, c)
   310  
   311  	s, err := m.OpenService(name)
   312  	if err != nil {
   313  		t.Fatalf("service %s is not installed", name)
   314  	}
   315  	defer s.Close()
   316  	defer s.Delete()
   317  
   318  	c.BinaryPathName = exepath
   319  	c = testConfig(t, s, c)
   320  
   321  	c.StartType = mgr.StartManual
   322  	err = s.UpdateConfig(c)
   323  	if err != nil {
   324  		t.Fatalf("UpdateConfig failed: %v", err)
   325  	}
   326  
   327  	testConfig(t, s, c)
   328  
   329  	c.StartType = mgr.StartAutomatic
   330  	c.DelayedAutoStart = true
   331  	err = s.UpdateConfig(c)
   332  	if err != nil {
   333  		t.Fatalf("UpdateConfig failed: %v", err)
   334  	}
   335  
   336  	testConfig(t, s, c)
   337  
   338  	svcnames, err := m.ListServices()
   339  	if err != nil {
   340  		t.Fatalf("ListServices failed: %v", err)
   341  	}
   342  	var serviceIsInstalled bool
   343  	for _, sn := range svcnames {
   344  		if sn == name {
   345  			serviceIsInstalled = true
   346  			break
   347  		}
   348  	}
   349  	if !serviceIsInstalled {
   350  		t.Errorf("ListServices failed to find %q service", name)
   351  	}
   352  
   353  	testSetRecoveryActions(t, s)
   354  	testRebootMessage(t, s, fmt.Sprintf("%s failed", name))
   355  	testRebootMessage(t, s, "") // delete reboot message
   356  	testRecoveryCommand(t, s, fmt.Sprintf("sc query %s", name))
   357  	testRecoveryCommand(t, s, "") // delete recovery command
   358  	testRecoveryActionsOnNonCrashFailures(t, s, true)
   359  	testRecoveryActionsOnNonCrashFailures(t, s, false)
   360  	testMultipleRecoverySettings(t, s, fmt.Sprintf("%s failed", name), fmt.Sprintf("sc query %s", name), true)
   361  
   362  	expectedStatus := svc.Status{
   363  		State: svc.Stopped,
   364  	}
   365  	testControl(t, s, svc.Stop, windows.ERROR_SERVICE_NOT_ACTIVE, expectedStatus)
   366  
   367  	remove(t, s)
   368  }
   369  
   370  func TestListDependentServices(t *testing.T) {
   371  	m, err := mgr.Connect()
   372  	if err != nil {
   373  		if errno, ok := err.(syscall.Errno); ok && errno == syscall.ERROR_ACCESS_DENIED {
   374  			t.Skip("Skipping test: we don't have rights to manage services.")
   375  		}
   376  		t.Fatalf("SCM connection failed: %s", err)
   377  	}
   378  	defer m.Disconnect()
   379  
   380  	baseServiceName := "testservice1"
   381  	dependentServiceName := "testservice2"
   382  	install(t, m, baseServiceName, "", mgr.Config{})
   383  	baseService, err := m.OpenService(baseServiceName)
   384  	if err != nil {
   385  		t.Fatalf("OpenService failed: %v", err)
   386  	}
   387  	defer remove(t, baseService)
   388  	install(t, m, dependentServiceName, "", mgr.Config{Dependencies: []string{baseServiceName}})
   389  	dependentService, err := m.OpenService(dependentServiceName)
   390  	if err != nil {
   391  		t.Fatalf("OpenService failed: %v", err)
   392  	}
   393  	defer remove(t, dependentService)
   394  
   395  	// test that both the base service and dependent service list the correct dependencies
   396  	dependentServices, err := baseService.ListDependentServices(svc.AnyActivity)
   397  	if err != nil {
   398  		t.Fatalf("baseService.ListDependentServices failed: %v", err)
   399  	}
   400  	if len(dependentServices) != 1 || dependentServices[0] != dependentServiceName {
   401  		t.Errorf("Found %v, instead of expected contents %s", dependentServices, dependentServiceName)
   402  	}
   403  	dependentServices, err = dependentService.ListDependentServices(svc.AnyActivity)
   404  	if err != nil {
   405  		t.Fatalf("dependentService.ListDependentServices failed: %v", err)
   406  	}
   407  	if len(dependentServices) != 0 {
   408  		t.Errorf("Found %v, where no service should be listed", dependentService)
   409  	}
   410  }