github.com/vvnotw/moby@v1.13.1/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/docker/docker/pkg/system" 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 syscall.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 = syscall.UTF16PtrFromString(e.Message) 134 if err != nil { 135 return err 136 } 137 138 count := uint16(1) 139 if exts != "" { 140 ss[1], err = syscall.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 system.GetOSVersion().Build == 14393 { 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 err = eventlog.Install(*flServiceName, p, false, eventlog.Info|eventlog.Warning|eventlog.Error) 234 if err != nil { 235 return err 236 } 237 238 return nil 239 } 240 241 func unregisterService() error { 242 m, err := mgr.Connect() 243 if err != nil { 244 return err 245 } 246 defer m.Disconnect() 247 248 s, err := m.OpenService(*flServiceName) 249 if err != nil { 250 return err 251 } 252 defer s.Close() 253 254 eventlog.Remove(*flServiceName) 255 err = s.Delete() 256 if err != nil { 257 return err 258 } 259 return nil 260 } 261 262 func initService(daemonCli *DaemonCli) (bool, error) { 263 if *flUnregisterService { 264 if *flRegisterService { 265 return true, errors.New("--register-service and --unregister-service cannot be used together") 266 } 267 return true, unregisterService() 268 } 269 270 if *flRegisterService { 271 return true, registerService() 272 } 273 274 if !*flRunService { 275 return false, nil 276 } 277 278 interactive, err := svc.IsAnInteractiveSession() 279 if err != nil { 280 return false, err 281 } 282 283 h := &handler{ 284 tosvc: make(chan bool), 285 fromsvc: make(chan error), 286 daemonCli: daemonCli, 287 } 288 289 var log *eventlog.Log 290 if !interactive { 291 log, err = eventlog.Open(*flServiceName) 292 if err != nil { 293 return false, err 294 } 295 } 296 297 logrus.AddHook(&etwHook{log}) 298 logrus.SetOutput(ioutil.Discard) 299 300 service = h 301 go func() { 302 if interactive { 303 err = debug.Run(*flServiceName, h) 304 } else { 305 err = svc.Run(*flServiceName, h) 306 } 307 308 h.fromsvc <- err 309 }() 310 311 // Wait for the first signal from the service handler. 312 err = <-h.fromsvc 313 if err != nil { 314 return false, err 315 } 316 return false, nil 317 } 318 319 func (h *handler) started() error { 320 // This must be delayed until daemonCli initializes Config.Root 321 err := initPanicFile(filepath.Join(h.daemonCli.Config.Root, "panic.log")) 322 if err != nil { 323 return err 324 } 325 326 h.tosvc <- false 327 return nil 328 } 329 330 func (h *handler) stopped(err error) { 331 logrus.Debugf("Stopping service: %v", err) 332 h.tosvc <- err != nil 333 <-h.fromsvc 334 } 335 336 func (h *handler) Execute(_ []string, r <-chan svc.ChangeRequest, s chan<- svc.Status) (bool, uint32) { 337 s <- svc.Status{State: svc.StartPending, Accepts: 0} 338 // Unblock initService() 339 h.fromsvc <- nil 340 341 // Wait for initialization to complete. 342 failed := <-h.tosvc 343 if failed { 344 logrus.Debug("Aborting service start due to failure during initialization") 345 return true, 1 346 } 347 348 s <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown | svc.Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)} 349 logrus.Debug("Service running") 350 Loop: 351 for { 352 select { 353 case failed = <-h.tosvc: 354 break Loop 355 case c := <-r: 356 switch c.Cmd { 357 case svc.Cmd(windows.SERVICE_CONTROL_PARAMCHANGE): 358 h.daemonCli.reloadConfig() 359 case svc.Interrogate: 360 s <- c.CurrentStatus 361 case svc.Stop, svc.Shutdown: 362 s <- svc.Status{State: svc.StopPending, Accepts: 0} 363 h.daemonCli.stop() 364 } 365 } 366 } 367 368 removePanicFile() 369 if failed { 370 return true, 1 371 } 372 return false, 0 373 } 374 375 func initPanicFile(path string) error { 376 var err error 377 panicFile, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0) 378 if err != nil { 379 return err 380 } 381 382 st, err := panicFile.Stat() 383 if err != nil { 384 return err 385 } 386 387 // If there are contents in the file already, move the file out of the way 388 // and replace it. 389 if st.Size() > 0 { 390 panicFile.Close() 391 os.Rename(path, path+".old") 392 panicFile, err = os.Create(path) 393 if err != nil { 394 return err 395 } 396 } 397 398 // Update STD_ERROR_HANDLE to point to the panic file so that Go writes to 399 // it when it panics. Remember the old stderr to restore it before removing 400 // the panic file. 401 sh := syscall.STD_ERROR_HANDLE 402 h, err := syscall.GetStdHandle(sh) 403 if err != nil { 404 return err 405 } 406 407 oldStderr = h 408 409 r, _, err := setStdHandle.Call(uintptr(sh), uintptr(panicFile.Fd())) 410 if r == 0 && err != nil { 411 return err 412 } 413 414 return nil 415 } 416 417 func removePanicFile() { 418 if st, err := panicFile.Stat(); err == nil { 419 if st.Size() == 0 { 420 sh := syscall.STD_ERROR_HANDLE 421 setStdHandle.Call(uintptr(sh), uintptr(oldStderr)) 422 panicFile.Close() 423 os.Remove(panicFile.Name()) 424 } 425 } 426 }