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