windows: use RegisterServiceCtrlHandlerEx to allow for advanced notifications

This allows services written with this library to register for more
advanced notifications like SERVICE_CONTROL_DEVICEEVENT for example.

Also the code now exposes the service status handle through a new
api call, because the handle is needed to register for such
notifications and can not be obtained by any other means.

Change-Id: I80796e1dd9d94ec9d6c236d0413b17288c67fe1f
Reviewed-on: https://go-review.googlesource.com/42812
Reviewed-by: Alex Brainman <alex.brainman@gmail.com>
Run-TryBot: Alex Brainman <alex.brainman@gmail.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
diff --git a/windows/svc/debug/service.go b/windows/svc/debug/service.go
index d5ab94b..123df98 100644
--- a/windows/svc/debug/service.go
+++ b/windows/svc/debug/service.go
@@ -31,7 +31,7 @@
 		for {
 			select {
 			case <-sig:
-				cmds <- svc.ChangeRequest{svc.Stop, status}
+				cmds <- svc.ChangeRequest{svc.Stop, 0, 0, status}
 			case status = <-changes:
 			}
 		}
diff --git a/windows/svc/service.go b/windows/svc/service.go
index 9864f7a..1ea4a88 100644
--- a/windows/svc/service.go
+++ b/windows/svc/service.go
@@ -35,11 +35,20 @@
 type Cmd uint32
 
 const (
-	Stop        = Cmd(windows.SERVICE_CONTROL_STOP)
-	Pause       = Cmd(windows.SERVICE_CONTROL_PAUSE)
-	Continue    = Cmd(windows.SERVICE_CONTROL_CONTINUE)
-	Interrogate = Cmd(windows.SERVICE_CONTROL_INTERROGATE)
-	Shutdown    = Cmd(windows.SERVICE_CONTROL_SHUTDOWN)
+	Stop                  = Cmd(windows.SERVICE_CONTROL_STOP)
+	Pause                 = Cmd(windows.SERVICE_CONTROL_PAUSE)
+	Continue              = Cmd(windows.SERVICE_CONTROL_CONTINUE)
+	Interrogate           = Cmd(windows.SERVICE_CONTROL_INTERROGATE)
+	Shutdown              = Cmd(windows.SERVICE_CONTROL_SHUTDOWN)
+	ParamChange           = Cmd(windows.SERVICE_CONTROL_PARAMCHANGE)
+	NetBindAdd            = Cmd(windows.SERVICE_CONTROL_NETBINDADD)
+	NetBindRemove         = Cmd(windows.SERVICE_CONTROL_NETBINDREMOVE)
+	NetBindEnable         = Cmd(windows.SERVICE_CONTROL_NETBINDENABLE)
+	NetBindDisable        = Cmd(windows.SERVICE_CONTROL_NETBINDDISABLE)
+	DeviceEvent           = Cmd(windows.SERVICE_CONTROL_DEVICEEVENT)
+	HardwareProfileChange = Cmd(windows.SERVICE_CONTROL_HARDWAREPROFILECHANGE)
+	PowerEvent            = Cmd(windows.SERVICE_CONTROL_POWEREVENT)
+	SessionChange         = Cmd(windows.SERVICE_CONTROL_SESSIONCHANGE)
 )
 
 // Accepted is used to describe commands accepted by the service.
@@ -63,6 +72,8 @@
 // ChangeRequest is sent to the service Handler to request service status change.
 type ChangeRequest struct {
 	Cmd           Cmd
+	EventType     uint32
+	EventData     uintptr
 	CurrentStatus Status
 }
 
@@ -85,16 +96,16 @@
 
 var (
 	// These are used by asm code.
-	goWaitsH                     uintptr
-	cWaitsH                      uintptr
-	ssHandle                     uintptr
-	sName                        *uint16
-	sArgc                        uintptr
-	sArgv                        **uint16
-	ctlHandlerProc               uintptr
-	cSetEvent                    uintptr
-	cWaitForSingleObject         uintptr
-	cRegisterServiceCtrlHandlerW uintptr
+	goWaitsH                       uintptr
+	cWaitsH                        uintptr
+	ssHandle                       uintptr
+	sName                          *uint16
+	sArgc                          uintptr
+	sArgv                          **uint16
+	ctlHandlerExProc               uintptr
+	cSetEvent                      uintptr
+	cWaitForSingleObject           uintptr
+	cRegisterServiceCtrlHandlerExW uintptr
 )
 
 func init() {
@@ -102,12 +113,16 @@
 	cSetEvent = k.MustFindProc("SetEvent").Addr()
 	cWaitForSingleObject = k.MustFindProc("WaitForSingleObject").Addr()
 	a := syscall.MustLoadDLL("advapi32.dll")
-	cRegisterServiceCtrlHandlerW = a.MustFindProc("RegisterServiceCtrlHandlerW").Addr()
+	cRegisterServiceCtrlHandlerExW = a.MustFindProc("RegisterServiceCtrlHandlerExW").Addr()
 }
 
