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>
This commit is contained in:
Julian Pastarmov
2017-05-18 05:52:01 +00:00
committed by Alex Brainman
parent 98b5b1e7e8
commit e62c3de784
4 changed files with 58 additions and 29 deletions
+1 -1
View File
@@ -31,7 +31,7 @@ func Run(name string, handler svc.Handler) error {
for { for {
select { select {
case <-sig: case <-sig:
cmds <- svc.ChangeRequest{svc.Stop, status} cmds <- svc.ChangeRequest{svc.Stop, 0, 0, status}
case status = <-changes: case status = <-changes:
} }
} }
+49 -22
View File
@@ -35,11 +35,20 @@ const (
type Cmd uint32 type Cmd uint32
const ( const (
Stop = Cmd(windows.SERVICE_CONTROL_STOP) Stop = Cmd(windows.SERVICE_CONTROL_STOP)
Pause = Cmd(windows.SERVICE_CONTROL_PAUSE) Pause = Cmd(windows.SERVICE_CONTROL_PAUSE)
Continue = Cmd(windows.SERVICE_CONTROL_CONTINUE) Continue = Cmd(windows.SERVICE_CONTROL_CONTINUE)
Interrogate = Cmd(windows.SERVICE_CONTROL_INTERROGATE) Interrogate = Cmd(windows.SERVICE_CONTROL_INTERROGATE)
Shutdown = Cmd(windows.SERVICE_CONTROL_SHUTDOWN) 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. // Accepted is used to describe commands accepted by the service.
@@ -63,6 +72,8 @@ type Status struct {
// ChangeRequest is sent to the service Handler to request service status change. // ChangeRequest is sent to the service Handler to request service status change.
type ChangeRequest struct { type ChangeRequest struct {
Cmd Cmd Cmd Cmd
EventType uint32
EventData uintptr
CurrentStatus Status CurrentStatus Status
} }
@@ -85,16 +96,16 @@ type Handler interface {
var ( var (
// These are used by asm code. // These are used by asm code.
goWaitsH uintptr goWaitsH uintptr
cWaitsH uintptr cWaitsH uintptr
ssHandle uintptr ssHandle uintptr
sName *uint16 sName *uint16
sArgc uintptr sArgc uintptr
sArgv **uint16 sArgv **uint16
ctlHandlerProc uintptr ctlHandlerExProc uintptr
cSetEvent uintptr cSetEvent uintptr
cWaitForSingleObject uintptr cWaitForSingleObject uintptr
cRegisterServiceCtrlHandlerW uintptr cRegisterServiceCtrlHandlerExW uintptr
) )
func init() { func init() {
@@ -102,12 +113,16 @@ func init() {
cSetEvent = k.MustFindProc("SetEvent").Addr() cSetEvent = k.MustFindProc("SetEvent").Addr()
cWaitForSingleObject = k.MustFindProc("WaitForSingleObject").Addr() cWaitForSingleObject = k.MustFindProc("WaitForSingleObject").Addr()
a := syscall.MustLoadDLL("advapi32.dll") 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 { type ctlEvent struct {
cmd Cmd cmd Cmd
errno uint32 eventType uint32
eventData uintptr
errno uint32
} }
// service provides access to windows service api. // service provides access to windows service api.
@@ -208,6 +223,8 @@ func (s *service) run() {
var outch chan ChangeRequest var outch chan ChangeRequest
inch := s.c inch := s.c
var cmd Cmd var cmd Cmd
var evtype uint32
var evdata uintptr
loop: loop:
for { for {
select { select {
@@ -219,7 +236,9 @@ loop:
inch = nil inch = nil
outch = cmdsToHandler outch = cmdsToHandler
cmd = r.cmd cmd = r.cmd
case outch <- ChangeRequest{cmd, status}: evtype = r.eventType
evdata = r.eventData
case outch <- ChangeRequest{cmd, evtype, evdata, status}:
inch = s.c inch = s.c
outch = nil outch = nil
case c := <-changesFromHandler: case c := <-changesFromHandler:
@@ -276,8 +295,8 @@ func Run(name string, handler Handler) error {
return err return err
} }
ctlHandler := func(ctl uint32) uintptr { ctlHandler := func(ctl uint32, evtype uint32, evdata uintptr, context uintptr) uintptr {
e := ctlEvent{cmd: Cmd(ctl)} e := ctlEvent{cmd: Cmd(ctl), eventType: evtype, eventData: evdata}
// We assume that this callback function is running on // We assume that this callback function is running on
// the same thread as Run. Nowhere in MS documentation // the same thread as Run. Nowhere in MS documentation
// I could find statement to guarantee that. So putting // I could find statement to guarantee that. So putting
@@ -288,6 +307,7 @@ func Run(name string, handler Handler) error {
e.errno = sysErrNewThreadInCallback e.errno = sysErrNewThreadInCallback
} }
s.c <- e s.c <- e
// Always return NO_ERROR (0) for now.
return 0 return 0
} }
@@ -301,7 +321,7 @@ func Run(name string, handler Handler) error {
goWaitsH = uintptr(s.goWaits.h) goWaitsH = uintptr(s.goWaits.h)
cWaitsH = uintptr(s.cWaits.h) cWaitsH = uintptr(s.cWaits.h)
sName = t[0].ServiceName sName = t[0].ServiceName
ctlHandlerProc, err = newCallback(ctlHandler) ctlHandlerExProc, err = newCallback(ctlHandler)
if err != nil { if err != nil {
return err return err
} }
@@ -314,3 +334,10 @@ func Run(name string, handler Handler) error {
} }
return nil 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)
}
+4 -3
View File
@@ -22,7 +22,8 @@ TEXT ·servicemain(SB),7,$0
MOVL AX, (SP) MOVL AX, (SP)
MOVL $·servicectlhandler(SB), AX MOVL $·servicectlhandler(SB), AX
MOVL AX, 4(SP) MOVL AX, 4(SP)
MOVL ·cRegisterServiceCtrlHandlerW(SB), AX MOVL $0, 8(SP)
MOVL ·cRegisterServiceCtrlHandlerExW(SB), AX
MOVL SP, BP MOVL SP, BP
CALL AX CALL AX
MOVL BP, SP MOVL BP, SP
@@ -61,7 +62,7 @@ exit:
// I do not know why, but this seems to be the only way to call // I do not know why, but this seems to be the only way to call
// ctlHandlerProc on Windows 7. // 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 TEXT ·servicectlhandler(SB),7,$0
MOVL ·ctlHandlerProc(SB), CX MOVL ·ctlHandlerExProc(SB), CX
JMP CX JMP CX
+4 -3
View File
@@ -13,7 +13,8 @@ TEXT ·servicemain(SB),7,$0
MOVQ ·sName(SB), CX MOVQ ·sName(SB), CX
MOVQ $·servicectlhandler(SB), DX 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 CALL AX
CMPQ AX, $0 CMPQ AX, $0
JE exit JE exit
@@ -35,7 +36,7 @@ exit:
// I do not know why, but this seems to be the only way to call // I do not know why, but this seems to be the only way to call
// ctlHandlerProc on Windows 7. // 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 TEXT ·servicectlhandler(SB),7,$0
MOVQ ·ctlHandlerProc(SB), AX MOVQ ·ctlHandlerExProc(SB), AX
JMP AX JMP AX