github.com/olljanat/moby@v1.13.1/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  	"time"
    13  	"unsafe"
    14  
    15  	"github.com/Sirupsen/logrus"
    16  	"github.com/docker/docker/pkg/system"
    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    syscall.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 = syscall.UTF16PtrFromString(e.Message)
   134  	if err != nil {
   135  		return err
   136  	}
   137  
   138  	count := uint16(1)
   139  	if exts != "" {
   140  		ss[1], err = syscall.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  	err = eventlog.Install(*flServiceName, p, false, eventlog.Info|eventlog.Warning|eventlog.Error)
   234  	if err != nil {
   235  		return err
   236  	}
   237  
   238  	return nil
   239  }
   240  
   241  func unregisterService() error {
   242  	m, err := mgr.Connect()
   243  	if err != nil {
   244  		return err
   245  	}
   246  	defer m.Disconnect()
   247  
   248  	s, err := m.OpenService(*flServiceName)
   249  	if err != nil {
   250  		return err
   251  	}
   252  	defer s.Close()
   253  
   254  	eventlog.Remove(*flServiceName)
   255  	err = s.Delete()
   256  	if err != nil {
   257  		return err
   258  	}
   259  	return nil
   260  }
   261  
   262  func initService(daemonCli *DaemonCli) (bool, error) {
   263  	if *flUnregisterService {
   264  		if *flRegisterService {
   265  			return true, errors.New("--register-service and --unregister-service cannot be used together")
   266  		}
   267  		return true, unregisterService()
   268  	}
   269  
   270  	if *flRegisterService {
   271  		return true, registerService()
   272  	}
   273  
   274  	if !*flRunService {
   275  		return false, nil
   276  	}
   277  
   278  	interactive, err := svc.IsAnInteractiveSession()
   279  	if err != nil {
   280  		return false, err
   281  	}
   282  
   283  	h := &handler{
   284  		tosvc:     make(chan bool),
   285  		fromsvc:   make(chan error),
   286  		daemonCli: daemonCli,
   287  	}
   288  
   289  	var log *eventlog.Log
   290  	if !interactive {
   291  		log, err = eventlog.Open(*flServiceName)
   292  		if err != nil {
   293  			return false, err
   294  		}
   295  	}
   296  
   297  	logrus.AddHook(&etwHook{log})
   298  	logrus.SetOutput(ioutil.Discard)
   299  
   300  	service = h
   301  	go func() {
   302  		if interactive {
   303  			err = debug.Run(*flServiceName, h)
   304  		} else {
   305  			err = svc.Run(*flServiceName, h)
   306  		}
   307  
   308  		h.fromsvc <- err
   309  	}()
   310  
   311  	// Wait for the first signal from the service handler.
   312  	err = <-h.fromsvc
   313  	if err != nil {
   314  		return false, err
   315  	}
   316  	return false, nil
   317  }
   318  
   319  func (h *handler) started() error {
   320  	// This must be delayed until daemonCli initializes Config.Root
   321  	err := initPanicFile(filepath.Join(h.daemonCli.Config.Root, "panic.log"))
   322  	if err != nil {
   323  		return err
   324  	}
   325  
   326  	h.tosvc <- false
   327  	return nil
   328  }
   329  
   330  func (h *handler) stopped(err error) {
   331  	logrus.Debugf("Stopping service: %v", err)
   332  	h.tosvc <- err != nil
   333  	<-h.fromsvc
   334  }
   335  
   336  func (h *handler) Execute(_ []string, r <-chan svc.ChangeRequest, s chan<- svc.Status) (bool, uint32) {
   337  	s <- svc.Status{State: svc.StartPending, Accepts: 0}
   338  	// Unblock initService()
   339  	h.fromsvc <- nil
   340  
   341  	// Wait for initialization to complete.
   342  	failed := <-h.tosvc
   343  	if failed {
   344  		logrus.Debug("Aborting service start due to failure during initialization")
   345  		return true, 1
   346  	}
   347  
   348  	s <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown | svc.Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)}
   349  	logrus.Debug("Service running")
   350  Loop:
   351  	for {
   352  		select {
   353  		case failed = <-h.tosvc:
   354  			break Loop
   355  		case c := <-r:
   356  			switch c.Cmd {
   357  			case svc.Cmd(windows.SERVICE_CONTROL_PARAMCHANGE):
   358  				h.daemonCli.reloadConfig()
   359  			case svc.Interrogate:
   360  				s <- c.CurrentStatus
   361  			case svc.Stop, svc.Shutdown:
   362  				s <- svc.Status{State: svc.StopPending, Accepts: 0}
   363  				h.daemonCli.stop()
   364  			}
   365  		}
   366  	}
   367  
   368  	removePanicFile()
   369  	if failed {
   370  		return true, 1
   371  	}
   372  	return false, 0
   373  }
   374  
   375  func initPanicFile(path string) error {
   376  	var err error
   377  	panicFile, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0)
   378  	if err != nil {
   379  		return err
   380  	}
   381  
   382  	st, err := panicFile.Stat()
   383  	if err != nil {
   384  		return err
   385  	}
   386  
   387  	// If there are contents in the file already, move the file out of the way
   388  	// and replace it.
   389  	if st.Size() > 0 {
   390  		panicFile.Close()
   391  		os.Rename(path, path+".old")
   392  		panicFile, err = os.Create(path)
   393  		if err != nil {
   394  			return err
   395  		}
   396  	}
   397  
   398  	// Update STD_ERROR_HANDLE to point to the panic file so that Go writes to
   399  	// it when it panics. Remember the old stderr to restore it before removing
   400  	// the panic file.
   401  	sh := syscall.STD_ERROR_HANDLE
   402  	h, err := syscall.GetStdHandle(sh)
   403  	if err != nil {
   404  		return err
   405  	}
   406  
   407  	oldStderr = h
   408  
   409  	r, _, err := setStdHandle.Call(uintptr(sh), uintptr(panicFile.Fd()))
   410  	if r == 0 && err != nil {
   411  		return err
   412  	}
   413  
   414  	return nil
   415  }
   416  
   417  func removePanicFile() {
   418  	if st, err := panicFile.Stat(); err == nil {
   419  		if st.Size() == 0 {
   420  			sh := syscall.STD_ERROR_HANDLE
   421  			setStdHandle.Call(uintptr(sh), uintptr(oldStderr))
   422  			panicFile.Close()
   423  			os.Remove(panicFile.Name())
   424  		}
   425  	}
   426  }