# main.rb - the main module # # Copyright (C) 2002 Yoshinori K. Okuji # # This file is part of BugCommunicator. # # BugCommunicator is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # BugCommunicator is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with BugCommunicator; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA require 'getoptlong' require 'thread' require 'drb/drb' require 'acl' # Syslog isn't essential. 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 # BugCommunicator-specific libraries. require 'bugcomm/utils' require 'bugcomm/config' require 'bugcomm/service' module BugCommunicator # Initialize module variables. @@verbosity = 0 @@log = $stderr @@pid_file = nil @@config = nil # Run itself as a daemon. def self.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 # Initialize the environment. def self.init config_file = BUGCOMMD_CONFIG_FILE log_file = nil passwd = nil ask_passwd = false daemonized = false template_dir = DATADIR parser = GetoptLong.new parser.set_options(['--help', '-h', GetoptLong::NO_ARGUMENT], ['--version', '-V', GetoptLong::NO_ARGUMENT], ['--config', '-c', GetoptLong::REQUIRED_ARGUMENT], ['--template', '-T', 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 ' -T, --template=DIR use DIR as a template directory' puts ' -v, --verbose print verbose messages' puts ' -V, --version print version information and exit' puts '' puts 'Report bugs to .' exit 0 when '--version' puts "bugcommd (BugCommunicator @VERSION@)" exit 0 when '--verbose' @@verbosity += 1 when '--config' config_file = File.expand_path(arg) when '--template' template_dir = 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 = 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? @@config.template_dir = template_dir # 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 # Run as a daemon, if necessary. daemonize if daemonized end # Signal handler. def self.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| unless t[:critical] t.raise(ShutdownException, "shutdowned by a signal") end end sleep 1 unless group.list.empty? end group.list.each do |t| t.raise(ShutdownException, "forcibly shutdowned by a signal") end end info("interrupted by the signal `#{sig}'") exit end # Cleanup the log. def self.clean_up @@log.close if @@log != $stderr @@log = $stderr end # The main routine. def self.main # 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, Service.new(@@config), acl) info("starting the dRuby service at `#{uri}'") DRb.thread.join rescue NoMemoryError, ScriptError, StandardError => e error("#{e.message} (#{e.class})") e.backtrace.each do |s| error(s) end ensure if @@pid_file and File.exist?(@@pid_file) info("deleteing the pid file `#{@@pid_file}'") File.delete(@@pid_file) end end end