windows: add Service.ListDependentServices

This method allows a user to list all Windows services which are
dependent upon a given service.

This commit makes use of the EnumDependentServices Windows API call.
Without this, a user would have to iterate through each service on the
system, and check if the given service is listed in each service's
dependencies list.

The implementation of ListDependentServices is mostly the same as
Mgr.ListServices, as the API calls behave in the same way.

Fixes golang/go#56766

Change-Id: I9ec18c97afd02f48deef691ccdd5c26d6501add1
Reviewed-on: https://go-review.googlesource.com/c/sys/+/451363
Reviewed-by: Than McIntosh <thanm@google.com>
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Benny Siegert <bsiegert@gmail.com>
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
This commit is contained in:
Sebastian Soto
2023-04-14 09:16:30 +00:00
committed by Alex Brainman
parent f25ff604a2
commit dbd8f99a5e
5 changed files with 109 additions and 2 deletions
+43
View File
@@ -17,6 +17,7 @@ import (
"testing"
"time"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/mgr"
)
@@ -294,3 +295,45 @@ func TestMyService(t *testing.T) {
remove(t, s)
}
func TestListDependentServices(t *testing.T) {
m, err := mgr.Connect()
if err != nil {
if errno, ok := err.(syscall.Errno); ok && errno == syscall.ERROR_ACCESS_DENIED {
t.Skip("Skipping test: we don't have rights to manage services.")
}
t.Fatalf("SCM connection failed: %s", err)
}
defer m.Disconnect()
baseServiceName := "testservice1"
dependentServiceName := "testservice2"
install(t, m, baseServiceName, "", mgr.Config{})
baseService, err := m.OpenService(baseServiceName)
if err != nil {
t.Fatalf("OpenService failed: %v", err)
}
defer remove(t, baseService)
install(t, m, dependentServiceName, "", mgr.Config{Dependencies: []string{baseServiceName}})
dependentService, err := m.OpenService(dependentServiceName)
if err != nil {
t.Fatalf("OpenService failed: %v", err)
}
defer remove(t, dependentService)
// test that both the base service and dependent service list the correct dependencies
dependentServices, err := baseService.ListDependentServices(svc.AnyActivity)
if err != nil {
t.Fatalf("baseService.ListDependentServices failed: %v", err)
}
if len(dependentServices) != 1 || dependentServices[0] != dependentServiceName {
t.Errorf("Found %v, instead of expected contents %s", dependentServices, dependentServiceName)
}
dependentServices, err = dependentService.ListDependentServices(svc.AnyActivity)
if err != nil {
t.Fatalf("dependentService.ListDependentServices failed: %v", err)
}
if len(dependentServices) != 0 {
t.Errorf("Found %v, where no service should be listed", dependentService)
}
}
+41 -2
View File
@@ -15,8 +15,6 @@ import (
"golang.org/x/sys/windows/svc"
)
// TODO(brainman): Use EnumDependentServices to enumerate dependent services.
// Service is used to access Windows service.
type Service struct {
Name string
@@ -76,3 +74,44 @@ func (s *Service) Query() (svc.Status, error) {
ServiceSpecificExitCode: t.ServiceSpecificExitCode,
}, nil
}
// ListDependentServices returns the names of the services dependent on service s, which match the given status.
func (s *Service) ListDependentServices(status svc.ActivityStatus) ([]string, error) {
var bytesNeeded, returnedServiceCount uint32
var services []windows.ENUM_SERVICE_STATUS
for {
var servicesPtr *windows.ENUM_SERVICE_STATUS
if len(services) > 0 {
servicesPtr = &services[0]
}
allocatedBytes := uint32(len(services)) * uint32(unsafe.Sizeof(windows.ENUM_SERVICE_STATUS{}))
err := windows.EnumDependentServices(s.Handle, uint32(status), servicesPtr, allocatedBytes, &bytesNeeded,
&returnedServiceCount)
if err == nil {
break
}
if err != syscall.ERROR_MORE_DATA {
return nil, err
}
if bytesNeeded <= allocatedBytes {
return nil, err
}
// ERROR_MORE_DATA indicates the provided buffer was too small, run the call again after resizing the buffer
requiredSliceLen := bytesNeeded / uint32(unsafe.Sizeof(windows.ENUM_SERVICE_STATUS{}))
if bytesNeeded%uint32(unsafe.Sizeof(windows.ENUM_SERVICE_STATUS{})) != 0 {
requiredSliceLen += 1
}
services = make([]windows.ENUM_SERVICE_STATUS, requiredSliceLen)
}
if returnedServiceCount == 0 {
return nil, nil
}
// The slice mutated by EnumDependentServices may have a length greater than returnedServiceCount, any elements
// past that should be ignored.
var dependents []string
for i := 0; i < int(returnedServiceCount); i++ {
dependents = append(dependents, windows.UTF16PtrToString(services[i].ServiceName))
}
return dependents, nil
}