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