On Wed, Feb 06, 2008 at 08:44:05PM +0100, Filippo Giunchedi wrote:
> I came up with the attached patch, it works but I'll give Zack a change to
> comment and/or adapt before commit.

Nope, that patch will only avoid failures during the initialization of a
DebFile object, but it won't work for actually accessing the content of
data.tar.bz2.

Anyhow, first things first, attached there is a modified version of
Filippo's patch which is a bit more generic in which parts can be
compressed and how they can be compressed. It also updates the
documentation of the affected methods. Such a patch can be applied to
fix the b0rken behaviour of scripts like dpkg-info, but that's it:
accessing data.tar.bz2 would not be possible (and will fail with an
exception). You can verify if with the proof of concept attached version
of dpkg-info, which tries to extract a file from the vim-runtime .deb
which as been pointed to in this bug report before (see the last line of
the attached dpkg-info).

The problem in fixing the access to data.tar.bz2 is that the Python
class BZ2File does not work as GZFile: in particular it does not accept
a file object as input, but rather requires a file*name*. So I see 2
solutions:

1) save on a temporary file the .tar.bz2 and read it using BZ2File, of
   course remembering to delete the temporary file when done. Will
   introduce the risk of leaving garbage around, but the code will be
   (as much as possible) uniform with what we have now

2) use some other facilities of the bz2 module to decompress in memory
   on the fly. One is BZ2Decompressor (which is an incremental
   decompressor), the other is bz2.decompress (which is one shot). The
   problem with both of them is that neither of the 2 implement a
   file-like interface, so we need either to do that by ourselves, or to
   change the code, rather heavily I presume, for being able to cope
   with that

(If my life was threatened, I would probably choose (1) among the two.)

3) A third (non-)solution would be to commit the patch write away, and
   return an error when trying to access the content of data.tar.bz2. At
   least all usages of .deb which only deal with .deb metadata and not
   with the actual content would work again ...

Go ahead, starting from my attached patch, with a proper error message
and a commit if you want (3).
Sooner or later I'll probably implement (1).

Cheers.

-- 
Stefano Zacchiroli -*- PhD in Computer Science ............... now what?
[EMAIL PROTECTED],cs.unibo.it,debian.org}  -<%>-  http://upsilon.cc/zack/
(15:56:48)  Zack: e la demo dema ?    /\    All one has to do is hit the
(15:57:15)  Bac: no, la demo scema    \/    right keys at the right time
=== modified file 'debian_bundle/debfile.py'
--- debian_bundle/debfile.py	2007-08-20 17:37:07 +0000
+++ debian_bundle/debfile.py	2008-02-20 16:14:15 +0000
@@ -1,6 +1,6 @@
 # DebFile: a Python representation of Debian .deb binary packages.
-# Copyright (C) 2007    Stefano Zacchiroli  <[EMAIL PROTECTED]>
-# Copyright (C) 2007    Filippo Giunchedi   <[EMAIL PROTECTED]>
+# Copyright (C) 2007-2008    Stefano Zacchiroli  <[EMAIL PROTECTED]>
+# Copyright (C) 2007-2008    Filippo Giunchedi   <[EMAIL PROTECTED]>
 #
 # This program is free software: you can redistribute it and/or modify
 # it under the terms of the GNU General Public License as published by
@@ -15,6 +15,7 @@
 # You should have received a copy of the GNU General Public License
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 
+import bz2
 import gzip
 import string
 import tarfile
@@ -24,8 +25,8 @@
 from changelog import Changelog
 from deb822 import Deb822
 
-DATA_PART = 'data.tar.gz'
-CTRL_PART = 'control.tar.gz'
+DATA_PART = 'data.tar'      # without compression extension
+CTRL_PART = 'control.tar'   # without compression extension
 INFO_PART = 'debian-binary'
 MAINT_SCRIPTS = ['preinst', 'postinst', 'prerm', 'postrm', 'config']
 
@@ -45,7 +46,8 @@
     A .deb package is considered as made of 2 parts: a 'data' part
     (corresponding to the 'data.tar.gz' archive embedded in a .deb) and a
     'control' part (the 'control.tar.gz' archive). Each of them is represented
-    by an instance of this class.
+    by an instance of this class. Each archive can either be a .tar.gz or a
+    .tar.bz2 archive.
 
     When referring to file members of the underlying .tar.gz archive, file
     names can be specified in one of 3 formats "file", "./file", "/file". In
