Package paramiko :: Module sftp_client
[frames] | no frames]

Source Code for Module paramiko.sftp_client

  1  # Copyright (C) 2003-2007  Robey Pointer <robeypointer@gmail.com> 
  2  # 
  3  # This file is part of paramiko. 
  4  # 
  5  # Paramiko is free software; you can redistribute it and/or modify it under the 
  6  # terms of the GNU Lesser General Public License as published by the Free 
  7  # Software Foundation; either version 2.1 of the License, or (at your option) 
  8  # any later version. 
  9  # 
 10  # Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY 
 11  # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR 
 12  # A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more 
 13  # details. 
 14  # 
 15  # You should have received a copy of the GNU Lesser General Public License 
 16  # along with Paramiko; if not, write to the Free Software Foundation, Inc., 
 17  # 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA. 
 18   
 19  """ 
 20  Client-mode SFTP support. 
 21  """ 
 22   
 23  from binascii import hexlify 
 24  import errno 
 25  import os 
 26  import stat 
 27  import threading 
 28  import time 
 29  import weakref 
 30   
 31  from paramiko.sftp import * 
 32  from paramiko.sftp_attr import SFTPAttributes 
 33  from paramiko.ssh_exception import SSHException 
 34  from paramiko.sftp_file import SFTPFile 
 35   
 36   
