diff options
-rw-r--r-- | paramiko/sftp.py | 264 | ||||
-rw-r--r-- | paramiko/sftp_attr.py | 182 | ||||
-rw-r--r-- | paramiko/sftp_client.py | 3 | ||||
-rw-r--r-- | paramiko/sftp_file.py | 137 |
4 files changed, 323 insertions, 263 deletions
diff --git a/paramiko/sftp.py b/paramiko/sftp.py index f7ed3762..e9d23b25 100644 --- a/paramiko/sftp.py +++ b/paramiko/sftp.py @@ -18,12 +18,11 @@ # along with Paramiko; if not, write to the Free Software Foundation, Inc., # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. -import struct, socket, stat, time +import struct, socket from common import * import util from channel import Channel from message import Message -from file import BufferedFile CMD_INIT, CMD_VERSION, CMD_OPEN, CMD_CLOSE, CMD_READ, CMD_WRITE, CMD_LSTAT, CMD_FSTAT, \ CMD_SETSTAT, CMD_FSETSTAT, CMD_OPENDIR, CMD_READDIR, CMD_REMOVE, CMD_MKDIR, \ @@ -56,271 +55,10 @@ FXF_EXCL = 0x20 _VERSION = 3 -class SFTPAttributes (object): - """ - Representation of the attributes of a file (or proxied file) for SFTP in - client or server mode. It attemps to mirror the object returned by - C{os.stat} as closely as possible, so it may have the following fields: - - st_size - - st_uid - - st_gid - - st_mode - - st_atime - - st_mtime - - Because SFTP allows flags to have other arbitrary named attributes, these - are stored in a dict named C{attr}. - """ - - FLAG_SIZE = 1 - FLAG_UIDGID = 2 - FLAG_PERMISSIONS = 4 - FLAG_AMTIME = 8 - FLAG_EXTENDED = 0x80000000L - - def __init__(self): - """ - Create a new (empty) SFTPAttributes object. All fields will be empty. - """ - self._flags = 0 - self.attr = {} - - def from_stat(cls, obj): - """ - Create an SFTPAttributes object from an existing C{stat} object (an - object returned by C{os.stat}). - - @param obj: an object returned by C{os.stat} (or equivalent). - @type obj: object - @return: new L{SFTPAttributes} object with the same attribute fields. - @rtype: L{SFTPAttributes} - """ - attr = cls() - attr.st_size = obj.st_size - attr.st_uid = obj.st_uid - attr.st_gid = obj.st_gid - attr.st_mode = obj.st_mode - attr.st_atime = obj.st_atime - attr.st_mtime = obj.st_mtime - return attr - from_stat = classmethod(from_stat) - - - ### internals... - - - def _from_msg(cls, msg): - attr = cls() - attr._unpack(msg) - return attr - _from_msg = classmethod(_from_msg) - - def _unpack(self, msg): - self._flags = msg.get_int() - if self._flags & self.FLAG_SIZE: - self.st_size = msg.get_int64() - if self._flags & self.FLAG_UIDGID: - self.st_uid = msg.get_int() - self.st_gid = msg.get_int() - if self._flags & self.FLAG_PERMISSIONS: - self.st_mode = msg.get_int() - if self._flags & self.FLAG_AMTIME: - self.st_atime = msg.get_int() - self.st_mtime = msg.get_int() - if self._flags & self.FLAG_EXTENDED: - count = msg.get_int() - for i in range(count): - self.attr[msg.get_string()] = msg.get_string() - return msg.get_remainder() - - def _pack(self, msg): - self._flags = 0 - if hasattr(self, 'st_size'): - self._flags |= self.FLAG_SIZE - if hasattr(self, 'st_uid') or hasattr(self, 'st_gid'): - self._flags |= self.FLAG_UIDGID - if hasattr(self, 'st_mode'): - self._flags |= self.FLAG_PERMISSIONS - if hasattr(self, 'st_atime') or hasattr(self, 'st_mtime'): - self._flags |= self.FLAG_AMTIME - if len(self.attr) > 0: - self._flags |= self.FLAG_EXTENDED - msg.add_int(self._flags) - if self._flags & self.FLAG_SIZE: - msg.add_int64(self.st_size) - if self._flags & self.FLAG_UIDGID: - msg.add_int(getattr(self, 'st_uid', 0)) - msg.add_int(getattr(self, 'st_gid', 0)) - if self._flags & self.FLAG_PERMISSIONS: - msg.add_int(self.st_mode) - if self._flags & self.FLAG_AMTIME: - msg.add_int(getattr(self, 'st_atime', 0)) - msg.add_int(getattr(self, 'st_mtime', 0)) - if self._flags & self.FLAG_EXTENDED: - msg.add_int(len(self.attr)) - for key, val in self.attr.iteritems(): - msg.add_string(key) - msg.add_string(val) - return - - def _rwx(n, suid, sticky=False): - if suid: - suid = 2 - out = '-r'[n >> 2] + '-w'[(n >> 1) & 1] - if sticky: - out += '-xTt'[suid + (n & 1)] - else: - out += '-xSs'[suid + (n & 1)] - return out - _rwx = staticmethod(_rwx) - - def __str__(self): - "create a unix-style long description of the file (like ls -l)" - if hasattr(self, 'permissions'): - kind = self.permissions & stat.S_IFMT - if kind == stat.S_IFIFO: - ks = 'p' - elif kind == stat.S_IFCHR: - ks = 'c' - elif kind == stat.S_IFDIR: - ks = 'd' - elif kind == stat.S_IFBLK: - ks = 'b' - elif kind == stat.S_IFREG: - ks = '-' - elif kind == stat.S_IFLNK: - ks = 'l' - elif kind == stat.S_IFSOCK: - ks = 's' - else: - ks = '?' - ks += _rwx((self.permissions & 0700) >> 6, self.permissions & stat.S_ISUID) - ks += _rwx((self.permissions & 070) >> 3, self.permissions & stat.S_ISGID) - ks += _rwx(self.permissions & 7, self.permissions & stat.S_ISVTX, True) - else: - ks = '?---------' - uid = getattr(self, 'uid', -1) - gid = getattr(self, 'gid', -1) - size = getattr(self, 'size', -1) - mtime = getattr(self, 'mtime', 0) - # compute display date - if abs(time.time() - mtime) > 15552000: - # (15552000 = 6 months) - datestr = time.strftime('%d %b %Y', time.localtime(mtime)) - else: - datestr = time.strftime('%d %b %H:%M', time.localtime(mtime)) - return '%s 1 %-8d %-8d %8d %-12s' % (ks, uid, gid, size, datestr) - - - class SFTPError (Exception): pass -class SFTPFile (BufferedFile): - - """ - Some sftp servers will choke if you send read/write requests larger than - this size. - """ - MAX_REQUEST_SIZE = 32768 - - def __init__(self, sftp, handle, mode='r', bufsize=-1): - BufferedFile.__init__(self) - self.sftp = sftp - self.handle = handle - BufferedFile._set_mode(self, mode, bufsize) - - def _get_size(self): - t, msg = self.sftp._request(CMD_FSTAT, self.handle) - if t != CMD_ATTRS: - raise SFTPError('Expected attrs') - attr = SFTPAttributes._from_msg(msg) - try: - return attr.st_size - except: - return 0 - - def close(self): - BufferedFile.close(self) - self.sftp._request(CMD_CLOSE, self.handle) - - def _read(self, size): - size = min(size, self.MAX_REQUEST_SIZE) - t, msg = self.sftp._request(CMD_READ, self.handle, long(self._realpos), int(size)) - if t != CMD_DATA: - raise SFTPError('Expected data') - return msg.get_string() - - def _write(self, data): - offset = 0 - while offset < len(data): - chunk = min(len(data) - offset, self.MAX_REQUEST_SIZE) - t, msg = self.sftp._request(CMD_WRITE, self.handle, long(self._realpos + offset), - str(data[offset : offset + chunk])) - offset += chunk - return len(data) - - def settimeout(self, timeout): - """ - Set a timeout on read/write operations on the underlying socket or - ssh L{Channel}. - - @see: L{Channel.settimeout} - @param timeout: seconds to wait for a pending read/write operation - before raising C{socket.timeout}, or C{None} for no timeout - @type timeout: float - """ - self.sock.settimeout(timeout) - - def gettimeout(self): - """ - Returns the timeout in seconds (as a float) associated with the socket - or ssh L{Channel} used for this file. - - @see: L{Channel.gettimeout} - @rtype: float - """ - return self.sock.gettimeout() - - def setblocking(self, blocking): - """ - Set blocking or non-blocking mode on the underiying socket or ssh - L{Channel}. - - @see: L{Channel.setblocking} - @param blocking: 0 to set non-blocking mode; non-0 to set blocking - mode. - @type blocking: int - """ - self.sock.setblocking(blocking) - - def seek(self, offset, whence=0): - self.flush() - if whence == self.SEEK_SET: - self._realpos = self._pos = offset - elif whence == self.SEEK_CUR: - self._realpos += offset - self._pos += offset - else: - self._realpos = self._pos = self._get_size() + offset - self._rbuffer = '' - - def stat(self): - """ - Retrieve information about this file from the remote system. This is - exactly like L{SFTP.stat}, except that it operates on an already-open - file. - - @return: an object containing attributes about this file. - @rtype: SFTPAttributes - """ - t, msg = self.sftp._request(CMD_FSTAT, self.handle) - if t != CMD_ATTRS: - raise SFTPError('Expected attributes') - return SFTPAttributes._from_msg(msg) - - class BaseSFTP (object): def __init__(self): self.logger = logging.getLogger('paramiko.sftp') diff --git a/paramiko/sftp_attr.py b/paramiko/sftp_attr.py new file mode 100644 index 00000000..a4e97943 --- /dev/null +++ b/paramiko/sftp_attr.py @@ -0,0 +1,182 @@ +#!/usr/bin/python + +# Copyright (C) 2003-2004 Robey Pointer <robey@lag.net> +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +import stat, time +from common import * +from sftp import * + + +class SFTPAttributes (object): + """ + Representation of the attributes of a file (or proxied file) for SFTP in + client or server mode. It attemps to mirror the object returned by + C{os.stat} as closely as possible, so it may have the following fields, + with the same meanings as those returned by an C{os.stat} object: + - st_size + - st_uid + - st_gid + - st_mode + - st_atime + - st_mtime + + Because SFTP allows flags to have other arbitrary named attributes, these + are stored in a dict named C{attr}. Occasionally, the filename is also + stored, in C{filename}. + """ + + FLAG_SIZE = 1 + FLAG_UIDGID = 2 + FLAG_PERMISSIONS = 4 + FLAG_AMTIME = 8 + FLAG_EXTENDED = 0x80000000L + + def __init__(self): + """ + Create a new (empty) SFTPAttributes object. All fields will be empty. + """ + self._flags = 0 + self.attr = {} + + def from_stat(cls, obj): + """ + Create an SFTPAttributes object from an existing C{stat} object (an + object returned by C{os.stat}). + + @param obj: an object returned by C{os.stat} (or equivalent). + @type obj: object + @return: new L{SFTPAttributes} object with the same attribute fields. + @rtype: L{SFTPAttributes} + """ + attr = cls() + attr.st_size = obj.st_size + attr.st_uid = obj.st_uid + attr.st_gid = obj.st_gid + attr.st_mode = obj.st_mode + attr.st_atime = obj.st_atime + attr.st_mtime = obj.st_mtime + return attr + from_stat = classmethod(from_stat) + + + ### internals... + + + def _from_msg(cls, msg): + attr = cls() + attr._unpack(msg) + return attr + _from_msg = classmethod(_from_msg) + + def _unpack(self, msg): + self._flags = msg.get_int() + if self._flags & self.FLAG_SIZE: + self.st_size = msg.get_int64() + if self._flags & self.FLAG_UIDGID: + self.st_uid = msg.get_int() + self.st_gid = msg.get_int() + if self._flags & self.FLAG_PERMISSIONS: + self.st_mode = msg.get_int() + if self._flags & self.FLAG_AMTIME: + self.st_atime = msg.get_int() + self.st_mtime = msg.get_int() + if self._flags & self.FLAG_EXTENDED: + count = msg.get_int() + for i in range(count): + self.attr[msg.get_string()] = msg.get_string() + return msg.get_remainder() + + def _pack(self, msg): + self._flags = 0 + if hasattr(self, 'st_size'): + self._flags |= self.FLAG_SIZE + if hasattr(self, 'st_uid') or hasattr(self, 'st_gid'): + self._flags |= self.FLAG_UIDGID + if hasattr(self, 'st_mode'): + self._flags |= self.FLAG_PERMISSIONS + if hasattr(self, 'st_atime') or hasattr(self, 'st_mtime'): + self._flags |= self.FLAG_AMTIME + if len(self.attr) > 0: + self._flags |= self.FLAG_EXTENDED + msg.add_int(self._flags) + if self._flags & self.FLAG_SIZE: + msg.add_int64(self.st_size) + if self._flags & self.FLAG_UIDGID: + msg.add_int(getattr(self, 'st_uid', 0)) + msg.add_int(getattr(self, 'st_gid', 0)) + if self._flags & self.FLAG_PERMISSIONS: + msg.add_int(self.st_mode) + if self._flags & self.FLAG_AMTIME: + msg.add_int(getattr(self, 'st_atime', 0)) + msg.add_int(getattr(self, 'st_mtime', 0)) + if self._flags & self.FLAG_EXTENDED: + msg.add_int(len(self.attr)) + for key, val in self.attr.iteritems(): + msg.add_string(key) + msg.add_string(val) + return + + def _rwx(n, suid, sticky=False): + if suid: + suid = 2 + out = '-r'[n >> 2] + '-w'[(n >> 1) & 1] + if sticky: + out += '-xTt'[suid + (n & 1)] + else: + out += '-xSs'[suid + (n & 1)] + return out + _rwx = staticmethod(_rwx) + + def __str__(self): + "create a unix-style long description of the file (like ls -l)" + if hasattr(self, 'permissions'): + kind = self.permissions & stat.S_IFMT + if kind == stat.S_IFIFO: + ks = 'p' + elif kind == stat.S_IFCHR: + ks = 'c' + elif kind == stat.S_IFDIR: + ks = 'd' + elif kind == stat.S_IFBLK: + ks = 'b' + elif kind == stat.S_IFREG: + ks = '-' + elif kind == stat.S_IFLNK: + ks = 'l' + elif kind == stat.S_IFSOCK: + ks = 's' + else: + ks = '?' + ks += _rwx((self.permissions & 0700) >> 6, self.permissions & stat.S_ISUID) + ks += _rwx((self.permissions & 070) >> 3, self.permissions & stat.S_ISGID) + ks += _rwx(self.permissions & 7, self.permissions & stat.S_ISVTX, True) + else: + ks = '?---------' + uid = getattr(self, 'uid', -1) + gid = getattr(self, 'gid', -1) + size = getattr(self, 'size', -1) + mtime = getattr(self, 'mtime', 0) + # compute display date + if abs(time.time() - mtime) > 15552000: + # (15552000 = 6 months) + datestr = time.strftime('%d %b %Y', time.localtime(mtime)) + else: + datestr = time.strftime('%d %b %H:%M', time.localtime(mtime)) + filename = getattr(self, 'filename', '?') + return '%s 1 %-8d %-8d %8d %-12s %s' % (ks, uid, gid, size, datestr, filename) diff --git a/paramiko/sftp_client.py b/paramiko/sftp_client.py index f9b8e693..d0b96d7d 100644 --- a/paramiko/sftp_client.py +++ b/paramiko/sftp_client.py @@ -23,6 +23,9 @@ Client-mode SFTP support. """ from sftp import * +from sftp_attr import SFTPAttributes +from sftp_file import SFTPFile + class SFTPClient (BaseSFTP): """ diff --git a/paramiko/sftp_file.py b/paramiko/sftp_file.py new file mode 100644 index 00000000..f01d384d --- /dev/null +++ b/paramiko/sftp_file.py @@ -0,0 +1,137 @@ +#!/usr/bin/python + +# Copyright (C) 2003-2004 Robey Pointer <robey@lag.net> +# +# This file is part of paramiko. +# +# Paramiko is free software; you can redistribute it and/or modify it under the +# terms of the GNU Lesser General Public License as published by the Free +# Software Foundation; either version 2.1 of the License, or (at your option) +# any later version. +# +# Paramiko is distrubuted in the hope that it will be useful, but WITHOUT ANY +# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR +# A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more +# details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with Paramiko; if not, write to the Free Software Foundation, Inc., +# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA. + +""" +L{SFTPFile} +""" + +from common import * +from sftp import * +from file import BufferedFile +from sftp_attr import SFTPAttributes + + +class SFTPFile (BufferedFile): + """ + Proxy object for a file on the remote server, in client mode SFTP. + """ + + # Some sftp servers will choke if you send read/write requests larger than + # this size. + MAX_REQUEST_SIZE = 32768 + + def __init__(self, sftp, handle, mode='r', bufsize=-1): + BufferedFile.__init__(self) + self.sftp = sftp + self.handle = handle + BufferedFile._set_mode(self, mode, bufsize) + + def close(self): + BufferedFile.close(self) + self.sftp._request(CMD_CLOSE, self.handle) + + def _read(self, size): + size = min(size, self.MAX_REQUEST_SIZE) + t, msg = self.sftp._request(CMD_READ, self.handle, long(self._realpos), int(size)) + if t != CMD_DATA: + raise SFTPError('Expected data') + return msg.get_string() + + def _write(self, data): + offset = 0 + while offset < len(data): + chunk = min(len(data) - offset, self.MAX_REQUEST_SIZE) + t, msg = self.sftp._request(CMD_WRITE, self.handle, long(self._realpos + offset), + str(data[offset : offset + chunk])) + offset += chunk + return len(data) + + def settimeout(self, timeout): + """ + Set a timeout on read/write operations on the underlying socket or + ssh L{Channel}. + + @see: L{Channel.settimeout} + @param timeout: seconds to wait for a pending read/write operation + before raising C{socket.timeout}, or C{None} for no timeout + @type timeout: float + """ + self.sock.settimeout(timeout) + + def gettimeout(self): + """ + Returns the timeout in seconds (as a float) associated with the socket + or ssh L{Channel} used for this file. + + @see: L{Channel.gettimeout} + @rtype: float + """ + return self.sock.gettimeout() + + def setblocking(self, blocking): + """ + Set blocking or non-blocking mode on the underiying socket or ssh + L{Channel}. + + @see: L{Channel.setblocking} + @param blocking: 0 to set non-blocking mode; non-0 to set blocking + mode. + @type blocking: int + """ + self.sock.setblocking(blocking) + + def seek(self, offset, whence=0): + self.flush() + if whence == self.SEEK_SET: + self._realpos = self._pos = offset + elif whence == self.SEEK_CUR: + self._realpos += offset + self._pos += offset + else: + self._realpos = self._pos = self._get_size() + offset + self._rbuffer = '' + + def stat(self): + """ + Retrieve information about this file from the remote system. This is + exactly like L{SFTP.stat}, except that it operates on an already-open + file. + + @return: an object containing attributes about this file. + @rtype: SFTPAttributes + """ + t, msg = self.sftp._request(CMD_FSTAT, self.handle) + if t != CMD_ATTRS: + raise SFTPError('Expected attributes') + return SFTPAttributes._from_msg(msg) + + + ### internals... + + + def _get_size(self): + t, msg = self.sftp._request(CMD_FSTAT, self.handle) + if t != CMD_ATTRS: + raise SFTPError('Expected attrs') + attr = SFTPAttributes._from_msg(msg) + try: + return attr.st_size + except: + return 0 |