[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).
This commit is contained in:
parent
4cbbc57c6b
commit
574c0dd368
227
paramiko/sftp.py
227
paramiko/sftp.py
|
@ -18,7 +18,7 @@
|
||||||
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
|
# along with Paramiko; if not, write to the Free Software Foundation, Inc.,
|
||||||
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
# 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
|
||||||
|
|
||||||
import struct, socket
|
import struct, socket, stat, time
|
||||||
from common import *
|
from common import *
|
||||||
import util
|
import util
|
||||||
from channel import Channel
|
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_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_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_READ = 0x1
|
||||||
FXF_WRITE = 0x2
|
FXF_WRITE = 0x2
|
||||||
FXF_APPEND = 0x4
|
FXF_APPEND = 0x4
|
||||||
|
@ -47,81 +57,160 @@ _VERSION = 3
|
||||||
|
|
||||||
|
|
||||||
class SFTPAttributes (object):
|
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_SIZE = 1
|
||||||
FLAG_UIDGID = 2
|
FLAG_UIDGID = 2
|
||||||
FLAG_PERMISSIONS = 4
|
FLAG_PERMISSIONS = 4
|
||||||
FLAG_AMTIME = 8
|
FLAG_AMTIME = 8
|
||||||
FLAG_EXTENDED = 0x80000000L
|
FLAG_EXTENDED = 0x80000000L
|
||||||
|
|
||||||
def __init__(self, msg=None):
|
def __init__(self):
|
||||||
self.flags = 0
|
"""
|
||||||
|
Create a new (empty) SFTPAttributes object. All fields will be empty.
|
||||||
|
"""
|
||||||
|
self._flags = 0
|
||||||
self.attr = {}
|
self.attr = {}
|
||||||
if msg is not None:
|
|
||||||
self.unpack(msg)
|
|
||||||
|
|
||||||
def unpack(self, msg):
|
def from_stat(cls, obj):
|
||||||
self.flags = msg.get_int()
|
"""
|
||||||
if self.flags & self.FLAG_SIZE:
|
Create an SFTPAttributes object from an existing C{stat} object (an
|
||||||
self.size = msg.get_int64()
|
object returned by C{os.stat}).
|
||||||
if self.flags & self.FLAG_UIDGID:
|
|
||||||
self.uid = msg.get_int()
|
@param obj: an object returned by C{os.stat} (or equivalent).
|
||||||
self.gid = msg.get_int()
|
@type obj: object
|
||||||
if self.flags & self.FLAG_PERMISSIONS:
|
@return: new L{SFTPAttributes} object with the same attribute fields.
|
||||||
self.permissions = msg.get_int()
|
@rtype: L{SFTPAttributes}
|
||||||
if self.flags & self.FLAG_AMTIME:
|
"""
|
||||||
self.atime = msg.get_int()
|
attr = cls()
|
||||||
self.mtime = msg.get_int()
|
attr.st_size = obj.st_size
|
||||||
if self.flags & self.FLAG_EXTENDED:
|
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()
|
count = msg.get_int()
|
||||||
for i in range(count):
|
for i in range(count):
|
||||||
self.attr[msg.get_string()] = msg.get_string()
|
self.attr[msg.get_string()] = msg.get_string()
|
||||||
return msg.get_remainder()
|
return msg.get_remainder()
|
||||||
|
|
||||||
def pack(self, msg):
|
def _pack(self, msg):
|
||||||
self.flags = 0
|
self._flags = 0
|
||||||
if hasattr(self, 'size'):
|
if hasattr(self, 'st_size'):
|
||||||
self.flags |= self.FLAG_SIZE
|
self._flags |= self.FLAG_SIZE
|
||||||
if hasattr(self, 'uid') or hasattr(self, 'gid'):
|
if hasattr(self, 'st_uid') or hasattr(self, 'st_gid'):
|
||||||
self.flags |= self.FLAG_UIDGID
|
self._flags |= self.FLAG_UIDGID
|
||||||
if hasattr(self, 'permissions'):
|
if hasattr(self, 'st_mode'):
|
||||||
self.flags |= self.FLAG_PERMISSIONS
|
self._flags |= self.FLAG_PERMISSIONS
|
||||||
if hasattr(self, 'atime') or hasattr(self, 'mtime'):
|
if hasattr(self, 'st_atime') or hasattr(self, 'st_mtime'):
|
||||||
self.flags |= self.FLAG_AMTIME
|
self._flags |= self.FLAG_AMTIME
|
||||||
if len(self.attr) > 0:
|
if len(self.attr) > 0:
|
||||||
self.flags |= self.FLAG_EXTENDED
|
self._flags |= self.FLAG_EXTENDED
|
||||||
msg.add_int(self.flags)
|
msg.add_int(self._flags)
|
||||||
if self.flags & self.FLAG_SIZE:
|
if self._flags & self.FLAG_SIZE:
|
||||||
msg.add_int64(self.size)
|
msg.add_int64(self.st_size)
|
||||||
if self.flags & self.FLAG_UIDGID:
|
if self._flags & self.FLAG_UIDGID:
|
||||||
msg.add_int(getattr(self, 'uid', 0))
|
msg.add_int(getattr(self, 'st_uid', 0))
|
||||||
msg.add_int(getattr(self, 'gid', 0))
|
msg.add_int(getattr(self, 'st_gid', 0))
|
||||||
if self.flags & self.FLAG_PERMISSIONS:
|
if self._flags & self.FLAG_PERMISSIONS:
|
||||||
msg.add_int(self.permissions)
|
msg.add_int(self.st_mode)
|
||||||
if self.flags & self.FLAG_AMTIME:
|
if self._flags & self.FLAG_AMTIME:
|
||||||
msg.add_int(getattr(self, 'atime', 0))
|
msg.add_int(getattr(self, 'st_atime', 0))
|
||||||
msg.add_int(getattr(self, 'mtime', 0))
|
msg.add_int(getattr(self, 'st_mtime', 0))
|
||||||
if self.flags & self.FLAG_EXTENDED:
|
if self._flags & self.FLAG_EXTENDED:
|
||||||
msg.add_int(len(self.attr))
|
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(key)
|
||||||
msg.add_string(val)
|
msg.add_string(val)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _pythonize(self):
|
def _rwx(n, suid, sticky=False):
|
||||||
"create attributes named the way python's os.stat does it"
|
if suid:
|
||||||
if hasattr(self, 'size'):
|
suid = 2
|
||||||
self.st_size = self.size
|
out = '-r'[n >> 2] + '-w'[(n >> 1) & 1]
|
||||||
if hasattr(self, 'uid'):
|
if sticky:
|
||||||
self.st_uid = self.uid
|
out += '-xTt'[suid + (n & 1)]
|
||||||
if hasattr(self, 'gid'):
|
else:
|
||||||
self.st_gid = self.gid
|
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'):
|
if hasattr(self, 'permissions'):
|
||||||
self.st_mode = self.permissions
|
kind = self.permissions & stat.S_IFMT
|
||||||
if hasattr(self, 'atime'):
|
if kind == stat.S_IFIFO:
|
||||||
self.st_atime = self.atime
|
ks = 'p'
|
||||||
if hasattr(self, 'mtime'):
|
elif kind == stat.S_IFCHR:
|
||||||
self.st_mtime = self.mtime
|
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):
|
class SFTPError (Exception):
|
||||||
|
@ -146,10 +235,9 @@ class SFTPFile (BufferedFile):
|
||||||
t, msg = self.sftp._request(CMD_FSTAT, self.handle)
|
t, msg = self.sftp._request(CMD_FSTAT, self.handle)
|
||||||
if t != CMD_ATTRS:
|
if t != CMD_ATTRS:
|
||||||
raise SFTPError('Expected attrs')
|
raise SFTPError('Expected attrs')
|
||||||
attr = SFTPAttributes()
|
attr = SFTPAttributes._from_msg(msg)
|
||||||
attr.unpack(msg)
|
|
||||||
try:
|
try:
|
||||||
return attr.size
|
return attr.st_size
|
||||||
except:
|
except:
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
@ -230,12 +318,17 @@ class SFTPFile (BufferedFile):
|
||||||
t, msg = self.sftp._request(CMD_FSTAT, self.handle)
|
t, msg = self.sftp._request(CMD_FSTAT, self.handle)
|
||||||
if t != CMD_ATTRS:
|
if t != CMD_ATTRS:
|
||||||
raise SFTPError('Expected attributes')
|
raise SFTPError('Expected attributes')
|
||||||
attr = SFTPAttributes(msg)
|
return SFTPAttributes._from_msg(msg)
|
||||||
attr._pythonize()
|
|
||||||
return attr
|
|
||||||
|
|
||||||
|
|
||||||
class BaseSFTP (object):
|
class BaseSFTP (object):
|
||||||
|
def __init__(self):
|
||||||
|
self.logger = logging.getLogger('paramiko.sftp')
|
||||||
|
|
||||||
|
|
||||||
|
### internals...
|
||||||
|
|
||||||
|
|
||||||
def _send_version(self):
|
def _send_version(self):
|
||||||
self._send_packet(CMD_INIT, struct.pack('>I', _VERSION))
|
self._send_packet(CMD_INIT, struct.pack('>I', _VERSION))
|
||||||
t, data = self._read_packet()
|
t, data = self._read_packet()
|
||||||
|
@ -246,10 +339,14 @@ class BaseSFTP (object):
|
||||||
# raise SFTPError('Incompatible sftp protocol')
|
# raise SFTPError('Incompatible sftp protocol')
|
||||||
return version
|
return version
|
||||||
|
|
||||||
|
def _send_server_version(self):
|
||||||
### internals...
|
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):
|
def _log(self, level, msg):
|
||||||
if type(msg) == type([]):
|
if type(msg) == type([]):
|
||||||
for m in msg:
|
for m in msg:
|
||||||
|
|
Loading…
Reference in New Issue