github.com/ssdev-go/moby@v17.12.1-ce-rc2+incompatible/cmd/dockerd/service_windows.go (about)

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