mirror of
				https://github.com/k3s-io/kubernetes.git
				synced 2025-10-31 13:50:01 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			314 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			314 lines
		
	
	
		
			11 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2012 The Go Authors. All rights reserved.
 | |
| // Use of this source code is governed by a BSD-style
 | |
| // license that can be found in the LICENSE file.
 | |
| 
 | |
| //go:build windows
 | |
| // +build windows
 | |
| 
 | |
| // Package svc provides everything required to build Windows service.
 | |
| package svc
 | |
| 
 | |
| import (
 | |
| 	"errors"
 | |
| 	"sync"
 | |
| 	"unsafe"
 | |
| 
 | |
| 	"golang.org/x/sys/internal/unsafeheader"
 | |
| 	"golang.org/x/sys/windows"
 | |
| )
 | |
| 
 | |
| // State describes service execution state (Stopped, Running and so on).
 | |
| type State uint32
 | |
| 
 | |
| const (
 | |
| 	Stopped         = State(windows.SERVICE_STOPPED)
 | |
| 	StartPending    = State(windows.SERVICE_START_PENDING)
 | |
| 	StopPending     = State(windows.SERVICE_STOP_PENDING)
 | |
| 	Running         = State(windows.SERVICE_RUNNING)
 | |
| 	ContinuePending = State(windows.SERVICE_CONTINUE_PENDING)
 | |
| 	PausePending    = State(windows.SERVICE_PAUSE_PENDING)
 | |
| 	Paused          = State(windows.SERVICE_PAUSED)
 | |
| )
 | |
| 
 | |
| // Cmd represents service state change request. It is sent to a service
 | |
| // by the service manager, and should be actioned upon by the service.
 | |
| type Cmd uint32
 | |
| 
 | |
| const (
 | |
| 	Stop                  = Cmd(windows.SERVICE_CONTROL_STOP)
 | |
| 	Pause                 = Cmd(windows.SERVICE_CONTROL_PAUSE)
 | |
| 	Continue              = Cmd(windows.SERVICE_CONTROL_CONTINUE)
 | |
| 	Interrogate           = Cmd(windows.SERVICE_CONTROL_INTERROGATE)
 | |
| 	Shutdown              = Cmd(windows.SERVICE_CONTROL_SHUTDOWN)
 | |
| 	ParamChange           = Cmd(windows.SERVICE_CONTROL_PARAMCHANGE)
 | |
| 	NetBindAdd            = Cmd(windows.SERVICE_CONTROL_NETBINDADD)
 | |
| 	NetBindRemove         = Cmd(windows.SERVICE_CONTROL_NETBINDREMOVE)
 | |
| 	NetBindEnable         = Cmd(windows.SERVICE_CONTROL_NETBINDENABLE)
 | |
| 	NetBindDisable        = Cmd(windows.SERVICE_CONTROL_NETBINDDISABLE)
 | |
| 	DeviceEvent           = Cmd(windows.SERVICE_CONTROL_DEVICEEVENT)
 | |
| 	HardwareProfileChange = Cmd(windows.SERVICE_CONTROL_HARDWAREPROFILECHANGE)
 | |
| 	PowerEvent            = Cmd(windows.SERVICE_CONTROL_POWEREVENT)
 | |
| 	SessionChange         = Cmd(windows.SERVICE_CONTROL_SESSIONCHANGE)
 | |
| 	PreShutdown           = Cmd(windows.SERVICE_CONTROL_PRESHUTDOWN)
 | |
| )
 | |
| 
 | |
| // Accepted is used to describe commands accepted by the service.
 | |
| // Note that Interrogate is always accepted.
 | |
| type Accepted uint32
 | |
| 
 | |
| const (
 | |
| 	AcceptStop                  = Accepted(windows.SERVICE_ACCEPT_STOP)
 | |
| 	AcceptShutdown              = Accepted(windows.SERVICE_ACCEPT_SHUTDOWN)
 | |
| 	AcceptPauseAndContinue      = Accepted(windows.SERVICE_ACCEPT_PAUSE_CONTINUE)
 | |
| 	AcceptParamChange           = Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)
 | |
| 	AcceptNetBindChange         = Accepted(windows.SERVICE_ACCEPT_NETBINDCHANGE)
 | |
