diff options
author | Robey Pointer <robey@lag.net> | 2004-09-07 06:51:03 +0000 |
---|---|---|
committer | Robey Pointer <robey@lag.net> | 2004-09-07 06:51:03 +0000 |
commit | 574c0dd3682a4fa850efc4fde8a55869ded59b5f (patch) | |
tree | 22a23bced2bfa2dc470c3f8aeab20b25922b3a1d | |
parent | 4cbbc57c6b68cdc731e55b8e81b38bec8da4aab3 (diff) |
[project @ Arch-1:robey@lag.net--2003-public%secsh--dev--1.0--patch-75]
clean up SFTPAttributes
add english descriptions to the FX_* error codes of sftp. clean up (and
document) SFTPAttributes since it's exported now, and make it simple to
generate one from a python os.stat object. make "_pythonize" the default --
that is, just use the same field names as python does for os.stat. (i'm not
sure why i didn't do it that way in the first place; probably ignorance.)
also add str() method that converts the SFTPAttributes into a string suitable
for use in ls (used in an obscure way in sftp servers).
-rw-r--r-- | paramiko/sftp.py | 231 |
1 files changed, 164 insertions, 67 deletions
diff --git a/paramiko/sftp.py b/paramiko/sftp.py index 6a44b2db..f7ed3762 100644 --- a/paramiko/sftp.py +++ b/paramiko/sftp.py @@ -18,7 +18,7 @@ # 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 +import struct, socket, stat, time from common import * import util from channel import Channel @@ -36,6 +36,16 @@ FX_OK = 0 FX_EOF, FX_NO_SUCH_FILE, FX_PERMISSION_DENIED, FX_FAILURE, FX_BAD_MESSAGE, \ FX_NO_CONNECTION, FX_CONNECTION_LOST, FX_OP_UNSUPPORTED = range(1, 9) +FX_DESC = [ 'Success', + 'End of file', + 'No such file', + 'Permission denied', + 'Failure', + 'Bad message', + 'No connection', + 'Connection lost', + 'Operation unsupported' ] + FXF_READ = 0x1 FXF_WRITE = 0x2 FXF_APPEND = 0x4 @@ -47,81 +57,160 @@ _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, msg=None): - self.flags = 0 + def __init__(self): + """ + Create a new (empty) SFTPAttributes object. All fields will be empty. + """ + self._flags = 0 self.attr = {} - if msg is not None: - self.unpack(msg) - - def unpack(self, msg): - self.flags = msg.get_int() - if self.flags & self.FLAG_SIZE: - self.size = msg.get_int64() - if self.flags & self.FLAG_UIDGID: - self.uid = msg.get_int() - self.gid = msg.get_int() - if self.flags & self.FLAG_PERMISSIONS: - self.permissions = msg.get_int() - if self.flags & self.FLAG_AMTIME: - self.atime = msg.get_int() - self.mtime = msg.get_int() - if self.flags & self.FLAG_EXTENDED: + + 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, 'size'): - self.flags |= self.FLAG_SIZE - if hasattr(self, 'uid') or hasattr(self, 'gid'): - self.flags |= self.FLAG_UIDGID - if hasattr(self, 'permissions'): - self.flags |= self.FLAG_PERMISSIONS - if hasattr(self, 'atime') or hasattr(self, 'mtime'): - self.flags |= self.FLAG_AMTIME + 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.size) - if self.flags & self.FLAG_UIDGID: - msg.add_int(getattr(self, 'uid', 0)) - msg.add_int(getattr(self, 'gid', 0)) - if self.flags & self.FLAG_PERMISSIONS: - msg.add_int(self.permissions) - if self.flags & self.FLAG_AMTIME: - msg.add_int(getattr(self, 'atime', 0)) - msg.add_int(getattr(self, 'mtime', 0)) - if self.flags & self.FLAG_EXTENDED: + 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: + for key, val in self.attr.iteritems(): msg.add_string(key) msg.add_string(val) return - def _pythonize(self): - "create attributes named the way python's os.stat does it" - if hasattr(self, 'size'): - self.st_size = self.size - if hasattr(self, 'uid'): - self.st_uid = self.uid - if hasattr(self, 'gid'): - self.st_gid = self.gid + 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'): - self.st_mode = self.permissions - if hasattr(self, 'atime'): - self.st_atime = self.atime - if hasattr(self, 'mtime'): - self.st_mtime = self.mtime + 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): @@ -146,10 +235,9 @@ class SFTPFile (BufferedFile): t, msg = self.sftp._request(CMD_FSTAT, self.handle) if t != CMD_ATTRS: raise SFTPError('Expected attrs') - attr = SFTPAttributes() - attr.unpack(msg) + attr = SFTPAttributes._from_msg(msg) try: - return attr.size + return attr.st_size except: return 0 @@ -230,12 +318,17 @@ class SFTPFile (BufferedFile): t, msg = self.sftp._request(CMD_FSTAT, self.handle) if t != CMD_ATTRS: raise SFTPError('Expected attributes') - attr = SFTPAttributes(msg) - attr._pythonize() - return attr + return SFTPAttributes._from_msg(msg) class BaseSFTP (object): + def __init__(self): + self.logger = logging.getLogger('paramiko.sftp') + + + ### internals... + + def _send_version(self): self._send_packet(CMD_INIT, struct.pack('>I', _VERSION)) t, data = self._read_packet() @@ -246,10 +339,14 @@ class BaseSFTP (object): # raise SFTPError('Incompatible sftp protocol') return version - - ### internals... - - + def _send_server_version(self): + self._send_packet(CMD_VERSION, struct.pack('>I', _VERSION)) + t, data = self._read_packet() + if t != CMD_INIT: + raise SFTPError('Incompatible sftp protocol') + version = struct.unpack('>I', data[:4])[0] + return version + def _log(self, level, msg): if type(msg) == type([]): for m in msg: |