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