@@ -61,11 +63,15 @@
 
     def tgz(self):
         """Return a TarFile object corresponding to this part of a .deb
-        package."""
+        package. Despite the name, this method supports both .tar.gz and
+        .tar.bz2 archives."""
 
         if self.__tgz is None:
-            gz = gzip.GzipFile(fileobj=self.__member, mode='r')
-            self.__tgz = tarfile.TarFile(fileobj=gz, mode='r')
+            if self.__member.name.endswith('.gz'):
+                zfile = gzip.GzipFile(fileobj=self.__member, mode='r')
+            elif self.__member.name.endswith('.bz2'):
+                zfile = bz2.BZ2File(fileobj=self.__member, mode='r')
+            self.__tgz = tarfile.TarFile(fileobj=zfile, mode='r')
         return self.__tgz
 
     @staticmethod
@@ -181,24 +187,37 @@
         - version       debian .deb file format version (not related with the
                         contained package version), 2.0 at the time of writing
                         for all .deb packages in the Debian archive
-        - data          DebPart object corresponding to the data.tar.gz
-                        archive contained in the .deb file
-        - control       DebPart object corresponding to the control.tar.gz
-                        archive contained in the .deb file
+        - data          DebPart object corresponding to the data.tar.gz (or
+                        data.tar.bz2) archive contained in the .deb file
+        - control       DebPart object corresponding to the control.tar.gz (or
+                        control.tar.bz2) archive contained in the .deb file
     """
 
     def __init__(self, filename=None, mode='r', fileobj=None):
         ArFile.__init__(self, filename, mode, fileobj)
-        required_names = set([INFO_PART, CTRL_PART, DATA_PART])
         actual_names = set(self.getnames())
-        if not (required_names <= actual_names):
-            raise DebError(
-                    "the following required .deb members are missing: " \
-                            + string.join(required_names - actual_names))
+
+        def compressed_part_name(basename):
+            candidates = [ '%s.%s' % (basename, ext) for ext in ['gz', 'bz2'] ]
+            parts = actual_names.intersection(set(candidates))
+            if not parts:
+                raise DebError("missing required part in given .deb" \
+                        " (expected one of: %s)" % candidates)
+            elif len(parts) > 1:
+                raise DebError("too many parts in given .deb" \
+                        " (was looking for *one* among: %s)" % candidates)
+            else:
+                return list(parts)[0]
+
+        if not INFO_PART in actual_names:
+            raise DebError("missing required part in given .deb" \
+                    " (expected: '%s')" % INFO_PART)
 
         self.__parts = {}
-        self.__parts[CTRL_PART] = DebControl(self.getmember(CTRL_PART))
-        self.__parts[DATA_PART] = DebData(self.getmember(DATA_PART))
+        self.__parts[CTRL_PART] = DebControl(self.getmember( \
+                compressed_part_name(CTRL_PART)))
+        self.__parts[DATA_PART] = DebData(self.getmember( \
+                compressed_part_name(DATA_PART)))
         self.__pkgname = None   # updated lazily by __updatePkgName
 
         f = self.getmember(INFO_PART)

#!/usr/bin/python

# dpkg-info - DebFile's implementation of "dpkg --info"
# Copyright (C) 2007 Stefano Zacchiroli <[EMAIL PROTECTED]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.


""" (An approximation of) a 'dpkg --info' implementation relying on DebFile
class. """

import os
import stat
import string
import sys

from debian_bundle import debfile

if __name__ == '__main__':
    if len(sys.argv) != 2:
        print "Usage: dpkg-info DEB"
        sys.exit(1)
    fname = sys.argv[1]

    deb = debfile.DebFile(fname)
    if deb.version == '2.0':
        print ' new debian package, version %s.' % deb.version
    print ' size %d bytes: control archive= %d bytes.' % (
            os.stat(fname)[stat.ST_SIZE], deb['control.tar.gz'].size)
    for fname in deb.control:   # print info about control part contents
        content = deb.control[fname]
        if not content:
            continue
        lines = content.split('\n')
        ftype = ''
        try:
            if lines[0].startswith('#!'):
                ftype = lines[0].split()[0]
        except IndexError:
            pass
        print '  %d bytes, %d lines, %s, %s' % (len(content), len(lines),
                fname, ftype)
    for n, v in deb.debcontrol().iteritems(): # print DEBIAN/control fields
        if n.lower() == 'description':  # increase indentation of long dsc
            lines = v.split('\n')
            shortDsc = lines[0]
            longDsc = string.join(map(lambda l: ' ' + l, lines[1:]), '\n')
            print ' %s: %s\n%s' % (n, shortDsc, longDsc)
        else:
            print ' %s: %s' % (n, v)
    print deb.data.get_content('/usr/share/vim/vim71/doc/diff.txt')

Attachment: signature.asc
Description: Digital signature

Reply via email to