win32-service 0.7.2-x86-mingw32

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,95 @@
1
+ LOG_FILE = 'C:\\win32_daemon_test.log'
2
+
3
+ begin
4
+ require 'rubygems'
5
+ require 'win32/daemon'
6
+ include Win32
7
+
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
20
+
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
+ }
40
+
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.
53
+
54
+ File.open(LOG_FILE, 'a'){ |f| f.puts "STATE: #{state}" }
55
+
56
+ msg = 'service_main left at: ' + Time.now.to_s
57
+
58
+ File.open(LOG_FILE, 'a'){ |f| f.puts msg }
59
+ end
60
+
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
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
78
+
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
87
+
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
92
+ rescue Exception => err
93
+ File.open(LOG_FILE, 'a'){ |fh| fh.puts 'Daemon failure: ' + err }
94
+ raise
95
+ end
@@ -0,0 +1,122 @@
1
+ ############################################################################
2
+ # demo_daemon_ctl.rb
3
+ #
4
+ # This is a command line script for installing and/or running a small
5
+ # Ruby program as a service. The service will simply write a small bit
6
+ # of text to a file every 20 seconds. It will also write some text to the
7
+ # file during the initialization (service_init) step.
8
+ #
9
+ # It should take about 10 seconds to start, which is intentional - it's a test
10
+ # of the service_init hook, so don't be surprised if you see "one moment,
11
+ # start pending" about 10 times on the command line.
12
+ #
13
+ # The file in question is C:\test.log. Feel free to delete it when finished.
14
+ #
15
+ # To run the service, you must install it first.
16
+ #
17
+ # Usage: ruby demo_daemon_ctl.rb <option>
18
+ #
19
+ # Note that you *must* pass this program an option
20
+ #
21
+ # Options:
22
+ # install - Installs the service. The service name is "DemoSvc"
23
+ # and the display name is "Demo".
24
+ # start - Starts the service. Make sure you stop it at some point or
25
+ # you will eventually fill up your filesystem!.
26
+ # stop - Stops the service.
27
+ # pause - Pauses the service.
28
+ # resume - Resumes the service.
29
+ # uninstall - Uninstalls the service.
30
+ # delete - Same as uninstall.
31
+ #
32
+ # You can also used the Windows Services GUI to start and stop the service.
33
+ #
34
+ # To get to the Windows Services GUI just follow:
35
+ # Start -> Control Panel -> Administrative Tools -> Services
36
+ ############################################################################
37
+ require 'win32/service'
38
+ require 'rbconfig'
39
+ include Win32
40
+ include Config
41
+
42
+ # Make sure you're using the version you think you're using.
43
+ puts 'VERSION: ' + Service::VERSION
44
+
45
+ SERVICE_NAME = 'DemoSvc'
46
+ SERVICE_DISPLAYNAME = 'Demo'
47
+
48
+ # Quote the full path to deal with possible spaces in the path name.
49
+ ruby = File.join(CONFIG['bindir'], 'ruby').tr('/', '\\')
50
+ path = ' "' + File.dirname(File.expand_path($0)).tr('/', '\\')
51
+ path += '\demo_daemon.rb"'
52
+ cmd = ruby + path
53
+
54
+ # You must provide at least one argument.
55
+ raise ArgumentError, 'No argument provided' unless ARGV[0]
56
+
57
+ case ARGV[0].downcase
58
+ when 'install'
59
+ Service.new(
60
+ :service_name => SERVICE_NAME,
61
+ :display_name => SERVICE_DISPLAYNAME,
62
+ :description => 'Sample Ruby service',
63
+ :binary_path_name => cmd
64
+ )
65
+ puts 'Service ' + SERVICE_NAME + ' installed'
66
+ when 'start'
67
+ if Service.status(SERVICE_NAME).current_state != 'running'
68
+ Service.start(SERVICE_NAME, nil, 'hello', 'world')
69
+ while Service.status(SERVICE_NAME).current_state != 'running'
70
+ puts 'One moment...' + Service.status(SERVICE_NAME).current_state
71
+ sleep 1
72
+ end
73
+ puts 'Service ' + SERVICE_NAME + ' started'
74
+ else
75
+ puts 'Already running'
76
+ end
77
+ when 'stop'
78
+ if Service.status(SERVICE_NAME).current_state != 'stopped'
79
+ Service.stop(SERVICE_NAME)
80
+ while Service.status(SERVICE_NAME).current_state != 'stopped'
81
+ puts 'One moment...' + Service.status(SERVICE_NAME).current_state
82
+ sleep 1
83
+ end
84
+ puts 'Service ' + SERVICE_NAME + ' stopped'
85
+ else
86
+ puts 'Already stopped'
87
+ end
88
+ when 'uninstall', 'delete'
89
+ if Service.status(SERVICE_NAME).current_state != 'stopped'
90
+ Service.stop(SERVICE_NAME)
91
+ end
92
+ while Service.status(SERVICE_NAME).current_state != 'stopped'
93
+ puts 'One moment...' + Service.status(SERVICE_NAME).current_state
94
+ sleep 1
95
+ end
96
+ Service.delete(SERVICE_NAME)
97
+ puts 'Service ' + SERVICE_NAME + ' deleted'
98
+ when 'pause'
99
+ if Service.status(SERVICE_NAME).current_state != 'paused'
100
+ Service.pause(SERVICE_NAME)
101
+ while Service.status(SERVICE_NAME).current_state != 'paused'
102
+ puts 'One moment...' + Service.status(SERVICE_NAME).current_state
103
+ sleep 1
104
+ end
105
+ puts 'Service ' + SERVICE_NAME + ' paused'
106
+ else
107
+ puts 'Already paused'
108
+ end
109
+ when 'resume'
110
+ if Service.status(SERVICE_NAME).current_state != 'running'
111
+ Service.resume(SERVICE_NAME)
112
+ while Service.status(SERVICE_NAME).current_state != 'running'
113
+ puts 'One moment...' + Service.status(SERVICE_NAME).current_state
114
+ sleep 1
115
+ end
116
+ puts 'Service ' + SERVICE_NAME + ' resumed'
117
+ else
118
+ puts 'Already running'
119
+ end
120
+ else
121
+ raise ArgumentError, 'unknown option: ' + ARGV[0]
122
+ end
@@ -0,0 +1,30 @@
1
+ #######################################################################
2
+ # demo_services.rb
3
+ #
4
+ # Test script for general futzing that shows off the basic
5
+ # capabilities of this library. Modify as you see fit.
6
+ #
7
+ # You can run this sample program via the "example:services" task.
8
+ #######################################################################
9
+ require 'win32/service'
10
+ include Win32
11
+
12
+ puts "VERSION: " + Service::VERSION
13
+
14
+ p Service.exists?('Schedule')
15
+ p Service.exists?('bogusxxx')
16
+
17
+ status = Service.status('Schedule')
18
+ p status
19
+
20
+ info = Service.config_info('Schedule')
21
+
22
+ print "\n\nShowing config info for Schedule service\n\n"
23
+ p info
24
+
25
+ print "\n\nAbout to show all services\n\n"
26
+ sleep 10
27
+
28
+ Service.services{ |struct|
29
+ p struct
30
+ }
@@ -0,0 +1,612 @@
1
+ #include "ruby.h"
2
+ #include <windows.h>
3
+ #include <string.h>
4
+ #include <stdlib.h>
5
+ #include <malloc.h>
6
+ #include <tchar.h>
7
+
8
+ #ifdef HAVE_SEH_H
9
+ #include <seh.h>
10
+ #endif
11
+
12
+ #define WIN32_SERVICE_VERSION "0.7.2"
13
+
14
+ // Ruby 1.9.x
15
+ #ifndef RSTRING_PTR
16
+ #define RSTRING_PTR(s) (RSTRING(s)->ptr)
17
+ #endif
18
+ #ifndef RSTRING_LEN
19
+ #define RSTRING_LEN(s) (RSTRING(s)->len)
20
+ #endif
21
+
22
+ #ifndef RARRAY_PTR
23
+ #define RARRAY_PTR(a) (RARRAY(a)->ptr)
24
+ #endif
25
+ #ifndef RARRAY_LEN
26
+ #define RARRAY_LEN(a) (RARRAY(a)->len)
27
+ #endif
28
+
29
+ static VALUE cDaemonError;
30
+
31
+ static HANDLE hThread;
32
+ static HANDLE hStartEvent;
33
+ static HANDLE hStopEvent;
34
+ static HANDLE hStopCompletedEvent;
35
+ static SERVICE_STATUS_HANDLE ssh;
36
+ static DWORD dwServiceState;
37
+ static TCHAR error[1024];
38
+ static int Argc;
39
+ static VALUE* Argv;
40
+
41
+ CRITICAL_SECTION csControlCode;
42
+ // I happen to know from looking in the header file
43
+ // that 0 is not a valid service control code
44
+ // so we will use it, the value does not matter
45
+ // as long as it will never show up in ServiceCtrl
46
+ // - Patrick Hurley
47
+ #define IDLE_CONTROL_CODE 0
48
+ static int waiting_control_code = IDLE_CONTROL_CODE;
49
+
50
+ static VALUE service_close(VALUE);
51
+ void WINAPI Service_Main(DWORD dwArgc, LPTSTR *lpszArgv);
52
+ void WINAPI Service_Ctrl(DWORD dwCtrlCode);
53
+ void SetTheServiceStatus(DWORD dwCurrentState,DWORD dwWin32ExitCode,
54
+ DWORD dwCheckPoint, DWORD dwWaitHint);
55
+
56
+ // Return an error code as a string
57
+ LPTSTR ErrorDescription(DWORD p_dwError)
58
+ {
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;
80
+ }
81
+
82
+ // Called by the service control manager after the call to
83
+ // StartServiceCtrlDispatcher.
84
+ void WINAPI Service_Main(DWORD dwArgc, LPTSTR *lpszArgv)
85
+ {
86
+ // Obtain the name of the service.
87
+ LPTSTR lpszServiceName = lpszArgv[0];
88
+
89
+ // Args passed to Service.start
90
+ if(dwArgc > 1){
91
+ unsigned int i;
92
+ Argc = dwArgc - 1;
93
+ Argv = malloc(sizeof(VALUE)*Argc);
94
+
95
+ for(i=1; i < dwArgc; i++)
96
+ Argv[i-1] = rb_str_new2(lpszArgv[i]);
97
+ }
98
+
99
+ // Register the service ctrl handler.
100
+ ssh = RegisterServiceCtrlHandler(
101
+ lpszServiceName,
102
+ (LPHANDLER_FUNCTION)Service_Ctrl
103
+ );
104
+
105
+ // no service to stop, no service handle to notify, nothing to do but exit
106
+ if(ssh == (SERVICE_STATUS_HANDLE)0)
107
+ return;
108
+
109
+ // The service has started.
110
+ SetTheServiceStatus(SERVICE_RUNNING, NO_ERROR, 0, 0);
111
+
112
+ SetEvent(hStartEvent);
113
+
114
+ // Main loop for the service.
115
+ while(WaitForSingleObject(hStopEvent, 1000) != WAIT_OBJECT_0)
116
+ {
117
+ }
118
+
119
+ // Main loop for the service.
120
+ while(WaitForSingleObject(hStopCompletedEvent, 1000) != WAIT_OBJECT_0)
121
+ {
122
+ }
123
+
124
+ // Stop the service.
125
+ SetTheServiceStatus(SERVICE_STOPPED, NO_ERROR, 0, 0);
126
+ }
127
+
128
+ VALUE Service_Event_Dispatch(VALUE val)
129
+ {
130
+ VALUE func,self;
131
+ VALUE result = Qnil;
132
+
133
+ if(val!=Qnil) {
134
+ self = RARRAY_PTR(val)[0];
135
+ func = NUM2INT(RARRAY_PTR(val)[1]);
136
+
137
+ result = rb_funcall(self,func,0);
138
+ }
139
+
140
+ return result;
141
+ }
142
+
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);
149
+
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
+ }
186
+
187
+ // Force service_stop call
188
+ {
189
+ VALUE EventHookHash = rb_ivar_get(self, rb_intern("@event_hooks"));
190
+
191
+ if(EventHookHash != Qnil){
192
+ VALUE val = rb_hash_aref(EventHookHash, INT2NUM(SERVICE_CONTROL_STOP));
193
+
194
+ if(val!=Qnil)
195
+ rb_thread_create(Service_Event_Dispatch, (void*) val);
196
+ }
197
+ }
198
+
199
+ return Qnil;
200
+ }
201
+
202
+ // Handles control signals from the service control manager.
203
+ void WINAPI Service_Ctrl(DWORD dwCtrlCode)
204
+ {
205
+ DWORD dwState = SERVICE_RUNNING;
206
+
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
+ }
251
+ }
252
+
253
+ // Wraps SetServiceStatus.
254
+ void SetTheServiceStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode,
255
+ DWORD dwCheckPoint, DWORD dwWaitHint)
256
+ {
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);
282
+ }
283
+
284
+ DWORD WINAPI ThreadProc(LPVOID lpParameter){
285
+ SERVICE_TABLE_ENTRY ste[] =
286
+ {{TEXT(""),(LPSERVICE_MAIN_FUNCTION)Service_Main}, {NULL, NULL}};
287
+
288
+ // No service to step, no service handle, no ruby exceptions, just
289
+ // terminate the thread.
290
+ if(!StartServiceCtrlDispatcher(ste))
291
+ return 1;
292
+
293
+ return 0;
294
+ }
295
+
296
+ static VALUE daemon_allocate(VALUE klass){
297
+ return Data_Wrap_Struct(klass, 0, 0, 0);
298
+ }
299
+
300
+ // Call service_main method
301
+ static VALUE daemon_mainloop_protect(VALUE self)
302
+ {
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;
311
+ }
312
+
313
+ static VALUE daemon_mainloop_ensure(VALUE self)
314
+ {
315
+ int i;
316
+
317
+ // Signal both the ruby thread and service_main thread to terminate
318
+ SetEvent(hStopEvent);
319
+
320
+ // Wait for ALL ruby threads to exit
321
+ for(i=1; TRUE; i++)
322
+ {
323
+ VALUE list = rb_funcall(rb_cThread, rb_intern("list"), 0);
324
+
325
+ if(RARRAY_LEN(list) <= 1)
326
+ break;
327
+
328
+ // This is another ugly polling loop, be as polite as possible
329
+ rb_thread_polling();
330
+
331
+ SetTheServiceStatus(SERVICE_STOP_PENDING, 0, i, 1000);
332
+ }
333
+
334
+ // Only one ruby thread
335
+ SetEvent(hStopCompletedEvent);
336
+
337
+ // Wait for the thread to stop BEFORE we close the hStopEvent handle
338
+ WaitForSingleObject(hThread, INFINITE);
339
+
340
+ // Close the event handle, ignoring failures. We may be cleaning up
341
+ // after an exception, so let that exception fall through.
342
+ CloseHandle(hStopEvent);
343
+
344
+ return self;
345
+ }
346
+
347
+ /*
348
+ * This is the method that actually puts your code into a loop and allows it
349
+ * to run as a service. The code that is actually run while in the mainloop
350
+ * is what you defined in your own Daemon#service_main method.
351
+ */
352
+ static VALUE daemon_mainloop(VALUE self)
353
+ {
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
+ }
404
+
405
+ #ifdef SERVICE_CONTROL_PARAMCHANGE
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
+ }
410
+ #endif
411
+
412
+ #ifdef SERVICE_CONTROL_NETBINDADD
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
+ }
417
+ #endif
418
+
419
+ #ifdef SERVICE_CONTROL_NETBINDREMOVE
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
+ }
424
+ #endif
425
+
426
+ #ifdef SERVICE_CONTROL_NETBINDENABLE
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
+ }
431
+ #endif
432
+
433
+ #ifdef SERVICE_CONTROL_NETBINDDISABLE
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
+ }
438
+ #endif
439
+
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);
447
+
448
+ // Create the event to signal the service to start.
449
+ hStartEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
450
+
451
+ if(hStartEvent == NULL)
452
+ rb_raise(cDaemonError, ErrorDescription(GetLastError()));
453
+
454
+ // Create the event to signal the service to stop.
455
+ hStopEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
456
+
457
+ if(hStopEvent == NULL)
458
+ rb_raise(cDaemonError, ErrorDescription(GetLastError()));
459
+
460
+ // Create the event to signal the service that stop has completed
461
+ hStopCompletedEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
462
+
463
+ if(hStopCompletedEvent == NULL)
464
+ rb_raise(cDaemonError, ErrorDescription(GetLastError()));
465
+
466
+ // Create Thread for service main
467
+ hThread = CreateThread(NULL, 0, ThreadProc, 0, 0, &ThreadId);
468
+
469
+ if(hThread == INVALID_HANDLE_VALUE)
470
+ rb_raise(cDaemonError, ErrorDescription(GetLastError()));
471
+
472
+ events[0] = hThread;
473
+ events[1] = hStartEvent;
474
+
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
+ }
479
+
480
+ // thread exited, so the show is off
481
+ if(index == WAIT_OBJECT_0)
482
+ rb_raise(cDaemonError, "Service_Main thread exited abnormally");
483
+
484
+ // from this point onward, stopevent must be triggered!
485
+
486
+ // Create the green thread to poll for Service_Ctrl events
487
+ rb_thread_create(Ruby_Service_Ctrl, (void *)self);
488
+
489
+ result = rb_protect(daemon_mainloop_protect, self, &status);
490
+
491
+ // service_main raised an exception
492
+ if(status){
493
+ daemon_mainloop_ensure(self);
494
+ rb_jump_tag(status);
495
+ }
496
+
497
+ // service_main exited cleanly
498
+ return daemon_mainloop_ensure(self);
499
+ }
500
+
501
+ /*
502
+ * Returns the state of the service (as an constant integer) which can be any
503
+ * of the service status constants, e.g. RUNNING, PAUSED, etc.
504
+ *
505
+ * This method is typically used within your service_main method to setup the
506
+ * loop. For example:
507
+ *
508
+ * class MyDaemon < Daemon
509
+ * def service_main
510
+ * while state == RUNNING || state == PAUSED || state == IDLE
511
+ * # Your main loop here
512
+ * end
513
+ * end
514
+ * end
515
+ *
516
+ * See the Daemon#running? method for an abstraction of the above code.
517
+ */
518
+ static VALUE daemon_state(VALUE self){
519
+ return UINT2NUM(dwServiceState);
520
+ }
521
+
522
+ /*
523
+ * Returns whether or not the service is in a running state, i.e. the service
524
+ * status is either RUNNING, PAUSED or IDLE.
525
+ *
526
+ * This is typically used within your service_main method to setup the main
527
+ * loop. For example:
528
+ *
529
+ * class MyDaemon < Daemon
530
+ * def service_main
531
+ * while running?
532
+ * # Your main loop here
533
+ * end
534
+ * end
535
+ * end
536
+ */
537
+ static VALUE daemon_is_running(VALUE self){
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;
549
+ }
550
+
551
+ /*
552
+ * This is a shortcut for Daemon.new + Daemon#mainloop.
553
+ */
554
+ static VALUE daemon_c_mainloop(VALUE klass){
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);
558
+ }
559
+
560
+ void Init_daemon()
561
+ {
562
+ /* The Win32 module serves as a namespace only. */
563
+ VALUE mWin32 = rb_define_module("Win32");
564
+
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);
569
+
570
+ /* Error typically raised if something goes wrong with your daemon. */
571
+ cDaemonError = rb_define_class_under(cDaemon, "Error", rb_eStandardError);
572
+
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);
577
+
578
+ rb_define_singleton_method(cDaemon, "mainloop", daemon_c_mainloop, 0);
579
+
580
+ // Intialize critical section used by green polling thread
581
+ InitializeCriticalSection(&csControlCode);
582
+
583
+ // Constants
584
+
585
+ /* 0.7.2: The version of this library */
586
+ rb_define_const(cDaemon, "VERSION", rb_str_new2(WIN32_SERVICE_VERSION));
587
+
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));
591
+
592
+ /* Service has received a signal to pause but is not yet paused */
593
+ rb_define_const(cDaemon, "PAUSE_PENDING", INT2NUM(SERVICE_PAUSE_PENDING));
594
+
595
+ /* Service is in a paused state */
596
+ rb_define_const(cDaemon, "PAUSED", INT2NUM(SERVICE_PAUSED));
597
+
598
+ /* Service is running */
599
+ rb_define_const(cDaemon, "RUNNING", INT2NUM(SERVICE_RUNNING));
600
+
601
+ /* Service has received a signal to start but is not yet running */
602
+ rb_define_const(cDaemon, "START_PENDING", INT2NUM(SERVICE_START_PENDING));
603
+
604
+ /* Service has received a signal to stop but has not yet stopped */
605
+ rb_define_const(cDaemon, "STOP_PENDING", INT2NUM(SERVICE_STOP_PENDING));
606
+
607
+ /* Service is stopped. */
608
+ rb_define_const(cDaemon, "STOPPED", INT2NUM(SERVICE_STOPPED));
609
+
610
+ /* Service is in an idle state */
611
+ rb_define_const(cDaemon, "IDLE", INT2NUM(0));
612
+ }