commit:     6b3d262e6316073a2a3be81086c05891d970ae2a
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Tue Jan  6 02:36:11 2015 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Fri Jan 16 17:07:11 2015 +0000
URL:        
http://sources.gentoo.org/gitweb/?p=proj/portage.git;a=commit;h=6b3d262e

BinpkgExtractorAsync: xz and gzip decompression (142579)

This adds support for using a binary package's compression header to
determine the compression type, providing forward-compatibility for
xz and gzip decompression. The file name extension is disregared, so
that it will be possible to use a compression-independent file naming
scheme in the future (see bug #150031 for discussion about proposed
file naming schemes).

Currently, only decompression is supported. It's useful to provide
forward-compatibility now, so that binhost clients will be prepared
to handle future binhost servers that use xz or gzip compression.

X-Gentoo-Bug: 142579
X-Gentoo-Bug-URL: https://bugs.gentoo.org/show_bug.cgi?id=142579
Acked-by: Brian Dolbec <dolsen <AT> gentoo.org>

---
 pym/_emerge/BinpkgExtractorAsync.py   | 25 ++++++++++---
 pym/portage/util/compression_probe.py | 68 +++++++++++++++++++++++++++++++++++
 2 files changed, 89 insertions(+), 4 deletions(-)

diff --git a/pym/_emerge/BinpkgExtractorAsync.py 
b/pym/_emerge/BinpkgExtractorAsync.py
index be74c2f..6aaa448 100644
--- a/pym/_emerge/BinpkgExtractorAsync.py
+++ b/pym/_emerge/BinpkgExtractorAsync.py
@@ -1,8 +1,13 @@
 # Copyright 1999-2013 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
 
+import logging
+
 from _emerge.SpawnProcess import SpawnProcess
 import portage
+from portage.localization import _
+from portage.util.compression_probe import (compression_probe,
+       _decompressors)
 import signal
 import subprocess
 
@@ -20,19 +25,31 @@ class BinpkgExtractorAsync(SpawnProcess):
                        if b"--xattrs" in output:
                                tar_options = "--xattrs"
 
-               # Add -q to bzip2 opts, in order to avoid "trailing garbage 
after
-               # EOF ignored" warning messages due to xpak trailer.
+               decomp_cmd = _decompressors.get(
+                       compression_probe(self.pkg_path))
+               if decomp_cmd is None:
+                       self.scheduler.output("!!! %s\n" %
+                               _("File compression header unrecognized: %s") %
+                               self.pkg_path, log_path=self.logfile,
+                               background=self.background, level=logging.ERROR)
+                       self.returncode = 1
+                       self._async_wait()
+                       return
+
+               # Add -q to decomp_cmd opts, in order to avoid "trailing garbage
+               # after EOF ignored" warning messages due to xpak trailer.
                # SIGPIPE handling (128 + SIGPIPE) should be compatible with
                # assert_sigpipe_ok() that's used by the ebuild unpack() helper.
                self.args = [self._shell_binary, "-c",
-                       ("${PORTAGE_BUNZIP2_COMMAND:-${PORTAGE_BZIP2_COMMAND} 
-d} -cq -- %s | tar -xp %s -C %s -f - ; " + \
+                       ("%s -cq -- %s | tar -xp %s -C %s -f - ; " + \
                        "p=(${PIPESTATUS[@]}) ; " + \
                        "if [[ ${p[0]} != 0 && ${p[0]} != %d ]] ; then " % (128 
+ signal.SIGPIPE) + \
                        "echo bzip2 failed with status ${p[0]} ; exit ${p[0]} ; 
fi ; " + \
                        "if [ ${p[1]} != 0 ] ; then " + \
                        "echo tar failed with status ${p[1]} ; exit ${p[1]} ; 
fi ; " + \
                        "exit 0 ;") % \
-                       (portage._shell_quote(self.pkg_path),
+                       (decomp_cmd,
+                       portage._shell_quote(self.pkg_path),
                        tar_options,
                        portage._shell_quote(self.image_dir))]
 

diff --git a/pym/portage/util/compression_probe.py 
b/pym/portage/util/compression_probe.py
new file mode 100644
index 0000000..1dc3547
--- /dev/null
+++ b/pym/portage/util/compression_probe.py
@@ -0,0 +1,68 @@
+# Copyright 2015 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+import errno
+import re
+import sys
+
+if sys.hexversion >= 0x3000000:
+       basestring = str
+
+from portage import _encodings, _unicode_encode
+from portage.exception import FileNotFound, PermissionDenied
+
+_decompressors = {
+       "bzip2": "${PORTAGE_BUNZIP2_COMMAND:-${PORTAGE_BZIP2_COMMAND} -d}",
+       "gzip": "gzip -d",
+       "xz": "xz -d",
+}
+
+_compression_re = re.compile(b'^(' +
+       b'(?P<bzip2>\x42\x5a\x68\x39)|' +
+       b'(?P<gzip>\x1f\x8b)|' +
+       b'(?P<xz>\xfd\x37\x7a\x58\x5a\x00))')
+
+def compression_probe(f):
+       """
+       Identify the compression type of a file. Returns one of the
+       following identifier strings:
+
+               bzip2
+               gzip
+               xz
+
+       @param f: a file path, or file-like object
+       @type f: str or file
+       @return: a string identifying the compression type, or None if the
+               compression type is unrecognized
+       @rtype str or None
+       """
+
+       open_file = isinstance(f, basestring)
+       if open_file:
+               try:
+                       f = open(_unicode_encode(f,
+                               encoding=_encodings['fs'], errors='strict'), 
mode='rb')
+               except IOError as e:
+                       if e.errno == PermissionDenied.errno:
+                               raise PermissionDenied(f)
+                       elif e.errno in (errno.ENOENT, errno.ESTALE):
+                               raise FileNotFound(f)
+                       else:
+                               raise
+
+       try:
+               return _compression_probe_file(f)
+       finally:
+               if open_file:
+                       f.close()
+
+def _compression_probe_file(f):
+
+       m = _compression_re.match(f.read(6))
+       if m is not None:
+               for k, v in m.groupdict().items():
+                       if v is not None:
+                               return k
+
+       return None

Reply via email to