...

Source file src/github.com/Microsoft/hcsshim/cmd/ncproxy/service.go

Documentation: github.com/Microsoft/hcsshim/cmd/ncproxy

     1  //go:build windows
     2  
     3  package main
     4  
     5  import (
     6  	"log"
     7  	"os"
     8  	"time"
     9  	"unsafe"
    10  
    11  	"golang.org/x/sys/windows"
    12  	"golang.org/x/sys/windows/svc"
    13  	"golang.org/x/sys/windows/svc/debug"
    14  	"golang.org/x/sys/windows/svc/mgr"
    15  )
    16  
    17  const serviceName = "ncproxy"
    18  
    19  var (
    20  	panicFile *os.File
    21  	oldStderr windows.Handle
    22  )
    23  
    24  type handler struct {
    25  	fromsvc chan error
    26  	done    chan struct{}
    27  }
    28  
    29  type serviceFailureActions struct {
    30  	ResetPeriod  uint32
    31  	RebootMsg    *uint16
    32  	Command      *uint16
    33  	ActionsCount uint32
    34  	Actions      uintptr
    35  }
    36  
    37  type scAction struct {
    38  	Type  uint32
    39  	Delay uint32
    40  }
    41  
    42  // See http://stackoverflow.com/questions/35151052/how-do-i-configure-failure-actions-of-a-windows-service-written-in-go
    43  const (
    44  	scActionNone    = 0
    45  	scActionRestart = 1
    46  
    47  	serviceConfigFailureActions = 2
    48  )
    49  
    50  func initPanicFile(path string) error {
    51  	panicFile, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0644)
    52  	if err != nil {
    53  		return err
    54  	}
    55  
    56  	st, err := panicFile.Stat()
    57  	if err != nil {
    58  		return err
    59  	}
    60  
    61  	// If there are contents in the file already, move the file out of the way
    62  	// and replace it.
    63  	if st.Size() > 0 {
    64  		panicFile.Close()
    65  		_ = os.Rename(path, path+".old")
    66  		panicFile, err = os.Create(path)
    67  		if err != nil {
    68  			return err
    69  		}
    70  	}
    71  
    72  	// Update STD_ERROR_HANDLE to point to the panic file so that Go writes to
    73  	// it when it panics. Remember the old stderr to restore it before removing
    74  	// the panic file.
    75  	sh := uint32(windows.STD_ERROR_HANDLE)
    76  	h, err := windows.GetStdHandle(sh)
    77  	if err != nil {
    78  		return err
    79  	}
    80  	oldStderr = h
    81  
    82  	if err := windows.SetStdHandle(sh, windows.Handle(panicFile.Fd())); err != nil {
    83  		return err
    84  	}
    85  
    86  	// Reset os.Stderr to the panic file (so fmt.Fprintf(os.Stderr,...) actually gets redirected)
    87  	os.Stderr = os.NewFile(panicFile.Fd(), "/dev/stderr-ncproxy")
    88  
    89  	// Force threads that panic to write to stderr (the panicFile handle now).
    90  	log.SetOutput(os.Stderr)
    91  	return nil
    92  }
    93  
    94  func removePanicFile() {
    95  	if st, err := panicFile.Stat(); err == nil {
    96  		// If there's anything in the file we wrote (e.g. panic logs), don't delete it.
    97  		if st.Size() == 0 {
    98  			sh := uint32(windows.STD_ERROR_HANDLE)
    99  			_ = windows.SetStdHandle(sh, oldStderr)
   100  			_ = panicFile.Close()
   101  			_ = os.Remove(panicFile.Name())
   102  		}
   103  	}
   104  }
   105  
   106  func registerService() error {
   107  	p, err := os.Executable()
   108  	if err != nil {
   109  		return err
   110  	}
   111  	m, err := mgr.Connect()
   112  	if err != nil {
   113  		return err
   114  	}
   115  	defer func() {
   116  		_ = m.Disconnect()
   117  	}()
   118  
   119  	c := mgr.Config{
   120  		ServiceType:  windows.SERVICE_WIN32_OWN_PROCESS,
   121  		StartType:    mgr.StartAutomatic,
   122  		ErrorControl: mgr.ErrorNormal,
   123  		DisplayName:  "Ncproxy",
   124  		Description:  "Network configuration proxy",
   125  	}
   126  
   127  	// Configure the service to launch with the arguments that were just passed.
   128  	args := []string{"--run-service"}
   129  	for _, a := range os.Args[1:] {
   130  		if a != "--register-service" {
   131  			args = append(args, a)
   132  		}
   133  	}
   134  
   135  	s, err := m.CreateService(serviceName, p, c, args...)
   136  	if err != nil {
   137  		return err
   138  	}
   139  	defer s.Close()
   140  
   141  	t := []scAction{
   142  		{Type: scActionRestart, Delay: uint32(15 * time.Second / time.Millisecond)},
   143  		{Type: scActionRestart, Delay: uint32(15 * time.Second / time.Millisecond)},
   144  		{Type: scActionNone},
   145  	}
   146  	lpInfo := serviceFailureActions{ResetPeriod: uint32(24 * time.Hour / time.Second), ActionsCount: uint32(3), Actions: uintptr(unsafe.Pointer(&t[0]))}
   147  	return windows.ChangeServiceConfig2(s.Handle, serviceConfigFailureActions, (*byte)(unsafe.Pointer(&lpInfo)))
   148  }
   149  
   150  func unregisterService() error {
   151  	m, err := mgr.Connect()
   152  	if err != nil {
   153  		return err
   154  	}
   155  	defer func() {
   156  		_ = m.Disconnect()
   157  	}()
   158  
   159  	s, err := m.OpenService(serviceName)
   160  	if err != nil {
   161  		return err
   162  	}
   163  	defer s.Close()
   164  
   165  	return s.Delete()
   166  }
   167  
   168  // launchService is the entry point for running ncproxy under SCM.
   169  func launchService(done chan struct{}) error {
   170  	h := &handler{
   171  		fromsvc: make(chan error),
   172  		done:    done,
   173  	}
   174  
   175  	interactive, err := svc.IsAnInteractiveSession() //nolint:staticcheck
   176  	if err != nil {
   177  		return err
   178  	}
   179  
   180  	go func() {
   181  		if interactive {
   182  			err = debug.Run(serviceName, h)
   183  		} else {
   184  			err = svc.Run(serviceName, h)
   185  		}
   186  		h.fromsvc <- err
   187  	}()
   188  
   189  	// Wait for the first signal from the service handler.
   190  	return <-h.fromsvc
   191  }
   192  
   193  func (h *handler) Execute(_ []string, r <-chan svc.ChangeRequest, s chan<- svc.Status) (bool, uint32) {
   194  	s <- svc.Status{State: svc.StartPending, Accepts: 0}
   195  	// Unblock launchService()
   196  	h.fromsvc <- nil
   197  
   198  	s <- svc.Status{State: svc.Running, Accepts: svc.AcceptStop | svc.AcceptShutdown | svc.Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)}
   199  
   200  Loop:
   201  	for c := range r {
   202  		switch c.Cmd {
   203  		case svc.Interrogate:
   204  			s <- c.CurrentStatus
   205  		case svc.Stop, svc.Shutdown:
   206  			s <- svc.Status{State: svc.StopPending, Accepts: 0}
   207  			break Loop
   208  		}
   209  	}
   210  
   211  	removePanicFile()
   212  	close(h.done)
   213  	return false, 0
   214  }
   215  

View as plain text