github.com/giovannyortegon/go@v0.0.0-20220115155912-8890063f5bdd/src/pkg/mod/golang.org/x/sys@v0.0.0-20210927094055-39ccf1dd6fa6/windows/svc/service.go (about)

     1  // Copyright 2012 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // +build windows
     6  
     7  // Package svc provides everything required to build Windows service.
     8  //
     9  package svc
    10  
    11  import (
    12  	"errors"
    13  	"runtime"
    14  	"syscall"
    15  	"unsafe"
    16  
    17  	"golang.org/x/sys/internal/unsafeheader"
    18  	"golang.org/x/sys/windows"
    19  )
    20  
    21  // State describes service execution state (Stopped, Running and so on).
    22  type State uint32
    23  
    24  const (
    25  	Stopped         = State(windows.SERVICE_STOPPED)
    26  	StartPending    = State(windows.SERVICE_START_PENDING)
    27  	StopPending     = State(windows.SERVICE_STOP_PENDING)
    28  	Running         = State(windows.SERVICE_RUNNING)
    29  	ContinuePending = State(windows.SERVICE_CONTINUE_PENDING)
    30  	PausePending    = State(windows.SERVICE_PAUSE_PENDING)
    31  	Paused          = State(windows.SERVICE_PAUSED)
    32  )
    33  
    34  // Cmd represents service state change request. It is sent to a service
    35  // by the service manager, and should be actioned upon by the service.
    36  type Cmd uint32
    37  
    38  const (
    39  	Stop                  = Cmd(windows.SERVICE_CONTROL_STOP)
    40  	Pause                 = Cmd(windows.SERVICE_CONTROL_PAUSE)
    41  	Continue              = Cmd(windows.SERVICE_CONTROL_CONTINUE)
    42  	Interrogate           = Cmd(windows.SERVICE_CONTROL_INTERROGATE)
    43  	Shutdown              = Cmd(windows.SERVICE_CONTROL_SHUTDOWN)
    44  	ParamChange           = Cmd(windows.SERVICE_CONTROL_PARAMCHANGE)
    45  	NetBindAdd            = Cmd(windows.SERVICE_CONTROL_NETBINDADD)
    46  	NetBindRemove         = Cmd(windows.SERVICE_CONTROL_NETBINDREMOVE)
    47  	NetBindEnable         = Cmd(windows.SERVICE_CONTROL_NETBINDENABLE)
    48  	NetBindDisable        = Cmd(windows.SERVICE_CONTROL_NETBINDDISABLE)
    49  	DeviceEvent           = Cmd(windows.SERVICE_CONTROL_DEVICEEVENT)
    50  	HardwareProfileChange = Cmd(windows.SERVICE_CONTROL_HARDWAREPROFILECHANGE)
    51  	PowerEvent            = Cmd(windows.SERVICE_CONTROL_POWEREVENT)
    52  	SessionChange         = Cmd(windows.SERVICE_CONTROL_SESSIONCHANGE)
    53  	PreShutdown           = Cmd(windows.SERVICE_CONTROL_PRESHUTDOWN)
    54  )
    55  
    56  // Accepted is used to describe commands accepted by the service.
    57  // Note that Interrogate is always accepted.
    58  type Accepted uint32
    59  
    60  const (
    61  	AcceptStop                  = Accepted(windows.SERVICE_ACCEPT_STOP)
    62  	AcceptShutdown              = Accepted(windows.SERVICE_ACCEPT_SHUTDOWN)
    63  	AcceptPauseAndContinue      = Accepted(windows.SERVICE_ACCEPT_PAUSE_CONTINUE)
    64  	AcceptParamChange           = Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)
    65  	AcceptNetBindChange         = Accepted(windows.SERVICE_ACCEPT_NETBINDCHANGE)
    66  	AcceptHardwareProfileChange = Accepted(windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE)
    67  	AcceptPowerEvent            = Accepted(windows.SERVICE_ACCEPT_POWEREVENT)
    68  	AcceptSessionChange         = Accepted(windows.SERVICE_ACCEPT_SESSIONCHANGE)
    69  	AcceptPreShutdown           = Accepted(windows.SERVICE_ACCEPT_PRESHUTDOWN)
    70  )
    71  
    72  // Status combines State and Accepted commands to fully describe running service.
    73  type Status struct {
    74  	State                   State
    75  	Accepts                 Accepted
    76  	CheckPoint              uint32 // used to report progress during a lengthy operation
    77  	WaitHint                uint32 // estimated time required for a pending operation, in milliseconds
    78  	ProcessId               uint32 // if the service is running, the process identifier of it, and otherwise zero
    79  	Win32ExitCode           uint32 // set if the service has exited with a win32 exit code
    80  	ServiceSpecificExitCode uint32 // set if the service has exited with a service-specific exit code
    81  }
    82  
    83  // ChangeRequest is sent to the service Handler to request service status change.
    84  type ChangeRequest struct {
    85  	Cmd           Cmd
    86  	EventType     uint32
    87  	EventData     uintptr
    88  	CurrentStatus Status
    89  	Context       uintptr
    90  }
    91  
    92  // Handler is the interface that must be implemented to build Windows service.
    93  type Handler interface {
    94  
    95  	// Execute will be called by the package code at the start of
    96  	// the service, and the service will exit once Execute completes.
    97  	// Inside Execute you must read service change requests from r and
    98  	// act accordingly. You must keep service control manager up to date
    99  	// about state of your service by writing into s as required.
   100  	// args contains service name followed by argument strings passed
   101  	// to the service.
   102  	// You can provide service exit code in exitCode return parameter,
   103  	// with 0 being "no error". You can also indicate if exit code,
   104  	// if any, is service specific or not by using svcSpecificEC
   105  	// parameter.
   106  	Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32)
   107  }
   108  
   109  var (
   110  	// These are used by asm code.
   111  	goWaitsH                       uintptr
   112  	cWaitsH                        uintptr
   113  	ssHandle                       uintptr
   114  	sName                          *uint16
   115  	sArgc                          uintptr
   116  	sArgv                          **uint16
   117  	ctlHandlerExProc               uintptr
   118  	cSetEvent                      uintptr
   119  	cWaitForSingleObject           uintptr
   120  	cRegisterServiceCtrlHandlerExW uintptr
   121  )
   122  
   123  func init() {
   124  	k := windows.NewLazySystemDLL("kernel32.dll")
   125  	cSetEvent = k.NewProc("SetEvent").Addr()
   126  	cWaitForSingleObject = k.NewProc("WaitForSingleObject").Addr()
   127  	a := windows.NewLazySystemDLL("advapi32.dll")
   128  	cRegisterServiceCtrlHandlerExW = a.NewProc("RegisterServiceCtrlHandlerExW").Addr()
   129  }
   130  
   131  type ctlEvent struct {
   132  	cmd       Cmd
   133  	eventType uint32
   134  	eventData uintptr
   135  	context   uintptr
   136  	errno     uint32
   137  }
   138  
   139  // service provides access to windows service api.
   140  type service struct {
   141  	name    string
   142  	h       windows.Handle
   143  	cWaits  *event
   144  	goWaits *event
   145  	c       chan ctlEvent
   146  	handler Handler
   147  }
   148  
   149  func newService(name string, handler Handler) (*service, error) {
   150  	var s service
   151  	var err error
   152  	s.name = name
   153  	s.c = make(chan ctlEvent)
   154  	s.handler = handler
   155  	s.cWaits, err = newEvent()
   156  	if err != nil {
   157  		return nil, err
   158  	}
   159  	s.goWaits, err = newEvent()
   160  	if err != nil {
   161  		s.cWaits.Close()
   162  		return nil, err
   163  	}
   164  	return &s, nil
   165  }
   166  
   167  func (s *service) close() error {
   168  	s.cWaits.Close()
   169  	s.goWaits.Close()
   170  	return nil
   171  }
   172  
   173  type exitCode struct {
   174  	isSvcSpecific bool
   175  	errno         uint32
   176  }
   177  
   178  func (s *service) updateStatus(status *Status, ec *exitCode) error {
   179  	if s.h == 0 {
   180  		return errors.New("updateStatus with no service status handle")
   181  	}
   182  	var t windows.SERVICE_STATUS
   183  	t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS
   184  	t.CurrentState = uint32(status.State)
   185  	if status.Accepts&AcceptStop != 0 {
   186  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP
   187  	}
   188  	if status.Accepts&AcceptShutdown != 0 {
   189  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN
   190  	}
   191  	if status.Accepts&AcceptPauseAndContinue != 0 {
   192  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE
   193  	}
   194  	if status.Accepts&AcceptParamChange != 0 {
   195  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PARAMCHANGE
   196  	}
   197  	if status.Accepts&AcceptNetBindChange != 0 {
   198  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_NETBINDCHANGE
   199  	}
   200  	if status.Accepts&AcceptHardwareProfileChange != 0 {
   201  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE
   202  	}
   203  	if status.Accepts&AcceptPowerEvent != 0 {
   204  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_POWEREVENT
   205  	}
   206  	if status.Accepts&AcceptSessionChange != 0 {
   207  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_SESSIONCHANGE
   208  	}
   209  	if status.Accepts&AcceptPreShutdown != 0 {
   210  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PRESHUTDOWN
   211  	}
   212  	if ec.errno == 0 {
   213  		t.Win32ExitCode = windows.NO_ERROR
   214  		t.ServiceSpecificExitCode = windows.NO_ERROR
   215  	} else if ec.isSvcSpecific {
   216  		t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR)
   217  		t.ServiceSpecificExitCode = ec.errno
   218  	} else {
   219  		t.Win32ExitCode = ec.errno
   220  		t.ServiceSpecificExitCode = windows.NO_ERROR
   221  	}
   222  	t.CheckPoint = status.CheckPoint
   223  	t.WaitHint = status.WaitHint
   224  	return windows.SetServiceStatus(s.h, &t)
   225  }
   226  
   227  const (
   228  	sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota
   229  	sysErrNewThreadInCallback
   230  )
   231  
   232  func (s *service) run() {
   233  	s.goWaits.Wait()
   234  	s.h = windows.Handle(ssHandle)
   235  
   236  	var argv []*uint16
   237  	hdr := (*unsafeheader.Slice)(unsafe.Pointer(&argv))
   238  	hdr.Data = unsafe.Pointer(sArgv)
   239  	hdr.Len = int(sArgc)
   240  	hdr.Cap = int(sArgc)
   241  
   242  	args := make([]string, len(argv))
   243  	for i, a := range argv {
   244  		args[i] = windows.UTF16PtrToString(a)
   245  	}
   246  
   247  	cmdsToHandler := make(chan ChangeRequest)
   248  	changesFromHandler := make(chan Status)
   249  	exitFromHandler := make(chan exitCode)
   250  
   251  	go func() {
   252  		ss, errno := s.handler.Execute(args, cmdsToHandler, changesFromHandler)
   253  		exitFromHandler <- exitCode{ss, errno}
   254  	}()
   255  
   256  	ec := exitCode{isSvcSpecific: true, errno: 0}
   257  	outcr := ChangeRequest{
   258  		CurrentStatus: Status{State: Stopped},
   259  	}
   260  	var outch chan ChangeRequest
   261  	inch := s.c
   262  loop:
   263  	for {
   264  		select {
   265  		case r := <-inch:
   266  			if r.errno != 0 {
   267  				ec.errno = r.errno
   268  				break loop
   269  			}
   270  			inch = nil
   271  			outch = cmdsToHandler
   272  			outcr.Cmd = r.cmd
   273  			outcr.EventType = r.eventType
   274  			outcr.EventData = r.eventData
   275  			outcr.Context = r.context
   276  		case outch <- outcr:
   277  			inch = s.c
   278  			outch = nil
   279  		case c := <-changesFromHandler:
   280  			err := s.updateStatus(&c, &ec)
   281  			if err != nil {
   282  				// best suitable error number
   283  				ec.errno = sysErrSetServiceStatusFailed
   284  				if err2, ok := err.(syscall.Errno); ok {
   285  					ec.errno = uint32(err2)
   286  				}
   287  				break loop
   288  			}
   289  			outcr.CurrentStatus = c
   290  		case ec = <-exitFromHandler:
   291  			break loop
   292  		}
   293  	}
   294  
   295  	s.updateStatus(&Status{State: Stopped}, &ec)
   296  	s.cWaits.Set()
   297  }
   298  
   299  func newCallback(fn interface{}) (cb uintptr, err error) {
   300  	defer func() {
   301  		r := recover()
   302  		if r == nil {
   303  			return
   304  		}
   305  		cb = 0
   306  		switch v := r.(type) {
   307  		case string:
   308  			err = errors.New(v)
   309  		case error:
   310  			err = v
   311  		default:
   312  			err = errors.New("unexpected panic in syscall.NewCallback")
   313  		}
   314  	}()
   315  	return syscall.NewCallback(fn), nil
   316  }
   317  
   318  // BUG(brainman): There is no mechanism to run multiple services
   319  // inside one single executable. Perhaps, it can be overcome by
   320  // using RegisterServiceCtrlHandlerEx Windows api.
   321  
   322  // Run executes service name by calling appropriate handler function.
   323  func Run(name string, handler Handler) error {
   324  	runtime.LockOSThread()
   325  
   326  	tid := windows.GetCurrentThreadId()
   327  
   328  	s, err := newService(name, handler)
   329  	if err != nil {
   330  		return err
   331  	}
   332  
   333  	ctlHandler := func(ctl, evtype, evdata, context uintptr) uintptr {
   334  		e := ctlEvent{cmd: Cmd(ctl), eventType: uint32(evtype), eventData: evdata, context: context}
   335  		// We assume that this callback function is running on
   336  		// the same thread as Run. Nowhere in MS documentation
   337  		// I could find statement to guarantee that. So putting
   338  		// check here to verify, otherwise things will go bad
   339  		// quickly, if ignored.
   340  		i := windows.GetCurrentThreadId()
   341  		if i != tid {
   342  			e.errno = sysErrNewThreadInCallback
   343  		}
   344  		s.c <- e
   345  		// Always return NO_ERROR (0) for now.
   346  		return windows.NO_ERROR
   347  	}
   348  
   349  	var svcmain uintptr
   350  	getServiceMain(&svcmain)
   351  	t := []windows.SERVICE_TABLE_ENTRY{
   352  		{ServiceName: syscall.StringToUTF16Ptr(s.name), ServiceProc: svcmain},
   353  		{ServiceName: nil, ServiceProc: 0},
   354  	}
   355  
   356  	goWaitsH = uintptr(s.goWaits.h)
   357  	cWaitsH = uintptr(s.cWaits.h)
   358  	sName = t[0].ServiceName
   359  	ctlHandlerExProc, err = newCallback(ctlHandler)
   360  	if err != nil {
   361  		return err
   362  	}
   363  
   364  	go s.run()
   365  
   366  	err = windows.StartServiceCtrlDispatcher(&t[0])
   367  	if err != nil {
   368  		return err
   369  	}
   370  	return nil
   371  }
   372  
   373  // StatusHandle returns service status handle. It is safe to call this function
   374  // from inside the Handler.Execute because then it is guaranteed to be set.
   375  // This code will have to change once multiple services are possible per process.
   376  func StatusHandle() windows.Handle {
   377  	return windows.Handle(ssHandle)
   378  }