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