123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246 |
- # Copyright (C) 2003-2006 Robey Pointer <robeypointer@gmail.com>
- #
- # 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 distributed 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.,
- # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
- import stat
- import time
- from paramiko.common import x80000000, o700, o70, xffffffff
- from paramiko.py3compat import long, PY2, strftime
- 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
- `os.stat` as closely as possible, so it may have the following fields,
- with the same meanings as those returned by an `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 ``attr``. Occasionally, the filename is also
- stored, in ``filename``.
- """
- FLAG_SIZE = 1
- FLAG_UIDGID = 2
- FLAG_PERMISSIONS = 4
- FLAG_AMTIME = 8
- FLAG_EXTENDED = x80000000
- def __init__(self):
- """
- Create a new (empty) SFTPAttributes object. All fields will be empty.
- """
- self._flags = 0
- self.st_size = None
- self.st_uid = None
- self.st_gid = None
- self.st_mode = None
- self.st_atime = None
- self.st_mtime = None
- self.attr = {}
- @classmethod
- def from_stat(cls, obj, filename=None):
- """
- Create an `.SFTPAttributes` object from an existing ``stat`` object (an
- object returned by `os.stat`).
- :param object obj: an object returned by `os.stat` (or equivalent).
- :param str filename: the filename associated with this file.
- :return: new `.SFTPAttributes` object with the same attribute fields.
- """
- 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
- if filename is not None:
- attr.filename = filename
- return attr
- def __repr__(self):
- return "<SFTPAttributes: {}>".format(self._debug_str())
- # ...internals...
- @classmethod
- def _from_msg(cls, msg, filename=None, longname=None):
- attr = cls()
- attr._unpack(msg)
- if filename is not None:
- attr.filename = filename
- if longname is not None:
- attr.longname = longname
- return attr
- 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()
- def _pack(self, msg):
- self._flags = 0
- if self.st_size is not None:
- self._flags |= self.FLAG_SIZE
- if (self.st_uid is not None) and (self.st_gid is not None):
- self._flags |= self.FLAG_UIDGID
- if self.st_mode is not None:
- self._flags |= self.FLAG_PERMISSIONS
- if (self.st_atime is not None) and (self.st_mtime is not None):
- 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(self.st_uid)
- msg.add_int(self.st_gid)
- if self._flags & self.FLAG_PERMISSIONS:
- msg.add_int(self.st_mode)
- if self._flags & self.FLAG_AMTIME:
- # throw away any fractional seconds
- msg.add_int(long(self.st_atime))
- msg.add_int(long(self.st_mtime))
- if self._flags & self.FLAG_EXTENDED:
- msg.add_int(len(self.attr))
- for key, val in self.attr.items():
- msg.add_string(key)
- msg.add_string(val)
- return
- def _debug_str(self):
- out = "[ "
- if self.st_size is not None:
- out += "size={} ".format(self.st_size)
- if (self.st_uid is not None) and (self.st_gid is not None):
- out += "uid={} gid={} ".format(self.st_uid, self.st_gid)
- if self.st_mode is not None:
- out += "mode=" + oct(self.st_mode) + " "
- if (self.st_atime is not None) and (self.st_mtime is not None):
- out += "atime={} mtime={} ".format(self.st_atime, self.st_mtime)
- for k, v in self.attr.items():
- out += '"{}"={!r} '.format(str(k), v)
- out += "]"
- return out
- @staticmethod
- 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
- def _as_text(self):
- """create a unix-style long description of the file (like ls -l)"""
- if self.st_mode is not None:
- kind = stat.S_IFMT(self.st_mode)
- 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 += self._rwx(
- (self.st_mode & o700) >> 6, self.st_mode & stat.S_ISUID
- )
- ks += self._rwx(
- (self.st_mode & o70) >> 3, self.st_mode & stat.S_ISGID
- )
- ks += self._rwx(
- self.st_mode & 7, self.st_mode & stat.S_ISVTX, True
- )
- else:
- ks = "?---------"
- # compute display date
- if (self.st_mtime is None) or (self.st_mtime == xffffffff):
- # shouldn't really happen
- datestr = "(unknown date)"
- else:
- time_tuple = time.localtime(self.st_mtime)
- if abs(time.time() - self.st_mtime) > 15552000:
- # (15552000 = 6 months)
- datestr = strftime("%d %b %Y", time_tuple)
- else:
- datestr = strftime("%d %b %H:%M", time_tuple)
- filename = getattr(self, "filename", "?")
- # not all servers support uid/gid
- uid = self.st_uid
- gid = self.st_gid
- size = self.st_size
- if uid is None:
- uid = 0
- if gid is None:
- gid = 0
- if size is None:
- size = 0
- # TODO: not sure this actually worked as expected beforehand, leaving
- # it untouched for the time being, re: .format() upgrade, until someone
- # has time to doublecheck
- return "%s 1 %-8d %-8d %8d %-12s %s" % (
- ks,
- uid,
- gid,
- size,
- datestr,
- filename,
- )
- def asbytes(self):
- return self._as_text().encode("utf-8")
- if PY2:
- __unicode__ = _as_text
- __str__ = asbytes
- else:
- __str__ = _as_text
|