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