gitee.com/quant1x/gox@v1.21.2/daemon/daemon_windows.go (about) 1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by 3 // license that can be found in the LICENSE file. 4 5 // Package daemon windows version 6 package daemon 7 8 import ( 9 "errors" 10 "fmt" 11 "os/exec" 12 "strconv" 13 "syscall" 14 "time" 15 "unicode/utf16" 16 "unsafe" 17 18 "golang.org/x/sys/windows" 19 "golang.org/x/sys/windows/registry" 20 "golang.org/x/sys/windows/svc" 21 "golang.org/x/sys/windows/svc/mgr" 22 ) 23 24 // windowsRecord - standard record (struct) for windows version of daemon package 25 type windowsRecord struct { 26 name string 27 description string 28 kind Kind 29 dependencies []string 30 } 31 32 func newDaemon(name, description string, kind Kind, dependencies []string) (Daemon, error) { 33 return &windowsRecord{name, description, kind, dependencies}, nil 34 } 35 36 func lowPrivMgr() (*mgr.Mgr, error) { 37 h, err := windows.OpenSCManager(nil, nil, windows.SC_MANAGER_CONNECT|windows.SC_MANAGER_ENUMERATE_SERVICE) 38 if err != nil { 39 return nil, err 40 } 41 return &mgr.Mgr{Handle: h}, nil 42 } 43 44 func lowPrivSvc(m *mgr.Mgr, name string) (*mgr.Service, error) { 45 h, err := windows.OpenService( 46 m.Handle, syscall.StringToUTF16Ptr(name), 47 windows.SERVICE_QUERY_CONFIG|windows.SERVICE_QUERY_STATUS|windows.SERVICE_START|windows.SERVICE_STOP) 48 if err != nil { 49 return nil, err 50 } 51 return &mgr.Service{Handle: h, Name: name}, nil 52 } 53 54 // Install the service 55 func (windows *windowsRecord) Install(args ...string) (string, error) { 56 initWindows() 57 installAction := "Install " + windows.description + ":" 58 59 execp, err := execPath() 60 if err != nil { 61 return installAction + failed, err 62 } 63 64 m, err := mgr.Connect() 65 if err != nil { 66 return installAction + failed, err 67 } 68 defer m.Disconnect() 69 70 s, err := m.OpenService(windows.name) 71 if err == nil { 72 s.Close() 73 return installAction + failed, ErrAlreadyRunning 74 } 75 76 // u, err := user.Current() 77 // if err != nil { 78 // return installAction + failed, err 79 // } 80 81 s, err = m.CreateService(windows.name, execp, mgr.Config{ 82 DisplayName: windows.name, 83 Description: windows.description, 84 StartType: mgr.StartAutomatic, 85 Dependencies: windows.dependencies, 86 // ServiceStartName: u.Username, 87 // Password: "", 88 }, args...) 89 if err != nil { 90 return installAction + failed, err 91 } 92 defer s.Close() 93 94 // set recovery action for service 95 // restart after 5 seconds for the first 3 times 96 // restart after 1 minute, otherwise 97 r := []mgr.RecoveryAction{ 98 mgr.RecoveryAction{ 99 Type: mgr.ServiceRestart, 100 Delay: 5000 * time.Millisecond, 101 }, 102 mgr.RecoveryAction{ 103 Type: mgr.ServiceRestart, 104 Delay: 5000 * time.Millisecond, 105 }, 106 mgr.RecoveryAction{ 107 Type: mgr.ServiceRestart, 108 Delay: 5000 * time.Millisecond, 109 }, 110 mgr.RecoveryAction{ 111 Type: mgr.ServiceRestart, 112 Delay: 60000 * time.Millisecond, 113 }, 114 } 115 // set reset period as a day 116 s.SetRecoveryActions(r, uint32(86400)) 117 118 return installAction + " completed.", nil 119 } 120 121 // Remove the service 122 func (windows *windowsRecord) Remove() (string, error) { 123 removeAction := "Removing " + windows.description + ":" 124 125 m, err := mgr.Connect() 126 if err != nil { 127 return removeAction + failed, getWindowsError(err) 128 } 129 defer m.Disconnect() 130 s, err := m.OpenService(windows.name) 131 if err != nil { 132 return removeAction + failed, getWindowsError(err) 133 } 134 defer s.Close() 135 err = s.Delete() 136 if err != nil { 137 return removeAction + failed, getWindowsError(err) 138 } 139 140 return removeAction + " completed.", nil 141 } 142 143 // Start the service 144 func (windows *windowsRecord) Start() (string, error) { 145 startAction := "Starting " + windows.description + ":" 146 147 m, err := mgr.Connect() 148 if err != nil { 149 return startAction + failed, getWindowsError(err) 150 } 151 defer m.Disconnect() 152 s, err := m.OpenService(windows.name) 153 if err != nil { 154 return startAction + failed, getWindowsError(err) 155 } 156 defer s.Close() 157 if err = s.Start(); err != nil { 158 return startAction + failed, getWindowsError(err) 159 } 160 161 return startAction + " completed.", nil 162 } 163 164 // Stop the service 165 func (windows *windowsRecord) Stop() (string, error) { 166 stopAction := "Stopping " + windows.description + ":" 167 168 m, err := mgr.Connect() 169 if err != nil { 170 return stopAction + failed, getWindowsError(err) 171 } 172 defer m.Disconnect() 173 s, err := m.OpenService(windows.name) 174 if err != nil { 175 return stopAction + failed, getWindowsError(err) 176 } 177 defer s.Close() 178 if err := stopAndWait(s); err != nil { 179 return stopAction + failed, getWindowsError(err) 180 } 181 182 return stopAction + " completed.", nil 183 } 184 185 func stopAndWait(s *mgr.Service) error { 186 // First stop the service. Then wait for the service to 187 // actually stop before starting it. 188 status, err := s.Control(svc.Stop) 189 if err != nil { 190 return err 191 } 192 193 timeDuration := time.Millisecond * 50 194 195 timeout := time.After(getStopTimeout() + (timeDuration * 2)) 196 tick := time.NewTicker(timeDuration) 197 defer tick.Stop() 198 199 for status.State != svc.Stopped { 200 select { 201 case <-tick.C: 202 status, err = s.Query() 203 if err != nil { 204 return err 205 } 206 case <-timeout: 207 break 208 } 209 } 210 return nil 211 } 212 213 func getStopTimeout() time.Duration { 214 // For default and paths see https://support.microsoft.com/en-us/kb/146092 215 defaultTimeout := time.Millisecond * 20000 216 key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control`, registry.READ) 217 if err != nil { 218 return defaultTimeout 219 } 220 sv, _, err := key.GetStringValue("WaitToKillServiceTimeout") 221 if err != nil { 222 return defaultTimeout 223 } 224 v, err := strconv.Atoi(sv) 225 if err != nil { 226 return defaultTimeout 227 } 228 return time.Millisecond * time.Duration(v) 229 } 230 231 // Status - Get service status 232 func (windows *windowsRecord) Status() (string, error) { 233 m, err := mgr.Connect() 234 if err != nil { 235 return "Getting status:" + failed, getWindowsError(err) 236 } 237 defer m.Disconnect() 238 s, err := m.OpenService(windows.name) 239 if err != nil { 240 return "Getting status:" + failed, getWindowsError(err) 241 } 242 defer s.Close() 243 status, err := s.Query() 244 if err != nil { 245 return "Getting status:" + failed, getWindowsError(err) 246 } 247 248 return "Status: " + getWindowsServiceStateFromUint32(status.State), nil 249 } 250 251 // Get executable path 252 func execPath() (string, error) { 253 var n uint32 254 b := make([]uint16, syscall.MAX_PATH) 255 size := uint32(len(b)) 256 257 r0, _, e1 := syscall.MustLoadDLL( 258 "kernel32.dll", 259 ).MustFindProc( 260 "GetModuleFileNameW", 261 ).Call(0, uintptr(unsafe.Pointer(&b[0])), uintptr(size)) 262 n = uint32(r0) 263 if n == 0 { 264 return "", e1 265 } 266 return string(utf16.Decode(b[0:n])), nil 267 } 268 269 // Get windows error 270 func getWindowsError(inputError error) error { 271 if exiterr, ok := inputError.(*exec.ExitError); ok { 272 if status, ok := exiterr.Sys().(syscall.WaitStatus); ok { 273 if sysErr, ok := WinErrCode[status.ExitStatus()]; ok { 274 return errors.New(fmt.Sprintf("\n %s: %s \n %s", sysErr.Title, sysErr.Description, sysErr.Action)) 275 } 276 } 277 } 278 279 return inputError 280 } 281 282 // Get windows service state 283 func getWindowsServiceStateFromUint32(state svc.State) string { 284 switch state { 285 case svc.Stopped: 286 return "SERVICE_STOPPED" 287 case svc.StartPending: 288 return "SERVICE_START_PENDING" 289 case svc.StopPending: 290 return "SERVICE_STOP_PENDING" 291 case svc.Running: 292 return "SERVICE_RUNNING" 293 case svc.ContinuePending: 294 return "SERVICE_CONTINUE_PENDING" 295 case svc.PausePending: 296 return "SERVICE_PAUSE_PENDING" 297 case svc.Paused: 298 return "SERVICE_PAUSED" 299 } 300 return "SERVICE_UNKNOWN" 301 } 302 303 type serviceHandler struct { 304 executable Executable 305 } 306 307 func (sh *serviceHandler) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) { 308 const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue 309 changes <- svc.Status{State: svc.StartPending} 310 fasttick := time.Tick(500 * time.Millisecond) 311 slowtick := time.Tick(2 * time.Second) 312 tick := fasttick 313 sh.executable.Start() 314 go sh.executable.Run() 315 changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} 316 loop: 317 for { 318 select { 319 case <-tick: 320 break 321 case c := <-r: 322 switch c.Cmd { 323 case svc.Interrogate: 324 changes <- c.CurrentStatus 325 // Testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4 326 time.Sleep(100 * time.Millisecond) 327 changes <- c.CurrentStatus 328 case svc.Stop, svc.Shutdown: 329 changes <- svc.Status{State: svc.StopPending} 330 sh.executable.Stop() 331 break loop 332 case svc.Pause: 333 changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted} 334 tick = slowtick 335 case svc.Continue: 336 changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted} 337 tick = fasttick 338 default: 339 continue loop 340 } 341 } 342 } 343 return 344 } 345 346 func (windows *windowsRecord) Run(e Executable) (string, error) { 347 runAction := "Running " + windows.description + ":" 348 349 interactive, err := svc.IsAnInteractiveSession() 350 if err != nil { 351 return runAction + failed, getWindowsError(err) 352 } 353 if !interactive { 354 // service called from windows service manager 355 // use API provided by golang.org/x/sys/windows 356 err = svc.Run(windows.name, &serviceHandler{ 357 executable: e, 358 }) 359 //err = svc.Run(windows.name, e) 360 if err != nil { 361 return runAction + failed, getWindowsError(err) 362 } 363 } else { 364 // otherwise, service should be called from terminal session 365 e.Run() 366 } 367 368 return runAction + " completed.", nil 369 } 370 371 // GetTemplate - gets service config template 372 func (linux *windowsRecord) GetTemplate() string { 373 return "" 374 } 375 376 // SetTemplate - sets service config template 377 func (linux *windowsRecord) SetTemplate(tplStr string) error { 378 return errors.New(fmt.Sprintf("templating is not supported for windows")) 379 }