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

Source Code for Module paramiko.sftp_client

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