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