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