# server.rb --- A simple IPC server executing Ruby programs. # Copyright (C) 1998-2005 Daiki Ueno # Author: Daiki Ueno # Created: 1998-09-28 # Keywords: IRC, riece, Ruby # This file is part of Riece. # This program 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, or (at your option) # any later version. # This program 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 GNU Emacs; see the file COPYING. If not, write to the # Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, # Boston, MA 02110-1301, USA. require 'thread' require 'stringio' class Server def initialize(outfile, errfile, logfile) @out = $stdout @err = $stderr $stdout = outfile ? File.new(outfile, 'a') : StringIO.new $stderr = errfile ? File.new(errfile, 'a') : StringIO.new @log = File.new(logfile, 'a') if logfile @buf = '' @que = Queue.new @thr = Hash.new @cnt = 0 end def dispatch(line) @log.puts(line) if @log case line.chomp when /\AD / @buf << $' when /\A(\S+)\s*/ c = $1 r = $' d = "dispatch_#{c.downcase}" if respond_to?(d, true) Thread.start do self.send(d, c, r) end else send_line("ERR 103 Unknown command\r\n") end end end def dispatch_cancel(c, r) send_line("ERR 100 Not implemented\r\n") end def dispatch_bye(c, r) send_line("ERR 100 Not implemented\r\n") end def dispatch_auth(c, r) send_line("ERR 100 Not implemented\r\n") end def dispatch_reset(c, r) send_line("ERR 100 Not implemented\r\n") end def dispatch_end(c, r) enq_data end def dispatch_help(c, r) send_line("ERR 100 Not implemented\r\n") end def dispatch_quit(c, r) send_line("ERR 100 Not implemented\r\n") end def dispatch_eval(c, r) r = deq_data if r.empty? name = nil Thread.exclusive do while @thr.include?(name = @cnt.to_s) @cnt += 1 end @thr[name] = Thread.current end send_line("S name #{name}\r\n") send_line("OK\r\n") Thread.current[:rubyserv_name] = name begin Thread.current[:rubyserv_error] = false Thread.current[:rubyserv_response] = eval(r, exec_env.empty_binding) rescue Exception => e Thread.current[:rubyserv_error] = true Thread.current[:rubyserv_response] = e.to_s.sub(/\A.*?\n#{Regexp.quote(__FILE__)}:\d+: /o, '') end send_line("# exit #{name}\r\n") end def dispatch_poll(c, r) thr = @thr[r] if !thr send_line("ERR 105 Parameter error: no such name \"#{r}\"\r\n") elsif thr.alive? send_line("S running #{r}\r\n") send_line("OK\r\n") else if thr[:rubyserv_error] send_line("S exited #{r}\r\n") else send_line("S finished #{r}\r\n") end if d = thr[:rubyserv_response] send_data(d.to_s) end send_line("OK\r\n") end end def dispatch_exit(c, r) thr = @thr[r] if !thr send_line("ERR 105 Parameter error: no such name \"#{r}\"\r\n") return end thr.kill if thr.alive? @thr.delete(r) send_line("OK\r\n") end def escape(s) s.gsub(/[%\r\n]/) {|m| '%%%02X' % m[0]} end def unescape(s) s.gsub(/%([0-9A-Z][0-9A-Z])/) {[$1].pack('H*')} end def send_data(d) d = escape(d) begin len = [d.length, 998].min # 998 = 1000 - "D " send_line("D #{d[0 ... len]}\r\n") d = d[len .. -1] end until d.empty? end def enq_data d = unescape(@buf) @buf = '' @que.enq(d) end def deq_data @que.deq end def send_line(line) @out.puts(line) @log.puts(line) if @log end def exec_env env = Object.new def env.empty_binding binding end out, log = @out, @log env.instance_eval {@out, @log = out, log} def env.send_line(line) @out.puts(line) @log.puts(line) if @log end def env.output(s) send_line("# output #{Thread.current[:rubyserv_name]} #{s}\r\n") end env end end if $0 == __FILE__ require 'optparse' opt_outfile, opt_errfile, opt_logfile = nil, nil, nil opts = OptionParser.new do |opts| opts.banner = <<"End" Usage: #{$0} [OPTIONS] End opts.on('-o', '--out OUTFILE', 'Send stdout to OUTFILE.') do |outfile| opt_outfile = outfile end opts.on('-e', '--err ERRFILE', 'Send stderr to ERRFILE.') do |errfile| opt_errfile = errfile end opts.on('-l', '--log LOGFILE', 'Send log to LOGFILE.') do |logfile| opt_logfile = logfile end opts.on_tail('--help', '-h', 'Show this message.') do $stdout.print(opts.to_s) exit(0) end end begin opts.parse!(ARGV) rescue OptionParser::ParseError $stderr.print(opts.to_s) exit(1) end server = Server.new(opt_outfile, opt_errfile, opt_logfile) while gets server.dispatch($_) end end