github.com/iDigitalFlame/xmt@v0.5.4/device/winapi/svc/service.go (about)

     1  //go:build windows
     2  // +build windows
     3  
     4  // Copyright (C) 2020 - 2023 iDigitalFlame
     5  //
     6  // This program is free software: you can redistribute it and/or modify
     7  // it under the terms of the GNU General Public License as published by
     8  // the Free Software Foundation, either version 3 of the License, or
     9  // any later version.
    10  //
    11  // This program is distributed in the hope that it will be useful,
    12  // but WITHOUT ANY WARRANTY; without even the implied warranty of
    13  // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    14  // GNU General Public License for more details.
    15  //
    16  // You should have received a copy of the GNU General Public License
    17  // along with this program.  If not, see <https://www.gnu.org/licenses/>.
    18  //
    19  
    20  // Package svc is a Windows specific Service interface. This can be used to create
    21  // Golang services that talk to SCM.
    22  //
    23  // It is recommended to use the 'device.Daemon*' functions instead of this package
    24  // as they are easier to use.
    25  package svc
    26  
    27  import (
    28  	"context"
    29  	"os"
    30  	"runtime"
    31  	"sync"
    32  	"syscall"
    33  	"unsafe"
    34  
    35  	"github.com/iDigitalFlame/xmt/device/winapi"
    36  	"github.com/iDigitalFlame/xmt/util/xerr"
    37  )
    38  
    39  // Standard Windows Service State values
    40  //
    41  // DO NOT REORDER
    42  const (
    43  	Stopped State = 1 + iota
    44  	StartPending
    45  	StopPending
    46  	Running
    47  	ContinuePending
    48  	PausePending
    49  	Paused
    50  )
    51  
    52  // Standard Windows Service Reason values
    53  //
    54  // DO NOT REORDER
    55  const (
    56  	ReasonDemand Reason = 1 << iota
    57  	ReasonAuto
    58  	ReasonTrigger
    59  	ReasonRestartOnFailure
    60  	ReasonDelayedAuto
    61  )
    62  
    63  // Standard Windows Service Command values
    64  //
    65  // DO NOT REORDER
    66  const (
    67  	Stop Command = 1 + iota
    68  	Pause
    69  	Continue
    70  	Interrogate
    71  	Shutdown
    72  	ParamChange
    73  	NetBindAdd
    74  	NetBindRemove
    75  	NetBindEnable
    76  	NetBindDisable
    77  	DeviceEvent
    78  	HardwareProfileChange
    79  	PowerEvent
    80  	SessionChange
    81  	PreShutdown
    82  )
    83  
    84  // Standard Windows Service Accepted values
    85  //
    86  // DO NOT REORDER
    87  const (
    88  	AcceptStop Accepted = 1 << iota
    89  	AcceptPauseAndContinue
    90  	AcceptShutdown
    91  	AcceptParamChange
    92  	AcceptNetBindChange
    93  	AcceptHardwareProfileChange
    94  	AcceptPowerEvent
    95  	AcceptSessionChange
    96  	AcceptPreShutdown
    97  )
    98  
    99  var service Service
   100  
   101  var callBack struct {
   102  	_ [0]func()
   103  	sync.Once
   104  	f, m uintptr
   105  }
   106  
   107  // State describes the current service execution state (Stopped, Running, etc.)
   108  type State uint32
   109  
   110  // Reason is the reason that the service was started.
   111  type Reason uint32
   112  
   113  // Command represents a service state change request. It is sent to a service
   114  // by the service manager, and should be acted upon by the service.
   115  type Command uint32
   116  
   117  // Accepted is used to describe commands accepted by the service.
   118  //
   119  // Interrogate is always accepted.
   120  type Accepted uint32
   121  
   122  // Change is sent to the service Handler to request service status changes and
   123  // updates to the service control manager.
   124  type Change struct {
   125  	Command   Command
   126  	EventType uint32
   127  	EventData uintptr
   128  	Status    Status
   129  	Context   uintptr
   130  }
   131  
   132  // Status combines State and Accepted commands to fully describe running
   133  // service.
   134  type Status struct {
   135  	_          [0]func()
   136  	State      State
   137  	Accepts    Accepted
   138  	CheckPoint uint32
   139  	WaitHint   uint32
   140  	ProcessID  uint32
   141  	ExitCode   uint32
   142  }
   143  
   144  // Service is a struct that is passed to the Handler function and can be used
   145  // to receive and send updates to the service control manager.
   146  //
   147  // NOTE(dij): The function 'DynamicStartReason' is only available on Windows >7
   148  // and will return an error if it does not exist.
   149  type Service struct {
   150  	f     Handler
   151  	e, in chan Change
   152  	out   chan Status
   153  	n     string
   154  	h     uintptr
   155  }
   156  
   157  // Handler is a function interface that must be implemented to run as a Windows
   158  // service.
   159  //
   160  // This function will be called by the package code at the start of the service,
   161  // and the service will exit once Execute completes.
   162  //
   163  // Inside the function, you may use the context or read service change requests
   164  // using the 's.Requests()' channel and act accordingly.
   165  //
   166  // You must keep service control manager up to date about state of your service
   167  // by using the 'Update' or 'UpdateState' functions.
   168  //
   169  // The supplied string list contains the service name followed by argument
   170  // strings passed to the service.
   171  //
   172  // You can provide service exit code in the return parameter, with 0 being
   173  // "no error".
   174  type Handler func(context.Context, Service, []string) uint32
   175  
   176  // Handle returns a pointer to the current Service. This handle is only valid in
   177  // the context of the running service.
   178  func (s *Service) Handle() uintptr {
   179  	return s.h
   180  }
   181  
   182  // Update is used to send an update to the Service Control Manager. The
   183  // supplied Status struct can be used to indicate status and progress to SCM.
   184  func (s *Service) Update(v Status) {
   185  	s.out <- v
   186  }
   187  
   188  // Run executes service name by calling the appropriate handler function.
   189  //
   190  // This function will block until complete.
   191  // Any errors returned indicate that bootstrappping of the service failed.
   192  //
   193  // Attempts to call this multiple times will return 'os.ErrInvalid'.
   194  //
   195  // NOTE: This function acts differently depending on the buildtags added.
   196  //
   197  //	The "svcdll" tag can be used to call this from 'ServiceMain' as a CGO dll,
   198  //	which requires no service wiring.
   199  func Run(name string, f Handler) error {
   200  	if service.f != nil {
   201  		return os.ErrInvalid
   202  	}
   203  	callBack.Do(func() {
   204  		service.e = make(chan Change)
   205  		callBack.m = syscall.NewCallback(serviceMain)
   206  		callBack.f = syscall.NewCallback(serviceHandler)
   207  	})
   208  	service.n, service.f = name, f
   209  	runtime.LockOSThread()
   210  	err := serviceWireThread(name)
   211  	runtime.UnlockOSThread()
   212  	return err
   213  }
   214  
   215  // Requests returns a receive-only chan that will receive any updates sent from
   216  // the Service control manager.
   217  //
   218  // It is required by SCM to act on these as soon as they are received.
   219  func (s *Service) Requests() <-chan Change {
   220  	return s.in
   221  }
   222  func serviceHandler(c, e, d, _ uintptr) uintptr {
   223  	//            NOTE(dij): ^ This pointer is SUPER FUCKING UNRELIABLE! Don't
   224  	//                       fucking use it!
   225  	service.e <- Change{Command: Command(c), EventType: uint32(e), EventData: d}
   226  	return 0
   227  }
   228  func serviceMain(argc uint32, argv **uint16) uintptr {
   229  	if service.f == nil || callBack.f == 0 || callBack.m == 0 {
   230  		return 0xE0000239
   231  	}
   232  	var err error
   233  	service.h, err = winapi.RegisterServiceCtrlHandlerEx(service.n, callBack.f, uintptr(unsafe.Pointer(&service)))
   234  	//                                                                          NOTE(dij): ^ For some reason, keeping
   235  	//                                                                                       this here prevents it from
   236  	//                                                                                       being garbage collected.
   237  	if err != nil {
   238  		if e, ok := err.(syscall.Errno); ok {
   239  			return uintptr(e)
   240  		}
   241  		return 0xE0000239
   242  	}
   243  	var a []string
   244  	if argc > 0 {
   245  		var (
   246  			e []*uint16
   247  			h = (*winapi.SliceHeader)(unsafe.Pointer(&e))
   248  		)
   249  		h.Data, h.Len, h.Cap = unsafe.Pointer(argv), int(argc), int(argc)
   250  		a = make([]string, len(e))
   251  		for i, v := range e {
   252  			a[i] = winapi.UTF16PtrToString(v)
   253  		}
   254  	}
   255  	if err := service.update(Status{State: StartPending}, false, 0); err != nil {
   256  		if e, ok := err.(syscall.Errno); ok {
   257  			service.update(Status{State: Stopped}, false, uint32(e))
   258  			return uintptr(e)
   259  		}
   260  		service.update(Status{State: Stopped}, false, 0xE0000239)
   261  		return 0xE0000239
   262  	}
   263  	var (
   264  		b, y = context.WithCancel(context.Background())
   265  		c    = Status{State: StartPending}
   266  		x    = make(chan uint32)
   267  		f    uint32
   268  	)
   269  	// NOTE(dij): Making the 'in' channel buffered so the sends to it doesn't
   270  	//            block.
   271  	service.in, service.out = make(chan Change, 1), make(chan Status)
   272  	go func() {
   273  		defer func() {
   274  			if r := recover(); r != nil {
   275  				x <- 0x1
   276  				close(x)
   277  			}
   278  		}()
   279  		x <- service.f(b, service, a)
   280  		close(x)
   281  	}()
   282  loop:
   283  	for {
   284  		select {
   285  		case f = <-x:
   286  			break loop
   287  		case v := <-service.e:
   288  			// NOTE(dij): Instead of dropping all the excess new entries on the
   289  			//            floor, we should clear them instead as we want the
   290  			//            service handler to see the latest entry.
   291  			for len(service.in) > 0 {
   292  				<-service.in
   293  			}
   294  			// NOTE(dij): Cancel the context and signal that we're working on
   295  			//            closing up.
   296  			switch v.Status = c; v.Command {
   297  			case Stop, Shutdown:
   298  				y()
   299  				service.update(Status{State: StopPending}, false, 0)
   300  			}
   301  			service.in <- v
   302  		case v := <-service.out:
   303  			if err := service.update(v, false, v.ExitCode); err != nil {
   304  				if e, ok := err.(syscall.Errno); ok {
   305  					f = uint32(e)
   306  				} else {
   307  					f = 0xE0000239
   308  				}
   309  				break loop
   310  			}
   311  			c = v
   312  		}
   313  	}
   314  	service.update(Status{State: StopPending}, f > 0, f)
   315  	y()
   316  	close(service.in)
   317  	close(service.out)
   318  	service.update(Status{State: Stopped}, f > 0, f)
   319  	close(service.e)
   320  	service.h = 0
   321  	return 0
   322  }
   323  
   324  // UpdateState is used to send an update to the Service Control Manager. The
   325  // supplied state type is required and an optional vardic of Accepted control
   326  // types can be used to indicate to SCM what commands are accepted.
   327  //
   328  // This is a quick helper function for the 'Update' function.
   329  func (s *Service) UpdateState(v State, a ...Accepted) {
   330  	if len(a) == 0 {
   331  		s.out <- Status{State: v}
   332  		return
   333  	}
   334  	if len(a) == 1 {
   335  		s.out <- Status{State: v, Accepts: a[0]}
   336  		return
   337  	}
   338  	u := Status{State: v, Accepts: a[0]}
   339  	for i := 1; i < len(a); i++ {
   340  		u.Accepts |= a[i]
   341  	}
   342  	s.out <- u
   343  }
   344  
   345  // DynamicStartReason will return the DynamicStartReason type. This function is
   346  // only available after Windows 8 and will return an error if it is not supported.
   347  func (s *Service) DynamicStartReason() (Reason, error) {
   348  	r, err := winapi.QueryServiceDynamicInformation(s.h, 1)
   349  	if err != nil {
   350  		return 0, err
   351  	}
   352  	return Reason(r), nil
   353  }
   354  func (s *Service) update(u Status, r bool, e uint32) error {
   355  	if s.h == 0 {
   356  		return xerr.Sub("update without a Service status handle", 0x14)
   357  	}
   358  	v := winapi.ServiceStatus{ServiceType: serviceType, CurrentState: uint32(u.State)}
   359  	if u.Accepts&AcceptStop != 0 {
   360  		v.ControlsAccepted |= 1
   361  	}
   362  	if u.Accepts&AcceptPauseAndContinue != 0 {
   363  		v.ControlsAccepted |= 2
   364  	}
   365  	if u.Accepts&AcceptShutdown != 0 {
   366  		v.ControlsAccepted |= 4
   367  	}
   368  	if u.Accepts&AcceptParamChange != 0 {
   369  		v.ControlsAccepted |= 8
   370  	}
   371  	if u.Accepts&AcceptNetBindChange != 0 {
   372  		v.ControlsAccepted |= 16
   373  	}
   374  	if u.Accepts&AcceptHardwareProfileChange != 0 {
   375  		v.ControlsAccepted |= 32
   376  	}
   377  	if u.Accepts&AcceptPowerEvent != 0 {
   378  		v.ControlsAccepted |= 64
   379  	}
   380  	if u.Accepts&AcceptSessionChange != 0 {
   381  		v.ControlsAccepted |= 128
   382  	}
   383  	if u.Accepts&AcceptPreShutdown != 0 {
   384  		v.ControlsAccepted |= 256
   385  	}
   386  	if e == 0 {
   387  		v.Win32ExitCode, v.ServiceSpecificExitCode = 0, 0
   388  	} else if r {
   389  		v.Win32ExitCode, v.ServiceSpecificExitCode = 1064, e
   390  	} else {
   391  		v.Win32ExitCode, v.ServiceSpecificExitCode = e, 0
   392  	}
   393  	v.CheckPoint, v.WaitHint = u.CheckPoint, u.WaitHint
   394  	return winapi.SetServiceStatus(s.h, &v)
   395  }