initial commit
[govpp.git] / vendor / golang.org / x / sys / windows / svc / service.go
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         "golang.org/x/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 }