win32-service 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ require 'mkmf'
2
+
3
+ dir_config('win32-daemon')
4
+ have_func('RegisterServiceCtrlHandlerEx')
5
+ create_makefile('win32/daemon', 'win32')
@@ -0,0 +1,592 @@
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.6.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
+ 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
+ // Redirect STDIN, STDOUT and STDERR to the NUL device if they're still
85
+ // associated with a tty. This helps newbs avoid Errno::EBADF errors.
86
+ if(rb_funcall(rb_stdin, rb_intern("isatty"), 0) == Qtrue)
87
+ rb_funcall(rb_stdin, rb_intern("reopen"), 1, rb_str_new2("NUL"));
88
+
89
+ if(rb_funcall(rb_stdout, rb_intern("isatty"), 0) == Qtrue)
90
+ rb_funcall(rb_stdout, rb_intern("reopen"), 1, rb_str_new2("NUL"));
91
+
92
+ if(rb_funcall(rb_stderr, rb_intern("isatty"), 0) == Qtrue)
93
+ rb_funcall(rb_stderr, rb_intern("reopen"), 1, rb_str_new2("NUL"));
94
+
95
+ // no service to stop, no service handle to notify, nothing to do but exit
96
+ if(ssh == (SERVICE_STATUS_HANDLE)0)
97
+ return;
98
+
99
+ // The service has started.
100
+ SetTheServiceStatus(SERVICE_RUNNING, NO_ERROR, 0, 0);
101
+
102
+ SetEvent(hStartEvent);
103
+
104
+ // Main loop for the service.
105
+ while(WaitForSingleObject(hStopEvent, 1000) != WAIT_OBJECT_0)
106
+ {
107
+ }
108
+
109
+ // Main loop for the service.
110
+ while(WaitForSingleObject(hStopCompletedEvent, 1000) != WAIT_OBJECT_0)
111
+ {
112
+ }
113
+
114
+ // Stop the service.
115
+ SetTheServiceStatus(SERVICE_STOPPED, NO_ERROR, 0, 0);
116
+ }
117
+
118
+ VALUE Service_Event_Dispatch(VALUE val)
119
+ {
120
+ VALUE func,self;
121
+ VALUE result = Qnil;
122
+
123
+ if(val!=Qnil) {
124
+ self = RARRAY(val)->ptr[0];
125
+ func = NUM2INT(RARRAY(val)->ptr[1]);
126
+
127
+ result = rb_funcall(self,func,0);
128
+ }
129
+
130
+ return result;
131
+ }
132
+
133
+ VALUE Ruby_Service_Ctrl(VALUE self)
134
+ {
135
+ while (WaitForSingleObject(hStopEvent,0) == WAIT_TIMEOUT)
136
+ {
137
+ __try
138
+ {
139
+ EnterCriticalSection(&csControlCode);
140
+
141
+ // Check to see if anything interesting has been signaled
142
+ if (waiting_control_code != IDLE_CONTROL_CODE)
143
+ {
144
+ if (waiting_control_code != SERVICE_CONTROL_STOP) {
145
+ // if there is a code, create a ruby thread to deal with it
146
+ // this might be over engineering the solution, but I don't
147
+ // want to block Service_Ctrl longer than necessary and the
148
+ // critical section will block it.
149
+ VALUE EventHookHash = rb_ivar_get(self, rb_intern("@event_hooks"));
150
+
151
+ if(EventHookHash != Qnil){
152
+ VALUE val = rb_hash_aref(EventHookHash, INT2NUM(waiting_control_code));
153
+
154
+ if(val != Qnil)
155
+ rb_thread_create(Service_Event_Dispatch, (void*) val);
156
+ }
157
+ }
158
+ else {
159
+ break;
160
+ }
161
+ waiting_control_code = IDLE_CONTROL_CODE;
162
+ }
163
+ }
164
+ __finally
165
+ {
166
+ LeaveCriticalSection(&csControlCode);
167
+ }
168
+
169
+ // This is an ugly polling loop, be as polite as possible
170
+ rb_thread_polling();
171
+ }
172
+
173
+ // force service_stop call
174
+ {
175
+ VALUE EventHookHash = rb_ivar_get(self, rb_intern("@event_hooks"));
176
+
177
+ if(EventHookHash != Qnil){
178
+ VALUE val = rb_hash_aref(EventHookHash, INT2NUM(SERVICE_CONTROL_STOP));
179
+
180
+ if(val!=Qnil)
181
+ rb_thread_create(Service_Event_Dispatch, (void*) val);
182
+ }
183
+ }
184
+
185
+ return Qnil;
186
+ }
187
+
188
+ // Handles control signals from the service control manager.
189
+ void WINAPI Service_Ctrl(DWORD dwCtrlCode)
190
+ {
191
+ DWORD dwState = SERVICE_RUNNING;
192
+
193
+ // hard to image this code ever failing, so we probably
194
+ // don't need the __try/__finally wrapper
195
+ __try
196
+ {
197
+ EnterCriticalSection(&csControlCode);
198
+ waiting_control_code = dwCtrlCode;
199
+ }
200
+ __finally
201
+ {
202
+ LeaveCriticalSection(&csControlCode);
203
+ }
204
+
205
+ switch(dwCtrlCode)
206
+ {
207
+ case SERVICE_CONTROL_STOP:
208
+ dwState = SERVICE_STOP_PENDING;
209
+ break;
210
+
211
+ case SERVICE_CONTROL_SHUTDOWN:
212
+ dwState = SERVICE_STOP_PENDING;
213
+ break;
214
+
215
+ case SERVICE_CONTROL_PAUSE:
216
+ dwState = SERVICE_PAUSED;
217
+ break;
218
+
219
+ case SERVICE_CONTROL_CONTINUE:
220
+ dwState = SERVICE_RUNNING;
221
+ break;
222
+
223
+ case SERVICE_CONTROL_INTERROGATE:
224
+ break;
225
+
226
+ default:
227
+ break;
228
+ }
229
+
230
+ // Set the status of the service.
231
+ SetTheServiceStatus(dwState, NO_ERROR, 0, 0);
232
+
233
+ // Tell service_main thread to stop.
234
+ if ((dwCtrlCode == SERVICE_CONTROL_STOP) ||
235
+ (dwCtrlCode == SERVICE_CONTROL_SHUTDOWN))
236
+ {
237
+ if(!SetEvent(hStopEvent))
238
+ SetTheServiceStatus(SERVICE_STOPPED, GetLastError(), 0, 0);
239
+ }
240
+ }
241
+
242
+ // Wraps SetServiceStatus.
243
+ void SetTheServiceStatus(DWORD dwCurrentState, DWORD dwWin32ExitCode,
244
+ DWORD dwCheckPoint, DWORD dwWaitHint)
245
+ {
246
+ SERVICE_STATUS ss; // Current status of the service.
247
+
248
+ // Disable control requests until the service is started.
249
+ if(dwCurrentState == SERVICE_START_PENDING){
250
+ ss.dwControlsAccepted = 0;
251
+ }
252
+ else{
253
+ ss.dwControlsAccepted =
254
+ SERVICE_ACCEPT_STOP|SERVICE_ACCEPT_SHUTDOWN|
255
+ SERVICE_ACCEPT_PAUSE_CONTINUE|SERVICE_ACCEPT_SHUTDOWN;
256
+ }
257
+
258
+ // Initialize ss structure.
259
+ ss.dwServiceType = SERVICE_WIN32_OWN_PROCESS;
260
+ ss.dwServiceSpecificExitCode = 0;
261
+ ss.dwCurrentState = dwCurrentState;
262
+ ss.dwWin32ExitCode = dwWin32ExitCode;
263
+ ss.dwCheckPoint = dwCheckPoint;
264
+ ss.dwWaitHint = dwWaitHint;
265
+
266
+ dwServiceState = dwCurrentState;
267
+
268
+ // Send status of the service to the Service Controller.
269
+ if(!SetServiceStatus(ssh, &ss))
270
+ SetEvent(hStopEvent);
271
+ }
272
+
273
+ DWORD WINAPI ThreadProc(LPVOID lpParameter){
274
+ SERVICE_TABLE_ENTRY ste[] =
275
+ {{TEXT(""),(LPSERVICE_MAIN_FUNCTION)Service_Main}, {NULL, NULL}};
276
+
277
+ // No service to step, no service handle, no ruby exceptions, just
278
+ // terminate the thread.
279
+ if(!StartServiceCtrlDispatcher(ste))
280
+ return 1;
281
+
282
+ return 0;
283
+ }
284
+
285
+ static VALUE daemon_allocate(VALUE klass){
286
+ return Data_Wrap_Struct(klass, 0, 0, 0);
287
+ }
288
+
289
+
290
+ // Call service_main method
291
+ static VALUE daemon_mainloop_protect(VALUE self)
292
+ {
293
+ if(rb_respond_to(self,rb_intern("service_main"))){
294
+ if(Argc == 0)
295
+ rb_funcall(self, rb_intern("service_main"), 0);
296
+ else
297
+ rb_funcall2(self, rb_intern("service_main"), Argc, Argv);
298
+ }
299
+
300
+ return self;
301
+ }
302
+
303
+ static VALUE daemon_mainloop_ensure(VALUE self)
304
+ {
305
+ int i;
306
+
307
+ // Signal both the ruby thread and service_main thread to terminate
308
+ SetEvent(hStopEvent);
309
+
310
+ // Wait for ALL ruby threads to exit
311
+ for(i=1; TRUE; i++)
312
+ {
313
+ VALUE list = rb_funcall(rb_cThread, rb_intern("list"), 0);
314
+
315
+ if(RARRAY(list)->len <= 1)
316
+ break;
317
+
318
+ // This is another ugly polling loop, be as polite as possible
319
+ rb_thread_polling();
320
+
321
+ SetTheServiceStatus(SERVICE_STOP_PENDING, 0, i, 1000);
322
+ }
323
+
324
+ // Only one ruby thread
325
+ SetEvent(hStopCompletedEvent);
326
+
327
+ // Wait for the thread to stop BEFORE we close the hStopEvent handle
328
+ WaitForSingleObject(hThread, INFINITE);
329
+
330
+ // Close the event handle, ignoring failures. We may be cleaning up
331
+ // after an exception, so let that exception fall through.
332
+ CloseHandle(hStopEvent);
333
+
334
+ return self;
335
+ }
336
+
337
+ /*
338
+ * This is the method that actually puts your code into a loop and allows it
339
+ * to run as a service. The code that is actually run while in the mainloop
340
+ * is what you defined in your own Daemon#service_main method.
341
+ */
342
+ static VALUE daemon_mainloop(VALUE self)
343
+ {
344
+ DWORD ThreadId;
345
+ HANDLE events[2];
346
+ DWORD index;
347
+ VALUE result, EventHookHash;
348
+ int status = 0;
349
+
350
+ dwServiceState = 0;
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
+ VALUE mWin32, cDaemon;
541
+
542
+ // Modules and classes
543
+ mWin32 = rb_define_module("Win32");
544
+ cDaemon = rb_define_class_under(mWin32, "Daemon", rb_cObject);
545
+ cDaemonError = rb_define_class_under(cDaemon, "Error", rb_eStandardError);
546
+
547
+ rb_define_alloc_func(cDaemon, daemon_allocate);
548
+ rb_define_method(cDaemon, "mainloop", daemon_mainloop, 0);
549
+ rb_define_method(cDaemon, "state", daemon_state, 0);
550
+ rb_define_method(cDaemon, "running?", daemon_is_running, 0);
551
+
552
+ rb_define_singleton_method(cDaemon, "mainloop", daemon_c_mainloop, 0);
553
+
554
+ // Intialize critical section used by green polling thread
555
+ InitializeCriticalSection(&csControlCode);
556
+
557
+ // Constants
558
+
559
+ /* 0.6.0: The version of this library */
560
+ rb_define_const(cDaemon, "VERSION", rb_str_new2(WIN32_SERVICE_VERSION));
561
+
562
+ /* Service has received a signal to resume but is not yet running */
563
+ rb_define_const(cDaemon, "CONTINUE_PENDING",
564
+ INT2NUM(SERVICE_CONTINUE_PENDING));
565
+
566
+ /* Service has received a signal to pause but is not yet paused */
567
+ rb_define_const(cDaemon, "PAUSE_PENDING",
568
+ INT2NUM(SERVICE_PAUSE_PENDING));
569
+
570
+ /* Service is in a paused state */
571
+ rb_define_const(cDaemon, "PAUSED",
572
+ INT2NUM(SERVICE_PAUSED));
573
+
574
+ /* Service is running */
575
+ rb_define_const(cDaemon, "RUNNING",
576
+ INT2NUM(SERVICE_RUNNING));
577
+
578
+ /* Service has received a signal to start but is not yet running */
579
+ rb_define_const(cDaemon, "START_PENDING",
580
+ INT2NUM(SERVICE_START_PENDING));
581
+
582
+ /* Service has received a signal to stop but has not yet stopped */
583
+ rb_define_const(cDaemon, "STOP_PENDING",
584
+ INT2NUM(SERVICE_STOP_PENDING));
585
+
586
+ /* Service is stopped. */
587
+ rb_define_const(cDaemon, "STOPPED",
588
+ INT2NUM(SERVICE_STOPPED));
589
+
590
+ /* Service is in an idle state */
591
+ rb_define_const(cDaemon, "IDLE", INT2NUM(0));
592
+ }