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