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 "golang.org/x/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)
43 ParamChange = Cmd(windows.SERVICE_CONTROL_PARAMCHANGE)
44 NetBindAdd = Cmd(windows.SERVICE_CONTROL_NETBINDADD)
45 NetBindRemove = Cmd(windows.SERVICE_CONTROL_NETBINDREMOVE)
46 NetBindEnable = Cmd(windows.SERVICE_CONTROL_NETBINDENABLE)
47 NetBindDisable = Cmd(windows.SERVICE_CONTROL_NETBINDDISABLE)
48 DeviceEvent = Cmd(windows.SERVICE_CONTROL_DEVICEEVENT)
49 HardwareProfileChange = Cmd(windows.SERVICE_CONTROL_HARDWAREPROFILECHANGE)
50 PowerEvent = Cmd(windows.SERVICE_CONTROL_POWEREVENT)
51 SessionChange = Cmd(windows.SERVICE_CONTROL_SESSIONCHANGE)
54 // Accepted is used to describe commands accepted by the service.
55 // Note that Interrogate is always accepted.
59 AcceptStop = Accepted(windows.SERVICE_ACCEPT_STOP)
60 AcceptShutdown = Accepted(windows.SERVICE_ACCEPT_SHUTDOWN)
61 AcceptPauseAndContinue = Accepted(windows.SERVICE_ACCEPT_PAUSE_CONTINUE)
62 AcceptParamChange = Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)
63 AcceptNetBindChange = Accepted(windows.SERVICE_ACCEPT_NETBINDCHANGE)
64 AcceptHardwareProfileChange = Accepted(windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE)
65 AcceptPowerEvent = Accepted(windows.SERVICE_ACCEPT_POWEREVENT)
66 AcceptSessionChange = Accepted(windows.SERVICE_ACCEPT_SESSIONCHANGE)
69 // Status combines State and Accepted commands to fully describe running service.
73 CheckPoint uint32 // used to report progress during a lengthy operation
74 WaitHint uint32 // estimated time required for a pending operation, in milliseconds
77 // ChangeRequest is sent to the service Handler to request service status change.
78 type ChangeRequest struct {
85 // Handler is the interface that must be implemented to build Windows service.
86 type Handler interface {
88 // Execute will be called by the package code at the start of
89 // the service, and the service will exit once Execute completes.
90 // Inside Execute you must read service change requests from r and
91 // act accordingly. You must keep service control manager up to date
92 // about state of your service by writing into s as required.
93 // args contains service name followed by argument strings passed
95 // You can provide service exit code in exitCode return parameter,
96 // with 0 being "no error". You can also indicate if exit code,
97 // if any, is service specific or not by using svcSpecificEC
99 Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32)
103 // These are used by asm code.
110 ctlHandlerExProc uintptr
112 cWaitForSingleObject uintptr
113 cRegisterServiceCtrlHandlerExW uintptr
117 k := syscall.MustLoadDLL("kernel32.dll")
118 cSetEvent = k.MustFindProc("SetEvent").Addr()
119 cWaitForSingleObject = k.MustFindProc("WaitForSingleObject").Addr()
120 a := syscall.MustLoadDLL("advapi32.dll")
121 cRegisterServiceCtrlHandlerExW = a.MustFindProc("RegisterServiceCtrlHandlerExW").Addr()
124 // The HandlerEx prototype also has a context pointer but since we don't use
125 // it at start-up time we don't have to pass it over either.
126 type ctlEvent struct {
133 // service provides access to windows service api.
134 type service struct {
143 func newService(name string, handler Handler) (*service, error) {
147 s.c = make(chan ctlEvent)
149 s.cWaits, err = newEvent()
153 s.goWaits, err = newEvent()
161 func (s *service) close() error {
167 type exitCode struct {
172 func (s *service) updateStatus(status *Status, ec *exitCode) error {
174 return errors.New("updateStatus with no service status handle")
176 var t windows.SERVICE_STATUS
177 t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS
178 t.CurrentState = uint32(status.State)
179 if status.Accepts&AcceptStop != 0 {
180 t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP
182 if status.Accepts&AcceptShutdown != 0 {
183 t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN
185 if status.Accepts&AcceptPauseAndContinue != 0 {
186 t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE
188 if status.Accepts&AcceptParamChange != 0 {
189 t.ControlsAccepted |= windows.SERVICE_ACCEPT_PARAMCHANGE
191 if status.Accepts&AcceptNetBindChange != 0 {
192 t.ControlsAccepted |= windows.SERVICE_ACCEPT_NETBINDCHANGE
194 if status.Accepts&AcceptHardwareProfileChange != 0 {
195 t.ControlsAccepted |= windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE
197 if status.Accepts&AcceptPowerEvent != 0 {
198 t.ControlsAccepted |= windows.SERVICE_ACCEPT_POWEREVENT
200 if status.Accepts&AcceptSessionChange != 0 {
201 t.ControlsAccepted |= windows.SERVICE_ACCEPT_SESSIONCHANGE
204 t.Win32ExitCode = windows.NO_ERROR
205 t.ServiceSpecificExitCode = windows.NO_ERROR
206 } else if ec.isSvcSpecific {
207 t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR)
208 t.ServiceSpecificExitCode = ec.errno
210 t.Win32ExitCode = ec.errno
211 t.ServiceSpecificExitCode = windows.NO_ERROR
213 t.CheckPoint = status.CheckPoint
214 t.WaitHint = status.WaitHint
215 return windows.SetServiceStatus(s.h, &t)
219 sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota
220 sysErrNewThreadInCallback
223 func (s *service) run() {
225 s.h = windows.Handle(ssHandle)
226 argv := (*[100]*int16)(unsafe.Pointer(sArgv))[:sArgc]
227 args := make([]string, len(argv))
228 for i, a := range argv {
229 args[i] = syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(a))[:])
232 cmdsToHandler := make(chan ChangeRequest)
233 changesFromHandler := make(chan Status)
234 exitFromHandler := make(chan exitCode)
237 ss, errno := s.handler.Execute(args, cmdsToHandler, changesFromHandler)
238 exitFromHandler <- exitCode{ss, errno}
241 status := Status{State: Stopped}
242 ec := exitCode{isSvcSpecific: true, errno: 0}
243 var outch chan ChangeRequest
257 outch = cmdsToHandler
261 case outch <- ChangeRequest{cmd, evtype, evdata, status}:
264 case c := <-changesFromHandler:
265 err := s.updateStatus(&c, &ec)
267 // best suitable error number
268 ec.errno = sysErrSetServiceStatusFailed
269 if err2, ok := err.(syscall.Errno); ok {
270 ec.errno = uint32(err2)
275 case ec = <-exitFromHandler:
280 s.updateStatus(&Status{State: Stopped}, &ec)
284 func newCallback(fn interface{}) (cb uintptr, err error) {
291 switch v := r.(type) {
297 err = errors.New("unexpected panic in syscall.NewCallback")
300 return syscall.NewCallback(fn), nil
303 // BUG(brainman): There is no mechanism to run multiple services
304 // inside one single executable. Perhaps, it can be overcome by
305 // using RegisterServiceCtrlHandlerEx Windows api.
307 // Run executes service name by calling appropriate handler function.
308 func Run(name string, handler Handler) error {
309 runtime.LockOSThread()
311 tid := windows.GetCurrentThreadId()
313 s, err := newService(name, handler)
318 ctlHandler := func(ctl uint32, evtype uint32, evdata uintptr, context uintptr) uintptr {
319 e := ctlEvent{cmd: Cmd(ctl), eventType: evtype, eventData: evdata}
320 // We assume that this callback function is running on
321 // the same thread as Run. Nowhere in MS documentation
322 // I could find statement to guarantee that. So putting
323 // check here to verify, otherwise things will go bad
324 // quickly, if ignored.
325 i := windows.GetCurrentThreadId()
327 e.errno = sysErrNewThreadInCallback
330 // Always return NO_ERROR (0) for now.
335 getServiceMain(&svcmain)
336 t := []windows.SERVICE_TABLE_ENTRY{
337 {syscall.StringToUTF16Ptr(s.name), svcmain},
341 goWaitsH = uintptr(s.goWaits.h)
342 cWaitsH = uintptr(s.cWaits.h)
343 sName = t[0].ServiceName
344 ctlHandlerExProc, err = newCallback(ctlHandler)
351 err = windows.StartServiceCtrlDispatcher(&t[0])
358 // StatusHandle returns service status handle. It is safe to call this function
359 // from inside the Handler.Execute because then it is guaranteed to be set.
360 // This code will have to change once multiple services are possible per process.
361 func StatusHandle() windows.Handle {
362 return windows.Handle(ssHandle)