]>
Commit | Line | Data |
---|---|---|
9e8b1d7a MT |
1 | #!/usr/bin/python |
2 | ||
114ac7ee | 3 | import logging |
9e8b1d7a MT |
4 | import os |
5 | import re | |
114ac7ee | 6 | import tarfile |
4496b160 | 7 | import tempfile |
114ac7ee | 8 | import xattr |
9e8b1d7a | 9 | |
66af936c MT |
10 | import util |
11 | ||
4496b160 MT |
12 | import pakfire.util as util |
13 | import pakfire.compress as compress | |
9e8b1d7a | 14 | from pakfire.errors import FileError |
4496b160 | 15 | from pakfire.constants import * |
9e8b1d7a MT |
16 | |
17 | from base import Package | |
18 | ||
114ac7ee MT |
19 | class InnerTarFile(tarfile.TarFile): |
20 | SUPPORTED_XATTRS = ("security.capability",) | |
21 | ||
22 | def __init__(self, *args, **kwargs): | |
23 | # Force the PAX format. | |
24 | kwargs["format"] = tarfile.PAX_FORMAT | |
25 | ||
26 | tarfile.TarFile.__init__(self, *args, **kwargs) | |
27 | ||
28 | def add(self, name, arcname=None, recursive=None, exclude=None, filter=None): | |
29 | """ | |
30 | Emulate the add function with xattrs support. | |
31 | """ | |
32 | tarinfo = self.gettarinfo(name, arcname) | |
33 | ||
34 | if tarinfo.isreg(): | |
91fe34fb MT |
35 | attrs = [] |
36 | ||
37 | # Use new modules code... | |
38 | if hasattr(xattr, "get_all"): | |
39 | attrs = xattr.get_all(name) | |
40 | ||
41 | # ...or use the deprecated API. | |
42 | else: | |
43 | for attr in xattr.listxattr(name): | |
44 | val = xattr.getxattr(name, attr) | |
45 | attrs.append((attr, val)) | |
114ac7ee MT |
46 | |
47 | for attr, val in attrs: | |
48 | # Skip all attrs that are not supported (e.g. selinux). | |
49 | if not attr in self.SUPPORTED_XATTRS: | |
50 | continue | |
51 | ||
52 | logging.debug("Saving xattr %s=%s from %s" % (attr, val, name)) | |
53 | ||
54 | tarinfo.pax_headers[attr] = val | |
55 | ||
56 | # Append the tar header and data to the archive. | |
57 | f = tarfile.bltn_open(name, "rb") | |
58 | self.addfile(tarinfo, f) | |
59 | f.close() | |
60 | ||
61 | elif tarinfo.isdir(): | |
62 | self.addfile(tarinfo) | |
63 | if recursive: | |
64 | for f in os.listdir(name): | |
65 | self.add(os.path.join(name, f), os.path.join(arcname, f), | |
66 | recursive, exclude, filter) | |
67 | ||
68 | else: | |
69 | self.addfile(tarinfo) | |
70 | ||
71 | def extract(self, member, path=""): | |
72 | # Extract file the normal way... | |
73 | tarfile.TarFile.extract(self, member, path) | |
74 | ||
75 | # ...and then apply the extended attributes. | |
76 | if member.pax_headers: | |
77 | target = os.path.join(path, member.name) | |
78 | ||
79 | for attr, val in member.pax_headers.items(): | |
80 | # Skip all attrs that are not supported (e.g. selinux). | |
81 | if not attr in self.SUPPORTED_XATTRS: | |
82 | continue | |
83 | ||
84 | logging.debug("Restoring xattr %s=%s to %s" % (attr, val, target)) | |
91fe34fb MT |
85 | if hasattr(xattr, "set"): |
86 | xattr.set(target, attr, val) | |
87 | ||
88 | else: | |
89 | xattr.setxattr(target, attr, val) | |
114ac7ee MT |
90 | |
91 | ||
9e8b1d7a MT |
92 | class FilePackage(Package): |
93 | """ | |
94 | This class is a wrapper that reads package data from the (outer) | |
95 | tarball and should never be used solely. | |
96 | """ | |
3723913b MT |
97 | def __init__(self, pakfire, repo, filename): |
98 | Package.__init__(self, pakfire, repo) | |
440cede8 | 99 | self.filename = os.path.abspath(filename) |
9e8b1d7a | 100 | |
36084c79 | 101 | # Place to cache the metadata |
9e8b1d7a MT |
102 | self._metadata = {} |
103 | ||
104 | self.check() | |
105 | ||
106 | def check(self): | |
107 | """ | |
108 | Initially check if the given file is of the correct type and | |
109 | can be opened. | |
110 | """ | |
111 | if not tarfile.is_tarfile(self.filename): | |
112 | raise FileError, "Given file is not of correct format: %s" % self.filename | |
113 | ||
114 | def __repr__(self): | |
115 | return "<%s %s>" % (self.__class__.__name__, self.filename) | |
116 | ||
edd6a268 MT |
117 | @property |
118 | def local(self): | |
119 | # A file package is always local. | |
120 | return True | |
121 | ||
36084c79 MT |
122 | def open_archive(self): |
123 | return tarfile.open(self.filename) | |
9e8b1d7a | 124 | |
4496b160 MT |
125 | def extract(self, message=None, prefix=None): |
126 | logging.debug("Extracting package %s" % self.friendly_name) | |
127 | ||
128 | if prefix is None: | |
129 | prefix = "" | |
130 | ||
131 | # A place to store temporary data. | |
132 | tempf = None | |
133 | ||
134 | # Open package data for read. | |
135 | archive = self.open_archive() | |
136 | ||
137 | # Get the package payload. | |
138 | payload = archive.extractfile("data.img") | |
139 | ||
140 | # Decompress the payload if needed. | |
141 | logging.debug("Compression: %s" % self.payload_compression) | |
142 | ||
143 | # Create a temporary file to store the decompressed output. | |
144 | garbage, tempf = tempfile.mkstemp(prefix="pakfire") | |
145 | ||
146 | i = payload | |
147 | o = open(tempf, "w") | |
148 | ||
149 | # Decompress the package payload. | |
150 | if self.payload_compression: | |
151 | compress.decompressobj(i, o, algo=self.payload_compression) | |
152 | ||
153 | else: | |
154 | buf = i.read(BUFFER_SIZE) | |
155 | while buf: | |
156 | o.write(buf) | |
157 | buf = i.read(BUFFER_SIZE) | |
158 | ||
159 | i.close() | |
160 | o.close() | |
161 | ||
162 | payload = open(tempf) | |
163 | ||
164 | # Open the tarball in the package. | |
165 | payload_archive = InnerTarFile.open(fileobj=payload) | |
166 | ||
167 | members = payload_archive.getmembers() | |
168 | ||
169 | # Load progressbar. | |
170 | pb = None | |
171 | if message: | |
172 | pb = util.make_progress("%-40s" % message, len(members)) | |
173 | ||
174 | i = 0 | |
175 | for member in members: | |
176 | # Update progress. | |
177 | if pb: | |
178 | i += 1 | |
179 | pb.update(i) | |
180 | ||
181 | target = os.path.join(prefix, member.name) | |
182 | ||
183 | # If the member is a directory and if it already exists, we | |
184 | # don't need to create it again. | |
185 | ||
186 | if os.path.exists(target): | |
187 | if member.isdir(): | |
188 | continue | |
189 | ||
190 | else: | |
191 | # Remove file if it has been existant | |
192 | os.unlink(target) | |
193 | ||
194 | #if self.pakfire.config.get("debug"): | |
195 | # msg = "Creating file (%s:%03d:%03d) " % \ | |
196 | # (tarfile.filemode(member.mode), member.uid, member.gid) | |
197 | # if member.issym(): | |
198 | # msg += "/%s -> %s" % (member.name, member.linkname) | |
199 | # elif member.islnk(): | |
200 | # msg += "/%s link to /%s" % (member.name, member.linkname) | |
201 | # else: | |
202 | # msg += "/%s" % member.name | |
203 | # logging.debug(msg) | |
204 | ||
205 | payload_archive.extract(member, path=prefix) | |
206 | ||
207 | # Close all open files. | |
208 | payload_archive.close() | |
209 | payload.close() | |
210 | archive.close() | |
211 | ||
212 | if tempf: | |
213 | os.unlink(tempf) | |
214 | ||
215 | if pb: | |
216 | pb.finish() | |
217 | ||
9e8b1d7a MT |
218 | @property |
219 | def file_version(self): | |
220 | """ | |
221 | Returns the version of the package metadata. | |
222 | """ | |
223 | return self.metadata.get("VERSION") | |
224 | ||
225 | @property | |
226 | def metadata(self): | |
227 | """ | |
228 | Read-in the metadata from the "info" file and cache it in _metadata. | |
229 | """ | |
230 | if not self._metadata: | |
36084c79 MT |
231 | a = self.open_archive() |
232 | f = a.extractfile("info") | |
9e8b1d7a MT |
233 | |
234 | for line in f.readlines(): | |
235 | m = re.match(r"^(\w+)=(.*)$", line) | |
236 | if m is None: | |
237 | continue | |
238 | ||
239 | key, val = m.groups() | |
240 | self._metadata[key] = val.strip("\"") | |
241 | ||
242 | f.close() | |
36084c79 | 243 | a.close() |
9e8b1d7a MT |
244 | |
245 | return self._metadata | |
246 | ||
247 | @property | |
248 | def size(self): | |
249 | """ | |
250 | Return the size of the package file. | |
251 | """ | |
252 | return os.path.getsize(self.filename) | |
253 | ||
254 | def __filelist_from_metadata(self): | |
36084c79 MT |
255 | a = self.open_archive() |
256 | f = a.extractfile("filelist") | |
9e8b1d7a | 257 | |
18ae128e MT |
258 | ret = [] |
259 | for line in f.readlines(): | |
260 | line = line.strip() | |
261 | if not line.startswith("/"): | |
262 | line = "/%s" % line | |
263 | ||
264 | ret.append(line) | |
9e8b1d7a MT |
265 | |
266 | f.close() | |
36084c79 | 267 | a.close() |
9e8b1d7a MT |
268 | |
269 | return ret | |
270 | ||
271 | def __filelist_from_payload(self): | |
272 | # XXX expect uncompressed payload for now | |
273 | # this is very simple and very slow | |
274 | ||
36084c79 MT |
275 | a = self.open_archive() |
276 | f = a.extractfile("data.img") | |
277 | t = tarfile.open(fileobj=f) | |
9e8b1d7a MT |
278 | |
279 | ret = ["/%s" % n for n in t.getnames()] | |
280 | ||
281 | t.close() | |
36084c79 MT |
282 | f.close() |
283 | a.close() | |
9e8b1d7a MT |
284 | |
285 | return ret | |
286 | ||
287 | @property | |
288 | def filelist(self): | |
289 | """ | |
290 | Return a list of the files that are contained in the package | |
291 | payload. | |
292 | ||
293 | At first, we try to get them from the metadata (which is the | |
294 | 'filelist' file). | |
295 | If the file is not existant, we will open the payload and | |
296 | read it instead. The latter is a very slow procedure and | |
297 | should not be used anyway. | |
298 | """ | |
18ae128e MT |
299 | if not hasattr(self, "__filelist"): |
300 | try: | |
301 | self.__filelist = self.__filelist_from_metadata() | |
302 | except KeyError: | |
303 | self.__filelist = self.__filelist_from_payload() | |
304 | ||
305 | return self.__filelist | |
9e8b1d7a MT |
306 | |
307 | @property | |
308 | def payload_compression(self): | |
309 | """ | |
310 | Return the compression type of the payload. | |
311 | """ | |
ce9ffa40 MT |
312 | comp = self.metadata.get("PKG_PAYLOAD_COMP", None) |
313 | ||
314 | # Remove triple X placeholder that was used some time. | |
315 | if comp == "X"*3: | |
d4c94aa5 | 316 | return None |
ce9ffa40 | 317 | |
d4c94aa5 | 318 | return comp or "xz" |
9e8b1d7a MT |
319 | |
320 | @property | |
321 | def signature(self): | |
322 | """ | |
323 | Read the signature from the archive or return None if no | |
324 | signature does exist. | |
325 | """ | |
326 | ret = None | |
327 | try: | |
36084c79 MT |
328 | a = self.open_archive() |
329 | f = a.extractfile("signature") | |
330 | ||
9e8b1d7a | 331 | ret = f.read() |
36084c79 | 332 | |
9e8b1d7a | 333 | f.close() |
36084c79 | 334 | a.close() |
9e8b1d7a MT |
335 | |
336 | except KeyError: | |
337 | # signature file could not be found | |
338 | pass | |
339 | ||
340 | return ret or None | |
341 | ||
66af936c MT |
342 | @property |
343 | def hash1(self): | |
344 | """ | |
345 | Calculate the hash1 of this package. | |
346 | """ | |
347 | return util.calc_hash1(self.filename) | |
348 | ||
a5f5fced MT |
349 | @property |
350 | def scriptlet(self): | |
351 | """ | |
352 | Read the scriptlet from the archive or return an empty string if no | |
353 | scriptlet does exist. | |
354 | """ | |
355 | ret = None | |
356 | try: | |
36084c79 MT |
357 | a = self.open_archive() |
358 | f = a.extractfile("control") | |
359 | ||
a5f5fced | 360 | ret = f.read() |
36084c79 | 361 | |
a5f5fced | 362 | f.close() |
36084c79 | 363 | a.close() |
a5f5fced MT |
364 | |
365 | except KeyError: | |
366 | # scriptlet file could not be found | |
367 | pass | |
368 | ||
369 | return ret or "" |