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):
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 @return: an object containing attributes about the given file 553 (since 1.7.4) 554 @rtype: SFTPAttributes 555 556 @since: 1.4 557 """ 558 file_size = os.stat(localpath).st_size 559 fl = file(localpath, 'rb') 560 try: 561 fr = self.file(remotepath, 'wb') 562 fr.set_pipelined(True) 563 size = 0 564 try: 565 while True: 566 data = fl.read(32768) 567 if len(data) == 0: 568 break 569 fr.write(data) 570 size += len(data) 571 if callback is not None: 572 callback(size, file_size) 573 finally: 574 fr.close() 575 finally: 576 fl.close() 577 s = self.stat(remotepath) 578 if s.st_size != size: 579 raise IOError('size mismatch in put! %d != %d' % (s.st_size, size)) 580 return s
581
582 - def get(self, remotepath, localpath, callback=None):
583 """ 584 Copy a remote file (C{remotepath}) from the SFTP server to the local 585 host as C{localpath}. Any exception raised by operations will be 586 passed through. This method is primarily provided as a convenience. 587 588 @param remotepath: the remote file to copy 589 @type remotepath: str 590 @param localpath: the destination path on the local host 591 @type localpath: str 592 @param callback: optional callback function that accepts the bytes 593 transferred so far and the total bytes to be transferred 594 (since 1.7.4) 595 @type callback: function(int, int) 596 597 @since: 1.4 598 """ 599 fr = self.file(remotepath, 'rb') 600 file_size = self.stat(remotepath).st_size 601 fr.prefetch() 602 try: 603 fl = file(localpath, 'wb') 604 try: 605 size = 0 606 while True: 607 data = fr.read(32768) 608 if len(data) == 0: 609 break 610 fl.write(data) 611 size += len(data) 612 if callback is not None: 613 callback(size, file_size) 614 finally: 615 fl.close() 616 finally: 617 fr.close() 618 s = os.stat(localpath) 619 if s.st_size != size: 620 raise IOError('size mismatch in get! %d != %d' % (s.st_size, size))
621 622 623 ### internals... 624 625
626 - def _request(self, t, *arg):
627 num = self._async_request(type(None), t, *arg) 628 return self._read_response(num)
629
630 - def _async_request(self, fileobj, t, *arg):
631 # this method may be called from other threads (prefetch) 632 self._lock.acquire() 633 try: 634 msg = Message() 635 msg.add_int(self.request_number) 636 for item in arg: 637 if type(item) is int: 638 msg.add_int(item) 639 elif type(item) is long: 640 msg.add_int64(item) 641 elif type(item) is str: 642 msg.add_string(item) 643 elif type(item) is SFTPAttributes: 644 item._pack(msg) 645 else: 646 raise Exception('unknown type for %r type %r' % (item, type(item))) 647 num = self.request_number 648 self._expecting[num] = fileobj 649 self._send_packet(t, str(msg)) 650 self.request_number += 1 651 finally: 652 self._lock.release() 653 return num
654
655 - def _read_response(self, waitfor=None):
656 while True: 657 try: 658 t, data = self._read_packet() 659 except EOFError, e: 660 raise SSHException('Server connection dropped: %s' % (str(e),)) 661 msg = Message(data) 662 num = msg.get_int() 663 if num not in self._expecting: 664 # might be response for a file that was closed before responses came back 665 self._log(DEBUG, 'Unexpected response #%d' % (num,)) 666 if waitfor is None: 667 # just doing a single check 668 break 669 continue 670 fileobj = self._expecting[num] 671 del self._expecting[num] 672 if num == waitfor: 673 # synchronous 674 if t == CMD_STATUS: 675 self._convert_status(msg) 676 return t, msg 677 if fileobj is not type(None): 678 fileobj._async_response(t, msg) 679 if waitfor is None: 680 # just doing a single check 681 break 682 return (None, None)
683
684 - def _finish_responses(self, fileobj):
685 while fileobj in self._expecting.values(): 686 self._read_response() 687 fileobj._check_exception()
688
689 - def _convert_status(self, msg):
690 """ 691 Raises EOFError or IOError on error status; otherwise does nothing. 692 """ 693 code = msg.get_int() 694 text = msg.get_string() 695 if code == SFTP_OK: 696 return 697 elif code == SFTP_EOF: 698 raise EOFError(text) 699 elif code == SFTP_NO_SUCH_FILE: 700 # clever idea from john a. meinel: map the error codes to errno 701 raise IOError(errno.ENOENT, text) 702 elif code == SFTP_PERMISSION_DENIED: 703 raise IOError(errno.EACCES, text) 704 else: 705 raise IOError(text)
706
707 - def _adjust_cwd(self, path):
708 """ 709 Return an adjusted path if we're emulating a "current working 710 directory" for the server. 711 """ 712 if type(path) is unicode: 713 path = path.encode('utf-8') 714 if self._cwd is None: 715 return path 716 if (len(path) > 0) and (path[0] == '/'): 717 # absolute path 718 return path 719 if self._cwd == '/': 720 return self._cwd + path 721 return self._cwd + '/' + path
722 723
724 -class SFTP (SFTPClient):
725 "an alias for L{SFTPClient} for backwards compatability" 726 pass
727