You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
315 lines
11 KiB
Go
315 lines
11 KiB
Go
2 years ago
|
// 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
|
||
|
}
|