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.
|
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 (
|
||||||
|
|
Загрузка…
Ссылка в новой задаче