@@ -9,10 +9,15 @@ import (
9
9
"errors"
10
10
"fmt"
11
11
"os/exec"
12
- "regexp "
12
+ "strconv "
13
13
"syscall"
14
+ "time"
14
15
"unicode/utf16"
15
16
"unsafe"
17
+
18
+ "golang.org/x/sys/windows/registry"
19
+ "golang.org/x/sys/windows/svc"
20
+ "golang.org/x/sys/windows/svc/mgr"
16
21
)
17
22
18
23
// windowsRecord - standard record (struct) for windows version of daemon package
@@ -37,58 +42,160 @@ func (windows *windowsRecord) Install(args ...string) (string, error) {
37
42
return installAction + failed , err
38
43
}
39
44
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
+ }
42
56
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 ... )
45
63
if err != nil {
46
- return installAction + failed , getWindowsError ( err )
64
+ return installAction + failed , err
47
65
}
66
+ defer s .Close ()
67
+
48
68
return installAction + " completed." , nil
49
69
}
50
70
51
71
// Remove the service
52
72
func (windows * windowsRecord ) Remove () (string , error ) {
53
73
removeAction := "Removing " + windows .description + ":"
54
- cmd := exec . Command ( "sc" , "delete" , windows . name , "confirm" )
55
- err := cmd . Run ()
74
+
75
+ m , err := mgr . Connect ()
56
76
if err != nil {
57
77
return removeAction + failed , getWindowsError (err )
58
78
}
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
+
59
90
return removeAction + " completed." , nil
60
91
}
61
92
62
93
// Start the service
63
94
func (windows * windowsRecord ) Start () (string , error ) {
64
95
startAction := "Starting " + windows .description + ":"
65
- cmd := exec . Command ( "sc" , "start" , windows . name )
66
- err := cmd . Run ()
96
+
97
+ m , err := mgr . Connect ()
67
98
if err != nil {
68
99
return startAction + failed , getWindowsError (err )
69
100
}
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
+
70
111
return startAction + " completed." , nil
71
112
}
72
113
73
114
// Stop the service
74
115
func (windows * windowsRecord ) Stop () (string , error ) {
75
116
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 )
78
124
if err != nil {
79
125
return stopAction + failed , getWindowsError (err )
80
126
}
127
+ defer s .Close ()
128
+ if err := stopAndWait (s ); err != nil {
129
+ return stopAction + failed , getWindowsError (err )
130
+ }
131
+
81
132
return stopAction + " completed." , nil
82
133
}
83
134
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
+
84
181
// Status - Get service status
85
182
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 )
88
189
if err != nil {
89
190
return "Getting status:" + failed , getWindowsError (err )
90
191
}
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
92
199
}
93
200
94
201
// Get executable path
@@ -123,9 +230,91 @@ func getWindowsError(inputError error) error {
123
230
}
124
231
125
232
// 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
+ }
129
318
130
- return service [ 2 ]
319
+ return runAction + " completed." , nil
131
320
}
0 commit comments