[Win32utils-devel] win32-daemon 0.6.1 problem
Heesob Park
phasis at gmail.com
Sun Nov 2 10:52:26 EST 2008
Hi,
2008/11/1 Berger, Daniel <Daniel.Berger at qwest.com>:
>
> Anyway, still no luck with running the Daemon itself. Now that we have
> callbacks working better in win32-api, I'm hoping to revisit the pure
> Ruby Win32::Daemon class again. :)
>
I struggled with the callback and service control whole weekend :)
I made the pure Ruby Win32::Daemon class as you expected.
Here are two patches and two ruby code.
First patch is trivial for callback support.
Second patch is a little messy. I added address attribute to callback class.
As you know, Win32 thread is not working well with Ruby.
So I implemented pure Ruby version as two separate processes without thread.
The daemon.rb and daemon0.rb must be in the same folder.
Some code cleanup and adding close handle code are required.
I think the startup argument notification need to reform.
I tried mmap and pipe but both failed with segment fault.
Patch for windows-pr/lib/windows/service.rb
--- service.rb.bak 2008-11-03 00:24:35.000000000 +0900
+++ service.rb 2008-11-03 00:24:35.000000000 +0900
@@ -128,7 +128,7 @@
API.new('QueryServiceLockStatus', 'LPLP', 'B', 'advapi32')
API.new('QueryServiceStatus', 'LP', 'B', 'advapi32')
API.new('QueryServiceStatusEx', 'LLPLP', 'B', 'advapi32')
- API.new('RegisterServiceCtrlHandler', 'PL', 'L', 'advapi32')
+ API.new('RegisterServiceCtrlHandler', 'PK', 'L', 'advapi32')
API.new('RegisterServiceCtrlHandlerEx', 'PPP', 'L', 'advapi32')
API.new('SetServiceBits', 'LLII', 'B', 'advapi32')
API.new('SetServiceStatus', 'LP', 'B', 'advapi32')
Patch for win32-api/ext/win32/api.c
--- api.c.old 2008-11-03 00:25:46.000000000 +0900
+++ api.c 2008-11-03 00:25:05.000000000 +0900
@@ -12,6 +12,7 @@
#define _T_STRING 5
VALUE cAPIError, cCallbackError;
+static VALUE ActiveCallback = Qnil;
typedef struct {
HANDLE library;
@@ -109,6 +110,7 @@
*/
static VALUE callback_init(int argc, VALUE* argv, VALUE self)
{
+ extern void *CallbackTable[];
VALUE v_proto, v_return, v_proc;
int i;
@@ -131,6 +133,8 @@
rb_iv_set(self, "@function", v_proc);
rb_iv_set(self, "@prototype", v_proto);
rb_iv_set(self, "@return_type", v_return);
+ rb_iv_set(self, "@address", ULONG2NUM((LPARAM)CallbackTable[RSTRING(v_proto)
+ ActiveCallback = self;
return self;
}
@@ -473,7 +477,6 @@
DWORD params[16];
} CALLPARAM;
-static VALUE ActiveCallback;
DWORD CallbackFunction(CALLPARAM param)
{
@@ -811,6 +814,7 @@
rb_define_attr(cCallback, "return_type", 1, 0);
/* The numeric address of the function pointer */
+ rb_define_attr(cCallback, "address", 1, 0);
rb_define_attr(cFunction, "address", 1, 0);
/* Constants */
======================= start daemon0.rb =======================
require 'windows/service'
require 'windows/thread'
require 'windows/synchronize'
require 'windows/handle'
require 'windows/error'
require 'windows/msvcrt/buffer'
require 'windows/msvcrt/string'
require 'win32/api'
include Windows::Service
include Windows::Thread
include Windows::Synchronize
include Windows::Handle
include Windows::Error
include Windows::MSVCRT::Buffer
include Windows::MSVCRT::String
# Wraps SetServiceStatus.
def set_the_service_statue(curr_state, exit_code, check_point, wait_hint)
service_status = 0.chr * 28 # sizeof(SERVICE_STATUS)
# Disable control requests until the service is started.
if curr_state == SERVICE_START_PENDING
val = 0
else
val = SERVICE_ACCEPT_STOP | SERVICE_ACCEPT_SHUTDOWN |
SERVICE_ACCEPT_PAUSE_CONTINUE | SERVICE_ACCEPT_SHUTDOWN
end
service_status[8,4] = [val].pack('L')
# Initialize service_status struct
service_status[0,4] = [SERVICE_WIN32_OWN_PROCESS].pack('L')
service_status[4,4] = [curr_state].pack('L')
service_status[12,4] = [exit_code].pack('L')
service_status[20,4] = [check_point].pack('L')
service_status[24,4] = [wait_hint].pack('L')
service_state = curr_state
# Send status of the service to the Service Controller.
unless SetServiceStatus(@status_handle, service_status)
SetEvent(@stop_event)
end
end
begin
@arg_event = OpenEvent(EVENT_ALL_ACCESS, 0, "arg_event_#{ARGV[0]}")
if @arg_event == 0
raise Error, get_last_error
end
@end_event = OpenEvent(EVENT_ALL_ACCESS, 0, "end_event_#{ARGV[0]}")
if @end_event == 0
raise Error, get_last_error
end
@stop_event = OpenEvent(EVENT_ALL_ACCESS, 0, "stop_event_#{ARGV[0]}")
if @stop_event == 0
raise Error, get_last_error
end
@pause_event = OpenEvent(EVENT_ALL_ACCESS, 0, "pause_event_#{ARGV[0]}")
if @pause_event == 0
raise Error, get_last_error
end
@resume_event = OpenEvent(EVENT_ALL_ACCESS, 0, "resume_event_#{ARGV[0]}")
if @resume_event == 0
raise Error, get_last_error
end
service_ctrl = Win32::API::Callback.new('L', 'V') do |ctrl_code|
state = SERVICE_RUNNING
case ctrl_code
when SERVICE_CONTROL_STOP
state = SERVICE_STOP_PENDING
when SERVICE_CONTROL_PAUSE
state = SERVICE_PAUSED
SetEvent(@pause_event)
when SERVICE_CONTROL_CONTINUE
state = SERVICE_RUNNING
SetEvent(@resume_event)
end
# Set the status of the service.
set_the_service_statue(state, NO_ERROR, 0, 0)
# Tell service_main thread to stop.
if [SERVICE_CONTROL_STOP].include?(ctrl_code)
wait_result = WaitForSingleObject(@end_event, INFINITE)
if wait_result != WAIT_OBJECT_0
set_the_service_statue(SERVICE_STOPPED,GetLastError(),0,0)
else
set_the_service_statue(SERVICE_STOPPED,NO_ERROR,0,0)
end
SetEvent(@stop_event)
sleep 3
end
end
svc_main = Win32::API::Callback.new('LL', 'I') do |argc, lpszargv|
argv = []
argc.times do |i|
ptr = 0.chr * 4
buf = 0.chr * 256
memcpy(ptr,lpszargv+i*4,4)
strcpy(buf,ptr.unpack('L').first)
argv << buf.strip
end
service_name = argv[0]
f = File.new(ENV['TEMP']+"\\daemon#{ARGV[0]}","w")
f.print(argv.join(';'))
f.close
SetEvent(@arg_event)
# Register the service ctrl handler.
@status_handle = RegisterServiceCtrlHandler(
service_name,service_ctrl
)
# No service to stop, no service handle to notify, nothing to do
# but exit at this point.
return false if @status_handle == 0
#The service has started.
set_the_service_statue(SERVICE_RUNNING, NO_ERROR, 0, 0)
true
end
dispatch_table = [""].pack('p') + [svc_main.address].pack('L') + "\0"*8
if(!StartServiceCtrlDispatcher(dispatch_table))
File.open("c:\\test.txt","a+") {|f|
f.puts("StartServiceCtrlDispatcher Fail")
}
end
rescue Exception => err
File.open("c:\\test.txt","a+") {|f|
f.puts(err.inspect)
}
end
======================= end daemon0.rb =======================
======================= start daemon.rb =======================
require 'windows/service'
require 'windows/thread'
require 'windows/synchronize'
require 'windows/handle'
require 'windows/error'
require 'windows/msvcrt/buffer'
require 'windows/msvcrt/string'
require 'win32/api'
require 'win32/process'
require 'win32/service'
module Win32
class Daemon
class Error < StandardError; end
include Windows::Service
include Windows::Thread
include Windows::Synchronize
include Windows::Handle
include Windows::Error
include Windows::MSVCRT::Buffer
include Windows::MSVCRT::String
VERSION = '0.6.1'
# The Daemon has received a resume signal, but is not yet running
CONTINUE_PENDING = 0x00000005
# The Daemon is in an idle state
IDLE = 0
# The Daemon is stopped, i.e. not running
STOPPED = 0x00000001
# The Daemon has received a start signal, but is not yet running
START_PENDING = 0x00000002
# The Daemon has received a sto signal but is not yet stopped.
STOP_PENDING = 0x00000003
# The Daemon is running normally
RUNNING = 0x00000004
# The Daemon has received a pause signal, but is not yet paused
PAUSE_PENDING = 0x00000006
# The Daemon is in a paused state
PAUSED = 0x00000007
# The current state of the service, e.g. RUNNING, PAUSED, etc.
attr_reader :state
def initialize
@critical_section = [0].pack('L')
@service_state = nil
@start_event = nil
@stop_event = nil
@control_code = 0
@event_hooks = {}
@status_handle = 0
end
def mainloop
@arg_event = CreateEvent(0, 0, 0, "arg_event_#{Process.pid}")
if @arg_event == 0
raise Error, get_last_error
end
@end_event = CreateEvent(0, 0, 0, "end_event_#{Process.pid}")
if @end_event == 0
raise Error, get_last_error
end
@stop_event = CreateEvent(0, 0, 0, "stop_event_#{Process.pid}")
if @stop_event == 0
raise Error, get_last_error
end
@pause_event = CreateEvent(0, 0, 0, "pause_event_#{Process.pid}")
if @pause_event == 0
raise Error, get_last_error
end
@resume_event = CreateEvent(0, 0, 0, "resume_event_#{Process.pid}")
if @resume_event == 0
raise Error, get_last_error
end
service_init() if defined?(service_init)
tmpfile = ENV['TEMP']+"\\daemon#{Process.pid}"
File.delete(tmpfile) if File.exist?(tmpfile)
ruby = File.join(CONFIG['bindir'], 'ruby ').tr('/', '\\')
path = File.dirname(__FILE__) + "//daemon0.rb #{Process.pid}"
Process.create(
:app_name => ruby + path,
:creation_flags => Process::CREATE_NO_WINDOW
)
wait_result = WaitForSingleObject(@arg_event, INFINITE)
if wait_result == WAIT_FAILED
error = 'WaitForSingleObject() failed: ' + get_last_error
raise Error, error
elsif File.exist?(tmpfile) && File.size(tmpfile)>0
f = File.open(tmpfile)
data = f.gets
f.close
else
data = ''
end
data = data || ''
argv = data.split(';')
@service_name = argv[0]
handles = [@stop_event, at pause_event, at resume_event]
t = Thread.new {
while(true) do
wait = WaitForMultipleObjects(
handles.size,
handles.pack('L*'),
0,
10
)
if wait >= WAIT_OBJECT_0 && wait <= WAIT_OBJECT_0 + handles.size - 1
index = wait - WAIT_OBJECT_0
case handles[index]
when @stop_event
service_stop() if defined?(service_stop)
return
when @pause_event
service_pause() if defined?(service_pause)
when @resume_event
service_resume() if defined?(service_resume)
end
end
end
}
service_main(argv) if defined?(service_main)
SetEvent(@end_event)
t.join
end
def running?
true if [RUNNING,START_PENDING].include?(state)
end
def state
case Win32::Service.status(@service_name).current_state
when 'running'
return RUNNING
when 'start pending'
return START_PENDING
when 'stopped'
return STOPPED
when 'paused'
return PAUSED
end
return 0
end
# Shortcut for Daemon.new#mainloop
def self.mainloop
new.mainloop
end
end
end
======================= end daemon.rb =======================
Regards,
Park Heesob
More information about the win32utils-devel
mailing list