sftp_attr.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. # Copyright (C) 2003-2006 Robey Pointer <robeypointer@gmail.com>
  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 distributed 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. # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
  18. import stat
  19. import time
  20. from paramiko.common import x80000000, o700, o70, xffffffff
  21. from paramiko.py3compat import long, PY2, strftime
  22. class SFTPAttributes(object):
  23. """
  24. Representation of the attributes of a file (or proxied file) for SFTP in
  25. client or server mode. It attemps to mirror the object returned by
  26. `os.stat` as closely as possible, so it may have the following fields,
  27. with the same meanings as those returned by an `os.stat` object:
  28. - ``st_size``
  29. - ``st_uid``
  30. - ``st_gid``
  31. - ``st_mode``
  32. - ``st_atime``
  33. - ``st_mtime``
  34. Because SFTP allows flags to have other arbitrary named attributes, these
  35. are stored in a dict named ``attr``. Occasionally, the filename is also
  36. stored, in ``filename``.
  37. """
  38. FLAG_SIZE = 1
  39. FLAG_UIDGID = 2
  40. FLAG_PERMISSIONS = 4
  41. FLAG_AMTIME = 8
  42. FLAG_EXTENDED = x80000000
  43. def __init__(self):
  44. """
  45. Create a new (empty) SFTPAttributes object. All fields will be empty.
  46. """
  47. self._flags = 0
  48. self.st_size = None
  49. self.st_uid = None
  50. self.st_gid = None
  51. self.st_mode = None
  52. self.st_atime = None
  53. self.st_mtime = None
  54. self.attr = {}
  55. @classmethod
  56. def from_stat(cls, obj, filename=None):
  57. """
  58. Create an `.SFTPAttributes` object from an existing ``stat`` object (an
  59. object returned by `os.stat`).
  60. :param object obj: an object returned by `os.stat` (or equivalent).
  61. :param str filename: the filename associated with this file.
  62. :return: new `.SFTPAttributes` object with the same attribute fields.
  63. """
  64. attr = cls()
  65. attr.st_size = obj.st_size
  66. attr.st_uid = obj.st_uid
  67. attr.st_gid = obj.st_gid
  68. attr.st_mode = obj.st_mode
  69. attr.st_atime = obj.st_atime
  70. attr.st_mtime = obj.st_mtime
  71. if filename is not None:
  72. attr.filename = filename
  73. return attr
  74. def __repr__(self):
  75. return "<SFTPAttributes: {}>".format(self._debug_str())
  76. # ...internals...
  77. @classmethod
  78. def _from_msg(cls, msg, filename=None, longname=None):
  79. attr = cls()
  80. attr._unpack(msg)
  81. if filename is not None:
  82. attr.filename = filename
  83. if longname is not None:
  84. attr.longname = longname
  85. return attr
  86. def _unpack(self, msg):
  87. self._flags = msg.get_int()
  88. if self._flags & self.FLAG_SIZE:
  89. self.st_size = msg.get_int64()
  90. if self._flags & self.FLAG_UIDGID:
  91. self.st_uid = msg.get_int()
  92. self.st_gid = msg.get_int()
  93. if self._flags & self.FLAG_PERMISSIONS:
  94. self.st_mode = msg.get_int()
  95. if self._flags & self.FLAG_AMTIME:
  96. self.st_atime = msg.get_int()
  97. self.st_mtime = msg.get_int()
  98. if self._flags & self.FLAG_EXTENDED:
  99. count = msg.get_int()
  100. for i in range(count):
  101. self.attr[msg.get_string()] = msg.get_string()
  102. def _pack(self, msg):
  103. self._flags = 0
  104. if self.st_size is not None:
  105. self._flags |= self.FLAG_SIZE
  106. if (self.st_uid is not None) and (self.st_gid is not None):
  107. self._flags |= self.FLAG_UIDGID
  108. if self.st_mode is not None:
  109. self._flags |= self.FLAG_PERMISSIONS
  110. if (self.st_atime is not None) and (self.st_mtime is not None):
  111. self._flags |= self.FLAG_AMTIME
  112. if len(self.attr) > 0:
  113. self._flags |= self.FLAG_EXTENDED
  114. msg.add_int(self._flags)
  115. if self._flags & self.FLAG_SIZE:
  116. msg.add_int64(self.st_size)
  117. if self._flags & self.FLAG_UIDGID:
  118. msg.add_int(self.st_uid)
  119. msg.add_int(self.st_gid)
  120. if self._flags & self.FLAG_PERMISSIONS:
  121. msg.add_int(self.st_mode)
  122. if self._flags & self.FLAG_AMTIME:
  123. # throw away any fractional seconds
  124. msg.add_int(long(self.st_atime))
  125. msg.add_int(long(self.st_mtime))
  126. if self._flags & self.FLAG_EXTENDED:
  127. msg.add_int(len(self.attr))
  128. for key, val in self.attr.items():
  129. msg.add_string(key)
  130. msg.add_string(val)
  131. return
  132. def _debug_str(self):
  133. out = "[ "
  134. if self.st_size is not None:
  135. out += "size={} ".format(self.st_size)
  136. if (self.st_uid is not None) and (self.st_gid is not None):
  137. out += "uid={} gid={} ".format(self.st_uid, self.st_gid)
  138. if self.st_mode is not None:
  139. out += "mode=" + oct(self.st_mode) + " "
  140. if (self.st_atime is not None) and (self.st_mtime is not None):
  141. out += "atime={} mtime={} ".format(self.st_atime, self.st_mtime)
  142. for k, v in self.attr.items():
  143. out += '"{}"={!r} '.format(str(k), v)
  144. out += "]"
  145. return out
  146. @staticmethod
  147. def _rwx(n, suid, sticky=False):
  148. if suid:
  149. suid = 2
  150. out = "-r"[n >> 2] + "-w"[(n >> 1) & 1]
  151. if sticky:
  152. out += "-xTt"[suid + (n & 1)]
  153. else:
  154. out += "-xSs"[suid + (n & 1)]
  155. return out
  156. def _as_text(self):
  157. """create a unix-style long description of the file (like ls -l)"""
  158. if self.st_mode is not None:
  159. kind = stat.S_IFMT(self.st_mode)
  160. if kind == stat.S_IFIFO:
  161. ks = "p"
  162. elif kind == stat.S_IFCHR:
  163. ks = "c"
  164. elif kind == stat.S_IFDIR:
  165. ks = "d"
  166. elif kind == stat.S_IFBLK:
  167. ks = "b"
  168. elif kind == stat.S_IFREG:
  169. ks = "-"
  170. elif kind == stat.S_IFLNK:
  171. ks = "l"
  172. elif kind == stat.S_IFSOCK:
  173. ks = "s"
  174. else:
  175. ks = "?"
  176. ks += self._rwx(
  177. (self.st_mode & o700) >> 6, self.st_mode & stat.S_ISUID
  178. )
  179. ks += self._rwx(
  180. (self.st_mode & o70) >> 3, self.st_mode & stat.S_ISGID
  181. )
  182. ks += self._rwx(
  183. self.st_mode & 7, self.st_mode & stat.S_ISVTX, True
  184. )
  185. else:
  186. ks = "?---------"
  187. # compute display date
  188. if (self.st_mtime is None) or (self.st_mtime == xffffffff):
  189. # shouldn't really happen
  190. datestr = "(unknown date)"
  191. else:
  192. time_tuple = time.localtime(self.st_mtime)
  193. if abs(time.time() - self.st_mtime) > 15552000:
  194. # (15552000 = 6 months)
  195. datestr = strftime("%d %b %Y", time_tuple)
  196. else:
  197. datestr = strftime("%d %b %H:%M", time_tuple)
  198. filename = getattr(self, "filename", "?")
  199. # not all servers support uid/gid
  200. uid = self.st_uid
  201. gid = self.st_gid
  202. size = self.st_size
  203. if uid is None:
  204. uid = 0
  205. if gid is None:
  206. gid = 0
  207. if size is None:
  208. size = 0
  209. # TODO: not sure this actually worked as expected beforehand, leaving
  210. # it untouched for the time being, re: .format() upgrade, until someone
  211. # has time to doublecheck
  212. return "%s 1 %-8d %-8d %8d %-12s %s" % (
  213. ks,
  214. uid,
  215. gid,
  216. size,
  217. datestr,
  218. filename,
  219. )
  220. def asbytes(self):
  221. return self._as_text().encode("utf-8")
  222. if PY2:
  223. __unicode__ = _as_text
  224. __str__ = asbytes
  225. else:
  226. __str__ = _as_text