win32-service 0.7.1 → 0.7.2
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.
- data/CHANGES +7 -0
- data/README +4 -7
- data/Rakefile +73 -33
- data/doc/daemon.txt +79 -79
- data/doc/service.txt +230 -231
- data/examples/demo_daemon.rb +82 -76
- data/examples/demo_services.rb +12 -5
- data/ext/extconf.rb +4 -0
- data/ext/win32/daemon.c +359 -358
- data/lib/win32/service.rb +5 -2
- data/test/test_win32_daemon.rb +11 -8
- data/test/test_win32_service.rb +3 -3
- data/win32-service.gemspec +2 -3
- metadata +40 -16
data/examples/demo_daemon.rb
CHANGED
@@ -1,89 +1,95 @@
|
|
1
|
-
LOG_FILE = 'C:\\
|
1
|
+
LOG_FILE = 'C:\\win32_daemon_test.log'
|
2
2
|
|
3
3
|
begin
|
4
|
-
|
5
|
-
|
4
|
+
require 'rubygems'
|
5
|
+
require 'win32/daemon'
|
6
|
+
include Win32
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
8
|
+
class DemoDaemon < Daemon
|
9
|
+
# This method fires off before the +service_main+ mainloop is entered.
|
10
|
+
# Any pre-setup code you need to run before your service's mainloop
|
11
|
+
# starts should be put here. Otherwise the service might fail with a
|
12
|
+
# timeout error when you try to start it.
|
13
|
+
#
|
14
|
+
def service_init
|
15
|
+
10.times{ |i|
|
16
|
+
File.open(LOG_FILE , 'a'){ |f| f.puts("#{i}") }
|
17
|
+
sleep 1
|
18
|
+
}
|
19
|
+
end
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
21
|
+
# This is the daemon's mainloop. In other words, whatever runs here
|
22
|
+
# is the code that runs while your service is running. Note that the
|
23
|
+
# loop is not implicit.
|
24
|
+
#
|
25
|
+
# You must setup a loop as I've done here with the 'while running?'
|
26
|
+
# code, or setup your own loop. Otherwise your service will exit and
|
27
|
+
# won't be especially useful.
|
28
|
+
#
|
29
|
+
# In this particular case, I've setup a loop to append a short message
|
30
|
+
# and timestamp to a file on your C: drive every 20 seconds. Be sure
|
31
|
+
# to stop the service when you're done!
|
32
|
+
#
|
33
|
+
def service_main(*args)
|
34
|
+
msg = 'service_main entered at: ' + Time.now.to_s
|
35
|
+
|
36
|
+
File.open(LOG_FILE, 'a'){ |f|
|
37
|
+
f.puts msg
|
38
|
+
f.puts "Args: " + args.join(',')
|
39
|
+
}
|
38
40
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
41
|
+
# While we're in here the daemon is running.
|
42
|
+
while running?
|
43
|
+
if state == RUNNING
|
44
|
+
sleep 20
|
45
|
+
msg = 'Service is running as of: ' + Time.now.to_s
|
46
|
+
File.open(LOG_FILE, 'a'){ |f| f.puts msg }
|
47
|
+
else # PAUSED or IDLE
|
48
|
+
sleep 0.5
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
# We've left the loop, the daemon is about to exit.
|
48
53
|
|
49
|
-
|
54
|
+
File.open(LOG_FILE, 'a'){ |f| f.puts "STATE: #{state}" }
|
50
55
|
|
51
|
-
|
52
|
-
|
53
|
-
|
56
|
+
msg = 'service_main left at: ' + Time.now.to_s
|
57
|
+
|
58
|
+
File.open(LOG_FILE, 'a'){ |f| f.puts msg }
|
59
|
+
end
|
54
60
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
61
|
+
# This event triggers when the service receives a signal to stop. I've
|
62
|
+
# added an explicit "exit!" here to ensure that the Ruby interpreter exits
|
63
|
+
# properly. I use 'exit!' instead of 'exit' because otherwise Ruby will
|
64
|
+
# raise a SystemExitError, which I don't want.
|
65
|
+
#
|
66
|
+
def service_stop
|
67
|
+
msg = 'Received stop signal at: ' + Time.now.to_s
|
68
|
+
File.open(LOG_FILE, 'a'){ |f| f.puts msg }
|
69
|
+
exit!
|
70
|
+
end
|
65
71
|
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
+
# This event triggers when the service receives a signal to pause.
|
73
|
+
#
|
74
|
+
def service_pause
|
75
|
+
msg = 'Received pause signal at: ' + Time.now.to_s
|
76
|
+
File.open(LOG_FILE, 'a'){ |f| f.puts msg }
|
77
|
+
end
|
72
78
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
79
|
+
# This event triggers when the service receives a signal to resume
|
80
|
+
# from a paused state.
|
81
|
+
#
|
82
|
+
def service_resume
|
83
|
+
msg = 'Received resume signal at: ' + Time.now.to_s
|
84
|
+
File.open(LOG_FILE, 'a'){ |f| f.puts msg }
|
85
|
+
end
|
86
|
+
end
|
81
87
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
88
|
+
# Create an instance of the Daemon and put it into a loop. I borrowed the
|
89
|
+
# method name 'mainloop' from Tk, btw.
|
90
|
+
#
|
91
|
+
DemoDaemon.mainloop
|
86
92
|
rescue Exception => err
|
87
|
-
|
88
|
-
|
93
|
+
File.open(LOG_FILE, 'a'){ |fh| fh.puts 'Daemon failure: ' + err }
|
94
|
+
raise
|
89
95
|
end
|
data/examples/demo_services.rb
CHANGED
@@ -3,21 +3,28 @@
|
|
3
3
|
#
|
4
4
|
# Test script for general futzing that shows off the basic
|
5
5
|
# capabilities of this library. Modify as you see fit.
|
6
|
+
#
|
7
|
+
# You can run this sample program via the "example:services" task.
|
6
8
|
#######################################################################
|
7
9
|
require 'win32/service'
|
8
10
|
include Win32
|
9
11
|
|
10
12
|
puts "VERSION: " + Service::VERSION
|
11
13
|
|
12
|
-
p Service.exists?(
|
13
|
-
p Service.exists?(
|
14
|
+
p Service.exists?('Schedule')
|
15
|
+
p Service.exists?('bogusxxx')
|
14
16
|
|
15
|
-
status = Service.status(
|
17
|
+
status = Service.status('Schedule')
|
16
18
|
p status
|
17
19
|
|
18
|
-
info = Service.config_info(
|
20
|
+
info = Service.config_info('Schedule')
|
21
|
+
|
22
|
+
print "\n\nShowing config info for Schedule service\n\n"
|
19
23
|
p info
|
20
24
|
|
25
|
+
print "\n\nAbout to show all services\n\n"
|
26
|
+
sleep 10
|
27
|
+
|
21
28
|
Service.services{ |struct|
|
22
|
-
|
29
|
+
p struct
|
23
30
|
}
|
data/ext/extconf.rb
CHANGED
data/ext/win32/daemon.c
CHANGED
@@ -5,7 +5,11 @@
|
|
5
5
|
#include <malloc.h>
|
6
6
|
#include <tchar.h>
|
7
7
|
|
8
|
-
#
|
8
|
+
#ifdef HAVE_SEH_H
|
9
|
+
#include <seh.h>
|
10
|
+
#endif
|
11
|
+
|
12
|
+
#define WIN32_SERVICE_VERSION "0.7.2"
|
9
13
|
|
10
14
|
// Ruby 1.9.x
|
11
15
|
#ifndef RSTRING_PTR
|
@@ -52,71 +56,73 @@ void SetTheServiceStatus(DWORD dwCurrentState,DWORD dwWin32ExitCode,
|
|
52
56
|
// Return an error code as a string
|
53
57
|
LPTSTR ErrorDescription(DWORD p_dwError)
|
54
58
|
{
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
59
|
+
HLOCAL hLocal = NULL;
|
60
|
+
static TCHAR ErrStr[1024];
|
61
|
+
int len;
|
62
|
+
|
63
|
+
if (!(len=FormatMessage(
|
64
|
+
FORMAT_MESSAGE_ALLOCATE_BUFFER |
|
65
|
+
FORMAT_MESSAGE_FROM_SYSTEM |
|
66
|
+
FORMAT_MESSAGE_IGNORE_INSERTS,
|
67
|
+
NULL,
|
68
|
+
p_dwError,
|
69
|
+
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), // Default language
|
70
|
+
(LPTSTR)&hLocal,
|
71
|
+
0,
|
72
|
+
NULL)))
|
73
|
+
{
|
74
|
+
rb_raise(rb_eStandardError, "unable to format error message");
|
75
|
+
}
|
76
|
+
memset(ErrStr, 0, sizeof(ErrStr));
|
77
|
+
strncpy(ErrStr, (LPTSTR)hLocal, len-2); // remove \r\n
|
78
|
+
LocalFree(hLocal);
|
79
|
+
return ErrStr;
|
76
80
|
}
|
77
81
|
|
78
82
|
// Called by the service control manager after the call to
|
79
83
|
// StartServiceCtrlDispatcher.
|
80
84
|
void WINAPI Service_Main(DWORD dwArgc, LPTSTR *lpszArgv)
|
81
85
|
{
|
82
|
-
|
83
|
-
|
86
|
+
// Obtain the name of the service.
|
87
|
+
LPTSTR lpszServiceName = lpszArgv[0];
|
84
88
|
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
89
|
+
// Args passed to Service.start
|
90
|
+
if(dwArgc > 1){
|
91
|
+
unsigned int i;
|
92
|
+
Argc = dwArgc - 1;
|
93
|
+
Argv = malloc(sizeof(VALUE)*Argc);
|
90
94
|
|
91
|
-
|
92
|
-
|
93
|
-
|
95
|
+
for(i=1; i < dwArgc; i++)
|
96
|
+
Argv[i-1] = rb_str_new2(lpszArgv[i]);
|
97
|
+
}
|
94
98
|
|
95
|
-
|
96
|
-
|
97
|
-
|
99
|
+
// Register the service ctrl handler.
|
100
|
+
ssh = RegisterServiceCtrlHandler(
|
101
|
+
lpszServiceName,
|
102
|
+
(LPHANDLER_FUNCTION)Service_Ctrl
|
103
|
+
);
|
98
104
|
|
99
|
-
|
100
|
-
|
101
|
-
|
105
|
+
// no service to stop, no service handle to notify, nothing to do but exit
|
106
|
+
if(ssh == (SERVICE_STATUS_HANDLE)0)
|
107
|
+
return;
|
102
108
|
|
103
|
-
|
104
|
-
|
109
|
+
// The service has started.
|
110
|
+
SetTheServiceStatus(SERVICE_RUNNING, NO_ERROR, 0, 0);
|
105
111
|
|
106
|
-
|
112
|
+
SetEvent(hStartEvent);
|
107
113
|
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
114
|
+
// Main loop for the service.
|
115
|
+
while(WaitForSingleObject(hStopEvent, 1000) != WAIT_OBJECT_0)
|
116
|
+
{
|
117
|
+
}
|
112
118
|
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
119
|
+
// Main loop for the service.
|
120
|
+
while(WaitForSingleObject(hStopCompletedEvent, 1000) != WAIT_OBJECT_0)
|
121
|
+
{
|
122
|
+
}
|
117
123
|
|
118
|
-
|
119
|
-
|
124
|
+
// Stop the service.
|
125
|
+
SetTheServiceStatus(SERVICE_STOPPED, NO_ERROR, 0, 0);
|
120
126
|
}
|
121
127
|
|
122
128
|
VALUE Service_Event_Dispatch(VALUE val)
|
@@ -134,174 +140,174 @@ VALUE Service_Event_Dispatch(VALUE val)
|
|
134
140
|
return result;
|
135
141
|
}
|
136
142
|
|
137
|
-
VALUE Ruby_Service_Ctrl(VALUE self)
|
138
|
-
{
|
139
|
-
|
140
|
-
{
|
141
|
-
|
142
|
-
|
143
|
-
EnterCriticalSection(&csControlCode);
|
144
|
-
|
145
|
-
// Check to see if anything interesting has been signaled
|
146
|
-
if (waiting_control_code != IDLE_CONTROL_CODE)
|
147
|
-
{
|
148
|
-
if (waiting_control_code != SERVICE_CONTROL_STOP) {
|
149
|
-
// if there is a code, create a ruby thread to deal with it
|
150
|
-
// this might be over engineering the solution, but I don't
|
151
|
-
// want to block Service_Ctrl longer than necessary and the
|
152
|
-
// critical section will block it.
|
153
|
-
VALUE EventHookHash = rb_ivar_get(self, rb_intern("@event_hooks"));
|
154
|
-
|
155
|
-
if(EventHookHash != Qnil){
|
156
|
-
VALUE val = rb_hash_aref(EventHookHash, INT2NUM(waiting_control_code));
|
157
|
-
|
158
|
-
if(val != Qnil)
|
159
|
-
rb_thread_create(Service_Event_Dispatch, (void*) val);
|
160
|
-
}
|
161
|
-
}
|
162
|
-
else {
|
163
|
-
break;
|
164
|
-
}
|
165
|
-
waiting_control_code = IDLE_CONTROL_CODE;
|
166
|
-
}
|
167
|
-
}
|
168
|
-
__finally
|
169
|
-
{
|
170
|
-
LeaveCriticalSection(&csControlCode);
|
171
|
-
}
|
143
|
+
VALUE Ruby_Service_Ctrl(VALUE self){
|
144
|
+
while(WaitForSingleObject(hStopEvent,0) == WAIT_TIMEOUT){
|
145
|
+
#if !defined(__GNUC__) || defined(HAVE_SEH_H)
|
146
|
+
__try{
|
147
|
+
#endif
|
148
|
+
EnterCriticalSection(&csControlCode);
|
172
149
|
|
173
|
-
//
|
174
|
-
|
175
|
-
|
150
|
+
// Check to see if anything interesting has been signaled
|
151
|
+
if(waiting_control_code != IDLE_CONTROL_CODE){
|
152
|
+
if(waiting_control_code != SERVICE_CONTROL_STOP){
|
153
|
+
// If there is a code, create a ruby thread to deal with it
|
154
|
+
// this might be over engineering the solution, but I don't
|
155
|
+
// want to block Service_Ctrl longer than necessary and the
|
156
|
+
// critical section will block it.
|
157
|
+
VALUE EventHookHash = rb_ivar_get(self, rb_intern("@event_hooks"));
|
158
|
+
|
159
|
+
if(EventHookHash != Qnil){
|
160
|
+
VALUE val = rb_hash_aref(
|
161
|
+
EventHookHash,
|
162
|
+
INT2NUM(waiting_control_code)
|
163
|
+
);
|
164
|
+
|
165
|
+
if(val != Qnil)
|
166
|
+
rb_thread_create(Service_Event_Dispatch, (void*) val);
|
167
|
+
}
|
168
|
+
}
|
169
|
+
else{
|
170
|
+
break;
|
171
|
+
}
|
172
|
+
|
173
|
+
waiting_control_code = IDLE_CONTROL_CODE;
|
174
|
+
}
|
175
|
+
#if !defined(__GNUC__) || defined(HAVE_SEH_H)
|
176
|
+
}
|
177
|
+
__finally {
|
178
|
+
#endif
|
179
|
+
LeaveCriticalSection(&csControlCode);
|
180
|
+
#if !defined(__GNUC__) || defined(HAVE_SEH_H)
|
181
|
+
}
|
182
|
+
#endif
|
183
|
+
// This is an ugly polling loop, be as polite as possible
|
184
|
+
rb_thread_polling();
|
185
|
+
}
|
176
186
|
|
177
|
-
|
178
|
-
|
179
|
-
|
187
|
+
// Force service_stop call
|
188
|
+
{
|
189
|
+
VALUE EventHookHash = rb_ivar_get(self, rb_intern("@event_hooks"));
|
180
190
|
|
181
|
-
|
182
|
-
|
191
|
+
if(EventHookHash != Qnil){
|
192
|
+
VALUE val = rb_hash_aref(EventHookHash, INT2NUM(SERVICE_CONTROL_STOP));
|
183
193
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
194
|
+
if(val!=Qnil)
|
195
|
+
rb_thread_create(Service_Event_Dispatch, (void*) val);
|
196
|
+
}
|
197
|
+
}
|
188
198
|
|
189
|
-
|
199
|
+
return Qnil;
|
190
200
|
}
|
191
201
|
|
192
202
|
// Handles control signals from the service control manager.
|
193
203
|
void WINAPI Service_Ctrl(DWORD dwCtrlCode)
|
194
204
|
{
|
195
|
-
|
205
|
+
DWORD dwState = SERVICE_RUNNING;
|
196
206
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
if(!SetEvent(hStopEvent))
|
242
|
-
SetTheServiceStatus(SERVICE_STOPPED, GetLastError(), 0, 0);
|
243
|
-
}
|
207
|
+
#if !defined(__GNUC__) || defined(HAVE_SEH_H)
|
208
|
+
__try{
|
209
|
+
#endif
|
210
|
+
EnterCriticalSection(&csControlCode);
|
211
|
+
waiting_control_code = dwCtrlCode;
|
212
|
+
#if !defined(__GNUC__) || defined(HAVE_SEH_H)
|
213
|
+
}
|
214
|
+
__finally{
|
215
|
+
#endif
|
216
|
+
LeaveCriticalSection(&csControlCode);
|
217
|
+
#if !defined(__GNUC__) || defined(HAVE_SEH_H)
|
218
|
+
}
|
219
|
+
#endif
|
220
|
+
|
221
|
+
switch(dwCtrlCode)
|
222
|
+
{
|
223
|
+
case SERVICE_CONTROL_STOP:
|
224
|
+
dwState = SERVICE_STOP_PENDING;
|
225
|
+
break;
|
226
|
+
case SERVICE_CONTROL_SHUTDOWN:
|
227
|
+
dwState = SERVICE_STOP_PENDING;
|
228
|
+
break;
|
229
|
+
case SERVICE_CONTROL_PAUSE:
|
230
|
+
dwState = SERVICE_PAUSED;
|
231
|
+
break;
|
232
|
+
case SERVICE_CONTROL_CONTINUE:
|
233
|
+
dwState = SERVICE_RUNNING;
|
234
|
+
break;
|
235
|
+
case SERVICE_CONTROL_INTERROGATE:
|
236
|
+
break;
|
237
|
+
default:
|
238
|
+
break;
|
239
|
+
}
|
240
|
+
|
241
|
+
// Set the status of the service.
|
242
|
+
SetTheServiceStatus(dwState, NO_ERROR, 0, 0);
|
243
|
+
|
244
|
+
// Tell service_main thread to stop.
|
245
|
+
if ((dwCtrlCode == SERVICE_CONTROL_STOP) ||
|
246
|
+
(dwCtrlCode == SERVICE_CONTROL_SHUTDOWN))
|
247
|
+
{
|
248
|
+
if(!SetEvent(hStopEvent))
|
249
|
+
SetTheServiceStatus(SERVICE_STOPPED, GetLastError(), 0, 0);
|
250
|
+
}
|
244
251
|
}
|
245
252
|
|
246
253
|
// Wraps SetServiceStatus.
|
247
254
|
void SetTheServiceStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode,
|
248
255
|
DWORD dwCheckPoint, DWORD dwWaitHint)
|
249
256
|
{
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
257
|
+
SERVICE_STATUS ss; // Current status of the service.
|
258
|
+
|
259
|
+
// Disable control requests until the service is started.
|
260
|
+
if(dwCurrentState == SERVICE_START_PENDING){
|
261
|
+
ss.dwControlsAccepted = 0;
|
262
|
+
}
|
263
|
+
else{
|
264
|
+
ss.dwControlsAccepted =
|
265
|
+
SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN|
|
266
|
+
SERVICE_ACCEPT_PAUSE_CONTINUE|SERVICE_ACCEPT_SHUTDOWN;
|
267
|
+
}
|
268
|
+
|
269
|
+
// Initialize ss structure.
|
270
|
+
ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
|
271
|
+
ss.dwServiceSpecificExitCode = 0;
|
272
|
+
ss.dwCurrentState = dwCurrentState;
|
273
|
+
ss.dwWin32ExitCode = dwWin32ExitCode;
|
274
|
+
ss.dwCheckPoint = dwCheckPoint;
|
275
|
+
ss.dwWaitHint = dwWaitHint;
|
276
|
+
|
277
|
+
dwServiceState = dwCurrentState;
|
278
|
+
|
279
|
+
// Send status of the service to the Service Controller.
|
280
|
+
if(!SetServiceStatus(ssh, &ss))
|
281
|
+
SetEvent(hStopEvent);
|
275
282
|
}
|
276
283
|
|
277
284
|
DWORD WINAPI ThreadProc(LPVOID lpParameter){
|
278
|
-
|
279
|
-
|
285
|
+
SERVICE_TABLE_ENTRY ste[] =
|
286
|
+
{{TEXT(""),(LPSERVICE_MAIN_FUNCTION)Service_Main}, {NULL, NULL}};
|
280
287
|
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
288
|
+
// No service to step, no service handle, no ruby exceptions, just
|
289
|
+
// terminate the thread.
|
290
|
+
if(!StartServiceCtrlDispatcher(ste))
|
291
|
+
return 1;
|
285
292
|
|
286
|
-
|
293
|
+
return 0;
|
287
294
|
}
|
288
295
|
|
289
296
|
static VALUE daemon_allocate(VALUE klass){
|
290
|
-
|
297
|
+
return Data_Wrap_Struct(klass, 0, 0, 0);
|
291
298
|
}
|
292
299
|
|
293
|
-
|
294
|
-
// Call service_main method
|
300
|
+
// Call service_main method
|
295
301
|
static VALUE daemon_mainloop_protect(VALUE self)
|
296
302
|
{
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
303
|
+
if(rb_respond_to(self,rb_intern("service_main"))){
|
304
|
+
if(Argc == 0)
|
305
|
+
rb_funcall(self, rb_intern("service_main"), 0);
|
306
|
+
else
|
307
|
+
rb_funcall2(self, rb_intern("service_main"), Argc, Argv);
|
308
|
+
}
|
309
|
+
|
310
|
+
return self;
|
305
311
|
}
|
306
312
|
|
307
313
|
static VALUE daemon_mainloop_ensure(VALUE self)
|
@@ -345,151 +351,151 @@ static VALUE daemon_mainloop_ensure(VALUE self)
|
|
345
351
|
*/
|
346
352
|
static VALUE daemon_mainloop(VALUE self)
|
347
353
|
{
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
354
|
+
DWORD ThreadId;
|
355
|
+
HANDLE events[2];
|
356
|
+
DWORD index;
|
357
|
+
VALUE result, EventHookHash;
|
358
|
+
int status = 0;
|
359
|
+
|
360
|
+
dwServiceState = 0;
|
361
|
+
|
362
|
+
// Redirect STDIN, STDOUT and STDERR to the NUL device if they're still
|
363
|
+
// associated with a tty. This helps newbs avoid Errno::EBADF errors.
|
364
|
+
if(rb_funcall(rb_stdin, rb_intern("isatty"), 0) == Qtrue)
|
365
|
+
rb_funcall(rb_stdin, rb_intern("reopen"), 1, rb_str_new2("NUL"));
|
366
|
+
|
367
|
+
if(rb_funcall(rb_stdout, rb_intern("isatty"), 0) == Qtrue)
|
368
|
+
rb_funcall(rb_stdout, rb_intern("reopen"), 1, rb_str_new2("NUL"));
|
369
|
+
|
370
|
+
if(rb_funcall(rb_stderr, rb_intern("isatty"), 0) == Qtrue)
|
371
|
+
rb_funcall(rb_stderr, rb_intern("reopen"), 1, rb_str_new2("NUL"));
|
372
|
+
|
373
|
+
// Use a markable instance variable to prevent the garbage collector
|
374
|
+
// from freeing the hash before Ruby_Service_Ctrl exits, or just
|
375
|
+
// at any ole time while running the service
|
376
|
+
EventHookHash = rb_hash_new();
|
377
|
+
rb_ivar_set(self, rb_intern("@event_hooks"), EventHookHash);
|
378
|
+
|
379
|
+
// Event hooks
|
380
|
+
if(rb_respond_to(self, rb_intern("service_stop"))){
|
381
|
+
rb_hash_aset(EventHookHash, INT2NUM(SERVICE_CONTROL_STOP),
|
382
|
+
rb_ary_new3(2, self, INT2NUM(rb_intern("service_stop"))));
|
383
|
+
}
|
384
|
+
|
385
|
+
if(rb_respond_to(self, rb_intern("service_pause"))){
|
386
|
+
rb_hash_aset(EventHookHash, INT2NUM(SERVICE_CONTROL_PAUSE),
|
387
|
+
rb_ary_new3(2, self, INT2NUM(rb_intern("service_pause"))));
|
388
|
+
}
|
389
|
+
|
390
|
+
if(rb_respond_to(self, rb_intern("service_resume"))){
|
391
|
+
rb_hash_aset(EventHookHash, INT2NUM(SERVICE_CONTROL_CONTINUE),
|
392
|
+
rb_ary_new3(2, self, INT2NUM(rb_intern("service_resume"))));
|
393
|
+
}
|
394
|
+
|
395
|
+
if(rb_respond_to(self, rb_intern("service_interrogate"))){
|
396
|
+
rb_hash_aset(EventHookHash, INT2NUM(SERVICE_CONTROL_INTERROGATE),
|
397
|
+
rb_ary_new3(2, self, INT2NUM(rb_intern("service_interrogate"))));
|
398
|
+
}
|
399
|
+
|
400
|
+
if(rb_respond_to(self, rb_intern("service_shutdown"))){
|
401
|
+
rb_hash_aset(EventHookHash, INT2NUM(SERVICE_CONTROL_SHUTDOWN),
|
402
|
+
rb_ary_new3(2, self, INT2NUM(rb_intern("service_shutdown"))));
|
403
|
+
}
|
398
404
|
|
399
405
|
#ifdef SERVICE_CONTROL_PARAMCHANGE
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
406
|
+
if(rb_respond_to(self, rb_intern("service_paramchange"))){
|
407
|
+
rb_hash_aset(EventHookHash, INT2NUM(SERVICE_CONTROL_PARAMCHANGE),
|
408
|
+
rb_ary_new3(2, self, INT2NUM(rb_intern("service_paramchange"))));
|
409
|
+
}
|
404
410
|
#endif
|
405
411
|
|
406
412
|
#ifdef SERVICE_CONTROL_NETBINDADD
|
407
|
-
|
408
|
-
|
409
|
-
|
410
|
-
|
413
|
+
if(rb_respond_to(self, rb_intern("service_netbindadd"))){
|
414
|
+
rb_hash_aset(EventHookHash, INT2NUM(SERVICE_CONTROL_NETBINDADD),
|
415
|
+
rb_ary_new3(2, self, INT2NUM(rb_intern("service_netbindadd"))));
|
416
|
+
}
|
411
417
|
#endif
|
412
418
|
|
413
419
|
#ifdef SERVICE_CONTROL_NETBINDREMOVE
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
420
|
+
if(rb_respond_to(self, rb_intern("service_netbindremove"))){
|
421
|
+
rb_hash_aset(EventHookHash, INT2NUM(SERVICE_CONTROL_NETBINDREMOVE),
|
422
|
+
rb_ary_new3(2, self, INT2NUM(rb_intern("service_netbindremove"))));
|
423
|
+
}
|
418
424
|
#endif
|
419
425
|
|
420
426
|
#ifdef SERVICE_CONTROL_NETBINDENABLE
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
427
|
+
if(rb_respond_to(self, rb_intern("service_netbindenable"))){
|
428
|
+
rb_hash_aset(EventHookHash, INT2NUM(SERVICE_CONTROL_NETBINDENABLE),
|
429
|
+
rb_ary_new3(2, self, INT2NUM(rb_intern("service_netbindenable"))));
|
430
|
+
}
|
425
431
|
#endif
|
426
432
|
|
427
433
|
#ifdef SERVICE_CONTROL_NETBINDDISABLE
|
428
|
-
|
429
|
-
|
430
|
-
|
431
|
-
|
434
|
+
if(rb_respond_to(self, rb_intern("service_netbinddisable"))){
|
435
|
+
rb_hash_aset(EventHookHash, INT2NUM(SERVICE_CONTROL_NETBINDDISABLE),
|
436
|
+
rb_ary_new3(2, self, INT2NUM(rb_intern("service_netbinddisable"))));
|
437
|
+
}
|
432
438
|
#endif
|
433
439
|
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
440
|
+
// Calling init here so that init failures never even tries to
|
441
|
+
// start the service... of course that means that init methods
|
442
|
+
// must be very quick, because the SCM will be receiving no
|
443
|
+
// START_PENDING messages while init's running - I may fix this
|
444
|
+
// later
|
445
|
+
if(rb_respond_to(self, rb_intern("service_init")))
|
446
|
+
rb_funcall(self, rb_intern("service_init"),0);
|
441
447
|
|
442
|
-
|
443
|
-
|
448
|
+
// Create the event to signal the service to start.
|
449
|
+
hStartEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
444
450
|
|
445
|
-
|
446
|
-
|
451
|
+
if(hStartEvent == NULL)
|
452
|
+
rb_raise(cDaemonError, ErrorDescription(GetLastError()));
|
447
453
|
|
448
|
-
|
449
|
-
|
454
|
+
// Create the event to signal the service to stop.
|
455
|
+
hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
450
456
|
|
451
|
-
|
452
|
-
|
457
|
+
if(hStopEvent == NULL)
|
458
|
+
rb_raise(cDaemonError, ErrorDescription(GetLastError()));
|
453
459
|
|
454
|
-
|
455
|
-
|
460
|
+
// Create the event to signal the service that stop has completed
|
461
|
+
hStopCompletedEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
|
456
462
|
|
457
|
-
|
458
|
-
|
463
|
+
if(hStopCompletedEvent == NULL)
|
464
|
+
rb_raise(cDaemonError, ErrorDescription(GetLastError()));
|
459
465
|
|
460
|
-
|
461
|
-
|
466
|
+
// Create Thread for service main
|
467
|
+
hThread = CreateThread(NULL, 0, ThreadProc, 0, 0, &ThreadId);
|
462
468
|
|
463
|
-
|
464
|
-
|
469
|
+
if(hThread == INVALID_HANDLE_VALUE)
|
470
|
+
rb_raise(cDaemonError, ErrorDescription(GetLastError()));
|
465
471
|
|
466
|
-
|
467
|
-
|
472
|
+
events[0] = hThread;
|
473
|
+
events[1] = hStartEvent;
|
468
474
|
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
475
|
+
// wait for Service_Main function to either start the service OR terminate
|
476
|
+
while((index = WaitForMultipleObjects(2,events,FALSE,1000)) == WAIT_TIMEOUT)
|
477
|
+
{
|
478
|
+
}
|
473
479
|
|
474
|
-
|
475
|
-
|
476
|
-
|
480
|
+
// thread exited, so the show is off
|
481
|
+
if(index == WAIT_OBJECT_0)
|
482
|
+
rb_raise(cDaemonError, "Service_Main thread exited abnormally");
|
477
483
|
|
478
|
-
|
484
|
+
// from this point onward, stopevent must be triggered!
|
479
485
|
|
480
|
-
|
481
|
-
|
486
|
+
// Create the green thread to poll for Service_Ctrl events
|
487
|
+
rb_thread_create(Ruby_Service_Ctrl, (void *)self);
|
482
488
|
|
483
|
-
|
489
|
+
result = rb_protect(daemon_mainloop_protect, self, &status);
|
484
490
|
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
489
|
-
|
491
|
+
// service_main raised an exception
|
492
|
+
if(status){
|
493
|
+
daemon_mainloop_ensure(self);
|
494
|
+
rb_jump_tag(status);
|
495
|
+
}
|
490
496
|
|
491
|
-
|
492
|
-
|
497
|
+
// service_main exited cleanly
|
498
|
+
return daemon_mainloop_ensure(self);
|
493
499
|
}
|
494
500
|
|
495
501
|
/*
|
@@ -510,7 +516,7 @@ static VALUE daemon_mainloop(VALUE self)
|
|
510
516
|
* See the Daemon#running? method for an abstraction of the above code.
|
511
517
|
*/
|
512
518
|
static VALUE daemon_state(VALUE self){
|
513
|
-
|
519
|
+
return UINT2NUM(dwServiceState);
|
514
520
|
}
|
515
521
|
|
516
522
|
/*
|
@@ -529,83 +535,78 @@ static VALUE daemon_state(VALUE self){
|
|
529
535
|
* end
|
530
536
|
*/
|
531
537
|
static VALUE daemon_is_running(VALUE self){
|
532
|
-
|
533
|
-
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
538
|
+
VALUE v_bool = Qfalse;
|
539
|
+
|
540
|
+
if(
|
541
|
+
(dwServiceState == SERVICE_RUNNING) ||
|
542
|
+
(dwServiceState == SERVICE_PAUSED) ||
|
543
|
+
(dwServiceState == 0)
|
544
|
+
){
|
545
|
+
v_bool = Qtrue;
|
546
|
+
}
|
547
|
+
|
548
|
+
return v_bool;
|
542
549
|
}
|
543
550
|
|
544
551
|
/*
|
545
552
|
* This is a shortcut for Daemon.new + Daemon#mainloop.
|
546
553
|
*/
|
547
554
|
static VALUE daemon_c_mainloop(VALUE klass){
|
548
|
-
|
549
|
-
|
550
|
-
|
555
|
+
VALUE v_args[1];
|
556
|
+
VALUE v_daemon = rb_class_new_instance(0, v_args, klass);
|
557
|
+
return rb_funcall(v_daemon, rb_intern("mainloop"), 0, 0);
|
551
558
|
}
|
552
559
|
|
553
560
|
void Init_daemon()
|
554
561
|
{
|
555
|
-
|
556
|
-
|
562
|
+
/* The Win32 module serves as a namespace only. */
|
563
|
+
VALUE mWin32 = rb_define_module("Win32");
|
557
564
|
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
565
|
+
/* The Daemon class encapsulates a Windows service through the use
|
566
|
+
* of callback methods and a main loop.
|
567
|
+
*/
|
568
|
+
VALUE cDaemon = rb_define_class_under(mWin32, "Daemon", rb_cObject);
|
562
569
|
|
563
|
-
|
564
|
-
|
570
|
+
/* Error typically raised if something goes wrong with your daemon. */
|
571
|
+
cDaemonError = rb_define_class_under(cDaemon, "Error", rb_eStandardError);
|
565
572
|
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
573
|
+
rb_define_alloc_func(cDaemon, daemon_allocate);
|
574
|
+
rb_define_method(cDaemon, "mainloop", daemon_mainloop, 0);
|
575
|
+
rb_define_method(cDaemon, "state", daemon_state, 0);
|
576
|
+
rb_define_method(cDaemon, "running?", daemon_is_running, 0);
|
570
577
|
|
571
|
-
|
578
|
+
rb_define_singleton_method(cDaemon, "mainloop", daemon_c_mainloop, 0);
|
572
579
|
|
573
|
-
|
574
|
-
|
580
|
+
// Intialize critical section used by green polling thread
|
581
|
+
InitializeCriticalSection(&csControlCode);
|
575
582
|
|
576
|
-
|
583
|
+
// Constants
|
577
584
|
|
578
|
-
|
579
|
-
|
585
|
+
/* 0.7.2: The version of this library */
|
586
|
+
rb_define_const(cDaemon, "VERSION", rb_str_new2(WIN32_SERVICE_VERSION));
|
580
587
|
|
581
|
-
|
582
|
-
|
583
|
-
|
588
|
+
/* Service has received a signal to resume but is not yet running */
|
589
|
+
rb_define_const(cDaemon, "CONTINUE_PENDING",
|
590
|
+
INT2NUM(SERVICE_CONTINUE_PENDING));
|
584
591
|
|
585
|
-
|
586
|
-
|
587
|
-
INT2NUM(SERVICE_PAUSE_PENDING));
|
592
|
+
/* Service has received a signal to pause but is not yet paused */
|
593
|
+
rb_define_const(cDaemon, "PAUSE_PENDING", INT2NUM(SERVICE_PAUSE_PENDING));
|
588
594
|
|
589
|
-
|
590
|
-
|
591
|
-
INT2NUM(SERVICE_PAUSED));
|
595
|
+
/* Service is in a paused state */
|
596
|
+
rb_define_const(cDaemon, "PAUSED", INT2NUM(SERVICE_PAUSED));
|
592
597
|
|
593
|
-
|
594
|
-
|
595
|
-
INT2NUM(SERVICE_RUNNING));
|
598
|
+
/* Service is running */
|
599
|
+
rb_define_const(cDaemon, "RUNNING", INT2NUM(SERVICE_RUNNING));
|
596
600
|
|
597
|
-
|
598
|
-
|
599
|
-
INT2NUM(SERVICE_START_PENDING));
|
601
|
+
/* Service has received a signal to start but is not yet running */
|
602
|
+
rb_define_const(cDaemon, "START_PENDING", INT2NUM(SERVICE_START_PENDING));
|
600
603
|
|
601
|
-
|
602
|
-
|
603
|
-
INT2NUM(SERVICE_STOP_PENDING));
|
604
|
+
/* Service has received a signal to stop but has not yet stopped */
|
605
|
+
rb_define_const(cDaemon, "STOP_PENDING", INT2NUM(SERVICE_STOP_PENDING));
|
604
606
|
|
605
|
-
|
606
|
-
|
607
|
-
INT2NUM(SERVICE_STOPPED));
|
607
|
+
/* Service is stopped. */
|
608
|
+
rb_define_const(cDaemon, "STOPPED", INT2NUM(SERVICE_STOPPED));
|
608
609
|
|
609
|
-
|
610
|
-
|
610
|
+
/* Service is in an idle state */
|
611
|
+
rb_define_const(cDaemon, "IDLE", INT2NUM(0));
|
611
612
|
}
|