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