123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431 |
- package main
- import (
- "bytes"
- "errors"
- "fmt"
- "io/ioutil"
- "log"
- "os"
- "os/exec"
- "path/filepath"
- "syscall"
- "time"
- "unsafe"
- "github.com/Sirupsen/logrus"
- "github.com/docker/docker/pkg/system"
- "github.com/spf13/pflag"
- "golang.org/x/sys/windows"
- "golang.org/x/sys/windows/svc"
- "golang.org/x/sys/windows/svc/debug"
- "golang.org/x/sys/windows/svc/eventlog"
- "golang.org/x/sys/windows/svc/mgr"
- )
- var (
- flServiceName *string
- flRegisterService *bool
- flUnregisterService *bool
- flRunService *bool
- setStdHandle = windows.NewLazySystemDLL("kernel32.dll").NewProc("SetStdHandle")
- oldStderr syscall.Handle
- panicFile *os.File
- service *handler
- )
- const (
- // These should match the values in event_messages.mc.
- eventInfo = 1
- eventWarn = 1
- eventError = 1
- eventDebug = 2
- eventPanic = 3
- eventFatal = 4
- eventExtraOffset = 10 // Add this to any event to get a string that supports extended data
- )
- func installServiceFlags(flags *pflag.FlagSet) {
- flServiceName = flags.String("service-name", "docker", "Set the Windows service name")
- flRegisterService = flags.Bool("register-service", false, "Register the service and exit")
- flUnregisterService = flags.Bool("unregister-service", false, "Unregister the service and exit")
- flRunService = flags.Bool("run-service", false, "")
- flags.MarkHidden("run-service")
- }
- type handler struct {
- tosvc chan bool
- fromsvc chan error
- daemonCli *DaemonCli
- }
- type etwHook struct {
- log *eventlog.Log
- }
- func (h *etwHook) Levels() []logrus.Level {
- return []logrus.Level{
- logrus.PanicLevel,
- logrus.FatalLevel,
- logrus.ErrorLevel,
- logrus.WarnLevel,
- logrus.InfoLevel,
- logrus.DebugLevel,
- }
- }
- func (h *etwHook) Fire(e *logrus.Entry) error {
- var (
- etype uint16
- eid uint32
- )
- switch e.Level {
- case logrus.PanicLevel:
- etype = windows.EVENTLOG_ERROR_TYPE
- eid = eventPanic
- case logrus.FatalLevel:
- etype = windows.EVENTLOG_ERROR_TYPE
- eid = eventFatal
- case logrus.ErrorLevel:
- etype = windows.EVENTLOG_ERROR_TYPE
- eid = eventError
- case logrus.WarnLevel:
- etype = windows.EVENTLOG_WARNING_TYPE
- eid = eventWarn
- case logrus.InfoLevel:
- etype = windows.EVENTLOG_INFORMATION_TYPE
- eid = eventInfo
- case logrus.DebugLevel:
- etype = windows.EVENTLOG_INFORMATION_TYPE
- eid = eventDebug
- default:
- return errors.New("unknown level")
- }
- // If there is additional data, include it as a second string.
- exts := ""
- if len(e.Data) > 0 {
- fs := bytes.Buffer{}
- for k, v := range e.Data {
- fs.WriteString(k)
- fs.WriteByte('=')
- fmt.Fprint(&fs, v)
- fs.WriteByte(' ')
- }
- exts = fs.String()[:fs.Len()-1]
- eid += eventExtraOffset
- }
- if h.log == nil {
- fmt.Fprintf(os.Stderr, "%s [%s]\n", e.Message, exts)
- return nil
- }
- var (
- ss [2]*uint16
- err error
- )
- ss[0], err = syscall.UTF16PtrFromString(e.Message)
- if err != nil {
- return err
- }
- count := uint16(1)
- if exts != "" {
- ss[1], err = syscall.UTF16PtrFromString(exts)
- if err != nil {
- return err
- }
- count++
- }
- return windows.ReportEvent(h.log.Handle, etype, 0, eid, 0, count, 0, &ss[0], nil)
- }
- func getServicePath() (string, error) {
- p, err := exec.LookPath(os.Args[0])
- if err != nil {
- return "", err
- }
- return filepath.Abs(p)
- }
- func registerService() error {
- p, err := getServicePath()
- if err != nil {
- return err
- }
- m, err := mgr.Connect()
- if err != nil {
- return err
- }
- defer m.Disconnect()
- depends := []string{}
- // This dependency is required on build 14393 (RS1)
- // it is added to the platform in newer builds
- if system.GetOSVersion().Build == 14393 {
- depends = append(depends, "ConDrv")
- }
- c := mgr.Config{
- ServiceType: windows.SERVICE_WIN32_OWN_PROCESS,
- StartType: mgr.StartAutomatic,
- ErrorControl: mgr.ErrorNormal,
- Dependencies: depends,
- DisplayName: "Docker Engine",
- }
- // Configure the service to launch with the arguments that were just passed.
- args := []string{"--run-service"}
- for _, a := range os.Args[1:] {
- if a != "--register-service" && a != "--unregister-service" {
- args = append(args, a)
- }
- }
- s, err := m.CreateService(*flServiceName, p, c, args...)
- if err != nil {
- return err
- }
- defer s.Close()
- // See http://stackoverflow.com/questions/35151052/how-do-i-configure-failure-actions-of-a-windows-service-written-in-go
- const (
- scActionNone = 0
- scActionRestart = 1
- scActionReboot = 2
- scActionRunCommand = 3
- serviceConfigFailureActions = 2
- )
- type serviceFailureActions struct {
- ResetPeriod uint32
- RebootMsg *uint16
- Command *uint16
- ActionsCount uint32
- Actions uintptr
- }
- type scAction struct {
- Type uint32
- Delay uint32
- }
- t := []scAction{
- {Type: scActionRestart, Delay: uint32(60 * time.Second / time.Millisecond)},
- {Type: scActionRestart, Delay: uint32(60 * time.Second / time.Millisecond)},
- {Type: scActionNone},
- }
- lpInfo := serviceFailureActions{ResetPeriod: uint32(24 * time.Hour / time.Second), ActionsCount: uint32(3), Actions: uintptr(unsafe.Pointer(&t[0]))}
- err = windows.ChangeServiceConfig2(s.Handle, serviceConfigFailureActions, (*byte)(unsafe.Pointer(&lpInfo)))
- if err != nil {
- return err
- }
- return eventlog.Install(*flServiceName, p, false, eventlog.Info|eventlog.Warning|eventlog.Error)
- }
- func unregisterService() error {
- m, err := mgr.Connect()
- if err != nil {
- return err
- }
- defer m.Disconnect()
- s, err := m.OpenService(*flServiceName)
- if err != nil {
- return err
- }
- defer s.Close()
- eventlog.Remove(*flServiceName)
- err = s.Delete()
- if err != nil {
- return err
- }
- return nil
- }
- // initService is the entry point for running the daemon as a Windows
- // service. It returns an indication to stop (if registering/un-registering);
- // an indication of whether it is running as a service; and an error.
- func initService(daemonCli *DaemonCli) (bool, bool, error) {
- if *flUnregisterService {
- if *flRegisterService {
- return true, false, errors.New("--register-service and --unregister-service cannot be used together")
- }
- return true, false, unregisterService()
- }
- if *flRegisterService {
- return true, false, registerService()
- }
- if !*flRunService {
- return false, false, nil
- }
- interactive, err := svc.IsAnInteractiveSession()
- if err != nil {
- return false, false, err
- }
- h := &handler{
- tosvc: make(chan bool),
- fromsvc: make(chan error),
- daemonCli: daemonCli,
- }
- var log *eventlog.Log
- if !interactive {
- log, err = eventlog.Open(*flServiceName)
- if err != nil {
- return false, false, err
- }
- }
- logrus.AddHook(&etwHook{log})
- logrus.SetOutput(ioutil.Discard)
- service = h
- go func() {
- if interactive {
- err = debug.Run(*flServiceName, h)
- } else {
- err = svc.Run(*flServiceName, h)
- }
- h.fromsvc <- err
- }()
- // Wait for the first signal from the service handler.
- err = <-h.fromsvc
- if err != nil {
- return false, false, err
- }
- return false, true, nil
- }
- func (h *handler) started() error {
- // This must be delayed until daemonCli initializes Config.Root
- err := initPanicFile(filepath.Join(h.daemonCli.Config.Root, "panic.log"))
- if err != nil {
- return err
- }
- h.tosvc <- false
- return nil
- }
- func (h *handler) stopped(err error) {
- logrus.Debugf("Stopping service: %v", err)
- h.tosvc <- err != nil
- <-h.fromsvc
- }
- func (h *handler) Execute(_ []string, r <-chan svc.ChangeRequest, s chan<- svc.Status) (bool, uint32) {
- s <- svc.Status{State: svc.StartPending, Accepts: 0}
- // Unblock initService()
- h.fromsvc <- nil
- // Wait for initialization to complete.
- failed := <-h.tosvc
- if failed {
- logrus.Debug("Aborting service start due to failure during initialization")
- return true, 1
- }
- s <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown | svc.Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)}
- logrus.Debug("Service running")
- Loop:
- for {
- select {
- case failed = <-h.tosvc:
- break Loop
- case c := <-r:
- switch c.Cmd {
- case svc.Cmd(windows.SERVICE_CONTROL_PARAMCHANGE):
- h.daemonCli.reloadConfig()
- case svc.Interrogate:
- s <- c.CurrentStatus
- case svc.Stop, svc.Shutdown:
- s <- svc.Status{State: svc.StopPending, Accepts: 0}
- h.daemonCli.stop()
- }
- }
- }
- removePanicFile()
- if failed {
- return true, 1
- }
- return false, 0
- }
- func initPanicFile(path string) error {
- var err error
- panicFile, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0)
- if err != nil {
- return err
- }
- st, err := panicFile.Stat()
- if err != nil {
- return err
- }
- // If there are contents in the file already, move the file out of the way
- // and replace it.
- if st.Size() > 0 {
- panicFile.Close()
- os.Rename(path, path+".old")
- panicFile, err = os.Create(path)
- if err != nil {
- return err
- }
- }
- // Update STD_ERROR_HANDLE to point to the panic file so that Go writes to
- // it when it panics. Remember the old stderr to restore it before removing
- // the panic file.
- sh := syscall.STD_ERROR_HANDLE
- h, err := syscall.GetStdHandle(sh)
- if err != nil {
- return err
- }
- oldStderr = h
- r, _, err := setStdHandle.Call(uintptr(sh), uintptr(panicFile.Fd()))
- if r == 0 && err != nil {
- return err
- }
- // Reset os.Stderr to the panic file (so fmt.Fprintf(os.Stderr,...) actually gets redirected)
- os.Stderr = os.NewFile(uintptr(panicFile.Fd()), "/dev/stderr")
- // Force threads that panic to write to stderr (the panicFile handle now), otherwise it will go into the ether
- log.SetOutput(os.Stderr)
- return nil
- }
- func removePanicFile() {
- if st, err := panicFile.Stat(); err == nil {
- if st.Size() == 0 {
- sh := syscall.STD_ERROR_HANDLE
- setStdHandle.Call(uintptr(sh), uintptr(oldStderr))
- panicFile.Close()
- os.Remove(panicFile.Name())
- }
- }
- }
|