Skip to content

Commit dbd8f99

Browse files
sebsotoalexbrainman
authored andcommitted
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>
1 parent f25ff60 commit dbd8f99

File tree

5 files changed

+109
-2
lines changed

5 files changed

+109
-2
lines changed

windows/service.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,12 @@ const (
141141
SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON = 1
142142
)
143143

144+
type ENUM_SERVICE_STATUS struct {
145+
ServiceName *uint16
146+
DisplayName *uint16
147+
ServiceStatus SERVICE_STATUS
148+
}
149+
144150
type SERVICE_STATUS struct {
145151
ServiceType uint32
146152
CurrentState uint32
@@ -245,3 +251,4 @@ type QUERY_SERVICE_LOCK_STATUS struct {
245251
//sys UnsubscribeServiceChangeNotifications(subscription uintptr) = sechost.UnsubscribeServiceChangeNotifications?
246252
//sys RegisterServiceCtrlHandlerEx(serviceName *uint16, handlerProc uintptr, context uintptr) (handle Handle, err error) = advapi32.RegisterServiceCtrlHandlerExW
247253
//sys QueryServiceDynamicInformation(service Handle, infoLevel uint32, dynamicInfo unsafe.Pointer) (err error) = advapi32.QueryServiceDynamicInformation?
254+
//sys EnumDependentServices(service Handle, activityState uint32, services *ENUM_SERVICE_STATUS, buffSize uint32, bytesNeeded *uint32, servicesReturned *uint32) (err error) = advapi32.EnumDependentServicesW

windows/svc/mgr/mgr_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import (
1717
"testing"
1818
"time"
1919

20+
"golang.org/x/sys/windows/svc"
2021
"golang.org/x/sys/windows/svc/mgr"
2122
)
2223

@@ -294,3 +295,45 @@ func TestMyService(t *testing.T) {
294295

295296
remove(t, s)
296297
}
298+
299+
func TestListDependentServices(t *testing.T) {
300+
m, err := mgr.Connect()
301+
if err != nil {
302+
if errno, ok := err.(syscall.Errno); ok && errno == syscall.ERROR_ACCESS_DENIED {
303+
t.Skip("Skipping test: we don't have rights to manage services.")
304+
}
305+
t.Fatalf("SCM connection failed: %s", err)
306+
}
307+
defer m.Disconnect()
308+
309+
baseServiceName := "testservice1"
310+
dependentServiceName := "testservice2"
311+
install(t, m, baseServiceName, "", mgr.Config{})
312+
baseService, err := m.OpenService(baseServiceName)
313+
if err != nil {
314+
t.Fatalf("OpenService failed: %v", err)
315+
}
316+
defer remove(t, baseService)
317+
install(t, m, dependentServiceName, "", mgr.Config{Dependencies: []string{baseServiceName}})
318+
dependentService, err := m.OpenService(dependentServiceName)
319+
if err != nil {
320+
t.Fatalf("OpenService failed: %v", err)
321+
}
322+
defer remove(t, dependentService)
323+
324+
// test that both the base service and dependent service list the correct dependencies
325+
dependentServices, err := baseService.ListDependentServices(svc.AnyActivity)
326+
if err != nil {
327+
t.Fatalf("baseService.ListDependentServices failed: %v", err)
328+
}
329+
if len(dependentServices) != 1 || dependentServices[0] != dependentServiceName {
330+
t.Errorf("Found %v, instead of expected contents %s", dependentServices, dependentServiceName)
331+
}
332+
dependentServices, err = dependentService.ListDependentServices(svc.AnyActivity)
333+
if err != nil {
334+
t.Fatalf("dependentService.ListDependentServices failed: %v", err)
335+
}
336+
if len(dependentServices) != 0 {
337+
t.Errorf("Found %v, where no service should be listed", dependentService)
338+
}
339+
}

windows/svc/mgr/service.go

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@ import (
1515
"golang.org/x/sys/windows/svc"
1616
)
1717

18-
// TODO(brainman): Use EnumDependentServices to enumerate dependent services.
19-
2018
// Service is used to access Windows service.
2119
type Service struct {
2220
Name string
@@ -76,3 +74,44 @@ func (s *Service) Query() (svc.Status, error) {
7674
ServiceSpecificExitCode: t.ServiceSpecificExitCode,
7775
}, nil
7876
}
77+
78+
// ListDependentServices returns the names of the services dependent on service s, which match the given status.
79+
func (s *Service) ListDependentServices(status svc.ActivityStatus) ([]string, error) {
80+
var bytesNeeded, returnedServiceCount uint32
81+
var services []windows.ENUM_SERVICE_STATUS
82+
for {
83+
var servicesPtr *windows.ENUM_SERVICE_STATUS
84+
if len(services) > 0 {
85+
servicesPtr = &services[0]
86+
}
87+
allocatedBytes := uint32(len(services)) * uint32(unsafe.Sizeof(windows.ENUM_SERVICE_STATUS{}))
88+
err := windows.EnumDependentServices(s.Handle, uint32(status), servicesPtr, allocatedBytes, &bytesNeeded,
89+
&returnedServiceCount)
90+
if err == nil {
91+
break
92+
}
93+
if err != syscall.ERROR_MORE_DATA {
94+
return nil, err
95+
}
96+
if bytesNeeded <= allocatedBytes {
97+
return nil, err
98+
}
99+
// ERROR_MORE_DATA indicates the provided buffer was too small, run the call again after resizing the buffer
100+
requiredSliceLen := bytesNeeded / uint32(unsafe.Sizeof(windows.ENUM_SERVICE_STATUS{}))
101+
if bytesNeeded%uint32(unsafe.Sizeof(windows.ENUM_SERVICE_STATUS{})) != 0 {
102+
requiredSliceLen += 1
103+
}
104+
services = make([]windows.ENUM_SERVICE_STATUS, requiredSliceLen)
105+
}
106+
if returnedServiceCount == 0 {
107+
return nil, nil
108+
}
109+
110+
// The slice mutated by EnumDependentServices may have a length greater than returnedServiceCount, any elements
111+
// past that should be ignored.
112+
var dependents []string
113+
for i := 0; i < int(returnedServiceCount); i++ {
114+
dependents = append(dependents, windows.UTF16PtrToString(services[i].ServiceName))
115+
}
116+
return dependents, nil
117+
}

windows/svc/service.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,15 @@ const (
6868
AcceptPreShutdown = Accepted(windows.SERVICE_ACCEPT_PRESHUTDOWN)
6969
)
7070

71+
// ActivityStatus allows for services to be selected based on active and inactive categories of service state.
72+
type ActivityStatus uint32
73+
74+
const (
75+
Active = ActivityStatus(windows.SERVICE_ACTIVE)
76+
Inactive = ActivityStatus(windows.SERVICE_INACTIVE)
77+
AnyActivity = ActivityStatus(windows.SERVICE_STATE_ALL)
78+
)
79+
7180
// Status combines State and Accepted commands to fully describe running service.
7281
type Status struct {
7382
State State

windows/zsyscall_windows.go

Lines changed: 9 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)