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 ProcessId uint32 // if the service is running, the process identifier of it, and otherwise zero 76} 77 78// ChangeRequest is sent to the service Handler to request service status change. 79type ChangeRequest struct { 80 Cmd Cmd 81 EventType uint32 82 EventData uintptr 83 CurrentStatus Status 84 Context uintptr 85} 86 87// Handler is the interface that must be implemented to build Windows service. 88type Handler interface { 89 90 // Execute will be called by the package code at the start of 91 // the service, and the service will exit once Execute completes. 92 // Inside Execute you must read service change requests from r and 93 // act accordingly. You must keep service control manager up to date 94 // about state of your service by writing into s as required. 95 // args contains service name followed by argument strings passed 96 // to the service. 97 // You can provide service exit code in exitCode return parameter, 98 // with 0 being "no error". You can also indicate if exit code, 99 // if any, is service specific or not by using svcSpecificEC 100 // parameter. 101 Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32) 102} 103 104var ( 105 // These are used by asm code. 106 goWaitsH uintptr 107 cWaitsH uintptr 108 ssHandle uintptr 109 sName *uint16 110 sArgc uintptr 111 sArgv **uint16 112 ctlHandlerExProc uintptr 113 cSetEvent uintptr 114 cWaitForSingleObject uintptr 115 cRegisterServiceCtrlHandlerExW uintptr 116) 117 118func init() { 119 k := windows.NewLazySystemDLL("kernel32.dll") 120 cSetEvent = k.NewProc("SetEvent").Addr() 121 cWaitForSingleObject = k.NewProc("WaitForSingleObject").Addr() 122 a := windows.NewLazySystemDLL("advapi32.dll") 123 cRegisterServiceCtrlHandlerExW = a.NewProc("RegisterServiceCtrlHandlerExW").Addr() 124} 125 126type ctlEvent struct { 127 cmd Cmd 128 eventType uint32 129 eventData uintptr 130 context uintptr 131 errno uint32 132} 133 134// service provides access to windows service api. 135type service struct { 136 name string 137 h windows.Handle 138 cWaits *event 139 goWaits *event 140 c chan ctlEvent 141 handler Handler 142} 143 144func newService(name string, handler Handler) (*service, error) { 145 var s service 146 var err error 147 s.name = name 148 s.c = make(chan ctlEvent) 149 s.handler = handler 150 s.cWaits, err = newEvent() 151 if err != nil { 152 return nil, err 153 } 154 s.goWaits, err = newEvent() 155 if err != nil { 156 s.cWaits.Close() 157 return nil, err 158 } 159 return &s, nil 160} 161 162func (s *service) close() error { 163 s.cWaits.Close() 164 s.goWaits.Close() 165 return nil 166} 167 168type exitCode struct { 169 isSvcSpecific bool 170 errno uint32 171} 172 173func (s *service) updateStatus(status *Status, ec *exitCode) error { 174 if s.h == 0 { 175 return errors.New("updateStatus with no service status handle") 176 } 177 var t windows.SERVICE_STATUS 178 t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS 179 t.CurrentState = uint32(status.State) 180 if status.Accepts&AcceptStop != 0 { 181 t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP 182 } 183 if status.Accepts&AcceptShutdown != 0 { 184 t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN 185 } 186 if status.Accepts&AcceptPauseAndContinue != 0 { 187 t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE 188 } 189 if status.Accepts&AcceptParamChange != 0 { 190 t.ControlsAccepted |= windows.SERVICE_ACCEPT_PARAMCHANGE 191 } 192 if status.Accepts&AcceptNetBindChange != 0 { 193 t.ControlsAccepted |= windows.SERVICE_ACCEPT_NETBINDCHANGE 194 } 195 if status.Accepts&AcceptHardwareProfileChange != 0 { 196 t.ControlsAccepted |= windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE 197 } 198 if status.Accepts&AcceptPowerEvent != 0 { 199 t.ControlsAccepted |= windows.SERVICE_ACCEPT_POWEREVENT 200 } 201 if status.Accepts&AcceptSessionChange != 0 { 202 t.ControlsAccepted |= windows.SERVICE_ACCEPT_SESSIONCHANGE 203 } 204 if ec.errno == 0 { 205 t.Win32ExitCode = windows.NO_ERROR 206 t.ServiceSpecificExitCode = windows.NO_ERROR 207 } else if ec.isSvcSpecific { 208 t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR) 209 t.ServiceSpecificExitCode = ec.errno 210 } else { 211 t.Win32ExitCode = ec.errno 212 t.ServiceSpecificExitCode = windows.NO_ERROR 213 } 214 t.CheckPoint = status.CheckPoint 215 t.WaitHint = status.WaitHint 216 return windows.SetServiceStatus(s.h, &t) 217} 218 219const ( 220 sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota 221 sysErrNewThreadInCallback 222) 223 224func (s *service) run() { 225 s.goWaits.Wait() 226 s.h = windows.Handle(ssHandle) 227 argv := (*[100]*int16)(unsafe.Pointer(sArgv))[:sArgc] 228 args := make([]string, len(argv)) 229 for i, a := range argv { 230 args[i] = syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(a))[:]) 231 } 232 233 cmdsToHandler := make(chan ChangeRequest) 234 changesFromHandler := make(chan Status) 235 exitFromHandler := make(chan exitCode) 236 237 go func() { 238 ss, errno := s.handler.Execute(args, cmdsToHandler, changesFromHandler) 239 exitFromHandler <- exitCode{ss, errno} 240 }() 241 242 ec := exitCode{isSvcSpecific: true, errno: 0} 243 outcr := ChangeRequest{ 244 CurrentStatus: Status{State: Stopped}, 245 } 246 var outch chan ChangeRequest 247 inch := s.c 248loop: 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 outcr.Cmd = r.cmd 259 outcr.EventType = r.eventType 260 outcr.EventData = r.eventData 261 outcr.Context = r.context 262 case outch <- outcr: 263 inch = s.c 264 outch = nil 265 case c := <-changesFromHandler: 266 err := s.updateStatus(&c, &ec) 267 if err != nil { 268 // best suitable error number 269 ec.errno = sysErrSetServiceStatusFailed 270 if err2, ok := err.(syscall.Errno); ok { 271 ec.errno = uint32(err2) 272 } 273 break loop 274 } 275 outcr.CurrentStatus = c 276 case ec = <-exitFromHandler: 277 break loop 278 } 279 } 280 281 s.updateStatus(&Status{State: Stopped}, &ec) 282 s.cWaits.Set() 283} 284 285func newCallback(fn interface{}) (cb uintptr, err error) { 286 defer func() { 287 r := recover() 288 if r == nil { 289 return 290 } 291 cb = 0 292 switch v := r.(type) { 293 case string: 294 err = errors.New(v) 295 case error: 296 err = v 297 default: 298 err = errors.New("unexpected panic in syscall.NewCallback") 299 } 300 }() 301 return syscall.NewCallback(fn), nil 302} 303 304// BUG(brainman): There is no mechanism to run multiple services 305// inside one single executable. Perhaps, it can be overcome by 306// using RegisterServiceCtrlHandlerEx Windows api. 307 308// Run executes service name by calling appropriate handler function. 309func Run(name string, handler Handler) error { 310 runtime.LockOSThread() 311 312 tid := windows.GetCurrentThreadId() 313 314 s, err := newService(name, handler) 315 if err != nil { 316 return err 317 } 318 319 ctlHandler := func(ctl, evtype, evdata, context uintptr) uintptr { 320 e := ctlEvent{cmd: Cmd(ctl), eventType: uint32(evtype), eventData: evdata, context: context} 321 // We assume that this callback function is running on 322 // the same thread as Run. Nowhere in MS documentation 323 // I could find statement to guarantee that. So putting 324 // check here to verify, otherwise things will go bad 325 // quickly, if ignored. 326 i := windows.GetCurrentThreadId() 327 if i != tid { 328 e.errno = sysErrNewThreadInCallback 329 } 330 s.c <- e 331 // Always return NO_ERROR (0) for now. 332 return windows.NO_ERROR 333 } 334 335 var svcmain uintptr 336 getServiceMain(&svcmain) 337 t := []windows.SERVICE_TABLE_ENTRY{ 338 {ServiceName: syscall.StringToUTF16Ptr(s.name), ServiceProc: svcmain}, 339 {ServiceName: nil, ServiceProc: 0}, 340 } 341 342 goWaitsH = uintptr(s.goWaits.h) 343 cWaitsH = uintptr(s.cWaits.h) 344 sName = t[0].ServiceName 345 ctlHandlerExProc, err = newCallback(ctlHandler) 346 if err != nil { 347 return err 348 } 349 350 go s.run() 351 352 err = windows.StartServiceCtrlDispatcher(&t[0]) 353 if err != nil { 354 return err 355 } 356 return nil 357} 358 359// StatusHandle returns service status handle. It is safe to call this function 360// from inside the Handler.Execute because then it is guaranteed to be set. 361// This code will have to change once multiple services are possible per process. 362func StatusHandle() windows.Handle { 363 return windows.Handle(ssHandle) 364} 365