1
// Copyright 2012 The Go Authors. All rights reserved.
2
// Use of this source code is governed by a BSD-style
3
// license that can be found in the LICENSE file.
7
// Package svc provides everything required to build Windows service.
17
"github.com/gabriel-samfira/sys/windows"
20
// State describes service execution state (Stopped, Running and so on).
24
Stopped = State(windows.SERVICE_STOPPED)
25
StartPending = State(windows.SERVICE_START_PENDING)
26
StopPending = State(windows.SERVICE_STOP_PENDING)
27
Running = State(windows.SERVICE_RUNNING)
28
ContinuePending = State(windows.SERVICE_CONTINUE_PENDING)
29
PausePending = State(windows.SERVICE_PAUSE_PENDING)
30
Paused = State(windows.SERVICE_PAUSED)
33
// Cmd represents service state change request. It is sent to a service
34
// by the service manager, and should be actioned upon by the service.
38
Stop = Cmd(windows.SERVICE_CONTROL_STOP)
39
Pause = Cmd(windows.SERVICE_CONTROL_PAUSE)
40
Continue = Cmd(windows.SERVICE_CONTROL_CONTINUE)
41
Interrogate = Cmd(windows.SERVICE_CONTROL_INTERROGATE)
42
Shutdown = Cmd(windows.SERVICE_CONTROL_SHUTDOWN)
45
// Accepted is used to describe commands accepted by the service.
46
// Note that Interrogate is always accepted.
50
AcceptStop = Accepted(windows.SERVICE_ACCEPT_STOP)
51
AcceptShutdown = Accepted(windows.SERVICE_ACCEPT_SHUTDOWN)
52
AcceptPauseAndContinue = Accepted(windows.SERVICE_ACCEPT_PAUSE_CONTINUE)
55
// Status combines State and Accepted commands to fully describe running service.
59
CheckPoint uint32 // used to report progress during a lengthy operation
60
WaitHint uint32 // estimated time required for a pending operation, in milliseconds
63
// ChangeRequest is sent to the service Handler to request service status change.
64
type ChangeRequest struct {
69
// Handler is the interface that must be implemented to build Windows service.
70
type Handler interface {
72
// Execute will be called by the package code at the start of
73
// the service, and the service will exit once Execute completes.
74
// Inside Execute you must read service change requests from r and
75
// act accordingly. You must keep service control manager up to date
76
// about state of your service by writing into s as required.
77
// args contains service name followed by argument strings passed
79
// You can provide service exit code in exitCode return parameter,
80
// with 0 being "no error". You can also indicate if exit code,
81
// if any, is service specific or not by using svcSpecificEC
83
Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32)
87
// These are used by asm code.
94
ctlHandlerProc uintptr
96
cWaitForSingleObject uintptr
97
cRegisterServiceCtrlHandlerW uintptr
101
k := syscall.MustLoadDLL("kernel32.dll")
102
cSetEvent = k.MustFindProc("SetEvent").Addr()
103
cWaitForSingleObject = k.MustFindProc("WaitForSingleObject").Addr()
104
a := syscall.MustLoadDLL("advapi32.dll")
105
cRegisterServiceCtrlHandlerW = a.MustFindProc("RegisterServiceCtrlHandlerW").Addr()
108
type ctlEvent struct {
113
// service provides access to windows service api.
114
type service struct {
123
func newService(name string, handler Handler) (*service, error) {
127
s.c = make(chan ctlEvent)
129
s.cWaits, err = newEvent()
133
s.goWaits, err = newEvent()
141
func (s *service) close() error {
147
type exitCode struct {
152
func (s *service) updateStatus(status *Status, ec *exitCode) error {
154
return errors.New("updateStatus with no service status handle")
156
var t windows.SERVICE_STATUS
157
t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS
158
t.CurrentState = uint32(status.State)
159
if status.Accepts&AcceptStop != 0 {
160
t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP
162
if status.Accepts&AcceptShutdown != 0 {
163
t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN
165
if status.Accepts&AcceptPauseAndContinue != 0 {
166
t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE
169
t.Win32ExitCode = windows.NO_ERROR
170
t.ServiceSpecificExitCode = windows.NO_ERROR
171
} else if ec.isSvcSpecific {
172
t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR)
173
t.ServiceSpecificExitCode = ec.errno
175
t.Win32ExitCode = ec.errno
176
t.ServiceSpecificExitCode = windows.NO_ERROR
178
t.CheckPoint = status.CheckPoint
179
t.WaitHint = status.WaitHint
180
return windows.SetServiceStatus(s.h, &t)
184
sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota
185
sysErrNewThreadInCallback
188
func (s *service) run() {
190
s.h = windows.Handle(ssHandle)
191
argv := (*[100]*int16)(unsafe.Pointer(sArgv))[:sArgc]
192
args := make([]string, len(argv))
193
for i, a := range argv {
194
args[i] = syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(a))[:])
197
cmdsToHandler := make(chan ChangeRequest)
198
changesFromHandler := make(chan Status)
199
exitFromHandler := make(chan exitCode)
202
ss, errno := s.handler.Execute(args, cmdsToHandler, changesFromHandler)
203
exitFromHandler <- exitCode{ss, errno}
206
status := Status{State: Stopped}
207
ec := exitCode{isSvcSpecific: true, errno: 0}
208
var outch chan ChangeRequest
220
outch = cmdsToHandler
222
case outch <- ChangeRequest{cmd, status}:
225
case c := <-changesFromHandler:
226
err := s.updateStatus(&c, &ec)
228
// best suitable error number
229
ec.errno = sysErrSetServiceStatusFailed
230
if err2, ok := err.(syscall.Errno); ok {
231
ec.errno = uint32(err2)
236
case ec = <-exitFromHandler:
241
s.updateStatus(&Status{State: Stopped}, &ec)
245
func newCallback(fn interface{}) (cb uintptr, err error) {
252
switch v := r.(type) {
258
err = errors.New("unexpected panic in syscall.NewCallback")
261
return syscall.NewCallback(fn), nil
264
// BUG(brainman): There is no mechanism to run multiple services
265
// inside one single executable. Perhaps, it can be overcome by
266
// using RegisterServiceCtrlHandlerEx Windows api.
268
// Run executes service name by calling appropriate handler function.
269
func Run(name string, handler Handler) error {
270
runtime.LockOSThread()
272
tid := windows.GetCurrentThreadId()
274
s, err := newService(name, handler)
279
ctlHandler := func(ctl uint32) uintptr {
280
e := ctlEvent{cmd: Cmd(ctl)}
281
// We assume that this callback function is running on
282
// the same thread as Run. Nowhere in MS documentation
283
// I could find statement to guarantee that. So putting
284
// check here to verify, otherwise things will go bad
285
// quickly, if ignored.
286
i := windows.GetCurrentThreadId()
288
e.errno = sysErrNewThreadInCallback
295
getServiceMain(&svcmain)
296
t := []windows.SERVICE_TABLE_ENTRY{
297
{syscall.StringToUTF16Ptr(s.name), svcmain},
301
goWaitsH = uintptr(s.goWaits.h)
302
cWaitsH = uintptr(s.cWaits.h)
303
sName = t[0].ServiceName
304
ctlHandlerProc, err = newCallback(ctlHandler)
311
err = windows.StartServiceCtrlDispatcher(&t[0])