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// 9package svc 10 11import ( 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). 21type State uint32 22 23const ( 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. 35type Cmd uint32 36 37const ( 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. 56type Accepted uint32 57 58const ( 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. 70type 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. 78type ChangeRequest struct { 79 Cmd Cmd 80 EventType uint32 81 EventData uintptr 82 CurrentStatus Status 83 Context uintptr 84} 85 86// Handler is the interface that must be implemented to build Windows service. 87type Handler interface { 88 89 // Execute will be called by the package code at the start of 90 // the service, and the service will exit once Execute completes. 91 // Inside Execute you must read service change requests from r and 92 // act accordingly. You must keep service control manager up to date 93 // about state of your service by writing into s as required. 94 // args contains service name followed by argument strings passed 95 // to the service. 96 // You can provide service exit code in exitCode return parameter, 97 // with 0 being "no error". You can also indicate if exit code, 98 // if any, is service specific or not by using svcSpecificEC 99 // parameter. 100 Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32) 101} 102 103var ( 104 // These are used by asm code. 105 goWaitsH uintptr 106 cWaitsH uintptr 107 ssHandle uintptr 108 sName *uint16 109 sArgc uintptr 110 sArgv **uint16 111 ctlHandlerExProc uintptr 112 cSetEvent uintptr 113 cWaitForSingleObject uintptr 114 cRegisterServiceCtrlHandlerExW uintptr 115) 116 117func init() { 118 k := windows.NewLazySystemDLL("kernel32.dll") 119 cSetEvent = k.NewProc("SetEvent").Addr() 120 cWaitForSingleObject = k.NewProc("WaitForSingleObject").Addr() 121 a := windows.NewLazySystemDLL("advapi32.dll") 122 cRegisterServiceCtrlHandlerExW = a.NewProc("RegisterServiceCtrlHandlerExW").Addr() 123} 124 125type ctlEvent struct { 126 cmd Cmd 127 eventType uint32 128 eventData uintptr 129 context uintptr 130 errno uint32 131} 132 133// service provides access to windows service api. 134type service struct { 135 name string 136 h windows.Handle 137 cWaits *event 138 goWaits *event 139 c chan ctlEvent 140 handler Handler 141} 142 143func 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 161func (s *service) close() error { 162 s.cWaits.Close() 163 s.goWaits.Close() 164 return nil 165} 166 167type exitCode struct { 168 isSvcSpecific bool 169 errno uint32 170} 171 172func (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 218const ( 219 sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota 220 sysErrNewThreadInCallback 221) 222 223func (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 ec := exitCode{isSvcSpecific: true, errno: 0} 242 outcr := ChangeRequest{ 243 CurrentStatus: Status{State: Stopped}, 244 } 245 var outch chan ChangeRequest 246 inch := s.c 247loop: 248 for { 249 select { 250 case r := <-inch: 251 if r.errno != 0 { 252 ec.errno = r.errno 253 break loop 254 } 255 inch = nil 256 outch = cmdsToHandler 257 outcr.Cmd = r.cmd 258 outcr.EventType = r.eventType 259 outcr.EventData = r.eventData 260 outcr.Context = r.context 261 case outch <- outcr: 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 outcr.CurrentStatus = c 275 case ec = <-exitFromHandler: 276 break loop 277 } 278 } 279 280 s.updateStatus(&Status{State: Stopped}, &ec) 281 s.cWaits.Set() 282} 283 284func 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. 308func 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, evtype, evdata, context uintptr) uintptr { 319 e := ctlEvent{cmd: Cmd(ctl), eventType: uint32(evtype), eventData: evdata, context: context} 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. 361func StatusHandle() windows.Handle { 362 return windows.Handle(ssHandle) 363} 364