win32-service 0.7.0-x86-mswin32-60 → 0.7.1-x86-mswin32-60

Sign up to get free protection for your applications and to get access to all the features.
data/ext/win32/daemon.c CHANGED
@@ -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
+ }