win32-srv 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e65966e29128d59cbb242c1549fa1ba64444eae9bf0ed37cc85b850f6189657f
4
+ data.tar.gz: 515609f752aeb02e5d4c1702b1b7a0fb86aae5c52497c62a8fde24a353a183de
5
+ SHA512:
6
+ metadata.gz: 1d696d6379adf61e28b82f700d39418df704ee8f0dfe0922e39738d781ee86e7f2740ffaf0f70a814d4eb3aee2706858b43ffa8635087b88f0f4be809f642771
7
+ data.tar.gz: aae9908fb03e8f0bc5a1f2cfaeb57e60e8a4f4387c8ba9b50f547de6eec3582a84fa008e27dc8e7fee1c18ef5d9186057da1d343e5b829b6e3965bacf3433be6
@@ -0,0 +1,366 @@
1
+ require_relative "windows/constants"
2
+ require_relative "windows/structs"
3
+ require_relative "windows/functions"
4
+ require_relative "windows/version"
5
+
6
+ # The Win32 module serves as a namespace only.
7
+ module Win32
8
+
9
+ # The Daemon class
10
+ class Daemon
11
+ include Windows::ServiceConstants
12
+ include Windows::ServiceStructs
13
+ include Windows::ServiceFunctions
14
+
15
+ extend Windows::ServiceStructs
16
+ extend Windows::ServiceFunctions
17
+
18
+ # The version of this library
19
+ VERSION = Win32::Service::VERSION
20
+
21
+ # Service is not running
22
+ STOPPED = SERVICE_STOPPED
23
+
24
+ # Service has received a start signal but is not yet running
25
+ START_PENDING = SERVICE_START_PENDING
26
+
27
+ # Service has received a stop signal but is not yet stopped
28
+ STOP_PENDING = SERVICE_STOP_PENDING
29
+
30
+ # Service is running
31
+ RUNNING = SERVICE_RUNNING
32
+
33
+ # Service has received a signal to resume but is not yet running
34
+ CONTINUE_PENDING = SERVICE_CONTINUE_PENDING
35
+
36
+ # Service has received a signal to pause but is not yet paused
37
+ PAUSE_PENDING = SERVICE_PAUSE_PENDING
38
+
39
+ # Service is paused
40
+ PAUSED = SERVICE_PAUSED
41
+
42
+ # Service controls
43
+
44
+ # Notifies service that it should stop
45
+ CONTROL_STOP = SERVICE_CONTROL_STOP
46
+
47
+ # Notifies service that it should pause
48
+ CONTROL_PAUSE = SERVICE_CONTROL_PAUSE
49
+
50
+ # Notifies service that it should resume
51
+ CONTROL_CONTINUE = SERVICE_CONTROL_CONTINUE
52
+
53
+ # Notifies service that it should return its current status information
54
+ CONTROL_INTERROGATE = SERVICE_CONTROL_INTERROGATE
55
+
56
+ # Notifies a service that its parameters have changed
57
+ CONTROL_PARAMCHANGE = SERVICE_CONTROL_PARAMCHANGE
58
+
59
+ # Notifies a service that there is a new component for binding
60
+ CONTROL_NETBINDADD = SERVICE_CONTROL_NETBINDADD
61
+
62
+ # Notifies a service that a component for binding has been removed
63
+ CONTROL_NETBINDREMOVE = SERVICE_CONTROL_NETBINDREMOVE
64
+
65
+ # Notifies a service that a component for binding has been enabled
66
+ CONTROL_NETBINDENABLE = SERVICE_CONTROL_NETBINDENABLE
67
+
68
+ # Notifies a service that a component for binding has been disabled
69
+ CONTROL_NETBINDDISABLE = SERVICE_CONTROL_NETBINDDISABLE
70
+
71
+ IDLE = 0
72
+
73
+ # Wraps SetServiceStatus.
74
+ SetTheServiceStatus = Proc.new do |dwCurrentState, dwWin32ExitCode, dwCheckPoint, dwWaitHint|
75
+ ss = SERVICE_STATUS.new # Current status of the service.
76
+
77
+ # Disable control requests until the service is started.
78
+ if dwCurrentState == SERVICE_START_PENDING
79
+ ss[:dwControlsAccepted] = 0
80
+ else
81
+ ss[:dwControlsAccepted] =
82
+ SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN |
83
+ SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_PARAMCHANGE
84
+ end
85
+
86
+ # Initialize ss structure.
87
+ ss[:dwServiceType] = SERVICE_WIN32_OWN_PROCESS
88
+ ss[:dwServiceSpecificExitCode] = 0
89
+ ss[:dwCurrentState] = dwCurrentState
90
+ ss[:dwWin32ExitCode] = dwWin32ExitCode
91
+ ss[:dwCheckPoint] = dwCheckPoint
92
+ ss[:dwWaitHint] = dwWaitHint
93
+
94
+ @@dwServiceState = dwCurrentState
95
+
96
+ # Send status of the service to the Service Controller.
97
+ unless SetServiceStatus(@@ssh, ss)
98
+ SetEvent(@@hStopEvent)
99
+ end
100
+ end
101
+
102
+ ERROR_CALL_NOT_IMPLEMENTED = 0x78
103
+
104
+ # Handles control signals from the service control manager.
105
+ Service_Ctrl_ex = Proc.new do |dwCtrlCode, dwEventType, lpEventData, lpContext|
106
+ @@waiting_control_code = dwCtrlCode
107
+ return_value = NO_ERROR
108
+
109
+ begin
110
+ dwState = SERVICE_RUNNING
111
+
112
+ case dwCtrlCode
113
+ when SERVICE_CONTROL_STOP
114
+ dwState = SERVICE_STOP_PENDING
115
+ when SERVICE_CONTROL_SHUTDOWN
116
+ dwState = SERVICE_STOP_PENDING
117
+ when SERVICE_CONTROL_PAUSE
118
+ dwState = SERVICE_PAUSED
119
+ when SERVICE_CONTROL_CONTINUE
120
+ dwState = SERVICE_RUNNING
121
+ # else
122
+ # TODO: Handle other control codes? Retain the current state?
123
+ end
124
+
125
+ # Set the status of the service except on interrogation.
126
+ unless dwCtrlCode == SERVICE_CONTROL_INTERROGATE
127
+ SetTheServiceStatus.call(dwState, NO_ERROR, 0, 0)
128
+ end
129
+
130
+ # Tell service_main thread to stop.
131
+ if dwCtrlCode == SERVICE_CONTROL_STOP || dwCtrlCode == SERVICE_CONTROL_SHUTDOWN
132
+ if SetEvent(@@hStopEvent) == 0
133
+ SetTheServiceStatus.call(SERVICE_STOPPED, FFI.errno, 0, 0)
134
+ end
135
+ end
136
+ rescue
137
+ return_value = ERROR_CALL_NOT_IMPLEMENTED
138
+ end
139
+
140
+ return_value
141
+ end
142
+
143
+ # Called by the service control manager after the call to StartServiceCtrlDispatcher.
144
+ Service_Main = FFI::Function.new(:void, %i{ulong pointer}, blocking: false) do |dwArgc, lpszArgv|
145
+ begin
146
+ # Obtain the name of the service.
147
+ if lpszArgv.address != 0
148
+ argv = lpszArgv.get_array_of_string(0, dwArgc)
149
+ lpszServiceName = argv[0]
150
+ else
151
+ lpszServiceName = ""
152
+ end
153
+
154
+ # Args passed to Service.start
155
+ if dwArgc > 1
156
+ @@Argv = argv[1..-1]
157
+ else
158
+ @@Argv = nil
159
+ end
160
+
161
+ # Register the service ctrl handler.
162
+ @@ssh = RegisterServiceCtrlHandlerEx(
163
+ lpszServiceName,
164
+ Service_Ctrl_ex,
165
+ nil
166
+ )
167
+
168
+ # No service to stop, no service handle to notify, nothing to do but exit.
169
+ return if @@ssh == 0
170
+
171
+ # The service has started.
172
+ SetTheServiceStatus.call(SERVICE_RUNNING, NO_ERROR, 0, 0)
173
+
174
+ SetEvent(@@hStartEvent)
175
+
176
+ # Main loop for the service.
177
+ while WaitForSingleObject(@@hStopEvent, 1000) != WAIT_OBJECT_0
178
+ end
179
+
180
+ # Main loop for the service.
181
+ while WaitForSingleObject(@@hStopCompletedEvent, 1000) != WAIT_OBJECT_0
182
+ end
183
+ ensure
184
+ # Stop the service.
185
+ SetTheServiceStatus.call(SERVICE_STOPPED, NO_ERROR, 0, 0)
186
+ end
187
+ end
188
+
189
+ ThreadProc = FFI::Function.new(:ulong, [:pointer]) do |lpParameter|
190
+ ste = FFI::MemoryPointer.new(SERVICE_TABLE_ENTRY, 2)
191
+
192
+ s = SERVICE_TABLE_ENTRY.new(ste[0])
193
+ s[:lpServiceName] = FFI::MemoryPointer.from_string("")
194
+ s[:lpServiceProc] = lpParameter
195
+
196
+ s = SERVICE_TABLE_ENTRY.new(ste[1])
197
+ s[:lpServiceName] = nil
198
+ s[:lpServiceProc] = nil
199
+
200
+ # No service to step, no service handle, no ruby exceptions, just terminate the thread..
201
+ unless StartServiceCtrlDispatcher(ste)
202
+ return 1
203
+ end
204
+
205
+ return 0
206
+ end
207
+
208
+ # This is a shortcut for Daemon.new + Daemon#mainloop.
209
+ #
210
+ def self.mainloop
211
+ new.mainloop
212
+ end
213
+
214
+ # This is the method that actually puts your code into a loop and allows it
215
+ # to run as a service. The code that is actually run while in the mainloop
216
+ # is what you defined in your own Daemon#service_main method.
217
+ #
218
+ def mainloop
219
+ @@waiting_control_code = IDLE_CONTROL_CODE
220
+ @@dwServiceState = 0
221
+
222
+ # Redirect STDIN, STDOUT and STDERR to the NUL device if they're still
223
+ # associated with a tty. This helps newbs avoid Errno::EBADF errors.
224
+ STDIN.reopen("NUL") if STDIN.isatty
225
+ STDOUT.reopen("NUL") if STDOUT.isatty
226
+ STDERR.reopen("NUL") if STDERR.isatty
227
+
228
+ # Calling init here so that init failures never even tries to start the
229
+ # service. Of course that means that init methods must be very quick
230
+ # because the SCM will be receiving no START_PENDING messages while
231
+ # init's running.
232
+ #
233
+ # TODO: Fix?
234
+ service_init if respond_to?("service_init")
235
+
236
+ # Create the event to signal the service to start.
237
+ @@hStartEvent = CreateEvent(nil, 1, 0, nil)
238
+
239
+ if @@hStartEvent == 0
240
+ raise SystemCallError.new("CreateEvent", FFI.errno)
241
+ end
242
+
243
+ # Create the event to signal the service to stop.
244
+ @@hStopEvent = CreateEvent(nil, 1, 0, nil)
245
+
246
+ if @@hStopEvent == 0
247
+ raise SystemCallError.new("CreateEvent", FFI.errno)
248
+ end
249
+
250
+ # Create the event to signal the service that stop has completed
251
+ @@hStopCompletedEvent = CreateEvent(nil, 1, 0, nil)
252
+
253
+ if @@hStopCompletedEvent == 0
254
+ raise SystemCallError.new("CreateEvent", FFI.errno)
255
+ end
256
+
257
+ hThread = CreateThread(nil, 0, ThreadProc, Service_Main, 0, nil)
258
+
259
+ if hThread == 0
260
+ raise SystemCallError.new("CreateThread", FFI.errno)
261
+ end
262
+
263
+ events = FFI::MemoryPointer.new(:pointer, 2)
264
+ events.put_pointer(0, FFI::Pointer.new(hThread))
265
+ events.put_pointer(FFI::Pointer.size, FFI::Pointer.new(@@hStartEvent))
266
+
267
+ while (index = WaitForMultipleObjects(2, events, 0, 1000)) == WAIT_TIMEOUT
268
+ end
269
+
270
+ if index == WAIT_FAILED
271
+ raise SystemCallError.new("WaitForMultipleObjects", FFI.errno)
272
+ end
273
+
274
+ # The thread exited, so the show is off.
275
+ if index == WAIT_OBJECT_0
276
+ raise "Service_Main thread exited abnormally"
277
+ end
278
+
279
+ thr = Thread.new do
280
+ begin
281
+ while WaitForSingleObject(@@hStopEvent, 1000) == WAIT_TIMEOUT
282
+ # Check to see if anything interesting has been signaled
283
+ case @@waiting_control_code
284
+ when SERVICE_CONTROL_PAUSE
285
+ service_pause if respond_to?("service_pause")
286
+ when SERVICE_CONTROL_CONTINUE
287
+ service_resume if respond_to?("service_resume")
288
+ when SERVICE_CONTROL_INTERROGATE
289
+ service_interrogate if respond_to?("service_interrogate")
290
+ when SERVICE_CONTROL_SHUTDOWN
291
+ service_shutdown if respond_to?("service_shutdown")
292
+ when SERVICE_CONTROL_PARAMCHANGE
293
+ service_paramchange if respond_to?("service_paramchange")
294
+ when SERVICE_CONTROL_NETBINDADD
295
+ service_netbindadd if respond_to?("service_netbindadd")
296
+ when SERVICE_CONTROL_NETBINDREMOVE
297
+ service_netbindremove if respond_to?("service_netbindremove")
298
+ when SERVICE_CONTROL_NETBINDENABLE
299
+ service_netbindenable if respond_to?("service_netbindenable")
300
+ when SERVICE_CONTROL_NETBINDDISABLE
301
+ service_netbinddisable if respond_to?("service_netbinddisable")
302
+ end
303
+
304
+ # handle user defined control codes
305
+ if @@waiting_control_code >= 128 && @@waiting_control_code <= 255
306
+ if respond_to?("service_user_defined_control")
307
+ service_user_defined_control(@@waiting_control_code)
308
+ end
309
+ end
310
+
311
+ @@waiting_control_code = IDLE_CONTROL_CODE
312
+ end
313
+
314
+ service_stop if respond_to?("service_stop")
315
+ ensure
316
+ SetEvent(@@hStopCompletedEvent)
317
+ end
318
+ end
319
+
320
+ if respond_to?("service_main")
321
+ service_main(*@@Argv)
322
+ end
323
+
324
+ thr.join
325
+ end
326
+
327
+ # Returns the state of the service (as an constant integer) which can be any
328
+ # of the service status constants, e.g. RUNNING, PAUSED, etc.
329
+ #
330
+ # This method is typically used within your service_main method to setup the
331
+ # loop. For example:
332
+ #
333
+ # class MyDaemon < Daemon
334
+ # def service_main
335
+ # while state == RUNNING || state == PAUSED || state == IDLE
336
+ # # Your main loop here
337
+ # end
338
+ # end
339
+ # end
340
+ #
341
+ # See the Daemon#running? method for an abstraction of the above code.
342
+ #
343
+ def state
344
+ @@dwServiceState
345
+ end
346
+
347
+ #
348
+ # Returns whether or not the service is in a running state, i.e. the service
349
+ # status is either RUNNING, PAUSED or IDLE.
350
+ #
351
+ # This is typically used within your service_main method to setup the main
352
+ # loop. For example:
353
+ #
354
+ # class MyDaemon < Daemon
355
+ # def service_main
356
+ # while running?
357
+ # # Your main loop here
358
+ # end
359
+ # end
360
+ # end
361
+ #
362
+ def running?
363
+ [SERVICE_RUNNING, SERVICE_PAUSED, 0].include?(@@dwServiceState)
364
+ end
365
+ end # Daemon
366
+ end # Win32