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')
signature.asc
Description: Digital signature

