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. You can pass in C{None} to stop using a current working
    507          directory.
    508
    509          @param path: new current working directory
    510          @type path: str
    511
    512          @raise IOError: if the requested path doesn't exist on the server
    513
    514          @since: 1.4
    515          """
    516          if path is None:
    517              self._cwd = None
    518              return
    519          if not stat.S_ISDIR(self.stat(path).st_mode):
    520              raise SFTPError(errno.ENOTDIR, "%s: %s" % (os.strerror(errno.ENOTDIR), path))
    521          self._cwd = self.normalize(path).encode('utf-8')
    522
    523 -    def getcwd(self):
    524          """
    525          Return the "current working directory" for this SFTP session, as
    526          emulated by paramiko.  If no directory has been set with L{chdir},
    527          this method will return C{None}.
    528
    529          @return: the current working directory on the server, or C{None}
    530          @rtype: str
    531
    532          @since: 1.4
    533          """
    534          return self._cwd
    535
    536 -    def put(self, localpath, remotepath, callback=None, confirm=True):
    537          """
    538          Copy a local file (C{localpath}) to the SFTP server as C{remotepath}.
    539          Any exception raised by operations will be passed through.  This
    540          method is primarily provided as a convenience.
    541
    542          The SFTP operations use pipelining for speed.
    543
    544          @param localpath: the local file to copy
    545          @type localpath: str
    546          @param remotepath: the destination path on the SFTP server
    547          @type remotepath: str
    548          @param callback: optional callback function that accepts the bytes
    549              transferred so far and the total bytes to be transferred
    550              (since 1.7.4)
    551          @type callback: function(int, int)
    552          @param confirm: whether to do a stat() on the file afterwards to
    553              confirm the file size (since 1.7.7)
    554          @type confirm: bool
    555
    556          @return: an object containing attributes about the given file
    557              (since 1.7.4)
    558          @rtype: SFTPAttributes
    559
    560          @since: 1.4
    561          """
    562          file_size = os.stat(localpath).st_size
    563          fl = file(localpath, 'rb')
    564          try:
    565              fr = self.file(remotepath, 'wb')
    566              fr.set_pipelined(True)
    567              size = 0
    568              try:
    569                  while True:
    570                      data = fl.read(32768)
    571                      if len(data) == 0:
    572                          break
    573                      fr.write(data)
    574                      size += len(data)
    575                      if callback is not None:
    576                          callback(size, file_size)
    577              finally:
    578                  fr.close()
    579          finally:
    580              fl.close()
    581          if confirm:
    582              s = self.stat(remotepath)
    583              if s.st_size != size:
    584                  raise IOError('size mismatch in put!  %d != %d' % (s.st_size, size))
    585          else:
    586              s = SFTPAttributes()
    587          return s
    588
    589 -    def get(self, remotepath, localpath, callback=None):
    590          """
    591          Copy a remote file (C{remotepath}) from the SFTP server to the local
    592          host as C{localpath}.  Any exception raised by operations will be
    593          passed through.  This method is primarily provided as a convenience.
    594
    595          @param remotepath: the remote file to copy
    596          @type remotepath: str
    597          @param localpath: the destination path on the local host
    598          @type localpath: str
    599          @param callback: optional callback function that accepts the bytes
    600              transferred so far and the total bytes to be transferred
    601              (since 1.7.4)
    602          @type callback: function(int, int)
    603
    604          @since: 1.4
    605          """
    606          fr = self.file(remotepath, 'rb')
    607          file_size = self.stat(remotepath).st_size
    608          fr.prefetch()
    609          try:
    610              fl = file(localpath, 'wb')
    611              try:
    612                  size = 0
    613                  while True:
    614                      data = fr.read(32768)
    615                      if len(data) == 0:
    616                          break
    617                      fl.write(data)
    618                      size += len(data)
    619                      if callback is not None:
    620                          callback(size, file_size)
    621              finally:
    622                  fl.close()
    623          finally:
    624              fr.close()
    625          s = os.stat(localpath)
    626          if s.st_size != size:
    627              raise IOError('size mismatch in get!  %d != %d' % (s.st_size, size))
    628
    629
    630      ###  internals...
    631
    632
    633 -    def _request(self, t, *arg):
    634          num = self._async_request(type(None), t, *arg)
    635          return self._read_response(num)
    636
    637 -    def _async_request(self, fileobj, t, *arg):
    638          # this method may be called from other threads (prefetch)
    639          self._lock.acquire()
    640          try:
    641              msg = Message()
    642              msg.add_int(self.request_number)
    643              for item in arg:
    644                  if type(item) is int:
    645                      msg.add_int(item)
    646                  elif type(item) is long:
    647                      msg.add_int64(item)
    648                  elif type(item) is str:
    649                      msg.add_string(item)
    650                  elif type(item) is SFTPAttributes:
    651                      item._pack(msg)
    652                  else:
    653                      raise Exception('unknown type for %r type %r' % (item, type(item)))
    654              num = self.request_number
    655              self._expecting[num] = fileobj
    656              self._send_packet(t, str(msg))
    657              self.request_number += 1
    658          finally:
    659              self._lock.release()
    660          return num
    661
    662 -    def _read_response(self, waitfor=None):
    663          while True:
    664              try:
    665                  t, data = self._read_packet()
    666              except EOFError, e:
    667                  raise SSHException('Server connection dropped: %s' % (str(e),))
    668              msg = Message(data)
    669              num = msg.get_int()
    670              if num not in self._expecting:
    671                  # might be response for a file that was closed before responses came back
    672                  self._log(DEBUG, 'Unexpected response #%d' % (num,))
    673                  if waitfor is None:
    674                      # just doing a single check
    675                      break
    676                  continue
    677              fileobj = self._expecting[num]
    678              del self._expecting[num]
    679              if num == waitfor:
    680                  # synchronous
    681                  if t == CMD_STATUS:
    682                      self._convert_status(msg)
    683                  return t, msg
    684              if fileobj is not type(None):
    685                  fileobj._async_response(t, msg)
    686              if waitfor is None:
    687                  # just doing a single check
    688                  break
    689          return (None, None)
    690
    691 -    def _finish_responses(self, fileobj):
    692          while fileobj in self._expecting.values():
    693              self._read_response()
    694              fileobj._check_exception()
    695
    696 -    def _convert_status(self, msg):
    697          """
    698          Raises EOFError or IOError on error status; otherwise does nothing.
    699          """
    700          code = msg.get_int()
    701          text = msg.get_string()
    702          if code == SFTP_OK:
    703              return
    704          elif code == SFTP_EOF:
    705              raise EOFError(text)
    706          elif code == SFTP_NO_SUCH_FILE:
    707              # clever idea from john a. meinel: map the error codes to errno
    708              raise IOError(errno.ENOENT, text)
    709          elif code == SFTP_PERMISSION_DENIED:
    710              raise IOError(errno.EACCES, text)
    711          else:
    712              raise IOError(text)
    713
    714 -    def _adjust_cwd(self, path):
    715          """
    716          Return an adjusted path if we're emulating a "current working
    717          directory" for the server.
    718          """
    719          if type(path) is unicode:
    720              path = path.encode('utf-8')
    721          if self._cwd is None:
    722              return path
    723          if (len(path) > 0) and (path[0] == '/'):
    724              # absolute path
    725              return path
    726          if self._cwd == '/':
    727              return self._cwd + path
    728          return self._cwd + '/' + path
    729
    730
    731 -class SFTP (SFTPClient):
    732      "an alias for L{SFTPClient} for backwards compatability"
    733      pass
    734