lib/net/ssh/proxy/command.rb in net-ssh-5.0.0.beta1 vs lib/net/ssh/proxy/command.rb in net-ssh-5.0.0.beta2

- old
+ new

@@ -1,123 +1,124 @@ require 'socket' require 'rubygems' require 'net/ssh/proxy/errors' require 'net/ssh/ruby_compat' -module Net; module SSH; module Proxy +module Net + module SSH + module Proxy - # An implementation of a command proxy. To use it, instantiate it, - # then pass the instantiated object via the :proxy key to - # Net::SSH.start: - # - # require 'net/ssh/proxy/command' - # - # proxy = Net::SSH::Proxy::Command.new('ssh relay nc %h %p') - # Net::SSH.start('host', 'user', :proxy => proxy) do |ssh| - # ... - # end - class Command + # An implementation of a command proxy. To use it, instantiate it, + # then pass the instantiated object via the :proxy key to + # Net::SSH.start: + # + # require 'net/ssh/proxy/command' + # + # proxy = Net::SSH::Proxy::Command.new('ssh relay nc %h %p') + # Net::SSH.start('host', 'user', :proxy => proxy) do |ssh| + # ... + # end + class Command + # The command line template + attr_reader :command_line_template - # The command line template - attr_reader :command_line_template + # The command line for the session + attr_reader :command_line - # The command line for the session - attr_reader :command_line + # Timeout in seconds in open, defaults to 60 + attr_accessor :timeout - # Timeout in seconds in open, defaults to 60 - attr_accessor :timeout + # Create a new socket factory that tunnels via a command executed + # with the user's shell, which is composed from the given command + # template. In the command template, `%h' will be substituted by + # the host name to connect and `%p' by the port. + def initialize(command_line_template) + @command_line_template = command_line_template + @command_line = nil + @timeout = 60 + end - # Create a new socket factory that tunnels via a command executed - # with the user's shell, which is composed from the given command - # template. In the command template, `%h' will be substituted by - # the host name to connect and `%p' by the port. - def initialize(command_line_template) - @command_line_template = command_line_template - @command_line = nil - @timeout = 60 - end - - # Return a new socket connected to the given host and port via the - # proxy that was requested when the socket factory was instantiated. - def open(host, port, connection_options = nil) - command_line = @command_line_template.gsub(/%(.)/) { - case $1 - when 'h' - host - when 'p' - port.to_s - when 'r' - remote_user = connection_options && connection_options[:remote_user] - if remote_user - remote_user - else - raise ArgumentError, "remote user name not available" + # Return a new socket connected to the given host and port via the + # proxy that was requested when the socket factory was instantiated. + def open(host, port, connection_options = nil) + command_line = @command_line_template.gsub(/%(.)/) { + case $1 + when 'h' + host + when 'p' + port.to_s + when 'r' + remote_user = connection_options && connection_options[:remote_user] + if remote_user + remote_user + else + raise ArgumentError, "remote user name not available" + end + when '%' + '%' + else + raise ArgumentError, "unknown key: #{$1}" + end + } + begin + io = IO.popen(command_line, "r+") + begin + if result = IO.select([io], nil, [io], @timeout) + if result.last.any? || io.eof? + raise "command failed" + end + else + raise "command timed out" + end + rescue StandardError + close_on_error(io) + raise + end + rescue StandardError => e + raise ConnectError, "#{e}: #{command_line}" end - when '%' - '%' - else - raise ArgumentError, "unknown key: #{$1}" - end - } - begin - io = IO.popen(command_line, "r+") - begin - if result = IO.select([io], nil, [io], @timeout) - if result.last.any? || io.eof? - raise "command failed" + @command_line = command_line + if Gem.win_platform? + # read_nonblock and write_nonblock are not available on Windows + # pipe. Use sysread and syswrite as a replacement works. + def io.send(data, flag) + syswrite(data) end + + def io.recv(size) + sysread(size) + end else - raise "command timed out" - end - rescue - close_on_error(io) - raise - end - rescue => e - raise ConnectError, "#{e}: #{command_line}" - end - @command_line = command_line - if Gem.win_platform? - # read_nonblock and write_nonblock are not available on Windows - # pipe. Use sysread and syswrite as a replacement works. - def io.send(data, flag) - syswrite(data) - end + def io.send(data, flag) + begin + result = write_nonblock(data) + rescue IO::WaitWritable, Errno::EINTR + IO.select(nil, [self]) + retry + end + result + end - def io.recv(size) - sysread(size) - end - else - def io.send(data, flag) - begin - result = write_nonblock(data) - rescue IO::WaitWritable, Errno::EINTR - IO.select(nil, [self]) - retry + def io.recv(size) + begin + result = read_nonblock(size) + rescue IO::WaitReadable, Errno::EINTR + timeout_in_seconds = 20 + if IO.select([self], nil, [self], timeout_in_seconds) == nil + raise "Unexpected spurious read wakeup" + end + retry + end + result + end end - result + io end - def io.recv(size) - begin - result = read_nonblock(size) - rescue IO::WaitReadable, Errno::EINTR - timeout_in_seconds = 20 - if IO.select([self], nil, [self], timeout_in_seconds) == nil - raise "Unexpected spurious read wakeup" - end - retry - end - result + def close_on_error(io) + Process.kill('TERM', io.pid) + Thread.new { io.close } end end - io end - - def close_on_error(io) - Process.kill('TERM', io.pid) - Thread.new { io.close } - end - end - -end; end; end +end