github.com/endophage/docker@v1.4.2-0.20161027011718-242853499895/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 13 "github.com/Sirupsen/logrus" 14 "github.com/spf13/pflag" 15 "golang.org/x/sys/windows" 16 "golang.org/x/sys/windows/svc" 17 "golang.org/x/sys/windows/svc/debug" 18 "golang.org/x/sys/windows/svc/eventlog" 19 "golang.org/x/sys/windows/svc/mgr" 20 ) 21 22 var ( 23 flServiceName *string 24 flRegisterService *bool 25 flUnregisterService *bool 26 flRunService *bool 27 28 setStdHandle = syscall.NewLazyDLL("kernel32.dll").NewProc("SetStdHandle") 29 oldStderr syscall.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 = syscall.UTF16PtrFromString(e.Message) 131 if err != nil { 132 return err 133 } 134 135 count := uint16(1) 136 if exts != "" { 137 ss[1], err = syscall.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 c := mgr.Config{ 167 ServiceType: windows.SERVICE_WIN32_OWN_PROCESS, 168 StartType: mgr.StartAutomatic, 169 ErrorControl: mgr.ErrorNormal, 170 DisplayName: "Docker Engine", 171 } 172 173 // Configure the service to launch with the arguments that were just passed. 174 args := []string{"--run-service"} 175 for _, a := range os.Args[1:] { 176 if a != "--register-service" && a != "--unregister-service" { 177 args = append(args, a) 178 } 179 } 180 181 s, err := m.CreateService(*flServiceName, p, c, args...) 182 if err != nil { 183 return err 184 } 185 defer s.Close() 186 err = eventlog.Install(*flServiceName, p, false, eventlog.Info|eventlog.Warning|eventlog.Error) 187 if err != nil { 188 return err 189 } 190 191 return nil 192 } 193 194 func unregisterService() error { 195 m, err := mgr.Connect() 196 if err != nil { 197 return err 198 } 199 defer m.Disconnect() 200 201 s, err := m.OpenService(*flServiceName) 202 if err != nil { 203 return err 204 } 205 defer s.Close() 206 207 eventlog.Remove(*flServiceName) 208 err = s.Delete() 209 if err != nil { 210 return err 211 } 212 return nil 213 } 214 215 func initService(daemonCli *DaemonCli) (bool, error) { 216 if *flUnregisterService { 217 if *flRegisterService { 218 return true, errors.New("--register-service and --unregister-service cannot be used together") 219 } 220 return true, unregisterService() 221 } 222 223 if *flRegisterService { 224 return true, registerService() 225 } 226 227 if !*flRunService { 228 return false, nil 229 } 230 231 interactive, err := svc.IsAnInteractiveSession() 232 if err != nil { 233 return false, err 234 } 235 236 h := &handler{ 237 tosvc: make(chan bool), 238 fromsvc: make(chan error), 239 daemonCli: daemonCli, 240 } 241 242 var log *eventlog.Log 243 if !interactive { 244 log, err = eventlog.Open(*flServiceName) 245 if err != nil { 246 return false, err 247 } 248 } 249 250 logrus.AddHook(&etwHook{log}) 251 logrus.SetOutput(ioutil.Discard) 252 253 service = h 254 go func() { 255 if interactive { 256 err = debug.Run(*flServiceName, h) 257 } else { 258 err = svc.Run(*flServiceName, h) 259 } 260 261 h.fromsvc <- err 262 }() 263 264 // Wait for the first signal from the service handler. 265 err = <-h.fromsvc 266 if err != nil { 267 return false, err 268 } 269 return false, nil 270 } 271 272 func (h *handler) started() error { 273 // This must be delayed until daemonCli initializes Config.Root 274 err := initPanicFile(filepath.Join(h.daemonCli.Config.Root, "panic.log")) 275 if err != nil { 276 return err 277 } 278 279 h.tosvc <- false 280 return nil 281 } 282 283 func (h *handler) stopped(err error) { 284 logrus.Debugf("Stopping service: %v", err) 285 h.tosvc <- err != nil 286 <-h.fromsvc 287 } 288 289 func (h *handler) Execute(_ []string, r <-chan svc.ChangeRequest, s chan<- svc.Status) (bool, uint32) { 290 s <- svc.Status{State: svc.StartPending, Accepts: 0} 291 // Unblock initService() 292 h.fromsvc <- nil 293 294 // Wait for initialization to complete. 295 failed := <-h.tosvc 296 if failed { 297 logrus.Debug("Aborting service start due to failure during initialization") 298 return true, 1 299 } 300 301 s <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown | svc.Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)} 302 logrus.Debug("Service running") 303 Loop: 304 for { 305 select { 306 case failed = <-h.tosvc: 307 break Loop 308 case c := <-r: 309 switch c.Cmd { 310 case svc.Cmd(windows.SERVICE_CONTROL_PARAMCHANGE): 311 h.daemonCli.reloadConfig() 312 case svc.Interrogate: 313 s <- c.CurrentStatus 314 case svc.Stop, svc.Shutdown: 315 s <- svc.Status{State: svc.StopPending, Accepts: 0} 316 h.daemonCli.stop() 317 } 318 } 319 } 320 321 removePanicFile() 322 if failed { 323 return true, 1 324 } 325 return false, 0 326 } 327 328 func initPanicFile(path string) error { 329 var err error 330 panicFile, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0) 331 if err != nil { 332 return err 333 } 334 335 st, err := panicFile.Stat() 336 if err != nil { 337 return err 338 } 339 340 // If there are contents in the file already, move the file out of the way 341 // and replace it. 342 if st.Size() > 0 { 343 panicFile.Close() 344 os.Rename(path, path+".old") 345 panicFile, err = os.Create(path) 346 if err != nil { 347 return err 348 } 349 } 350 351 // Update STD_ERROR_HANDLE to point to the panic file so that Go writes to 352 // it when it panics. Remember the old stderr to restore it before removing 353 // the panic file. 354 sh := syscall.STD_ERROR_HANDLE 355 h, err := syscall.GetStdHandle(sh) 356 if err != nil { 357 return err 358 } 359 360 oldStderr = h 361 362 r, _, err := setStdHandle.Call(uintptr(sh), uintptr(panicFile.Fd())) 363 if r == 0 && err != nil { 364 return err 365 } 366 367 return nil 368 } 369 370 func removePanicFile() { 371 if st, err := panicFile.Stat(); err == nil { 372 if st.Size() == 0 { 373 sh := syscall.STD_ERROR_HANDLE 374 setStdHandle.Call(uintptr(sh), uintptr(oldStderr)) 375 panicFile.Close() 376 os.Remove(panicFile.Name()) 377 } 378 } 379 }