37 -def _to_unicode(s):
38 """ 39 decode a string as ascii or utf8 if possible (as required by the sftp 40 protocol). if neither works, just return a byte string because the server 41 probably doesn't know the filename's encoding. 42 """ 43 try: 44 return s.encode('ascii') 45 except UnicodeError: 46 try: 47 return s.decode('utf-8') 48 except UnicodeError: 49 return s
50 51
52 -class SFTPClient (BaseSFTP):
53 """ 54 SFTP client object. C{SFTPClient} is used to open an sftp session across 55 an open ssh L{Transport} and do remote file operations. 56 """ 57
58 - def __init__(self, sock):
59 """ 60 Create an SFTP client from an existing L{Channel}. The channel 61 should already have requested the C{"sftp"} subsystem. 62 63 An alternate way to create an SFTP client context is by using 64 L{from_transport}. 65 66 @param sock: an open L{Channel} using the C{"sftp"} subsystem 67 @type sock: L{Channel} 68 69 @raise SSHException: if there's an exception while negotiating 70 sftp 71 """ 72 BaseSFTP.__init__(self) 73 self.sock = sock 74 self.ultra_debug = False 75 self.request_number = 1 76 # lock for request_number 77 self._lock = threading.Lock() 78 self._cwd = None 79 # request # -> SFTPFile 80 self._expecting = weakref.WeakValueDictionary() 81 if type(sock) is Channel: 82 # override default logger 83 transport = self.sock.get_transport() 84 self.logger = util.get_logger(transport.get_log_channel() + '.sftp') 85 self.ultra_debug = transport.get_hexdump() 86 try: 87 server_version = self._send_version() 88 except EOFError, x: 89 raise SSHException('EOF during negotiation') 90 self._log(INFO, 'Opened sftp connection (server version %d)' % server_version)
91
92 - def from_transport(cls, t):
93 """ 94 Create an SFTP client channel from an open L{Transport}. 95 96 @param t: an open L{Transport} which is already authenticated 97 @type t: L{Transport} 98 @return: a new L{SFTPClient} object, referring to an sftp session 99 (channel) across the transport 100 @rtype: L{SFTPClient} 101 """ 102 chan = t.open_session() 103 if chan is None: 104 return None 105 chan.invoke_subsystem('sftp') 106 return cls(chan)
107 from_transport = classmethod(from_transport) 108
109 - def _log(self, level, msg, *args):
110 if isinstance(msg, list): 111 for m in msg: 112 super(SFTPClient, self)._log(level, "[chan %s] " + m, *([ self.sock.get_name() ] + list(args))) 113 else: 114 super(SFTPClient, self)._log(level, "[chan %s] " + msg, *([ self.sock.get_name() ] + list(args)))
115
116 - def close(self):
117 """ 118 Close the SFTP session and its underlying channel. 119 120 @since: 1.4 121 """ 122 self._log(INFO, 'sftp session closed.') 123 self.sock.close()
124
125 - def get_channel(self):
126 """ 127 Return the underlying L{Channel} object for this SFTP session. This 128 might be useful for doing things like setting a timeout on the channel. 129 130 @return: the SSH channel 131 @rtype: L{Channel} 132 133 @since: 1.7.1 134 """ 135 return self.sock
136
137 - def listdir(self, path='.'):
138 """ 139 Return a list containing the names of the entries in the given C{path}. 140 The list is in arbitrary order. It does not include the special 141 entries C{'.'} and C{'..'} even if they are present in the folder. 142 This method is meant to mirror C{os.listdir} as closely as possible. 143 For a list of full L{SFTPAttributes} objects, see L{listdir_attr}. 144 145 @param path: path to list (defaults to C{'.'}) 146 @type path: str 147 @return: list of filenames 148 @rtype: list of str 149 """ 150 return [f.filename for f in self.listdir_attr(path)]
151
152 - def listdir_attr(self, path='.'):
153 """ 154 Return a list containing L{SFTPAttributes} objects corresponding to 155 files in the given C{path}. The list is in arbitrary order. It does 156 not include the special entries C{'.'} and C{'..'} even if they are 157 present in the folder. 158 159 The returned L{SFTPAttributes} objects will each have an additional 160 field: C{longname}, which may contain a formatted string of the file's 161 attributes, in unix format. The content of this string will probably 162 depend on the SFTP server implementation. 163 164 @param path: path to list (defaults to C{'.'}) 165 @type path: str 166 @return: list of attributes 167 @rtype: list of L{SFTPAttributes} 168 169 @since: 1.2 170 """ 171 path = self._adjust_cwd(path) 172 self._log(DEBUG, 'listdir(%r)' % path) 173 t, msg = self._request(CMD_OPENDIR, path) 174 if t != CMD_HANDLE: 175 raise SFTPError('Expected handle') 176 handle = msg.get_string() 177 filelist = [] 178 while True: 179 try: 180 t, msg = self._request(CMD_READDIR, handle) 181 except EOFError, e: 182 # done with handle 183 break 184 if t != CMD_NAME: 185 raise SFTPError('Expected name response') 186 count = msg.get_int() 187 for i in range(count): 188 filename = _to_unicode(msg.get_string()) 189 longname = _to_unicode(msg.get_string()) 190 attr = SFTPAttributes._from_msg(msg, filename, longname) 191 if (filename != '.') and (filename != '..'): 192 filelist.append(attr) 193 self._request(CMD_CLOSE, handle) 194 return filelist
195
196 - def open(self, filename, mode='r', bufsize=-1):
197 """ 198 Open a file on the remote server. The arguments are the same as for 199 python's built-in C{file} (aka C{open}). A file-like object is 200 returned, which closely mimics the behavior of a normal python file 201 object. 202 203 The mode indicates how the file is to be opened: C{'r'} for reading, 204 C{'w'} for writing (truncating an existing file), C{'a'} for appending, 205 C{'r+'} for reading/writing, C{'w+'} for reading/writing (truncating an 206 existing file), C{'a+'} for reading/appending. The python C{'b'} flag 207 is ignored, since SSH treats all files as binary. The C{'U'} flag is 208 supported in a compatible way. 209 210 Since 1.5.2, an C{'x'} flag indicates that the operation should only 211 succeed if the file was created and did not previously exist. This has 212 no direct mapping to python's file flags, but is commonly known as the 213 C{O_EXCL} flag in posix. 214 215 The file will be buffered in standard python style by default, but 216 can be altered with the C{bufsize} parameter. C{0} turns off 217 buffering, C{1} uses line buffering, and any number greater than 1 218 (C{>1}) uses that specific buffer size. 219 220 @param filename: name of the file to open 221 @type filename: str 222 @param mode: mode (python-style) to open in 223 @type mode: str 224 @param bufsize: desired buffering (-1 = default buffer size) 225 @type bufsize: int 226 @return: a file object representing the open file 227 @rtype: SFTPFile 228 229 @raise IOError: if the file could not be opened. 230 """ 231 filename = self._adjust_cwd(filename) 232 self._log(DEBUG, 'open(%r, %r)' % (filename, mode)) 233 imode = 0 234 if ('r' in mode) or ('+' in mode): 235 imode |= SFTP_FLAG_READ 236 if ('w' in mode) or ('+' in mode) or ('a' in mode): 237 imode |= SFTP_FLAG_WRITE 238 if ('w' in mode): 239 imode |= SFTP_FLAG_CREATE | SFTP_FLAG_TRUNC 240 if ('a' in mode): 241 imode |= SFTP_FLAG_CREATE | SFTP_FLAG_APPEND 242 if ('x' in mode): 243 imode |= SFTP_FLAG_CREATE | SFTP_FLAG_EXCL 244 attrblock = SFTPAttributes() 245 t, msg = self._request(CMD_OPEN, filename, imode, attrblock) 246 if t != CMD_HANDLE: 247 raise SFTPError('Expected handle') 248 handle = msg.get_string() 249 self._log(DEBUG, 'open(%r, %r) -> %s' % (filename, mode, hexlify(handle))) 250 return SFTPFile(self, handle, mode, bufsize)
251 252 # python continues to vacillate about "open" vs "file"... 253 file = open 254
255 - def remove(self, path):
256 """ 257 Remove the file at the given path. This only works on files; for 258 removing folders (directories), use L{rmdir}. 259 260 @param path: path (absolute or relative) of the file to remove 261 @type path: str 262 263 @raise IOError: if the path refers to a folder (directory) 264 """ 265 path = self._adjust_cwd(path) 266 self._log(DEBUG, 'remove(%r)' % path) 267 self._request(CMD_REMOVE, path)
268 269 unlink = remove 270
271 - def rename(self, oldpath, newpath):
272 """ 273 Rename a file or folder from C{oldpath} to C{newpath}. 274 275 @param oldpath: existing name of the file or folder 276 @type oldpath: str 277 @param newpath: new name for the file or folder 278 @type newpath: str 279 280 @raise IOError: if C{newpath} is a folder, or something else goes 281 wrong 282 """ 283 oldpath = self._adjust_cwd(oldpath) 284 newpath = self._adjust_cwd(newpath) 285 self._log(DEBUG, 'rename(%r, %r)' % (oldpath, newpath)) 286 self._request(CMD_RENAME, oldpath, newpath)
287
288 - def mkdir(self, path, mode=0777):
289 """ 290 Create a folder (directory) named C{path} with numeric mode C{mode}. 291 The default mode is 0777 (octal). On some systems, mode is ignored. 292 Where it is used, the current umask value is first masked out. 293 294 @param path: name of the folder to create 295 @type path: str 296 @param mode: permissions (posix-style) for the newly-created folder 297 @type mode: int 298 """ 299 path = self._adjust_cwd(path) 300 self._log(DEBUG, 'mkdir(%r, %r)' % (path, mode)) 301 attr = SFTPAttributes() 302 attr.st_mode = mode 303 self._request(CMD_MKDIR, path, attr)
304
305 - def rmdir(self, path):
306 """ 307 Remove the folder named C{path}. 308 309 @param path: name of the folder to remove 310 @type path: str 311 """ 312 path = self._adjust_cwd(path) 313 self._log(DEBUG, 'rmdir(%r)' % path) 314 self._request(CMD_RMDIR, path)
315
316 - def stat(self, path):
317 """ 318 Retrieve information about a file on the remote system. The return 319 value is an object whose attributes correspond to the attributes of 320 python's C{stat} structure as returned by C{os.stat}, except that it 321 contains fewer fields. An SFTP server may return as much or as little 322 info as it wants, so the results may vary from server to server. 323 324 Unlike a python C{stat} object, the result may not be accessed as a 325 tuple. This is mostly due to the author's slack factor. 326 327 The fields supported are: C{st_mode}, C{st_size}, C{st_uid}, C{st_gid}, 328 C{st_atime}, and C{st_mtime}. 329 330 @param path: the filename to stat 331 @type path: str 332 @return: an object containing attributes about the given file 333 @rtype: SFTPAttributes 334 """ 335 path = self._adjust_cwd(path) 336 self._log(DEBUG, 'stat(%r)' % path) 337 t, msg = self._request(CMD_STAT, path) 338 if t != CMD_ATTRS: 339 raise SFTPError('Expected attributes') 340 return SFTPAttributes._from_msg(msg)
341
342 - def lstat(self, path):
343 """ 344 Retrieve information about a file on the remote system, without 345 following symbolic links (shortcuts). This otherwise behaves exactly 346 the same as L{stat}. 347 348 @param path: the filename to stat 349 @type path: str 350 @return: an object containing attributes about the given file 351 @rtype: SFTPAttributes 352 """ 353 path = self._adjust_cwd(path) 354 self._log(DEBUG, 'lstat(%r)' % path) 355 t, msg = self._request(CMD_LSTAT, path) 356 if t != CMD_ATTRS: 357 raise SFTPError('Expected attributes') 358 return SFTPAttributes._from_msg(msg)
359 375
376 - def chmod(self, path, mode):
377 """ 378 Change the mode (permissions) of a file. The permissions are 379 unix-style and identical to those used by python's C{os.chmod} 380 function. 381 382 @param path: path of the file to change the permissions of 383 @type path: str 384 @param mode: new permissions 385 @type mode: int 386 """ 387 path = self._adjust_cwd(path) 388 self._log(DEBUG, 'chmod(%r, %r)' % (path, mode)) 389 attr = SFTPAttributes() 390 attr.st_mode = mode 391 self._request(CMD_SETSTAT, path, attr)
392
393 - def chown(self, path, uid, gid):
394 """ 395 Change the owner (C{uid}) and group (C{gid}) of a file. As with 396 python's C{os.chown} function, you must pass both arguments, so if you 397 only want to change one, use L{stat} first to retrieve the current 398 owner and group. 399 400 @param path: path of the file to change the owner and group of 401 @type path: str 402 @param uid: new owner's uid 403 @type uid: int 404 @param gid: new group id 405 @type gid: int 406 """ 407 path = self._adjust_cwd(path) 408 self._log(DEBUG, 'chown(%r, %r, %r)' % (path, uid, gid)) 409 attr = SFTPAttributes() 410 attr.st_uid, attr.st_gid = uid, gid 411 self._request(CMD_SETSTAT, path, attr)
412
413 - def utime(self, path, times):
414 """ 415 Set the access and modified times of the file specified by C{path}. If 416 C{times} is C{None}, then the file's access and modified times are set 417 to the current time. Otherwise, C{times} must be a 2-tuple of numbers, 418 of the form C{(atime, mtime)}, which is used to set the access and 419 modified times, respectively. This bizarre API is mimicked from python 420 for the sake of consistency -- I apologize. 421 422 @param path: path of the file to modify 423 @type path: str 424 @param times: C{None} or a tuple of (access time, modified time) in 425 standard internet epoch time (seconds since 01 January 1970 GMT) 426 @type times: tuple(int) 427 """ 428 path = self._adjust_cwd(path) 429 if times is None: 430 times = (time.time(), time.time()) 431 self._log(DEBUG, 'utime(%r, %r)' % (path, times)) 432 attr = SFTPAttributes() 433 attr.st_atime, attr.st_mtime = times 434 self._request(CMD_SETSTAT, path, attr)
435
436 - def truncate(self, path, size):
437 """ 438 Change the size of the file specified by C{path}. This usually extends 439 or shrinks the size of the file, just like the C{truncate()} method on 440 python file objects. 441 442 @param path: path of the file to modify 443 @type path: str 444 @param size: the new size of the file 445 @type size: int or long 446 """ 447 path = self._adjust_cwd(path) 448 self._log(DEBUG, 'truncate(%r, %r)' % (path, size)) 449 attr = SFTPAttributes() 450 attr.st_size = size 451 self._request(CMD_SETSTAT, path, attr)
452 475
476 - def normalize(self, path):
477 """ 478 Return the normalized path (on the server) of a given path. This 479 can be used to quickly resolve symbolic links or determine what the 480 server is considering to be the "current folder" (by passing C{'.'} 481 as C{path}). 482 483 @param path: path to be normalized 484 @type path: str 485 @return: normalized form of the given path 486 @rtype: str 487 488 @raise IOError: if the path can't be resolved on the server 489 """ 490 path = self._adjust_cwd(path) 491 self._log(DEBUG, 'normalize(%r)' % path) 492 t, msg = self._request(CMD_REALPATH, path) 493 if t != CMD_NAME: 494 raise SFTPError('Expected name response') 495 count = msg.get_int() 496 if count != 1: 497 raise SFTPError('Realpath returned %d results' % count) 498 return _to_unicode(msg.get_string())
499
500 - def chdir(self, path):
501 """ 502 Change the "current directory" of this SFTP session. Since SFTP 503 doesn't really have the concept of a current working directory, this 504 is emulated by paramiko. Once you use this method to set a working 505 directory, all operations on this SFTPClient object will be relative 506 to that path. You can pass in C{None} to stop using a current working 507 directory. 508 509 @param path: new current working directory 510 @type path: str 511 512 @raise IOError: if the requested path doesn't exist on the server 513 514 @since: 1.4 515 """ 516 if path is None: 517 self._cwd = None 518 return 519 if not stat.S_ISDIR(self.stat(path).st_mode): 520 raise SFTPError(errno.ENOTDIR, "%s: %s" % (os.strerror(errno.ENOTDIR), path)) 521 self._cwd = self.normalize(path).encode('utf-8')
522
523 - def getcwd(self):
524 """ 525 Return the "current working directory" for this SFTP session, as 526 emulated by paramiko. If no directory has been set with L{chdir}, 527 this method will return C{None}. 528 529 @return: the current working directory on the server, or C{None} 530 @rtype: str 531 532 @since: 1.4 533 """ 534 return self._cwd
535
536 - def put(self, localpath, remotepath, callback=None, confirm=True):
537 """ 538 Copy a local file (C{localpath}) to the SFTP server as C{remotepath}. 539 Any exception raised by operations will be passed through. This 540 method is primarily provided as a convenience. 541 542 The SFTP operations use pipelining for speed. 543 544 @param localpath: the local file to copy 545 @type localpath: str 546 @param remotepath: the destination path on the SFTP server 547 @type remotepath: str 548 @param callback: optional callback function that accepts the bytes 549 transferred so far and the total bytes to be transferred 550 (since 1.7.4) 551 @type callback: function(int, int) 552 @param confirm: whether to do a stat() on the file afterwards to 553 confirm the file size (since 1.7.7) 554 @type confirm: bool 555 556 @return: an object containing attributes about the given file 557 (since 1.7.4) 558 @rtype: SFTPAttributes 559 560 @since: 1.4 561 """ 562 file_size = os.stat(localpath).st_size 563 fl = file(localpath, 'rb') 564 try: 565 fr = self.file(remotepath, 'wb') 566 fr.set_pipelined(True) 567 size = 0 568 try: 569 while True: 570 data = fl.read(32768) 571 if len(data) == 0: 572 break 573 fr.write(data) 574 size += len(data) 575 if callback is not None: 576 callback(size, file_size) 577 finally: 578 fr.close() 579 finally: 580 fl.close() 581 if confirm: 582 s = self.stat(remotepath) 583 if s.st_size != size: 584 raise IOError('size mismatch in put! %d != %d' % (s.st_size, size)) 585 else: 586 s = SFTPAttributes() 587 return s
588
589 - def get(self, remotepath, localpath, callback=None):
590 """ 591 Copy a remote file (C{remotepath}) from the SFTP server to the local 592 host as C{localpath}. Any exception raised by operations will be 593 passed through. This method is primarily provided as a convenience. 594 595 @param remotepath: the remote file to copy 596 @type remotepath: str 597 @param localpath: the destination path on the local host 598 @type localpath: str 599 @param callback: optional callback function that accepts the bytes 600 transferred so far and the total bytes to be transferred 601 (since 1.7.4) 602 @type callback: function(int, int) 603 604 @since: 1.4 605 """ 606 fr = self.file(remotepath, 'rb') 607 file_size = self.stat(remotepath).st_size 608 fr.prefetch() 609 try: 610 fl = file(localpath, 'wb') 611 try: 612 size = 0 613 while True: 614 data = fr.read(32768) 615 if len(data) == 0: 616 break 617 fl.write(data) 618 size += len(data) 619 if callback is not None: 620 callback(size, file_size) 621 finally: 622 fl.close() 623 finally: 624 fr.close() 625 s = os.stat(localpath) 626 if s.st_size != size: 627 raise IOError('size mismatch in get! %d != %d' % (s.st_size, size))
628 629 630 ### internals... 631 632
633 - def _request(self, t, *arg):
634 num = self._async_request(type(None), t, *arg) 635 return self._read_response(num)
636
637 - def _async_request(self, fileobj, t, *arg):
638 # this method may be called from other threads (prefetch) 639 self._lock.acquire() 640 try: 641 msg = Message() 642 msg.add_int(self.request_number) 643 for item in arg: 644 if type(item) is int: 645 msg.add_int(item) 646 elif type(item) is long: 647 msg.add_int64(item) 648 elif type(item) is str: 649 msg.add_string(item) 650 elif type(item) is SFTPAttributes: 651 item._pack(msg) 652 else: 653 raise Exception('unknown type for %r type %r' % (item, type(item))) 654 num = self.request_number 655 self._expecting[num] = fileobj 656 self._send_packet(t, str(msg)) 657 self.request_number += 1 658 finally: 659 self._lock.release() 660 return num
661
662 - def _read_response(self, waitfor=None):
663 while True: 664 try: 665 t, data = self._read_packet() 666 except EOFError, e: 667 raise SSHException('Server connection dropped: %s' % (str(e),)) 668 msg = Message(data) 669 num = msg.get_int() 670 if num not in self._expecting: 671 # might be response for a file that was closed before responses came back 672 self._log(DEBUG, 'Unexpected response #%d' % (num,)) 673 if waitfor is None: 674 # just doing a single check 675 break 676 continue 677 fileobj = self._expecting[num] 678 del self._expecting[num] 679 if num == waitfor: 680 # synchronous 681 if t == CMD_STATUS: 682 self._convert_status(msg) 683 return t, msg 684 if fileobj is not type(None): 685 fileobj._async_response(t, msg) 686 if waitfor is None: 687 # just doing a single check 688 break 689 return (None, None)
690
691 - def _finish_responses(self, fileobj):
692 while fileobj in self._expecting.values(): 693 self._read_response() 694 fileobj._check_exception()
695
696 - def _convert_status(self, msg):
697 """ 698 Raises EOFError or IOError on error status; otherwise does nothing. 699 """ 700 code = msg.get_int() 701 text = msg.get_string() 702 if code == SFTP_OK: 703 return 704 elif code == SFTP_EOF: 705 raise EOFError(text) 706 elif code == SFTP_NO_SUCH_FILE: 707 # clever idea from john a. meinel: map the error codes to errno 708 raise IOError(errno.ENOENT, text) 709 elif code == SFTP_PERMISSION_DENIED: 710 raise IOError(errno.EACCES, text) 711 else: 712 raise IOError(text)
713
714 - def _adjust_cwd(self, path):
715 """ 716 Return an adjusted path if we're emulating a "current working 717 directory" for the server. 718 """ 719 if type(path) is unicode: 720 path = path.encode('utf-8') 721 if self._cwd is None: 722 return path 723 if (len(path) > 0) and (path[0] == '/'): 724 # absolute path 725 return path 726 if self._cwd == '/': 727 return self._cwd + path 728 return self._cwd + '/' + path
729 730
731 -class SFTP (SFTPClient):
732 "an alias for L{SFTPClient} for backwards compatability" 733 pass
734