| // 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. |
| |
| // +build windows |
| |
| // Package svc provides everything required to build Windows service. |
| // |
| package svc |
| |
| import ( |
| "errors" |
| "runtime" |
| "syscall" |
| "unsafe" |
| |
| "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) |
| ) |
| |
| // 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) |
| ) |
| |
| // 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 |
| } |
| |
| // 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) |
| } |
| |
| var ( |
| // These are used by asm code. |
| goWaitsH uintptr |
| cWaitsH uintptr |
| ssHandle uintptr |
| sName *uint16 |
| sArgc uintptr |
| sArgv **uint16 |
| ctlHandlerExProc uintptr |
| cSetEvent uintptr |
| cWaitForSingleObject uintptr |
| cRegisterServiceCtrlHandlerExW uintptr |
| ) |
| |
| func init() { |
| k := windows.NewLazySystemDLL("kernel32.dll") |
| cSetEvent = k.NewProc("SetEvent").Addr() |
| cWaitForSingleObject = k.NewProc("WaitForSingleObject").Addr() |
| a := windows.NewLazySystemDLL("advapi32.dll") |
| cRegisterServiceCtrlHandlerExW = a.NewProc("RegisterServiceCtrlHandlerExW").Addr() |
| } |
| |
| 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 |
| cWaits *event |
| goWaits *event |
| c chan ctlEvent |
| handler Handler |
| } |
| |
| func newService(name string, handler Handler) (*service, error) { |
| var s service |
| var err error |
| s.name = name |
| s.c = make(chan ctlEvent) |
| s.handler = handler |
| s.cWaits, err = newEvent() |
| if err != nil { |
| return nil, err |
| } |
| s.goWaits, err = newEvent() |
| if err != nil { |
| s.cWaits.Close() |
| return nil, err |
| } |
| return &s, nil |
| } |
| |
| func (s *service) close() error { |
| s.cWaits.Close() |
| s.goWaits.Close() |
| return nil |
| } |
| |
| 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 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) |
| } |
| |
| const ( |
| sysErrSetServiceStatusFailed = uint32(syscall.APPLICATION_ERROR) + iota |
| sysErrNewThreadInCallback |
| ) |
| |
| func (s *service) run() { |
| s.goWaits.Wait() |
| s.h = windows.Handle(ssHandle) |
| argv := (*[100]*int16)(unsafe.Pointer(sArgv))[:sArgc] |
| args := make([]string, len(argv)) |
| for i, a := range argv { |
| args[i] = syscall.UTF16ToString((*[1 << 20]uint16)(unsafe.Pointer(a))[:]) |
| } |
| |
| cmdsToHandler := make(chan ChangeRequest) |
| changesFromHandler := make(chan Status) |
| exitFromHandler := make(chan exitCode) |
| |
| go func() { |
| ss, errno := s.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 := s.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 = s.c |
| outch = nil |
| case c := <-changesFromHandler: |
| err := s.updateStatus(&c, &ec) |
| if err != nil { |
| // best suitable error number |
| ec.errno = sysErrSetServiceStatusFailed |
| if err2, ok := err.(syscall.Errno); ok { |
| ec.errno = uint32(err2) |
| } |
| break loop |
| } |
| outcr.CurrentStatus = c |
| case ec = <-exitFromHandler: |
| break loop |
| } |
| } |
| |
| s.updateStatus(&Status{State: Stopped}, &ec) |
| s.cWaits.Set() |
| } |
| |
| func newCallback(fn interface{}) (cb uintptr, err error) { |
| defer func() { |
| r := recover() |
| if r == nil { |
| return |
| } |
| cb = 0 |
| switch v := r.(type) { |
| case string: |
| err = errors.New(v) |
| case error: |
| err = v |
| default: |
| err = errors.New("unexpected panic in syscall.NewCallback") |
| } |
| }() |
| return syscall.NewCallback(fn), nil |
| } |
| |
| // BUG(brainman): There is no mechanism to run multiple services |
| // inside one single executable. Perhaps, it can be overcome by |
| // using RegisterServiceCtrlHandlerEx Windows api. |
| |
| // Run executes service name by calling appropriate handler function. |
| func Run(name string, handler Handler) error { |
| runtime.LockOSThread() |
| |
| tid := windows.GetCurrentThreadId() |
| |
| s, err := newService(name, handler) |
| if err != nil { |
| return err |
| } |
| |
| ctlHandler := func(ctl, evtype, evdata, context uintptr) uintptr { |
| e := ctlEvent{cmd: Cmd(ctl), eventType: uint32(evtype), eventData: evdata, context: context} |
| // We assume that this callback function is running on |
| // the same thread as Run. Nowhere in MS documentation |
| // I could find statement to guarantee that. So putting |
| // check here to verify, otherwise things will go bad |
| // quickly, if ignored. |
| i := windows.GetCurrentThreadId() |
| if i != tid { |
| e.errno = sysErrNewThreadInCallback |
| } |
| s.c <- e |
| // Always return NO_ERROR (0) for now. |
| return windows.NO_ERROR |
| } |
| |
| var svcmain uintptr |
| getServiceMain(&svcmain) |
| t := []windows.SERVICE_TABLE_ENTRY{ |
| {ServiceName: syscall.StringToUTF16Ptr(s.name), ServiceProc: svcmain}, |
| {ServiceName: nil, ServiceProc: 0}, |
| } |
| |
| goWaitsH = uintptr(s.goWaits.h) |
| cWaitsH = uintptr(s.cWaits.h) |
| sName = t[0].ServiceName |
| ctlHandlerExProc, err = newCallback(ctlHandler) |
| if err != nil { |
| return err |
| } |
| |
| go s.run() |
| |
| err = windows.StartServiceCtrlDispatcher(&t[0]) |
| if err != nil { |
| return err |
| } |
| return nil |
| } |
| |
| // 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. |
| // This code will have to change once multiple services are possible per process. |
| func StatusHandle() windows.Handle { |
| return windows.Handle(ssHandle) |
| } |