/[pupa]/bugcomm/src/bugcommd.in
ViewVC logotype

Diff of /bugcomm/src/bugcommd.in

Parent Directory Parent Directory | Revision Log Revision Log | View Patch Patch

revision 1.4 by okuji, Wed Aug 14 16:11:36 2002 UTC revision 1.5 by okuji, Wed Sep 11 10:01:33 2002 UTC
# Line 18  Line 18 
18  # along with Foobar; if not, write to the Free Software  # along with Foobar; if not, write to the Free Software
19  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA  # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20    
21  # Load modules.  require 'bugcomm/main'
22  require 'getoptlong'  require 'bugcomm/utils'
 require 'mysql'  
 require 'drb/drb'  
 require 'acl'  
 require 'cgi'  
23    
24  # Syslog isn't essential.  module BugCommunicator
 begin  
   require 'syslog'  
 rescue LoadError  
 end  
   
 # Define the method `shadow=' specific to $stdin, to hide a password.  
 begin  
   require 'termios'  
   def $stdin.shadow=(bool)  
     tios = Termios.getattr($stdin)  
     if bool  
       tios.c_lflag &= ~Termios::ECHO  
     else  
       tios.c_lflag |= Termios::ECHO  
     end  
     Termios.setattr($stdin, TCSANOW, tios)  
   end  
 rescue LoadError  
   def $stdin.shadow=(bool)  
     system('stty', if bool then '-echo' else 'echo' end)  
   end  
 end  
   
 # The master class.  
 class BugCommunicator  
   
   # Class variables.  
   @@verbosity = 0  
   @@log = $stderr  
     
   # Exceptions.  
   class BugCommError < Exception; end  
   
   # Helper functions.  
   def self.error(str)  
     if @@log.respond_to?(:err)  
       @@log.err('%s', str)  
     elsif @@log.respond_to?(:puts)  
       @@log.puts("bugcommd [#{Process.pid}]: error: #{str}")  
     end  
   end  
   
   def self.info(str)  
     if @@verbosity > 0  
       if @@log.respond_to?(:info)  
         @@log.info('%s', str)  
       elsif @@log.respond_to?(:puts)  
         @@log.puts("bugcommd [#{Process.pid}]: info: #{str}")  
       end  
     end  
   end  
   
   def self.debug(str)  
     if @@verbosity > 1  
       if @@log.respond_to?(:debug)  
         @@log.debug('%s', str)  
       elsif @@log.respond_to?(:puts)  
         @@log.puts("bugcommd [#{Process.pid}]: debug: #{str}")  
       end  
     end  
   end  
   
   def self.clean_up  
     @@log.close if @@log != $stderr  
     @@log = $stderr  
   end  
   # End of helper functions.  
     
   # Configuration Manager.  
   class Config  
     def initialize(file)  
       m = Module.new  
       m.module_eval(IO.readlines(file, nil).first, file)  
   
       # Set the defaults.  
       @host = 'localhost'  
       @port = 8844  
       @acl = nil  
       @size_limit = nil  
       @address = nil  
       @smtp_host = 'localhost'  
       @site_admins = nil  
       @url = nil  
       @db_host = 'localhost'  
       @db_name = 'bugcomm'  
       @db_user = 'bugcomm'  
       @db_password = nil  
   
       if defined?(m::HOST)  
         unless m::HOST.kind_of?(String)  
           raise TypeError, 'HOST must be a string'  
         end  
         @host = m::HOST  
       end  
   
       if defined?(m::PORT)  
         unless m::PORT.kind_of?(Integer)  
           raise TypeError, 'PORT must be a string'  
         end  
         @port = m::PORT  
       end  
   
       if defined?(m::ACL)  
         unless m::ACL.kind_of?(Array)  
           raise TypeError, 'ACL must be an array'  
         end  
         @acl = m::ACL  
       end  
   
       if defined?(m::SIZE_LIMIT)  
         unless m::SIZE_LIMIT.kind_of?(Integer)  
           raise TypeError, 'SIZE_LIMIT must be an integer'  
         end  
         @size_limit = m::SIZE_LIMIT  
       end  
   
       if defined?(m::ADDRESS)  
         unless m::ADDRESS.kind_of?(String)  
           raise TypeError, 'ADDRESS must be an string'  
         end  
         @address = m::ADDRESS  
       else  
         raise RuntimeError, 'ADDRESS must be defined'  
       end  
   
       if defined?(m::SMTP_HOST)  
         unless m::SMTP_HOST.kind_of?(String)  
           raise TypeError, 'SMTP_HOST must be an string'  
         end  
         @smtp_host = m::SMTP_HOST  
       end  
