/
service.go
133 lines (121 loc) · 3.67 KB
/
service.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
// 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 svchelper
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"sync"
"time"
"golang.org/x/sys/windows/svc"
"golang.org/x/sys/windows/svc/debug"
"golang.org/x/sys/windows/svc/eventlog"
)
var elog debug.Log
type Service interface {
Schedule(ctx context.Context, wg *sync.WaitGroup, cancel context.CancelFunc) error
}
type ServiceWrapper struct {
service Service
serviceName string
serviceDisplayName string
serviceDescription string
useExePathAsWorkingDirectory bool
}
func GetServiceWrapper(service Service, servicName, serviceDisplayName, serviceDescription string, useExePathAsWorkingDirectory bool) (*ServiceWrapper, error) {
if useExePathAsWorkingDirectory {
if err := setExePathAsWorkingDirectory(); err != nil {
return nil, fmt.Errorf("when changing working directory: %s", err)
}
}
return &ServiceWrapper{
service: service,
serviceName: servicName,
serviceDisplayName: serviceDisplayName,
serviceDescription: serviceDescription,
useExePathAsWorkingDirectory: useExePathAsWorkingDirectory,
}, nil
}
func setExePathAsWorkingDirectory() error {
executablePath, err := os.Executable()
if err != nil {
return fmt.Errorf("when getting executable path: %s", err)
}
executableDir := filepath.Dir(executablePath)
if err := os.Chdir(executableDir); err != nil {
return fmt.Errorf("when changing to executable path: %s", err)
}
return nil
}
func (sw *ServiceWrapper) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown // | svc.AcceptPauseAndContinue
changes <- svc.Status{State: svc.StartPending}
ctx, cancel := context.WithCancel(context.Background())
wg := &sync.WaitGroup{}
if err := sw.service.Schedule(ctx, wg, cancel); err != nil {
elog.Error(1, fmt.Sprintf("When scheduling the service '%s': %s", sw.serviceName, err))
cancel()
wg.Wait()
errno = 1
return
}
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
loop:
for {
select {
case <-ctx.Done():
elog.Info(1, "The wrapped service cancelled the execution")
wg.Wait()
errno = 0
break loop
case c := <-r:
switch c.Cmd {
case svc.Interrogate:
changes <- c.CurrentStatus
// Testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4
time.Sleep(100 * time.Millisecond)
changes <- c.CurrentStatus
case svc.Stop, svc.Shutdown:
// golang.org/x/sys/windows/svc.TestExample is verifying this output.
testOutput := strings.Join(args, "-")
testOutput += fmt.Sprintf("-%d", c.Context)
elog.Info(1, testOutput)
cancel()
wg.Wait()
break loop
default:
elog.Error(1, fmt.Sprintf("unexpected control request #%d", c))
}
}
}
changes <- svc.Status{State: svc.StopPending}
return
}
func (sw *ServiceWrapper) RunService(isDebug bool) error {
var err error
if isDebug {
elog = debug.New(sw.serviceName)
} else {
elog, err = eventlog.Open(sw.serviceName)
if err != nil {
return fmt.Errorf("when opening the eventlog: %w", err)
}
}
defer elog.Close()
elog.Info(1, fmt.Sprintf("starting %s service", sw.serviceName))
run := svc.Run
if isDebug {
run = debug.Run
}
if err = run(sw.serviceName, sw); err != nil {
elog.Error(1, fmt.Sprintf("%s service failed: %v", sw.serviceName, err))
return err
}
elog.Info(1, fmt.Sprintf("%s service stopped", sw.serviceName))
return nil
}