~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/gabriel-samfira/sys/windows/svc/service.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
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.
 
4
 
 
5
// +build windows
 
6
 
 
7
// Package svc provides everything required to build Windows service.
 
8
//
 
9
package svc
 
10
 
 
11
import (
 
12
        "errors"
 
13
        "runtime"
 
14
        "syscall"
 
15
        "unsafe"
 
16
 
 
17
        "github.com/gabriel-samfira/sys/windows"
 
18
)
 
19
 
 
20
// State describes service execution state (Stopped, Running and so on).
 
21
type State uint32
 
22
 
 
23
const (
 
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)
 
31
)
 
32
 
 
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.
 
35
type Cmd uint32
 
36
 
 
37
const (
 
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)
 
43
)
 
44
 
 
45
// Accepted is used to describe commands accepted by the service.
 
46
// Note that Interrogate is always accepted.
 
47
type Accepted uint32
 
48
 
 
49
const (
 
50
        AcceptStop             = Accepted(windows.SERVICE_ACCEPT_STOP)
 
51
        AcceptShutdown         = Accepted(windows.SERVICE_ACCEPT_SHUTDOWN)
 
52
        AcceptPauseAndContinue = Accepted(windows.SERVICE_ACCEPT_PAUSE_CONTINUE)
 
53
)
 
54
 
 
55
// Status combines State and Accepted commands to fully describe running service.
 
56
type Status struct {
 
57
        State      State
 
58
        Accepts    Accepted
 
59
        CheckPoint uint32 // used to report progress during a lengthy operation
 
60
        WaitHint   uint32 // estimated time required for a pending operation, in milliseconds
 
61
}
 
62
 
 
63
// ChangeRequest is sent to the service Handler to request service status change.
 
64
type ChangeRequest struct {
 
65
        Cmd           Cmd
 
66
        CurrentStatus Status
 
67
}
 
68
 
 
69
// Handler is the interface that must be implemented to build Windows service.
 
70
type Handler interface {
 
71
 
 
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
 
78
        // to the service.
 
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
 
82
        // parameter.
 
83
        Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32)
 
84
}
 
85
 
 
86
var (
 
87
        // These are used by asm code.
 
88
        goWaitsH                     uintptr
 
89
        cWaitsH                      uintptr
 
90
        ssHandle                     uintptr
 
91
        sName                        *uint16
 
92
        sArgc                        uintptr
 
93
        sArgv                        **uint16
 
94
        ctlHandlerProc               uintptr
 
95
        cSetEvent                    uintptr
 
96
        cWaitForSingleObject         uintptr
 
97
        cRegisterServiceCtrlHandlerW uintptr
 
98
)
 
99
 
 
100
func init() {
 
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()
 
106
}
 
107
 
 
108
type ctlEvent struct {
 
109
        cmd   Cmd
 
110
        errno uint32
 
111
}
 
112
 
 
113
// service provides access to windows service api.
 
114
type service struct {
 
115
        name    string
 
116
        h       windows.Handle
 
117
        cWaits  *event
 
118
        goWaits *event
 
119
        c       chan ctlEvent
 
120
        handler Handler
 
121
}
 
122
 
 
123
func newService(name string, handler Handler) (*service, error) {
 
124
        var s service
 
125
        var err error
 
126
        s.name = name
 
127
        s.c = make(chan ctlEvent)
 
128
        s.handler = handler
 
129
        s.cWaits, err = newEvent()
 
130
        if err != nil {
 
131
                return nil, err
 
132
        }
 
133
        s.goWaits, err = newEvent()
 
134
        if err != nil {
 
135
                s.cWaits.Close()
 
136
                return nil, err
 
137
        }
 
138
        return &s, nil
 
139
}
 
140
 
 
141
func (s *service) close() error {
 
142
        s.cWaits.Close()
 
143
        s.goWaits.Close()
 
144
        return nil
 
145
}
 
146
 
 
147
type exitCode struct {
 
148
        isSvcSpecific bool
 
149
        errno         uint32
 
150
}
 
151
 
 
152
func (s *service) updateStatus(status *Status, ec *exitCode) error {
 
153
        if s.h == 0 {
 
154
                return errors.New("updateStatus with no service status handle")
 
155
        }
 
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
 
161
        }
 
162
        if status.Accepts&AcceptShutdown != 0 {
 
163
                t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN
 
164
        }
 
165
        if status.Accepts&AcceptPauseAndContinue != 0 {
 
166
                t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE
 
167
        }
 