| 	AcceptHardwareProfileChange = Accepted(windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE)
 | |
| 	AcceptPowerEvent            = Accepted(windows.SERVICE_ACCEPT_POWEREVENT)
 | |
| 	AcceptSessionChange         = Accepted(windows.SERVICE_ACCEPT_SESSIONCHANGE)
 | |
| 	AcceptPreShutdown           = Accepted(windows.SERVICE_ACCEPT_PRESHUTDOWN)
 | |
| )
 | |
| 
 | |
| // Status combines State and Accepted commands to fully describe running service.
 | |
| type Status struct {
 | |
| 	State                   State
 | |
| 	Accepts                 Accepted
 | |
| 	CheckPoint              uint32 // used to report progress during a lengthy operation
 | |
| 	WaitHint                uint32 // estimated time required for a pending operation, in milliseconds
 | |
| 	ProcessId               uint32 // if the service is running, the process identifier of it, and otherwise zero
 | |
| 	Win32ExitCode           uint32 // set if the service has exited with a win32 exit code
 | |
| 	ServiceSpecificExitCode uint32 // set if the service has exited with a service-specific exit code
 | |
| }
 | |
| 
 | |
| // StartReason is the reason that the service was started.
 | |
| type StartReason uint32
 | |
| 
 | |
| const (
 | |
| 	StartReasonDemand           = StartReason(windows.SERVICE_START_REASON_DEMAND)
 | |
| 	StartReasonAuto             = StartReason(windows.SERVICE_START_REASON_AUTO)
 | |
| 	StartReasonTrigger          = StartReason(windows.SERVICE_START_REASON_TRIGGER)
 | |
| 	StartReasonRestartOnFailure = StartReason(windows.SERVICE_START_REASON_RESTART_ON_FAILURE)
 | |
| 	StartReasonDelayedAuto      = StartReason(windows.SERVICE_START_REASON_DELAYEDAUTO)
 | |
| )
 | |
| 
 | |
| // ChangeRequest is sent to the service Handler to request service status change.
 | |
| type ChangeRequest struct {
 | |
| 	Cmd           Cmd
 | |
| 	EventType     uint32
 | |
| 	EventData     uintptr
 | |
| 	CurrentStatus Status
 | |
| 	Context       uintptr
 | |
| }
 | |
| 
 | |
| // Handler is the interface that must be implemented to build Windows service.
 | |
| type Handler interface {
 | |
| 	// Execute will be called by the package code at the start of
 | |
| 	// the service, and the service will exit once Execute completes.
 | |
| 	// Inside Execute you must read service change requests from r and
 | |
| 	// act accordingly. You must keep service control manager up to date
 | |
| 	// about state of your service by writing into s as required.
 | |
| 	// args contains service name followed by argument strings passed
 | |
| 	// to the service.
 | |
| 	// You can provide service exit code in exitCode return parameter,
 | |
| 	// with 0 being "no error". You can also indicate if exit code,
 | |
| 	// if any, is service specific or not by using svcSpecificEC
 | |
| 	// parameter.
 | |
| 	Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32)
 | |
| }
 | |
| 
 | |
| type ctlEvent struct {
 | |
| 	cmd       Cmd
 | |
| 	eventType uint32
 | |
| 	eventData uintptr
 | |
| 	context   uintptr
 | |
| 	errno     uint32
 | |
| }
 | |
| 
 | |
| // service provides access to windows service api.
 | |
| type service struct {
 | |
| 	name    string
 | |
| 	h       windows.Handle
 | |
| 	c       chan ctlEvent
 | |
| 	handler Handler
 | |
| }
 | |
| 
 | |
| type exitCode struct {
 | |
| 	isSvcSpecific bool
 | |
| 	errno         uint32
 | |
| }
 | |
| 
 | |
