github.com/zhuohuang-hust/src-cbuild@v0.0.0-20230105071821-c7aab3e7c840/cmd/dockerd/service_windows.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "syscall" 12 "time" 13 "unsafe" 14 15 "github.com/Sirupsen/logrus" 16 "github.com/spf13/pflag" 17 "golang.org/x/sys/windows" 18 "golang.org/x/sys/windows/svc" 19 "golang.org/x/sys/windows/svc/debug" 20 "golang.org/x/sys/windows/svc/eventlog" 21 "golang.org/x/sys/windows/svc/mgr" 22 ) 23 24 var ( 25 flServiceName *string 26 flRegisterService *bool 27 flUnregisterService *bool 28 flRunService *bool 29 30 setStdHandle = windows.NewLazySystemDLL("kernel32.dll").NewProc("SetStdHandle") 31 oldStderr syscall.Handle 32 panicFile *os.File 33 34 service *handler 35 ) 36 37 const ( 38 // These should match the values in event_messages.mc. 39 eventInfo = 1 40 eventWarn = 1 41 eventError = 1 42 eventDebug = 2 43 eventPanic = 3 44 eventFatal = 4 45 46 eventExtraOffset = 10 // Add this to any event to get a string that supports extended data 47 ) 48 49 func installServiceFlags(flags *pflag.FlagSet) { 50 flServiceName = flags.String("service-name", "docker", "Set the Windows service name") 51 flRegisterService = flags.Bool("register-service", false, "Register the service and exit") 52 flUnregisterService = flags.Bool("unregister-service", false, "Unregister the service and exit") 53 flRunService = flags.Bool("run-service", false, "") 54 flags.MarkHidden("run-service") 55 } 56 57 type handler struct { 58 tosvc chan bool 59 fromsvc chan error 60 daemonCli *DaemonCli 61 } 62 63 type etwHook struct { 64 log *eventlog.Log 65 } 66 67 func (h *etwHook) Levels() []logrus.Level { 68 return []logrus.Level{ 69 logrus.PanicLevel, 70 logrus.FatalLevel, 71 logrus.ErrorLevel, 72 logrus.WarnLevel, 73 logrus.InfoLevel, 74 logrus.DebugLevel, 75 } 76 } 77 78 func (h *etwHook) Fire(e *logrus.Entry) error { 79 var ( 80 etype uint16 81 eid uint32 82 ) 83 84 switch e.Level { 85 case logrus.PanicLevel: 86 etype = windows.EVENTLOG_ERROR_TYPE 87 eid = eventPanic 88 case logrus.FatalLevel: 89 etype = windows.EVENTLOG_ERROR_TYPE 90 eid = eventFatal 91 case logrus.ErrorLevel: 92 etype = windows.EVENTLOG_ERROR_TYPE 93 eid = eventError 94 case logrus.WarnLevel: 95 etype = windows.EVENTLOG_WARNING_TYPE 96 eid = eventWarn 97 case logrus.InfoLevel: 98 etype = windows.EVENTLOG_INFORMATION_TYPE 99 eid = eventInfo 100 case logrus.DebugLevel: 101 etype = windows.EVENTLOG_INFORMATION_TYPE 102 eid = eventDebug 103 default: 104 return errors.New("unknown level") 105 } 106 107 // If there is additional data, include it as a second string. 108 exts := "" 109 if len(e.Data) > 0 { 110 fs := bytes.Buffer{} 111 for k, v := range e.Data { 112 fs.WriteString(k) 113 fs.WriteByte('=') 114 fmt.Fprint(&fs, v) 115 fs.WriteByte(' ') 116 } 117 118 exts = fs.String()[:fs.Len()-1] 119 eid += eventExtraOffset 120 } 121 122 if h.log == nil { 123 fmt.Fprintf(os.Stderr, "%s [%s]\n", e.Message, exts) 124 return nil 125 } 126 127 var ( 128 ss [2]*uint16 129 err error 130 ) 131 132 ss[0], err = syscall.UTF16PtrFromString(e.Message) 133 if err != nil { 134 return err 135 } 136 137 count := uint16(1) 138 if exts != "" { 139 ss[1], err = syscall.UTF16PtrFromString(exts) 140 if err != nil { 141 return err 142 } 143 144 count++ 145 } 146 147 return windows.ReportEvent(h.log.Handle, etype, 0, eid, 0, count, 0, &ss[0], nil) 148 } 149 150 func getServicePath() (string, error) { 151 p, err := exec.LookPath(os.Args[0]) 152 if err != nil { 153 return "", err 154 } 155 return filepath.Abs(p) 156 } 157 158 func registerService() error { 159 p, err := getServicePath() 160 if err != nil { 161 return err 162 } 163 m, err := mgr.Connect() 164 if err != nil { 165 return err 166 } 167 defer m.Disconnect() 168 c := mgr.Config{ 169 ServiceType: windows.SERVICE_WIN32_OWN_PROCESS, 170 StartType: mgr.StartAutomatic, 171 ErrorControl: mgr.ErrorNormal, 172 DisplayName: "Docker Engine", 173 } 174 175 // Configure the service to launch with the arguments that were just passed. 176 args := []string{"--run-service"} 177 for _, a := range os.Args[1:] { 178 if a != "--register-service" && a != "--unregister-service" { 179 args = append(args, a) 180 } 181 } 182 183 s, err := m.CreateService(*flServiceName, p, c, args...) 184 if err != nil { 185 return err 186 } 187 defer s.Close() 188 189 // See http://stackoverflow.com/questions/35151052/how-do-i-configure-failure-actions-of-a-windows-service-written-in-go 190 const ( 191 scActionNone = 0 192 scActionRestart = 1 193 scActionReboot = 2 194 scActionRunCommand = 3 195 196 serviceConfigFailureActions = 2 197 ) 198 199 type serviceFailureActions struct { 200 ResetPeriod uint32 201 RebootMsg *uint16 202 Command *uint16 203 ActionsCount uint32 204 Actions uintptr 205 } 206 207 type scAction struct { 208 Type uint32 209 Delay uint32 210 } 211 t := []scAction{ 212 {Type: scActionRestart, Delay: uint32(60 * time.Second / time.Millisecond)}, 213 {Type: scActionRestart, Delay: uint32(60 * time.Second / time.Millisecond)}, 214 {Type: scActionNone}, 215 } 216 lpInfo := serviceFailureActions{ResetPeriod: uint32(24 * time.Hour / time.Second), ActionsCount: uint32(3), Actions: uintptr(unsafe.Pointer(&t[0]))} 217 err = windows.ChangeServiceConfig2(s.Handle, serviceConfigFailureActions, (*byte)(unsafe.Pointer(&lpInfo))) 218 if err != nil { 219 return err 220 } 221 222 err = eventlog.Install(*flServiceName, p, false, eventlog.Info|eventlog.Warning|eventlog.Error) 223 if err != nil { 224 return err 225 } 226 227 return nil 228 } 229 230 func unregisterService() error { 231 m, err := mgr.Connect() 232 if err != nil { 233 return err 234 } 235 defer m.Disconnect() 236 237 s, err := m.OpenService(*flServiceName) 238 if err != nil { 239 return err 240 } 241 defer s.Close() 242 243 eventlog.Remove(*flServiceName) 244 err = s.Delete() 245 if err != nil { 246 return err 247 } 248 return nil 249 } 250 251 func initService(daemonCli *DaemonCli) (bool, error) { 252 if *flUnregisterService { 253 if *flRegisterService { 254 return true, errors.New("--register-service and --unregister-service cannot be used together") 255 } 256 return true, unregisterService() 257 } 258 259 if *flRegisterService { 260 return true, registerService() 261 } 262 263 if !*flRunService { 264 return false, nil 265 } 266 267 interactive, err := svc.IsAnInteractiveSession() 268 if err != nil { 269 return false, err 270 } 271 272 h := &handler{ 273 tosvc: make(chan bool), 274 fromsvc: make(chan error), 275 daemonCli: daemonCli, 276 } 277 278 var log *eventlog.Log 279 if !interactive { 280 log, err = eventlog.Open(*flServiceName) 281 if err != nil { 282 return false, err 283 } 284 } 285 286 logrus.AddHook(&etwHook{log}) 287 logrus.SetOutput(ioutil.Discard) 288 289 service = h 290 go func() { 291 if interactive { 292 err = debug.Run(*flServiceName, h) 293 } else { 294 err = svc.Run(*flServiceName, h) 295 } 296 297 h.fromsvc <- err 298 }() 299 300 // Wait for the first signal from the service handler. 301 err = <-h.fromsvc 302 if err != nil { 303 return false, err 304 } 305 return false, nil 306 } 307 308 func (h *handler) started() error { 309 // This must be delayed until daemonCli initializes Config.Root 310 err := initPanicFile(filepath.Join(h.daemonCli.Config.Root, "panic.log")) 311 if err != nil { 312 return err 313 } 314 315 h.tosvc <- false 316 return nil 317 } 318 319 func (h *handler) stopped(err error) { 320 logrus.Debugf("Stopping service: %v", err) 321 h.tosvc <- err != nil 322 <-h.fromsvc 323 } 324 325 func (h *handler) Execute(_ []string, r <-chan svc.ChangeRequest, s chan<- svc.Status) (bool, uint32) { 326 s <- svc.Status{State: svc.StartPending, Accepts: 0} 327 // Unblock initService() 328 h.fromsvc <- nil 329 330 // Wait for initialization to complete. 331 failed := <-h.tosvc 332 if failed { 333 logrus.Debug("Aborting service start due to failure during initialization") 334 return true, 1 335 } 336 337 s <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown | svc.Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)} 338 logrus.Debug("Service running") 339 Loop: 340 for { 341 select { 342 case failed = <-h.tosvc: 343 break Loop 344 case c := <-r: 345 switch c.Cmd { 346 case svc.Cmd(windows.SERVICE_CONTROL_PARAMCHANGE): 347 h.daemonCli.reloadConfig() 348 case svc.Interrogate: 349 s <- c.CurrentStatus 350 case svc.Stop, svc.Shutdown: 351 s <- svc.Status{State: svc.StopPending, Accepts: 0} 352 h.daemonCli.stop() 353 } 354 } 355 } 356 357 removePanicFile() 358 if failed { 359 return true, 1 360 } 361 return false, 0 362 } 363 364 func initPanicFile(path string) error { 365 var err error 366 panicFile, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0) 367 if err != nil { 368 return err 369 } 370 371 st, err := panicFile.Stat() 372 if err != nil { 373 return err 374 } 375 376 // If there are contents in the file already, move the file out of the way 377 // and replace it. 378 if st.Size() > 0 { 379 panicFile.Close() 380 os.Rename(path, path+".old") 381 panicFile, err = os.Create(path) 382 if err != nil { 383 return err 384 } 385 } 386 387 // Update STD_ERROR_HANDLE to point to the panic file so that Go writes to 388 // it when it panics. Remember the old stderr to restore it before removing 389 // the panic file. 390 sh := syscall.STD_ERROR_HANDLE 391 h, err := syscall.GetStdHandle(sh) 392 if err != nil { 393 return err 394 } 395 396 oldStderr = h 397 398 r, _, err := setStdHandle.Call(uintptr(sh), uintptr(panicFile.Fd())) 399 if r == 0 && err != nil { 400 return err 401 } 402 403 return nil 404 } 405 406 func removePanicFile() { 407 if st, err := panicFile.Stat(); err == nil { 408 if st.Size() == 0 { 409 sh := syscall.STD_ERROR_HANDLE 410 setStdHandle.Call(uintptr(sh), uintptr(oldStderr)) 411 panicFile.Close() 412 os.Remove(panicFile.Name()) 413 } 414 } 415 }