github.com/sohaha/zlsgo@v1.7.13-0.20240501141223-10dd1a906f76/zutil/daemon/daemon_windows.go (about) 1 package daemon 2 3 import ( 4 "context" 5 "fmt" 6 "strconv" 7 "strings" 8 "sync" 9 "time" 10 11 "github.com/sohaha/zlsgo/zshell" 12 "golang.org/x/sys/windows/registry" 13 "golang.org/x/sys/windows/svc" 14 "golang.org/x/sys/windows/svc/eventlog" 15 "golang.org/x/sys/windows/svc/mgr" 16 ) 17 18 type ( 19 windowsSystem struct{} 20 windowsService struct { 21 i Iface 22 stopStartErr error 23 *Config 24 errSync sync.Mutex 25 } 26 ) 27 28 const version = "windows-service" 29 30 var interactive = false 31 32 func init() { 33 var err error 34 chooseSystem(windowsSystem{}) 35 interactive, err = svc.IsAnInteractiveSession() 36 if err != nil { 37 panic(err) 38 } 39 } 40 41 func (windowsSystem) String() string { 42 return version 43 } 44 45 func (windowsSystem) Detect() bool { 46 return true 47 } 48 49 func (windowsSystem) Interactive() bool { 50 return interactive 51 } 52 53 func (windowsSystem) New(i Iface, c *Config) (ServiceIface, error) { 54 if c.Context == nil { 55 c.Context = context.Background() 56 } 57 58 ws := &windowsService{ 59 i: i, 60 Config: c, 61 } 62 63 return ws, nil 64 } 65 66 func (w *windowsService) String() string { 67 if len(w.DisplayName) > 0 { 68 return w.DisplayName 69 } 70 return w.Name 71 } 72 73 func (w *windowsService) setError(err error) { 74 w.errSync.Lock() 75 defer w.errSync.Unlock() 76 w.stopStartErr = err 77 } 78 79 func (w *windowsService) getError() error { 80 w.errSync.Lock() 81 defer w.errSync.Unlock() 82 return w.stopStartErr 83 } 84 85 func (w *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) { 86 const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown 87 changes <- svc.Status{State: svc.StartPending} 88 89 if err := w.i.Start(w); err != nil { 90 w.setError(err) 91 return true, 1 92 } 93 94 changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} 95 loop: 96 for { 97 c := <-r 98 switch c.Cmd { 99 case svc.Interrogate: 100 changes <- c.CurrentStatus 101 case svc.Stop, svc.Shutdown: 102 changes <- svc.Status{State: svc.StopPending} 103 if err := w.i.Stop(w); err != nil { 104 w.setError(err) 105 return true, 2 106 } 107 break loop 108 default: 109 continue loop 110 } 111 } 112 113 return false, 0 114 } 115 116 func (w *windowsService) Install() error { 117 m, err := connect() 118 if err != nil { 119 return err 120 } 121 defer m.Disconnect() 122 exepath := w.execPath() 123 s, err := m.OpenService(w.Name) 124 if err == nil { 125 s.Close() 126 return fmt.Errorf("service %s already exists", w.Name) 127 } 128 password := "" 129 if p, ok := w.Options["Password"]; ok { 130 password, _ = p.(string) 131 } 132 s, err = m.CreateService(w.Name, exepath, mgr.Config{ 133 DisplayName: w.DisplayName, 134 Description: w.Description, 135 StartType: mgr.StartAutomatic, 136 ServiceStartName: w.UserName, 137 Password: password, 138 }, w.Arguments...) 139 if err != nil { 140 return err 141 } 142 defer s.Close() 143 err = eventlog.InstallAsEventCreate(w.Name, eventlog.Error|eventlog.Warning|eventlog.Info) 144 if err != nil { 145 _ = s.Delete() 146 return fmt.Errorf("installAsEventCreate() failed: %s", err) 147 } 148 149 if isServiceRestart(w.Config) { 150 _ = s.SetRecoveryActions([]mgr.RecoveryAction{ 151 { 152 Type: mgr.ServiceRestart, 153 Delay: 0, 154 }, 155 }, 0) 156 } 157 158 return nil 159 } 160 161 func (w *windowsService) Uninstall() error { 162 m, err := connect() 163 if err != nil { 164 return err 165 } 166 defer m.Disconnect() 167 168 s, err := m.OpenService(w.Name) 169 if err != nil { 170 return fmt.Errorf("service %s is not installed", w.Name) 171 } 172 defer s.Close() 173 174 _ = w.Stop() 175 176 err = s.Delete() 177 if err != nil { 178 return err 179 } 180 181 err = eventlog.Remove(w.Name) 182 if err != nil { 183 return fmt.Errorf("removeEventLogSource() failed: %s", err) 184 } 185 186 return nil 187 } 188 189 func (w *windowsService) Run() error { 190 w.setError(nil) 191 if !interactive { 192 runErr := svc.Run(w.Name, w) 193 startStopErr := w.getError() 194 if startStopErr != nil { 195 return startStopErr 196 } 197 if runErr != nil { 198 return runErr 199 } 200 return nil 201 } 202 err := w.i.Start(w) 203 if err != nil { 204 return err 205 } 206 207 select { 208 case <-SingleKillSignal(): 209 case <-w.Config.Context.Done(): 210 } 211 212 return w.i.Stop(w) 213 } 214 215 func (w *windowsService) Start() error { 216 m, err := connect() 217 if err != nil { 218 return err 219 } 220 defer m.Disconnect() 221 s, err := m.OpenService(w.Name) 222 if err != nil { 223 return err 224 } 225 defer s.Close() 226 227 if isServiceRestart(w.Config) { 228 _ = s.SetRecoveryActions([]mgr.RecoveryAction{ 229 { 230 Type: mgr.ServiceRestart, 231 Delay: 0, 232 }, 233 }, 0) 234 } 235 236 return s.Start() 237 } 238 239 func (w *windowsService) Stop() error { 240 m, err := connect() 241 if err != nil { 242 return err 243 } 244 defer m.Disconnect() 245 246 s, err := m.OpenService(w.Name) 247 if err != nil { 248 return err 249 } 250 defer s.Close() 251 252 if isServiceRestart(w.Config) { 253 _ = s.SetRecoveryActions([]mgr.RecoveryAction{ 254 { 255 Type: mgr.NoAction, 256 Delay: 0, 257 }, 258 }, 0) 259 } 260 261 return w.stopWait(s) 262 } 263 264 func (w *windowsService) Restart() error { 265 err := w.Stop() 266 if err != nil { 267 return err 268 } 269 return w.Start() 270 } 271 272 func (w *windowsService) Status() string { 273 m, err := connect() 274 if err != nil { 275 return "Unknown" 276 } 277 defer m.Disconnect() 278 s, err := m.OpenService(w.Name) 279 if err != nil { 280 return err.Error() 281 } 282 defer s.Close() 283 q, err := s.Query() 284 if err != nil { 285 return err.Error() 286 } 287 switch q.State { 288 case svc.Running: 289 return "Running" 290 case svc.StopPending: 291 return "StopPending" 292 case svc.Stopped: 293 return "Stop" 294 } 295 return strconv.Itoa(int(q.State)) 296 } 297 298 func (w *windowsService) forceKeep(processId uint32) error { 299 ss := "taskkill /F /pid " + strconv.Itoa(int(processId)) 300 _, _, _, err := zshell.Run(ss) 301 return err 302 } 303 304 func (w *windowsService) stopWait(s *mgr.Service) error { 305 status, err := s.Control(svc.Stop) 306 if err != nil { 307 if !strings.Contains(err.Error(), "not valid") { 308 return err 309 } 310 status, err = s.Query() 311 if err != nil { 312 return err 313 } 314 _ = w.forceKeep(status.ProcessId) 315 } 316 317 timeDuration := time.Millisecond * 100 318 timeout := time.After(getStopTimeout() + (timeDuration * 2)) 319 tick := time.NewTicker(timeDuration) 320 defer tick.Stop() 321 for status.State != svc.Stopped { 322 select { 323 case <-tick.C: 324 status, err = s.Query() 325 if err != nil { 326 return err 327 } 328 case <-timeout: 329 _ = w.forceKeep(status.ProcessId) 330 break 331 } 332 } 333 return nil 334 } 335 336 func connect() (*mgr.Mgr, error) { 337 m, err := mgr.Connect() 338 if err != nil { 339 if strings.Contains(err.Error(), "Access is denied") { 340 err = ErrNotAnAdministrator 341 } 342 } 343 return m, err 344 } 345 func getStopTimeout() time.Duration { 346 // For default and paths see https://support.microsoft.com/en-us/kb/146092 347 defaultTimeout := time.Millisecond * 20000 348 key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control`, registry.READ) 349 if err != nil { 350 return defaultTimeout 351 } 352 sv, _, err := key.GetStringValue("WaitToKillServiceTimeout") 353 if err != nil { 354 return defaultTimeout 355 } 356 v, err := strconv.Atoi(sv) 357 if err != nil { 358 return defaultTimeout 359 } 360 return time.Millisecond * time.Duration(v) 361 }