github.com/go/docker@v1.12.0-rc2/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 flag "github.com/docker/docker/pkg/mflag" 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 = flag.String([]string{"-service-name"}, "docker", "Set the Windows service name") 24 flRegisterService = flag.Bool([]string{"-register-service"}, false, "Register the service and exit") 25 flUnregisterService = flag.Bool([]string{"-unregister-service"}, false, "Unregister the service and exit") 26 flRunService = flag.Bool([]string{"-run-service"}, false, "") 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 type handler struct { 48 tosvc chan bool 49 fromsvc chan error 50 } 51 52 type etwHook struct { 53 log *eventlog.Log 54 } 55 56 func (h *etwHook) Levels() []logrus.Level { 57 return []logrus.Level{ 58 logrus.PanicLevel, 59 logrus.FatalLevel, 60 logrus.ErrorLevel, 61 logrus.WarnLevel, 62 logrus.InfoLevel, 63 logrus.DebugLevel, 64 } 65 } 66 67 func (h *etwHook) Fire(e *logrus.Entry) error { 68 var ( 69 etype uint16 70 eid uint32 71 ) 72 73 switch e.Level { 74 case logrus.PanicLevel: 75 etype = windows.EVENTLOG_ERROR_TYPE 76 eid = eventPanic 77 case logrus.FatalLevel: 78 etype = windows.EVENTLOG_ERROR_TYPE 79 eid = eventFatal 80 case logrus.ErrorLevel: 81 etype = windows.EVENTLOG_ERROR_TYPE 82 eid = eventError 83 case logrus.WarnLevel: 84 etype = windows.EVENTLOG_WARNING_TYPE 85 eid = eventWarn 86 case logrus.InfoLevel: 87 etype = windows.EVENTLOG_INFORMATION_TYPE 88 eid = eventInfo 89 case logrus.DebugLevel: 90 etype = windows.EVENTLOG_INFORMATION_TYPE 91 eid = eventDebug 92 default: 93 return errors.New("unknown level") 94 } 95 96 // If there is additional data, include it as a second string. 97 exts := "" 98 if len(e.Data) > 0 { 99 fs := bytes.Buffer{} 100 for k, v := range e.Data { 101 fs.WriteString(k) 102 fs.WriteByte('=') 103 fmt.Fprint(&fs, v) 104 fs.WriteByte(' ') 105 } 106 107 exts = fs.String()[:fs.Len()-1] 108 eid += eventExtraOffset 109 } 110 111 if h.log == nil { 112 fmt.Fprintf(os.Stderr, "%s [%s]\n", e.Message, exts) 113 return nil 114 } 115 116 var ( 117 ss [2]*uint16 118 err error 119 ) 120 121 ss[0], err = syscall.UTF16PtrFromString(e.Message) 122 if err != nil { 123 return err 124 } 125 126 count := uint16(1) 127 if exts != "" { 128 ss[1], err = syscall.UTF16PtrFromString(exts) 129 if err != nil { 130 return err 131 } 132 133 count++ 134 } 135 136 return windows.ReportEvent(h.log.Handle, etype, 0, eid, 0, count, 0, &ss[0], nil) 137 } 138 139 func getServicePath() (string, error) { 140 p, err := exec.LookPath(os.Args[0]) 141 if err != nil { 142 return "", err 143 } 144 return filepath.Abs(p) 145 } 146 147 func registerService() error { 148 p, err := getServicePath() 149 if err != nil { 150 return err 151 } 152 m, err := mgr.Connect() 153 if err != nil { 154 return err 155 } 156 defer m.Disconnect() 157 c := mgr.Config{ 158 ServiceType: windows.SERVICE_WIN32_OWN_PROCESS, 159 StartType: mgr.StartAutomatic, 160 ErrorControl: mgr.ErrorNormal, 161 DisplayName: "Docker Engine", 162 } 163 164 // Configure the service to launch with the arguments that were just passed. 165 args := []string{"--run-service"} 166 for _, a := range os.Args[1:] { 167 if a != "--register-service" && a != "--unregister-service" { 168 args = append(args, a) 169 } 170 } 171 172 s, err := m.CreateService(*flServiceName, p, c, args...) 173 if err != nil { 174 return err 175 } 176 defer s.Close() 177 err = eventlog.Install(*flServiceName, p, false, eventlog.Info|eventlog.Warning|eventlog.Error) 178 if err != nil { 179 return err 180 } 181 182 return nil 183 } 184 185 func unregisterService() error { 186 m, err := mgr.Connect() 187 if err != nil { 188 return err 189 } 190 defer m.Disconnect() 191 192 s, err := m.OpenService(*flServiceName) 193 if err != nil { 194 return err 195 } 196 defer s.Close() 197 198 eventlog.Remove(*flServiceName) 199 err = s.Delete() 200 if err != nil { 201 return err 202 } 203 return nil 204 } 205 206 func initService() (bool, error) { 207 if *flUnregisterService { 208 if *flRegisterService { 209 return true, errors.New("--register-service and --unregister-service cannot be used together") 210 } 211 return true, unregisterService() 212 } 213 214 if *flRegisterService { 215 return true, registerService() 216 } 217 218 if !*flRunService { 219 return false, nil 220 } 221 222 interactive, err := svc.IsAnInteractiveSession() 223 if err != nil { 224 return false, err 225 } 226 227 h := &handler{ 228 tosvc: make(chan bool), 229 fromsvc: make(chan error), 230 } 231 232 var log *eventlog.Log 233 if !interactive { 234 log, err = eventlog.Open(*flServiceName) 235 if err != nil { 236 return false, err 237 } 238 } 239 240 logrus.AddHook(&etwHook{log}) 241 logrus.SetOutput(ioutil.Discard) 242 243 service = h 244 go func() { 245 if interactive { 246 err = debug.Run(*flServiceName, h) 247 } else { 248 err = svc.Run(*flServiceName, h) 249 } 250 251 h.fromsvc <- err 252 }() 253 254 // Wait for the first signal from the service handler. 255 err = <-h.fromsvc 256 if err != nil { 257 return false, err 258 } 259 return false, nil 260 } 261 262 func (h *handler) started() error { 263 // This must be delayed until daemonCli initializes Config.Root 264 err := initPanicFile(filepath.Join(daemonCli.Config.Root, "panic.log")) 265 if err != nil { 266 return err 267 } 268 269 h.tosvc <- false 270 return nil 271 } 272 273 func (h *handler) stopped(err error) { 274 logrus.Debugf("Stopping service: %v", err) 275 h.tosvc <- err != nil 276 <-h.fromsvc 277 } 278 279 func (h *handler) Execute(_ []string, r <-chan svc.ChangeRequest, s chan<- svc.Status) (bool, uint32) { 280 s <- svc.Status{State: svc.StartPending, Accepts: 0} 281 // Unblock initService() 282 h.fromsvc <- nil 283 284 // Wait for initialization to complete. 285 failed := <-h.tosvc 286 if failed { 287 logrus.Debug("Aborting service start due to failure during initialization") 288 return true, 1 289 } 290 291 s <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown | svc.Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)} 292 logrus.Debug("Service running") 293 Loop: 294 for { 295 select { 296 case failed = <-h.tosvc: 297 break Loop 298 case c := <-r: 299 switch c.Cmd { 300 case svc.Cmd(windows.SERVICE_CONTROL_PARAMCHANGE): 301 daemonCli.reloadConfig() 302 case svc.Interrogate: 303 s <- c.CurrentStatus 304 case svc.Stop, svc.Shutdown: 305 s <- svc.Status{State: svc.StopPending, Accepts: 0} 306 daemonCli.stop() 307 } 308 } 309 } 310 311 removePanicFile() 312 if failed { 313 return true, 1 314 } 315 return false, 0 316 } 317 318 func initPanicFile(path string) error { 319 var err error 320 panicFile, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0) 321 if err != nil { 322 return err 323 } 324 325 st, err := panicFile.Stat() 326 if err != nil { 327 return err 328 } 329 330 // If there are contents in the file already, move the file out of the way 331 // and replace it. 332 if st.Size() > 0 { 333 panicFile.Close() 334 os.Rename(path, path+".old") 335 panicFile, err = os.Create(path) 336 if err != nil { 337 return err 338 } 339 } 340 341 // Update STD_ERROR_HANDLE to point to the panic file so that Go writes to 342 // it when it panics. Remember the old stderr to restore it before removing 343 // the panic file. 344 sh := syscall.STD_ERROR_HANDLE 345 h, err := syscall.GetStdHandle(sh) 346 if err != nil { 347 return err 348 } 349 350 oldStderr = h 351 352 r, _, err := setStdHandle.Call(uintptr(sh), uintptr(panicFile.Fd())) 353 if r == 0 && err != nil { 354 return err 355 } 356 357 return nil 358 } 359 360 func removePanicFile() { 361 if st, err := panicFile.Stat(); err == nil { 362 if st.Size() == 0 { 363 sh := syscall.STD_ERROR_HANDLE 364 setStdHandle.Call(uintptr(sh), uintptr(oldStderr)) 365 panicFile.Close() 366 os.Remove(panicFile.Name()) 367 } 368 } 369 }