Merge pull request #1 from darkliquid/RegisterServiceCtrlHandlerExW

Add support for PRE_SHUTDOWN service control signals
This commit is contained in:
Steven Hartland 2015-02-02 09:31:33 +00:00
Родитель ea9e709e16 80c47eaec1
Коммит e4eca0c38e
21 изменённых файлов: 98 добавлений и 45 удалений

2
README
Просмотреть файл

@ -1 +1,3 @@
This repository holds Go packages that can be used to create Windows service. 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 package debug
import ( import (
"code.google.com/p/winsvc/svc"
"os" "os"
"os/signal" "os/signal"
"syscall" "syscall"
"github.com/multiplay/winsvc/svc"
) )
// Run executes service named name by calling appropriate handler function. // Run executes service named name by calling appropriate handler function.

Просмотреть файл

@ -7,8 +7,8 @@
package eventlog package eventlog
import ( import (
"code.google.com/p/winsvc/registry" "github.com/multiplay/winsvc/registry"
"code.google.com/p/winsvc/winapi" "github.com/multiplay/winsvc/winapi"
"errors" "errors"
"syscall" "syscall"
) )

Просмотреть файл

@ -9,7 +9,7 @@
package eventlog package eventlog
import ( import (
"code.google.com/p/winsvc/winapi" "github.com/multiplay/winsvc/winapi"
"errors" "errors"
"syscall" "syscall"
) )

Просмотреть файл

@ -7,7 +7,7 @@
package eventlog_test package eventlog_test
import ( import (
"code.google.com/p/winsvc/eventlog" "github.com/multiplay/winsvc/eventlog"
"testing" "testing"
) )

Просмотреть файл

@ -7,8 +7,8 @@
package main package main
import ( import (
"code.google.com/p/winsvc/eventlog" "github.com/multiplay/winsvc/eventlog"
"code.google.com/p/winsvc/mgr" "github.com/multiplay/winsvc/mgr"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"

Просмотреть файл

@ -13,7 +13,7 @@
package main package main
import ( import (
"code.google.com/p/winsvc/svc" "github.com/multiplay/winsvc/svc"
"fmt" "fmt"
"log" "log"
"os" "os"

Просмотреть файл

@ -7,8 +7,8 @@
package main package main
import ( import (
"code.google.com/p/winsvc/mgr" "github.com/multiplay/winsvc/mgr"
"code.google.com/p/winsvc/svc" "github.com/multiplay/winsvc/svc"
"fmt" "fmt"
"time" "time"
) )

Просмотреть файл

@ -7,11 +7,12 @@
package main package main
import ( import (
"code.google.com/p/winsvc/debug"
"code.google.com/p/winsvc/eventlog"
"code.google.com/p/winsvc/svc"
"fmt" "fmt"
"time" "time"
"github.com/multiplay/winsvc/debug"
"github.com/multiplay/winsvc/eventlog"
"github.com/multiplay/winsvc/svc"
) )
var elog debug.Log var elog debug.Log
@ -19,15 +20,19 @@ var elog debug.Log
type myservice struct{} type myservice struct{}
func (m *myservice) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) { 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} changes <- svc.Status{State: svc.StartPending}
fasttick := time.Tick(500 * time.Millisecond) fasttick := time.Tick(500 * time.Millisecond)
slowtick := time.Tick(2 * time.Second) slowtick := time.Tick(2 * time.Second)
tick := fasttick tick := fasttick
shutdown := make(chan struct{})
checkpoint := uint32(1)
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
loop: loop:
for { for {
select { select {
case <-shutdown:
break loop
case <-tick: case <-tick:
beep() beep()
case c := <-r: case c := <-r:
@ -39,6 +44,31 @@ loop:
changes <- c.CurrentStatus changes <- c.CurrentStatus
case svc.Stop, svc.Shutdown: case svc.Stop, svc.Shutdown:
break loop 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: case svc.Pause:
changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted} changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted}
tick = slowtick tick = slowtick
@ -50,7 +80,7 @@ loop:
} }
} }
} }
changes <- svc.Status{State: svc.StopPending} changes <- svc.Status{State: svc.StopPending, CheckPoint: checkpoint}
return return
} }

Просмотреть файл

@ -7,7 +7,7 @@
package mgr package mgr
import ( import (
"code.google.com/p/winsvc/winapi" "github.com/multiplay/winsvc/winapi"
"syscall" "syscall"
"unsafe" "unsafe"
) )

