24 |
require 'drb/drb' |
require 'drb/drb' |
25 |
require 'acl' |
require 'acl' |
26 |
require 'cgi' |
require 'cgi' |
27 |
|
require 'syslog' |
28 |
|
|
29 |
# Define the method `shadow=' specific to $stdin, to hide a password. |
# Define the method `shadow=' specific to $stdin, to hide a password. |
30 |
begin |
begin |
44 |
end |
end |
45 |
end |
end |
46 |
|
|
47 |
# Define some classes under the module `BugCommunicator'. |
# The master class. |
48 |
module BugCommunicator |
class BugCommunicator |
49 |
|
|
50 |
|
# Class variables. |
51 |
|
@@verbosity = 0 |
52 |
|
@@log = $stderr |
53 |
|
|
54 |
|
# Exceptions. |
55 |
class BugCommError < Exception; end |
class BugCommError < Exception; end |
56 |
|
|
57 |
|
# Helper functions. |
58 |
|
def self.error(str) |
59 |
|
if @@log.respond_to?(:err) |
60 |
|
@@log.err('%s', str) |
61 |
|
elsif @@log.respond_to?(:puts) |
62 |
|
@@log.puts("bugcommd [#{Process.pid}]: error: #{str}") |
63 |
|
end |
64 |
|
end |
65 |
|
|
66 |
|
def self.info(str) |
67 |
|
if @@verbosity > 0 |
68 |
|
if @@log.respond_to?(:info) |
69 |
|
@@log.info('%s', str) |
70 |
|
elsif @@log.respond_to?(:puts) |
71 |
|
@@log.puts("bugcommd [#{Process.pid}]: info: #{str}") |
72 |
|
end |
73 |
|
end |
74 |
|
end |
75 |
|
|
76 |
|
def self.debug(str) |
77 |
|
if @@verbosity > 1 |
78 |
|
if @@log.respond_to?(:debug) |
79 |
|
@@log.debug('%s', str) |
80 |
|
elsif @@log.respond_to?(:puts) |
81 |
|
@@log.puts("bugcommd [#{Process.pid}]: debug: #{str}") |
82 |
|
end |
83 |
|
end |
84 |
|
end |
85 |
|
|
86 |
|
def self.clean_up |
87 |
|
@@log.close if @@log != $stderr |
88 |
|
@@log = $stderr |
89 |
|
end |
90 |
|
# End of helper functions. |
91 |
|
|
92 |
# Configuration Manager. |
# Configuration Manager. |
93 |
class Config |
class Config |
237 |
private :u |
private :u |
238 |
|
|
239 |
def error_log(exception) |
def error_log(exception) |
240 |
error("#{exception.message} (#{exception.class})\n" + |
error("#{exception.message} (#{exception.class})") |
241 |
exception.backtrace.join("\n")) |
exception.backtrace.each do |s| |
242 |
|
error(s) |
243 |
|
end |
244 |
end |
end |
245 |
private :error_log |
private :error_log |
246 |
|
|
278 |
end |
end |
279 |
end |
end |
280 |
# End of class Command. |
# End of class Command. |
|
|
|
|
end |
|
281 |
|
|
282 |
# Constants. |
def initialize |
283 |
BUGCOMM_VERSION = '@VERSION@' |
@config_file = '@BUGCOMMD_CONFIG_FILE@' |
284 |
|
@log_file = nil |
285 |
|
@pid_file = nil |
286 |
|
@passwd = nil |
287 |
|
@ask_passwd = false |
288 |
|
@daemonized = false |
289 |
|
|
290 |
|
parser = GetoptLong.new |
291 |
|
parser.set_options(['--help', '-h', GetoptLong::NO_ARGUMENT], |
292 |
|
['--version', '-V', GetoptLong::NO_ARGUMENT], |
293 |
|
['--config', '-c', GetoptLong::REQUIRED_ARGUMENT], |
294 |
|
['--log', '-l', GetoptLong::REQUIRED_ARGUMENT], |
295 |
|
['--daemon', '-d', GetoptLong::NO_ARGUMENT], |
296 |
|
['--pid-file', '-p', GetoptLong::REQUIRED_ARGUMENT], |
297 |
|
['--password', '-P', GetoptLong::OPTIONAL_ARGUMENT], |
298 |
|
['--verbose', '-v', GetoptLong::NO_ARGUMENT]) |
299 |
|
# FIXME: should catch errors gracefully. |
300 |
|
parser.each do |name, arg| |
301 |
|
case name |
302 |
|
when '--help' |
303 |
|
puts 'Usage: bugcommd [OPTION]...' |
304 |
|
puts '' |
305 |
|
puts 'BugCommunicator server.' |
306 |
|
puts '' |
307 |
|
puts ' -c, --config=FILE use FILE as a configuration file' |
308 |
|
puts ' -d, --daemon run as a daemon' |
309 |
|
puts ' -h, --help display this help and exit' |
310 |
|
puts ' -p, --pid=FILE write a process id to FILE' |
311 |
|
puts ' -P, --password[=PASSWD] set the database password' |
312 |
|
puts ' -l, --log=FILE write log messages to FILE' |
313 |
|
puts ' -v, --verbose print verbose messages' |
314 |
|
puts ' -V, --version print version information and exit' |
315 |
|
puts '' |
316 |
|
puts 'Report bugs to <okuji@enbug.org>.' |
317 |
|
exit 0 |
318 |
|
when '--version' |
319 |
|
puts "bugcommd (BugCommunicator @VERSION@)" |
320 |
|
exit 0 |
321 |
|
when '--verbose' |
322 |
|
@@verbosity += 1 |
323 |
|
when '--config' |
324 |
|
@config_file = File.expand_path(arg) |
325 |
|
when '--log' |
326 |
|
@log_file = File.expand_path(arg) |
327 |
|
when '--daemon' |
328 |
|
@daemonized = true |
329 |
|
when '--pid-file' |
330 |
|
@pid_file = File.expand_path(arg) |
331 |
|
when '--password' |
332 |
|
if arg.empty? |
333 |
|
@ask_passwd = true |
334 |
|
else |
335 |
|
@passwd = arg |
336 |
|
end |
337 |
|
end |
338 |
|
end |
339 |
|
|
340 |
|
# Initialize the configuration. |
341 |
|
@config = BugCommunicator::Config.new(@config_file) |
342 |
|
|
343 |
|
if @ask_passwd |
344 |
|
begin |
345 |
|
$stdin.shadow = true |
346 |
|
$stderr.print "Password for the database user `#{@config.db_user}': " |
347 |
|
@passwd = gets.chomp |
348 |
|
$stderr.print "\n" |
349 |
|
ensure |
350 |
|
$stdin.shadow = false |
351 |
|
end |
352 |
|
end |
353 |
|
|
354 |
|
@config.db_password = @passwd unless @passwd.nil? |
355 |
|
|
356 |
# Variables. |
# Trap some signals. |
357 |
$verbosity = 0 |
trap(:INT) { handler('SIGINT') } |
358 |
$log = nil |
trap(:HUP) { handler('SIGHUP') } |
359 |
|
trap(:TERM) { handler('SIGTERM') } |
360 |
config_file = '@BUGCOMMD_CONFIG_FILE@' |
trap(:USR1, "IGNORE") |
361 |
log_file = nil |
trap(:USR2, "IGNORE") |
362 |
pid_file = nil |
|
363 |
passwd = nil |
# Open the log. |
364 |
ask_passwd = false |
if @log_file.nil? and @daemonized |
365 |
daemonized = false |
@@log = Syslog.open('bugcommd', |
366 |
|
Syslog::Constants::LOG_PID | |
367 |
## Start the main routine. |
Syslog::Constants::LOG_CONS, |
368 |
# Parse arguments. |
Syslog::Constants::LOG_DAEMON) |
|
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 #{BUGCOMM_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 |
|
369 |
else |
else |
370 |
passwd = arg |
unless @log_file.nil? |
371 |
|
@@log = File.open(@log_file, 'a') |
372 |
|
end |
373 |
end |
end |
374 |
end |
end |
|
end |
|
|
|
|
|
# Initialize the configuration. |
|
|
config = BugCommunicator::Config.new(config_file) |
|
|
|
|
|
if ask_passwd |
|
|
begin |
|
|
$stdin.shadow = true |
|
|
$stderr.print "Enter the 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? |
|
|
|
|
|
# Open the log. |
|
|
if log_file.nil? and daemonized |
|
|
require 'syslog' |
|
|
$log = Syslog.open('bugcommd', |
|
|
Syslog::Constants::LOG_PID | |
|
|
Syslog::Constants::LOG_CONS, |
|
|
Syslog::Constants::LOG_DAEMON) |
|
|
else |
|
|
if log_file.nil? |
|
|
$log = $stderr |
|
|
else |
|
|
$log = File.open(log_file, 'a') |
|
|
end |
|
|
|
|
|
def $log.err(str, *args) |
|
|
self.puts("bugcommd [error]: " + sprintf(str, *args)) |
|
|
end |
|
|
def $log.info(str, *args) |
|
|
self.puts("bugcommd [info]: " + sprintf(str, *args)) |
|
|
end |
|
|
def $log.debug(str, *args) |
|
|
self.puts("bugcommd [debug]: " + sprintf(str, *args)) |
|
|
end |
|
|
end |
|
375 |
|
|
|
at_exit { $log.close } |
|
|
|
|
|
def error(str) |
|
|
$log.err('%s', str) |
|
|
end |
|
|
|
|
|
def info(str) |
|
|
$log.info('%s', str) if $verbosity > 0 |
|
|
end |
|
|
|
|
|
def debug(str) |
|
|
$log.debug('%s', str) if $verbosity > 1 |
|
|
end |
|
|
|
|
|
begin |
|
376 |
# Signal handler. |
# Signal handler. |
377 |
def handler(sig) |
def handler(sig) |
378 |
s = DRb.primary_server |
s = DRb.primary_server |
384 |
next if t == Thread.current or t == Thread.main or t == s.thread |
next if t == Thread.current or t == Thread.main or t == s.thread |
385 |
group.add(t) |
group.add(t) |
386 |
end |
end |
387 |
|
|
388 |
10.times do |i| |
10.times do |i| |
389 |
group.list.each do |t| |
group.list.each do |t| |
390 |
t.kill unless t[:critical] |
t.kill unless t[:critical] |
391 |
end |
end |
392 |
|
|
393 |
sleep 1 unless group.list.empty? |
sleep 1 unless group.list.empty? |
394 |
end |
end |
395 |
|
|
396 |
group.list.each do |t| |
group.list.each do |t| |
397 |
t.kill |
t.kill |
398 |
end |
end |
399 |
end |
end |
400 |
|
|
401 |
info("interrupted by the signal `#{sig}'") |
self.class.info("interrupted by the signal `#{sig}'") |
402 |
exit |
exit |
403 |
end |
end |
404 |
|
private :handler |
|
trap(:INT) { handler('SIGINT') } |
|
|
trap(:HUP) { handler('SIGHUP') } |
|
|
trap(:TERM) { handler('SIGTERM') } |
|
|
trap(:USR1, "IGNORE") |
|
|
trap(:USR2, "IGNORE") |
|
405 |
|
|
406 |
# Run as a daemon, if necessary. |
# Run itself as a daemon. |
407 |
if daemonized |
def daemonize |
408 |
exit! if fork |
exit! if fork |
409 |
|
|
410 |
Process.setsid |
Process.setsid |
411 |
if pid = fork |
if pid = fork |
412 |
unless pid_file.nil? |
unless @pid_file.nil? |
413 |
File.open(pid_file, 'w') do |f| |
File.open(@pid_file, 'w') do |f| |
414 |
f.write pid |
f.write pid |
415 |
end |
end |
416 |
end |
end |
423 |
$stdout.close |
$stdout.close |
424 |
$stderr.close |
$stderr.close |
425 |
end |
end |
426 |
|
private :daemonize |
427 |
|
|
428 |
|
# The main routine. |
429 |
|
def main |
430 |
|
begin |
431 |
|
# Run as a daemon, if necessary. |
432 |
|
daemonize if @daemonized |
433 |
|
|
434 |
|
# Run a dRuby service. |
435 |
|
acl = ACL.new(@config.acl, ACL::DENY_ALLOW) |
436 |
|
uri = "druby://#{@config.host}:#{@config.port}" |
437 |
|
DRb::DRbServer.default_argc_limit(2) |
438 |
|
unless @config.size_limit.nil? |
439 |
|
DRb::DRbServer.default_load_limit(@config.size_limit) |
440 |
|
end |
441 |
|
DRb::DRbServer.new(uri, Command.new(@config), acl) |
442 |
|
self.class.info("starting the dRuby service at `#{uri}'") |
443 |
|
DRb.thread.join |
444 |
|
rescue BugCommError, NoMemoryError, ScriptError, StandardError => e |
445 |
|
self.class.error("#{e.message} (#{e.class})") |
446 |
|
e.backtrace.each do |s| |
447 |
|
self.class.error(s) |
448 |
|
end |
449 |
|
ensure |
450 |
|
if @pid_file and File.exist?(@pid_file) |
451 |
|
self.class.info("deleteing the pid file `#{@pid_file}'") |
452 |
|
File.delete(@pid_file) |
453 |
|
end |
454 |
|
end |
455 |
|
end |
456 |
|
|
457 |
|
end |
458 |
|
|
459 |
# Run a dRuby service. |
begin |
460 |
acl = ACL.new(config.acl, ACL::DENY_ALLOW) |
BugCommunicator.debug('initializing the server') |
461 |
uri = "druby://#{config.host}:#{config.port}" |
bugcomm = BugCommunicator.new |
462 |
begin |
BugCommunicator.info('starting the service') |
463 |
s = DRb::DRbServer.new(uri, BugCommunicator::Command.new(config), acl) |
bugcomm.main |
|
s.argc_limit = 2 |
|
|
s.load_limit = config.size_limit unless config.size_limit.nil? |
|
|
rescue SignalException, Interrupt |
|
|
end |
|
|
DRb.thread.join |
|
|
rescue BugCommunicator::BugCommError, NoMemoryError, |
|
|
ScriptError, StandardError => e |
|
|
error("#{e.message} (#{e.class})") |
|
|
error(e.backtrace.join("\n")) |
|
464 |
ensure |
ensure |
465 |
File.delete(pid_file) if pid_file and File.exist?(pid_file) |
BugCommunicator.info('stopping the service') |
466 |
|
BugCommunicator.clean_up |
467 |
end |
end |