github.com/juelite/golang.org-x-sys@v0.0.0-20181121071242-7b69e1c5db33/windows/svc/service.go (about) 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 }