Package paramiko :: Module sftp_client |
[frames] | no frames]

# Source Code for Module paramiko.sftp_client

      1  # Copyright (C) 2003-2007  Robey Pointer <robeypointer@gmail.com>
      2  #
      3  # This file is part of paramiko.
      4  #
      5  # Paramiko is free software; you can redistribute it and/or modify it under the
      6  # terms of the GNU Lesser General Public License as published by the Free
      7  # Software Foundation; either version 2.1 of the License, or (at your option)
      8  # any later version.
      9  #
     10  # Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY
     11  # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     12  # A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
     13  # details.
     14  #
     15  # You should have received a copy of the GNU Lesser General Public License
     16  # along with Paramiko; if not, write to the Free Software Foundation, Inc.,
     17  # 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
     18
     19  """
     20  Client-mode SFTP support.
     21  """
     22
     23  from binascii import hexlify
     24  import errno
     25  import os
     26  import stat
     27  import threading
     28  import time
     29  import weakref
     30
     31  from paramiko.sftp import *
     32  from paramiko.sftp_attr import SFTPAttributes
     33  from paramiko.ssh_exception import SSHException
     34  from paramiko.sftp_file import SFTPFile
     35
     36
     37 -def _to_unicode(s):
     38      """
     39      decode a string as ascii or utf8 if possible (as required by the sftp
     40      protocol).  if neither works, just return a byte string because the server
     41      probably doesn't know the filename's encoding.
     42      """
     43      try:
     44          return s.encode('ascii')
     45      except UnicodeError:
     46          try:
     47              return s.decode('utf-8')
     48          except UnicodeError:
     49              return s
     50
     51
     52 -class SFTPClient (BaseSFTP):
     53      """
     54      SFTP client object.  C{SFTPClient} is used to open an sftp session across
     55      an open ssh L{Transport} and do remote file operations.
     56      """
     57
     58 -    def __init__(self, sock):
     59          """
     60          Create an SFTP client from an existing L{Channel}.  The channel
     61          should already have requested the C{"sftp"} subsystem.
     62
     63          An alternate way to create an SFTP client context is by using
     64          L{from_transport}.
     65
     66          @param sock: an open L{Channel} using the C{"sftp"} subsystem
     67          @type sock: L{Channel}
     68
     69          @raise SSHException: if there's an exception while negotiating
     70              sftp
     71          """
     72          BaseSFTP.__init__(self)
     73          self.sock = sock
     74          self.ultra_debug = False
     75          self.request_number = 1
     76          # lock for request_number
     77          self._lock = threading.Lock()
     78          self._cwd = None
     79          # request # -> SFTPFile
     80          self._expecting = weakref.WeakValueDictionary()
     81          if type(sock) is Channel:
     82              # override default logger
     83              transport = self.sock.get_transport()
     84              self.logger = util.get_logger(transport.get_log_channel() + '.sftp')
     85              self.ultra_debug = transport.get_hexdump()
     86          try:
     87              server_version = self._send_version()
     88          except EOFError, x:
     89              raise SSHException('EOF during negotiation')
     90          self._log(INFO, 'Opened sftp connection (server version %d)' % server_version)
     91
     92 -    def from_transport(cls, t):
     93          """
     94          Create an SFTP client channel from an open L{Transport}.
     95
     96          @param t: an open L{Transport} which is already authenticated
     97          @type t: L{Transport}
     98          @return: a new L{SFTPClient} object, referring to an sftp session
     99              (channel) across the transport
    100          @rtype: L{SFTPClient}
    101          """
    102          chan = t.open_session()
    103          if chan is None:
    104              return None
    105          chan.invoke_subsystem('sftp')
    106          return cls(chan)
    107      from_transport = classmethod(from_transport)
    108
    109 -    def _log(self, level, msg, *args):
    110          if isinstance(msg, list):
    111              for m in msg:
    112                  super(SFTPClient, self)._log(level, "[chan %s] " + m, *([ self.sock.get_name() ] + list(args)))
    113          else:
    114              super(SFTPClient, self)._log(level, "[chan %s] " + msg, *([ self.sock.get_name() ] + list(args)))
    115
    116 -    def close(self):
    117          """
    118          Close the SFTP session and its underlying channel.
    119
    120          @since: 1.4
    121          """
    122          self._log(INFO, 'sftp session closed.')
    123          self.sock.close()
    124
    125 -    def get_channel(self):
    126          """
    127          Return the underlying L{Channel} object for this SFTP session.  This
    128          might be useful for doing things like setting a timeout on the channel.
    129
    130          @return: the SSH channel
    131          @rtype: L{Channel}
    132
    133          @since: 1.7.1
    134          """
    135          return self.sock
    136
    137 -    def listdir(self, path='.'):
    138          """
    139          Return a list containing the names of the entries in the given C{path}.
    140          The list is in arbitrary order.  It does not include the special
    141          entries C{'.'} and C{'..'} even if they are present in the folder.
    142          This method is meant to mirror C{os.listdir} as closely as possible.
    143          For a list of full L{SFTPAttributes} objects, see L{listdir_attr}.
    144
    145          @param path: path to list (defaults to C{'.'})
    146          @type path: str
    147          @return: list of filenames
    148          @rtype: list of str
    149          """
    150          return [f.filename for f in self.listdir_attr(path)]
    151
    152 -    def listdir_attr(self, path='.'):
    153          """
    154          Return a list containing L{SFTPAttributes} objects corresponding to
    155          files in the given C{path}.  The list is in arbitrary order.  It does
    156          not include the special entries C{'.'} and C{'..'} even if they are
    157          present in the folder.
    158
    159          The returned L{SFTPAttributes} objects will each have an additional
    160          field: C{longname}, which may contain a formatted string of the file's
    161          attributes, in unix format.  The content of this string will probably
    162          depend on the SFTP server implementation.
    163
    164          @param path: path to list (defaults to C{'.'})
    165          @type path: str
    166          @return: list of attributes
    167          @rtype: list of L{SFTPAttributes}
    168
    169          @since: 1.2
    170          """
    171          path = self._adjust_cwd(path)
    172          self._log(DEBUG, 'listdir(%r)' % path)
    173          t, msg = self._request(CMD_OPENDIR, path)
    174          if t != CMD_HANDLE:
    175              raise SFTPError('Expected handle')
    176          handle = msg.get_string()
    177          filelist = []
    178          while True:
    179              try:
    180                  t, msg = self._request(CMD_READDIR, handle)
    181              except EOFError, e:
    182                  # done with handle
    183                  break
    184              if t != CMD_NAME:
    185                  raise SFTPError('Expected name response')
    186              count = msg.get_int()
    187              for i in range(count):
    188                  filename = _to_unicode(msg.get_string())
    189                  longname = _to_unicode(msg.get_string())
    190                  attr = SFTPAttributes._from_msg(msg, filename, longname)
    191                  if (filename != '.') and (filename != '..'):
    192                      filelist.append(attr)
    193          self._request(CMD_CLOSE, handle)
    194          return filelist
    195
    196 -    def open(self, filename, mode='r', bufsize=-1):
    197          """
    198          Open a file on the remote server.  The arguments are the same as for
    199          python's built-in C{file} (aka C{open}).  A file-like object is
    200          returned, which closely mimics the behavior of a normal python file
    201          object.
    202
    203          The mode indicates how the file is to be opened: C{'r'} for reading,
    204          C{'w'} for writing (truncating an existing file), C{'a'} for appending,
    205          C{'r+'} for reading/writing, C{'w+'} for reading/writing (truncating an
    206          existing file), C{'a+'} for reading/appending.  The python C{'b'} flag
    207          is ignored, since SSH treats all files as binary.  The C{'U'} flag is
    208          supported in a compatible way.
    209
    210          Since 1.5.2, an C{'x'} flag indicates that the operation should only
    211          succeed if the file was created and did not previously exist.  This has
    212          no direct mapping to python's file flags, but is commonly known as the
    213          C{O_EXCL} flag in posix.
    214
    215          The file will be buffered in standard python style by default, but
    216          can be altered with the C{bufsize} parameter.  C{0} turns off
    217          buffering, C{1} uses line buffering, and any number greater than 1
    218          (C{>1}) uses that specific buffer size.
    219
    220          @param filename: name of the file to open
    221          @type filename: str
    222          @param mode: mode (python-style) to open in
    223          @type mode: str
    224          @param bufsize: desired buffering (-1 = default buffer size)
    225          @type bufsize: int
    226          @return: a file object representing the open file
    227          @rtype: SFTPFile
    228
    229          @raise IOError: if the file could not be opened.
    230          """
    231          filename = self._adjust_cwd(filename)
    232          self._log(DEBUG, 'open(%r, %r)' % (filename, mode))
    233          imode = 0
    234          if ('r' in mode) or ('+' in mode):
    235              imode |= SFTP_FLAG_READ
    236          if ('w' in mode) or ('+' in mode) or ('a' in mode):
    237              imode |= SFTP_FLAG_WRITE
    238          if ('w' in mode):
    239              imode |= SFTP_FLAG_CREATE | SFTP_FLAG_TRUNC
    240          if ('a' in mode):
    241              imode |= SFTP_FLAG_CREATE | SFTP_FLAG_APPEND
    242          if ('x' in mode):
    243              imode |= SFTP_FLAG_CREATE | SFTP_FLAG_EXCL
    244          attrblock = SFTPAttributes()
    245          t, msg = self._request(CMD_OPEN, filename, imode, attrblock)
    246          if t != CMD_HANDLE:
    247              raise SFTPError('Expected handle')
    248          handle = msg.get_string()
    249          self._log(DEBUG, 'open(%r, %r) -> %s' % (filename, mode, hexlify(handle)))
    250          return SFTPFile(self, handle, mode, bufsize)
    251
    252      # python continues to vacillate about "open" vs "file"...
    253      file = open
    254
    255 -    def remove(self, path):
    256          """
    257          Remove the file at the given path.  This only works on files; for
    258          removing folders (directories), use L{rmdir}.
    259
    260          @param path: path (absolute or relative) of the file to remove
    261          @type path: str
    262
    263          @raise IOError: if the path refers to a folder (directory)
    264          """
    265          path = self._adjust_cwd(path)
    266          self._log(DEBUG, 'remove(%r)' % path)
    267          self._request(CMD_REMOVE, path)
    268
    269      unlink = remove
    270
    271 -    def rename(self, oldpath, newpath):
    272          """
    273          Rename a file or folder from C{oldpath} to C{newpath}.
    274
    275          @param oldpath: existing name of the file or folder
    276          @type oldpath: str
    277          @param newpath: new name for the file or folder
    278          @type newpath: str
    279
    280          @raise IOError: if C{newpath} is a folder, or something else goes
    281              wrong
    282          """
    283          oldpath = self._adjust_cwd(oldpath)
    284          newpath = self._adjust_cwd(newpath)
    285          self._log(DEBUG, 'rename(%r, %r)' % (oldpath, newpath))
    286          self._request(CMD_RENAME, oldpath, newpath)
    287
    288 -    def mkdir(self, path, mode=0777):
    289          """
    290          Create a folder (directory) named C{path} with numeric mode C{mode}.
    291          The default mode is 0777 (octal).  On some systems, mode is ignored.
    292          Where it is used, the current umask value is first masked out.
    293
    294          @param path: name of the folder to create
    295          @type path: str
    296          @param mode: permissions (posix-style) for the newly-created folder
    297          @type mode: int
    298          """
    299          path = self._adjust_cwd(path)
    300          self._log(DEBUG, 'mkdir(%r, %r)' % (path, mode))
    301          attr = SFTPAttributes()
    302          attr.st_mode = mode
    303          self._request(CMD_MKDIR, path, attr)
    304
    305 -    def rmdir(self, path):
    306          """
    307          Remove the folder named C{path}.
    308
    309          @param path: name of the folder to remove
    310          @type path: str
    311          """
    312          path = self._adjust_cwd(path)
    313          self._log(DEBUG, 'rmdir(%r)' % path)
    314          self._request(CMD_RMDIR, path)
    315
    316 -    def stat(self, path):
    317          """
    318          Retrieve information about a file on the remote system.  The return
    319          value is an object whose attributes correspond to the attributes of
    320          python's C{stat} structure as returned by C{os.stat}, except that it
    321          contains fewer fields.  An SFTP server may return as much or as little
    322          info as it wants, so the results may vary from server to server.
    323
    324          Unlike a python C{stat} object, the result may not be accessed as a
    325          tuple.  This is mostly due to the author's slack factor.
    326
    327          The fields supported are: C{st_mode}, C{st_size}, C{st_uid}, C{st_gid},
    328          C{st_atime}, and C{st_mtime}.
    329
    330          @param path: the filename to stat
    331          @type path: str
    332          @return: an object containing attributes about the given file
    333          @rtype: SFTPAttributes
    334          """
    335          path = self._adjust_cwd(path)
    336          self._log(DEBUG, 'stat(%r)' % path)
    337          t, msg = self._request(CMD_STAT, path)
    338          if t != CMD_ATTRS:
    339              raise SFTPError('Expected attributes')
    340          return SFTPAttributes._from_msg(msg)
    341
    342 -    def lstat(self, path):
    343          """
    344          Retrieve information about a file on the remote system, without
    345          following symbolic links (shortcuts).  This otherwise behaves exactly
    346          the same as L{stat}.
    347
    348          @param path: the filename to stat
    349          @type path: str
    350          @return: an object containing attributes about the given file
    351          @rtype: SFTPAttributes
    352          """
    353          path = self._adjust_cwd(path)
    354          self._log(DEBUG, 'lstat(%r)' % path)
    355          t, msg = self._request(CMD_LSTAT, path)
    356          if t != CMD_ATTRS:
    357              raise SFTPError('Expected attributes')
    358          return SFTPAttributes._from_msg(msg)
    359
    360 -    def symlink(self, source, dest):
    361          """
    362          Create a symbolic link (shortcut) of the C{source} path at
    363          C{destination}.
    364
    365          @param source: path of the original file
    366          @type source: str
    367          @param dest: path of the newly created symlink
    368          @type dest: str
    369          """
    370          dest = self._adjust_cwd(dest)
    371          self._log(DEBUG, 'symlink(%r, %r)' % (source, dest))
    372          if type(source) is unicode:
    373              source = source.encode('utf-8')
    374          self._request(CMD_SYMLINK, source, dest)
    375
    376 -    def chmod(self, path, mode):
    377          """
    378          Change the mode (permissions) of a file.  The permissions are
    379          unix-style and identical to those used by python's C{os.chmod}
    380          function.
    381
    382          @param path: path of the file to change the permissions of
    383          @type path: str
    384          @param mode: new permissions
    385          @type mode: int
    386          """
    387          path = self._adjust_cwd(path)
    388          self._log(DEBUG, 'chmod(%r, %r)' % (path, mode))
    389          attr = SFTPAttributes()
    390          attr.st_mode = mode
    391          self._request(CMD_SETSTAT, path, attr)
    392
    393 -    def chown(self, path, uid, gid):
    394          """
    395          Change the owner (C{uid}) and group (C{gid}) of a file.  As with
    396          python's C{os.chown} function, you must pass both arguments, so if you
    397          only want to change one, use L{stat} first to retrieve the current
    398          owner and group.
    399
    400          @param path: path of the file to change the owner and group of
    401          @type path: str
    402          @param uid: new owner's uid
    403          @type uid: int
    404          @param gid: new group id
    405          @type gid: int
    406          """
    407          path = self._adjust_cwd(path)
    408          self._log(DEBUG, 'chown(%r, %r, %r)' % (path, uid, gid))
    409          attr = SFTPAttributes()
    410          attr.st_uid, attr.st_gid = uid, gid
    411          self._request(CMD_SETSTAT, path, attr)
    412
    413 -    def utime(self, path, times):
    414          """
    415          Set the access and modified times of the file specified by C{path}.  If
    416          C{times} is C{None}, then the file's access and modified times are set
    417          to the current time.  Otherwise, C{times} must be a 2-tuple of numbers,
    418          of the form C{(atime, mtime)}, which is used to set the access and
    419          modified times, respectively.  This bizarre API is mimicked from python
    420          for the sake of consistency -- I apologize.
    421
    422          @param path: path of the file to modify
    423          @type path: str
    424          @param times: C{None} or a tuple of (access time, modified time) in
    425              standard internet epoch time (seconds since 01 January 1970 GMT)
    426          @type times: tuple(int)
    427          """
    428          path = self._adjust_cwd(path)
    429          if times is None:
    430              times = (time.time(), time.time())
    431          self._log(DEBUG, 'utime(%r, %r)' % (path, times))
    432          attr = SFTPAttributes()
    433          attr.st_atime, attr.st_mtime = times
    434          self._request(CMD_SETSTAT, path, attr)
    435
    436 -    def truncate(self, path, size):
    437          """
    438          Change the size of the file specified by C{path}.  This usually extends
    439          or shrinks the size of the file, just like the C{truncate()} method on
    440          python file objects.
    441
    442          @param path: path of the file to modify
    443          @type path: str
    444          @param size: the new size of the file
    445          @type size: int or long
    446          """
    447          path = self._adjust_cwd(path)
    448          self._log(DEBUG, 'truncate(%r, %r)' % (path, size))
    449          attr = SFTPAttributes()
    450          attr.st_size = size
    451          self._request(CMD_SETSTAT, path, attr)
    452
    453 -    def readlink(self, path):
    454          """
    455          Return the target of a symbolic link (shortcut).  You can use
    456          L{symlink} to create these.  The result may be either an absolute or
    457          relative pathname.
    458
    459          @param path: path of the symbolic link file
    460          @type path: str
    461          @return: target path
    462          @rtype: str
    463          """
    464          path = self._adjust_cwd(path)
    465          self._log(DEBUG, 'readlink(%r)' % path)
    466          t, msg = self._request(CMD_READLINK, path)
    467          if t != CMD_NAME:
    468              raise SFTPError('Expected name response')
    469          count = msg.get_int()
    470          if count == 0:
    471              return None
    472          if count != 1:
    473              raise SFTPError('Readlink returned %d results' % count)
    474          return _to_unicode(msg.get_string())
    475
    476 -    def normalize(self, path):
    477          """
    478          Return the normalized path (on the server) of a given path.  This
    479          can be used to quickly resolve symbolic links or determine what the
    480          server is considering to be the "current folder" (by passing C{'.'}
    481          as C{path}).
    482
    483          @param path: path to be normalized
    484          @type path: str
    485          @return: normalized form of the given path
    486          @rtype: str
    487
    488          @raise IOError: if the path can't be resolved on the server
    489          """
    490          path = self._adjust_cwd(path)
    491          self._log(DEBUG, 'normalize(%r)' % path)
    492          t, msg = self._request(CMD_REALPATH, path)
    493          if t != CMD_NAME:
    494              raise SFTPError('Expected name response')
    495          count = msg.get_int()
    496          if count != 1:
    497              raise SFTPError('Realpath returned %d results' % count)
    498          return _to_unicode(msg.get_string())
    499
    500 -    def chdir(self, path):
    501          """
    502          Change the "current directory" of this SFTP session.  Since SFTP
    503          doesn't really have the concept of a current working directory, this
    504          is emulated by paramiko.  Once you use this method to set a working
    505          directory, all operations on this SFTPClient object will be relative
    506          to that path.
    507
    508          @param path: new current working directory
    509          @type path: str
    510
    511          @raise IOError: if the requested path doesn't exist on the server
    512
    513          @since: 1.4
    514          """
    515          if not stat.S_ISDIR(self.stat(path).st_mode):
    516              raise SFTPError(errno.ENOTDIR, "%s: %s" % (os.strerror(errno.ENOTDIR), path))
    517          self._cwd = self.normalize(path)
    518
    519 -    def getcwd(self):
    520          """
    521          Return the "current working directory" for this SFTP session, as
    522          emulated by paramiko.  If no directory has been set with L{chdir},
    523          this method will return C{None}.
    524
    525          @return: the current working directory on the server, or C{None}
    526          @rtype: str
    527
    528          @since: 1.4
    529          """
    530          return self._cwd
    531
    532 -    def put(self, localpath, remotepath, callback=None):
    533          """
    534          Copy a local file (C{localpath}) to the SFTP server as C{remotepath}.
    535          Any exception raised by operations will be passed through.  This
    536          method is primarily provided as a convenience.
    537
    538          The SFTP operations use pipelining for speed.
    539
    540          @param localpath: the local file to copy
    541          @type localpath: str
    542          @param remotepath: the destination path on the SFTP server
    543          @type remotepath: str
    544          @param callback: optional callback function that accepts the bytes
    545              transferred so far and the total bytes to be transferred
    546              (since 1.7.4)
    547          @type callback: function(int, int)
    548          @return: an object containing attributes about the given file
    549              (since 1.7.4)
    550          @rtype: SFTPAttributes
    551
    552          @since: 1.4
    553          """
    554          file_size = os.stat(localpath).st_size
    555          fl = file(localpath, 'rb')
    556          try:
    557              fr = self.file(remotepath, 'wb')
    558              fr.set_pipelined(True)
    559              size = 0
    560              try:
    561                  while True:
    562                      data = fl.read(32768)
    563                      if len(data) == 0:
    564                          break
    565                      fr.write(data)
    566                      size += len(data)
    567                      if callback is not None:
    568                          callback(size, file_size)
    569              finally:
    570                  fr.close()
    571          finally:
    572              fl.close()
    573          s = self.stat(remotepath)
    574          if s.st_size != size:
    575              raise IOError('size mismatch in put!  %d != %d' % (s.st_size, size))
    576          return s
    577
    578 -    def get(self, remotepath, localpath, callback=None):
    579          """
    580          Copy a remote file (C{remotepath}) from the SFTP server to the local
    581          host as C{localpath}.  Any exception raised by operations will be
    582          passed through.  This method is primarily provided as a convenience.
    583
    584          @param remotepath: the remote file to copy
    585          @type remotepath: str
    586          @param localpath: the destination path on the local host
    587          @type localpath: str
    588          @param callback: optional callback function that accepts the bytes
    589              transferred so far and the total bytes to be transferred
    590              (since 1.7.4)
    591          @type callback: function(int, int)
    592
    593          @since: 1.4
    594          """
    595          fr = self.file(remotepath, 'rb')
    596          file_size = self.stat(remotepath).st_size
    597          fr.prefetch()
    598          try:
    599              fl = file(localpath, 'wb')
    600              try:
    601                  size = 0
    602                  while True:
    603                      data = fr.read(32768)
    604                      if len(data) == 0:
    605                          break
    606                      fl.write(data)
    607                      size += len(data)
    608                      if callback is not None:
    609                          callback(size, file_size)
    610              finally:
    611                  fl.close()
    612          finally:
    613              fr.close()
    614          s = os.stat(localpath)
    615          if s.st_size != size:
    616              raise IOError('size mismatch in get!  %d != %d' % (s.st_size, size))
    617
    618
    619      ###  internals...
    620
    621
    622 -    def _request(self, t, *arg):
    623          num = self._async_request(type(None), t, *arg)
    624          return self._read_response(num)
    625
    626 -    def _async_request(self, fileobj, t, *arg):
    627          # this method may be called from other threads (prefetch)
    628          self._lock.acquire()
    629          try:
    630              msg = Message()
    631              msg.add_int(self.request_number)
    632              for item in arg:
    633                  if type(item) is int:
    634                      msg.add_int(item)
    635                  elif type(item) is long:
    636                      msg.add_int64(item)
    637                  elif type(item) is str:
    638                      msg.add_string(item)
    639                  elif type(item) is SFTPAttributes:
    640                      item._pack(msg)
    641                  else:
    642                      raise Exception('unknown type for %r type %r' % (item, type(item)))
    643              num = self.request_number
    644              self._expecting[num] = fileobj
    645              self._send_packet(t, str(msg))
    646              self.request_number += 1
    647          finally:
    648              self._lock.release()
    649          return num
    650
    651 -    def _read_response(self, waitfor=None):
    652          while True:
    653              try:
    654                  t, data = self._read_packet()
    655              except EOFError, e:
    656                  raise SSHException('Server connection dropped: %s' % (str(e),))
    657              msg = Message(data)
    658              num = msg.get_int()
    659              if num not in self._expecting:
    660                  # might be response for a file that was closed before responses came back
    661                  self._log(DEBUG, 'Unexpected response #%d' % (num,))
    662                  if waitfor is None:
    663                      # just doing a single check
    664                      break
    665                  continue
    666              fileobj = self._expecting[num]
    667              del self._expecting[num]
    668              if num == waitfor:
    669                  # synchronous
    670                  if t == CMD_STATUS:
    671                      self._convert_status(msg)
    672                  return t, msg
    673              if fileobj is not type(None):
    674                  fileobj._async_response(t, msg)
    675              if waitfor is None:
    676                  # just doing a single check
    677                  break
    678          return (None, None)
    679
    680 -    def _finish_responses(self, fileobj):
    681          while fileobj in self._expecting.values():
    682              self._read_response()
    683              fileobj._check_exception()
    684
    685 -    def _convert_status(self, msg):
    686          """
    687          Raises EOFError or IOError on error status; otherwise does nothing.
    688          """
    689          code = msg.get_int()
    690          text = msg.get_string()
    691          if code == SFTP_OK:
    692              return
    693          elif code == SFTP_EOF:
    694              raise EOFError(text)
    695          elif code == SFTP_NO_SUCH_FILE:
    696              # clever idea from john a. meinel: map the error codes to errno
    697              raise IOError(errno.ENOENT, text)
    698          elif code == SFTP_PERMISSION_DENIED:
    699              raise IOError(errno.EACCES, text)
    700          else:
    701              raise IOError(text)
    702
    703 -    def _adjust_cwd(self, path):
    704          """
    705          Return an adjusted path if we're emulating a "current working
    706          directory" for the server.
    707          """
    708          if type(path) is unicode:
    709              path = path.encode('utf-8')
    710          if self._cwd is None:
    711              return path
    712          if (len(path) > 0) and (path[0] == '/'):
    713              # absolute path
    714              return path
    715          if self._cwd == '/':
    716              return self._cwd + path
    717          return self._cwd + '/' + path
    718
    719
    720 -class SFTP (SFTPClient):
    721      "an alias for L{SFTPClient} for backwards compatability"
    722      pass
    723