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