windows/svc: allow querying service start reason
The QueryServiceDynamicInformation API makes it possible to determine
why a service started. This is declared as an optional function because
the API is Win8+.
Reference:
https://docs.microsoft.com/en-us/windows/win32/api/winsvc/nf-winsvc-queryservicedynamicinformation
Change-Id: I18e9a95b35f8c37d94c9900626c1785f425701dc
Reviewed-on: https://go-review.googlesource.com/c/sys/+/358394
Trust: Jason A. Donenfeld <Jason@zx2c4.com>
Run-TryBot: Jason A. Donenfeld <Jason@zx2c4.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/windows/service.go b/windows/service.go
index 5b28ae1..f8deca8 100644
--- a/windows/service.go
+++ b/windows/service.go
@@ -17,8 +17,6 @@
SC_MANAGER_ALL_ACCESS = 0xf003f
)
-//sys OpenSCManager(machineName *uint16, databaseName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenSCManagerW
-
const (
SERVICE_KERNEL_DRIVER = 1
SERVICE_FILE_SYSTEM_DRIVER = 2
@@ -133,6 +131,14 @@
SC_EVENT_DATABASE_CHANGE = 0
SC_EVENT_PROPERTY_CHANGE = 1
SC_EVENT_STATUS_CHANGE = 2
+
+ SERVICE_START_REASON_DEMAND = 0x00000001
+ SERVICE_START_REASON_AUTO = 0x00000002
+ SERVICE_START_REASON_TRIGGER = 0x00000004
+ SERVICE_START_REASON_RESTART_ON_FAILURE = 0x00000008
+ SERVICE_START_REASON_DELAYEDAUTO = 0x00000010
+
+ SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON = 1
)
type SERVICE_STATUS struct {
@@ -217,6 +223,7 @@
LockDuration uint32
}
+//sys OpenSCManager(machineName *uint16, databaseName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenSCManagerW
//sys CloseServiceHandle(handle Handle) (err error) = advapi32.CloseServiceHandle
//sys CreateService(mgr Handle, serviceName *uint16, displayName *uint16, access uint32, srvType uint32, startType uint32, errCtl uint32, pathName *uint16, loadOrderGroup *uint16, tagId *uint32, dependencies *uint16, serviceStartName *uint16, password *uint16) (handle Handle, err error) [failretval==0] = advapi32.CreateServiceW
//sys OpenService(mgr Handle, serviceName *uint16, access uint32) (handle Handle, err error) [failretval==0] = advapi32.OpenServiceW
@@ -237,3 +244,4 @@
//sys SubscribeServiceChangeNotifications(service Handle, eventType uint32, callback uintptr, callbackCtx uintptr, subscription *uintptr) (ret error) = sechost.SubscribeServiceChangeNotifications?
//sys UnsubscribeServiceChangeNotifications(subscription uintptr) = sechost.UnsubscribeServiceChangeNotifications?
//sys RegisterServiceCtrlHandlerEx(serviceName *uint16, handlerProc uintptr, context uintptr) (handle Handle, err error) = advapi32.RegisterServiceCtrlHandlerExW
+//sys QueryServiceDynamicInformation(service Handle, infoLevel uint32, dynamicInfo unsafe.Pointer) (err error) = advapi32.QueryServiceDynamicInformation?
diff --git a/windows/svc/service.go b/windows/svc/service.go
index 9ad6eb4..5b05c3e 100644
--- a/windows/svc/service.go
+++ b/windows/svc/service.go
@@ -80,6 +80,17 @@
ServiceSpecificExitCode uint32 // set if the service has exited with a service-specific exit code
}
+// StartReason is the reason that the service was started.
+type StartReason uint32
+
+const (
+ StartReasonDemand = StartReason(windows.SERVICE_START_REASON_DEMAND)
+ StartReasonAuto = StartReason(windows.SERVICE_START_REASON_AUTO)
+ StartReasonTrigger = StartReason(windows.SERVICE_START_REASON_TRIGGER)
+ StartReasonRestartOnFailure = StartReason(windows.SERVICE_START_REASON_RESTART_ON_FAILURE)
+ StartReasonDelayedAuto = StartReason(windows.SERVICE_START_REASON_DELAYEDAUTO)
+)
+
// ChangeRequest is sent to the service Handler to request service status change.
type ChangeRequest struct {
Cmd Cmd
@@ -284,7 +295,20 @@
// StatusHandle returns service status handle. It is safe to call this function
// from inside the Handler.Execute because then it is guaranteed to be set.
-// This code will have to change once multiple services are possible per process.
func StatusHandle() windows.Handle {
return theService.h
}
+
+// DynamicStartReason returns the reason why the service was started. It is safe
+// to call this function from inside the Handler.Execute because then it is
+// guaranteed to be set.
+func DynamicStartReason() (StartReason, error) {
+ var allocReason *uint32
+ err := windows.QueryServiceDynamicInformation(theService.h, windows.SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON, unsafe.Pointer(&allocReason))
+ if err != nil {
+ return 0, err
+ }
+ reason := StartReason(*allocReason)
+ windows.LocalFree(windows.Handle(unsafe.Pointer(allocReason)))
+ return reason, nil
+}
diff --git a/windows/zsyscall_windows.go b/windows/zsyscall_windows.go
index 044ecb1..91817d6 100644
--- a/windows/zsyscall_windows.go
+++ b/windows/zsyscall_windows.go
@@ -115,6 +115,7 @@
procOpenThreadToken = modadvapi32.NewProc("OpenThreadToken")
procQueryServiceConfig2W = modadvapi32.NewProc("QueryServiceConfig2W")
procQueryServiceConfigW = modadvapi32.NewProc("QueryServiceConfigW")
+ procQueryServiceDynamicInformation = modadvapi32.NewProc("QueryServiceDynamicInformation")
procQueryServiceLockStatusW = modadvapi32.NewProc("QueryServiceLockStatusW")
procQueryServiceStatus = modadvapi32.NewProc("QueryServiceStatus")
procQueryServiceStatusEx = modadvapi32.NewProc("QueryServiceStatusEx")
@@ -976,6 +977,18 @@
return
}
+func QueryServiceDynamicInformation(service Handle, infoLevel uint32, dynamicInfo unsafe.Pointer) (err error) {
+ err = procQueryServiceDynamicInformation.Find()
+ if err != nil {
+ return
+ }
+ r1, _, e1 := syscall.Syscall(procQueryServiceDynamicInformation.Addr(), 3, uintptr(service), uintptr(infoLevel), uintptr(dynamicInfo))
+ if r1 == 0 {
+ err = errnoErr(e1)
+ }
+ return
+}
+
func QueryServiceLockStatus(mgr Handle, lockStatus *QUERY_SERVICE_LOCK_STATUS, bufSize uint32, bytesNeeded *uint32) (err error) {
r1, _, e1 := syscall.Syscall6(procQueryServiceLockStatusW.Addr(), 4, uintptr(mgr), uintptr(unsafe.Pointer(lockStatus)), uintptr(bufSize), uintptr(unsafe.Pointer(bytesNeeded)), 0, 0)
if r1 == 0 {