1package cmd
2
3import (
4	"context"
5	"errors"
6	"fmt"
7	"os"
8	"sync"
9
10	runtimedebug "runtime/debug"
11
12	"github.com/sirupsen/logrus"
13	"golang.org/x/sys/windows/svc"
14	"golang.org/x/sys/windows/svc/debug"
15	"golang.org/x/sys/windows/svc/eventlog"
16)
17
18var (
19	_    svc.Handler = &Service{}
20	elog debug.Log
21)
22
23func NewService(args []string) *Service {
24	return &Service{args: args}
25}
26
27type Service struct {
28	args []string
29	wg   sync.WaitGroup
30	mu   sync.Mutex
31}
32
33func (s *Service) start(ctx context.Context, args []string, changes chan<- svc.Status) chan error {
34	s.mu.Lock()
35	defer s.mu.Unlock()
36	s.wg.Wait()
37	s.wg.Add(1)
38	result := make(chan error, 1)
39	go func() {
40		defer func() {
41			if e := recover(); e != nil {
42				changes <- svc.Status{State: svc.Stopped}
43				stack := runtimedebug.Stack()
44				result <- errors.New(string(stack))
45			}
46		}()
47		defer s.wg.Done()
48		changes <- svc.Status{State: svc.StartPending}
49		// Start service here
50		binPath, err := exePath()
51		if err != nil {
52			panic(err)
53		}
54		configFile := args[0]
55		logPath := args[1]
56		logFile, err := os.OpenFile(logPath, os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
57		if err != nil {
58			result <- fmt.Errorf("service quit: cant't open log file: %s", err)
59		}
60		defer logFile.Close()
61
62		logger := logrus.New()
63		logger.SetFormatter(&logrus.JSONFormatter{})
64		logger.SetOutput(logFile)
65		entry := logger.WithFields(logrus.Fields{
66			"component": "cmd",
67		})
68
69		args = []string{binPath, "start", "-c", configFile}
70		command := newStartCommand(ctx, args, entry)
71		accepts := svc.AcceptShutdown | svc.AcceptStop
72		changes <- svc.Status{State: svc.Running, Accepts: accepts}
73
74		if err := command.Execute(); err != nil {
75			logger.WithError(err).Error("sensu-agent exited with error")
76			result <- err
77		}
78	}()
79	return result
80}
81
82func (s *Service) Execute(_ []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (bool, uint32) {
83	ctx, cancel := context.WithCancel(context.Background())
84	errs := s.start(ctx, s.args, changes)
85	elog, _ := eventlog.Open(serviceName)
86	defer elog.Close()
87	for {
88		select {
89		case req := <-r:
90			switch req.Cmd {
91			case svc.Stop, svc.Shutdown:
92				elog.Info(1, "service shutting down")
93				changes <- svc.Status{State: svc.StopPending}
94				cancel()
95				s.wg.Wait()
96				changes <- svc.Status{State: svc.Stopped}
97				return false, 0
98			}
99		case err := <-errs:
100			elog.Error(1, fmt.Sprintf("restarting due to error (%v) %s", s.args, err))
101			s.start(ctx, s.args, changes)
102		}
103	}
104	return false, 0
105}
106
107func runService(args []string) error {
108	elog, err := eventlog.Open(serviceName)
109	if err != nil {
110		return err
111	}
112	defer elog.Close()
113	elog.Info(1, fmt.Sprintf("starting %s service (%v)", serviceName, args))
114	if err := svc.Run(serviceName, NewService(args)); err != nil {
115		return err
116	}
117	elog.Info(1, fmt.Sprintf("%s service terminated", serviceName))
118	return nil
119}
120