golang.org/x/sys@v0.9.0/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 //go:build windows 6 // +build windows 7 8 // Package svc provides everything required to build Windows service. 9 package svc 10 11 import ( 12 "errors" 13 "sync" 14 "unsafe" 15 16 "golang.org/x/sys/internal/unsafeheader" 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 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 PreShutdown = Cmd(windows.SERVICE_CONTROL_PRESHUTDOWN) 53 ) 54 55 // Accepted is used to describe commands accepted by the service. 56 // Note that Interrogate is always accepted. 57 type Accepted uint32 58 59 const ( 60 AcceptStop = Accepted(windows.SERVICE_ACCEPT_STOP) 61 AcceptShutdown = Accepted(windows.SERVICE_ACCEPT_SHUTDOWN) 62 AcceptPauseAndContinue = Accepted(windows.SERVICE_ACCEPT_PAUSE_CONTINUE) 63 AcceptParamChange = Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE) 64 AcceptNetBindChange = Accepted(windows.SERVICE_ACCEPT_NETBINDCHANGE) 65 AcceptHardwareProfileChange = Accepted(windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE) 66 AcceptPowerEvent = Accepted(windows.SERVICE_ACCEPT_POWEREVENT) 67 AcceptSessionChange = Accepted(windows.SERVICE_ACCEPT_SESSIONCHANGE) 68 AcceptPreShutdown = Accepted(windows.SERVICE_ACCEPT_PRESHUTDOWN) 69 ) 70 71 // ActivityStatus allows for services to be selected based on active and inactive categories of service state. 72 type ActivityStatus uint32 73 74 const ( 75 Active = ActivityStatus(windows.SERVICE_ACTIVE) 76 Inactive = ActivityStatus(windows.SERVICE_INACTIVE) 77 AnyActivity = ActivityStatus(windows.SERVICE_STATE_ALL) 78 ) 79 80 // Status combines State and Accepted commands to fully describe running service. 81 type Status struct { 82 State State 83 Accepts Accepted 84 CheckPoint uint32 // used to report progress during a lengthy operation 85 WaitHint uint32 // estimated time required for a pending operation, in milliseconds 86 ProcessId uint32 // if the service is running, the process identifier of it, and otherwise zero 87 Win32ExitCode uint32 // set if the service has exited with a win32 exit code 88 ServiceSpecificExitCode uint32 // set if the service has exited with a service-specific exit code 89 } 90 91 // StartReason is the reason that the service was started. 92 type StartReason uint32 93 94 const ( 95 StartReasonDemand = StartReason(windows.SERVICE_START_REASON_DEMAND) 96 StartReasonAuto = StartReason(windows.SERVICE_START_REASON_AUTO) 97 StartReasonTrigger = StartReason(windows.SERVICE_START_REASON_TRIGGER) 98 StartReasonRestartOnFailure = StartReason(windows.SERVICE_START_REASON_RESTART_ON_FAILURE) 99 StartReasonDelayedAuto = StartReason(windows.SERVICE_START_REASON_DELAYEDAUTO) 100 ) 101 102 // ChangeRequest is sent to the service Handler to request service status change. 103 type ChangeRequest struct { 104 Cmd Cmd 105 EventType uint32 106 EventData uintptr 107 CurrentStatus Status 108 Context uintptr 109 } 110 111 // Handler is the interface that must be implemented to build Windows service. 112 type Handler interface { 113 // Execute will be called by the package code at the start of 114 // the service, and the service will exit once Execute completes. 115 // Inside Execute you must read service change requests from r and 116 // act accordingly. You must keep service control manager up to date 117 // about state of your service by writing into s as required. 118 // args contains service name followed by argument strings passed 119 // to the service. 120 // You can provide service exit code in exitCode return parameter, 121 // with 0 being "no error". You can also indicate if exit code, 122 // if any, is service specific or not by using svcSpecificEC 123 // parameter. 124 Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32) 125 } 126 127 type ctlEvent struct { 128 cmd Cmd 129 eventType uint32 130 eventData uintptr 131 context uintptr 132 errno uint32 133 } 134 135 // service provides access to windows service api. 136 type service struct { 137 name string 138 h windows.Handle 139 c chan ctlEvent 140 handler Handler 141 } 142 143 type exitCode struct { 144 isSvcSpecific bool 145 errno uint32 146 } 147 148 func (s *service) updateStatus(status *Status, ec *exitCode) error { 149 if s.h == 0 { 150 return errors.New("updateStatus with no service status handle") 151 } 152 var t windows.SERVICE_STATUS 153 t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS 154 t.CurrentState = uint32(status.State) 155 if status.Accepts&AcceptStop != 0 { 156 t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP 157 } 158 if status.Accepts&AcceptShutdown != 0 { 159 t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN 160 } 161 if status.Accepts&AcceptPauseAndContinue != 0 { 162 t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE 163 } 164 if status.Accepts&AcceptParamChange != 0 { 165 t.ControlsAccepted |= windows.SERVICE_ACCEPT_PARAMCHANGE 166 } 167 if status.Accepts&AcceptNetBindChange != 0 { 168 t.ControlsAccepted |= windows.SERVICE_ACCEPT_NETBINDCHANGE 169 } 170 if status.Accepts&AcceptHardwareProfileChange != 0 { 171 t.ControlsAccepted |= windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE 172 } 173 if status.Accepts&AcceptPowerEvent != 0 { 174 t.ControlsAccepted |= windows.SERVICE_ACCEPT_POWEREVENT 175 } 176 if status.Accepts&AcceptSessionChange != 0 { 177 t.ControlsAccepted |= windows.SERVICE_ACCEPT_SESSIONCHANGE 178 } 179 if status.Accepts&AcceptPreShutdown != 0 { 180 t.ControlsAccepted |= windows.SERVICE_ACCEPT_PRESHUTDOWN 181 } 182 if ec.errno == 0 { 183 t.Win32ExitCode = windows.NO_ERROR 184 t.ServiceSpecificExitCode = windows.NO_ERROR 185 } else if ec.isSvcSpecific { 186 t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR) 187 t.ServiceSpecificExitCode = ec.errno 188 } else { 189 t.Win32ExitCode = ec.errno 190 t.ServiceSpecificExitCode = windows.NO_ERROR 191 } 192 t.CheckPoint = status.CheckPoint 193 t.WaitHint = status.WaitHint 194 return windows.SetServiceStatus(s.h, &t) 195 } 196 197 var ( 198 initCallbacks sync.Once 199 ctlHandlerCallback uintptr 200 serviceMainCallback uintptr 201 ) 202 203 func ctlHandler(ctl, evtype, evdata, context uintptr) uintptr { 204 s := (*service)(unsafe.Pointer(context)) 205 e := ctlEvent{cmd: Cmd(ctl), eventType: uint32(evtype), eventData: evdata, context: 123456} // Set context to 123456 to test issue #25660. 206 s.c <- e 207 return 0 208 } 209 210 var theService service // This is, unfortunately, a global, which means only one service per process. 211 212 // serviceMain is the entry point called by the service manager, registered earlier by 213 // the call to StartServiceCtrlDispatcher. 214 func serviceMain(argc uint32, argv **uint16) uintptr { 215 handle, err := windows.RegisterServiceCtrlHandlerEx(windows.StringToUTF16Ptr(theService.name), ctlHandlerCallback, uintptr(unsafe.Pointer(&theService))) 216 if sysErr, ok := err.(windows.Errno); ok { 217 return uintptr(sysErr) 218 } else if err != nil { 219 return uintptr(windows.ERROR_UNKNOWN_EXCEPTION) 220 } 221 theService.h = handle 222 defer func() { 223 theService.h = 0 224 }() 225 var args16 []*uint16 226 hdr := (*unsafeheader.Slice)(unsafe.Pointer(&args16)) 227 hdr.Data = unsafe.Pointer(argv) 228 hdr.Len = int(argc) 229 hdr.Cap = int(argc) 230 231 args := make([]string, len(args16)) 232 for i, a := range args16 { 233 args[i] = windows.UTF16PtrToString(a) 234 } 235 236 cmdsToHandler := make(chan ChangeRequest) 237 changesFromHandler := make(chan Status) 238 exitFromHandler := make(chan exitCode) 239 240 go func() { 241 ss, errno := theService.handler.Execute(args, cmdsToHandler, changesFromHandler) 242 exitFromHandler <- exitCode{ss, errno} 243 }() 244 245 ec := exitCode{isSvcSpecific: true, errno: 0} 246 outcr := ChangeRequest{ 247 CurrentStatus: Status{State: Stopped}, 248 } 249 var outch chan ChangeRequest 250 inch := theService.c 251 loop: 252 for { 253 select { 254 case r := <-inch: 255 if r.errno != 0 { 256 ec.errno = r.errno 257 break loop 258 } 259 inch = nil 260 outch = cmdsToHandler 261 outcr.Cmd = r.cmd 262 outcr.EventType = r.eventType 263 outcr.EventData = r.eventData 264 outcr.Context = r.context 265 case outch <- outcr: 266 inch = theService.c 267 outch = nil 268 case c := <-changesFromHandler: 269 err := theService.updateStatus(&c, &ec) 270 if err != nil { 271 ec.errno = uint32(windows.ERROR_EXCEPTION_IN_SERVICE) 272 if err2, ok := err.(windows.Errno); ok { 273 ec.errno = uint32(err2) 274 } 275 break loop 276 } 277 outcr.CurrentStatus = c 278 case ec = <-exitFromHandler: 279 break loop 280 } 281 } 282 283 theService.updateStatus(&Status{State: Stopped}, &ec) 284 285 return windows.NO_ERROR 286 } 287 288 // Run executes service name by calling appropriate handler function. 289 func Run(name string, handler Handler) error { 290 initCallbacks.Do(func() { 291 ctlHandlerCallback = windows.NewCallback(ctlHandler) 292 serviceMainCallback = windows.NewCallback(serviceMain) 293 }) 294 theService.name = name 295 theService.handler = handler 296 theService.c = make(chan ctlEvent) 297 t := []windows.SERVICE_TABLE_ENTRY{ 298 {ServiceName: windows.StringToUTF16Ptr(theService.name), ServiceProc: serviceMainCallback}, 299 {ServiceName: nil, ServiceProc: 0}, 300 } 301 return windows.StartServiceCtrlDispatcher(&t[0]) 302 } 303 304 // StatusHandle returns service status handle. It is safe to call this function 305 // from inside the Handler.Execute because then it is guaranteed to be set. 306 func StatusHandle() windows.Handle { 307 return theService.h 308 } 309 310 // DynamicStartReason returns the reason why the service was started. It is safe 311 // to call this function from inside the Handler.Execute because then it is 312 // guaranteed to be set. 313 func DynamicStartReason() (StartReason, error) { 314 var allocReason *uint32 315 err := windows.QueryServiceDynamicInformation(theService.h, windows.SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON, unsafe.Pointer(&allocReason)) 316 if err != nil { 317 return 0, err 318 } 319 reason := StartReason(*allocReason) 320 windows.LocalFree(windows.Handle(unsafe.Pointer(allocReason))) 321 return reason, nil 322 }