X-Git-Url: https://git.cworth.org/git?a=blobdiff_plain;f=lib%2Fsup%2Fmbox%2Fssh-file.rb;h=4ae4bbadf6f875b8655dbe5b461bca2b31afef22;hb=aef0216d7f988ab87a3430c9a65210f0d55dfc64;hp=80211d050dff1a441da162ec5e373184aa4a090d;hpb=396af5ad95a9307a456fa29b3bdd55b1bfef0660;p=sup diff --git a/lib/sup/mbox/ssh-file.rb b/lib/sup/mbox/ssh-file.rb index 80211d0..4ae4bba 100644 --- a/lib/sup/mbox/ssh-file.rb +++ b/lib/sup/mbox/ssh-file.rb @@ -16,12 +16,6 @@ class SSHFileError < StandardError; end ## all of the methods here can throw SSHFileErrors, SocketErrors, ## Net::SSH::Exceptions and Errno::ENOENTs. -## debugging TODO: remove me -def debug s - Redwood::log s -end -module_function :debug - ## a simple buffer of contiguous data class Buffer def initialize @@ -66,6 +60,7 @@ class Buffer x = @buf.index(what, start - @start) x.nil? ? nil : x + @start end + def rindex what, start=0 x = @buf.rindex(what, start - @start) x.nil? ? nil : x + @start @@ -91,6 +86,12 @@ class SSHFile REASONABLE_TRANSFER_SIZE = 1024 * 32 SIZE_CHECK_INTERVAL = 60 * 1 # seconds + ## upon these errors we'll try to rereconnect a few times + RECOVERABLE_ERRORS = [ Errno::EPIPE, Errno::ETIMEDOUT ] + + @@shells = {} + @@shells_mutex = Mutex.new + def initialize host, fn, ssh_opts={} @buf = Buffer.new @host = host @@ -99,43 +100,15 @@ class SSHFile @file_size = nil @offset = 0 @say_id = nil - @broken_msg = nil @shell = nil - @shell_mutex = Mutex.new + @shell_mutex = nil + @buf_mutex = Mutex.new end - def to_s; "mbox+ssh://#@host/#@fn"; end ## TODO: remove thisis EVILness - def broken?; !@broken_msg.nil?; end - - ## TODO: share this code with imap - def say s - @say_id = BufferManager.say s, @say_id if BufferManager.instantiated? - Redwood::log s - end - def shutup - BufferManager.clear @say_id if BufferManager.instantiated? - @say_id = nil - end - private :say, :shutup + def to_s; "mbox+ssh://#@host/#@fn"; end ## TODO: remove this EVILness def connect - raise SSHFileError, @broken_msg if broken? - - @shell_mutex.synchronize do - return if @shell - - begin - say "Opening SSH connection to #{@host}..." - #raise SSHFileError, "simulated SSH file error" - session = Net::SSH.start @host, @ssh_opts - say "Starting SSH shell..." - @shell = session.shell.sync - say "Checking for #@fn..." - raise Errno::ENOENT, @fn unless @shell.test("-e #@fn").status == 0 - ensure - shutup - end - end + do_remote nil end def eof?; @offset >= size; end @@ -143,6 +116,7 @@ class SSHFile def seek loc; @offset = loc; end def tell; @offset; end def total; size; end + def path; @fn end def size if @file_size.nil? || (Time.now - @last_size_check) > SIZE_CHECK_INTERVAL @@ -154,42 +128,81 @@ class SSHFile def gets return nil if eof? - make_buf_include @offset - expand_buf_forward while @buf.index("\n", @offset).nil? && @buf.endd < size - returning(@buf[@offset .. (@buf.index("\n", @offset) || -1)]) { |line| @offset += line.length } + @buf_mutex.synchronize do + make_buf_include @offset + expand_buf_forward while @buf.index("\n", @offset).nil? && @buf.endd < size + returning(@buf[@offset .. (@buf.index("\n", @offset) || -1)]) { |line| @offset += line.length } + end end def read n return nil if eof? - make_buf_include @offset, n - @buf[@offset ... (@offset += n)] + @buf_mutex.synchronize do + make_buf_include @offset, n + @buf[@offset ... (@offset += n)] + end end private + ## TODO: share this code with imap + def say s + @say_id = BufferManager.say s, @say_id if BufferManager.instantiated? + info s + end + + def shutup + BufferManager.clear @say_id if BufferManager.instantiated? && @say_id + @say_id = nil + end + + def unsafe_connect + return if @shell + + @key = [@host, @ssh_opts[:username]] + begin + @shell, @shell_mutex = @@shells_mutex.synchronize do + unless @@shells.member? @key + say "Opening SSH connection to #{@host} for #@fn..." + session = Net::SSH.start @host, @ssh_opts + say "Starting SSH shell..." + @@shells[@key] = [session.shell.sync, Mutex.new] + end + @@shells[@key] + end + + say "Checking for #@fn..." + @shell_mutex.synchronize { raise Errno::ENOENT, @fn unless @shell.test("-e #@fn").status == 0 } + ensure + shutup + end + end + def do_remote cmd, expected_size=0 + retries = 0 + result = nil + begin - retries = 0 - connect - MBox::debug "sending command: #{cmd.inspect}" - begin - result = @shell.send_command cmd + unsafe_connect + if cmd + # MBox::debug "sending command: #{cmd.inspect}" + result = @shell_mutex.synchronize { x = @shell.send_command cmd; sleep 0.25; x } raise SSHFileError, "Failure during remote command #{cmd.inspect}: #{(result.stderr || result.stdout || "")[0 .. 100]}" unless result.status == 0 - rescue Net::SSH::Exception # these happen occasionally for no apparent reason. gotta love that nondeterminism! - retry if (retries += 1) <= 3 - raise - rescue Errno::EPIPE - if (retries += 1) <= e + end + ## Net::SSH::Exceptions seem to happen every once in a while for + ## no good reason. + rescue Net::SSH::Exception, *RECOVERABLE_ERRORS + if (retries += 1) <= 3 + @@shells_mutex.synchronize do @shell = nil - connect - retry + @@shells[@key] = nil end + retry end - rescue Net::SSH::Exception, SSHFileError, Errno::ENOENT => e - @broken_msg = e.message raise end - result.stdout + + result.stdout if cmd end def get_bytes offset, size