25    
26        if defined?(m::SITE_ADMINS)    # These constants will be replaced by the configure script.
27          unless m::SITE_ADMINS.kind_of?(Array)    BUGCOMM_VERSION = '@VERSION@'
28            raise TypeError, 'SITE_ADMINS must be an array'    BUGCOMMD_CONFIG_FILE = '@BUGCOMMD_CONFIG_FILE@'
29          end    DATADIR = '@datadir@'.sub(/$\{prefix\}/, '@prefix@')
         @site_admins = m::SITE_ADMINS  
       else  
         raise RuntimeError, 'SITE_ADMINS must be defined'  
       end  
   
       if defined?(m::URL)  
         unless m::URL.kind_of?(String)  
           raise TypeError, 'URL must be a string'  
         end  
         @url = m::URL  
       else  
         raise RuntimeError, 'URL must be defined'  
       end  
   
       if defined?(m::DB_HOST)  
         unless m::DB_HOST.kind_of?(String)  
           raise TypeError, 'DB_HOST must be an string'  
         end  
         @db_host = m::DB_HOST  
       end  
   
       if defined?(m::DB_NAME)  
         unless m::DB_NAME.kind_of?(String)  
           raise TypeError, 'DB_NAME must be an string'  
         end  
         @db_name = m::DB_NAME  
       end  
   
       if defined?(m::DB_USER)  
         unless m::DB_USER.kind_of?(String)  
           raise TypeError, 'DB_USER must be an string'  
         end  
         @db_user = m::DB_USER  
       end  
   
       if defined?(m::DB_PASSWORD)  
         unless m::DB_PASSWORD.kind_of?(String)  
           raise TypeError, 'DB_PASSWORD must be an string'  
         end  
         @db_password = m::DB_PASSWORD  
       end  
     end  
   
     attr_reader :host, :port, :acl, :size_limit  
     attr_reader :address, :smtp_host, :site_admins  
     attr_reader :url  
     attr_reader :db_host, :db_name, :db_user  
     attr_accessor :db_password  
   end  
   # End of class Config.  
   
   # Command Processor.  
   class Command  
     def initialize(config)  
       @config = config  
     end  
   
     # Helper method to disable signals temporarily.  
     def critical_region  
       t = Thread.current  
       begin  
         t[:critical] = true  
         yield  
       ensure  
         t[:critical] = false  
       end  
     end  
     private :critical_region  
   
     def h(str)  
       CGI.escapeHTML(str)  
     end  
     private :h  
   
     def u(str)  
       CGI.escape(str)  
     end  
     private :u  
   
     def error_log(exception)  
       error("#{exception.message} (#{exception.class})")  
       exception.backtrace.each do |s|  
         error(s)  
       end  
     end  
     private :error_log  
       
     def mail(message)  
       begin  
           
       rescue BugCommError, NoMemoryError, ScriptError, StandardError => e  
         error_log(e)  
         return "#{e.message} (#{e.class})\n" + e.backtrace.join("\n")  
       end  
   
       nil  
     end  
   
     def cgi(metavars, request)  
       begin  
           
       rescue BugCommError => e  
         error_log(e)  
         return "Content-Type: text/html\r\n\r\n" +  
           "<html><head><title>Error</title></head><body>\r\n" +  
           "<h1>Error</h1><p>#{h(e.message)}</p>\r\n" +  
           "</body></html>"  
       rescue NoMemoryError, ScriptError, StandardError => e  
         error_log(e)  
         return "Content-Type: text/html\r\n\r\n" +  
           "<html><head><title>Server internal error</title></head><body>\r\n" +  
           "<h1>Server internal error</h1>\r\n" +  
           "<pre>#{h(e.message)} (#{h(e.class)})\r\n" +  
           h(e.backtrace.join("\r\n")) + "</pre><p>\r\n" +  
           "Please send bug reports to " +  
           @config.site_admins.collect {|admin| h(admin)}.join(", ") +  
           ".</p></body></html>"  
       end  
     end  
   end  
   # End of class Command.  
   
   def initialize  
     @config_file = '@BUGCOMMD_CONFIG_FILE@'  
     @log_file = nil  
     @pid_file = nil  
     @passwd = nil  
     @ask_passwd = false  
     @daemonized = false  
   
     parser = GetoptLong.new  
     parser.set_options(['--help',      '-h', GetoptLong::NO_ARGUMENT],  
                        ['--version',   '-V', GetoptLong::NO_ARGUMENT],  
                        ['--config',    '-c', GetoptLong::REQUIRED_ARGUMENT],  
                        ['--log',       '-l', GetoptLong::REQUIRED_ARGUMENT],  
                        ['--daemon',    '-d', GetoptLong::NO_ARGUMENT],  
                        ['--pid-file',  '-p', GetoptLong::REQUIRED_ARGUMENT],  
                        ['--password',  '-P', GetoptLong::OPTIONAL_ARGUMENT],  
                        ['--verbose',   '-v', GetoptLong::NO_ARGUMENT])  
     # FIXME: should catch errors gracefully.  
     parser.each do |name, arg|  
       case name  
       when '--help'  
         puts 'Usage: bugcommd [OPTION]...'  
         puts ''  
         puts 'BugCommunicator server.'  
         puts ''  
         puts '    -c, --config=FILE        use FILE as a configuration file'  
         puts '    -d, --daemon             run as a daemon'  
         puts '    -h, --help               display this help and exit'  
         puts '    -p, --pid=FILE           write a process id to FILE'  
         puts '    -P, --password[=PASSWD]  set the database password'  
         puts '    -l, --log=FILE           write log messages to FILE'  
         puts '    -v, --verbose            print verbose messages'  
         puts '    -V, --version            print version information and exit'  
         puts ''  
         puts 'Report bugs to <okuji@enbug.org>.'  
         exit 0  
       when '--version'  
         puts "bugcommd (BugCommunicator @VERSION@)"  
         exit 0  
       when '--verbose'  
         @@verbosity += 1  
       when '--config'  
         @config_file = File.expand_path(arg)  
       when '--log'  
         @log_file = File.expand_path(arg)  
       when '--daemon'  
         @daemonized = true  
       when '--pid-file'  
         @pid_file = File.expand_path(arg)  
       when '--password'  
         if arg.empty?  
           @ask_passwd = true  
         else  
           @passwd = arg  
         end  
       end  
     end  
       
     # Initialize the configuration.  
     @config = BugCommunicator::Config.new(@config_file)  
       
     if @ask_passwd  
       begin  
         $stdin.shadow = true  
         $stderr.print "Password for the database user `#{@config.db_user}': "  
         @passwd = gets.chomp  
         $stderr.print "\n"  
       ensure  
         $stdin.shadow = false  
       end  
     end  
       
     @config.db_password = @passwd unless @passwd.nil?  
   
     # Trap some signals.  
     trap(:INT) { handler('SIGINT') }  
     trap(:HUP) { handler('SIGHUP') }  
     trap(:TERM) { handler('SIGTERM') }  
     trap(:USR1, "IGNORE")  
     trap(:USR2, "IGNORE")  
       
     # Open the log.  
     if @log_file.nil? and @daemonized  
       unless defined?(Syslog)  
         raise "you must specify the option `--log=FILE', because UNIX syslog interface for Ruby is not available"  
       end  
         
       @@log = Syslog.open('bugcommd',  
                           Syslog::Constants::LOG_PID |  
                           Syslog::Constants::LOG_CONS,  
                           Syslog::Constants::LOG_DAEMON)  
     else  
       unless @log_file.nil?  
         @@log = File.open(@log_file, 'a')  
       end  
     end  
   end  
   
   # Signal handler.  
   def handler(sig)  
     s = DRb.primary_server  
     unless s.nil?  
       s.stop_service  
         
       group = ThreadGroup.new  
       Thread.list.each do |t|  
         next if t == Thread.current or t == Thread.main or t == s.thread  
         group.add(t)  
       end  
         
       10.times do |i|  
         group.list.each do |t|  
           t.kill unless t[:critical]  
         end  
           
         sleep 1 unless group.list.empty?  
       end  
         
       group.list.each do |t|  
         t.kill  
       end  
     end  
       
     self.class.info("interrupted by the signal `#{sig}'")  
     exit  
   end  
   private :handler  
     
   # Run itself as a daemon.  
   def daemonize  
     exit! if fork  
       
     Process.setsid  
     if pid = fork  
       unless @pid_file.nil?  
         File.open(@pid_file, 'w') do |f|  
           f.write pid  
         end  
       end  
       exit!  
     end  
       
     Dir.chdir('/')  
     File.umask 0  
     $stdin.close  
     $stdout.close  
     $stderr.close  
   end  
   private :daemonize  
     
   # The main routine.  
   def main  
     begin  
       # Run as a daemon, if necessary.  
       daemonize if @daemonized  
         
       # Run a dRuby service.  
       acl = ACL.new(@config.acl, ACL::DENY_ALLOW)  
       uri = "druby://#{@config.host}:#{@config.port}"  
       DRb::DRbServer.default_argc_limit(2)  
       unless @config.size_limit.nil?  
         DRb::DRbServer.default_load_limit(@config.size_limit)  
       end  
       DRb::DRbServer.new(uri, Command.new(@config), acl)  
       self.class.info("starting the dRuby service at `#{uri}'")  
       DRb.thread.join  
     rescue BugCommError, NoMemoryError, ScriptError, StandardError => e  
       self.class.error("#{e.message} (#{e.class})")  
       e.backtrace.each do |s|  
         self.class.error(s)  
       end  
     ensure  
       if @pid_file and File.exist?(@pid_file)  
         self.class.info("deleteing the pid file `#{@pid_file}'")  
         File.delete(@pid_file)  
       end  
     end  
   end  
30        
31  end  end
32    
33  begin  begin
34    BugCommunicator.debug('initializing the server')    BugCommunicator.debug('initializing the server')
35    bugcomm = BugCommunicator.new    BugCommunicator.init()
36    BugCommunicator.info('starting the service')    BugCommunicator.info('starting the service')
37    bugcomm.main    BugCommunicator.main()
38  ensure  ensure
39    BugCommunicator.info('stopping the service')    BugCommunicator.info('stopping the service')
40    BugCommunicator.clean_up    BugCommunicator.clean_up()
41  end  end

Legend:
Removed from v.1.4  
changed lines
  Added in v.1.5

savannah-hackers-public@gnu.org
ViewVC Help
Powered by ViewVC 1.1.26