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