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  }