1
# Copyright (C) 2003-2007 Robey Pointer <robeypointer@gmail.com>
3
# This file is part of Paramiko.
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)
10
# Paramiko is distributed 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
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.
20
from binascii import hexlify
27
from paramiko import util
28
from paramiko.channel import Channel
29
from paramiko.message import Message
30
from paramiko.common import INFO, DEBUG, o777
31
from paramiko.py3compat import bytestring, b, u, long
32
from paramiko.sftp import (
33
BaseSFTP, CMD_OPENDIR, CMD_HANDLE, SFTPError, CMD_READDIR, CMD_NAME,
34
CMD_CLOSE, SFTP_FLAG_READ, SFTP_FLAG_WRITE, SFTP_FLAG_CREATE,
35
SFTP_FLAG_TRUNC, SFTP_FLAG_APPEND, SFTP_FLAG_EXCL, CMD_OPEN, CMD_REMOVE,
36
CMD_RENAME, CMD_MKDIR, CMD_RMDIR, CMD_STAT, CMD_ATTRS, CMD_LSTAT,
37
CMD_SYMLINK, CMD_SETSTAT, CMD_READLINK, CMD_REALPATH, CMD_STATUS,
38
CMD_EXTENDED, SFTP_OK, SFTP_EOF, SFTP_NO_SUCH_FILE, SFTP_PERMISSION_DENIED,
41
from paramiko.sftp_attr import SFTPAttributes
42
from paramiko.ssh_exception import SSHException
43
from paramiko.sftp_file import SFTPFile
44
from paramiko.util import ClosingContextManager
49
decode a string as ascii or utf8 if possible (as required by the sftp
50
protocol). if neither works, just return a byte string because the server
51
probably doesn't know the filename's encoding.
54
return s.encode('ascii')
55
except (UnicodeError, AttributeError):
57
return s.decode('utf-8')
65
class SFTPClient(BaseSFTP, ClosingContextManager):
69
Used to open an SFTP session across an open SSH `.Transport` and perform
70
remote file operations.
72
Instances of this class may be used as context managers.
74
def __init__(self, sock):
76
Create an SFTP client from an existing `.Channel`. The channel
77
should already have requested the ``"sftp"`` subsystem.
79
An alternate way to create an SFTP client context is by using
82
:param .Channel sock: an open `.Channel` using the ``"sftp"`` subsystem
85
`.SSHException` -- if there's an exception while negotiating sftp
87
BaseSFTP.__init__(self)
89
self.ultra_debug = False
90
self.request_number = 1
91
# lock for request_number
92
self._lock = threading.Lock()
94
# request # -> SFTPFile
95
self._expecting = weakref.WeakValueDictionary()
96
if type(sock) is Channel:
97
# override default logger
98
transport = self.sock.get_transport()
99
self.logger = util.get_logger(
100
transport.get_log_channel() + '.sftp')
101
self.ultra_debug = transport.get_hexdump()
103
server_version = self._send_version()
105
raise SSHException('EOF during negotiation')
108
'Opened sftp connection (server version {})'.format(server_version)
112
def from_transport(cls, t, window_size=None, max_packet_size=None):
114
Create an SFTP client channel from an open `.Transport`.
116
Setting the window and packet sizes might affect the transfer speed.
117
The default settings in the `.Transport` class are the same as in
118
OpenSSH and should work adequately for both files transfers and
119
interactive sessions.
121
:param .Transport t: an open `.Transport` which is already
123
:param int window_size:
124
optional window size for the `.SFTPClient` session.
125
:param int max_packet_size:
126
optional max packet size for the `.SFTPClient` session..
129
a new `.SFTPClient` object, referring to an sftp session (channel)
132
.. versionchanged:: 1.15
133
Added the ``window_size`` and ``max_packet_size`` arguments.
135
chan = t.open_session(window_size=window_size,
136
max_packet_size=max_packet_size)
139
chan.invoke_subsystem('sftp')
142
def _log(self, level, msg, *args):
143
if isinstance(msg, list):
145
self._log(level, m, *args)
147
# NOTE: these bits MUST continue using %-style format junk because
148
# logging.Logger.log() explicitly requires it. Grump.
149
# escape '%' in msg (they could come from file or directory names)
151
msg = msg.replace('%', '%%')
152
super(SFTPClient, self)._log(
154
"[chan %s] " + msg, *([self.sock.get_name()] + list(args)))
158
Close the SFTP session and its underlying channel.
160
.. versionadded:: 1.4
162
self._log(INFO, 'sftp session closed.')
165
def get_channel(self):
167
Return the underlying `.Channel` object for this SFTP session. This
168
might be useful for doing things like setting a timeout on the channel.
170
.. versionadded:: 1.7.1
174
def listdir(self, path='.'):
176
Return a list containing the names of the entries in the given
179
The list is in arbitrary order. It does not include the special
180
entries ``'.'`` and ``'..'`` even if they are present in the folder.
181
This method is meant to mirror ``os.listdir`` as closely as possible.
182
For a list of full `.SFTPAttributes` objects, see `listdir_attr`.
184
:param str path: path to list (defaults to ``'.'``)
186
return [f.filename for f in self.listdir_attr(path)]
188
def listdir_attr(self, path='.'):
190
Return a list containing `.SFTPAttributes` objects corresponding to
191
files in the given ``path``. The list is in arbitrary order. It does
192
not include the special entries ``'.'`` and ``'..'`` even if they are
193
present in the folder.
195
The returned `.SFTPAttributes` objects will each have an additional
196
field: ``longname``, which may contain a formatted string of the file's
197
attributes, in unix format. The content of this string will probably
198
depend on the SFTP server implementation.
200
:param str path: path to list (defaults to ``'.'``)
201
:return: list of `.SFTPAttributes` objects
203
.. versionadded:: 1.2
205
path = self._adjust_cwd(path)
206
self._log(DEBUG, 'listdir({!r})'.format(path))
207
t, msg = self._request(CMD_OPENDIR, path)
209
raise SFTPError('Expected handle')
210
handle = msg.get_binary()
214
t, msg = self._request(CMD_READDIR, handle)
219
raise SFTPError('Expected name response')
220
count = msg.get_int()
221
for i in range(count):
222
filename = msg.get_text()
223
longname = msg.get_text()
224
attr = SFTPAttributes._from_msg(msg, filename, longname)
225
if (filename != '.') and (filename != '..'):
226
filelist.append(attr)
227
self._request(CMD_CLOSE, handle)
230
def listdir_iter(self, path='.', read_aheads=50):
232
Generator version of `.listdir_attr`.
234
See the API docs for `.listdir_attr` for overall details.
236
This function adds one more kwarg on top of `.listdir_attr`:
237
``read_aheads``, an integer controlling how many
238
``SSH_FXP_READDIR`` requests are made to the server. The default of 50
239
should suffice for most file listings as each request/response cycle
240
may contain multiple files (dependent on server implementation.)
242
.. versionadded:: 1.15
244
path = self._adjust_cwd(path)
245
self._log(DEBUG, 'listdir({!r})'.format(path))
246
t, msg = self._request(CMD_OPENDIR, path)
249
raise SFTPError('Expected handle')
251
handle = msg.get_string()
256
# Send out a bunch of readdir requests so that we can read the
257
# responses later on Section 6.7 of the SSH file transfer RFC
259
# http://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
260
for i in range(read_aheads):
261
num = self._async_request(type(None), CMD_READDIR, handle)
265
# For each of our sent requests
266
# Read and parse the corresponding packets
267
# If we're at the end of our queued requests, then fire off
269
# Exit the loop when we've reached the end of the directory
272
t, pkt_data = self._read_packet()
273
msg = Message(pkt_data)
274
new_num = msg.get_int()
277
self._convert_status(msg)
278
count = msg.get_int()
279
for i in range(count):
280
filename = msg.get_text()
281
longname = msg.get_text()
282
attr = SFTPAttributes._from_msg(
283
msg, filename, longname)
284
if (filename != '.') and (filename != '..'):
287
# If we've hit the end of our queued requests, reset nums.
291
self._request(CMD_CLOSE, handle)
295
def open(self, filename, mode='r', bufsize=-1):
297
Open a file on the remote server. The arguments are the same as for
298
Python's built-in `python:file` (aka `python:open`). A file-like
299
object is returned, which closely mimics the behavior of a normal
300
Python file object, including the ability to be used as a context
303
The mode indicates how the file is to be opened: ``'r'`` for reading,
304
``'w'`` for writing (truncating an existing file), ``'a'`` for
305
appending, ``'r+'`` for reading/writing, ``'w+'`` for reading/writing
306
(truncating an existing file), ``'a+'`` for reading/appending. The
307
Python ``'b'`` flag is ignored, since SSH treats all files as binary.
308
The ``'U'`` flag is supported in a compatible way.
310
Since 1.5.2, an ``'x'`` flag indicates that the operation should only
311
succeed if the file was created and did not previously exist. This has
312
no direct mapping to Python's file flags, but is commonly known as the
313
``O_EXCL`` flag in posix.
315
The file will be buffered in standard Python style by default, but
316
can be altered with the ``bufsize`` parameter. ``0`` turns off
317
buffering, ``1`` uses line buffering, and any number greater than 1
318
(``>1``) uses that specific buffer size.
320
:param str filename: name of the file to open
321
:param str mode: mode (Python-style) to open in
322
:param int bufsize: desired buffering (-1 = default buffer size)
323
:return: an `.SFTPFile` object representing the open file
325
:raises: ``IOError`` -- if the file could not be opened.
327
filename = self._adjust_cwd(filename)
328
self._log(DEBUG, 'open({!r}, {!r})'.format(filename, mode))
330
if ('r' in mode) or ('+' in mode):
331
imode |= SFTP_FLAG_READ
332
if ('w' in mode) or ('+' in mode) or ('a' in mode):
333
imode |= SFTP_FLAG_WRITE
335
imode |= SFTP_FLAG_CREATE | SFTP_FLAG_TRUNC
337
imode |= SFTP_FLAG_CREATE | SFTP_FLAG_APPEND
339
imode |= SFTP_FLAG_CREATE | SFTP_FLAG_EXCL
340
attrblock = SFTPAttributes()
341
t, msg = self._request(CMD_OPEN, filename, imode, attrblock)
343
raise SFTPError('Expected handle')
344
handle = msg.get_binary()
347
'open({!r}, {!r}) -> {}'.format(filename, mode, u(hexlify(handle)))
349
return SFTPFile(self, handle, mode, bufsize)
351
# Python continues to vacillate about "open" vs "file"...
354
def remove(self, path):
356
Remove the file at the given path. This only works on files; for
357
removing folders (directories), use `rmdir`.
359
:param str path: path (absolute or relative) of the file to remove
361
:raises: ``IOError`` -- if the path refers to a folder (directory)
363
path = self._adjust_cwd(path)
364
self._log(DEBUG, 'remove({!r})'.format(path))
365
self._request(CMD_REMOVE, path)
369
def rename(self, oldpath, newpath):
371
Rename a file or folder from ``oldpath`` to ``newpath``.
374
This method implements 'standard' SFTP ``RENAME`` behavior; those
375
seeking the OpenSSH "POSIX rename" extension behavior should use
379
existing name of the file or folder
381
new name for the file or folder, must not exist already
384
``IOError`` -- if ``newpath`` is a folder, or something else goes
387
oldpath = self._adjust_cwd(oldpath)
388
newpath = self._adjust_cwd(newpath)
389
self._log(DEBUG, 'rename({!r}, {!r})'.format(oldpath, newpath))
390
self._request(CMD_RENAME, oldpath, newpath)
392
def posix_rename(self, oldpath, newpath):
394
Rename a file or folder from ``oldpath`` to ``newpath``, following
397
:param str oldpath: existing name of the file or folder
398
:param str newpath: new name for the file or folder, will be
399
overwritten if it already exists
402
``IOError`` -- if ``newpath`` is a folder, posix-rename is not
403
supported by the server or something else goes wrong
407
oldpath = self._adjust_cwd(oldpath)
408
newpath = self._adjust_cwd(newpath)
409
self._log(DEBUG, 'posix_rename({!r}, {!r})'.format(oldpath, newpath))
411
CMD_EXTENDED, "posix-rename@openssh.com", oldpath, newpath
414
def mkdir(self, path, mode=o777):
416
Create a folder (directory) named ``path`` with numeric mode ``mode``.
417
The default mode is 0777 (octal). On some systems, mode is ignored.
418
Where it is used, the current umask value is first masked out.
420
:param str path: name of the folder to create
421
:param int mode: permissions (posix-style) for the newly-created folder
423
path = self._adjust_cwd(path)
424
self._log(DEBUG, 'mkdir({!r}, {!r})'.format(path, mode))
425
attr = SFTPAttributes()
427
self._request(CMD_MKDIR, path, attr)
429
def rmdir(self, path):
431
Remove the folder named ``path``.
433
:param str path: name of the folder to remove
435
path = self._adjust_cwd(path)
436
self._log(DEBUG, 'rmdir({!r})'.format(path))
437
self._request(CMD_RMDIR, path)
439
def stat(self, path):
441
Retrieve information about a file on the remote system. The return
442
value is an object whose attributes correspond to the attributes of
443
Python's ``stat`` structure as returned by ``os.stat``, except that it
444
contains fewer fields. An SFTP server may return as much or as little
445
info as it wants, so the results may vary from server to server.
447
Unlike a Python `python:stat` object, the result may not be accessed as
448
a tuple. This is mostly due to the author's slack factor.
450
The fields supported are: ``st_mode``, ``st_size``, ``st_uid``,
451
``st_gid``, ``st_atime``, and ``st_mtime``.
453
:param str path: the filename to stat
455
an `.SFTPAttributes` object containing attributes about the given
458
path = self._adjust_cwd(path)
459
self._log(DEBUG, 'stat({!r})'.format(path))
460
t, msg = self._request(CMD_STAT, path)
462
raise SFTPError('Expected attributes')
463
return SFTPAttributes._from_msg(msg)
465
def lstat(self, path):
467
Retrieve information about a file on the remote system, without
468
following symbolic links (shortcuts). This otherwise behaves exactly
471
:param str path: the filename to stat
473
an `.SFTPAttributes` object containing attributes about the given
476
path = self._adjust_cwd(path)
477
self._log(DEBUG, 'lstat({!r})'.format(path))
478
t, msg = self._request(CMD_LSTAT, path)
480
raise SFTPError('Expected attributes')
481
return SFTPAttributes._from_msg(msg)
483
def symlink(self, source, dest):
485
Create a symbolic link to the ``source`` path at ``destination``.
487
:param str source: path of the original file
488
:param str dest: path of the newly created symlink
490
dest = self._adjust_cwd(dest)
491
self._log(DEBUG, 'symlink({!r}, {!r})'.format(source, dest))
492
source = bytestring(source)
493
self._request(CMD_SYMLINK, source, dest)
495
def chmod(self, path, mode):
497
Change the mode (permissions) of a file. The permissions are
498
unix-style and identical to those used by Python's `os.chmod`
501
:param str path: path of the file to change the permissions of
502
:param int mode: new permissions
504
path = self._adjust_cwd(path)
505
self._log(DEBUG, 'chmod({!r}, {!r})'.format(path, mode))
506
attr = SFTPAttributes()
508
self._request(CMD_SETSTAT, path, attr)
510
def chown(self, path, uid, gid):
512
Change the owner (``uid``) and group (``gid``) of a file. As with
513
Python's `os.chown` function, you must pass both arguments, so if you
514
only want to change one, use `stat` first to retrieve the current
517
:param str path: path of the file to change the owner and group of
518
:param int uid: new owner's uid
519
:param int gid: new group id
521
path = self._adjust_cwd(path)
522
self._log(DEBUG, 'chown({!r}, {!r}, {!r})'.format(path, uid, gid))
523
attr = SFTPAttributes()
524
attr.st_uid, attr.st_gid = uid, gid
525
self._request(CMD_SETSTAT, path, attr)
527
def utime(self, path, times):
529
Set the access and modified times of the file specified by ``path``.
530
If ``times`` is ``None``, then the file's access and modified times
531
are set to the current time. Otherwise, ``times`` must be a 2-tuple
532
of numbers, of the form ``(atime, mtime)``, which is used to set the
533
access and modified times, respectively. This bizarre API is mimicked
534
from Python for the sake of consistency -- I apologize.
536
:param str path: path of the file to modify
538
``None`` or a tuple of (access time, modified time) in standard
539
internet epoch time (seconds since 01 January 1970 GMT)
541
path = self._adjust_cwd(path)
543
times = (time.time(), time.time())
544
self._log(DEBUG, 'utime({!r}, {!r})'.format(path, times))
545
attr = SFTPAttributes()
546
attr.st_atime, attr.st_mtime = times
547
self._request(CMD_SETSTAT, path, attr)
549
def truncate(self, path, size):
551
Change the size of the file specified by ``path``. This usually
552
extends or shrinks the size of the file, just like the `~file.truncate`
553
method on Python file objects.
555
:param str path: path of the file to modify
556
:param int size: the new size of the file
558
path = self._adjust_cwd(path)
559
self._log(DEBUG, 'truncate({!r}, {!r})'.format(path, size))
560
attr = SFTPAttributes()
562
self._request(CMD_SETSTAT, path, attr)
564
def readlink(self, path):
566
Return the target of a symbolic link (shortcut). You can use
567
`symlink` to create these. The result may be either an absolute or
570
:param str path: path of the symbolic link file
571
:return: target path, as a `str`
573
path = self._adjust_cwd(path)
574
self._log(DEBUG, 'readlink({!r})'.format(path))
575
t, msg = self._request(CMD_READLINK, path)
577
raise SFTPError('Expected name response')
578
count = msg.get_int()
582
raise SFTPError('Readlink returned {} results'.format(count))
583
return _to_unicode(msg.get_string())
585
def normalize(self, path):
587
Return the normalized path (on the server) of a given path. This
588
can be used to quickly resolve symbolic links or determine what the
589
server is considering to be the "current folder" (by passing ``'.'``
592
:param str path: path to be normalized
593
:return: normalized form of the given path (as a `str`)
595
:raises: ``IOError`` -- if the path can't be resolved on the server
597
path = self._adjust_cwd(path)
598
self._log(DEBUG, 'normalize({!r})'.format(path))
599
t, msg = self._request(CMD_REALPATH, path)
601
raise SFTPError('Expected name response')
602
count = msg.get_int()
604
raise SFTPError('Realpath returned {} results'.format(count))
605
return msg.get_text()
607
def chdir(self, path=None):
609
Change the "current directory" of this SFTP session. Since SFTP
610
doesn't really have the concept of a current working directory, this is
611
emulated by Paramiko. Once you use this method to set a working
612
directory, all operations on this `.SFTPClient` object will be relative
613
to that path. You can pass in ``None`` to stop using a current working
616
:param str path: new current working directory
619
``IOError`` -- if the requested path doesn't exist on the server
621
.. versionadded:: 1.4
626
if not stat.S_ISDIR(self.stat(path).st_mode):
629
code, "{}: {}".format(os.strerror(code), path)
631
self._cwd = b(self.normalize(path))
635
Return the "current working directory" for this SFTP session, as
636
emulated by Paramiko. If no directory has been set with `chdir`,
637
this method will return ``None``.
639
.. versionadded:: 1.4
641
# TODO: make class initialize with self._cwd set to self.normalize('.')
642
return self._cwd and u(self._cwd)
644
def _transfer_with_callback(self, reader, writer, file_size, callback):
647
data = reader.read(32768)
652
if callback is not None:
653
callback(size, file_size)
656
def putfo(self, fl, remotepath, file_size=0, callback=None, confirm=True):
658
Copy the contents of an open file object (``fl``) to the SFTP server as
659
``remotepath``. Any exception raised by operations will be passed
662
The SFTP operations use pipelining for speed.
664
:param fl: opened file or file-like object to copy
665
:param str remotepath: the destination path on the SFTP server
666
:param int file_size:
667
optional size parameter passed to callback. If none is specified,
669
:param callable callback:
670
optional callback function (form: ``func(int, int)``) that accepts
671
the bytes transferred so far and the total bytes to be transferred
674
whether to do a stat() on the file afterwards to confirm the file
678
an `.SFTPAttributes` object containing attributes about the given
681
.. versionadded:: 1.10
683
with self.file(remotepath, 'wb') as fr:
684
fr.set_pipelined(True)
685
size = self._transfer_with_callback(
686
reader=fl, writer=fr, file_size=file_size, callback=callback
689
s = self.stat(remotepath)
690
if s.st_size != size:
692
'size mismatch in put! {} != {}'.format(s.st_size, size))
697
def put(self, localpath, remotepath, callback=None, confirm=True):
699
Copy a local file (``localpath``) to the SFTP server as ``remotepath``.
700
Any exception raised by operations will be passed through. This
701
method is primarily provided as a convenience.
703
The SFTP operations use pipelining for speed.
705
:param str localpath: the local file to copy
706
:param str remotepath: the destination path on the SFTP server. Note
707
that the filename should be included. Only specifying a directory
708
may result in an error.
709
:param callable callback:
710
optional callback function (form: ``func(int, int)``) that accepts
711
the bytes transferred so far and the total bytes to be transferred
713
whether to do a stat() on the file afterwards to confirm the file
716
:return: an `.SFTPAttributes` object containing attributes about the
719
.. versionadded:: 1.4
720
.. versionchanged:: 1.7.4
721
``callback`` and rich attribute return value added.
722
.. versionchanged:: 1.7.7
723
``confirm`` param added.
725
file_size = os.stat(localpath).st_size
726
with open(localpath, 'rb') as fl:
727
return self.putfo(fl, remotepath, file_size, callback, confirm)
729
def getfo(self, remotepath, fl, callback=None):
731
Copy a remote file (``remotepath``) from the SFTP server and write to
732
an open file or file-like object, ``fl``. Any exception raised by
733
operations will be passed through. This method is primarily provided
736
:param object remotepath: opened file or file-like object to copy to
738
the destination path on the local host or open file object
739
:param callable callback:
740
optional callback function (form: ``func(int, int)``) that accepts
741
the bytes transferred so far and the total bytes to be transferred
742
:return: the `number <int>` of bytes written to the opened file object
744
.. versionadded:: 1.10
746
file_size = self.stat(remotepath).st_size
747
with self.open(remotepath, 'rb') as fr:
748
fr.prefetch(file_size)
749
return self._transfer_with_callback(
750
reader=fr, writer=fl, file_size=file_size, callback=callback
753
def get(self, remotepath, localpath, callback=None):
755
Copy a remote file (``remotepath``) from the SFTP server to the local
756
host as ``localpath``. Any exception raised by operations will be
757
passed through. This method is primarily provided as a convenience.
759
:param str remotepath: the remote file to copy
760
:param str localpath: the destination path on the local host
761
:param callable callback:
762
optional callback function (form: ``func(int, int)``) that accepts
763
the bytes transferred so far and the total bytes to be transferred
765
.. versionadded:: 1.4
766
.. versionchanged:: 1.7.4
767
Added the ``callback`` param
769
with open(localpath, 'wb') as fl:
770
size = self.getfo(remotepath, fl, callback)
771
s = os.stat(localpath)
772
if s.st_size != size:
774
'size mismatch in get! {} != {}'.format(s.st_size, size))
778
def _request(self, t, *arg):
779
num = self._async_request(type(None), t, *arg)
780
return self._read_response(num)
782
def _async_request(self, fileobj, t, *arg):
783
# this method may be called from other threads (prefetch)
787
msg.add_int(self.request_number)
789
if isinstance(item, long):
791
elif isinstance(item, int):
793
elif isinstance(item, SFTPAttributes):
796
# For all other types, rely on as_string() to either coerce
797
# to bytes before writing or raise a suitable exception.
799
num = self.request_number
800
self._expecting[num] = fileobj
801
self.request_number += 1
804
self._send_packet(t, msg)
807
def _read_response(self, waitfor=None):
810
t, data = self._read_packet()
811
except EOFError as e:
812
raise SSHException('Server connection dropped: {}'.format(e))
817
if num not in self._expecting:
818
# might be response for a file that was closed before
819
# responses came back
820
self._log(DEBUG, 'Unexpected response #{}'.format(num))
822
# just doing a single check
825
fileobj = self._expecting[num]
826
del self._expecting[num]
832
self._convert_status(msg)
835
# can not rewrite this to deal with E721, either as a None check
836
# nor as not an instance of None or NoneType
837
if fileobj is not type(None): # noqa
838
fileobj._async_response(t, msg, num)
840
# just doing a single check
844
def _finish_responses(self, fileobj):
845
while fileobj in self._expecting.values():
846
self._read_response()
847
fileobj._check_exception()
849
def _convert_status(self, msg):
851
Raises EOFError or IOError on error status; otherwise does nothing.
854
text = msg.get_text()
857
elif code == SFTP_EOF:
859
elif code == SFTP_NO_SUCH_FILE:
860
# clever idea from john a. meinel: map the error codes to errno
861
raise IOError(errno.ENOENT, text)
862
elif code == SFTP_PERMISSION_DENIED:
863
raise IOError(errno.EACCES, text)
867
def _adjust_cwd(self, path):
869
Return an adjusted path if we're emulating a "current working
870
directory" for the server.
873
if self._cwd is None:
875
if len(path) and path[0:1] == b_slash:
878
if self._cwd == b_slash:
879
return self._cwd + path
880
return self._cwd + b_slash + path
883
class SFTP(SFTPClient):
885
An alias for `.SFTPClient` for backwards compatibility.