github.com/iDigitalFlame/xmt@v0.5.4/device/winapi/svc/service.go (about) 1 //go:build windows 2 // +build windows 3 4 // Copyright (C) 2020 - 2023 iDigitalFlame 5 // 6 // This program is free software: you can redistribute it and/or modify 7 // it under the terms of the GNU General Public License as published by 8 // the Free Software Foundation, either version 3 of the License, or 9 // any later version. 10 // 11 // This program is distributed in the hope that it will be useful, 12 // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 // GNU General Public License for more details. 15 // 16 // You should have received a copy of the GNU General Public License 17 // along with this program. If not, see <https://www.gnu.org/licenses/>. 18 // 19 20 // Package svc is a Windows specific Service interface. This can be used to create 21 // Golang services that talk to SCM. 22 // 23 // It is recommended to use the 'device.Daemon*' functions instead of this package 24 // as they are easier to use. 25 package svc 26 27 import ( 28 "context" 29 "os" 30 "runtime" 31 "sync" 32 "syscall" 33 "unsafe" 34 35 "github.com/iDigitalFlame/xmt/device/winapi" 36 "github.com/iDigitalFlame/xmt/util/xerr" 37 ) 38 39 // Standard Windows Service State values 40 // 41 // DO NOT REORDER 42 const ( 43 Stopped State = 1 + iota 44 StartPending 45 StopPending 46 Running 47 ContinuePending 48 PausePending 49 Paused 50 ) 51 52 // Standard Windows Service Reason values 53 // 54 // DO NOT REORDER 55 const ( 56 ReasonDemand Reason = 1 << iota 57 ReasonAuto 58 ReasonTrigger 59 ReasonRestartOnFailure 60 ReasonDelayedAuto 61 ) 62 63 // Standard Windows Service Command values 64 // 65 // DO NOT REORDER 66 const ( 67 Stop Command = 1 + iota 68 Pause 69 Continue 70 Interrogate 71 Shutdown 72 ParamChange 73 NetBindAdd 74 NetBindRemove 75 NetBindEnable 76 NetBindDisable 77 DeviceEvent 78 HardwareProfileChange 79 PowerEvent 80 SessionChange 81 PreShutdown 82 ) 83 84 // Standard Windows Service Accepted values 85 // 86 // DO NOT REORDER 87 const ( 88 AcceptStop Accepted = 1 << iota 89 AcceptPauseAndContinue 90 AcceptShutdown 91 AcceptParamChange 92 AcceptNetBindChange 93 AcceptHardwareProfileChange 94 AcceptPowerEvent 95 AcceptSessionChange 96 AcceptPreShutdown 97 ) 98 99 var service Service 100 101 var callBack struct { 102 _ [0]func() 103 sync.Once 104 f, m uintptr 105 } 106 107 // State describes the current service execution state (Stopped, Running, etc.) 108 type State uint32 109 110 // Reason is the reason that the service was started. 111 type Reason uint32 112 113 // Command represents a service state change request. It is sent to a service 114 // by the service manager, and should be acted upon by the service. 115 type Command uint32 116 117 // Accepted is used to describe commands accepted by the service. 118 // 119 // Interrogate is always accepted. 120 type Accepted uint32 121 122 // Change is sent to the service Handler to request service status changes and 123 // updates to the service control manager. 124 type Change struct { 125 Command Command 126 EventType uint32 127 EventData uintptr 128 Status Status 129 Context uintptr 130 } 131 132 // Status combines State and Accepted commands to fully describe running 133 // service. 134 type Status struct { 135 _ [0]func() 136 State State 137 Accepts Accepted 138 CheckPoint uint32 139 WaitHint uint32 140 ProcessID uint32 141 ExitCode uint32 142 } 143 144 // Service is a struct that is passed to the Handler function and can be used 145 // to receive and send updates to the service control manager. 146 // 147 // NOTE(dij): The function 'DynamicStartReason' is only available on Windows >7 148 // and will return an error if it does not exist. 149 type Service struct { 150 f Handler 151 e, in chan Change 152 out chan Status 153 n string 154 h uintptr 155 } 156 157 // Handler is a function interface that must be implemented to run as a Windows 158 // service. 159 // 160 // This function will be called by the package code at the start of the service, 161 // and the service will exit once Execute completes. 162 // 163 // Inside the function, you may use the context or read service change requests 164 // using the 's.Requests()' channel and act accordingly. 165 // 166 // You must keep service control manager up to date about state of your service 167 // by using the 'Update' or 'UpdateState' functions. 168 // 169 // The supplied string list contains the service name followed by argument 170 // strings passed to the service. 171 // 172 // You can provide service exit code in the return parameter, with 0 being 173 // "no error". 174 type Handler func(context.Context, Service, []string) uint32 175 176 // Handle returns a pointer to the current Service. This handle is only valid in 177 // the context of the running service. 178 func (s *Service) Handle() uintptr { 179 return s.h 180 } 181 182 // Update is used to send an update to the Service Control Manager. The 183 // supplied Status struct can be used to indicate status and progress to SCM. 184 func (s *Service) Update(v Status) { 185 s.out <- v 186 } 187 188 // Run executes service name by calling the appropriate handler function. 189 // 190 // This function will block until complete. 191 // Any errors returned indicate that bootstrappping of the service failed. 192 // 193 // Attempts to call this multiple times will return 'os.ErrInvalid'. 194 // 195 // NOTE: This function acts differently depending on the buildtags added. 196 // 197 // The "svcdll" tag can be used to call this from 'ServiceMain' as a CGO dll, 198 // which requires no service wiring. 199 func Run(name string, f Handler) error { 200 if service.f != nil { 201 return os.ErrInvalid 202 } 203 callBack.Do(func() { 204 service.e = make(chan Change) 205 callBack.m = syscall.NewCallback(serviceMain) 206 callBack.f = syscall.NewCallback(serviceHandler) 207 }) 208 service.n, service.f = name, f 209 runtime.LockOSThread() 210 err := serviceWireThread(name) 211 runtime.UnlockOSThread() 212 return err 213 } 214 215 // Requests returns a receive-only chan that will receive any updates sent from 216 // the Service control manager. 217 // 218 // It is required by SCM to act on these as soon as they are received. 219 func (s *Service) Requests() <-chan Change { 220 return s.in 221 } 222 func serviceHandler(c, e, d, _ uintptr) uintptr { 223 // NOTE(dij): ^ This pointer is SUPER FUCKING UNRELIABLE! Don't 224 // fucking use it! 225 service.e <- Change{Command: Command(c), EventType: uint32(e), EventData: d} 226 return 0 227 } 228 func serviceMain(argc uint32, argv **uint16) uintptr { 229 if service.f == nil || callBack.f == 0 || callBack.m == 0 { 230 return 0xE0000239 231 } 232 var err error 233 service.h, err = winapi.RegisterServiceCtrlHandlerEx(service.n, callBack.f, uintptr(unsafe.Pointer(&service))) 234 // NOTE(dij): ^ For some reason, keeping 235 // this here prevents it from 236 // being garbage collected. 237 if err != nil { 238 if e, ok := err.(syscall.Errno); ok { 239 return uintptr(e) 240 } 241 return 0xE0000239 242 } 243 var a []string 244 if argc > 0 { 245 var ( 246 e []*uint16 247 h = (*winapi.SliceHeader)(unsafe.Pointer(&e)) 248 ) 249 h.Data, h.Len, h.Cap = unsafe.Pointer(argv), int(argc), int(argc) 250 a = make([]string, len(e)) 251 for i, v := range e { 252 a[i] = winapi.UTF16PtrToString(v) 253 } 254 } 255 if err := service.update(Status{State: StartPending}, false, 0); err != nil { 256 if e, ok := err.(syscall.Errno); ok { 257 service.update(Status{State: Stopped}, false, uint32(e)) 258 return uintptr(e) 259 } 260 service.update(Status{State: Stopped}, false, 0xE0000239) 261 return 0xE0000239 262 } 263 var ( 264 b, y = context.WithCancel(context.Background()) 265 c = Status{State: StartPending} 266 x = make(chan uint32) 267 f uint32 268 ) 269 // NOTE(dij): Making the 'in' channel buffered so the sends to it doesn't 270 // block. 271 service.in, service.out = make(chan Change, 1), make(chan Status) 272 go func() { 273 defer func() { 274 if r := recover(); r != nil { 275 x <- 0x1 276 close(x) 277 } 278 }() 279 x <- service.f(b, service, a) 280 close(x) 281 }() 282 loop: 283 for { 284 select { 285 case f = <-x: 286 break loop 287 case v := <-service.e: 288 // NOTE(dij): Instead of dropping all the excess new entries on the 289 // floor, we should clear them instead as we want the 290 // service handler to see the latest entry. 291 for len(service.in) > 0 { 292 <-service.in 293 } 294 // NOTE(dij): Cancel the context and signal that we're working on 295 // closing up. 296 switch v.Status = c; v.Command { 297 case Stop, Shutdown: 298 y() 299 service.update(Status{State: StopPending}, false, 0) 300 } 301 service.in <- v 302 case v := <-service.out: 303 if err := service.update(v, false, v.ExitCode); err != nil { 304 if e, ok := err.(syscall.Errno); ok { 305 f = uint32(e) 306 } else { 307 f = 0xE0000239 308 } 309 break loop 310 } 311 c = v 312 } 313 } 314 service.update(Status{State: StopPending}, f > 0, f) 315 y() 316 close(service.in) 317 close(service.out) 318 service.update(Status{State: Stopped}, f > 0, f) 319 close(service.e) 320 service.h = 0 321 return 0 322 } 323 324 // UpdateState is used to send an update to the Service Control Manager. The 325 // supplied state type is required and an optional vardic of Accepted control 326 // types can be used to indicate to SCM what commands are accepted. 327 // 328 // This is a quick helper function for the 'Update' function. 329 func (s *Service) UpdateState(v State, a ...Accepted) { 330 if len(a) == 0 { 331 s.out <- Status{State: v} 332 return 333 } 334 if len(a) == 1 { 335 s.out <- Status{State: v, Accepts: a[0]} 336 return 337 } 338 u := Status{State: v, Accepts: a[0]} 339 for i := 1; i < len(a); i++ { 340 u.Accepts |= a[i] 341 } 342 s.out <- u 343 } 344 345 // DynamicStartReason will return the DynamicStartReason type. This function is 346 // only available after Windows 8 and will return an error if it is not supported. 347 func (s *Service) DynamicStartReason() (Reason, error) { 348 r, err := winapi.QueryServiceDynamicInformation(s.h, 1) 349 if err != nil { 350 return 0, err 351 } 352 return Reason(r), nil 353 } 354 func (s *Service) update(u Status, r bool, e uint32) error { 355 if s.h == 0 { 356 return xerr.Sub("update without a Service status handle", 0x14) 357 } 358 v := winapi.ServiceStatus{ServiceType: serviceType, CurrentState: uint32(u.State)} 359 if u.Accepts&AcceptStop != 0 { 360 v.ControlsAccepted |= 1 361 } 362 if u.Accepts&AcceptPauseAndContinue != 0 { 363 v.ControlsAccepted |= 2 364 } 365 if u.Accepts&AcceptShutdown != 0 { 366 v.ControlsAccepted |= 4 367 } 368 if u.Accepts&AcceptParamChange != 0 { 369 v.ControlsAccepted |= 8 370 } 371 if u.Accepts&AcceptNetBindChange != 0 { 372 v.ControlsAccepted |= 16 373 } 374 if u.Accepts&AcceptHardwareProfileChange != 0 { 375 v.ControlsAccepted |= 32 376 } 377 if u.Accepts&AcceptPowerEvent != 0 { 378 v.ControlsAccepted |= 64 379 } 380 if u.Accepts&AcceptSessionChange != 0 { 381 v.ControlsAccepted |= 128 382 } 383 if u.Accepts&AcceptPreShutdown != 0 { 384 v.ControlsAccepted |= 256 385 } 386 if e == 0 { 387 v.Win32ExitCode, v.ServiceSpecificExitCode = 0, 0 388 } else if r { 389 v.Win32ExitCode, v.ServiceSpecificExitCode = 1064, e 390 } else { 391 v.Win32ExitCode, v.ServiceSpecificExitCode = e, 0 392 } 393 v.CheckPoint, v.WaitHint = u.CheckPoint, u.WaitHint 394 return winapi.SetServiceStatus(s.h, &v) 395 }