[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