]> git.ipfire.org Git - pakfire.git/blame - pakfire/packages/file.py
Fix download information.
[pakfire.git] / pakfire / packages / file.py
CommitLineData
9e8b1d7a
MT
1#!/usr/bin/python
2
114ac7ee 3import logging
9e8b1d7a
MT
4import os
5import re
114ac7ee 6import tarfile
4496b160 7import tempfile
114ac7ee 8import xattr
9e8b1d7a 9
66af936c
MT
10import util
11
4496b160
MT
12import pakfire.util as util
13import pakfire.compress as compress
9e8b1d7a 14from pakfire.errors import FileError
4496b160 15from pakfire.constants import *
9e8b1d7a
MT
16
17from base import Package
18
114ac7ee
MT
19class 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
92class 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 ""