Skip to content

Commit aa76b00

Browse files
committed
Merge branch 'release/0.11.0'
2 parents fbd475b + cc5cf79 commit aa76b00

8 files changed

+258
-20
lines changed

README.md

+1
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,7 @@ See `examples/cron/cron_job.go`
199199
- [Maximus](https://github.com/maximus12793)
200200
- [AlgorathDev](https://github.com/AlgorathDev)
201201
- [Alexis Camilleri](https://github.com/krysennn)
202+
- [neverland4u](https://github.com/neverland4u)
202203

203204
All the contributors are welcome. If you would like to be the contributor please accept some rules.
204205

daemon.go

+14-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// license that can be found in the LICENSE file.
44

55
/*
6-
Package daemon 0.10.4 for use with Go (golang) services.
6+
Package daemon 0.11.0 for use with Go (golang) services.
77
88
Package daemon provides primitives for daemonization of golang services.
99
This package is not provide implementation of user daemon,
@@ -174,6 +174,19 @@ type Daemon interface {
174174

175175
// Status - check the service status
176176
Status() (string, error)
177+
178+
// Run - run executable service
179+
Run(e Executable) (string, error)
180+
}
181+
182+
// Executable interface defines controlling methods of executable service
183+
type Executable interface {
184+
// Start - non-blocking start service
185+
Start()
186+
// Stop - non-blocking stop service
187+
Stop()
188+
// Run - blocking run service
189+
Run()
177190
}
178191

179192
// New - Create a new daemon

daemon_darwin.go

+7
Original file line numberDiff line numberDiff line change
@@ -186,6 +186,13 @@ func (darwin *darwinRecord) Status() (string, error) {
186186
return statusAction, nil
187187
}
188188

189+
// Run - Run service
190+
func (darwin *darwinRecord) Run(e Executable) (string, error) {
191+
runAction := "Running " + darwin.description + ":"
192+
e.Run()
193+
return runAction + " completed.", nil
194+
}
195+
189196
var propertyList = `<?xml version="1.0" encoding="UTF-8"?>
190197
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
191198
<plist version="1.0">

daemon_freebsd.go

+7
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,13 @@ func (bsd *bsdRecord) Status() (string, error) {
227227
return statusAction, nil
228228
}
229229

230+
// Run - Run service
231+
func (bsd *bsdRecord) Run(e Executable) (string, error) {
232+
runAction := "Running " + bsd.description + ":"
233+
e.Run()
234+
return runAction + " completed.", nil
235+
}
236+
230237
var bsdConfig = `#!/bin/sh
231238
#
232239
# PROVIDE: {{.Name}}

daemon_linux_systemd.go

+7
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,13 @@ func (linux *systemDRecord) Status() (string, error) {
192192
return statusAction, nil
193193
}
194194

195+
// Run - Run service
196+
func (linux *systemDRecord) Run(e Executable) (string, error) {
197+
runAction := "Running " + linux.description + ":"
198+
e.Run()
199+
return runAction + " completed.", nil
200+
}
201+
195202
var systemDConfig = `[Unit]
196203
Description={{.Description}}
197204
Requires={{.Dependencies}}

daemon_linux_systemv.go

+7
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,13 @@ func (linux *systemVRecord) Status() (string, error) {
200200
return statusAction, nil
201201
}
202202

203+
// Run - Run service
204+
func (linux *systemVRecord) Run(e Executable) (string, error) {
205+
runAction := "Running " + linux.description + ":"
206+
e.Run()
207+
return runAction + " completed.", nil
208+
}
209+
203210
var systemVConfig = `#! /bin/sh
204211
#
205212
# /etc/rc.d/init.d/{{.Name}}

daemon_linux_upstart.go

+7
Original file line numberDiff line numberDiff line change
@@ -178,6 +178,13 @@ func (linux *upstartRecord) Status() (string, error) {
178178
return statusAction, nil
179179
}
180180

181+
// Run - Run service
182+
func (linux *upstartRecord) Run(e Executable) (string, error) {
183+
runAction := "Running " + linux.description + ":"
184+
e.Run()
185+
return runAction + " completed.", nil
186+
}
187+
181188
var upstatConfig = `# {{.Name}} {{.Description}}
182189
183190
description "{{.Description}}"

daemon_windows.go

+208-19
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,15 @@ import (
99
"errors"
1010
"fmt"
1111
"os/exec"
12-
"regexp"
12+
"strconv"
1313
"syscall"
14+
"time"
1415
"unicode/utf16"
1516
"unsafe"
17+
18+
"golang.org/x/sys/windows/registry"
19+
"golang.org/x/sys/windows/svc"
20+
"golang.org/x/sys/windows/svc/mgr"
1621
)
1722

1823
// windowsRecord - standard record (struct) for windows version of daemon package
@@ -37,58 +42,160 @@ func (windows *windowsRecord) Install(args ...string) (string, error) {
3742
return installAction + failed, err
3843
}
3944

40-
cmdArgs := []string{"create", windows.name, "start=auto", "binPath=" + execp}
41-
cmdArgs = append(cmdArgs, args...)
45+
m, err := mgr.Connect()
46+
if err != nil {
47+
return installAction + failed, err
48+
}
49+
defer m.Disconnect()
50+
51+
s, err := m.OpenService(windows.name)
52+
if err == nil {
53+
s.Close()
54+
return installAction + failed, err
55+
}
4256

43-
cmd := exec.Command("sc", cmdArgs...)
44-
_, err = cmd.Output()
57+
s, err = m.CreateService(windows.name, execp, mgr.Config{
58+
DisplayName: windows.name,
59+
Description: windows.description,
60+
StartType: mgr.StartAutomatic,
61+
Dependencies: windows.dependencies,
62+
}, args...)
4563
if err != nil {
46-
return installAction + failed, getWindowsError(err)
64+
return installAction + failed, err
4765
}
66+
defer s.Close()
67+
4868
return installAction + " completed.", nil
4969
}
5070

5171
// Remove the service
5272
func (windows *windowsRecord) Remove() (string, error) {
5373
removeAction := "Removing " + windows.description + ":"
54-
cmd := exec.Command("sc", "delete", windows.name, "confirm")
55-
err := cmd.Run()
74+
75+
m, err := mgr.Connect()
5676
if err != nil {
5777
return removeAction + failed, getWindowsError(err)
5878
}
79+
defer m.Disconnect()
80+
s, err := m.OpenService(windows.name)
81+
if err != nil {
82+
return removeAction + failed, getWindowsError(err)
83+
}
84+
defer s.Close()
85+
err = s.Delete()
86+
if err != nil {
87+
return removeAction + failed, getWindowsError(err)
88+
}
89+
5990
return removeAction + " completed.", nil
6091
}
6192

6293
// Start the service
6394
func (windows *windowsRecord) Start() (string, error) {
6495
startAction := "Starting " + windows.description + ":"
65-
cmd := exec.Command("sc", "start", windows.name)
66-
err := cmd.Run()
96+
97+
m, err := mgr.Connect()
6798
if err != nil {
6899
return startAction + failed, getWindowsError(err)
69100
}
101+
defer m.Disconnect()
102+
s, err := m.OpenService(windows.name)
103+
if err != nil {
104+
return startAction + failed, getWindowsError(err)
105+
}
106+
defer s.Close()
107+
if err = s.Start(); err != nil {
108+
return startAction + failed, getWindowsError(err)
109+
}
110+
70111
return startAction + " completed.", nil
71112
}
72113

73114
// Stop the service
74115
func (windows *windowsRecord) Stop() (string, error) {
75116
stopAction := "Stopping " + windows.description + ":"
76-
cmd := exec.Command("sc", "stop", windows.name)
77-
err := cmd.Run()
117+
118+
m, err := mgr.Connect()
119+
if err != nil {
120+
return stopAction + failed, getWindowsError(err)
121+
}
122+
defer m.Disconnect()
123+
s, err := m.OpenService(windows.name)
78124
if err != nil {
79125
return stopAction + failed, getWindowsError(err)
80126
}
127+
defer s.Close()
128+
if err := stopAndWait(s); err != nil {
129+
return stopAction + failed, getWindowsError(err)
130+
}
131+
81132
return stopAction + " completed.", nil
82133
}
83134

135+
func stopAndWait(s *mgr.Service) error {
136+
// First stop the service. Then wait for the service to
137+
// actually stop before starting it.
138+
status, err := s.Control(svc.Stop)
139+
if err != nil {
140+
return err
141+
}
142+
143+
timeDuration := time.Millisecond * 50
144+
145+
timeout := time.After(getStopTimeout() + (timeDuration * 2))
146+
tick := time.NewTicker(timeDuration)
147+
defer tick.Stop()
148+
149+
for status.State != svc.Stopped {
150+
select {
151+
case <-tick.C:
152+
status, err = s.Query()
153+
if err != nil {
154+
return err
155+
}
156+
case <-timeout:
157+
break
158+
}
159+
}
160+
return nil
161+
}
162+
163+
func getStopTimeout() time.Duration {
164+
// For default and paths see https://support.microsoft.com/en-us/kb/146092
165+
defaultTimeout := time.Millisecond * 20000
166+
key, err := registry.OpenKey(registry.LOCAL_MACHINE, `SYSTEM\CurrentControlSet\Control`, registry.READ)
167+
if err != nil {
168+
return defaultTimeout
169+
}
170+
sv, _, err := key.GetStringValue("WaitToKillServiceTimeout")
171+
if err != nil {
172+
return defaultTimeout
173+
}
174+
v, err := strconv.Atoi(sv)
175+
if err != nil {
176+
return defaultTimeout
177+
}
178+
return time.Millisecond * time.Duration(v)
179+
}
180+
84181
// Status - Get service status
85182
func (windows *windowsRecord) Status() (string, error) {
86-
cmd := exec.Command("sc", "query", windows.name)
87-
out, err := cmd.Output()
183+
m, err := mgr.Connect()
184+
if err != nil {
185+
return "Getting status:" + failed, getWindowsError(err)
186+
}
187+
defer m.Disconnect()
188+
s, err := m.OpenService(windows.name)
88189
if err != nil {
89190
return "Getting status:" + failed, getWindowsError(err)
90191
}
91-
return "Status: " + "SERVICE_" + getWindowsServiceState(out), nil
192+
defer s.Close()
193+
status, err := s.Query()
194+
if err != nil {
195+
return "Getting status:" + failed, getWindowsError(err)
196+
}
197+
198+
return "Status: " + getWindowsServiceStateFromUint32(status.State), nil
92199
}
93200

94201
// Get executable path
@@ -123,9 +230,91 @@ func getWindowsError(inputError error) error {
123230
}
124231

125232
// Get windows service state
126-
func getWindowsServiceState(out []byte) string {
127-
regex := regexp.MustCompile("STATE.*: (?P<state_code>[0-9]) (?P<state>.*) ")
128-
service := regex.FindAllStringSubmatch(string(out), -1)[0]
233+
func getWindowsServiceStateFromUint32(state svc.State) string {
234+
switch state {
235+
case svc.Stopped:
236+
return "SERVICE_STOPPED"
237+
case svc.StartPending:
238+
return "SERVICE_START_PENDING"
239+
case svc.StopPending:
240+
return "SERVICE_STOP_PENDING"
241+
case svc.Running:
242+
return "SERVICE_RUNNING"
243+
case svc.ContinuePending:
244+
return "SERVICE_CONTINUE_PENDING"
245+
case svc.PausePending:
246+
return "SERVICE_PAUSE_PENDING"
247+
case svc.Paused:
248+
return "SERVICE_PAUSED"
249+
}
250+
return "SERVICE_UNKNOWN"
251+
}
252+
253+
type serviceHandler struct {
254+
executable Executable
255+
}
256+
257+
func (sh *serviceHandler) Execute(args []string, r <-chan svc.ChangeRequest, changes chan<- svc.Status) (ssec bool, errno uint32) {
258+
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.AcceptPauseAndContinue
259+
changes <- svc.Status{State: svc.StartPending}
260+
261+
fasttick := time.Tick(500 * time.Millisecond)
262+
slowtick := time.Tick(2 * time.Second)
263+
tick := fasttick
264+
265+
sh.executable.Start()
266+
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
267+
268+
loop:
269+
for {
270+
select {
271+
case <-tick:
272+
break
273+
case c := <-r:
274+
switch c.Cmd {
275+
case svc.Interrogate:
276+
changes <- c.CurrentStatus
277+
// Testing deadlock from https://code.google.com/p/winsvc/issues/detail?id=4
278+
time.Sleep(100 * time.Millisecond)
279+
changes <- c.CurrentStatus
280+
case svc.Stop, svc.Shutdown:
281+
changes <- svc.Status{State: svc.StopPending}
282+
sh.executable.Stop()
283+
break loop
284+
case svc.Pause:
285+
changes <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted}
286+
tick = slowtick
287+
case svc.Continue:
288+
changes <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
289+
tick = fasttick
290+
default:
291+
continue loop
292+
}
293+
}
294+
}
295+
return
296+
}
297+
298+
func (windows *windowsRecord) Run(e Executable) (string, error) {
299+
runAction := "Running " + windows.description + ":"
300+
301+
interactive, err := svc.IsAnInteractiveSession()
302+
if err != nil {
303+
return runAction + failed, getWindowsError(err)
304+
}
305+
if !interactive {
306+
// service called from windows service manager
307+
// use API provided by golang.org/x/sys/windows
308+
err = svc.Run(windows.name, &serviceHandler{
309+
executable: e,
310+
})
311+
if err != nil {
312+
return runAction + failed, getWindowsError(err)
313+
}
314+
} else {
315+
// otherwise, service should be called from terminal session
316+
e.Run()
317+
}
129318

130-
return service[2]
319+
return runAction + " completed.", nil
131320
}

0 commit comments

Comments
 (0)