github.com/rish1988/moby@v25.0.2+incompatible/cmd/dockerd/service_windows.go (about)

     1  package main
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	"io"
     9  	"os"
    10  	"path/filepath"
    11  	"time"
    12  
    13  	"github.com/containerd/log"
    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  	oldStderr windows.Handle
    29  	panicFile *os.File
    30  
    31  	service *handler
    32  )
    33  
    34  const (
    35  	// These should match the values in event_messages.mc.
    36  	eventInfo  = 1
    37  	eventWarn  = 1
    38  	eventError = 1
    39  	eventDebug = 2
    40  	eventPanic = 3
    41  	eventFatal = 4
    42  
    43  	eventExtraOffset = 10 // Add this to any event to get a string that supports extended data
    44  )
    45  
    46  func installServiceFlags(flags *pflag.FlagSet) {
    47  	flServiceName = flags.String("service-name", "docker", "Set the Windows service name")
    48  	flRegisterService = flags.Bool("register-service", false, "Register the service and exit")
    49  	flUnregisterService = flags.Bool("unregister-service", false, "Unregister the service and exit")
    50  	flRunService = flags.Bool("run-service", false, "")
    51  	_ = flags.MarkHidden("run-service")
    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() []log.Level {
    65  	return []log.Level{
    66  		log.PanicLevel,
    67  		log.FatalLevel,
    68  		log.ErrorLevel,
    69  		log.WarnLevel,
    70  		log.InfoLevel,
    71  		log.DebugLevel,
    72  	}
    73  }
    74  
    75  func (h *etwHook) Fire(e *log.Entry) error {
    76  	var (
    77  		etype uint16
    78  		eid   uint32
    79  	)
    80  
    81  	switch e.Level {
    82  	case log.PanicLevel:
    83  		etype = windows.EVENTLOG_ERROR_TYPE
    84  		eid = eventPanic
    85  	case log.FatalLevel:
    86  		etype = windows.EVENTLOG_ERROR_TYPE
    87  		eid = eventFatal
    88  	case log.ErrorLevel:
    89  		etype = windows.EVENTLOG_ERROR_TYPE
    90  		eid = eventError
    91  	case log.WarnLevel:
    92  		etype = windows.EVENTLOG_WARNING_TYPE
    93  		eid = eventWarn
    94  	case log.InfoLevel:
    95  		etype = windows.EVENTLOG_INFORMATION_TYPE
    96  		eid = eventInfo
    97  	case log.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 = windows.UTF16PtrFromString(e.Message)
   130  	if err != nil {
   131  		return err
   132  	}
   133  
   134  	count := uint16(1)
   135  	if exts != "" {
   136  		ss[1], err = windows.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 registerService() error {
   148  	p, err := os.Executable()
   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  
   158  	c := mgr.Config{
   159  		ServiceType:  windows.SERVICE_WIN32_OWN_PROCESS,
   160  		StartType:    mgr.StartAutomatic,
   161  		ErrorControl: mgr.ErrorNormal,
   162  		Dependencies: []string{},
   163  		DisplayName:  "Docker Engine",
   164  	}
   165  
   166  	// Configure the service to launch with the arguments that were just passed.
   167  	args := []string{"--run-service"}
   168  	for _, a := range os.Args[1:] {
   169  		if a != "--register-service" && a != "--unregister-service" {
   170  			args = append(args, a)
   171  		}
   172  	}
   173  
   174  	s, err := m.CreateService(*flServiceName, p, c, args...)
   175  	if err != nil {
   176  		return err
   177  	}
   178  	defer s.Close()
   179  
   180  	err = s.SetRecoveryActions(
   181  		[]mgr.RecoveryAction{
   182  			{Type: mgr.ServiceRestart, Delay: 15 * time.Second},
   183  			{Type: mgr.ServiceRestart, Delay: 15 * time.Second},
   184  			{Type: mgr.NoAction},
   185  		},
   186  		uint32(24*time.Hour/time.Second),
   187  	)
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	return eventlog.Install(*flServiceName, p, false, eventlog.Info|eventlog.Warning|eventlog.Error)
   193  }
   194  
   195  func unregisterService() error {
   196  	m, err := mgr.Connect()
   197  	if err != nil {
   198  		return err
   199  	}
   200  	defer m.Disconnect()
   201  
   202  	s, err := m.OpenService(*flServiceName)
   203  	if err != nil {
   204  		return err
   205  	}
   206  	defer s.Close()
   207  
   208  	eventlog.Remove(*flServiceName)
   209  	err = s.Delete()
   210  	if err != nil {
   211  		return err
   212  	}
   213  	return nil
   214  }
   215  
   216  // initService is the entry point for running the daemon as a Windows
   217  // service. It returns an indication to stop (if registering/un-registering);
   218  // an indication of whether it is running as a service; and an error.
   219  func initService(daemonCli *DaemonCli) (bool, bool, error) {
   220  	if *flUnregisterService {
   221  		if *flRegisterService {
   222  			return true, false, errors.New("--register-service and --unregister-service cannot be used together")
   223  		}
   224  		return true, false, unregisterService()
   225  	}
   226  
   227  	if *flRegisterService {
   228  		return true, false, registerService()
   229  	}
   230  
   231  	if !*flRunService {
   232  		return false, false, nil
   233  	}
   234  
   235  	// Check if we're running as a Windows service or interactively.
   236  	isService, err := svc.IsWindowsService()
   237  	if err != nil {
   238  		return false, false, err
   239  	}
   240  
   241  	h := &handler{
   242  		tosvc:     make(chan bool),
   243  		fromsvc:   make(chan error),
   244  		daemonCli: daemonCli,
   245  	}
   246  
   247  	var eventLog *eventlog.Log
   248  	if isService {
   249  		eventLog, err = eventlog.Open(*flServiceName)
   250  		if err != nil {
   251  			return false, false, err
   252  		}
   253  	}
   254  
   255  	log.L.Logger.AddHook(&etwHook{eventLog})
   256  	log.L.Logger.SetOutput(io.Discard)
   257  
   258  	service = h
   259  	go func() {
   260  		if isService {
   261  			err = svc.Run(*flServiceName, h)
   262  		} else {
   263  			err = debug.Run(*flServiceName, h)
   264  		}
   265  
   266  		h.fromsvc <- err
   267  	}()
   268  
   269  	// Wait for the first signal from the service handler.
   270  	err = <-h.fromsvc
   271  	if err != nil {
   272  		return false, false, err
   273  	}
   274  	return false, true, nil
   275  }
   276  
   277  func (h *handler) started() error {
   278  	// This must be delayed until daemonCli initializes Config.Root
   279  	err := initPanicFile(filepath.Join(h.daemonCli.Config.Root, "panic.log"))
   280  	if err != nil {
   281  		return err
   282  	}
   283  
   284  	h.tosvc <- false
   285  	return nil
   286  }
   287  
   288  func (h *handler) stopped(err error) {
   289  	log.G(context.TODO()).Debugf("Stopping service: %v", err)
   290  	h.tosvc <- err != nil
   291  	<-h.fromsvc
   292  }
   293  
   294  func (h *handler) Execute(_ []string, r <-chan svc.ChangeRequest, s chan<- svc.Status) (bool, uint32) {
   295  	s <- svc.Status{State: svc.StartPending, Accepts: 0}
   296  	// Unblock initService()
   297  	h.fromsvc <- nil
   298  
   299  	// Wait for initialization to complete.
   300  	failed := <-h.tosvc
   301  	if failed {
   302  		log.G(context.TODO()).Debug("Aborting service start due to failure during initialization")
   303  		return true, 1
   304  	}
   305  
   306  	s <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown | svc.Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)}
   307  	log.G(context.TODO()).Debug("Service running")
   308  Loop:
   309  	for {
   310  		select {
   311  		case failed = <-h.tosvc:
   312  			break Loop
   313  		case c := <-r:
   314  			switch c.Cmd {
   315  			case svc.Cmd(windows.SERVICE_CONTROL_PARAMCHANGE):
   316  				h.daemonCli.reloadConfig()
   317  			case svc.Interrogate:
   318  				s <- c.CurrentStatus
   319  			case svc.Stop, svc.Shutdown:
   320  				s <- svc.Status{State: svc.StopPending, Accepts: 0}
   321  				h.daemonCli.stop()
   322  			}
   323  		}
   324  	}
   325  
   326  	removePanicFile()
   327  	if failed {
   328  		return true, 1
   329  	}
   330  	return false, 0
   331  }
   332  
   333  func initPanicFile(path string) error {
   334  	var err error
   335  	panicFile, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0o200)
   336  	if err != nil {
   337  		return err
   338  	}
   339  
   340  	st, err := panicFile.Stat()
   341  	if err != nil {
   342  		return err
   343  	}
   344  
   345  	// If there are contents in the file already, move the file out of the way
   346  	// and replace it.
   347  	if st.Size() > 0 {
   348  		panicFile.Close()
   349  		os.Rename(path, path+".old")
   350  		panicFile, err = os.Create(path)
   351  		if err != nil {
   352  			return err
   353  		}
   354  	}
   355  
   356  	// Update STD_ERROR_HANDLE to point to the panic file so that Go writes to
   357  	// it when it panics. Remember the old stderr to restore it before removing
   358  	// the panic file.
   359  	h, err := windows.GetStdHandle(windows.STD_ERROR_HANDLE)
   360  	if err != nil {
   361  		return err
   362  	}
   363  	oldStderr = h
   364  
   365  	err = windows.SetStdHandle(windows.STD_ERROR_HANDLE, windows.Handle(panicFile.Fd()))
   366  	if err != nil {
   367  		return err
   368  	}
   369  
   370  	// Reset os.Stderr to the panic file (so fmt.Fprintf(os.Stderr,...) actually gets redirected)
   371  	os.Stderr = os.NewFile(panicFile.Fd(), "/dev/stderr")
   372  
   373  	// Force threads that panic to write to stderr (the panicFile handle now), otherwise it will go into the ether
   374  	log.L.Logger.SetOutput(os.Stderr)
   375  
   376  	return nil
   377  }
   378  
   379  func removePanicFile() {
   380  	if st, err := panicFile.Stat(); err == nil {
   381  		if st.Size() == 0 {
   382  			windows.SetStdHandle(windows.STD_ERROR_HANDLE, oldStderr)
   383  			panicFile.Close()
   384  			os.Remove(panicFile.Name())
   385  		}
   386  	}
   387  }