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