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 {