]> git.ipfire.org Git - pakfire.git/blob - pakfire/packages/file.py
One huge commit, that breaks pakfire.
[pakfire.git] / pakfire / packages / file.py
1 #!/usr/bin/python
2 ###############################################################################
3 # #
4 # Pakfire - The IPFire package management system #
5 # Copyright (C) 2011 Pakfire development team #
6 # #
7 # This program is free software: you can redistribute it and/or modify #
8 # it under the terms of the GNU General Public License as published by #
9 # the Free Software Foundation, either version 3 of the License, or #
10 # (at your option) any later version. #
11 # #
12 # This program is distributed in the hope that it will be useful, #
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of #
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
15 # GNU General Public License for more details. #
16 # #
17 # You should have received a copy of the GNU General Public License #
18 # along with this program. If not, see <http://www.gnu.org/licenses/>. #
19 # #
20 ###############################################################################
21
22 import logging
23 import os
24 import re
25 import tarfile
26 import tempfile
27 import xattr
28
29 import pakfire.util as util
30 import pakfire.compress as compress
31 from pakfire.constants import *
32
33 from base import Package
34 from lexer import FileLexer
35
36 # XXX need to add zlib and stuff here.
37 PAYLOAD_COMPRESSION_MAGIC = {
38 "xz" : "\xfd7zXZ",
39 }
40
41 class InnerTarFile(tarfile.TarFile):
42 SUPPORTED_XATTRS = ("security.capability",)
43
44 def __init__(self, *args, **kwargs):
45 # Force the PAX format.
46 kwargs["format"] = tarfile.PAX_FORMAT
47
48 tarfile.TarFile.__init__(self, *args, **kwargs)
49
50 def add(self, name, arcname=None, recursive=None, exclude=None, filter=None):
51 """
52 Emulate the add function with xattrs support.
53 """
54 tarinfo = self.gettarinfo(name, arcname)
55
56 if tarinfo.isreg():
57 attrs = []
58
59 # Use new modules code...
60 if hasattr(xattr, "get_all"):
61 attrs = xattr.get_all(name)
62
63 # ...or use the deprecated API.
64 else:
65 for attr in xattr.listxattr(name):
66 val = xattr.getxattr(name, attr)
67 attrs.append((attr, val))
68
69 for attr, val in attrs:
70 # Skip all attrs that are not supported (e.g. selinux).
71 if not attr in self.SUPPORTED_XATTRS:
72 continue
73
74 logging.debug("Saving xattr %s=%s from %s" % (attr, val, name))
75
76 tarinfo.pax_headers[attr] = val
77
78 # Append the tar header and data to the archive.
79 f = tarfile.bltn_open(name, "rb")
80 self.addfile(tarinfo, f)
81 f.close()
82
83 elif tarinfo.isdir():
84 self.addfile(tarinfo)
85 if recursive:
86 for f in os.listdir(name):
87 self.add(os.path.join(name, f), os.path.join(arcname, f),
88 recursive, exclude, filter)
89
90 else:
91 self.addfile(tarinfo)
92
93 def extract(self, member, path=""):
94 # Extract file the normal way...
95 tarfile.TarFile.extract(self, member, path)
96
97 # ...and then apply the extended attributes.
98 if member.pax_headers:
99 target = os.path.join(path, member.name)
100
101 for attr, val in member.pax_headers.items():
102 # Skip all attrs that are not supported (e.g. selinux).
103 if not attr in self.SUPPORTED_XATTRS:
104 continue
105
106 logging.debug("Restoring xattr %s=%s to %s" % (attr, val, target))
107 if hasattr(xattr, "set"):
108 xattr.set(target, attr, val)
109
110 else:
111 xattr.setxattr(target, attr, val)
112
113
114 class FilePackage(Package):
115 """
116 This class is a wrapper that reads package data from the (outer)
117 tarball and should never be used solely.
118 """
119 def __init__(self, pakfire, repo, filename):
120 Package.__init__(self, pakfire, repo)
121 self.filename = os.path.abspath(filename)
122
123 # Place to cache the metadata
124 self._metadata = {}
125
126 # Store the format of this package file.
127 self.format = self.get_format()
128
129 # XXX need to make this much better.
130 self.check()
131
132 # Read the info file.
133 if self.format >= 1:
134 a = self.open_archive()
135 f = a.extractfile("info")
136
137 self.lexer = FileLexer(f.readlines())
138
139 f.close()
140 a.close()
141
142 elif self.format == 0:
143 pass
144
145 else:
146 raise PackageFormatUnsupportedError, _("Filename: %s") % self.filename
147
148 def check(self):
149 """
150 Initially check if the given file is of the correct type and
151 can be opened.
152 """
153 if not tarfile.is_tarfile(self.filename):
154 raise FileError, "Given file is not of correct format: %s" % self.filename
155
156 assert self.format in PACKAGE_FORMATS_SUPPORTED
157
158 def get_format(self):
159 a = self.open_archive()
160 try:
161 f = a.extractfile("pakfire-format")
162 except KeyError:
163 return 0
164
165 format = f.read()
166 try:
167 format = int(format)
168 except TypeError:
169 format = 0
170
171 f.close()
172 a.close()
173
174 return format
175
176 def __repr__(self):
177 return "<%s %s>" % (self.__class__.__name__, self.filename)
178
179 @property
180 def local(self):
181 # A file package is always local.
182 return True
183
184 def open_archive(self):
185 return tarfile.open(self.filename, format=tarfile.PAX_FORMAT)
186
187 def extract(self, message=None, prefix=None):
188 logging.debug("Extracting package %s" % self.friendly_name)
189
190 if prefix is None:
191 prefix = ""
192
193 # A place to store temporary data.
194 tempf = None
195
196 # Open package data for read.
197 archive = self.open_archive()
198
199 # Get the package payload.
200 payload = archive.extractfile("data.img")
201
202 # Decompress the payload if needed.
203 logging.debug("Compression: %s" % self.payload_compression)
204
205 # Create a temporary file to store the decompressed output.
206 garbage, tempf = tempfile.mkstemp(prefix="pakfire")
207
208 i = payload
209 o = open(tempf, "w")
210
211 # Decompress the package payload.
212 if self.payload_compression:
213 compress.decompressobj(i, o, algo=self.payload_compression)
214
215 else:
216 buf = i.read(BUFFER_SIZE)
217 while buf:
218 o.write(buf)
219 buf = i.read(BUFFER_SIZE)
220
221 i.close()
222 o.close()
223
224 payload = open(tempf)
225
226 # Open the tarball in the package.
227 payload_archive = InnerTarFile.open(fileobj=payload)
228
229 members = payload_archive.getmembers()
230
231 # Load progressbar.
232 pb = None
233 if message:
234 message = "%-10s : %s" % (message, self.friendly_name)
235 pb = util.make_progress(message, len(members), eta=False)
236
237 i = 0
238 for member in members:
239 # Update progress.
240 if pb:
241 i += 1
242 pb.update(i)
243
244 target = os.path.join(prefix, member.name)
245
246 # If the member is a directory and if it already exists, we
247 # don't need to create it again.
248
249 if os.path.exists(target):
250 if member.isdir():
251 continue
252
253 else:
254 # Remove file if it has been existant
255 os.unlink(target)
256
257 #if self.pakfire.config.get("debug"):
258 # msg = "Creating file (%s:%03d:%03d) " % \
259 # (tarfile.filemode(member.mode), member.uid, member.gid)
260 # if member.issym():
261 # msg += "/%s -> %s" % (member.name, member.linkname)
262 # elif member.islnk():
263 # msg += "/%s link to /%s" % (member.name, member.linkname)
264 # else:
265 # msg += "/%s" % member.name
266 # logging.debug(msg)
267
268 payload_archive.extract(member, path=prefix)
269
270 # Close all open files.
271 payload_archive.close()
272 payload.close()
273 archive.close()
274
275 if tempf:
276 os.unlink(tempf)
277
278 if pb:
279 pb.finish()
280
281 @property
282 def metadata(self):
283 """
284 Read-in the metadata from the "info" file and cache it in _metadata.
285 """
286 assert self.format == 0, self
287
288 if not self._metadata:
289 a = self.open_archive()
290 f = a.extractfile("info")
291
292 for line in f.readlines():
293 m = re.match(r"^(\w+)=(.*)$", line)
294 if m is None:
295 continue
296
297 key, val = m.groups()
298 self._metadata[key] = val.strip("\"")
299
300 f.close()
301 a.close()
302
303 return self._metadata
304
305 @property
306 def size(self):
307 """
308 Return the size of the package file.
309 """
310 return os.path.getsize(self.filename)
311
312 def __filelist_from_metadata(self):
313 a = self.open_archive()
314 f = a.extractfile("filelist")
315
316 ret = []
317 for line in f.readlines():
318 line = line.strip()
319 if not line.startswith("/"):
320 line = "/%s" % line
321
322 ret.append(line)
323
324 f.close()
325 a.close()
326
327 return ret
328
329 def __filelist_from_payload(self):
330 # XXX expect uncompressed payload for now
331 # this is very simple and very slow
332
333 a = self.open_archive()
334 f = a.extractfile("data.img")
335 t = tarfile.open(fileobj=f)
336
337 ret = ["/%s" % n for n in t.getnames()]
338
339 t.close()
340 f.close()
341 a.close()
342
343 return ret
344
345 @property
346 def filelist(self):
347 """
348 Return a list of the files that are contained in the package
349 payload.
350
351 At first, we try to get them from the metadata (which is the
352 'filelist' file).
353 If the file is not existant, we will open the payload and
354 read it instead. The latter is a very slow procedure and
355 should not be used anyway.
356 """
357 if not hasattr(self, "__filelist"):
358 try:
359 self.__filelist = self.__filelist_from_metadata()
360 except KeyError:
361 self.__filelist = self.__filelist_from_payload()
362
363 return self.__filelist
364
365 @property
366 def configfiles(self):
367 a = self.open_archive()
368
369 f = a.extractfile("configs")
370 for line in f.readlines():
371 if not line.startswith("/"):
372 line = "/%s" % line
373 yield line
374
375 a.close()
376
377 @property
378 def payload_compression(self):
379 """
380 Return the (guessed) compression type of the payload.
381 """
382 # Get the max. length of the magic values.
383 max_length = max([len(v) for v in PAYLOAD_COMPRESSION_MAGIC.values()])
384
385 a = self.open_archive()
386 f = a.extractfile("data.img")
387
388 # Read magic bytes from file.
389 magic = f.read(max_length)
390
391 f.close()
392 a.close()
393
394 for algo, m in PAYLOAD_COMPRESSION_MAGIC.items():
395 if not magic.startswith(m):
396 continue
397
398 return algo
399
400 @property
401 def signature(self):
402 # XXX needs to be replaced
403 """
404 Read the signature from the archive or return None if no
405 signature does exist.
406 """
407 ret = None
408 try:
409 a = self.open_archive()
410 f = a.extractfile("signature")
411
412 ret = f.read()
413
414 f.close()
415 a.close()
416
417 except KeyError:
418 # signature file could not be found
419 pass
420
421 return ret or None
422
423 @property
424 def hash1(self):
425 """
426 Calculate the hash1 of this package.
427 """
428 return util.calc_hash1(self.filename)
429
430 @property
431 def name(self):
432 if self.format >= 1:
433 name = self.lexer.package.get_var("name")
434 elif self.format == 0:
435 name = self.metadata.get("PKG_NAME")
436
437 assert name, self
438 return name
439
440 @property
441 def epoch(self):
442 if self.format >= 1:
443 epoch = self.lexer.package.get_var("epoch", 0)
444 elif self.format == 0:
445 epoch = self.metadata.get("PKG_EPOCH")
446
447 try:
448 epoch = int(epoch)
449 except TypeError:
450 epoch = 0
451
452 return epoch
453
454 @property
455 def version(self):
456 if self.format >= 1:
457 version = self.lexer.package.get_var("version")
458 elif self.format == 0:
459 version = self.metadata.get("PKG_VER")
460
461 assert version, self
462 return version
463
464 @property
465 def release(self):
466 if self.format >= 1:
467 release = self.lexer.package.get_var("release")
468 elif self.format == 0:
469 release = self.metadata.get("PKG_REL")
470
471 assert release, self
472 return release
473
474 @property
475 def arch(self):
476 if self.format >= 1:
477 arch = self.lexer.package.get_var("arch")
478 elif self.format == 0:
479 arch = self.metadata.get("PKG_ARCH")
480
481 assert arch, self
482 return arch
483
484 @property
485 def vendor(self):
486 if self.format >= 1:
487 vendor = self.lexer.distro.get_var("vendor")
488 elif self.format == 0:
489 vendor = self.metadata.get("PKG_VENDOR")
490
491 return vendor
492
493 @property
494 def summary(self):
495 if self.format >= 1:
496 summary = self.lexer.package.get_var("summary")
497 elif self.format == 0:
498 summary = self.metadata.get("PKG_SUMMARY")
499
500 assert summary, self
501 return summary
502
503 @property
504 def description(self):
505 if self.format >= 1:
506 description = self.lexer.package.get_var("description")
507 elif self.format == 0:
508 description = self.metadata.get("PKG_DESC")
509
510 return description
511
512 @property
513 def groups(self):
514 if self.format >= 1:
515 groups = self.lexer.package.get_var("groups")
516 elif self.format == 0:
517 groups = self.metadata.get("PKG_GROUPS")
518
519 if groups:
520 return groups.split()
521
522 return []
523
524 @property
525 def license(self):
526 if self.format >= 1:
527 license = self.lexer.package.get_var("license")
528 elif self.format == 0:
529 license = self.metadata.get("PKG_LICENSE")
530
531 return license
532
533 @property
534 def url(self):
535 if self.format >= 1:
536 url = self.lexer.package.get_var("url")
537 elif self.format == 0:
538 url = self.metadata.get("PKG_URL")
539
540 return url
541
542 @property
543 def maintainer(self):
544 if self.format >= 1:
545 maintainer = self.lexer.package.get_var("maintainer")
546 elif self.format == 0:
547 maintainer = self.metadata.get("PKG_MAINTAINER")
548
549 return maintainer
550
551 @property
552 def uuid(self):
553 if self.format >= 1:
554 uuid = self.lexer.package.get_var("uuid")
555 elif self.format == 0:
556 uuid = self.metadata.get("PKG_UUID")
557
558 #assert uuid, self XXX re-enable this
559 return uuid
560
561 @property
562 def build_id(self):
563 if self.format >= 1:
564 build_id = self.lexer.build.get_var("id")
565 elif self.format == 0:
566 build_id = self.metadata.get("BUILD_ID")
567
568 assert build_id, self
569 return build_id
570
571 @property
572 def build_host(self):
573 if self.format >= 1:
574 build_host = self.lexer.build.get_var("host")
575 elif self.format == 0:
576 build_host = self.metadata.get("BUILD_HOST")
577
578 assert build_host, self
579 return build_host
580
581 @property
582 def build_time(self):
583 if self.format >= 1:
584 build_time = self.lexer.build.get_var("time")
585 elif self.format == 0:
586 build_time = self.metadata.get("BUILD_TIME")
587
588 # XXX re-enable this later
589 #assert build_time, self
590
591 try:
592 build_time = int(build_time)
593 except TypeError:
594 build_time = 0
595
596 return build_time
597
598 @property
599 def provides(self):
600 if self.format >= 1:
601 provides = self.lexer.deps.get_var("provides")
602 elif self.format == 0:
603 provides = self.metadata.get("PKG_PROVIDES")
604
605 if not provides:
606 return []
607
608 return provides.split()
609
610 @property
611 def requires(self):
612 if self.format >= 1:
613 requires = self.lexer.deps.get_var("requires")
614 elif self.format == 0:
615 requires = self.metadata.get("PKG_REQUIRES")
616
617 if not requires:
618 return []
619
620 return requires.split()
621
622 @property
623 def prerequires(self):
624 if self.format >= 1:
625 prerequires = self.lexer.deps.get_var("prerequires")
626 elif self.format == 0:
627 prerequires = self.metadata.get("PKG_PREREQUIRES")
628
629 if not prerequires:
630 return []
631
632 return prerequires.split()
633
634 @property
635 def obsoletes(self):
636 if self.format >= 1:
637 obsoletes = self.lexer.deps.get_var("obsoletes")
638 elif self.format == 0:
639 obsoletes = self.metadata.get("PKG_OBSOLETES")
640
641 if not obsoletes:
642 return []
643
644 return obsoletes.split()
645
646 @property
647 def conflicts(self):
648 if self.format >= 1:
649 conflicts = self.lexer.deps.get_var("conflicts")
650 elif self.format == 0:
651 conflicts = self.metadata.get("PKG_CONFLICTS")
652
653 if not conflicts:
654 return []
655
656 return conflicts.split()