golang.org/x/sys@v0.9.0/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  //go:build windows
     6  // +build windows
     7  
     8  // Package svc provides everything required to build Windows service.
     9  package svc
    10  
    11  import (
    12  	"errors"
    13  	"sync"
    14  	"unsafe"
    15  
    16  	"golang.org/x/sys/internal/unsafeheader"
    17  	"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  	PreShutdown           = Cmd(windows.SERVICE_CONTROL_PRESHUTDOWN)
    53  )
    54  
    55  // Accepted is used to describe commands accepted by the service.
    56  // Note that Interrogate is always accepted.
    57  type Accepted uint32
    58  
    59  const (
    60  	AcceptStop                  = Accepted(windows.SERVICE_ACCEPT_STOP)
    61  	AcceptShutdown              = Accepted(windows.SERVICE_ACCEPT_SHUTDOWN)
    62  	AcceptPauseAndContinue      = Accepted(windows.SERVICE_ACCEPT_PAUSE_CONTINUE)
    63  	AcceptParamChange           = Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)
    64  	AcceptNetBindChange         = Accepted(windows.SERVICE_ACCEPT_NETBINDCHANGE)
    65  	AcceptHardwareProfileChange = Accepted(windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE)
    66  	AcceptPowerEvent            = Accepted(windows.SERVICE_ACCEPT_POWEREVENT)
    67  	AcceptSessionChange         = Accepted(windows.SERVICE_ACCEPT_SESSIONCHANGE)
    68  	AcceptPreShutdown           = Accepted(windows.SERVICE_ACCEPT_PRESHUTDOWN)
    69  )
    70  
    71  // ActivityStatus allows for services to be selected based on active and inactive categories of service state.
    72  type ActivityStatus uint32
    73  
    74  const (
    75  	Active      = ActivityStatus(windows.SERVICE_ACTIVE)
    76  	Inactive    = ActivityStatus(windows.SERVICE_INACTIVE)
    77  	AnyActivity = ActivityStatus(windows.SERVICE_STATE_ALL)
    78  )
    79  
    80  // Status combines State and Accepted commands to fully describe running service.
    81  type Status struct {
    82  	State                   State
    83  	Accepts                 Accepted
    84  	CheckPoint              uint32 // used to report progress during a lengthy operation
    85  	WaitHint                uint32 // estimated time required for a pending operation, in milliseconds
    86  	ProcessId               uint32 // if the service is running, the process identifier of it, and otherwise zero
    87  	Win32ExitCode           uint32 // set if the service has exited with a win32 exit code
    88  	ServiceSpecificExitCode uint32 // set if the service has exited with a service-specific exit code
    89  }
    90  
    91  // StartReason is the reason that the service was started.
    92  type StartReason uint32
    93  
    94  const (
    95  	StartReasonDemand           = StartReason(windows.SERVICE_START_REASON_DEMAND)
    96  	StartReasonAuto             = StartReason(windows.SERVICE_START_REASON_AUTO)
    97  	StartReasonTrigger          = StartReason(windows.SERVICE_START_REASON_TRIGGER)
    98  	StartReasonRestartOnFailure = StartReason(windows.SERVICE_START_REASON_RESTART_ON_FAILURE)
    99  	StartReasonDelayedAuto      = StartReason(windows.SERVICE_START_REASON_DELAYEDAUTO)
   100  )
   101  
   102  // ChangeRequest is sent to the service Handler to request service status change.
   103  type ChangeRequest struct {
   104  	Cmd           Cmd
   105  	EventType     uint32
   106  	EventData     uintptr
   107  	CurrentStatus Status
   108  	Context       uintptr
   109  }
   110  
   111  // Handler is the interface that must be implemented to build Windows service.
   112  type Handler interface {
   113  	// Execute will be called by the package code at the start of
   114  	// the service, and the service will exit once Execute completes.
   115  	// Inside Execute you must read service change requests from r and
   116  	// act accordingly. You must keep service control manager up to date
   117  	// about state of your service by writing into s as required.
   118  	// args contains service name followed by argument strings passed
   119  	// to the service.
   120  	// You can provide service exit code in exitCode return parameter,
   121  	// with 0 being "no error". You can also indicate if exit code,
   122  	// if any, is service specific or not by using svcSpecificEC
   123  	// parameter.
   124  	Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32)
   125  }
   126  
   127  type ctlEvent struct {
   128  	cmd       Cmd
   129  	eventType uint32
   130  	eventData uintptr
   131  	context   uintptr
   132  	errno     uint32
   133  }
   134  
   135  // service provides access to windows service api.
   136  type service struct {
   137  	name    string
   138  	h       windows.Handle
   139  	c       chan ctlEvent
   140  	handler Handler
   141  }
   142  
   143  type exitCode struct {
   144  	isSvcSpecific bool
   145  	errno         uint32
   146  }
   147  
   148  func (s *service) updateStatus(status *Status, ec *exitCode) error {
   149  	if s.h == 0 {
   150  		return errors.New("updateStatus with no service status handle")
   151  	}
   152  	var t windows.SERVICE_STATUS
   153  	t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS
   154  	t.CurrentState = uint32(status.State)
   155  	if status.Accepts&AcceptStop != 0 {
   156  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP
   157  	}
   158  	if status.Accepts&AcceptShutdown != 0 {
   159  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN
   160  	}
   161  	if status.Accepts&AcceptPauseAndContinue != 0 {
   162  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE
   163  	}
   164  	if status.Accepts&AcceptParamChange != 0 {
   165  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PARAMCHANGE
   166  	}
   167  	if status.Accepts&AcceptNetBindChange != 0 {
   168  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_NETBINDCHANGE
   169  	}
   170  	if status.Accepts&AcceptHardwareProfileChange != 0 {
   171  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE
   172  	}
   173  	if status.Accepts&AcceptPowerEvent != 0 {
   174  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_POWEREVENT
   175  	}
   176  	if status.Accepts&AcceptSessionChange != 0 {
   177  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_SESSIONCHANGE
   178  	}
   179  	if status.Accepts&AcceptPreShutdown != 0 {
   180  		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PRESHUTDOWN
   181  	}
   182  	if ec.errno == 0 {
   183  		t.Win32ExitCode = windows.NO_ERROR
   184  		t.ServiceSpecificExitCode = windows.NO_ERROR
   185  	} else if ec.isSvcSpecific {
   186  		t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR)
   187  		t.ServiceSpecificExitCode = ec.errno
   188  	} else {
   189  		t.Win32ExitCode = ec.errno
   190  		t.ServiceSpecificExitCode = windows.NO_ERROR
   191  	}
   192  	t.CheckPoint = status.CheckPoint
   193  	t.WaitHint = status.WaitHint
   194  	return windows.SetServiceStatus(s.h, &t)
   195  }
   196  
   197  var (
   198  	initCallbacks       sync.Once
   199  	ctlHandlerCallback  uintptr
   200  	serviceMainCallback uintptr
   201  )
   202  
   203  func ctlHandler(ctl, evtype, evdata, context uintptr) uintptr {
   204  	s := (*service)(unsafe.Pointer(context))
   205  	e := ctlEvent{cmd: Cmd(ctl), eventType: uint32(evtype), eventData: evdata, context: 123456} // Set context to 123456 to test issue #25660.
   206  	s.c <- e
   207  	return 0
   208  }
   209  
   210  var theService service // This is, unfortunately, a global, which means only one service per process.
   211  
   212  // serviceMain is the entry point called by the service manager, registered earlier by
   213  // the call to StartServiceCtrlDispatcher.
   214  func serviceMain(argc uint32, argv **uint16) uintptr {
   215  	handle, err := windows.RegisterServiceCtrlHandlerEx(windows.StringToUTF16Ptr(theService.name), ctlHandlerCallback, uintptr(unsafe.Pointer(&theService)))
   216  	if sysErr, ok := err.(windows.Errno); ok {
   217  		return uintptr(sysErr)
   218  	} else if err != nil {
   219  		return uintptr(windows.ERROR_UNKNOWN_EXCEPTION)
   220  	}
   221  	theService.h = handle
   222  	defer func() {
   223  		theService.h = 0
   224  	}()
   225  	var args16 []*uint16
   226  	hdr := (*unsafeheader.Slice)(unsafe.Pointer(&args16))
   227  	hdr.Data = unsafe.Pointer(argv)
   228  	hdr.Len = int(argc)
   229  	hdr.Cap = int(argc)
   230  
   231  	args := make([]string, len(args16))
   232  	for i, a := range args16 {
   233  		args[i] = windows.UTF16PtrToString(a)
   234  	}
   235  
   236  	cmdsToHandler := make(chan ChangeRequest)
   237  	changesFromHandler := make(chan Status)
   238  	exitFromHandler := make(chan exitCode)
   239  
   240  	go func() {
   241  		ss, errno := theService.handler.Execute(args, cmdsToHandler, changesFromHandler)
   242  		exitFromHandler <- exitCode{ss, errno}
   243  	}()
   244  
   245  	ec := exitCode{isSvcSpecific: true, errno: 0}
   246  	outcr := ChangeRequest{
   247  		CurrentStatus: Status{State: Stopped},
   248  	}
   249  	var outch chan ChangeRequest
   250  	inch := theService.c
   251  loop:
   252  	for {
   253  		select {
   254  		case r := <-inch:
   255  			if r.errno != 0 {
   256  				ec.errno = r.errno
   257  				break loop
   258  			}
   259  			inch = nil
   260  			outch = cmdsToHandler
   261  			outcr.Cmd = r.cmd
   262  			outcr.EventType = r.eventType
   263  			outcr.EventData = r.eventData
   264  			outcr.Context = r.context
   265  		case outch <- outcr:
   266  			inch = theService.c
   267  			outch = nil
   268  		case c := <-changesFromHandler:
   269  			err := theService.updateStatus(&c, &ec)
   270  			if err != nil {
   271  				ec.errno = uint32(windows.ERROR_EXCEPTION_IN_SERVICE)
   272  				if err2, ok := err.(windows.Errno); ok {
   273  					ec.errno = uint32(err2)
   274  				}
   275  				break loop
   276  			}
   277  			outcr.CurrentStatus = c
   278  		case ec = <-exitFromHandler:
   279  			break loop
   280  		}
   281  	}
   282  
   283  	theService.updateStatus(&Status{State: Stopped}, &ec)
   284  
   285  	return windows.NO_ERROR
   286  }
   287  
   288  // Run executes service name by calling appropriate handler function.
   289  func Run(name string, handler Handler) error {
   290  	initCallbacks.Do(func() {
   291  		ctlHandlerCallback = windows.NewCallback(ctlHandler)
   292  		serviceMainCallback = windows.NewCallback(serviceMain)
   293  	})
   294  	theService.name = name
   295  	theService.handler = handler
   296  	theService.c = make(chan ctlEvent)
   297  	t := []windows.SERVICE_TABLE_ENTRY{
   298  		{ServiceName: windows.StringToUTF16Ptr(theService.name), ServiceProc: serviceMainCallback},
   299  		{ServiceName: nil, ServiceProc: 0},
   300  	}
   301  	return windows.StartServiceCtrlDispatcher(&t[0])
   302  }
   303  
   304  // StatusHandle returns service status handle. It is safe to call this function
   305  // from inside the Handler.Execute because then it is guaranteed to be set.
   306  func StatusHandle() windows.Handle {
   307  	return theService.h
   308  }
   309  
   310  // DynamicStartReason returns the reason why the service was started. It is safe
   311  // to call this function from inside the Handler.Execute because then it is
   312  // guaranteed to be set.
   313  func DynamicStartReason() (StartReason, error) {
   314  	var allocReason *uint32
   315  	err := windows.QueryServiceDynamicInformation(theService.h, windows.SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON, unsafe.Pointer(&allocReason))
   316  	if err != nil {
   317  		return 0, err
   318  	}
   319  	reason := StartReason(*allocReason)
   320  	windows.LocalFree(windows.Handle(unsafe.Pointer(allocReason)))
   321  	return reason, nil
   322  }