123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- // Copyright 2015 Daniel Theophanes.
- // Use of this source code is governed by a zlib-style
- // license that can be found in the LICENSE file.
- package service
- import (
- "fmt"
- "os"
- "os/signal"
- "strconv"
- "strings"
- "sync"
- "time"
- "golang.org/x/sys/windows/registry"
- "golang.org/x/sys/windows/svc"
- "golang.org/x/sys/windows/svc/eventlog"
- "golang.org/x/sys/windows/svc/mgr"
- )
- const version = "windows-service"
- type windowsService struct {
- i Interface
- *Config
- errSync sync.Mutex
- stopStartErr error
- }
- // WindowsLogger allows using windows specific logging methods.
- type WindowsLogger struct {
- ev *eventlog.Log
- errs chan<- error
- }
- type windowsSystem struct{}
- func (windowsSystem) String() string {
- return version
- }
- func (windowsSystem) Detect() bool {
- return true
- }
- func (windowsSystem) Interactive() bool {
- return interactive
- }
- func (windowsSystem) New(i Interface, c *Config) (Service, error) {
- ws := &windowsService{
- i: i,
- Config: c,
- }
- return ws, nil
- }
- func init() {
- ChooseSystem(windowsSystem{})
- }
- func (l WindowsLogger) send(err error) error {
- if err == nil {
- return nil
- }
- if l.errs != nil {
- l.errs <- err
- }
- return err
- }
- // Error logs an error message.
- func (l WindowsLogger) Error(v ...interface{}) error {
- return l.send(l.ev.Error(3, fmt.Sprint(v...)))
- }
- // Warning logs an warning message.
- func (l WindowsLogger) Warning(v ...interface{}) error {
- return l.send(l.ev.Warning(2, fmt.Sprint(v...)))
- }
- // Info logs an info message.
- func (l WindowsLogger) Info(v ...interface{}) error {
- return l.send(l.ev.Info(1, fmt.Sprint(v...)))
- }
- // Errorf logs an error message.
- func (l WindowsLogger) Errorf(format string, a ...interface{}) error {
- return l.send(l.ev.Error(3, fmt.Sprintf(format, a...)))
- }
- // Warningf logs an warning message.
- func (l WindowsLogger) Warningf(format string, a ...interface{}) error {
- return l.send(l.ev.Warning(2, fmt.Sprintf(format, a...)))
- }
- // Infof logs an info message.
- func (l WindowsLogger) Infof(format string, a ...interface{}) error {
- return l.send(l.ev.Info(1, fmt.Sprintf(format, a...)))
- }
- // NError logs an error message and an event ID.
- func (l WindowsLogger) NError(eventID uint32, v ...interface{}) error {
- return l.send(l.ev.Error(eventID, fmt.Sprint(v...)))
- }
- // NWarning logs an warning message and an event ID.
- func (l WindowsLogger) NWarning(eventID uint32, v ...interface{}) error {
- return l.send(l.ev.Warning(eventID, fmt.Sprint(v...)))
- }
- // NInfo logs an info message and an event ID.
- func (l WindowsLogger) NInfo(eventID uint32, v ...interface{}) error {
- return l.send(l.ev.Info(eventID, fmt.Sprint(v...)))
- }
- // NErrorf logs an error message and an event ID.
- func (l WindowsLogger) NErrorf(eventID uint32, format string, a ...interface{}) error {
- return l.send(l.ev.Error(eventID, fmt.Sprintf(format, a...)))
- }
- // NWarningf logs an warning message and an event ID.
- func (l WindowsLogger) NWarningf(eventID uint32, format string, a ...interface{}) error {
- return l.send(l.ev.Warning(eventID, fmt.Sprintf(format, a...)))
- }
- // NInfof logs an info message and an event ID.
- func (l WindowsLogger) NInfof(eventID uint32, format string, a ...interface{}) error {
- return l.send(l.ev.Info(eventID, fmt.Sprintf(format, a...)))
- }
- var interactive = false
- func init() {
- var err error
- interactive, err = svc.IsAnInteractiveSession()
- if err != nil {
- panic(err)
- }
- }
- func (ws *windowsService) String() string {
- if len(ws.DisplayName) > 0 {
- return ws.DisplayName
- }
- return ws.Name
- }
- func (ws *windowsService) Platform() string {
- return version
- }
- func (ws *windowsService) setError(err error) {
- ws.errSync.Lock()
- defer ws.errSync.Unlock()
- ws.stopStartErr = err
- }
- func (ws *windowsService) getError() error {
- ws.errSync.Lock()
- defer ws.errSync.Unlock()
- return ws.stopStartErr
- }
- func (ws *windowsService) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
- const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown
- changes <- svc.Status{State: svc.StartPending}
- if err := ws.i.Start(ws); err != nil {
- ws.setError(err)
- return true, 1
- }
- changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
- loop:
- for {
- c := <-r
- switch c.Cmd {
- case svc.Interrogate:
- changes <- c.CurrentStatus
- case svc.Stop:
- changes <- svc.Status{State: svc.StopPending}
- if err := ws.i.Stop(ws); err != nil {
- ws.setError(err)
- return true, 2
- }
- break loop
- case svc.Shutdown:
- changes <- svc.Status{State: svc.StopPending}
- var err error
- if wsShutdown, ok := ws.i.(Shutdowner); ok {
- err = wsShutdown.Shutdown(ws)
- } else {
- err = ws.i.Stop(ws)
- }
- if err != nil {
- ws.setError(err)
- return true, 2
- }
- break loop
- default:
- continue loop
- }
- }
- return false, 0
- }
- func (ws *windowsService) Install() error {
- exepath, err := ws.execPath()
- if err != nil {
- return err
- }
- m, err := mgr.Connect()
- if err != nil {
- return err
- }
- defer m.Disconnect()
- s, err := m.OpenService(ws.Name)
- if err == nil {
- s.Close()
- return fmt.Errorf("service %s already exists", ws.Name)
- }
- s, err = m.CreateService(ws.Name, exepath, mgr.Config{
- DisplayName: ws.DisplayName,
- Description: ws.Description,
- StartType: mgr.StartAutomatic,
- ServiceStartName: ws.UserName,
- Password: ws.Option.string("Password", ""),
- Dependencies: ws.Dependencies,
- DelayedAutoStart: ws.Option.bool("DelayedAutoStart", false),
- }, ws.Arguments...)
- if err != nil {
- return err
- }
- defer s.Close()
- err = eventlog.InstallAsEventCreate(ws.Name, eventlog.Error|eventlog.Warning|eventlog.Info)
- if err != nil {
- if !strings.Contains(err.Error(), "exists") {
- s.Delete()
- return fmt.Errorf("SetupEventLogSource() failed: %s", err)
- }
- }
- return nil
- }
- func (ws *windowsService) Uninstall() error {
- m, err := mgr.Connect()
- if err != nil {
- return err
- }
- defer m.Disconnect()
- s, err := m.OpenService(ws.Name)
- if err != nil {
- return fmt.Errorf("service %s is not installed", ws.Name)
- }
- defer s.Close()
- err = s.Delete()
- if err != nil {
- return err
- }
- err = eventlog.Remove(ws.Name)
- if err != nil {
- return fmt.Errorf("RemoveEventLogSource() failed: %s", err)
- }
- return nil
- }
- func (ws *windowsService) Run() error {
- ws.setError(nil)
- if !interactive {
- // Return error messages from start and stop routines
- // that get executed in the Execute method.
- // Guarded with a mutex as it may run a different thread
- // (callback from windows).
- runErr := svc.Run(ws.Name, ws)
- startStopErr := ws.getError()
- if startStopErr != nil {
- return startStopErr
- }
- if runErr != nil {
- return runErr
- }
- return nil
- }
- err := ws.i.Start(ws)
- if err != nil {
- return err
- }
- sigChan := make(chan os.Signal)
- signal.Notify(sigChan, os.Interrupt)
- <-sigChan
- return ws.i.Stop(ws)
- }
- func (ws *windowsService) Status() (Status, error) {
- m, err := mgr.Connect()
- if err != nil {
- return StatusUnknown, err
- }
- defer m.Disconnect()
- s, err := m.OpenService(ws.Name)
- if err != nil {
- if err.Error() == "The specified service does not exist as an installed service." {
- return StatusUnknown, ErrNotInstalled
- }
- return StatusUnknown, err
- }
- defer s.Close()
- status, err := s.Query()
- if err != nil {
- return StatusUnknown, err
- }
- switch status.State {
- case svc.StartPending:
- fallthrough
- case svc.Running:
- return StatusRunning, nil
- case svc.PausePending:
- fallthrough
- case svc.Paused:
- fallthrough
- case svc.ContinuePending:
- fallthrough
- case svc.StopPending:
- fallthrough
- case svc.Stopped:
- return StatusStopped, nil
- default:
- return StatusUnknown, fmt.Errorf("unknown status %v", status)
- }
- }
- func (ws *windowsService) Start() error {
- m, err := mgr.Connect()
- if err != nil {
- return err
- }
- defer m.Disconnect()
- s, err := m.OpenService(ws.Name)
- if err != nil {
- return err
- }
- defer s.Close()
- return s.Start()
- }
- func (ws *windowsService) Stop() error {
- m, err := mgr.Connect()
- if err != nil {
- return err
- }
- defer m.Disconnect()
- s, err := m.OpenService(ws.Name)
- if err != nil {
- return err
- }
- defer s.Close()
- return ws.stopWait(s)
- }
- func (ws *windowsService) Restart() error {
- m, err := mgr.Connect()
- if err != nil {
- return err
- }
- defer m.Disconnect()
- s, err := m.OpenService(ws.Name)
- if err != nil {
- return err
- }
- defer s.Close()
- err = ws.stopWait(s)
- if err != nil {
- return err
- }
- return s.Start()
- }
- func (ws *windowsService) stopWait(s *mgr.Service) error {
- // First stop the service. Then wait for the service to
- // actually stop before starting it.
- status, err := s.Control(svc.Stop)
- if err != nil {
- return err
- }
- timeDuration := time.Millisecond * 50
- timeout := time.After(getStopTimeout() + (timeDuration * 2))
- tick := time.NewTicker(timeDuration)
- defer tick.Stop()
- for status.State != svc.Stopped {
- select {
- case <-tick.C:
- status, err = s.Query()
- if err != nil {
- return err
- }
- case <-timeout:
- break
- }
- }
- return nil
- }
- // getStopTimeout fetches the time before windows will kill the service.
- func getStopTimeout() time.Duration {
- // For default and paths see https://support.microsoft.com/en-us/kb/146092
- defaultTimeout := time.Millisecond * 20000
- key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control`, registry.READ)
- if err != nil {
- return defaultTimeout
- }
- sv, _, err := key.GetStringValue("WaitToKillServiceTimeout")
- if err != nil {
- return defaultTimeout
- }
- v, err := strconv.Atoi(sv)
- if err != nil {
- return defaultTimeout
- }
- return time.Millisecond * time.Duration(v)
- }
- func (ws *windowsService) Logger(errs chan<- error) (Logger, error) {
- if interactive {
- return ConsoleLogger, nil
- }
- return ws.SystemLogger(errs)
- }
- func (ws *windowsService) SystemLogger(errs chan<- error) (Logger, error) {
- el, err := eventlog.Open(ws.Name)
- if err != nil {
- return nil, err
- }
- return WindowsLogger{el, errs}, nil
- }
|