+// The HandlerEx prototype also has a context pointer but since we don't use
+// it at start-up time we don't have to pass it over either.
 type ctlEvent struct {
-	cmd   Cmd
-	errno uint32
+	cmd       Cmd
+	eventType uint32
+	eventData uintptr
+	errno     uint32
 }
 
 // service provides access to windows service api.
@@ -208,6 +223,8 @@
 	var outch chan ChangeRequest
 	inch := s.c
 	var cmd Cmd
+	var evtype uint32
+	var evdata uintptr
 loop:
 	for {
 		select {
@@ -219,7 +236,9 @@
 			inch = nil
 			outch = cmdsToHandler
 			cmd = r.cmd
-		case outch <- ChangeRequest{cmd, status}:
+			evtype = r.eventType
+			evdata = r.eventData
+		case outch <- ChangeRequest{cmd, evtype, evdata, status}:
 			inch = s.c
 			outch = nil
 		case c := <-changesFromHandler:
@@ -276,8 +295,8 @@
 		return err
 	}
 
-	ctlHandler := func(ctl uint32) uintptr {
-		e := ctlEvent{cmd: Cmd(ctl)}
+	ctlHandler := func(ctl uint32, evtype uint32, evdata uintptr, context uintptr) uintptr {
+		e := ctlEvent{cmd: Cmd(ctl), eventType: evtype, eventData: evdata}
 		// We assume that this callback function is running on
 		// the same thread as Run. Nowhere in MS documentation
 		// I could find statement to guarantee that. So putting
@@ -288,6 +307,7 @@
 			e.errno = sysErrNewThreadInCallback
 		}
 		s.c <- e
+		// Always return NO_ERROR (0) for now.
 		return 0
 	}
 
@@ -301,7 +321,7 @@
 	goWaitsH = uintptr(s.goWaits.h)
 	cWaitsH = uintptr(s.cWaits.h)
 	sName = t[0].ServiceName
-	ctlHandlerProc, err = newCallback(ctlHandler)
+	ctlHandlerExProc, err = newCallback(ctlHandler)
 	if err != nil {
 		return err
 	}
@@ -314,3 +334,10 @@
 	}
 	return nil
 }
+
+// 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 windows.Handle(ssHandle)
+}
diff --git a/windows/svc/sys_386.s b/windows/svc/sys_386.s
index 5e11bfa..2c82a9d 100644
--- a/windows/svc/sys_386.s
+++ b/windows/svc/sys_386.s
@@ -22,7 +22,8 @@
 	MOVL	AX, (SP)
 	MOVL	$·servicectlhandler(SB), AX
 	MOVL	AX, 4(SP)
-	MOVL	·cRegisterServiceCtrlHandlerW(SB), AX
+	MOVL	$0, 8(SP)
+	MOVL	·cRegisterServiceCtrlHandlerExW(SB), AX
 	MOVL	SP, BP
 	CALL	AX
 	MOVL	BP, SP
@@ -61,7 +62,7 @@
 // I do not know why, but this seems to be the only way to call
 // ctlHandlerProc on Windows 7.
 
-// func servicectlhandler(ctl uint32) uintptr
+// func servicectlhandler(ctl uint32, evtype uint32, evdata uintptr, context uintptr) uintptr {
 TEXT ·servicectlhandler(SB),7,$0
-	MOVL	·ctlHandlerProc(SB), CX
+	MOVL	·ctlHandlerExProc(SB), CX
 	JMP	CX
diff --git a/windows/svc/sys_amd64.s b/windows/svc/sys_amd64.s
index 87dbec8..06b4259 100644
--- a/windows/svc/sys_amd64.s
+++ b/windows/svc/sys_amd64.s
@@ -13,7 +13,8 @@
 
 	MOVQ	·sName(SB), CX
 	MOVQ	$·servicectlhandler(SB), DX
-	MOVQ	·cRegisterServiceCtrlHandlerW(SB), AX
+	// BUG(pastarmovj): Figure out a way to pass in context in R8.
+	MOVQ	·cRegisterServiceCtrlHandlerExW(SB), AX
 	CALL	AX
 	CMPQ	AX, $0
 	JE	exit
@@ -35,7 +36,7 @@
 // I do not know why, but this seems to be the only way to call
 // ctlHandlerProc on Windows 7.
 
-// func servicectlhandler(ctl uint32) uintptr
+// func ·servicectlhandler(ctl uint32, evtype uint32, evdata uintptr, context uintptr) uintptr {
 TEXT ·servicectlhandler(SB),7,$0
-	MOVQ	·ctlHandlerProc(SB), AX
+	MOVQ	·ctlHandlerExProc(SB), AX
 	JMP	AX