]>
Commit | Line | Data |
---|---|---|
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 | 22 | import logging |
9e8b1d7a MT |
23 | import os |
24 | import re | |
114ac7ee | 25 | import tarfile |
4496b160 | 26 | import tempfile |
114ac7ee | 27 | import xattr |
9e8b1d7a | 28 | |
4496b160 MT |
29 | import pakfire.util as util |
30 | import pakfire.compress as compress | |
4496b160 | 31 | from pakfire.constants import * |
75bb74a7 | 32 | from pakfire.i18n import _ |
9e8b1d7a MT |
33 | |
34 | from base import Package | |
c07a3ca7 MT |
35 | from lexer import FileLexer |
36 | ||
37 | # XXX need to add zlib and stuff here. | |
38 | PAYLOAD_COMPRESSION_MAGIC = { | |
39 | "xz" : "\xfd7zXZ", | |
40 | } | |
9e8b1d7a | 41 | |
114ac7ee MT |
42 | class 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 |
124 | class 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 | ||
656 | class SourcePackage(FilePackage): | |
657 | pass | |
658 | ||
659 | ||
660 | class 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 |