github.com/zhuohuang-hust/src-cbuild@v0.0.0-20230105071821-c7aab3e7c840/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  	err = eventlog.Install(*flServiceName, p, false, eventlog.Info|eventlog.Warning|eventlog.Error)
   223  	if err != nil {
   224  		return err
   225  	}
   226  
   227  	return nil
   228  }
   229  
   230  func unregisterService() error {
   231  	m, err := mgr.Connect()
   232  	if err != nil {
   233  		return err
   234  	}
   235  	defer m.Disconnect()
   236  
   237  	s, err := m.OpenService(*flServiceName)
   238  	if err != nil {
   239  		return err
   240  	}
   241  	defer s.Close()
   242  
   243  	eventlog.Remove(*flServiceName)
   244  	err = s.Delete()
   245  	if err != nil {
   246  		return err
   247  	}
   248  	return nil
   249  }
   250  
   251  func initService(daemonCli *DaemonCli) (bool, error) {
   252  	if *flUnregisterService {
   253  		if *flRegisterService {
   254  			return true, errors.New("--register-service and --unregister-service cannot be used together")
   255  		}
   256  		return true, unregisterService()
   257  	}
   258  
   259  	if *flRegisterService {
   260  		return true, registerService()
   261  	}
   262  
   263  	if !*flRunService {
   264  		return false, nil
   265  	}
   266  
   267  	interactive, err := svc.IsAnInteractiveSession()
   268  	if err != nil {
   269  		return false, err
   270  	}
   271  
   272  	h := &handler{
   273  		tosvc:     make(chan bool),
   274  		fromsvc:   make(chan error),
   275  		daemonCli: daemonCli,
   276  	}
   277  
   278  	var log *eventlog.Log
   279  	if !interactive {
   280  		log, err = eventlog.Open(*flServiceName)
   281  		if err != nil {
   282  			return false, err
   283  		}
   284  	}
   285  
   286  	logrus.AddHook(&etwHook{log})
   287  	logrus.SetOutput(ioutil.Discard)
   288  
   289  	service = h
   290  	go func() {
   291  		if interactive {
   292  			err = debug.Run(*flServiceName, h)
   293  		} else {
   294  			err = svc.Run(*flServiceName, h)
   295  		}
   296  
   297  		h.fromsvc <- err
   298  	}()
   299  
   300  	// Wait for the first signal from the service handler.
   301  	err = <-h.fromsvc
   302  	if err != nil {
   303  		return false, err
   304  	}
   305  	return false, nil
   306  }
   307  
   308  func (h *handler) started() error {
   309  	// This must be delayed until daemonCli initializes Config.Root
   310  	err := initPanicFile(filepath.Join(h.daemonCli.Config.Root, "panic.log"))
   311  	if err != nil {
   312  		return err
   313  	}
   314  
   315  	h.tosvc <- false
   316  	return nil
   317  }
   318  
   319  func (h *handler) stopped(err error) {
   320  	logrus.Debugf("Stopping service: %v", err)
   321  	h.tosvc <- err != nil
   322  	<-h.fromsvc
   323  }
   324  
   325  func (h *handler) Execute(_ []string, r <-chan svc.ChangeRequest, s chan<- svc.Status) (bool, uint32) {
   326  	s <- svc.Status{State: svc.StartPending, Accepts: 0}
   327  	// Unblock initService()
   328  	h.fromsvc <- nil
   329  
   330  	// Wait for initialization to complete.
   331  	failed := <-h.tosvc
   332  	if failed {
   333  		logrus.Debug("Aborting service start due to failure during initialization")
   334  		return true, 1
   335  	}
   336  
   337  	s <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown | svc.Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)}
   338  	logrus.Debug("Service running")
   339  Loop:
   340  	for {
   341  		select {
   342  		case failed = <-h.tosvc:
   343  			break Loop
   344  		case c := <-r:
   345  			switch c.Cmd {
   346  			case svc.Cmd(windows.SERVICE_CONTROL_PARAMCHANGE):
   347  				h.daemonCli.reloadConfig()
   348  			case svc.Interrogate:
   349  				s <- c.CurrentStatus
   350  			case svc.Stop, svc.Shutdown:
   351  				s <- svc.Status{State: svc.StopPending, Accepts: 0}
   352  				h.daemonCli.stop()
   353  			}
   354  		}
   355  	}
   356  
   357  	removePanicFile()
   358  	if failed {
   359  		return true, 1
   360  	}
   361  	return false, 0
   362  }
   363  
   364  func initPanicFile(path string) error {
   365  	var err error
   366  	panicFile, err = os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0)
   367  	if err != nil {
   368  		return err
   369  	}
   370  
   371  	st, err := panicFile.Stat()
   372  	if err != nil {
   373  		return err
   374  	}
   375  
   376  	// If there are contents in the file already, move the file out of the way
   377  	// and replace it.
   378  	if st.Size() > 0 {
   379  		panicFile.Close()
   380  		os.Rename(path, path+".old")
   381  		panicFile, err = os.Create(path)
   382  		if err != nil {
   383  			return err
   384  		}
   385  	}
   386  
   387  	// Update STD_ERROR_HANDLE to point to the panic file so that Go writes to
   388  	// it when it panics. Remember the old stderr to restore it before removing
   389  	// the panic file.
   390  	sh := syscall.STD_ERROR_HANDLE
   391  	h, err := syscall.GetStdHandle(sh)
   392  	if err != nil {
   393  		return err
   394  	}
   395  
   396  	oldStderr = h
   397  
   398  	r, _, err := setStdHandle.Call(uintptr(sh), uintptr(panicFile.Fd()))
   399  	if r == 0 && err != nil {
   400  		return err
   401  	}
   402  
   403  	return nil
   404  }
   405  
   406  func removePanicFile() {
   407  	if st, err := panicFile.Stat(); err == nil {
   408  		if st.Size() == 0 {
   409  			sh := syscall.STD_ERROR_HANDLE
   410  			setStdHandle.Call(uintptr(sh), uintptr(oldStderr))
   411  			panicFile.Close()
   412  			os.Remove(panicFile.Name())
   413  		}
   414  	}
   415  }