Merge pull request #1 from darkliquid/RegisterServiceCtrlHandlerExW
Add support for PRE_SHUTDOWN service control signals
This commit is contained in:
Коммит
e4eca0c38e
2
README
2
README
|
@ -1 +1,3 @@
|
|||
This repository holds Go packages that can be used to create Windows service.
|
||||
It includes additional patches from Multiplay to support the extended service
|
||||
control signals such as PRE_SHUTDOWN.
|
||||
|
|
|
@ -9,10 +9,11 @@
|
|||
package debug
|
||||
|
||||
import (
|
||||
"code.google.com/p/winsvc/svc"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/multiplay/winsvc/svc"
|
||||
)
|
||||
|
||||
// Run executes service named name by calling appropriate handler function.
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
package eventlog
|
||||
|
||||
import (
|
||||
"code.google.com/p/winsvc/registry"
|
||||
"code.google.com/p/winsvc/winapi"
|
||||
"github.com/multiplay/winsvc/registry"
|
||||
"github.com/multiplay/winsvc/winapi"
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
package eventlog
|
||||
|
||||
import (
|
||||
"code.google.com/p/winsvc/winapi"
|
||||
"github.com/multiplay/winsvc/winapi"
|
||||
"errors"
|
||||
"syscall"
|
||||
)
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
package eventlog_test
|
||||
|
||||
import (
|
||||
"code.google.com/p/winsvc/eventlog"
|
||||
"github.com/multiplay/winsvc/eventlog"
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"code.google.com/p/winsvc/eventlog"
|
||||
"code.google.com/p/winsvc/mgr"
|
||||
"github.com/multiplay/winsvc/eventlog"
|
||||
"github.com/multiplay/winsvc/mgr"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"code.google.com/p/winsvc/svc"
|
||||
"github.com/multiplay/winsvc/svc"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"code.google.com/p/winsvc/mgr"
|
||||
"code.google.com/p/winsvc/svc"
|
||||
"github.com/multiplay/winsvc/mgr"
|
||||
"github.com/multiplay/winsvc/svc"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
|
|
@ -7,11 +7,12 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"code.google.com/p/winsvc/debug"
|
||||
"code.google.com/p/winsvc/eventlog"
|
||||
"code.google.com/p/winsvc/svc"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/multiplay/winsvc/debug"
|
||||
"github.com/multiplay/winsvc/eventlog"
|
||||
"github.com/multiplay/winsvc/svc"
|
||||
)
|
||||
|
||||
var elog debug.Log
|
||||
|
@ -19,15 +20,19 @@ var elog debug.Log
|
|||
type myservice struct{}
|
||||
|
||||
func (m *myservice) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
|
||||
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
|
||||
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue | svc.AcceptPreShutdown
|
||||
changes <- svc.Status{State: svc.StartPending}
|
||||
fasttick := time.Tick(500 * time.Millisecond)
|
||||
slowtick := time.Tick(2 * time.Second)
|
||||
tick := fasttick
|
||||
shutdown := make(chan struct{})
|
||||
checkpoint := uint32(1)
|
||||
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-shutdown:
|
||||
break loop
|
||||
case <-tick:
|
||||
beep()
|
||||
case c := <-r:
|
||||
|
@ -39,6 +44,31 @@ loop:
|
|||
changes <- c.CurrentStatus
|
||||
case svc.Stop, svc.Shutdown:
|
||||
break loop
|
||||
case svc.PreShutdown:
|
||||
go func() {
|
||||
defer close(shutdown)
|
||||
// This for loop represents some long running process that
|
||||
// allows for periodically updating the service state so the
|
||||
// SCM doesn't think the process has become non-responsive
|
||||
for {
|
||||
// This sends a status update, with an increased
|
||||
// checkpoint so the SCM doesn't think progress has
|
||||
// stalled and a wait hint larger than the expected
|
||||
// time till the next update (in this case our fake
|
||||
// 'work' process is a time.Sleep).
|
||||
//
|
||||
// After 30 seconds of 'work', we return, closing the
|
||||
// shutdown channel, which breaks the loop, which allows
|
||||
// the server to fallthrough to the Stopped state after
|
||||
// the Execute method returns.
|
||||
changes <- svc.Status{State: svc.StopPending, WaitHint: 3000, CheckPoint: checkpoint}
|
||||
time.Sleep(1 * time.Second)
|
||||
checkpoint++
|
||||
if checkpoint > 30 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
case svc.Pause:
|
||||
changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted}
|
||||
tick = slowtick
|
||||
|
@ -50,7 +80,7 @@ loop:
|
|||
}
|
||||
}
|
||||
}
|
||||
changes <- svc.Status{State: svc.StopPending}
|
||||
changes <- svc.Status{State: svc.StopPending, CheckPoint: checkpoint}
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
package mgr
|
||||
|
||||
import (
|
||||
"code.google.com/p/winsvc/winapi"
|
||||
"github.com/multiplay/winsvc/winapi"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
package mgr
|
||||
|
||||
import (
|
||||
"code.google.com/p/winsvc/winapi"
|
||||
"github.com/multiplay/winsvc/winapi"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
package mgr_test
|
||||
|
||||
import (
|
||||
"code.google.com/p/winsvc/mgr"
|
||||
"github.com/multiplay/winsvc/mgr"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
package mgr
|
||||
|
||||
import (
|
||||
"code.google.com/p/winsvc/svc"
|
||||
"code.google.com/p/winsvc/winapi"
|
||||
"github.com/multiplay/winsvc/svc"
|
||||
"github.com/multiplay/winsvc/winapi"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
package registry
|
||||
|
||||
import (
|
||||
"code.google.com/p/winsvc/winapi"
|
||||
"github.com/multiplay/winsvc/winapi"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
|
|
@ -7,8 +7,8 @@
|
|||
package svc
|
||||
|
||||
import (
|
||||
"code.google.com/p/winsvc/winapi"
|
||||
"errors"
|
||||
"github.com/multiplay/winsvc/winapi"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
package svc
|
||||
|
||||
import (
|
||||
"code.google.com/p/winsvc/winapi"
|
||||
"github.com/multiplay/winsvc/winapi"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
|
|
@ -9,8 +9,8 @@
|
|||
package svc
|
||||
|
||||
import (
|
||||
"code.google.com/p/winsvc/winapi"
|
||||
"errors"
|
||||
"github.com/multiplay/winsvc/winapi"
|
||||
"runtime"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
@ -32,6 +32,7 @@ const (
|
|||
// Cmd represents service state change request. It is sent to a service
|
||||
// by the service manager, and should be actioned upon by the service.
|
||||
type Cmd uint32
|
||||
type EventType uint32
|
||||
|
||||
const (
|
||||
Stop = Cmd(winapi.SERVICE_CONTROL_STOP)
|
||||
|
@ -39,6 +40,7 @@ const (
|
|||
Continue = Cmd(winapi.SERVICE_CONTROL_CONTINUE)
|
||||
Interrogate = Cmd(winapi.SERVICE_CONTROL_INTERROGATE)
|
||||
Shutdown = Cmd(winapi.SERVICE_CONTROL_SHUTDOWN)
|
||||
PreShutdown = Cmd(winapi.SERVICE_CONTROL_PRESHUTDOWN)
|
||||
)
|
||||
|
||||
// Accepted is used to describe commands accepted by the service.
|
||||
|
@ -48,6 +50,7 @@ type Accepted uint32
|
|||
const (
|
||||
AcceptStop = Accepted(winapi.SERVICE_ACCEPT_STOP)
|
||||
AcceptShutdown = Accepted(winapi.SERVICE_ACCEPT_SHUTDOWN)
|
||||
AcceptPreShutdown = Accepted(winapi.SERVICE_ACCEPT_PRESHUTDOWN)
|
||||
AcceptPauseAndContinue = Accepted(winapi.SERVICE_ACCEPT_PAUSE_CONTINUE)
|
||||
)
|
||||
|
||||
|
@ -83,16 +86,16 @@ type Handler interface {
|
|||
|
||||
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
|
||||
ctlHandlerProc uintptr
|
||||
cSetEvent uintptr
|
||||
cWaitForSingleObject uintptr
|
||||
cRegisterServiceCtrlHandlerExW uintptr
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -100,12 +103,15 @@ func init() {
|
|||
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()
|
||||
}
|
||||
|
||||
type ctlEvent struct {
|
||||
cmd Cmd
|
||||
errno uint32
|
||||
cmd Cmd
|
||||
eventType EventType
|
||||
eventData uintptr
|
||||
context uintptr
|
||||
errno uint32
|
||||
}
|
||||
|
||||
// service provides access to windows service api.
|
||||
|
@ -160,6 +166,9 @@ func (s *service) updateStatus(status *Status, ec *exitCode) error {
|
|||
if status.Accepts&AcceptShutdown != 0 {
|
||||
t.ControlsAccepted |= winapi.SERVICE_ACCEPT_SHUTDOWN
|
||||
}
|
||||
if status.Accepts&AcceptPreShutdown != 0 {
|
||||
t.ControlsAccepted |= winapi.SERVICE_ACCEPT_PRESHUTDOWN
|
||||
}
|
||||
if status.Accepts&AcceptPauseAndContinue != 0 {
|
||||
t.ControlsAccepted |= winapi.SERVICE_ACCEPT_PAUSE_CONTINUE
|
||||
}
|
||||
|
@ -274,8 +283,8 @@ func Run(name string, handler Handler) error {
|
|||
return err
|
||||
}
|
||||
|
||||
ctlHandler := func(ctl uint32) uintptr {
|
||||
e := ctlEvent{cmd: Cmd(ctl)}
|
||||
ctlHandler := func(ctl uint32, event uint32, eventData uintptr, ctx uintptr) uintptr {
|
||||
e := ctlEvent{cmd: Cmd(ctl), eventType: EventType(event), eventData: eventData, context: ctx}
|
||||
// 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
|
||||
|
|
|
@ -7,14 +7,15 @@
|
|||
package svc_test
|
||||
|
||||
import (
|
||||
"code.google.com/p/winsvc/mgr"
|
||||
"code.google.com/p/winsvc/svc"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/multiplay/winsvc/mgr"
|
||||
"github.com/multiplay/winsvc/svc"
|
||||
)
|
||||
|
||||
func getState(t *testing.T, s *mgr.Service) svc.State {
|
||||
|
@ -61,7 +62,7 @@ func TestExample(t *testing.T) {
|
|||
defer os.RemoveAll(dir)
|
||||
|
||||
exepath := filepath.Join(dir, "a.exe")
|
||||
o, err := exec.Command("go", "build", "-o", exepath, "code.google.com/p/winsvc/example").CombinedOutput()
|
||||
o, err := exec.Command("go", "build", "-o", exepath, "github.com/multiplay/winsvc/example").CombinedOutput()
|
||||
if err != nil {
|
||||
t.Fatalf("failed to build service program: %v\n%v", err, string(o))
|
||||
}
|
||||
|
|
|
@ -21,7 +21,8 @@ TEXT ·servicemain(SB),7,$0
|
|||
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
|
||||
|
|
|
@ -8,11 +8,12 @@ TEXT ·servicemain(SB),7,$0
|
|||
MOVL CX, ·sArgc(SB)
|
||||
MOVL DX, ·sArgv(SB)
|
||||
|
||||
SUBQ $32, SP // stack for the first 4 syscall params
|
||||
SUBQ $32, SP // stack for the first 4 syscall params
|
||||
|
||||
MOVQ ·sName(SB), CX
|
||||
MOVQ $·servicectlhandler(SB), DX
|
||||
MOVQ ·cRegisterServiceCtrlHandlerW(SB), AX
|
||||
MOVQ $0, R8
|
||||
MOVQ ·cRegisterServiceCtrlHandlerExW(SB), AX
|
||||
CALL AX
|
||||
CMPQ AX, $0
|
||||
JE exit
|
||||
|
|
|
@ -70,6 +70,10 @@ const (
|
|||
SERVICE_ACCEPT_HARDWAREPROFILECHANGE
|
||||
SERVICE_ACCEPT_POWEREVENT
|
||||
SERVICE_ACCEPT_SESSIONCHANGE
|
||||
SERVICE_ACCEPT_PRESHUTDOWN
|
||||
SERVICE_ACCEPT_TIMECHANGE
|
||||
SERVICE_ACCEPT_TRIGGEREVENT
|
||||
SERVICE_ACCEPT_USERMODEREBOOT
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -87,6 +91,10 @@ const (
|
|||
SERVICE_CONTROL_HARDWAREPROFILECHANGE
|
||||
SERVICE_CONTROL_POWEREVENT
|
||||
SERVICE_CONTROL_SESSIONCHANGE
|
||||
SERVICE_CONTROL_PRESHUTDOWN
|
||||
SERVICE_CONTROL_TIMECHANGE
|
||||
SERVICE_CONTROL_TRIGGEREVENT = 0x00000020
|
||||
SERVICE_CONTROL_USERMODEREBOOT = 0x00000040
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
Загрузка…
Ссылка в новой задаче