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