168
        if ec.errno == 0 {
 
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
 
174
        } else {
 
175
                t.Win32ExitCode = ec.errno
 
176
                t.ServiceSpecificExitCode = windows.NO_ERROR
 
177
        }
 
178
        t.CheckPoint = status.CheckPoint
 
179
        t.WaitHint = status.WaitHint
 
180
        return windows.SetServiceStatus(s.h, &t)
 
181
}
 
182
 
 
183
const (
 
184
        sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota
 
185
        sysErrNewThreadInCallback
 
186
)
 
187
 
 
188
func (s *service) run() {
 
189
        s.goWaits.Wait()
 
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))[:])
 
195
        }
 
196
 
 
197
        cmdsToHandler := make(chan ChangeRequest)
 
198
        changesFromHandler := make(chan Status)
 
199
        exitFromHandler := make(chan exitCode)
 
200
 
 
201
        go func() {
 
202
                ss, errno := s.handler.Execute(args, cmdsToHandler, changesFromHandler)
 
203
                exitFromHandler <- exitCode{ss, errno}
 
204
        }()
 
205
 
 
206
        status := Status{State: Stopped}
 
207
        ec := exitCode{isSvcSpecific: true, errno: 0}
 
208
        var outch chan ChangeRequest
 
209
        inch := s.c
 
210
        var cmd Cmd
 
211
loop:
 
212
        for {
 
213
                select {
 
214
                case r := <-inch:
 
215
                        if r.errno != 0 {
 
216
                                ec.errno = r.errno
 
217
                                break loop
 
218
                        }
 
219
                        inch = nil
 
220
                        outch = cmdsToHandler
 
221
                        cmd = r.cmd
 
222
                case outch <- ChangeRequest{cmd, status}:
 
223
                        inch = s.c
 
224
                        outch = nil
 
225
                case c := <-changesFromHandler:
 
226
                        err := s.updateStatus(&c, &ec)
 
227
                        if err != nil {
 
228
                                // best suitable error number
 
229
                                ec.errno = sysErrSetServiceStatusFailed
 
230
                                if err2, ok := err.(syscall.Errno); ok {
 
231
                                        ec.errno = uint32(err2)
 
232
                                }
 
233
                                break loop
 
234
                        }
 
235
                        status = c
 
236
                case ec = <-exitFromHandler:
 
237
                        break loop
 
238
                }
 
239
        }
 
240
 
 
241
        s.updateStatus(&Status{State: Stopped}, &ec)
 
242
        s.cWaits.Set()
 
243
}
 
244
 
 
245
func newCallback(fn interface{}) (cb uintptr, err error) {
 
246
        defer func() {
 
247
                r := recover()
 
248
                if r == nil {
 
249
                        return
 
250
                }
 
251
                cb = 0
 
252
                switch v := r.(type) {
 
253
                case string:
 
254
                        err = errors.New(v)
 
255
                case error:
 
256
                        err = v
 
257
                default:
 
258
                        err = errors.New("unexpected panic in syscall.NewCallback")
 
259
                }
 
260
        }()
 
261
        return syscall.NewCallback(fn), nil
 
262
}
 
263
 
 
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.
 
267
 
 
268
// Run executes service name by calling appropriate handler function.
 
269
func Run(name string, handler Handler) error {
 
270
        runtime.LockOSThread()
 
271
 
 
272
        tid := windows.GetCurrentThreadId()
 
273
 
 
274
        s, err := newService(name, handler)
 
275
        if err != nil {
 
276
                return err
 
277
        }
 
278
 
 
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()
 
287
                if i != tid {
 
288
                        e.errno = sysErrNewThreadInCallback
 
289
                }
 
290
                s.c <- e
 
291
                return 0
 
292
        }
 
293
 
 
294
        var svcmain uintptr
 
295
        getServiceMain(&svcmain)
 
296
        t := []windows.SERVICE_TABLE_ENTRY{
 
297
                {syscall.StringToUTF16Ptr(s.name), svcmain},
 
298
                {nil, 0},
 
299
        }
 
300
 
 
301
        goWaitsH = uintptr(s.goWaits.h)
 
302
        cWaitsH = uintptr(s.cWaits.h)
 
303
        sName = t[0].ServiceName
 
304
        ctlHandlerProc, err = newCallback(ctlHandler)
 
305
        if err != nil {
 
306
                return err
 
307
        }
 
308
 
 
309
        go s.run()
 
310
 
 
311
        err = windows.StartServiceCtrlDispatcher(&t[0])
 
312
        if err != nil {
 
313
                return err
 
314
        }
 
315
        return nil
 
316
}