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