github.com/go-xe2/third@v1.0.3/golang.org/x/sys/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/go-xe2/third/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  }
    76  
    77  // ChangeRequest is sent to the service Handler to request service status change.
    78  type ChangeRequest struct {
    79  	Cmd           Cmd
    80  	EventType     uint32
    81  	EventData     uintptr
    82  	CurrentStatus Status
    83  }
    84  
    85  // Handler is the interface that must be implemented to build Windows service.
    86  type Handler interface {
    87  
    88  	// Execute will be called by the package code at the start of
    89  	// the service, and the service will exit once Execute completes.
    90  	// Inside Execute you must read service change requests from r and
    91  	// act accordingly. You must keep service control manager up to date
    92  	// about state of your service by writing into s as required.
    93  	// args contains service name followed by argument strings passed
    94  	// to the service.
    95  	// You can provide service exit code in exitCode return parameter,
    96  	// with 0 being "no error". You can also indicate if exit code,
    97  	// if any, is service specific or not by using svcSpecificEC
    98  	// parameter.
    99  	Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32)
   100  }
   101  
   102  var (
   103  	// These are used by asm code.
   104  	goWaitsH                       uintptr
   105  	cWaitsH                        uintptr
   106  	ssHandle                       uintptr
   107  	sName                          *uint16
   108  	sArgc                          uintptr
   109  	sArgv                          **uint16
   110  	ctlHandlerExProc               uintptr
   111  	cSetEvent                      uintptr
   112  	cWaitForSingleObject           uintptr
   113  	cRegisterServiceCtrlHandlerExW uintptr
   114  )
   115  
   116  func init() {
   117  	k := syscall.MustLoadDLL("kernel32.dll")
   118  	cSetEvent = k.MustFindProc("SetEvent").Addr()
   119  	cWaitForSingleObject = k.MustFindProc("WaitForSingleObject").Addr()
   120  	a := syscall.MustLoadDLL("advapi32.dll")
   121  	cRegisterServiceCtrlHandlerExW = a.MustFindProc("RegisterServiceCtrlHandlerExW").Addr()
   122  }
   123  
   124  // The HandlerEx prototype also has a context pointer but since we don't use
   125  // it at start-up time we don't have to pass it over either.
   126  type ctlEvent struct {
   127  	cmd       Cmd
   128  	eventType uint32
   129  	eventData uintptr
   130  	errno     uint32
   131  }
   132  
   133  // service provides access to windows service api.
   134  type service struct {
   135  	name    string
   136  	h       windows.Handle
   137  	cWaits  *event
   138  	goWaits *event
   139  	c       chan ctlEvent
   140  	handler Handler
   141  }
   142  
   143  func newService(name string, handler Handler) (*service, error) {
   144  	var s service
   145  	var err error
   146  	s.name = name
   147  	s.c = make(chan ctlEvent)
   148  	s.handler = handler
   149  	s.cWaits, err = newEvent()
   150  	if err != nil {
   151  		return nil, err
   152  	}
   153  	s.goWaits, err = newEvent()
   154  	if err != nil {
   155  		s.cWaits.Close()
   156  		return nil, err
   157  	}
   158  	return &s, nil
   159  }
   160  
   161  func (s *service) close() error {
   162  	s.cWaits.Close()
   163  	s.goWaits.Close()
   164  	return nil
   165  }
   166  
   167  type exitCode struct {
   168  	isSvcSpecific bool
   169  	errno         uint32
   170  }
   171  
   172  func (s *service) updateStatus(status *Status, ec *exitCode) error {
   173  	if s.h == 0 {
   174  		return errors.New("updateStatus with no service status handle")
   175  	}
   176  	var t windows.SERVICE_STATUS
   177  	t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS
   178  	t.CurrentState = uint32(status.State)
   179  	if status.Accepts&AcceptStop != 0 {
   180  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP
   181  	}
   182  	if status.Accepts&AcceptShutdown != 0 {
   183  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN
   184  	}
   185  	if status.Accepts&AcceptPauseAndContinue != 0 {
   186  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE
   187  	}
   188  	if status.Accepts&AcceptParamChange != 0 {
   189  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PARAMCHANGE
   190  	}
   191  	if status.Accepts&AcceptNetBindChange != 0 {
   192  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_NETBINDCHANGE
   193  	}
   194  	if status.Accepts&AcceptHardwareProfileChange != 0 {
   195  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE
   196  	}
   197  	if status.Accepts&AcceptPowerEvent != 0 {
   198  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_POWEREVENT
   199  	}
   200  	if status.Accepts&AcceptSessionChange != 0 {
   201  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_SESSIONCHANGE
   202  	}
   203  	if ec.errno == 0 {
   204  		t.Win32ExitCode = windows.NO_ERROR
   205  		t.ServiceSpecificExitCode = windows.NO_ERROR
   206  	} else if ec.isSvcSpecific {
   207  		t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR)
   208  		t.ServiceSpecificExitCode = ec.errno
   209  	} else {
   210  		t.Win32ExitCode = ec.errno
   211  		t.ServiceSpecificExitCode = windows.NO_ERROR
   212  	}
   213  	t.CheckPoint = status.CheckPoint
   214  	t.WaitHint = status.WaitHint
   215  	return windows.SetServiceStatus(s.h, &t)
   216  }
   217  
   218  const (
   219  	sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota
   220  	sysErrNewThreadInCallback
   221  )
   222  
   223  func (s *service) run() {
   224  	s.goWaits.Wait()
   225  	s.h = windows.Handle(ssHandle)
   226  	argv := (*[100]*int16)(unsafe.Pointer(sArgv))[:sArgc]
   227  	args := make([]string, len(argv))
   228  	for i, a := range argv {
   229  		args[i] = syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(a))[:])
   230  	}
   231  
   232  	cmdsToHandler := make(chan ChangeRequest)
   233  	changesFromHandler := make(chan Status)
   234  	exitFromHandler := make(chan exitCode)
   235  
   236  	go func() {
   237  		ss, errno := s.handler.Execute(args, cmdsToHandler, changesFromHandler)
   238  		exitFromHandler <- exitCode{ss, errno}
   239  	}()
   240  
   241  	status := Status{State: Stopped}
   242  	ec := exitCode{isSvcSpecific: true, errno: 0}
   243  	var outch chan ChangeRequest
   244  	inch := s.c
   245  	var cmd Cmd
   246  	var evtype uint32
   247  	var evdata uintptr
   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  			cmd = r.cmd
   259  			evtype = r.eventType
   260  			evdata = r.eventData
   261  		case outch <- ChangeRequest{cmd, evtype, evdata, status}:
   262  			inch = s.c
   263  			outch = nil
   264  		case c := <-changesFromHandler:
   265  			err := s.updateStatus(&c, &ec)
   266  			if err != nil {
   267  				// best suitable error number
   268  				ec.errno = sysErrSetServiceStatusFailed
   269  				if err2, ok := err.(syscall.Errno); ok {
   270  					ec.errno = uint32(err2)
   271  				}
   272  				break loop
   273  			}
   274  			status = c
   275  		case ec = <-exitFromHandler:
   276  			break loop
   277  		}
   278  	}
   279  
   280  	s.updateStatus(&Status{State: Stopped}, &ec)
   281  	s.cWaits.Set()
   282  }
   283  
   284  func newCallback(fn interface{}) (cb uintptr, err error) {
   285  	defer func() {
   286  		r := recover()
   287  		if r == nil {
   288  			return
   289  		}
   290  		cb = 0
   291  		switch v := r.(type) {
   292  		case string:
   293  			err = errors.New(v)
   294  		case error:
   295  			err = v
   296  		default:
   297  			err = errors.New("unexpected panic in syscall.NewCallback")
   298  		}
   299  	}()
   300  	return syscall.NewCallback(fn), nil
   301  }
   302  
   303  // BUG(brainman): There is no mechanism to run multiple services
   304  // inside one single executable. Perhaps, it can be overcome by
   305  // using RegisterServiceCtrlHandlerEx Windows api.
   306  
   307  // Run executes service name by calling appropriate handler function.
   308  func Run(name string, handler Handler) error {
   309  	runtime.LockOSThread()
   310  
   311  	tid := windows.GetCurrentThreadId()
   312  
   313  	s, err := newService(name, handler)
   314  	if err != nil {
   315  		return err
   316  	}
   317  
   318  	ctlHandler := func(ctl uint32, evtype uint32, evdata uintptr, context uintptr) uintptr {
   319  		e := ctlEvent{cmd: Cmd(ctl), eventType: evtype, eventData: evdata}
   320  		// We assume that this callback function is running on
   321  		// the same thread as Run. Nowhere in MS documentation
   322  		// I could find statement to guarantee that. So putting
   323  		// check here to verify, otherwise things will go bad
   324  		// quickly, if ignored.
   325  		i := windows.GetCurrentThreadId()
   326  		if i != tid {
   327  			e.errno = sysErrNewThreadInCallback
   328  		}
   329  		s.c <- e
   330  		// Always return NO_ERROR (0) for now.
   331  		return 0
   332  	}
   333  
   334  	var svcmain uintptr
   335  	getServiceMain(&svcmain)
   336  	t := []windows.SERVICE_TABLE_ENTRY{
   337  		{ServiceName: syscall.StringToUTF16Ptr(s.name), ServiceProc: svcmain},
   338  		{ServiceName: nil, ServiceProc: 0},
   339  	}
   340  
   341  	goWaitsH = uintptr(s.goWaits.h)
   342  	cWaitsH = uintptr(s.cWaits.h)
   343  	sName = t[0].ServiceName
   344  	ctlHandlerExProc, err = newCallback(ctlHandler)
   345  	if err != nil {
   346  		return err
   347  	}
   348  
   349  	go s.run()
   350  
   351  	err = windows.StartServiceCtrlDispatcher(&t[0])
   352  	if err != nil {
   353  		return err
   354  	}
   355  	return nil
   356  }
   357  
   358  // StatusHandle returns service status handle. It is safe to call this function
   359  // from inside the Handler.Execute because then it is guaranteed to be set.
   360  // This code will have to change once multiple services are possible per process.
   361  func StatusHandle() windows.Handle {
   362  	return windows.Handle(ssHandle)
   363  }