| func (s *service) updateStatus(status *Status, ec *exitCode) error {
 | |
| 	if s.h == 0 {
 | |
| 		return errors.New("updateStatus with no service status handle")
 | |
| 	}
 | |
| 	var t windows.SERVICE_STATUS
 | |
| 	t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS
 | |
| 	t.CurrentState = uint32(status.State)
 | |
| 	if status.Accepts&AcceptStop != 0 {
 | |
| 		t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP
 | |
| 	}
 | |
| 	if status.Accepts&AcceptShutdown != 0 {
 | |
| 		t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN
 | |
| 	}
 | |
| 	if status.Accepts&AcceptPauseAndContinue != 0 {
 | |
| 		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE
 | |
| 	}
 | |
| 	if status.Accepts&AcceptParamChange != 0 {
 | |
| 		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PARAMCHANGE
 | |
| 	}
 | |
| 	if status.Accepts&AcceptNetBindChange != 0 {
 | |
| 		t.ControlsAccepted |= windows.SERVICE_ACCEPT_NETBINDCHANGE
 | |
| 	}
 | |
| 	if status.Accepts&AcceptHardwareProfileChange != 0 {
 | |
| 		t.ControlsAccepted |= windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE
 | |
| 	}
 | |
| 	if status.Accepts&AcceptPowerEvent != 0 {
 | |
| 		t.ControlsAccepted |= windows.SERVICE_ACCEPT_POWEREVENT
 | |
| 	}
 | |
| 	if status.Accepts&AcceptSessionChange != 0 {
 | |
| 		t.ControlsAccepted |= windows.SERVICE_ACCEPT_SESSIONCHANGE
 | |
| 	}
 | |
| 	if status.Accepts&AcceptPreShutdown != 0 {
 | |
| 		t.ControlsAccepted |= windows.SERVICE_ACCEPT_PRESHUTDOWN
 | |
| 	}
 | |
| 	if ec.errno == 0 {
 | |
| 		t.Win32ExitCode = windows.NO_ERROR
 | |
| 		t.ServiceSpecificExitCode = windows.NO_ERROR
 | |
| 	} else if ec.isSvcSpecific {
 | |
| 		t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR)
 | |
| 		t.ServiceSpecificExitCode = ec.errno
 | |
| 	} else {
 | |
| 		t.Win32ExitCode = ec.errno
 | |
| 		t.ServiceSpecificExitCode = windows.NO_ERROR
 | |
| 	}
 | |
| 	t.CheckPoint = status.CheckPoint
 | |
| 	t.WaitHint = status.WaitHint
 | |
| 	return windows.SetServiceStatus(s.h, &t)
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	initCallbacks       sync.Once
 | |
| 	ctlHandlerCallback  uintptr
 | |
| 	serviceMainCallback uintptr
 | |
| )
 | |
| 
 | |
| func ctlHandler(ctl, evtype, evdata, context uintptr) uintptr {
 | |
| 	s := (*service)(unsafe.Pointer(context))
 | |
| 	e := ctlEvent{cmd: Cmd(ctl), eventType: uint32(evtype), eventData: evdata, context: 123456} // Set context to 123456 to test issue #25660.
 | |
| 	s.c <- e
 | |
| 	return 0
 | |
| }
 | |
| 
 | |
| var theService service // This is, unfortunately, a global, which means only one service per process.
 | |
| 
 | |
| // serviceMain is the entry point called by the service manager, registered earlier by
 | |
| // the call to StartServiceCtrlDispatcher.
 | |
