github.com/Heebron/moby@v0.0.0-20221111184709-6eab4f55faf7/cmd/dockerd/service_windows.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"log"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"time"
    13  
    14  	"github.com/sirupsen/logrus"
    15  	"github.com/spf13/pflag"
    16  	"golang.org/x/sys/windows"
    17  	"golang.org/x/sys/windows/svc"
    18  	"golang.org/x/sys/windows/svc/debug"
    19  	"golang.org/x/sys/windows/svc/eventlog"
    20  	"golang.org/x/sys/windows/svc/mgr"
    21  )
    22  
    23  var (
    24  	flServiceName       *string
    25  	flRegisterService   *bool
    26  	flUnregisterService *bool
    27  	flRunService        *bool
    28  
    29  	oldStderr windows.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  	_ = flags.MarkHidden("run-service")
    53  }
    54  
    55  type handler struct {
    56  	tosvc     chan bool
    57  	fromsvc   chan error
    58  	daemonCli *DaemonCli
    59  }
    60  
    61  type etwHook struct {
    62  	log *eventlog.Log
    63  }
    64  
    65  func (h *etwHook) Levels() []logrus.Level {
    66  	return []logrus.Level{
    67  		logrus.PanicLevel,
    68  		logrus.FatalLevel,
    69  		logrus.ErrorLevel,
    70  		logrus.WarnLevel,
    71  		logrus.InfoLevel,
    72  		logrus.DebugLevel,
    73  	}
    74  }
    75  
    76  func (h *etwHook) Fire(e *logrus.Entry) error {
    77  	var (
    78  		etype uint16
    79  		eid   uint32
    80  	)
    81  
    82  	switch e.Level {
    83  	case logrus.PanicLevel:
    84  		etype = windows.EVENTLOG_ERROR_TYPE
    85  		eid = eventPanic
    86  	case logrus.FatalLevel:
    87  		etype = windows.EVENTLOG_ERROR_TYPE
    88  		eid = eventFatal
    89  	case logrus.ErrorLevel:
    90  		etype = windows.EVENTLOG_ERROR_TYPE
    91  		eid = eventError
    92  	case logrus.WarnLevel:
    93  		etype = windows.EVENTLOG_WARNING_TYPE
    94  		eid = eventWarn
    95  	case logrus.InfoLevel:
    96  		etype = windows.EVENTLOG_INFORMATION_TYPE
    97  		eid = eventInfo
    98  	case logrus.DebugLevel:
    99  		etype = windows.EVENTLOG_INFORMATION_TYPE
   100  		eid = eventDebug
   101  	default:
   102  		return errors.New("unknown level")
   103  	}
   104  
   105  	// If there is additional data, include it as a second string.
   106  	exts := ""
   107  	if len(e.Data) > 0 {
   108  		fs := bytes.Buffer{}
   109  		for k, v := range e.Data {
   110  			fs.WriteString(k)
   111  			fs.WriteByte('=')
   112  			fmt.Fprint(&fs, v)
   113  			fs.WriteByte(' ')
   114  		}
   115  
   116  		exts = fs.String()[:fs.Len()-1]
   117  		eid += eventExtraOffset
   118  	}
   119  
   120  	if h.log == nil {
   121  		fmt.Fprintf(os.Stderr, "%s [%s]\n", e.Message, exts)
   122  		return nil
   123  	}
   124  
   125  	var (
   126  		ss  [2]*uint16
   127  		err error
   128  	)
   129  
   130  	ss[0], err = windows.UTF16PtrFromString(e.Message)
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	count := uint16(1)
   136  	if exts != "" {
   137  		ss[1], err = windows.UTF16PtrFromString(exts)
   138  		if err != nil {
   139  			return err
   140  		}
   141  
   142  		count++
   143  	}
   144  
   145  	return windows.ReportEvent(h.log.Handle, etype, 0, eid, 0, count, 0, &ss[0], nil)
   146  }
   147  
   148  func getServicePath() (string, error) {
   149  	p, err := exec.LookPath(os.Args[0])
   150  	if err != nil {
   151  		return "", err
   152  	}
   153  	return filepath.Abs(p)
   154  }
   155  
   156  func registerService() error {
   157  	p, err := getServicePath()
   158  	if err != nil {
   159  		return err
   160  	}
   161  	m, err := mgr.Connect()
   162  	if err != nil {
   163  		return err
   164  	}
   165  	defer m.Disconnect()
   166  
   167  	c := mgr.Config{
   168  		ServiceType:  windows.SERVICE_WIN32_OWN_PROCESS,
   169  		StartType:    mgr.StartAutomatic,
   170  		ErrorControl: mgr.ErrorNormal,
   171  		Dependencies: []string{},
   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  	err = s.SetRecoveryActions(
   190  		[]mgr.RecoveryAction{
   191  			{Type: mgr.ServiceRestart, Delay: 15 * time.Second},
   192  			{Type: mgr.ServiceRestart, Delay: 15 * time.Second},
   193  			{Type: mgr.NoAction},
   194  		},
   195  		uint32(24*time.Hour/time.Second),
   196  	)
   197  	if err != nil {
   198  		return err
   199  	}
   200  
   201  	return eventlog.Install(*flServiceName, p, false, eventlog.Info|eventlog.Warning|eventlog.Error)
   202  }
   203  
   204  func unregisterService() error {
   205  	m, err := mgr.Connect()
   206  	if err != nil {
   207  		return err
   208  	}
   209  	defer m.Disconnect()
   210  
   211  	s, err := m.OpenService(*flServiceName)
   212  	if err != nil {
   213  		return err
   214  	}
   215  	defer s.Close()
   216  
   217  	eventlog.Remove(*flServiceName)
   218  	err = s.Delete()
   219  	if err != nil {
   220  		return err
   221  	}
   222  	return nil
   223  }
   224  
   225  // initService is the entry point for running the daemon as a Windows
   226  // service. It returns an indication to stop (if registering/un-registering);
   227  // an indication of whether it is running as a service; and an error.
   228  func initService(daemonCli *DaemonCli) (bool, bool, error) {
   229  	if *flUnregisterService {
   230  		if *flRegisterService {
   231  			return true, false, errors.New("--register-service and --unregister-service cannot be used together")
   232  		}
   233  		return true, false, unregisterService()
   234  	}
   235  
   236  	if *flRegisterService {
   237  		return true, false, registerService()
   238  	}
   239  
   240  	if !*flRunService {
   241  		return false, false, nil
   242  	}
   243  
   244  	// Check if we're running as a Windows service or interactively.
   245  	isService, err := svc.IsWindowsService()
   246  	if err != nil {
   247  		return false, false, err
   248  	}
   249  
   250  	h := &handler{
   251  		tosvc:     make(chan bool),
   252  		fromsvc:   make(chan error),
   253  		daemonCli: daemonCli,
   254  	}
   255  
   256  	var log *eventlog.Log
   257  	if isService {
   258  		log, err = eventlog.Open(*flServiceName)
   259  		if err != nil {
   260  			return false, false, err
   261  		}
   262  	}
   263  
   264  	logrus.AddHook(&etwHook{log})
   265  	logrus.SetOutput(io.Discard)
   266  
   267  	service = h
   268  	go func() {
   269  		if isService {
   270  			err = svc.Run(*flServiceName, h)
   271  		} else {
   272  			err = debug.Run(*flServiceName, h)
   273  		}
   274  
   275  		h.fromsvc <- err
   276  	}()
   277  
   278  	// Wait for the first signal from the service handler.
   279  	err = <-h.fromsvc
   280  	if err != nil {
   281  		return false, false, err
   282  	}
   283  	return false, true, nil
   284  }
   285  
   286  func (h *handler) started() error {
   287  	// This must be delayed until daemonCli initializes Config.Root
   288  	err := initPanicFile(filepath.Join(h.daemonCli.Config.Root, "panic.log"))
   289  	if err != nil {
   290  		return err
   291  	}
   292  
   293  	h.tosvc <- false
   294  	return nil
   295  }
   296  
   297  func (h *handler) stopped(err error) {
   298  	logrus.Debugf("Stopping service: %v", err)
   299  	h.tosvc <- err != nil
   300  	<-h.fromsvc
   301  }
   302  
   303  func (h *handler) Execute(_ []string, r <-chan svc.ChangeRequest, s chan<- svc.Status) (bool, uint32) {
   304  	s <- svc.Status{State: svc.StartPending, Accepts: 0}
   305  	// Unblock initService()
   306  	h.fromsvc <- nil
   307  
   308  	// Wait for initialization to complete.
   309  	failed := <-h.tosvc
   310  	if failed {
   311  		logrus.Debug("Aborting service start due to failure during initialization")
   312  		return true, 1
   313  	}
   314  
   315  	s <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown | svc.Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)}
   316  	logrus.Debug("Service running")
   317  Loop:
   318  	for {
   319  		select {
   320  		case failed = <-h.tosvc:
   321  			break Loop
   322  		case c := <-r:
   323  			switch c.Cmd {
   324  			case svc.Cmd(windows.SERVICE_CONTROL_PARAMCHANGE):
   325  				h.daemonCli.reloadConfig()
   326  			case svc.Interrogate:
   327  				s <- c.CurrentStatus
   328  			case svc.Stop, svc.Shutdown:
   329  				s <- svc.Status{State: svc.StopPending, Accepts: 0}
   330  				h.daemonCli.stop()
   331  			}
   332  		}
   333  	}
   334  
   335  	removePanicFile()
   336  	if failed {
   337  		return true, 1
   338  	}
   339  	return false, 0
   340  }
   341  
   342  func initPanicFile(path string) error {
   343  	var err error
   344  	panicFile, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o200)
   345  	if err != nil {
   346  		return err
   347  	}
   348  
   349  	st, err := panicFile.Stat()
   350  	if err != nil {
   351  		return err
   352  	}
   353  
   354  	// If there are contents in the file already, move the file out of the way
   355  	// and replace it.
   356  	if st.Size() > 0 {
   357  		panicFile.Close()
   358  		os.Rename(path, path+".old")
   359  		panicFile, err = os.Create(path)
   360  		if err != nil {
   361  			return err
   362  		}
   363  	}
   364  
   365  	// Update STD_ERROR_HANDLE to point to the panic file so that Go writes to
   366  	// it when it panics. Remember the old stderr to restore it before removing
   367  	// the panic file.
   368  	h, err := windows.GetStdHandle(windows.STD_ERROR_HANDLE)
   369  	if err != nil {
   370  		return err
   371  	}
   372  	oldStderr = h
   373  
   374  	err = windows.SetStdHandle(windows.STD_ERROR_HANDLE, windows.Handle(panicFile.Fd()))
   375  	if err != nil {
   376  		return err
   377  	}
   378  
   379  	// Reset os.Stderr to the panic file (so fmt.Fprintf(os.Stderr,...) actually gets redirected)
   380  	os.Stderr = os.NewFile(panicFile.Fd(), "/dev/stderr")
   381  
   382  	// Force threads that panic to write to stderr (the panicFile handle now), otherwise it will go into the ether
   383  	log.SetOutput(os.Stderr)
   384  
   385  	return nil
   386  }
   387  
   388  func removePanicFile() {
   389  	if st, err := panicFile.Stat(); err == nil {
   390  		if st.Size() == 0 {
   391  			windows.SetStdHandle(windows.STD_ERROR_HANDLE, oldStderr)
   392  			panicFile.Close()
   393  			os.Remove(panicFile.Name())
   394  		}
   395  	}
   396  }