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# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. 18 19 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.py3compat import b, u, long 32from paramiko.sftp import ( 33 BaseSFTP, 34 CMD_OPENDIR, 35 CMD_HANDLE, 36 SFTPError, 37 CMD_READDIR, 38 CMD_NAME, 39 CMD_CLOSE, 40 SFTP_FLAG_READ, 41 SFTP_FLAG_WRITE, 42 SFTP_FLAG_CREATE, 43 SFTP_FLAG_TRUNC, 44 SFTP_FLAG_APPEND, 45 SFTP_FLAG_EXCL, 46 CMD_OPEN, 47 CMD_REMOVE, 48 CMD_RENAME, 49 CMD_MKDIR, 50 CMD_RMDIR, 51 CMD_STAT, 52 CMD_ATTRS, 53 CMD_LSTAT, 54 CMD_SYMLINK, 55 CMD_SETSTAT, 56 CMD_READLINK, 57 CMD_REALPATH, 58 CMD_STATUS, 59 CMD_EXTENDED, 60 SFTP_OK, 61 SFTP_EOF, 62 SFTP_NO_SUCH_FILE, 63 SFTP_PERMISSION_DENIED, 64) 65 66from paramiko.sftp_attr import SFTPAttributes 67from paramiko.ssh_exception import SSHException 68from paramiko.sftp_file import SFTPFile 69from paramiko.util import ClosingContextManager 70 71 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 85 86 87b_slash = b"/" 88 89 90class SFTPClient(BaseSFTP, ClosingContextManager): 91 """ 92 SFTP client object. 93 94 Used to open an SFTP session across an open SSH `.Transport` and perform 95 remote file operations. 96 97 Instances of this class may be used as context managers. 98 """ 99 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. 104 105 An alternate way to create an SFTP client context is by using 106 `from_transport`. 107 108 :param .Channel sock: an open `.Channel` using the ``"sftp"`` subsystem 109 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 ) 139 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`. 144 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. 149 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.. 156 157 :return: 158 a new `.SFTPClient` object, referring to an sftp session (channel) 159 across the transport 160 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) 171 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(SFTPClient, self)._log( 183 level, 184 "[chan %s] " + msg, 185 *([self.sock.get_name()] + list(args)) 186 ) 187 188 def close(self): 189 """ 190 Close the SFTP session and its underlying channel. 191 192 .. versionadded:: 1.4 193 """ 194 self._log(INFO, "sftp session closed.") 195 self.sock.close() 196 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. 201 202 .. versionadded:: 1.7.1 203 """ 204 return self.sock 205 206 def listdir(self, path="."): 207 """ 208 Return a list containing the names of the entries in the given 209 ``path``. 210 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`. 215 216 :param str path: path to list (defaults to ``'.'``) 217 """ 218 return [f.filename for f in self.listdir_attr(path)] 219 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. 226 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. 231 232 :param str path: path to list (defaults to ``'.'``) 233 :return: list of `.SFTPAttributes` objects 234 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 261 262 def listdir_iter(self, path=".", read_aheads=50): 263 """ 264 Generator version of `.listdir_attr`. 265 266 See the API docs for `.listdir_attr` for overall details. 267 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.) 273 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) 279 280 if t != CMD_HANDLE: 281 raise SFTPError("Expected handle") 282 283 handle = msg.get_string() 284 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) 295 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 318 319 # If we've hit the end of our queued requests, reset nums. 320 nums = list() 321 322 except EOFError: 323 self._request(CMD_CLOSE, handle) 324 return 325 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. 333 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. 340 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. 345 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. 350 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 (-1 = default buffer size) 354 :return: an `.SFTPFile` object representing the open file 355 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) 383 384 # Python continues to vacillate about "open" vs "file"... 385 file = open 386 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`. 391 392 :param str path: path (absolute or relative) of the file to remove 393 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) 399 400 unlink = remove 401 402 def rename(self, oldpath, newpath): 403 """ 404 Rename a file or folder from ``oldpath`` to ``newpath``. 405 406 .. note:: 407 This method implements 'standard' SFTP ``RENAME`` behavior; those 408 seeking the OpenSSH "POSIX rename" extension behavior should use 409 `posix_rename`. 410 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 415 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) 424 425 def posix_rename(self, oldpath, newpath): 426 """ 427 Rename a file or folder from ``oldpath`` to ``newpath``, following 428 posix conventions. 429 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 433 434 :raises: 435 ``IOError`` -- if ``newpath`` is a folder, posix-rename is not 436 supported by the server or something else goes wrong 437 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 ) 446 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. 452 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) 461 462 def rmdir(self, path): 463 """ 464 Remove the folder named ``path``. 465 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) 471 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. 479 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. 482 483 The fields supported are: ``st_mode``, ``st_size``, ``st_uid``, 484 ``st_gid``, ``st_atime``, and ``st_mtime``. 485 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) 497 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`. 503 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) 515 516 def symlink(self, source, dest): 517 """ 518 Create a symbolic link to the ``source`` path at ``destination``. 519 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) 527 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. 533 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) 542 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. 549 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) 559 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. 568 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) 581 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. 587 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) 596 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. 602 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()) 617 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``). 624 625 :param str path: path to be normalized 626 :return: normalized form of the given path (as a `str`) 627 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() 639 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. 648 649 :param str path: new current working directory 650 651 :raises: 652 ``IOError`` -- if the requested path doesn't exist on the server 653 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)) 663 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``. 669 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) 674 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 686 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. 692 693 The SFTP operations use pipelining for speed. 694 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) 707 708 :return: 709 an `.SFTPAttributes` object containing attributes about the given 710 file. 711 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 728 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. 734 735 The SFTP operations use pipelining for speed. 736 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 747 748 :return: an `.SFTPAttributes` object containing attributes about the 749 given file 750 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) 760 761 def getfo(self, remotepath, fl, callback=None): 762 """ 763 Copy a remote file (``remotepath``) from the SFTP server and write to 764 an open file or file-like object, ``fl``. Any exception raised by 765 operations will be passed through. This method is primarily provided 766 as a convenience. 767 768 :param object remotepath: opened file or file-like object to copy to 769 :param str fl: 770 the destination path on the local host or open file object 771 :param callable callback: 772 optional callback function (form: ``func(int, int)``) that accepts 773 the bytes transferred so far and the total bytes to be transferred 774 :return: the `number <int>` of bytes written to the opened file object 775 776 .. versionadded:: 1.10 777 """ 778 file_size = self.stat(remotepath).st_size 779 with self.open(remotepath, "rb") as fr: 780 fr.prefetch(file_size) 781 return self._transfer_with_callback( 782 reader=fr, writer=fl, file_size=file_size, callback=callback 783 ) 784 785 def get(self, remotepath, localpath, callback=None): 786 """ 787 Copy a remote file (``remotepath``) from the SFTP server to the local 788 host as ``localpath``. Any exception raised by operations will be 789 passed through. This method is primarily provided as a convenience. 790 791 :param str remotepath: the remote file to copy 792 :param str localpath: the destination path on the local host 793 :param callable callback: 794 optional callback function (form: ``func(int, int)``) that accepts 795 the bytes transferred so far and the total bytes to be transferred 796 797 .. versionadded:: 1.4 798 .. versionchanged:: 1.7.4 799 Added the ``callback`` param 800 """ 801 with open(localpath, "wb") as fl: 802 size = self.getfo(remotepath, fl, callback) 803 s = os.stat(localpath) 804 if s.st_size != size: 805 raise IOError( 806 "size mismatch in get! {} != {}".format(s.st_size, size) 807 ) 808 809 # ...internals... 810 811 def _request(self, t, *arg): 812 num = self._async_request(type(None), t, *arg) 813 return self._read_response(num) 814 815 def _async_request(self, fileobj, t, *arg): 816 # this method may be called from other threads (prefetch) 817 self._lock.acquire() 818 try: 819 msg = Message() 820 msg.add_int(self.request_number) 821 for item in arg: 822 if isinstance(item, long): 823 msg.add_int64(item) 824 elif isinstance(item, int): 825 msg.add_int(item) 826 elif isinstance(item, SFTPAttributes): 827 item._pack(msg) 828 else: 829 # For all other types, rely on as_string() to either coerce 830 # to bytes before writing or raise a suitable exception. 831 msg.add_string(item) 832 num = self.request_number 833 self._expecting[num] = fileobj 834 self.request_number += 1 835 finally: 836 self._lock.release() 837 self._send_packet(t, msg) 838 return num 839 840 def _read_response(self, waitfor=None): 841 while True: 842 try: 843 t, data = self._read_packet() 844 except EOFError as e: 845 raise SSHException("Server connection dropped: {}".format(e)) 846 msg = Message(data) 847 num = msg.get_int() 848 self._lock.acquire() 849 try: 850 if num not in self._expecting: 851 # might be response for a file that was closed before 852 # responses came back 853 self._log(DEBUG, "Unexpected response #{}".format(num)) 854 if waitfor is None: 855 # just doing a single check 856 break 857 continue 858 fileobj = self._expecting[num] 859 del self._expecting[num] 860 finally: 861 self._lock.release() 862 if num == waitfor: 863 # synchronous 864 if t == CMD_STATUS: 865 self._convert_status(msg) 866 return t, msg 867 868 # can not rewrite this to deal with E721, either as a None check 869 # nor as not an instance of None or NoneType 870 if fileobj is not type(None): # noqa 871 fileobj._async_response(t, msg, num) 872 if waitfor is None: 873 # just doing a single check 874 break 875 return None, None 876 877 def _finish_responses(self, fileobj): 878 while fileobj in self._expecting.values(): 879 self._read_response() 880 fileobj._check_exception() 881 882 def _convert_status(self, msg): 883 """ 884 Raises EOFError or IOError on error status; otherwise does nothing. 885 """ 886 code = msg.get_int() 887 text = msg.get_text() 888 if code == SFTP_OK: 889 return 890 elif code == SFTP_EOF: 891 raise EOFError(text) 892 elif code == SFTP_NO_SUCH_FILE: 893 # clever idea from john a. meinel: map the error codes to errno 894 raise IOError(errno.ENOENT, text) 895 elif code == SFTP_PERMISSION_DENIED: 896 raise IOError(errno.EACCES, text) 897 else: 898 raise IOError(text) 899 900 def _adjust_cwd(self, path): 901 """ 902 Return an adjusted path if we're emulating a "current working 903 directory" for the server. 904 """ 905 path = b(path) 906 if self._cwd is None: 907 return path 908 if len(path) and path[0:1] == b_slash: 909 # absolute path 910 return path 911 if self._cwd == b_slash: 912 return self._cwd + path 913 return self._cwd + b_slash + path 914 915 916class SFTP(SFTPClient): 917 """ 918 An alias for `.SFTPClient` for backwards compatibility. 919 """ 920 921 pass 922