123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314 |
- // 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
- }
|