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