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