paramiko.sftp_client

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