Coverage for /pythoncovmergedfiles/medio/medio/src/paramiko/paramiko/sftp_client.py: 17%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
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 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
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# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20from binascii import hexlify
21import errno
22import os
23import stat
24import threading
25import time
26import weakref
27from paramiko import util
28from paramiko.channel import Channel
29from paramiko.message import Message
30from paramiko.common import INFO, DEBUG, o777
31from paramiko.sftp import (
32 BaseSFTP,
33 CMD_OPENDIR,
34 CMD_HANDLE,
35 SFTPError,
36 CMD_READDIR,
37 CMD_NAME,
38 CMD_CLOSE,
39 SFTP_FLAG_READ,
40 SFTP_FLAG_WRITE,
41 SFTP_FLAG_CREATE,
42 SFTP_FLAG_TRUNC,
43 SFTP_FLAG_APPEND,
44 SFTP_FLAG_EXCL,
45 CMD_OPEN,
46 CMD_REMOVE,
47 CMD_RENAME,
48 CMD_MKDIR,
49 CMD_RMDIR,
50 CMD_STAT,
51 CMD_ATTRS,
52 CMD_LSTAT,
53 CMD_SYMLINK,
54 CMD_SETSTAT,
55 CMD_READLINK,
56 CMD_REALPATH,
57 CMD_STATUS,
58 CMD_EXTENDED,
59 SFTP_OK,
60 SFTP_EOF,
61 SFTP_NO_SUCH_FILE,
62 SFTP_PERMISSION_DENIED,
63 int64,
64)
66from paramiko.sftp_attr import SFTPAttributes
67from paramiko.ssh_exception import SSHException
68from paramiko.sftp_file import SFTPFile
69from paramiko.util import ClosingContextManager, b, u
72def _to_unicode(s):
73 """
74 decode a string as ascii or utf8 if possible (as required by the sftp
75 protocol). if neither works, just return a byte string because the server
76 probably doesn't know the filename's encoding.
77 """
78 try:
79 return s.encode("ascii")
80 except (UnicodeError, AttributeError):
81 try:
82 return s.decode("utf-8")
83 except UnicodeError:
84 return s
87b_slash = b"/"
90class SFTPClient(BaseSFTP, ClosingContextManager):
91 """
92 SFTP client object.
94 Used to open an SFTP session across an open SSH `.Transport` and perform
95 remote file operations.
97 Instances of this class may be used as context managers.
98 """
100 def __init__(self, sock):
101 """
102 Create an SFTP client from an existing `.Channel`. The channel
103 should already have requested the ``"sftp"`` subsystem.
105 An alternate way to create an SFTP client context is by using
106 `from_transport`.
108 :param .Channel sock: an open `.Channel` using the ``"sftp"`` subsystem
110 :raises:
111 `.SSHException` -- if there's an exception while negotiating sftp
112 """
113 BaseSFTP.__init__(self)
114 self.sock = sock
115 self.ultra_debug = False
116 self.request_number = 1
117 # lock for request_number
118 self._lock = threading.Lock()
119 self._cwd = None
120 # request # -> SFTPFile
121 self._expecting = weakref.WeakValueDictionary()
122 if type(sock) is Channel:
123 # override default logger
124 transport = self.sock.get_transport()
125 self.logger = util.get_logger(
126 transport.get_log_channel() + ".sftp"
127 )
128 self.ultra_debug = transport.get_hexdump()
129 try:
130 server_version = self._send_version()
131 except EOFError:
132 raise SSHException("EOF during negotiation")
133 self._log(
134 INFO,
135 "Opened sftp connection (server version {})".format(
136 server_version
137 ),
138 )
140 @classmethod
141 def from_transport(cls, t, window_size=None, max_packet_size=None):
142 """
143 Create an SFTP client channel from an open `.Transport`.
145 Setting the window and packet sizes might affect the transfer speed.
146 The default settings in the `.Transport` class are the same as in
147 OpenSSH and should work adequately for both files transfers and
148 interactive sessions.
150 :param .Transport t: an open `.Transport` which is already
151 authenticated
152 :param int window_size:
153 optional window size for the `.SFTPClient` session.
154 :param int max_packet_size:
155 optional max packet size for the `.SFTPClient` session..
157 :return:
158 a new `.SFTPClient` object, referring to an sftp session (channel)
159 across the transport
161 .. versionchanged:: 1.15
162 Added the ``window_size`` and ``max_packet_size`` arguments.
163 """
164 chan = t.open_session(
165 window_size=window_size, max_packet_size=max_packet_size
166 )
167 if chan is None:
168 return None
169 chan.invoke_subsystem("sftp")
170 return cls(chan)
172 def _log(self, level, msg, *args):
173 if isinstance(msg, list):
174 for m in msg:
175 self._log(level, m, *args)
176 else:
177 # NOTE: these bits MUST continue using %-style format junk because
178 # logging.Logger.log() explicitly requires it. Grump.
179 # escape '%' in msg (they could come from file or directory names)
180 # before logging
181 msg = msg.replace("%", "%%")
182 super()._log(
183 level,
184 "[chan %s] " + msg,
185 *([self.sock.get_name()] + list(args))
186 )
188 def close(self):
189 """
190 Close the SFTP session and its underlying channel.
192 .. versionadded:: 1.4
193 """
194 self._log(INFO, "sftp session closed.")
195 self.sock.close()
197 def get_channel(self):
198 """
199 Return the underlying `.Channel` object for this SFTP session. This
200 might be useful for doing things like setting a timeout on the channel.
202 .. versionadded:: 1.7.1
203 """
204 return self.sock
206 def listdir(self, path="."):
207 """
208 Return a list containing the names of the entries in the given
209 ``path``.
211 The list is in arbitrary order. It does not include the special
212 entries ``'.'`` and ``'..'`` even if they are present in the folder.
213 This method is meant to mirror ``os.listdir`` as closely as possible.
214 For a list of full `.SFTPAttributes` objects, see `listdir_attr`.
216 :param str path: path to list (defaults to ``'.'``)
217 """
218 return [f.filename for f in self.listdir_attr(path)]
220 def listdir_attr(self, path="."):
221 """
222 Return a list containing `.SFTPAttributes` objects corresponding to
223 files in the given ``path``. The list is in arbitrary order. It does
224 not include the special entries ``'.'`` and ``'..'`` even if they are
225 present in the folder.
227 The returned `.SFTPAttributes` objects will each have an additional
228 field: ``longname``, which may contain a formatted string of the file's
229 attributes, in unix format. The content of this string will probably
230 depend on the SFTP server implementation.
232 :param str path: path to list (defaults to ``'.'``)
233 :return: list of `.SFTPAttributes` objects
235 .. versionadded:: 1.2
236 """
237 path = self._adjust_cwd(path)
238 self._log(DEBUG, "listdir({!r})".format(path))
239 t, msg = self._request(CMD_OPENDIR, path)
240 if t != CMD_HANDLE:
241 raise SFTPError("Expected handle")
242 handle = msg.get_binary()
243 filelist = []
244 while True:
245 try:
246 t, msg = self._request(CMD_READDIR, handle)
247 except EOFError:
248 # done with handle
249 break
250 if t != CMD_NAME:
251 raise SFTPError("Expected name response")
252 count = msg.get_int()
253 for i in range(count):
254 filename = msg.get_text()
255 longname = msg.get_text()
256 attr = SFTPAttributes._from_msg(msg, filename, longname)
257 if (filename != ".") and (filename != ".."):
258 filelist.append(attr)
259 self._request(CMD_CLOSE, handle)
260 return filelist
262 def listdir_iter(self, path=".", read_aheads=50):
263 """
264 Generator version of `.listdir_attr`.
266 See the API docs for `.listdir_attr` for overall details.
268 This function adds one more kwarg on top of `.listdir_attr`:
269 ``read_aheads``, an integer controlling how many
270 ``SSH_FXP_READDIR`` requests are made to the server. The default of 50
271 should suffice for most file listings as each request/response cycle
272 may contain multiple files (dependent on server implementation.)
274 .. versionadded:: 1.15
275 """
276 path = self._adjust_cwd(path)
277 self._log(DEBUG, "listdir({!r})".format(path))
278 t, msg = self._request(CMD_OPENDIR, path)
280 if t != CMD_HANDLE:
281 raise SFTPError("Expected handle")
283 handle = msg.get_string()
285 nums = list()
286 while True:
287 try:
288 # Send out a bunch of readdir requests so that we can read the
289 # responses later on Section 6.7 of the SSH file transfer RFC
290 # explains this
291 # http://filezilla-project.org/specs/draft-ietf-secsh-filexfer-02.txt
292 for i in range(read_aheads):
293 num = self._async_request(type(None), CMD_READDIR, handle)
294 nums.append(num)
296 # For each of our sent requests
297 # Read and parse the corresponding packets
298 # If we're at the end of our queued requests, then fire off
299 # some more requests
300 # Exit the loop when we've reached the end of the directory
301 # handle
302 for num in nums:
303 t, pkt_data = self._read_packet()
304 msg = Message(pkt_data)
305 new_num = msg.get_int()
306 if num == new_num:
307 if t == CMD_STATUS:
308 self._convert_status(msg)
309 count = msg.get_int()
310 for i in range(count):
311 filename = msg.get_text()
312 longname = msg.get_text()
313 attr = SFTPAttributes._from_msg(
314 msg, filename, longname
315 )
316 if (filename != ".") and (filename != ".."):
317 yield attr
319 # If we've hit the end of our queued requests, reset nums.
320 nums = list()
322 except EOFError:
323 self._request(CMD_CLOSE, handle)
324 return
326 def open(self, filename, mode="r", bufsize=-1):
327 """
328 Open a file on the remote server. The arguments are the same as for
329 Python's built-in `python:file` (aka `python:open`). A file-like
330 object is returned, which closely mimics the behavior of a normal
331 Python file object, including the ability to be used as a context
332 manager.
334 The mode indicates how the file is to be opened: ``'r'`` for reading,
335 ``'w'`` for writing (truncating an existing file), ``'a'`` for
336 appending, ``'r+'`` for reading/writing, ``'w+'`` for reading/writing
337 (truncating an existing file), ``'a+'`` for reading/appending. The
338 Python ``'b'`` flag is ignored, since SSH treats all files as binary.
339 The ``'U'`` flag is supported in a compatible way.
341 Since 1.5.2, an ``'x'`` flag indicates that the operation should only
342 succeed if the file was created and did not previously exist. This has
343 no direct mapping to Python's file flags, but is commonly known as the
344 ``O_EXCL`` flag in posix.
346 The file will be buffered in standard Python style by default, but
347 can be altered with the ``bufsize`` parameter. ``<=0`` turns off
348 buffering, ``1`` uses line buffering, and any number greater than 1
349 (``>1``) uses that specific buffer size.
351 :param str filename: name of the file to open
352 :param str mode: mode (Python-style) to open in
353 :param int bufsize: desired buffering (default: ``-1``)
354 :return: an `.SFTPFile` object representing the open file
356 :raises: ``IOError`` -- if the file could not be opened.
357 """
358 filename = self._adjust_cwd(filename)
359 self._log(DEBUG, "open({!r}, {!r})".format(filename, mode))
360 imode = 0
361 if ("r" in mode) or ("+" in mode):
362 imode |= SFTP_FLAG_READ
363 if ("w" in mode) or ("+" in mode) or ("a" in mode):
364 imode |= SFTP_FLAG_WRITE
365 if "w" in mode:
366 imode |= SFTP_FLAG_CREATE | SFTP_FLAG_TRUNC
367 if "a" in mode:
368 imode |= SFTP_FLAG_CREATE | SFTP_FLAG_APPEND
369 if "x" in mode:
370 imode |= SFTP_FLAG_CREATE | SFTP_FLAG_EXCL
371 attrblock = SFTPAttributes()
372 t, msg = self._request(CMD_OPEN, filename, imode, attrblock)
373 if t != CMD_HANDLE:
374 raise SFTPError("Expected handle")
375 handle = msg.get_binary()
376 self._log(
377 DEBUG,
378 "open({!r}, {!r}) -> {}".format(
379 filename, mode, u(hexlify(handle))
380 ),
381 )
382 return SFTPFile(self, handle, mode, bufsize)
384 # Python continues to vacillate about "open" vs "file"...
385 file = open
387 def remove(self, path):
388 """
389 Remove the file at the given path. This only works on files; for
390 removing folders (directories), use `rmdir`.
392 :param str path: path (absolute or relative) of the file to remove
394 :raises: ``IOError`` -- if the path refers to a folder (directory)
395 """
396 path = self._adjust_cwd(path)
397 self._log(DEBUG, "remove({!r})".format(path))
398 self._request(CMD_REMOVE, path)
400 unlink = remove
402 def rename(self, oldpath, newpath):
403 """
404 Rename a file or folder from ``oldpath`` to ``newpath``.
406 .. note::
407 This method implements 'standard' SFTP ``RENAME`` behavior; those
408 seeking the OpenSSH "POSIX rename" extension behavior should use
409 `posix_rename`.
411 :param str oldpath:
412 existing name of the file or folder
413 :param str newpath:
414 new name for the file or folder, must not exist already
416 :raises:
417 ``IOError`` -- if ``newpath`` is a folder, or something else goes
418 wrong
419 """
420 oldpath = self._adjust_cwd(oldpath)
421 newpath = self._adjust_cwd(newpath)
422 self._log(DEBUG, "rename({!r}, {!r})".format(oldpath, newpath))
423 self._request(CMD_RENAME, oldpath, newpath)
425 def posix_rename(self, oldpath, newpath):
426 """
427 Rename a file or folder from ``oldpath`` to ``newpath``, following
428 posix conventions.
430 :param str oldpath: existing name of the file or folder
431 :param str newpath: new name for the file or folder, will be
432 overwritten if it already exists
434 :raises:
435 ``IOError`` -- if ``newpath`` is a folder, posix-rename is not
436 supported by the server or something else goes wrong
438 :versionadded: 2.2
439 """
440 oldpath = self._adjust_cwd(oldpath)
441 newpath = self._adjust_cwd(newpath)
442 self._log(DEBUG, "posix_rename({!r}, {!r})".format(oldpath, newpath))
443 self._request(
444 CMD_EXTENDED, "posix-rename@openssh.com", oldpath, newpath
445 )
447 def mkdir(self, path, mode=o777):
448 """
449 Create a folder (directory) named ``path`` with numeric mode ``mode``.
450 The default mode is 0777 (octal). On some systems, mode is ignored.
451 Where it is used, the current umask value is first masked out.
453 :param str path: name of the folder to create
454 :param int mode: permissions (posix-style) for the newly-created folder
455 """
456 path = self._adjust_cwd(path)
457 self._log(DEBUG, "mkdir({!r}, {!r})".format(path, mode))
458 attr = SFTPAttributes()
459 attr.st_mode = mode
460 self._request(CMD_MKDIR, path, attr)
462 def rmdir(self, path):
463 """
464 Remove the folder named ``path``.
466 :param str path: name of the folder to remove
467 """
468 path = self._adjust_cwd(path)
469 self._log(DEBUG, "rmdir({!r})".format(path))
470 self._request(CMD_RMDIR, path)
472 def stat(self, path):
473 """
474 Retrieve information about a file on the remote system. The return
475 value is an object whose attributes correspond to the attributes of
476 Python's ``stat`` structure as returned by ``os.stat``, except that it
477 contains fewer fields. An SFTP server may return as much or as little
478 info as it wants, so the results may vary from server to server.
480 Unlike a Python `python:stat` object, the result may not be accessed as
481 a tuple. This is mostly due to the author's slack factor.
483 The fields supported are: ``st_mode``, ``st_size``, ``st_uid``,
484 ``st_gid``, ``st_atime``, and ``st_mtime``.
486 :param str path: the filename to stat
487 :return:
488 an `.SFTPAttributes` object containing attributes about the given
489 file
490 """
491 path = self._adjust_cwd(path)
492 self._log(DEBUG, "stat({!r})".format(path))
493 t, msg = self._request(CMD_STAT, path)
494 if t != CMD_ATTRS:
495 raise SFTPError("Expected attributes")
496 return SFTPAttributes._from_msg(msg)
498 def lstat(self, path):
499 """
500 Retrieve information about a file on the remote system, without
501 following symbolic links (shortcuts). This otherwise behaves exactly
502 the same as `stat`.
504 :param str path: the filename to stat
505 :return:
506 an `.SFTPAttributes` object containing attributes about the given
507 file
508 """
509 path = self._adjust_cwd(path)
510 self._log(DEBUG, "lstat({!r})".format(path))
511 t, msg = self._request(CMD_LSTAT, path)
512 if t != CMD_ATTRS:
513 raise SFTPError("Expected attributes")
514 return SFTPAttributes._from_msg(msg)
516 def symlink(self, source, dest):
517 """
518 Create a symbolic link to the ``source`` path at ``destination``.
520 :param str source: path of the original file
521 :param str dest: path of the newly created symlink
522 """
523 dest = self._adjust_cwd(dest)
524 self._log(DEBUG, "symlink({!r}, {!r})".format(source, dest))
525 source = b(source)
526 self._request(CMD_SYMLINK, source, dest)
528 def chmod(self, path, mode):
529 """
530 Change the mode (permissions) of a file. The permissions are
531 unix-style and identical to those used by Python's `os.chmod`
532 function.
534 :param str path: path of the file to change the permissions of
535 :param int mode: new permissions
536 """
537 path = self._adjust_cwd(path)
538 self._log(DEBUG, "chmod({!r}, {!r})".format(path, mode))
539 attr = SFTPAttributes()
540 attr.st_mode = mode
541 self._request(CMD_SETSTAT, path, attr)
543 def chown(self, path, uid, gid):
544 """
545 Change the owner (``uid``) and group (``gid``) of a file. As with
546 Python's `os.chown` function, you must pass both arguments, so if you
547 only want to change one, use `stat` first to retrieve the current
548 owner and group.
550 :param str path: path of the file to change the owner and group of
551 :param int uid: new owner's uid
552 :param int gid: new group id
553 """
554 path = self._adjust_cwd(path)
555 self._log(DEBUG, "chown({!r}, {!r}, {!r})".format(path, uid, gid))
556 attr = SFTPAttributes()
557 attr.st_uid, attr.st_gid = uid, gid
558 self._request(CMD_SETSTAT, path, attr)
560 def utime(self, path, times):
561 """
562 Set the access and modified times of the file specified by ``path``.
563 If ``times`` is ``None``, then the file's access and modified times
564 are set to the current time. Otherwise, ``times`` must be a 2-tuple
565 of numbers, of the form ``(atime, mtime)``, which is used to set the
566 access and modified times, respectively. This bizarre API is mimicked
567 from Python for the sake of consistency -- I apologize.
569 :param str path: path of the file to modify
570 :param tuple times:
571 ``None`` or a tuple of (access time, modified time) in standard
572 internet epoch time (seconds since 01 January 1970 GMT)
573 """
574 path = self._adjust_cwd(path)
575 if times is None:
576 times = (time.time(), time.time())
577 self._log(DEBUG, "utime({!r}, {!r})".format(path, times))
578 attr = SFTPAttributes()
579 attr.st_atime, attr.st_mtime = times
580 self._request(CMD_SETSTAT, path, attr)
582 def truncate(self, path, size):
583 """
584 Change the size of the file specified by ``path``. This usually
585 extends or shrinks the size of the file, just like the `~file.truncate`
586 method on Python file objects.
588 :param str path: path of the file to modify
589 :param int size: the new size of the file
590 """
591 path = self._adjust_cwd(path)
592 self._log(DEBUG, "truncate({!r}, {!r})".format(path, size))
593 attr = SFTPAttributes()
594 attr.st_size = size
595 self._request(CMD_SETSTAT, path, attr)
597 def readlink(self, path):
598 """
599 Return the target of a symbolic link (shortcut). You can use
600 `symlink` to create these. The result may be either an absolute or
601 relative pathname.
603 :param str path: path of the symbolic link file
604 :return: target path, as a `str`
605 """
606 path = self._adjust_cwd(path)
607 self._log(DEBUG, "readlink({!r})".format(path))
608 t, msg = self._request(CMD_READLINK, path)
609 if t != CMD_NAME:
610 raise SFTPError("Expected name response")
611 count = msg.get_int()
612 if count == 0:
613 return None
614 if count != 1:
615 raise SFTPError("Readlink returned {} results".format(count))
616 return _to_unicode(msg.get_string())
618 def normalize(self, path):
619 """
620 Return the normalized path (on the server) of a given path. This
621 can be used to quickly resolve symbolic links or determine what the
622 server is considering to be the "current folder" (by passing ``'.'``
623 as ``path``).
625 :param str path: path to be normalized
626 :return: normalized form of the given path (as a `str`)
628 :raises: ``IOError`` -- if the path can't be resolved on the server
629 """
630 path = self._adjust_cwd(path)
631 self._log(DEBUG, "normalize({!r})".format(path))
632 t, msg = self._request(CMD_REALPATH, path)
633 if t != CMD_NAME:
634 raise SFTPError("Expected name response")
635 count = msg.get_int()
636 if count != 1:
637 raise SFTPError("Realpath returned {} results".format(count))
638 return msg.get_text()
640 def chdir(self, path=None):
641 """
642 Change the "current directory" of this SFTP session. Since SFTP
643 doesn't really have the concept of a current working directory, this is
644 emulated by Paramiko. Once you use this method to set a working
645 directory, all operations on this `.SFTPClient` object will be relative
646 to that path. You can pass in ``None`` to stop using a current working
647 directory.
649 :param str path: new current working directory
651 :raises:
652 ``IOError`` -- if the requested path doesn't exist on the server
654 .. versionadded:: 1.4
655 """
656 if path is None:
657 self._cwd = None
658 return
659 if not stat.S_ISDIR(self.stat(path).st_mode):
660 code = errno.ENOTDIR
661 raise SFTPError(code, "{}: {}".format(os.strerror(code), path))
662 self._cwd = b(self.normalize(path))
664 def getcwd(self):
665 """
666 Return the "current working directory" for this SFTP session, as
667 emulated by Paramiko. If no directory has been set with `chdir`,
668 this method will return ``None``.
670 .. versionadded:: 1.4
671 """
672 # TODO: make class initialize with self._cwd set to self.normalize('.')
673 return self._cwd and u(self._cwd)
675 def _transfer_with_callback(self, reader, writer, file_size, callback):
676 size = 0
677 while True:
678 data = reader.read(32768)
679 writer.write(data)
680 size += len(data)
681 if len(data) == 0:
682 break
683 if callback is not None:
684 callback(size, file_size)
685 return size
687 def putfo(self, fl, remotepath, file_size=0, callback=None, confirm=True):
688 """
689 Copy the contents of an open file object (``fl``) to the SFTP server as
690 ``remotepath``. Any exception raised by operations will be passed
691 through.
693 The SFTP operations use pipelining for speed.
695 :param fl: opened file or file-like object to copy
696 :param str remotepath: the destination path on the SFTP server
697 :param int file_size:
698 optional size parameter passed to callback. If none is specified,
699 size defaults to 0
700 :param callable callback:
701 optional callback function (form: ``func(int, int)``) that accepts
702 the bytes transferred so far and the total bytes to be transferred
703 (since 1.7.4)
704 :param bool confirm:
705 whether to do a stat() on the file afterwards to confirm the file
706 size (since 1.7.7)
708 :return:
709 an `.SFTPAttributes` object containing attributes about the given
710 file.
712 .. versionadded:: 1.10
713 """
714 with self.file(remotepath, "wb") as fr:
715 fr.set_pipelined(True)
716 size = self._transfer_with_callback(
717 reader=fl, writer=fr, file_size=file_size, callback=callback
718 )
719 if confirm:
720 s = self.stat(remotepath)
721 if s.st_size != size:
722 raise IOError(
723 "size mismatch in put! {} != {}".format(s.st_size, size)
724 )
725 else:
726 s = SFTPAttributes()
727 return s
729 def put(self, localpath, remotepath, callback=None, confirm=True):
730 """
731 Copy a local file (``localpath``) to the SFTP server as ``remotepath``.
732 Any exception raised by operations will be passed through. This
733 method is primarily provided as a convenience.
735 The SFTP operations use pipelining for speed.
737 :param str localpath: the local file to copy
738 :param str remotepath: the destination path on the SFTP server. Note
739 that the filename should be included. Only specifying a directory
740 may result in an error.
741 :param callable callback:
742 optional callback function (form: ``func(int, int)``) that accepts
743 the bytes transferred so far and the total bytes to be transferred
744 :param bool confirm:
745 whether to do a stat() on the file afterwards to confirm the file
746 size
748 :return: an `.SFTPAttributes` object containing attributes about the
749 given file
751 .. versionadded:: 1.4
752 .. versionchanged:: 1.7.4
753 ``callback`` and rich attribute return value added.
754 .. versionchanged:: 1.7.7
755 ``confirm`` param added.
756 """
757 file_size = os.stat(localpath).st_size
758 with open(localpath, "rb") as fl:
759 return self.putfo(fl, remotepath, file_size, callback, confirm)
761 def getfo(
762 self,
763 remotepath,
764 fl,
765 callback=None,
766 prefetch=True,
767 max_concurrent_prefetch_requests=None,
768 ):
769 """
770 Copy a remote file (``remotepath``) from the SFTP server and write to
771 an open file or file-like object, ``fl``. Any exception raised by
772 operations will be passed through. This method is primarily provided
773 as a convenience.
775 :param object remotepath: opened file or file-like object to copy to
776 :param str fl:
777 the destination path on the local host or open file object
778 :param callable callback:
779 optional callback function (form: ``func(int, int)``) that accepts
780 the bytes transferred so far and the total bytes to be transferred
781 :param bool prefetch:
782 controls whether prefetching is performed (default: True)
783 :param int max_concurrent_prefetch_requests:
784 The maximum number of concurrent read requests to prefetch. See
785 `.SFTPClient.get` (its ``max_concurrent_prefetch_requests`` param)
786 for details.
787 :return: the `number <int>` of bytes written to the opened file object
789 .. versionadded:: 1.10
790 .. versionchanged:: 2.8
791 Added the ``prefetch`` keyword argument.
792 .. versionchanged:: 3.3
793 Added ``max_concurrent_prefetch_requests``.
794 """
795 file_size = self.stat(remotepath).st_size
796 with self.open(remotepath, "rb") as fr:
797 if prefetch:
798 fr.prefetch(file_size, max_concurrent_prefetch_requests)
799 return self._transfer_with_callback(
800 reader=fr, writer=fl, file_size=file_size, callback=callback
801 )
803 def get(
804 self,
805 remotepath,
806 localpath,
807 callback=None,
808 prefetch=True,
809 max_concurrent_prefetch_requests=None,
810 ):
811 """
812 Copy a remote file (``remotepath``) from the SFTP server to the local
813 host as ``localpath``. Any exception raised by operations will be
814 passed through. This method is primarily provided as a convenience.
816 :param str remotepath: the remote file to copy
817 :param str localpath: the destination path on the local host
818 :param callable callback:
819 optional callback function (form: ``func(int, int)``) that accepts
820 the bytes transferred so far and the total bytes to be transferred
821 :param bool prefetch:
822 controls whether prefetching is performed (default: True)
823 :param int max_concurrent_prefetch_requests:
824 The maximum number of concurrent read requests to prefetch.
825 When this is ``None`` (the default), do not limit the number of
826 concurrent prefetch requests. Note: OpenSSH's sftp internally
827 imposes a limit of 64 concurrent requests, while Paramiko imposes
828 no limit by default; consider setting a limit if a file can be
829 successfully received with sftp but hangs with Paramiko.
831 .. versionadded:: 1.4
832 .. versionchanged:: 1.7.4
833 Added the ``callback`` param
834 .. versionchanged:: 2.8
835 Added the ``prefetch`` keyword argument.
836 .. versionchanged:: 3.3
837 Added ``max_concurrent_prefetch_requests``.
838 """
839 with open(localpath, "wb") as fl:
840 size = self.getfo(
841 remotepath,
842 fl,
843 callback,
844 prefetch,
845 max_concurrent_prefetch_requests,
846 )
847 s = os.stat(localpath)
848 if s.st_size != size:
849 raise IOError(
850 "size mismatch in get! {} != {}".format(s.st_size, size)
851 )
853 # ...internals...
855 def _request(self, t, *args):
856 num = self._async_request(type(None), t, *args)
857 return self._read_response(num)
859 def _async_request(self, fileobj, t, *args):
860 # this method may be called from other threads (prefetch)
861 self._lock.acquire()
862 try:
863 msg = Message()
864 msg.add_int(self.request_number)
865 for item in args:
866 if isinstance(item, int64):
867 msg.add_int64(item)
868 elif isinstance(item, int):
869 msg.add_int(item)
870 elif isinstance(item, SFTPAttributes):
871 item._pack(msg)
872 else:
873 # For all other types, rely on as_string() to either coerce
874 # to bytes before writing or raise a suitable exception.
875 msg.add_string(item)
876 num = self.request_number
877 self._expecting[num] = fileobj
878 self.request_number += 1
879 finally:
880 self._lock.release()
881 self._send_packet(t, msg)
882 return num
884 def _read_response(self, waitfor=None):
885 while True:
886 try:
887 t, data = self._read_packet()
888 except EOFError as e:
889 raise SSHException("Server connection dropped: {}".format(e))
890 msg = Message(data)
891 num = msg.get_int()
892 self._lock.acquire()
893 try:
894 if num not in self._expecting:
895 # might be response for a file that was closed before
896 # responses came back
897 self._log(DEBUG, "Unexpected response #{}".format(num))
898 if waitfor is None:
899 # just doing a single check
900 break
901 continue
902 fileobj = self._expecting[num]
903 del self._expecting[num]
904 finally:
905 self._lock.release()
906 if num == waitfor:
907 # synchronous
908 if t == CMD_STATUS:
909 self._convert_status(msg)
910 return t, msg
912 # can not rewrite this to deal with E721, either as a None check
913 # nor as not an instance of None or NoneType
914 if fileobj is not type(None): # noqa
915 fileobj._async_response(t, msg, num)
916 if waitfor is None:
917 # just doing a single check
918 break
919 return None, None
921 def _finish_responses(self, fileobj):
922 while fileobj in self._expecting.values():
923 self._read_response()
924 fileobj._check_exception()
926 def _convert_status(self, msg):
927 """
928 Raises EOFError or IOError on error status; otherwise does nothing.
929 """
930 code = msg.get_int()
931 text = msg.get_text()
932 if code == SFTP_OK:
933 return
934 elif code == SFTP_EOF:
935 raise EOFError(text)
936 elif code == SFTP_NO_SUCH_FILE:
937 # clever idea from john a. meinel: map the error codes to errno
938 raise IOError(errno.ENOENT, text)
939 elif code == SFTP_PERMISSION_DENIED:
940 raise IOError(errno.EACCES, text)
941 else:
942 raise IOError(text)
944 def _adjust_cwd(self, path):
945 """
946 Return an adjusted path if we're emulating a "current working
947 directory" for the server.
948 """
949 path = b(path)
950 if self._cwd is None:
951 return path
952 if len(path) and path[0:1] == b_slash:
953 # absolute path
954 return path
955 if self._cwd == b_slash:
956 return self._cwd + path
957 return self._cwd + b_slash + path
960class SFTP(SFTPClient):
961 """
962 An alias for `.SFTPClient` for backwards compatibility.
963 """
965 pass