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/internal/unsafeheader" 18 "golang.org/x/sys/windows" 19) 20 21// State describes service execution state (Stopped, Running and so on). 22type State uint32 23 24const ( 25 Stopped = State(windows.SERVICE_STOPPED) 26 StartPending = State(windows.SERVICE_START_PENDING) 27 StopPending = State(windows.SERVICE_STOP_PENDING) 28 Running = State(windows.SERVICE_RUNNING) 29 ContinuePending = State(windows.SERVICE_CONTINUE_PENDING) 30 PausePending = State(windows.SERVICE_PAUSE_PENDING) 31 Paused = State(windows.SERVICE_PAUSED) 32) 33 34// Cmd represents service state change request. It is sent to a service 35// by the service manager, and should be actioned upon by the service. 36type Cmd uint32 37 38const ( 39 Stop = Cmd(windows.SERVICE_CONTROL_STOP) 40 Pause = Cmd(windows.SERVICE_CONTROL_PAUSE) 41 Continue = Cmd(windows.SERVICE_CONTROL_CONTINUE) 42 Interrogate = Cmd(windows.SERVICE_CONTROL_INTERROGATE) 43 Shutdown = Cmd(windows.SERVICE_CONTROL_SHUTDOWN) 44 ParamChange = Cmd(windows.SERVICE_CONTROL_PARAMCHANGE) 45 NetBindAdd = Cmd(windows.SERVICE_CONTROL_NETBINDADD) 46 NetBindRemove = Cmd(windows.SERVICE_CONTROL_NETBINDREMOVE) 47 NetBindEnable = Cmd(windows.SERVICE_CONTROL_NETBINDENABLE) 48 NetBindDisable = Cmd(windows.SERVICE_CONTROL_NETBINDDISABLE) 49 DeviceEvent = Cmd(windows.SERVICE_CONTROL_DEVICEEVENT) 50 HardwareProfileChange = Cmd(windows.SERVICE_CONTROL_HARDWAREPROFILECHANGE) 51 PowerEvent = Cmd(windows.SERVICE_CONTROL_POWEREVENT) 52 SessionChange = Cmd(windows.SERVICE_CONTROL_SESSIONCHANGE) 53 PreShutdown = Cmd(windows.SERVICE_CONTROL_PRESHUTDOWN) 54) 55 56// Accepted is used to describe commands accepted by the service. 57// Note that Interrogate is always accepted. 58type Accepted uint32 59 60const ( 61 AcceptStop = Accepted(windows.SERVICE_ACCEPT_STOP) 62 AcceptShutdown = Accepted(windows.SERVICE_ACCEPT_SHUTDOWN) 63 AcceptPauseAndContinue = Accepted(windows.SERVICE_ACCEPT_PAUSE_CONTINUE) 64 AcceptParamChange = Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE) 65 AcceptNetBindChange = Accepted(windows.SERVICE_ACCEPT_NETBINDCHANGE) 66 AcceptHardwareProfileChange = Accepted(windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE) 67 AcceptPowerEvent = Accepted(windows.SERVICE_ACCEPT_POWEREVENT) 68 AcceptSessionChange = Accepted(windows.SERVICE_ACCEPT_SESSIONCHANGE) 69 AcceptPreShutdown = Accepted(windows.SERVICE_ACCEPT_PRESHUTDOWN) 70) 71 72// Status combines State and Accepted commands to fully describe running service. 73type Status struct { 74 State State 75 Accepts Accepted 76 CheckPoint uint32 // used to report progress during a lengthy operation 77 WaitHint uint32 // estimated time required for a pending operation, in milliseconds 78 ProcessId uint32 // if the service is running, the process identifier of it, and otherwise zero 79 Win32ExitCode uint32 // set if the service has exited with a win32 exit code 80 ServiceSpecificExitCode uint32 // set if the service has exited with a service-specific exit code 81} 82 83// ChangeRequest is sent to the service Handler to request service status change. 84type ChangeRequest struct { 85 Cmd Cmd 86 EventType uint32 87 EventData uintptr 88 CurrentStatus Status 89 Context uintptr 90} 91 92// Handler is the interface that must be implemented to build Windows service. 93type Handler interface { 94 95 // Execute will be called by the package code at the start of 96 // the service, and the service will exit once Execute completes. 97 // Inside Execute you must read service change requests from r and 98 // act accordingly. You must keep service control manager up to date 99 // about state of your service by writing into s as required. 100 // args contains service name followed by argument strings passed 101 // to the service. 102 // You can provide service exit code in exitCode return parameter, 103 // with 0 being "no error". You can also indicate if exit code, 104 // if any, is service specific or not by using svcSpecificEC 105 // parameter. 106 Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32) 107} 108 109var ( 110 // These are used by asm code. 111 goWaitsH uintptr 112 cWaitsH uintptr 113 ssHandle uintptr 114 sName *uint16 115 sArgc uintptr 116 sArgv **uint16 117 ctlHandlerExProc uintptr 118 cSetEvent uintptr 119 cWaitForSingleObject uintptr 120 cRegisterServiceCtrlHandlerExW uintptr 121) 122 123func init() { 124 k := windows.NewLazySystemDLL("kernel32.dll") 125 cSetEvent = k.NewProc("SetEvent").Addr() 126 cWaitForSingleObject = k.NewProc("WaitForSingleObject").Addr() 127 a := windows.NewLazySystemDLL("advapi32.dll") 128 cRegisterServiceCtrlHandlerExW = a.NewProc("RegisterServiceCtrlHandlerExW").Addr() 129} 130 131type ctlEvent struct { 132 cmd Cmd 133 eventType uint32 134 eventData uintptr 135 context uintptr 136 errno uint32 137} 138 139// service provides access to windows service api. 140type service struct { 141 name string 142 h windows.Handle 143 cWaits *event 144 goWaits *event 145 c chan ctlEvent 146 handler Handler 147} 148 149func newService(name string, handler Handler) (*service, error) { 150 var s service 151 var err error 152 s.name = name 153 s.c = make(chan ctlEvent) 154 s.handler = handler 155 s.cWaits, err = newEvent() 156 if err != nil { 157 return nil, err 158 } 159 s.goWaits, err = newEvent() 160 if err != nil { 161 s.cWaits.Close() 162 return nil, err 163 } 164 return &s, nil 165} 166 167func (s *service) close() error { 168 s.cWaits.Close() 169 s.goWaits.Close() 170 return nil 171} 172 173type exitCode struct { 174 isSvcSpecific bool 175 errno uint32 176} 177 178func (s *service) updateStatus(status *Status, ec *exitCode) error { 179 if s.h == 0 { 180 return errors.New("updateStatus with no service status handle") 181 } 182 var t windows.SERVICE_STATUS 183 t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS 184 t.CurrentState = uint32(status.State) 185 if status.Accepts&AcceptStop != 0 { 186 t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP 187 } 188 if status.Accepts&AcceptShutdown != 0 { 189 t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN 190 } 191 if status.Accepts&AcceptPauseAndContinue != 0 { 192 t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE 193 } 194 if status.Accepts&AcceptParamChange != 0 { 195 t.ControlsAccepted |= windows.SERVICE_ACCEPT_PARAMCHANGE 196 } 197 if status.Accepts&AcceptNetBindChange != 0 { 198 t.ControlsAccepted |= windows.SERVICE_ACCEPT_NETBINDCHANGE 199 } 200 if status.Accepts&AcceptHardwareProfileChange != 0 { 201 t.ControlsAccepted |= windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE 202 } 203 if status.Accepts&AcceptPowerEvent != 0 { 204 t.ControlsAccepted |= windows.SERVICE_ACCEPT_POWEREVENT 205 } 206 if status.Accepts&AcceptSessionChange != 0 { 207 t.ControlsAccepted |= windows.SERVICE_ACCEPT_SESSIONCHANGE 208 } 209 if status.Accepts&AcceptPreShutdown != 0 { 210 t.ControlsAccepted |= windows.SERVICE_ACCEPT_PRESHUTDOWN 211 } 212 if ec.errno == 0 { 213 t.Win32ExitCode = windows.NO_ERROR 214 t.ServiceSpecificExitCode = windows.NO_ERROR 215 } else if ec.isSvcSpecific { 216 t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR) 217 t.ServiceSpecificExitCode = ec.errno 218 } else { 219 t.Win32ExitCode = ec.errno 220 t.ServiceSpecificExitCode = windows.NO_ERROR 221 } 222 t.CheckPoint = status.CheckPoint 223 t.WaitHint = status.WaitHint 224 return windows.SetServiceStatus(s.h, &t) 225} 226 227const ( 228 sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota 229 sysErrNewThreadInCallback 230) 231 232func (s *service) run() { 233 s.goWaits.Wait() 234 s.h = windows.Handle(ssHandle) 235 236 var argv []*uint16 237 hdr := (*unsafeheader.Slice)(unsafe.Pointer(&argv)) 238 hdr.Data = unsafe.Pointer(sArgv) 239 hdr.Len = int(sArgc) 240 hdr.Cap = int(sArgc) 241 242 args := make([]string, len(argv)) 243 for i, a := range argv { 244 args[i] = windows.UTF16PtrToString(a) 245 } 246 247 cmdsToHandler := make(chan ChangeRequest) 248 changesFromHandler := make(chan Status) 249 exitFromHandler := make(chan exitCode) 250 251 go func() { 252 ss, errno := s.handler.Execute(args, cmdsToHandler, changesFromHandler) 253 exitFromHandler <- exitCode{ss, errno} 254 }() 255 256 ec := exitCode{isSvcSpecific: true, errno: 0} 257 outcr := ChangeRequest{ 258 CurrentStatus: Status{State: Stopped}, 259 } 260 var outch chan ChangeRequest 261 inch := s.c 262loop: 263 for { 264 select { 265 case r := <-inch: 266 if r.errno != 0 { 267 ec.errno = r.errno 268 break loop 269 } 270 inch = nil 271 outch = cmdsToHandler 272 outcr.Cmd = r.cmd 273 outcr.EventType = r.eventType 274 outcr.EventData = r.eventData 275 outcr.Context = r.context 276 case outch <- outcr: 277 inch = s.c 278 outch = nil 279 case c := <-changesFromHandler: 280 err := s.updateStatus(&c, &ec) 281 if err != nil { 282 // best suitable error number 283 ec.errno = sysErrSetServiceStatusFailed 284 if err2, ok := err.(syscall.Errno); ok { 285 ec.errno = uint32(err2) 286 } 287 break loop 288 } 289 outcr.CurrentStatus = c 290 case ec = <-exitFromHandler: 291 break loop 292 } 293 } 294 295 s.updateStatus(&Status{State: Stopped}, &ec) 296 s.cWaits.Set() 297} 298 299func newCallback(fn interface{}) (cb uintptr, err error) { 300 defer func() { 301 r := recover() 302 if r == nil { 303 return 304 } 305 cb = 0 306 switch v := r.(type) { 307 case string: 308 err = errors.New(v) 309 case error: 310 err = v 311 default: 312 err = errors.New("unexpected panic in syscall.NewCallback") 313 } 314 }() 315 return syscall.NewCallback(fn), nil 316} 317 318// BUG(brainman): There is no mechanism to run multiple services 319// inside one single executable. Perhaps, it can be overcome by 320// using RegisterServiceCtrlHandlerEx Windows api. 321 322// Run executes service name by calling appropriate handler function. 323func Run(name string, handler Handler) error { 324 runtime.LockOSThread() 325 326 tid := windows.GetCurrentThreadId() 327 328 s, err := newService(name, handler) 329 if err != nil { 330 return err 331 } 332 333 ctlHandler := func(ctl, evtype, evdata, context uintptr) uintptr { 334 e := ctlEvent{cmd: Cmd(ctl), eventType: uint32(evtype), eventData: evdata, context: context} 335 // We assume that this callback function is running on 336 // the same thread as Run. Nowhere in MS documentation 337 // I could find statement to guarantee that. So putting 338 // check here to verify, otherwise things will go bad 339 // quickly, if ignored. 340 i := windows.GetCurrentThreadId() 341 if i != tid { 342 e.errno = sysErrNewThreadInCallback 343 } 344 s.c <- e 345 // Always return NO_ERROR (0) for now. 346 return windows.NO_ERROR 347 } 348 349 var svcmain uintptr 350 getServiceMain(&svcmain) 351 t := []windows.SERVICE_TABLE_ENTRY{ 352 {ServiceName: syscall.StringToUTF16Ptr(s.name), ServiceProc: svcmain}, 353 {ServiceName: nil, ServiceProc: 0}, 354 } 355 356 goWaitsH = uintptr(s.goWaits.h) 357 cWaitsH = uintptr(s.cWaits.h) 358 sName = t[0].ServiceName 359 ctlHandlerExProc, err = newCallback(ctlHandler) 360 if err != nil { 361 return err 362 } 363 364 go s.run() 365 366 err = windows.StartServiceCtrlDispatcher(&t[0]) 367 if err != nil { 368 return err 369 } 370 return nil 371} 372 373// StatusHandle returns service status handle. It is safe to call this function 374// from inside the Handler.Execute because then it is guaranteed to be set. 375// This code will have to change once multiple services are possible per process. 376func StatusHandle() windows.Handle { 377 return windows.Handle(ssHandle) 378} 379