win32-service 0.7.0 → 0.7.1

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