github.com/go-xe2/third@v1.0.3/golang.org/x/sys/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 "github.com/go-xe2/third/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 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) 52 ) 53 54 // Accepted is used to describe commands accepted by the service. 55 // Note that Interrogate is always accepted. 56 type Accepted uint32 57 58 const ( 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) 67 ) 68 69 // Status combines State and Accepted commands to fully describe running service. 70 type Status struct { 71 State State 72 Accepts Accepted 73 CheckPoint uint32 // used to report progress during a lengthy operation 74 WaitHint uint32 // estimated time required for a pending operation, in milliseconds 75 } 76 77 // ChangeRequest is sent to the service Handler to request service status change. 78 type ChangeRequest struct { 79 Cmd Cmd 80 EventType uint32 81 EventData uintptr 82 CurrentStatus Status 83 } 84 85 // Handler is the interface that must be implemented to build Windows service. 86 type Handler interface { 87 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 94 // to the service. 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 98 // parameter. 99 Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32) 100 } 101 102 var ( 103 // These are used by asm code. 104 goWaitsH uintptr 105 cWaitsH uintptr 106 ssHandle uintptr 107 sName *uint16 108 sArgc uintptr 109 sArgv **uint16 110 ctlHandlerExProc uintptr 111 cSetEvent uintptr 112 cWaitForSingleObject uintptr 113 cRegisterServiceCtrlHandlerExW uintptr 114 ) 115 116 func init() { 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() 122 } 123 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 { 127 cmd Cmd 128 eventType uint32 129 eventData uintptr 130 errno uint32 131 } 132 133 // service provides access to windows service api. 134 type service struct { 135 name string 136 h windows.Handle 137 cWaits *event 138 goWaits *event 139 c chan ctlEvent 140 handler Handler 141 } 142 143 func newService(name string, handler Handler) (*service, error) { 144 var s service 145 var err error 146 s.name = name 147 s.c = make(chan ctlEvent) 148 s.handler = handler 149 s.cWaits, err = newEvent() 150 if err != nil { 151 return nil, err 152 } 153 s.goWaits, err = newEvent() 154 if err != nil { 155 s.cWaits.Close() 156 return nil, err 157 } 158 return &s, nil 159 } 160 161 func (s *service) close() error { 162 s.cWaits.Close() 163 s.goWaits.Close() 164 return nil 165 } 166 167 type exitCode struct { 168 isSvcSpecific bool 169 errno uint32 170 } 171 172 func (s *service) updateStatus(status *Status, ec *exitCode) error { 173 if s.h == 0 { 174 return errors.New("updateStatus with no service status handle") 175 } 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 181 } 182 if status.Accepts&AcceptShutdown != 0 { 183 t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN 184 } 185 if status.Accepts&AcceptPauseAndContinue != 0 { 186 t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE 187 } 188 if status.Accepts&AcceptParamChange != 0 { 189 t.ControlsAccepted |= windows.SERVICE_ACCEPT_PARAMCHANGE 190 } 191 if status.Accepts&AcceptNetBindChange != 0 { 192 t.ControlsAccepted |= windows.SERVICE_ACCEPT_NETBINDCHANGE 193 } 194 if status.Accepts&AcceptHardwareProfileChange != 0 { 195 t.ControlsAccepted |= windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE 196 } 197 if status.Accepts&AcceptPowerEvent != 0 { 198 t.ControlsAccepted |= windows.SERVICE_ACCEPT_POWEREVENT 199 } 200 if status.Accepts&AcceptSessionChange != 0 { 201 t.ControlsAccepted |= windows.SERVICE_ACCEPT_SESSIONCHANGE 202 } 203 if ec.errno == 0 { 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 209 } else { 210 t.Win32ExitCode = ec.errno 211 t.ServiceSpecificExitCode = windows.NO_ERROR 212 } 213 t.CheckPoint = status.CheckPoint 214 t.WaitHint = status.WaitHint 215 return windows.SetServiceStatus(s.h, &t) 216 } 217 218 const ( 219 sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota 220 sysErrNewThreadInCallback 221 ) 222 223 func (s *service) run() { 224 s.goWaits.Wait() 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))[:]) 230 } 231 232 cmdsToHandler := make(chan ChangeRequest) 233 changesFromHandler := make(chan Status) 234 exitFromHandler := make(chan exitCode) 235 236 go func() { 237 ss, errno := s.handler.Execute(args, cmdsToHandler, changesFromHandler) 238 exitFromHandler <- exitCode{ss, errno} 239 }() 240 241 status := Status{State: Stopped} 242 ec := exitCode{isSvcSpecific: true, errno: 0} 243 var outch chan ChangeRequest 244 inch := s.c 245 var cmd Cmd 246 var evtype uint32 247 var evdata uintptr 248 loop: 249 for { 250 select { 251 case r := <-inch: 252 if r.errno != 0 { 253 ec.errno = r.errno 254 break loop 255 } 256 inch = nil 257 outch = cmdsToHandler 258 cmd = r.cmd 259 evtype = r.eventType 260 evdata = r.eventData 261 case outch <- ChangeRequest{cmd, evtype, evdata, status}: 262 inch = s.c 263 outch = nil 264 case c := <-changesFromHandler: 265 err := s.updateStatus(&c, &ec) 266 if err != nil { 267 // best suitable error number 268 ec.errno = sysErrSetServiceStatusFailed 269 if err2, ok := err.(syscall.Errno); ok { 270 ec.errno = uint32(err2) 271 } 272 break loop 273 } 274 status = c 275 case ec = <-exitFromHandler: 276 break loop 277 } 278 } 279 280 s.updateStatus(&Status{State: Stopped}, &ec) 281 s.cWaits.Set() 282 } 283 284 func newCallback(fn interface{}) (cb uintptr, err error) { 285 defer func() { 286 r := recover() 287 if r == nil { 288 return 289 } 290 cb = 0 291 switch v := r.(type) { 292 case string: 293 err = errors.New(v) 294 case error: 295 err = v 296 default: 297 err = errors.New("unexpected panic in syscall.NewCallback") 298 } 299 }() 300 return syscall.NewCallback(fn), nil 301 } 302 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. 306 307 // Run executes service name by calling appropriate handler function. 308 func Run(name string, handler Handler) error { 309 runtime.LockOSThread() 310 311 tid := windows.GetCurrentThreadId() 312 313 s, err := newService(name, handler) 314 if err != nil { 315 return err 316 } 317 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() 326 if i != tid { 327 e.errno = sysErrNewThreadInCallback 328 } 329 s.c <- e 330 // Always return NO_ERROR (0) for now. 331 return 0 332 } 333 334 var svcmain uintptr 335 getServiceMain(&svcmain) 336 t := []windows.SERVICE_TABLE_ENTRY{ 337 {ServiceName: syscall.StringToUTF16Ptr(s.name), ServiceProc: svcmain}, 338 {ServiceName: nil, ServiceProc: 0}, 339 } 340 341 goWaitsH = uintptr(s.goWaits.h) 342 cWaitsH = uintptr(s.cWaits.h) 343 sName = t[0].ServiceName 344 ctlHandlerExProc, err = newCallback(ctlHandler) 345 if err != nil { 346 return err 347 } 348 349 go s.run() 350 351 err = windows.StartServiceCtrlDispatcher(&t[0]) 352 if err != nil { 353 return err 354 } 355 return nil 356 } 357 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) 363 }