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