Diff
Modified: trunk/javasand/javasand.gemspec (497 => 498)
--- trunk/javasand/javasand.gemspec 2007-04-25 05:27:22 UTC (rev 497)
+++ trunk/javasand/javasand.gemspec 2007-04-25 07:49:31 UTC (rev 498)
@@ -2,9 +2,9 @@
spec = Gem::Specification.new do |s|
s.name = "javasand"
- s.version = "0.0.1"
+ s.version = "0.0.2"
s.author = "JRuby-extras"
- s.email = "[EMAIL PROTECTED]"
+ s.email = "[EMAIL PROTECTED]"
s.homepage = "http://jruby-extras.rubyforge.org/"
s.platform = Gem::Platform::RUBY #should be JAVA
s.summary = "Sandbox support for JRuby. Only usable with JRuby"
@@ -12,7 +12,7 @@
s.files = candidates.delete_if do |item| item.include?(".svn") || item.include?("rdoc") end
s.require_path = "lib"
s.has_rdoc = false
- s.requirements << "JRuby with the org.jruby.Profile-class"
+ s.requirements << "JRuby 0.9.9 or larger"
end
if $0 == __FILE__
Added: trunk/javasand/lib/sandbox/irb.rb (0 => 498)
--- trunk/javasand/lib/sandbox/irb.rb (rev 0)
+++ trunk/javasand/lib/sandbox/irb.rb 2007-04-25 07:49:31 UTC (rev 498)
@@ -0,0 +1,95 @@
+require 'irb'
+require 'sandbox'
+
+class Sandbox::IRB
+
+ def initialize(box)
+ @box, @sig, @p = box, :IN_IRB
+ @box_errors = %w[StandardError ScriptError].map { |x| @box.eval(x) }
+ @prompt = {:start => ">> ", :continue => ".. ", :nested => ".. ",
+ :string => " ", :return => "=> %s\n"}
+ end
+
+ def box_eval(str)
+ @box.eval(str)
+ end
+
+ def start(io)
+ scanner = RubyLex.new
+ scanner.exception_on_syntax_error = false
+ scanner.set_prompt do |ltype, indent, continue, line_no|
+ if ltype
+ f = @prompt[:string]
+ elsif continue
+ f = @prompt[:continue]
+ elsif indent > 0
+ f = @prompt[:nested]
+ else
+ f = @prompt[:start]
+ end
+ f = "" unless f
+ @p = prompt(f, ltype, indent, line_no)
+ end
+
+ scanner.set_input(io) do
+ signal_status(:IN_INPUT) do
+ io.print @p
+ io.gets
+ end
+ end
+
+ scanner.each_top_level_statement do |line, line_no|
+ signal_status(:IN_EVAL) do
+ line.untaint
+ begin
+ val = box_eval(line)
+ io.puts @prompt[:return] % [val.inspect]
+ rescue Sandbox::Exception, Sandbox::TimeoutError => e
+ io.print e, "\n"
+ end
+ end
+ end
+ end
+
+ def prompt(prompt, ltype, indent, line_no)
+ p = prompt.dup
+ p.gsub!(/%([0-9]+)?([a-zA-Z])/) do
+ case $2
+ # when "N"
+ # @context.irb_name
+ # when "m"
+ # @context.main.to_s
+ # when "M"
+ # @context.main.inspect
+ when "l"
+ ltype
+ when "i"
+ if $1
+ format("%" + $1 + "d", indent)
+ else
+ indent.to_s
+ end
+ when "n"
+ if $1
+ format("%" + $1 + "d", line_no)
+ else
+ line_no.to_s
+ end
+ when "%"
+ "%"
+ end
+ end
+ p
+ end
+
+ def signal_status(status)
+ return yield if @sig == :IN_LOAD
+ sig_back = @sig
+ @sig = status
+ begin
+ yield
+ ensure
+ @sig = sig_back
+ end
+ end
+end
Added: trunk/javasand/lib/sandbox/prelude.rb (0 => 498)
--- trunk/javasand/lib/sandbox/prelude.rb (rev 0)
+++ trunk/javasand/lib/sandbox/prelude.rb 2007-04-25 07:49:31 UTC (rev 498)
@@ -0,0 +1,21 @@
+# Alternate "safer" versions of Ruby methods. Mostly non-blocking.
+[Fixnum, Bignum, Float].each do |klass|
+ klass.class_eval do
+
+ # A very weak version of pow, it doesn't work on Floats, but it's
+ # gonna fill the most common uses for now.
+ def ** x
+ case x
+ when 0; 1
+ when 1; self
+ else
+ y = 1
+ while 0 <= (x -= 1) do
+ y *= self
+ end
+ y
+ end
+ end
+
+ end
+end
Added: trunk/javasand/lib/sandbox/server.rb (0 => 498)
--- trunk/javasand/lib/sandbox/server.rb (rev 0)
+++ trunk/javasand/lib/sandbox/server.rb 2007-04-25 07:49:31 UTC (rev 498)
@@ -0,0 +1,140 @@
+require 'socket'
+require 'sandbox/irb'
+
+class Sandbox::IRBServer
+ attr_reader :acceptor
+ attr_reader :workers
+ attr_reader :host
+ attr_reader :port
+ attr_reader :timeout
+ attr_reader :num_processors
+
+ def initialize(host, port, num_processors=(2**30-1), timeout=0)
+ @socket = TCPServer.new(host, port)
+ @host = host
+ @port = port
+ @workers = ThreadGroup.new
+ @timeout = timeout
+ @num_processors = num_processors
+ @death_time = 60
+ @sessions = {}
+ end
+
+ def randid
+ abc = %{ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz}
+ (1..20).map { abc[rand(abc.size),1] }.join
+ end
+
+ def new_sandbox
+ Sandbox.safe(:timeout => 10)
+ end
+
+ def process_client(client)
+ begin
+ case client.gets
+ when /^LOGIN (\w+)/
+ sess = $1
+ else
+ sess = randid
+ end
+
+ @sessions[sess] ||= new_sandbox
+ client.puts sess
+ Sandbox::IRB.new(@sessions[sess]).start(client)
+ rescue EOFError,Errno::ECONNRESET,Errno::EPIPE,Errno::EINVAL,Errno::EBADF
+ # ignored
+ rescue Errno::EMFILE
+ reap_dead_workers('too many files')
+ rescue Object
+ STDERR.puts "#{Time.now}: ERROR: #$!"
+ STDERR.puts $!.backtrace.join("\n")
+ ensure
+ client.close unless client.closed?
+ end
+ end
+
+ # Used internally to kill off any worker threads that have taken too long
+ # to complete processing. Only called if there are too many processors
+ # currently servicing. It returns the count of workers still active
+ # after the reap is done. It only runs if there are workers to reap.
+ def reap_dead_workers(reason='unknown')
+ if @workers.list.length > 0
+ STDERR.puts "#{Time.now}: Reaping [EMAIL PROTECTED] threads for slow workers because of '#{reason}'"
+ mark = Time.now
+ @workers.list.each do |w|
+ w[:started_on] = Time.now if not w[:started_on]
+
+ if mark - w[:started_on] > @death_time + @timeout
+ STDERR.puts "Thread #{w.inspect} is too old, killing."
+ w.raise(TimeoutError.new("Timed out thread."))
+ end
+ end
+ end
+
+ return @workers.list.length
+ end
+
+ # Performs a wait on all the currently running threads and kills any that take
+ # too long. Right now it just waits 60 seconds, but will expand this to
+ # allow setting. The @timeout setting does extend this waiting period by
+ # that much longer.
+ def graceful_shutdown
+ while reap_dead_workers("shutdown") > 0
+ STDERR.print "Waiting for [EMAIL PROTECTED] requests to finish, could take [EMAIL PROTECTED] + @timeout} seconds."
+ sleep @death_time / 10
+ end
+ end
+
+
+ # Runs the thing. It returns the thread used so you can "join" it. You can also
+ # access the HttpServer::acceptor attribute to get the thread later.
+ def run
+ BasicSocket.do_not_reverse_lookup=true
+
+ @acceptor = Thread.new do
+ while true
+ begin
+ client = @socket.accept
+ worker_list = @workers.list
+
+ if worker_list.length >= @num_processors
+ STDERR.puts "Server overloaded with #{worker_list.length} processors ([EMAIL PROTECTED] max). Dropping connection."
+ client.close
+ reap_dead_workers("max processors")
+ else
+ thread = Thread.new { process_client(client) }
+ thread.abort_on_exception = true
+ thread[:started_on] = Time.now
+ @workers.add(thread)
+
+ sleep @timeout/100 if @timeout > 0
+ end
+ rescue StopServer
+ @socket.close if not @socket.closed?
+ break
+ rescue Errno::EMFILE
+ reap_dead_workers("too many open files")
+ sleep 0.5
+ rescue Errno::ECONNABORTED
+ # client closed the socket even before accept
+ client.close if not client.closed?
+ end
+ end
+
+ graceful_shutdown
+ end
+
+ return @acceptor
+ end
+
+ # Stops the acceptor thread and then causes the worker threads to finish
+ # off the request queue before finally exiting.
+ def stop
+ stopper = Thread.new do
+ exc = StopServer.new
+ @acceptor.raise(exc)
+ end
+ stopper.priority = 10
+ end
+
+end
Added: trunk/javasand/lib/sandbox.rb (0 => 498)
--- trunk/javasand/lib/sandbox.rb (rev 0)
+++ trunk/javasand/lib/sandbox.rb 2007-04-25 07:49:31 UTC (rev 498)
@@ -0,0 +1,90 @@
+require 'sand_table'
+require 'thread'
+
+module Sandbox
+
+ BUILD = "#{VERSION}.#{REV_ID[6..-3]}" #:nodoc:
+ PRELUDE = File.expand_path("../sandbox/prelude.rb", __FILE__) #:nodoc:
+
+ #
+ # Stands in for an exception raised within the sandbox during evaluation.
+ # (See Sandbox#eval.)
+ #
+ class Exception
+ end
+
+ #
+ # Raised when the duration of a sandbox evaluation exceeds a specified
+ # timeout. (See Sandbox#eval.)
+ #
+ class TimeoutError < Exception
+ end
+
+ class Full
+ private :_eval
+ #
+ # call-seq:
+ # sandbox.eval(str, opts={}) => obj
+ #
+ # Evaluates +str+ as Ruby code inside the sandbox and returns
+ # the result. If an option hash +opts+ is provided, any options
+ # specified in it take precedence over options specified when +sandbox+
+ # was created. (See Sandbox.new.)
+ #
+ # Available options include:
+ #
+ # [:timeout] The maximum time in seconds which Sandbox#eval is allowed to
+ # run before it is forcibly terminated.
+ # [:safelevel] The $SAFE level to use during evaluation in the sandbox.
+ #
+ # If evaluation times out, Sandbox#eval will raise a
+ # Sandbox::TimeoutError. If no timeout is specified, Sandbox#eval will
+ # be allowed to run indefinitely.
+ #
+ def eval(str, opts = {})
+ opts = @options.merge(opts)
+ if opts[:timeout] or opts[:safelevel]
+ th, exc, timed_out = nil, nil, false
+ safelevel = opts[:safelevel]
+ val = nil
+ th = Thread.start(str) do
+ $SAFE = safelevel if safelevel and safelevel > $SAFE
+ begin
+ val = _eval(str)
+ rescue Exception => exc
+ end
+ end
+ th.join(opts[:timeout])
+ if th.alive?
+ if th.respond_to? :kill!
+ th.kill!
+ else
+ th.kill
+ end
+ timed_out = true
+ end
+ if timed_out
+ raise TimeoutError, "#{self.class}#eval timed out"
+ elsif exc
+ raise exc
+ else
+ val
+ end
+ else
+ _eval(str)
+ end
+ end
+
+ #
+ # call-seq:
+ # sandbox.load(portname, opts={}) => obj
+ #
+ # Reads all available data from the given I/O port +portname+ and
+ # then evaluates it as a string in +sandbox+. (See Sandbox#eval.)
+ #
+ def load(io, opts = {})
+ eval(IO.read(io), opts)
+ end
+ end
+
+end
Modified: trunk/javasand/src/java/SandTableService.java (497 => 498)
--- trunk/javasand/src/java/SandTableService.java 2007-04-25 05:27:22 UTC (rev 497)
+++ trunk/javasand/src/java/SandTableService.java 2007-04-25 07:49:31 UTC (rev 498)
@@ -19,9 +19,9 @@
cSandboxFull.attr_accessor(new IRubyObject[]{runtime.newString("options")});
CallbackFactory fullcb = runtime.callbackFactory(SandboxFull.class);
- mSandbox.getMetaClass().defineFastMethod("new",fullcb.getOptSingletonMethod("s_new"));
- mSandbox.getMetaClass().defineFastMethod("safe",fullcb.getOptSingletonMethod("s_safe"));
- mSandbox.getMetaClass().defineFastMethod("current",fullcb.getSingletonMethod("s_current"));
+ mSandbox.getMetaClass().defineFastMethod("new",fullcb.getFastOptSingletonMethod("s_new"));
+ mSandbox.getMetaClass().defineFastMethod("safe",fullcb.getFastOptSingletonMethod("s_safe"));
+ mSandbox.getMetaClass().defineFastMethod("current",fullcb.getFastSingletonMethod("s_current"));
cSandboxFull.getMetaClass().defineFastMethod("new",fullcb.getFastOptSingletonMethod("newInstance"));
cSandboxFull.defineFastMethod("initialize",fullcb.getFastOptSingletonMethod("initialize"));
Modified: trunk/javasand/src/java/org/jruby/ext/sandbox/SandboxFull.java (497 => 498)
--- trunk/javasand/src/java/org/jruby/ext/sandbox/SandboxFull.java 2007-04-25 05:27:22 UTC (rev 497)
+++ trunk/javasand/src/java/org/jruby/ext/sandbox/SandboxFull.java 2007-04-25 07:49:31 UTC (rev 498)
@@ -38,6 +38,7 @@
import org.jruby.RubyObject;
import org.jruby.Profile;
+import org.jruby.runtime.Arity;
import org.jruby.runtime.Block;
import org.jruby.runtime.CallbackFactory;
import org.jruby.runtime.load.BasicLibraryService;
@@ -73,7 +74,7 @@
}
public static IRubyObject initialize(IRubyObject recv, IRubyObject[] args) {
- args = recv.scanArgs(args,0,1);
+ args = Arity.scanArgs(recv.getRuntime(),args,0,1);
if(args[0].isNil()) {
args[0] = RubyHash.newHash(recv.getRuntime());
}