...
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 s := (*service)(unsafe.Pointer(context))
203 e := ctlEvent{cmd: Cmd(ctl), eventType: uint32(evtype), eventData: evdata, context: 123456}
204 s.c <- e
205 return 0
206 }
207
208 var theService service
209
210
211
212 func serviceMain(argc uint32, argv **uint16) uintptr {
213 handle, err := windows.RegisterServiceCtrlHandlerEx(windows.StringToUTF16Ptr(theService.name), ctlHandlerCallback, uintptr(unsafe.Pointer(&theService)))
214 if sysErr, ok := err.(windows.Errno); ok {
215 return uintptr(sysErr)
216 } else if err != nil {
217 return uintptr(windows.ERROR_UNKNOWN_EXCEPTION)
218 }
219 theService.h = handle
220 defer func() {
221 theService.h = 0
222 }()
223 args16 := unsafe.Slice(argv, int(argc))
224
225 args := make([]string, len(args16))
226 for i, a := range args16 {
227 args[i] = windows.UTF16PtrToString(a)
228 }
229
230 cmdsToHandler := make(chan ChangeRequest)
231 changesFromHandler := make(chan Status)
232 exitFromHandler := make(chan exitCode)
233
234 go func() {
235 ss, errno := theService.handler.Execute(args, cmdsToHandler, changesFromHandler)
236 exitFromHandler <- exitCode{ss, errno}
237 }()
238
239 ec := exitCode{isSvcSpecific: true, errno: 0}
240 outcr := ChangeRequest{
241 CurrentStatus: Status{State: Stopped},
242 }
243 var outch chan ChangeRequest
244 inch := theService.c
245 loop:
246 for {
247 select {
248 case r := <-inch:
249 if r.errno != 0 {
250 ec.errno = r.errno
251 break loop
252 }
253 inch = nil
254 outch = cmdsToHandler
255 outcr.Cmd = r.cmd
256 outcr.EventType = r.eventType
257 outcr.EventData = r.eventData
258 outcr.Context = r.context
259 case outch <- outcr:
260 inch = theService.c
261 outch = nil
262 case c := <-changesFromHandler:
263 err := theService.updateStatus(&c, &ec)
264 if err != nil {
265 ec.errno = uint32(windows.ERROR_EXCEPTION_IN_SERVICE)
266 if err2, ok := err.(windows.Errno); ok {
267 ec.errno = uint32(err2)
268 }
269 break loop
270 }
271 outcr.CurrentStatus = c
272 case ec = <-exitFromHandler:
273 break loop
274 }
275 }
276
277 theService.updateStatus(&Status{State: Stopped}, &ec)
278
279 return windows.NO_ERROR
280 }
281
282
283 func Run(name string, handler Handler) error {
284 initCallbacks.Do(func() {
285 ctlHandlerCallback = windows.NewCallback(ctlHandler)
286 serviceMainCallback = windows.NewCallback(serviceMain)
287 })
288 theService.name = name
289 theService.handler = handler
290 theService.c = make(chan ctlEvent)
291 t := []windows.SERVICE_TABLE_ENTRY{
292 {ServiceName: windows.StringToUTF16Ptr(theService.name), ServiceProc: serviceMainCallback},
293 {ServiceName: nil, ServiceProc: 0},
294 }
295 return windows.StartServiceCtrlDispatcher(&t[0])
296 }
297
298
299
300 func StatusHandle() windows.Handle {
301 return theService.h
302 }
303
304
305
306
307 func DynamicStartReason() (StartReason, error) {
308 var allocReason *uint32
309 err := windows.QueryServiceDynamicInformation(theService.h, windows.SERVICE_DYNAMIC_INFORMATION_LEVEL_START_REASON, unsafe.Pointer(&allocReason))
310 if err != nil {
311 return 0, err
312 }
313 reason := StartReason(*allocReason)
314 windows.LocalFree(windows.Handle(unsafe.Pointer(allocReason)))
315 return reason, nil
316 }
317
View as plain text