Просмотреть файл

@ -12,7 +12,7 @@
package mgr package mgr
import ( import (
"code.google.com/p/winsvc/winapi" "github.com/multiplay/winsvc/winapi"
"syscall" "syscall"
) )

Просмотреть файл

@ -7,7 +7,7 @@
package mgr_test package mgr_test
import ( import (
"code.google.com/p/winsvc/mgr" "github.com/multiplay/winsvc/mgr"
"os" "os"
"path/filepath" "path/filepath"
"testing" "testing"

Просмотреть файл

@ -7,8 +7,8 @@
package mgr package mgr
import ( import (
"code.google.com/p/winsvc/svc" "github.com/multiplay/winsvc/svc"
"code.google.com/p/winsvc/winapi" "github.com/multiplay/winsvc/winapi"
"syscall" "syscall"
) )

Просмотреть файл

@ -9,7 +9,7 @@
package registry package registry
import ( import (
"code.google.com/p/winsvc/winapi" "github.com/multiplay/winsvc/winapi"
"syscall" "syscall"
"unsafe" "unsafe"
) )

Просмотреть файл

@ -7,8 +7,8 @@
package svc package svc
import ( import (
"code.google.com/p/winsvc/winapi"
"errors" "errors"
"github.com/multiplay/winsvc/winapi"
"syscall" "syscall"
) )

Просмотреть файл

@ -7,7 +7,7 @@
package svc package svc
import ( import (
"code.google.com/p/winsvc/winapi" "github.com/multiplay/winsvc/winapi"
"syscall" "syscall"
"unsafe" "unsafe"
) )

Просмотреть файл

@ -9,8 +9,8 @@
package svc package svc
import ( import (
"code.google.com/p/winsvc/winapi"
"errors" "errors"
"github.com/multiplay/winsvc/winapi"
"runtime" "runtime"
"syscall" "syscall"
"unsafe" "unsafe"
@ -32,6 +32,7 @@ const (
// Cmd represents service state change request. It is sent to a service // Cmd represents service state change request. It is sent to a service
// by the service manager, and should be actioned upon by the service. // by the service manager, and should be actioned upon by the service.
type Cmd uint32 type Cmd uint32
type EventType uint32
const ( const (
Stop = Cmd(winapi.SERVICE_CONTROL_STOP) Stop = Cmd(winapi.SERVICE_CONTROL_STOP)
@ -39,6 +40,7 @@ const (
Continue = Cmd(winapi.SERVICE_CONTROL_CONTINUE) Continue = Cmd(winapi.SERVICE_CONTROL_CONTINUE)
Interrogate = Cmd(winapi.SERVICE_CONTROL_INTERROGATE) Interrogate = Cmd(winapi.SERVICE_CONTROL_INTERROGATE)
Shutdown = Cmd(winapi.SERVICE_CONTROL_SHUTDOWN) Shutdown = Cmd(winapi.SERVICE_CONTROL_SHUTDOWN)
PreShutdown = Cmd(winapi.SERVICE_CONTROL_PRESHUTDOWN)
) )
// Accepted is used to describe commands accepted by the service. // Accepted is used to describe commands accepted by the service.
@ -48,6 +50,7 @@ type Accepted uint32
const ( const (
AcceptStop = Accepted(winapi.SERVICE_ACCEPT_STOP) AcceptStop = Accepted(winapi.SERVICE_ACCEPT_STOP)
AcceptShutdown = Accepted(winapi.SERVICE_ACCEPT_SHUTDOWN) AcceptShutdown = Accepted(winapi.SERVICE_ACCEPT_SHUTDOWN)
AcceptPreShutdown = Accepted(winapi.SERVICE_ACCEPT_PRESHUTDOWN)
AcceptPauseAndContinue = Accepted(winapi.SERVICE_ACCEPT_PAUSE_CONTINUE) AcceptPauseAndContinue = Accepted(winapi.SERVICE_ACCEPT_PAUSE_CONTINUE)
) )
@ -83,16 +86,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 ctlHandlerProc uintptr
cSetEvent uintptr cSetEvent uintptr
cWaitForSingleObject uintptr cWaitForSingleObject uintptr
cRegisterServiceCtrlHandlerW uintptr cRegisterServiceCtrlHandlerExW uintptr
) )
func init() { func init() {
@ -100,12 +103,15 @@ 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()
} }
type ctlEvent struct { type ctlEvent struct {
cmd Cmd cmd Cmd
errno uint32 eventType EventType
eventData uintptr
context uintptr
errno uint32
} }
// service provides access to windows service api. // 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 { if status.Accepts&AcceptShutdown != 0 {
t.ControlsAccepted |= winapi.SERVICE_ACCEPT_SHUTDOWN t.ControlsAccepted |= winapi.SERVICE_ACCEPT_SHUTDOWN
} }
if status.Accepts&AcceptPreShutdown != 0 {
t.ControlsAccepted |= winapi.SERVICE_ACCEPT_PRESHUTDOWN
}
if status.Accepts&AcceptPauseAndContinue != 0 { if status.Accepts&AcceptPauseAndContinue != 0 {
t.ControlsAccepted |= winapi.SERVICE_ACCEPT_PAUSE_CONTINUE t.ControlsAccepted |= winapi.SERVICE_ACCEPT_PAUSE_CONTINUE
} }
@ -274,8 +283,8 @@ func Run(name string, handler Handler) error {
return err return err
} }
ctlHandler := func(ctl uint32) uintptr { ctlHandler := func(ctl uint32, event uint32, eventData uintptr, ctx uintptr) uintptr {
e := ctlEvent{cmd: Cmd(ctl)} e := ctlEvent{cmd: Cmd(ctl), eventType: EventType(event), eventData: eventData, context: ctx}
// 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

Просмотреть файл

@ -7,14 +7,15 @@
package svc_test package svc_test
import ( import (
"code.google.com/p/winsvc/mgr"
"code.google.com/p/winsvc/svc"
"io/ioutil" "io/ioutil"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"testing" "testing"
"time" "time"
"github.com/multiplay/winsvc/mgr"
"github.com/multiplay/winsvc/svc"
) )
func getState(t *testing.T, s *mgr.Service) svc.State { func getState(t *testing.T, s *mgr.Service) svc.State {
@ -61,7 +62,7 @@ func TestExample(t *testing.T) {
defer os.RemoveAll(dir) defer os.RemoveAll(dir)
exepath := filepath.Join(dir, "a.exe") 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 { if err != nil {
t.Fatalf("failed to build service program: %v\n%v", err, string(o)) 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 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

Просмотреть файл

@ -8,11 +8,12 @@ TEXT ·servicemain(SB),7,$0
MOVL CX, ·sArgc(SB) MOVL CX, ·sArgc(SB)
MOVL DX, ·sArgv(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 ·sName(SB), CX
MOVQ $·servicectlhandler(SB), DX MOVQ $·servicectlhandler(SB), DX
MOVQ ·cRegisterServiceCtrlHandlerW(SB), AX MOVQ $0, R8
MOVQ ·cRegisterServiceCtrlHandlerExW(SB), AX
CALL AX CALL AX
CMPQ AX, $0 CMPQ AX, $0
JE exit JE exit

Просмотреть файл

@ -70,6 +70,10 @@ const (
SERVICE_ACCEPT_HARDWAREPROFILECHANGE SERVICE_ACCEPT_HARDWAREPROFILECHANGE
SERVICE_ACCEPT_POWEREVENT SERVICE_ACCEPT_POWEREVENT
SERVICE_ACCEPT_SESSIONCHANGE SERVICE_ACCEPT_SESSIONCHANGE
SERVICE_ACCEPT_PRESHUTDOWN
SERVICE_ACCEPT_TIMECHANGE
SERVICE_ACCEPT_TRIGGEREVENT
SERVICE_ACCEPT_USERMODEREBOOT
) )
const ( const (
@ -87,6 +91,10 @@ const (
SERVICE_CONTROL_HARDWAREPROFILECHANGE SERVICE_CONTROL_HARDWAREPROFILECHANGE
SERVICE_CONTROL_POWEREVENT SERVICE_CONTROL_POWEREVENT
SERVICE_CONTROL_SESSIONCHANGE SERVICE_CONTROL_SESSIONCHANGE
SERVICE_CONTROL_PRESHUTDOWN
SERVICE_CONTROL_TIMECHANGE
SERVICE_CONTROL_TRIGGEREVENT = 0x00000020
SERVICE_CONTROL_USERMODEREBOOT = 0x00000040
) )
const ( const (