| func serviceMain(argc uint32, argv **uint16) uintptr {
 | |
| 	handle, err := windows.RegisterServiceCtrlHandlerEx(windows.StringToUTF16Ptr(theService.name), ctlHandlerCallback, uintptr(unsafe.Pointer(&theService)))
 | |
| 	if sysErr, ok := err.(windows.Errno); ok {
 | |
| 		return uintptr(sysErr)
 | |
| 	} else if err != nil {
 | |
| 		return uintptr(windows.ERROR_UNKNOWN_EXCEPTION)
 | |
| 	}
 | |
| 	theService.h = handle
 | |
| 	defer func() {
 | |
| 		theService.h = 0
 | |
| 	}()
 | |
| 	var args16 []*uint16
 | |
| 	hdr := (*unsafeheader.Slice)(unsafe.Pointer(&args16))
 | |
| 	hdr.Data = unsafe.Pointer(argv)
 | |
| 	hdr.Len = int(argc)
 | |
| 	hdr.Cap = int(argc)
 | |
| 
 | |
| 	args := make([]string, len(args16))
 | |
| 	for i, a := range args16 {
 | |
| 		args[i] = windows.UTF16PtrToString(a)
 | |
| 	}
 | |
| 
 | |
| 	cmdsToHandler := make(chan ChangeRequest)
 | |
| 	changesFromHandler := make(chan Status)
 | |
| 	exitFromHandler := make(chan exitCode)
 | |
| 
 | |
| 	go func() {
 | |
| 		ss, errno := theService.handler.Execute(args, cmdsToHandler, changesFromHandler)
 | |
| 		exitFromHandler <- exitCode{ss, errno}
 | |
| 	}()
 | |
| 
 | |
| 	ec := exitCode{isSvcSpecific: true, errno: 0}
 | |
| 	outcr := ChangeRequest{
 | |
| 		CurrentStatus: Status{State: Stopped},
 | |
| 	}
 | |
| 	var outch chan ChangeRequest
 | |
| 	inch := theService.c
 | |
| loop:
 | |
| 	for {
 | |
| 		select {
 | |
| 		case r := <-inch:
 | |
| 			if r.errno != 0 {
 | |
| 				ec.errno = r.errno
 | |
| 				break loop
 | |
| 			}
 | |
| 			inch = nil
 | |
| 			outch = cmdsToHandler
 | |
| 			outcr.Cmd = r.cmd
 | |
| 			outcr.EventType = r.eventType
 | |
| 			outcr.EventData = r.eventData
 | |
| 			outcr.Context = r.context
 | |
| 		case outch <- outcr:
 | |
| 			inch = theService.c
 | |
| 			outch = nil
 | |
| 		case c := <-changesFromHandler:
 | |
| 			err := theService.updateStatus(&c, &ec)
 | |
| 			if err != nil {
 | |
| 				ec.errno = uint32(windows.ERROR_EXCEPTION_IN_SERVICE)
 | |
| 				if err2, ok := err.(windows.Errno); ok {
 | |
| 					ec.errno = uint32(err2)
 | |
| 				}
 | |
| 				break loop
 | |
| 			}
 | |
| 			outcr.CurrentStatus = c
 | |
| 		case ec = <-exitFromHandler:
 | |
| 			break loop
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	theService.updateStatus(&Status{State: Stopped}, &ec)
 | |
| 
 | |
| 	return windows.NO_ERROR
 | |
| }
 | |
| 
 | |
| // Run executes service name by calling appropriate handler function.
 | |
| func Run(name string, handler Handler) error {
 | |
| 	initCallbacks.Do(func() {
 | |
| 		ctlHandlerCallback = windows.NewCallback(ctlHandler)
 | |
| 		serviceMainCallback = windows.NewCallback(serviceMain)
 | |
| 	})
 | |
| 	theService.name = name
 | |
| 	theService.handler = handler
 | |
| 	theService.c = make(chan ctlEvent)
 | |
| 	t := []windows.SERVICE_TABLE_ENTRY{
 | |
| 		{ServiceName: windows.StringToUTF16Ptr(theService.name), ServiceProc: serviceMainCallback},
 | |
| 		{ServiceName: nil, ServiceProc: 0},
 | |
| 	}
 | |
| 	return windows.StartServiceCtrlDispatcher(&t[0])
 | |
| }
 | |
| 
 | |
| // StatusHandle returns service status handle. It is safe to call this function
 | |
| // from inside the Handler.Execute because then it is guaranteed to be set.
 | |
| func StatusHandle() windows.Handle {
 | |
| 	return theService.h
 | |
| }
 | |
| 
 | |
| // DynamicStartReason returns the reason why the service was started. It is safe
 | |
| // to call this function from inside the Handler.Execute because then it is
 | |
| // guaranteed to be set.
 | |
| func DynamicStartReason() (StartReason, error) {
 | |
| 	var allocReason *uint32
 | |
| 	err := windows.QueryServiceDynamicInformation(theService.h, windows.SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON, unsafe.Pointer(&allocReason))
 | |
| 	if err != nil {
 | |
| 		return 0, err
 | |
| 	}
 | |
| 	reason := StartReason(*allocReason)
 | |
| 	windows.LocalFree(windows.Handle(unsafe.Pointer(allocReason)))
 | |
| 	return reason, nil
 | |
| }
 |