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