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

356 statements  

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. 

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.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) 

65 

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 

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()._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 (default: ``-1``) 

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( 

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. 

774 

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 

788 

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 ) 

802 

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. 

815 

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. 

830 

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 ) 

852 

853 # ...internals... 

854 

855 def _request(self, t, *args): 

856 num = self._async_request(type(None), t, *args) 

857 return self._read_response(num) 

858 

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 

883 

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 

911 

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 

920 

921 def _finish_responses(self, fileobj): 

922 while fileobj in self._expecting.values(): 

923 self._read_response() 

924 fileobj._check_exception() 

925 

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) 

943 

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 

958 

959 

960class SFTP(SFTPClient): 

961 """ 

962 An alias for `.SFTPClient` for backwards compatibility. 

963 """ 

964 

965 pass