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.
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 (