...
1
2
3
4
5
6
7
8 package svc
9
10 import (
11 "errors"
12 "sync"
13 "unsafe"
14
15 "golang.org/x/sys/windows"
16 )
17
18
19 type State uint32
20
21 const (
22 Stopped = State(windows.SERVICE_STOPPED)
23 StartPending = State(windows.SERVICE_START_PENDING)
24 StopPending = State(windows.SERVICE_STOP_PENDING)
25 Running = State(windows.SERVICE_RUNNING)
26 ContinuePending = State(windows.SERVICE_CONTINUE_PENDING)
27 PausePending = State(windows.SERVICE_PAUSE_PENDING)
28 Paused = State(windows.SERVICE_PAUSED)
29 )
30
31
32
33 type Cmd uint32
34
35 const (
36 Stop = Cmd(windows.SERVICE_CONTROL_STOP)
37 Pause = Cmd(windows.SERVICE_CONTROL_PAUSE)
38 Continue = Cmd(windows.SERVICE_CONTROL_CONTINUE)
39 Interrogate = Cmd(windows.SERVICE_CONTROL_INTERROGATE)
40 Shutdown = Cmd(windows.SERVICE_CONTROL_SHUTDOWN)
41 ParamChange = Cmd(windows.SERVICE_CONTROL_PARAMCHANGE)
42 NetBindAdd = Cmd(windows.SERVICE_CONTROL_NETBINDADD)
43 NetBindRemove = Cmd(windows.SERVICE_CONTROL_NETBINDREMOVE)
44 NetBindEnable = Cmd(windows.SERVICE_CONTROL_NETBINDENABLE)
45 NetBindDisable = Cmd(windows.SERVICE_CONTROL_NETBINDDISABLE)
46 DeviceEvent = Cmd(windows.SERVICE_CONTROL_DEVICEEVENT)
47 HardwareProfileChange = Cmd(windows.SERVICE_CONTROL_HARDWAREPROFILECHANGE)
48 PowerEvent = Cmd(windows.SERVICE_CONTROL_POWEREVENT)
49 SessionChange = Cmd(windows.SERVICE_CONTROL_SESSIONCHANGE)
50 PreShutdown = Cmd(windows.SERVICE_CONTROL_PRESHUTDOWN)
51 )
52
53
54
55 type Accepted uint32
56
57 const (
58 AcceptStop = Accepted(windows.SERVICE_ACCEPT_STOP)
59 AcceptShutdown = Accepted(windows.SERVICE_ACCEPT_SHUTDOWN)
60 AcceptPauseAndContinue = Accepted(windows.SERVICE_ACCEPT_PAUSE_CONTINUE)
61 AcceptParamChange = Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)
62 AcceptNetBindChange = Accepted(windows.SERVICE_ACCEPT_NETBINDCHANGE)
63 AcceptHardwareProfileChange = Accepted(windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE)
64 AcceptPowerEvent = Accepted(windows.SERVICE_ACCEPT_POWEREVENT)
65 AcceptSessionChange = Accepted(windows.SERVICE_ACCEPT_SESSIONCHANGE)
66 AcceptPreShutdown = Accepted(windows.SERVICE_ACCEPT_PRESHUTDOWN)
67 )
68
69
70 type ActivityStatus uint32
71
72 const (
73 Active = ActivityStatus(windows.SERVICE_ACTIVE)
74 Inactive = ActivityStatus(windows.SERVICE_INACTIVE)
75 AnyActivity = ActivityStatus(windows.SERVICE_STATE_ALL)
76 )
77
78
79 type Status struct {
80 State State
81 Accepts Accepted
82 CheckPoint uint32
83 WaitHint uint32
84 ProcessId uint32
85 Win32ExitCode uint32
86 ServiceSpecificExitCode uint32
87 }
88
89
90 type StartReason uint32
91
92 const (
93 StartReasonDemand = StartReason(windows.SERVICE_START_REASON_DEMAND)
94 StartReasonAuto = StartReason(windows.SERVICE_START_REASON_AUTO)
95 StartReasonTrigger = StartReason(windows.SERVICE_START_REASON_TRIGGER)
96 StartReasonRestartOnFailure = StartReason(windows.SERVICE_START_REASON_RESTART_ON_FAILURE)
97 StartReasonDelayedAuto = StartReason(windows.SERVICE_START_REASON_DELAYEDAUTO)
98 )
99
100
101 type ChangeRequest struct {
102 Cmd Cmd
103 EventType uint32
104 EventData uintptr
105 CurrentStatus Status
106 Context uintptr
107 }
108
109
110 type Handler interface {
111
112
113
114
115
116
117
118
119
120
121
122 Execute(args []string, r <-chan ChangeRequest, s chan<- Status) (svcSpecificEC bool, exitCode uint32)
123 }
124
125 type ctlEvent struct {
126 cmd Cmd
127 eventType uint32
128 eventData uintptr
129 context uintptr
130 errno uint32
131 }
132
133
134 type service struct {
135 name string
136 h windows.Handle
137 c chan ctlEvent
138 handler Handler
139 }
140
141 type exitCode struct {
142 isSvcSpecific bool
143 errno uint32
144 }
145
146 func (s *service) updateStatus(status *Status, ec *exitCode) error {
147 if s.h == 0 {
148 return errors.New("updateStatus with no service status handle")
149 }
150 var t windows.SERVICE_STATUS
151 t.ServiceType = windows.SERVICE_WIN32_OWN_PROCESS
152 t.CurrentState = uint32(status.State)
153 if status.Accepts&AcceptStop != 0 {
154 t.ControlsAccepted |= windows.SERVICE_ACCEPT_STOP
155 }
156 if status.Accepts&AcceptShutdown != 0 {
157 t.ControlsAccepted |= windows.SERVICE_ACCEPT_SHUTDOWN
158 }
159 if status.Accepts&AcceptPauseAndContinue != 0 {
160 t.ControlsAccepted |= windows.SERVICE_ACCEPT_PAUSE_CONTINUE
161 }
162 if status.Accepts&AcceptParamChange != 0 {
163 t.ControlsAccepted |= windows.SERVICE_ACCEPT_PARAMCHANGE
164 }
165 if status.Accepts&AcceptNetBindChange != 0 {
166 t.ControlsAccepted |= windows.SERVICE_ACCEPT_NETBINDCHANGE
167 }
168 if status.Accepts&AcceptHardwareProfileChange != 0 {
169 t.ControlsAccepted |= windows.SERVICE_ACCEPT_HARDWAREPROFILECHANGE
170 }
171 if status.Accepts&AcceptPowerEvent != 0 {
172 t.ControlsAccepted |= windows.SERVICE_ACCEPT_POWEREVENT
173 }
174 if status.Accepts&AcceptSessionChange != 0 {
175 t.ControlsAccepted |= windows.SERVICE_ACCEPT_SESSIONCHANGE
176 }
177 if status.Accepts&AcceptPreShutdown != 0 {
178 t.ControlsAccepted |= windows.SERVICE_ACCEPT_PRESHUTDOWN
179 }
180 if ec.errno == 0 {
181 t.Win32ExitCode = windows.NO_ERROR
182 t.ServiceSpecificExitCode = windows.NO_ERROR
183 } else if ec.isSvcSpecific {
184 t.Win32ExitCode = uint32(windows.ERROR_SERVICE_SPECIFIC_ERROR)
185 t.ServiceSpecificExitCode = ec.errno
186 } else {
187 t.Win32ExitCode = ec.errno
188 t.ServiceSpecificExitCode = windows.NO_ERROR
189 }
190 t.CheckPoint = status.CheckPoint
191 t.WaitHint = status.WaitHint
192 return windows.SetServiceStatus(s.h, &t)
193 }
194
195 var (
196 initCallbacks sync.Once
197 ctlHandlerCallback uintptr
198 serviceMainCallback uintptr
199 )
200
201 func ctlHandler(ctl, evtype, evdata, context uintptr) uintptr {
202 e := ctlEvent{cmd: Cmd(ctl), eventType: uint32(evtype), eventData: evdata, context: 123456}
203 theService.c <- e
204 return 0
205 }
206
207 var theService service
208
209
210
211 func serviceMain(argc uint32, argv **uint16) uintptr {
212 handle, err := windows.RegisterServiceCtrlHandlerEx(windows.StringToUTF16Ptr(theService.name), ctlHandlerCallback, 0)
213 if sysErr, ok := err.(windows.Errno); ok {
214 return uintptr(sysErr)
215 } else if err != nil {
216 return uintptr(windows.ERROR_UNKNOWN_EXCEPTION)
217 }
218 theService.h = handle
219 defer func() {
220 theService.h = 0
221 }()
222 args16 := unsafe.Slice(argv, int(argc))
223
224 args := make([]string, len(args16))
225 for i, a := range args16 {
226 args[i] = windows.UTF16PtrToString(a)
227 }
228
229 cmdsToHandler := make(chan ChangeRequest)
230 changesFromHandler := make(chan Status)
231 exitFromHandler := make(chan exitCode)
232
233 go func() {
234 ss, errno := theService.handler.Execute(args, cmdsToHandler, changesFromHandler)
235 exitFromHandler <- exitCode{ss, errno}
236 }()
237
238 ec := exitCode{isSvcSpecific: true, errno: 0}
239 outcr := ChangeRequest{
240 CurrentStatus: Status{State: Stopped},
241 }
242 var outch chan ChangeRequest
243 inch := theService.c
244 loop:
245 for {
246 select {
247 case r := <-inch:
248 if r.errno != 0 {
249 ec.errno = r.errno
250 break loop
251 }
252 inch = nil
253 outch = cmdsToHandler
254 outcr.Cmd = r.cmd
255 outcr.EventType = r.eventType
256 outcr.EventData = r.eventData
257 outcr.Context = r.context
258 case outch <- outcr:
259 inch = theService.c
260 outch = nil
261 case c := <-changesFromHandler:
262 err := theService.updateStatus(&c, &ec)
263 if err != nil {
264 ec.errno = uint32(windows.ERROR_EXCEPTION_IN_SERVICE)
265 if err2, ok := err.(windows.Errno); ok {
266 ec.errno = uint32(err2)
267 }
268 break loop
269 }
270 outcr.CurrentStatus = c
271 case ec = <-exitFromHandler:
272 break loop
273 }
274 }
275
276 theService.updateStatus(&Status{State: Stopped}, &ec)
277
278 return windows.NO_ERROR
279 }
280
281
282 func Run(name string, handler Handler) error {
283 initCallbacks.Do(func() {
284 ctlHandlerCallback = windows.NewCallback(ctlHandler)
285 serviceMainCallback = windows.NewCallback(serviceMain)
286 })
287 theService.name = name
288 theService.handler = handler
289 theService.c = make(chan ctlEvent)
290 t := []windows.SERVICE_TABLE_ENTRY{
291 {ServiceName: windows.StringToUTF16Ptr(theService.name), ServiceProc: serviceMainCallback},
292 {ServiceName: nil, ServiceProc: 0},
293 }
294 return windows.StartServiceCtrlDispatcher(&t[0])
295 }
296
297
298
299 func StatusHandle() windows.Handle {
300 return theService.h
301 }
302
303
304
305
306 func DynamicStartReason() (StartReason, error) {
307 var allocReason *uint32
308 err := windows.QueryServiceDynamicInformation(theService.h, windows.SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON, unsafe.Pointer(&allocReason))
309 if err != nil {
310 return 0, err
311 }
312 reason := StartReason(*allocReason)
313 windows.LocalFree(windows.Handle(unsafe.Pointer(allocReason)))
314 return reason, nil
315 }
316
View as plain text