From 400fc71e7887799b3b55d7d429303275311b3dee Mon Sep 17 00:00:00 2001 From: Christoph Goehre Date: Sun, 30 Sep 2007 18:34:17 +0200 Subject: Minor changes for package building * I'm the new Maintainer (Closes: #414621) * build package with cdbs * add XS-Vcs tags to git archive * move python-dev and python-support to B-Depends (instead of B-D-I) to clam lintian --- debian/changelog | 12 ++- debian/control | 8 +- debian/manpages | 1 + debian/rules | 45 ++------- lib/ChangeFile.py | 109 --------------------- lib/DebianSigVerifier.py | 35 ------- lib/Dnotify.py | 199 -------------------------------------- lib/DpkgControl.py | 156 ------------------------------ lib/DpkgDatalist.py | 81 ---------------- lib/GPGSigVerifier.py | 78 --------------- lib/OrderedDict.py | 76 --------------- lib/SafeWriteFile.py | 81 ---------------- lib/SignedFile.py | 107 -------------------- lib/__init__.py | 1 - lib/misc.py | 36 ------- lib/version.py | 1 - minidinstall/ChangeFile.py | 109 +++++++++++++++++++++ minidinstall/DebianSigVerifier.py | 35 +++++++ minidinstall/Dnotify.py | 199 ++++++++++++++++++++++++++++++++++++++ minidinstall/DpkgControl.py | 156 ++++++++++++++++++++++++++++++ minidinstall/DpkgDatalist.py | 81 ++++++++++++++++ minidinstall/GPGSigVerifier.py | 78 +++++++++++++++ minidinstall/OrderedDict.py | 76 +++++++++++++++ minidinstall/SafeWriteFile.py | 81 ++++++++++++++++ minidinstall/SignedFile.py | 107 ++++++++++++++++++++ minidinstall/__init__.py | 1 + minidinstall/misc.py | 36 +++++++ minidinstall/version.py | 1 + setup.py | 33 +++++++ 29 files changed, 1017 insertions(+), 1002 deletions(-) create mode 100644 debian/manpages delete mode 100644 lib/ChangeFile.py delete mode 100644 lib/DebianSigVerifier.py delete mode 100644 lib/Dnotify.py delete mode 100755 lib/DpkgControl.py delete mode 100644 lib/DpkgDatalist.py delete mode 100644 lib/GPGSigVerifier.py delete mode 100644 lib/OrderedDict.py delete mode 100755 lib/SafeWriteFile.py delete mode 100755 lib/SignedFile.py delete mode 100644 lib/__init__.py delete mode 100644 lib/misc.py delete mode 100644 lib/version.py create mode 100644 minidinstall/ChangeFile.py create mode 100644 minidinstall/DebianSigVerifier.py create mode 100644 minidinstall/Dnotify.py create mode 100755 minidinstall/DpkgControl.py create mode 100644 minidinstall/DpkgDatalist.py create mode 100644 minidinstall/GPGSigVerifier.py create mode 100644 minidinstall/OrderedDict.py create mode 100755 minidinstall/SafeWriteFile.py create mode 100755 minidinstall/SignedFile.py create mode 100644 minidinstall/__init__.py create mode 100644 minidinstall/misc.py create mode 100644 minidinstall/version.py create mode 100644 setup.py diff --git a/debian/changelog b/debian/changelog index 271200c..a9204b9 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,10 +1,18 @@ -mini-dinstall (0.6.21-0.3) unstable; urgency=low +mini-dinstall (0.6.22) unstable; urgency=low + [ Guido Guenther ] * UNRELEASED * Create the release files in the correct subdirs with archive-style = simple-subdir so it works with secure-apt (Closes: #343371) - -- Guido Guenther Sun, 23 Sep 2007 12:23:43 +0200 + [ Christoph Goehre ] + * I'm the new Maintainer (Closes: #414621) + * build package with cdbs + * add XS-Vcs tags to git archive + * move python-dev and python-support to B-Depends (instead of B-D-I) to clam + lintian + + -- Christoph Goehre Sun, 30 Sep 2007 17:51:10 +0200 mini-dinstall (0.6.21-0.2) unstable; urgency=low diff --git a/debian/control b/debian/control index d17fe78..9643c4c 100644 --- a/debian/control +++ b/debian/control @@ -1,10 +1,12 @@ Source: mini-dinstall Priority: optional Section: devel -Maintainer: Thomas Viehmann -Build-Depends: debhelper (>= 4.1.25) -Build-Depends-Indep: python-dev, python-support (>= 0.3) +Maintainer: Christoph Goehre +Uploaders: Guido Guenther +Build-Depends: cdbs, debhelper (>= 4.1.25), python-dev, python-support (>= 0.3) Standards-Version: 3.7.2 +XS-Vcs-Git: git://git.debian.org/git/mini-dinstall/mini-dinstall.git +XS-Vcs-Browser: http://git.debian.org/?p=mini-dinstall/mini-dinstall.git Package: mini-dinstall Architecture: all diff --git a/debian/manpages b/debian/manpages new file mode 100644 index 0000000..846db09 --- /dev/null +++ b/debian/manpages @@ -0,0 +1 @@ +doc/mini-dinstall.1 diff --git a/debian/rules b/debian/rules index 71a1e53..3d6bcd4 100755 --- a/debian/rules +++ b/debian/rules @@ -3,44 +3,15 @@ # Sample debian/rules that uses debhelper. # This file is public domain software, originally written by Joey Hess. -build: +# Uncomment this to turn on verbose mode. +#export DH_VERBOSE=1 -lib/version.py: debian/changelog - echo "pkg_version = \"`dpkg-parsechangelog | awk '/^Version:/ { print $$2 }'`\"" > lib/version.py +DEB_PYTHON_SYSTEM=pysupport -clean: lib/version.py - dh_testdir - dh_testroot - rm -f build-stamp - dh_clean +include /usr/share/cdbs/1/rules/debhelper.mk +include /usr/share/cdbs/1/class/python-distutils.mk -install: build - dh_testdir - dh_testroot - dh_clean -k - dh_installdirs - $(MAKE) install DESTDIR=debian/mini-dinstall +update-version: + echo "pkg_version = \"$(DEB_UPSTREAM_VERSION)\"" > minidinstall/version.py -# Build architecture-independent files here. -binary-indep: build install - dh_testdir - dh_testroot - dh_installchangelogs - dh_installdocs - dh_installexamples - dh_link - dh_compress - dh_fixperms - dh_pysupport - dh_python - dh_installdeb - dh_gencontrol - dh_md5sums - dh_builddeb - -# Build architecture-dependent files here. -binary-arch: build install -# We have nothing to do by default. - -binary: binary-indep binary-arch -.PHONY: build clean binary-indep binary-arch binary install +.PHONY: update-version diff --git a/lib/ChangeFile.py b/lib/ChangeFile.py deleted file mode 100644 index b74e623..0000000 --- a/lib/ChangeFile.py +++ /dev/null @@ -1,109 +0,0 @@ -# ChangeFile - -# A class which represents a Debian change file. - -# Copyright 2002 Colin Walters - -# This file 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 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -import os, re, sys, string, stat, popen2 -import threading, Queue -import logging -from minidinstall import DpkgControl, SignedFile - -class ChangeFileException(Exception): - def __init__(self, value): - self._value = value - def __str__(self): - return `self._value` - -class ChangeFile(DpkgControl.DpkgParagraph): - def __init__(self): - DpkgControl.DpkgParagraph.__init__(self) - self._logger = logging.getLogger("mini-dinstall") - - def load_from_file(self, filename): - f = SignedFile.SignedFile(open(filename)) - self.load(f) - f.close() - - def getFiles(self): - out = [] - try: - files = self['files'] - except KeyError: - return [] - lineregexp = re.compile("^([0-9a-f]{32})[ \t]+(\d+)[ \t]+([-/a-zA-Z0-9]+)[ \t]+([-a-zA-Z0-9]+)[ \t]+([0-9a-zA-Z][-+:.,=~0-9a-zA-Z_]+)$") - for line in files: - if line == '': - continue - match = lineregexp.match(line) - if (match is None): - raise ChangeFileException("Couldn't parse file entry \"%s\" in Files field of .changes" % (line,)) - out.append((match.group(1), match.group(2), match.group(3), match.group(4), match.group(5))) - return out - - def verify(self, sourcedir): - for (md5sum, size, section, prioriy, filename) in self.getFiles(): - self._verify_file_integrity(os.path.join(sourcedir, filename), int(size), md5sum) - - def _verify_file_integrity(self, filename, expected_size, expected_md5sum): - self._logger.debug('Checking integrity of %s' % (filename,)) - try: - statbuf = os.stat(filename) - if not stat.S_ISREG(statbuf[stat.ST_MODE]): - raise ChangeFileException("%s is not a regular file" % (filename,)) - size = statbuf[stat.ST_SIZE] - except OSError, e: - raise ChangeFileException("Can't stat %s: %s" % (filename,e.strerror)) - if size != expected_size: - raise ChangeFileException("File size for %s does not match that specified in .dsc" % (filename,)) - if (self._get_file_md5sum(filename) != expected_md5sum): - raise ChangeFileException("md5sum for %s does not match that specified in .dsc" % (filename,)) - self._logger.debug('Verified md5sum %s and size %s for %s' % (expected_md5sum, expected_size, filename)) - - def _get_file_md5sum(self, filename): - if os.access('/usr/bin/md5sum', os.X_OK): - cmd = '/usr/bin/md5sum %s' % (filename,) - self._logger.debug("Running: %s" % (cmd,)) - child = popen2.Popen3(cmd, 1) - child.tochild.close() - erroutput = child.childerr.read() - child.childerr.close() - if erroutput != '': - child.fromchild.close() - raise ChangeFileException("md5sum returned error output \"%s\"" % (erroutput,)) - (md5sum, filename) = string.split(child.fromchild.read(), None, 1) - child.fromchild.close() - status = child.wait() - if not (status is None or (os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0)): - if os.WIFEXITED(status): - msg = "md5sum exited with error code %d" % (os.WEXITSTATUS(status),) - elif os.WIFSTOPPED(status): - msg = "md5sum stopped unexpectedly with signal %d" % (os.WSTOPSIG(status),) - elif os.WIFSIGNALED(status): - msg = "md5sum died with signal %d" % (os.WTERMSIG(status),) - raise ChangeFileException(msg) - return md5sum.strip() - import md5 - f = open(filename) - md5sum = md5.new() - buf = f.read(8192) - while buf != '': - md5sum.update(buf) - buf = f.read(8192) - return md5sum.hexdigest() - -# vim:ts=4:sw=4:et: diff --git a/lib/DebianSigVerifier.py b/lib/DebianSigVerifier.py deleted file mode 100644 index d441a58..0000000 --- a/lib/DebianSigVerifier.py +++ /dev/null @@ -1,35 +0,0 @@ -# DebianSigVerifier -*- mode: python; coding: utf-8 -*- - -# A class for verifying signed files, using Debian keys - -# Copyright © 2002 Colin Walters - -# This file 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 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -import os, re, sys, string, stat, logging -from minidinstall.GPGSigVerifier import GPGSigVerifier - -class DebianSigVerifier(GPGSigVerifier): - _dpkg_ring = '/etc/dpkg/local-keyring.gpg' - def __init__(self, keyrings=None, extra_keyrings=None): - if keyrings is None: - keyrings = ['/usr/share/keyrings/debian-keyring.gpg', '/usr/share/keyrings/debian-keyring.pgp'] - if os.access(self._dpkg_ring, os.R_OK): - keyrings.append(self._dpkg_ring) - if not extra_keyrings is None: - keyrings += extra_keyrings - GPGSigVerifier.__init__(self, keyrings) - -# vim:ts=4:sw=4:et: diff --git a/lib/Dnotify.py b/lib/Dnotify.py deleted file mode 100644 index 122e03c..0000000 --- a/lib/Dnotify.py +++ /dev/null @@ -1,199 +0,0 @@ -# Dnotify -*- mode: python; coding: utf-8 -*- - -# A simple FAM-like beast in Python - -# Copyright © 2002 Colin Walters - -# This file 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 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -import os, re, sys, string, stat, threading, Queue, time -import logging -from minidinstall import misc - -class DnotifyException(Exception): - def __init__(self, value): - self._value = value - def __str__(self): - return `self._value` - -class DirectoryNotifierFactory: - def create(self, dirs, use_dnotify=1, poll_time=30, logger=None, cancel_event=None): - if use_dnotify and os.access('/usr/bin/dnotify', os.X_OK): - if logger: - logger.debug("Using dnotify directory notifier") - return DnotifyDirectoryNotifier(dirs, logger) - else: - if logger: - logger.debug("Using mtime-polling directory notifier") - return MtimeDirectoryNotifier(dirs, poll_time, logger, cancel_event=cancel_event) - -class DnotifyNullLoggingFilter(logging.Filter): - def filter(self, record): - return 0 - -class DirectoryNotifier: - def __init__(self, dirs, logger, cancel_event=None): - self._cwd = os.getcwd() - self._dirs = dirs - if cancel_event is None: - self._cancel_event = threading.Event() - else: - self._cancel_event = cancel_event - if logger is None: - self._logger = logging.getLogger("Dnotify") - self._logger.addFilter(DnotifyNullLoggingFilter()) - else: - self._logger = logger - - def cancelled(self): - return self._cancel_event.isSet() - -class DirectoryNotifierAsyncWrapper(threading.Thread): - def __init__(self, dnotify, queue, logger=None, name=None): - if not name is None: - threading.Thread.__init__(self, name=name) - else: - threading.Thread.__init__(self) - self._eventqueue = queue - self._dnotify = dnotify - if logger is None: - self._logger = logging.getLogger("Dnotify") - self._logger.addFilter(DnotifyNullLoggingFilter()) - else: - self._logger = logger - - def cancel(self): - self._cancel_event.set() - - def run(self): - self._logger.info('Created new thread (%s) for async directory notification' % (self.getName())) - while not self._dnotify.cancelled(): - dir = self._dnotify.poll() - self._eventqueue.put(dir) - self._logger.info('Caught cancel event; async dnotify thread exiting') - -class MtimeDirectoryNotifier(DirectoryNotifier): - def __init__(self, dirs, poll_time, logger, cancel_event=None): - DirectoryNotifier.__init__(self, dirs, logger, cancel_event=cancel_event) - self._changed = [] - self._dirmap = {} - self._polltime = poll_time - for dir in dirs: - self._dirmap[dir] = os.stat(os.path.join(self._cwd, dir))[stat.ST_MTIME] - - def poll(self, timeout=None): - timeout_time = None - if timeout: - timeout_time = time.time() + timeout - while self._changed == []: - if timeout_time and time.time() > timeout_time: - return None - self._logger.debug('Polling...') - for dir in self._dirmap.keys(): - oldtime = self._dirmap[dir] - mtime = os.stat(os.path.join(self._cwd, dir))[stat.ST_MTIME] - if oldtime < mtime: - self._logger.debug('Directory "%s" has changed' % (dir,)) - self._changed.append(dir) - self._dirmap[dir] = mtime - if self._changed == []: - for x in range(self._polltime): - if self._cancel_event.isSet(): - return None - time.sleep(1) - ret = self._changed[0] - self._changed = self._changed[1:] - return ret - -class DnotifyDirectoryNotifier(DirectoryNotifier): - def __init__(self, dirs, logger): - DirectoryNotifier.__init__(self, dirs, logger) - self._queue = Queue.Queue() - dnotify = DnotifyThread(self._queue, self._dirs, self._logger) - dnotify.start() - - def poll(self, timeout=None): - # delete duplicates - i = self._queue.qsize() - self._logger.debug('Queue size: %d', (i,)) - set = {} - while i > 0: - dir = self._queue_get(timeout) - if dir is None: - # We shouldn't have to do this; no one else is reading - # from the queue. But we do it just to be safe. - for key in set.keys(): - self._queue.put(key) - return None - set[dir] = 1 - i -= 1 - for key in set.keys(): - self._queue.put(key) - i = self._queue.qsize() - self._logger.debug('Queue size (after duplicate filter): %d', (i,)) - return self._queue_get(timeout) - - def _queue_get(self, timeout): - if timeout is None: - return self._queue.get() - timeout_time = time.time() + timeout - while 1: - try: - self._queue.get(0) - except Queue.Empty: - if time.time() > timeout_time: - return None - else: - time.sleep(15) - -class DnotifyThread(threading.Thread): - def __init__(self, queue, dirs, logger): - threading.Thread.__init__(self) - self._queue = queue - self._dirs = dirs - self._logger = logger - - def run(self): - self._logger.debug('Starting dnotify reading thread') - (infd, outfd) = os.pipe() - pid = os.fork() - if pid == 0: - os.close(infd) - misc.dup2(outfd, 1) - args = ['dnotify', '-m', '-c', '-d', '-a', '-r'] + list(self._dirs) + ['-e', 'printf', '"{}\\0"'] - os.execv('/usr/bin/dnotify', args) - os.exit(1) - - os.close(outfd) - stdout = os.fdopen(infd) - c = 'x' - while c != '': - curline = '' - c = stdout.read(1) - while c != '' and c != '\0': - curline += c - c = stdout.read(1) - if c == '': - break - self._logger.debug('Directory "%s" changed' % (curline,)) - self._queue.put(curline) - (pid, status) = os.waitpid(pid, 0) - if status is None: - ecode = 0 - else: - ecode = os.WEXITSTATUS(status) - raise DnotifyException("dnotify exited with code %s" % (ecode,)) - -# vim:ts=4:sw=4:et: diff --git a/lib/DpkgControl.py b/lib/DpkgControl.py deleted file mode 100755 index 38147c7..0000000 --- a/lib/DpkgControl.py +++ /dev/null @@ -1,156 +0,0 @@ -# DpkgControl.py -# -# This module implements control file parsing. -# -# DpkgParagraph is a low-level class, that reads/parses a single paragraph -# from a file object. -# -# DpkgControl uses DpkgParagraph in a loop, pulling out the value of a -# defined key(package), and using that as a key in it's internal -# dictionary. -# -# DpkgSourceControl grabs the first paragraph from the file object, stores -# it in object.source, then passes control to DpkgControl.load, to parse -# the rest of the file. -# -# To test this, pass it a filetype char, a filename, then, optionally, -# the key to a paragraph to display, and if a fourth arg is given, only -# show that field. -# -# Copyright 2001 Adam Heath -# -# This file 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 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -import re, string -from DpkgDatalist import * -from minidinstall.SignedFile import * -from types import ListType - -class DpkgParagraph(DpkgOrderedDatalist): - caseSensitive = 0 - trueFieldCasing = {} - - def setCaseSensitive( self, value ): self.caseSensitive = value - - def load( self, f ): - "Paragraph data from a file object." - key = None - value = None - while 1: - line = f.readline() - if not line: - return - # skip blank lines until we reach a paragraph - if line == '\n': - if not self: - continue - else: - return - line = line[ :-1 ] - if line[ 0 ] != ' ': - key, value = string.split( line, ":", 1 ) - if value: value = value[ 1: ] - if not self.caseSensitive: - newkey = string.lower( key ) - if not self.trueFieldCasing.has_key( key ): - self.trueFieldCasing[ newkey ] = key - key = newkey - else: - if isinstance( value, ListType ): - value.append( line[ 1: ] ) - else: - value = [ value, line[ 1: ] ] - self[ key ] = value - - def _storeField( self, f, value, lead = " " ): - if isinstance( value, ListType ): - value = string.join( map( lambda v, lead = lead: v and ( lead + v ) or v, value ), "\n" ) - else: - if value: value = lead + value - f.write( "%s\n" % ( value ) ) - - def _store( self, f ): - "Write our paragraph data to a file object" - for key in self.keys(): - value = self[ key ] - if self.trueFieldCasing.has_key( key ): - key = self.trueFieldCasing[ key ] - f.write( "%s:" % key ) - self._storeField( f, value ) - -class DpkgControl(DpkgOrderedDatalist): - - key = "package" - caseSensitive = 0 - - def setkey( self, key ): self.key = key - def setCaseSensitive( self, value ): self.caseSensitive = value - - def _load_one( self, f ): - p = DpkgParagraph( None ) - p.setCaseSensitive( self.caseSensitive ) - p.load( f ) - return p - - def load( self, f ): - while 1: - p = self._load_one( f ) - if not p: break - self[ p[ self.key ] ] = p - - def _store( self, f ): - "Write our control data to a file object" - - for key in self.keys(): - self[ key ]._store( f ) - f.write( "\n" ) - -class DpkgSourceControl( DpkgControl ): - source = None - - def load( self, f ): - f = SignedFile(f) - self.source = self._load_one( f ) - DpkgControl.load( self, f ) - - def __repr__( self ): - return self.source.__repr__() + "\n" + DpkgControl.__repr__( self ) - - def _store( self, f ): - "Write our control data to a file object" - self.source._store( f ) - f.write( "\n" ) - DpkgControl._store( self, f ) - -if __name__ == "__main__": - import sys - types = { 'p' : DpkgParagraph, 'c' : DpkgControl, 's' : DpkgSourceControl } - type = sys.argv[ 1 ] - if not types.has_key( type ): - print "Unknown type `%s'!" % type - sys.exit( 1 ) - file = open( sys.argv[ 2 ], "r" ) - data = types[ type ]() - data.load( file ) - if len( sys.argv ) > 3: - para = data[ sys.argv[ 3 ] ] - if len( sys.argv ) > 4: - para._storeField( sys.stdout, para[ sys.argv[ 4 ] ], "" ) - else: - para._store( sys.stdout ) - else: - data._store( sys.stdout ) - -# vim:ts=4:sw=4:et: diff --git a/lib/DpkgDatalist.py b/lib/DpkgDatalist.py deleted file mode 100644 index 0c11612..0000000 --- a/lib/DpkgDatalist.py +++ /dev/null @@ -1,81 +0,0 @@ -# DpkgDatalist.py -# -# This module implements DpkgDatalist, an abstract class for storing -# a list of objects in a file. Children of this class have to implement -# the load and _store methods. -# -# Copyright 2001 Wichert Akkerman -# -# This file 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 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -import os, sys -from UserDict import UserDict -from OrderedDict import OrderedDict -from minidinstall.SafeWriteFile import SafeWriteFile -from types import StringType - -class DpkgDatalistException(Exception): - UNKNOWN = 0 - SYNTAXERROR = 1 - - def __init__(self, message="", reason=UNKNOWN, file=None, line=None): - self.message=message - self.reason=reason - self.filename=file - self.line=line - -class _DpkgDatalist: - def __init__(self, fn=""): - '''Initialize a DpkgDatalist object. An optional argument is a - file from which we load values.''' - - self.filename=fn - if self.filename: - self.load(self.filename) - - def store(self, fn=None): - "Store variable data in a file." - - if fn==None: - fn=self.filename - # Special case for writing to stdout - if not fn: - self._store(sys.stdout) - return - - # Write to a temporary file first - if type(fn) == StringType: - vf=SafeWriteFile(fn+".new", fn, "w") - else: - vf=fn - try: - self._store(vf) - finally: - if type(fn) == StringType: - vf.close() - - -class DpkgDatalist(UserDict, _DpkgDatalist): - def __init__(self, fn=""): - UserDict.__init__(self) - _DpkgDatalist.__init__(self, fn) - - -class DpkgOrderedDatalist(OrderedDict, _DpkgDatalist): - def __init__(self, fn=""): - OrderedDict.__init__(self) - _DpkgDatalist.__init__(self, fn) - -# vim:ts=4:sw=4:et: diff --git a/lib/GPGSigVerifier.py b/lib/GPGSigVerifier.py deleted file mode 100644 index 78aeebb..0000000 --- a/lib/GPGSigVerifier.py +++ /dev/null @@ -1,78 +0,0 @@ -# GPGSigVerifier -*- mode: python; coding: utf-8 -*- - -# A class for verifying signed files - -# Copyright © 2002 Colin Walters - -# This file 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 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -import os, re, sys, string, stat -from minidinstall import misc - -class GPGSigVerifierException(Exception): - def __init__(self, value): - self._value = value - def __str__(self): - return `self._value` - -class GPGSigVerificationFailure(Exception): - def __init__(self, value, output): - self._value = value - self._output = output - def __str__(self): - return `self._value` - - def getOutput(self): - return self._output - -class GPGSigVerifier: - def __init__(self, keyrings, gpgv=None): - self._keyrings = keyrings - if gpgv is None: - gpgv = '/usr/bin/gpgv' - if not os.access(gpgv, os.X_OK): - raise GPGSigVerifierException("Couldn't execute \"%s\"" % (gpgv,)) - self._gpgv = gpgv - - def verify(self, filename, sigfilename=None): - (stdin, stdout) = os.pipe() - pid = os.fork() - if pid == 0: - os.close(stdin) - misc.dup2(stdout, 1) - misc.dup2(stdout, 2) - args = [] - for keyring in self._keyrings: - args.append('--keyring') - args.append(keyring) - if sigfilename: - args.append(sigfilename) - args = [self._gpgv] + args + [filename] - os.execv(self._gpgv, args) - os.exit(1) - os.close(stdout) - output = os.fdopen(stdin).readlines() - (pid, status) = os.waitpid(pid, 0) - if not (status is None or (os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0)): - if os.WIFEXITED(status): - msg = "gpgv exited with error code %d" % (os.WEXITSTATUS(status),) - elif os.WIFSTOPPED(status): - msg = "gpgv stopped unexpectedly with signal %d" % (os.WSTOPSIG(status),) - elif os.WIFSIGNALED(status): - msg = "gpgv died with signal %d" % (os.WTERMSIG(status),) - raise GPGSigVerificationFailure(msg, output) - return output - -# vim:ts=4:sw=4:et: diff --git a/lib/OrderedDict.py b/lib/OrderedDict.py deleted file mode 100644 index fa3f276..0000000 --- a/lib/OrderedDict.py +++ /dev/null @@ -1,76 +0,0 @@ -# OrderedDict.py -# -# This class functions almost exactly like UserDict. However, when using -# the sequence methods, it returns items in the same order in which they -# were added, instead of some random order. -# -# Copyright 2001 Adam Heath -# -# This file 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 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -from UserDict import UserDict - -class OrderedDict(UserDict): - __order=[] - - def __init__(self, dict=None): - UserDict.__init__(self) - self.__order=[] - if dict is not None and dict.__class__ is not None: - self.update(dict) - - def __cmp__(self, dict): - if isinstance(dict, OrderedDict): - ret=cmp(self.__order, dict.__order) - if not ret: - ret=UserDict.__cmp__(self, dict) - return ret - else: - return UserDict.__cmp__(self, dict) - - def __setitem__(self, key, value): - if not self.has_key(key): - self.__order.append(key) - UserDict.__setitem__(self, key, value) - - def __delitem__(self, key): - if self.has_key(key): - del self.__order[self.__order.index(key)] - UserDict.__delitem__(self, key) - - def clear(self): - self.__order=[] - UserDict.clear(self) - - def copy(self): - if self.__class__ is OrderedDict: - return OrderedDict(self) - import copy - return copy.copy(self) - - def keys(self): - return self.__order - - def items(self): - return map(lambda x, self=self: (x, self.__getitem__(x)), self.__order) - - def values(self): - return map(lambda x, self=self: self.__getitem__(x), self.__order) - - def update(self, dict): - for k, v in dict.items(): - self.__setitem__(k, v) - -# vim:ts=4:sw=4:et: diff --git a/lib/SafeWriteFile.py b/lib/SafeWriteFile.py deleted file mode 100755 index 1777d36..0000000 --- a/lib/SafeWriteFile.py +++ /dev/null @@ -1,81 +0,0 @@ -# SafeWriteFile.py -# -# This file is a writable file object. It writes to a specified newname, -# and when closed, renames the file to the realname. If the object is -# deleted, without being closed, this rename isn't done. If abort() is -# called, it also disables the rename. -# -# Copyright 2001 Adam Heath -# -# This file 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 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -from types import StringType -from shutil import copy2 -from string import find -from os import rename - -class ObjectNotAllowed(Exception): - pass - - -class InvalidMode(Exception): - pass - - -class SafeWriteFile: - def __init__(self, newname, realname, mode="w", bufsize=-1): - - if type(newname)!=StringType: - raise ObjectNotAllowed(newname) - if type(realname)!=StringType: - raise ObjectNotAllowed(realname) - - if find(mode, "r")>=0: - raise InvalidMode(mode) - if find(mode, "a")>=0 or find(mode, "+") >= 0: - copy2(realname, newname) - self.fobj=open(newname, mode, bufsize) - self.newname=newname - self.realname=realname - self.__abort=0 - - def close(self): - self.fobj.close() - if not (self.closed and self.__abort): - rename(self.newname, self.realname) - - def abort(self): - self.__abort=1 - - def __del__(self): - self.abort() - del self.fobj - - def __getattr__(self, attr): - try: - return self.__dict__[attr] - except: - return eval("self.fobj." + attr) - - -if __name__ == "__main__": - import time - f=SafeWriteFile("sf.new", "sf.data") - f.write("test\n") - f.flush() - time.sleep(1) - f.close() - -# vim:ts=4:sw=4:et: diff --git a/lib/SignedFile.py b/lib/SignedFile.py deleted file mode 100755 index 648186e..0000000 --- a/lib/SignedFile.py +++ /dev/null @@ -1,107 +0,0 @@ -# SignedFile -*- mode: python; coding: utf-8 -*- - -# SignedFile offers a subset of file object operations, and is -# designed to transparently handle files with PGP signatures. - -# Copyright © 2002 Colin Walters -# -# This file 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 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. -# -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -import re,string - -class SignedFile: - _stream = None - _eof = 0 - _signed = 0 - _signature = None - _signatureversion = None - _initline = None - def __init__(self, stream): - self._stream = stream - line = stream.readline() - if (line == "-----BEGIN PGP SIGNED MESSAGE-----\n"): - self._signed = 1 - while (1): - line = stream.readline() - if (len(line) == 0 or line == '\n'): - break - else: - self._initline = line - - def readline(self): - if self._eof: - return '' - if self._initline: - line = self._initline - self._initline = None - else: - line = self._stream.readline() - if not self._signed: - return line - elif line == "-----BEGIN PGP SIGNATURE-----\n": - self._eof = 1 - self._signature = [] - self._signatureversion = self._stream.readline() - self._stream.readline() # skip blank line - while 1: - line = self._stream.readline() - if len(line) == 0 or line == "-----END PGP SIGNATURE-----\n": - break - self._signature.append(line) - self._signature = string.join - return '' - return line - - def readlines(self): - ret = [] - while 1: - line = self.readline() - if (line != ''): - ret.append(line) - else: - break - return ret - - def close(self): - self._stream.close() - - def getSigned(self): - return self._signed - - def getSignature(self): - return self._signature - - def getSignatureVersion(self): - return self._signatureversion - -if __name__=="__main__": - import sys - if len(sys.argv) == 0: - print "Need one file as an argument" - sys.exit(1) - filename = sys.argv[1] - f=SignedFile(open(filename)) - if f.getSigned(): - print "**** SIGNED ****" - else: - print "**** NOT SIGNED ****" - lines=f.readlines() - print lines - if not f.getSigned(): - assert(len(lines) == len(actuallines)) - else: - print "Signature: %s" % (f.getSignature()) - -# vim:ts=4:sw=4:et: diff --git a/lib/__init__.py b/lib/__init__.py deleted file mode 100644 index 8b13789..0000000 --- a/lib/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/misc.py b/lib/misc.py deleted file mode 100644 index eb52d00..0000000 --- a/lib/misc.py +++ /dev/null @@ -1,36 +0,0 @@ -# misc -*- mode: python; coding: utf-8 -*- - -# misc tools for mini-dinstall - -# Copyright © 2004 Thomas Viehmann - -# This file 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 2 of the License, or -# (at your option) any later version. -# -# This program is distributed in the hope that it will be useful, but -# WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# General Public License for more details. - -# You should have received a copy of the GNU General Public License -# along with this program; if not, write to the Free Software -# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. - -import os, errno, time - -def dup2(fd,fd2): - # dup2 with EBUSY retries (cf. dup2(2) and Debian bug #265513) - success = 0 - tries = 0 - while (not success): - try: - os.dup2(fd,fd2) - success = 1 - except OSError, e: - if (e.errno != errno.EBUSY) or (tries >= 3): - raise - # wait 0-2 seconds befor next try - time.sleep(tries) - tries += 1 diff --git a/lib/version.py b/lib/version.py deleted file mode 100644 index 250e04b..0000000 --- a/lib/version.py +++ /dev/null @@ -1 +0,0 @@ -pkg_version = "0.6.21-0.2" diff --git a/minidinstall/ChangeFile.py b/minidinstall/ChangeFile.py new file mode 100644 index 0000000..b74e623 --- /dev/null +++ b/minidinstall/ChangeFile.py @@ -0,0 +1,109 @@ +# ChangeFile + +# A class which represents a Debian change file. + +# Copyright 2002 Colin Walters + +# This file 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 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import os, re, sys, string, stat, popen2 +import threading, Queue +import logging +from minidinstall import DpkgControl, SignedFile + +class ChangeFileException(Exception): + def __init__(self, value): + self._value = value + def __str__(self): + return `self._value` + +class ChangeFile(DpkgControl.DpkgParagraph): + def __init__(self): + DpkgControl.DpkgParagraph.__init__(self) + self._logger = logging.getLogger("mini-dinstall") + + def load_from_file(self, filename): + f = SignedFile.SignedFile(open(filename)) + self.load(f) + f.close() + + def getFiles(self): + out = [] + try: + files = self['files'] + except KeyError: + return [] + lineregexp = re.compile("^([0-9a-f]{32})[ \t]+(\d+)[ \t]+([-/a-zA-Z0-9]+)[ \t]+([-a-zA-Z0-9]+)[ \t]+([0-9a-zA-Z][-+:.,=~0-9a-zA-Z_]+)$") + for line in files: + if line == '': + continue + match = lineregexp.match(line) + if (match is None): + raise ChangeFileException("Couldn't parse file entry \"%s\" in Files field of .changes" % (line,)) + out.append((match.group(1), match.group(2), match.group(3), match.group(4), match.group(5))) + return out + + def verify(self, sourcedir): + for (md5sum, size, section, prioriy, filename) in self.getFiles(): + self._verify_file_integrity(os.path.join(sourcedir, filename), int(size), md5sum) + + def _verify_file_integrity(self, filename, expected_size, expected_md5sum): + self._logger.debug('Checking integrity of %s' % (filename,)) + try: + statbuf = os.stat(filename) + if not stat.S_ISREG(statbuf[stat.ST_MODE]): + raise ChangeFileException("%s is not a regular file" % (filename,)) + size = statbuf[stat.ST_SIZE] + except OSError, e: + raise ChangeFileException("Can't stat %s: %s" % (filename,e.strerror)) + if size != expected_size: + raise ChangeFileException("File size for %s does not match that specified in .dsc" % (filename,)) + if (self._get_file_md5sum(filename) != expected_md5sum): + raise ChangeFileException("md5sum for %s does not match that specified in .dsc" % (filename,)) + self._logger.debug('Verified md5sum %s and size %s for %s' % (expected_md5sum, expected_size, filename)) + + def _get_file_md5sum(self, filename): + if os.access('/usr/bin/md5sum', os.X_OK): + cmd = '/usr/bin/md5sum %s' % (filename,) + self._logger.debug("Running: %s" % (cmd,)) + child = popen2.Popen3(cmd, 1) + child.tochild.close() + erroutput = child.childerr.read() + child.childerr.close() + if erroutput != '': + child.fromchild.close() + raise ChangeFileException("md5sum returned error output \"%s\"" % (erroutput,)) + (md5sum, filename) = string.split(child.fromchild.read(), None, 1) + child.fromchild.close() + status = child.wait() + if not (status is None or (os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0)): + if os.WIFEXITED(status): + msg = "md5sum exited with error code %d" % (os.WEXITSTATUS(status),) + elif os.WIFSTOPPED(status): + msg = "md5sum stopped unexpectedly with signal %d" % (os.WSTOPSIG(status),) + elif os.WIFSIGNALED(status): + msg = "md5sum died with signal %d" % (os.WTERMSIG(status),) + raise ChangeFileException(msg) + return md5sum.strip() + import md5 + f = open(filename) + md5sum = md5.new() + buf = f.read(8192) + while buf != '': + md5sum.update(buf) + buf = f.read(8192) + return md5sum.hexdigest() + +# vim:ts=4:sw=4:et: diff --git a/minidinstall/DebianSigVerifier.py b/minidinstall/DebianSigVerifier.py new file mode 100644 index 0000000..d441a58 --- /dev/null +++ b/minidinstall/DebianSigVerifier.py @@ -0,0 +1,35 @@ +# DebianSigVerifier -*- mode: python; coding: utf-8 -*- + +# A class for verifying signed files, using Debian keys + +# Copyright © 2002 Colin Walters + +# This file 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 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import os, re, sys, string, stat, logging +from minidinstall.GPGSigVerifier import GPGSigVerifier + +class DebianSigVerifier(GPGSigVerifier): + _dpkg_ring = '/etc/dpkg/local-keyring.gpg' + def __init__(self, keyrings=None, extra_keyrings=None): + if keyrings is None: + keyrings = ['/usr/share/keyrings/debian-keyring.gpg', '/usr/share/keyrings/debian-keyring.pgp'] + if os.access(self._dpkg_ring, os.R_OK): + keyrings.append(self._dpkg_ring) + if not extra_keyrings is None: + keyrings += extra_keyrings + GPGSigVerifier.__init__(self, keyrings) + +# vim:ts=4:sw=4:et: diff --git a/minidinstall/Dnotify.py b/minidinstall/Dnotify.py new file mode 100644 index 0000000..122e03c --- /dev/null +++ b/minidinstall/Dnotify.py @@ -0,0 +1,199 @@ +# Dnotify -*- mode: python; coding: utf-8 -*- + +# A simple FAM-like beast in Python + +# Copyright © 2002 Colin Walters + +# This file 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 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import os, re, sys, string, stat, threading, Queue, time +import logging +from minidinstall import misc + +class DnotifyException(Exception): + def __init__(self, value): + self._value = value + def __str__(self): + return `self._value` + +class DirectoryNotifierFactory: + def create(self, dirs, use_dnotify=1, poll_time=30, logger=None, cancel_event=None): + if use_dnotify and os.access('/usr/bin/dnotify', os.X_OK): + if logger: + logger.debug("Using dnotify directory notifier") + return DnotifyDirectoryNotifier(dirs, logger) + else: + if logger: + logger.debug("Using mtime-polling directory notifier") + return MtimeDirectoryNotifier(dirs, poll_time, logger, cancel_event=cancel_event) + +class DnotifyNullLoggingFilter(logging.Filter): + def filter(self, record): + return 0 + +class DirectoryNotifier: + def __init__(self, dirs, logger, cancel_event=None): + self._cwd = os.getcwd() + self._dirs = dirs + if cancel_event is None: + self._cancel_event = threading.Event() + else: + self._cancel_event = cancel_event + if logger is None: + self._logger = logging.getLogger("Dnotify") + self._logger.addFilter(DnotifyNullLoggingFilter()) + else: + self._logger = logger + + def cancelled(self): + return self._cancel_event.isSet() + +class DirectoryNotifierAsyncWrapper(threading.Thread): + def __init__(self, dnotify, queue, logger=None, name=None): + if not name is None: + threading.Thread.__init__(self, name=name) + else: + threading.Thread.__init__(self) + self._eventqueue = queue + self._dnotify = dnotify + if logger is None: + self._logger = logging.getLogger("Dnotify") + self._logger.addFilter(DnotifyNullLoggingFilter()) + else: + self._logger = logger + + def cancel(self): + self._cancel_event.set() + + def run(self): + self._logger.info('Created new thread (%s) for async directory notification' % (self.getName())) + while not self._dnotify.cancelled(): + dir = self._dnotify.poll() + self._eventqueue.put(dir) + self._logger.info('Caught cancel event; async dnotify thread exiting') + +class MtimeDirectoryNotifier(DirectoryNotifier): + def __init__(self, dirs, poll_time, logger, cancel_event=None): + DirectoryNotifier.__init__(self, dirs, logger, cancel_event=cancel_event) + self._changed = [] + self._dirmap = {} + self._polltime = poll_time + for dir in dirs: + self._dirmap[dir] = os.stat(os.path.join(self._cwd, dir))[stat.ST_MTIME] + + def poll(self, timeout=None): + timeout_time = None + if timeout: + timeout_time = time.time() + timeout + while self._changed == []: + if timeout_time and time.time() > timeout_time: + return None + self._logger.debug('Polling...') + for dir in self._dirmap.keys(): + oldtime = self._dirmap[dir] + mtime = os.stat(os.path.join(self._cwd, dir))[stat.ST_MTIME] + if oldtime < mtime: + self._logger.debug('Directory "%s" has changed' % (dir,)) + self._changed.append(dir) + self._dirmap[dir] = mtime + if self._changed == []: + for x in range(self._polltime): + if self._cancel_event.isSet(): + return None + time.sleep(1) + ret = self._changed[0] + self._changed = self._changed[1:] + return ret + +class DnotifyDirectoryNotifier(DirectoryNotifier): + def __init__(self, dirs, logger): + DirectoryNotifier.__init__(self, dirs, logger) + self._queue = Queue.Queue() + dnotify = DnotifyThread(self._queue, self._dirs, self._logger) + dnotify.start() + + def poll(self, timeout=None): + # delete duplicates + i = self._queue.qsize() + self._logger.debug('Queue size: %d', (i,)) + set = {} + while i > 0: + dir = self._queue_get(timeout) + if dir is None: + # We shouldn't have to do this; no one else is reading + # from the queue. But we do it just to be safe. + for key in set.keys(): + self._queue.put(key) + return None + set[dir] = 1 + i -= 1 + for key in set.keys(): + self._queue.put(key) + i = self._queue.qsize() + self._logger.debug('Queue size (after duplicate filter): %d', (i,)) + return self._queue_get(timeout) + + def _queue_get(self, timeout): + if timeout is None: + return self._queue.get() + timeout_time = time.time() + timeout + while 1: + try: + self._queue.get(0) + except Queue.Empty: + if time.time() > timeout_time: + return None + else: + time.sleep(15) + +class DnotifyThread(threading.Thread): + def __init__(self, queue, dirs, logger): + threading.Thread.__init__(self) + self._queue = queue + self._dirs = dirs + self._logger = logger + + def run(self): + self._logger.debug('Starting dnotify reading thread') + (infd, outfd) = os.pipe() + pid = os.fork() + if pid == 0: + os.close(infd) + misc.dup2(outfd, 1) + args = ['dnotify', '-m', '-c', '-d', '-a', '-r'] + list(self._dirs) + ['-e', 'printf', '"{}\\0"'] + os.execv('/usr/bin/dnotify', args) + os.exit(1) + + os.close(outfd) + stdout = os.fdopen(infd) + c = 'x' + while c != '': + curline = '' + c = stdout.read(1) + while c != '' and c != '\0': + curline += c + c = stdout.read(1) + if c == '': + break + self._logger.debug('Directory "%s" changed' % (curline,)) + self._queue.put(curline) + (pid, status) = os.waitpid(pid, 0) + if status is None: + ecode = 0 + else: + ecode = os.WEXITSTATUS(status) + raise DnotifyException("dnotify exited with code %s" % (ecode,)) + +# vim:ts=4:sw=4:et: diff --git a/minidinstall/DpkgControl.py b/minidinstall/DpkgControl.py new file mode 100755 index 0000000..38147c7 --- /dev/null +++ b/minidinstall/DpkgControl.py @@ -0,0 +1,156 @@ +# DpkgControl.py +# +# This module implements control file parsing. +# +# DpkgParagraph is a low-level class, that reads/parses a single paragraph +# from a file object. +# +# DpkgControl uses DpkgParagraph in a loop, pulling out the value of a +# defined key(package), and using that as a key in it's internal +# dictionary. +# +# DpkgSourceControl grabs the first paragraph from the file object, stores +# it in object.source, then passes control to DpkgControl.load, to parse +# the rest of the file. +# +# To test this, pass it a filetype char, a filename, then, optionally, +# the key to a paragraph to display, and if a fourth arg is given, only +# show that field. +# +# Copyright 2001 Adam Heath +# +# This file 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 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import re, string +from DpkgDatalist import * +from minidinstall.SignedFile import * +from types import ListType + +class DpkgParagraph(DpkgOrderedDatalist): + caseSensitive = 0 + trueFieldCasing = {} + + def setCaseSensitive( self, value ): self.caseSensitive = value + + def load( self, f ): + "Paragraph data from a file object." + key = None + value = None + while 1: + line = f.readline() + if not line: + return + # skip blank lines until we reach a paragraph + if line == '\n': + if not self: + continue + else: + return + line = line[ :-1 ] + if line[ 0 ] != ' ': + key, value = string.split( line, ":", 1 ) + if value: value = value[ 1: ] + if not self.caseSensitive: + newkey = string.lower( key ) + if not self.trueFieldCasing.has_key( key ): + self.trueFieldCasing[ newkey ] = key + key = newkey + else: + if isinstance( value, ListType ): + value.append( line[ 1: ] ) + else: + value = [ value, line[ 1: ] ] + self[ key ] = value + + def _storeField( self, f, value, lead = " " ): + if isinstance( value, ListType ): + value = string.join( map( lambda v, lead = lead: v and ( lead + v ) or v, value ), "\n" ) + else: + if value: value = lead + value + f.write( "%s\n" % ( value ) ) + + def _store( self, f ): + "Write our paragraph data to a file object" + for key in self.keys(): + value = self[ key ] + if self.trueFieldCasing.has_key( key ): + key = self.trueFieldCasing[ key ] + f.write( "%s:" % key ) + self._storeField( f, value ) + +class DpkgControl(DpkgOrderedDatalist): + + key = "package" + caseSensitive = 0 + + def setkey( self, key ): self.key = key + def setCaseSensitive( self, value ): self.caseSensitive = value + + def _load_one( self, f ): + p = DpkgParagraph( None ) + p.setCaseSensitive( self.caseSensitive ) + p.load( f ) + return p + + def load( self, f ): + while 1: + p = self._load_one( f ) + if not p: break + self[ p[ self.key ] ] = p + + def _store( self, f ): + "Write our control data to a file object" + + for key in self.keys(): + self[ key ]._store( f ) + f.write( "\n" ) + +class DpkgSourceControl( DpkgControl ): + source = None + + def load( self, f ): + f = SignedFile(f) + self.source = self._load_one( f ) + DpkgControl.load( self, f ) + + def __repr__( self ): + return self.source.__repr__() + "\n" + DpkgControl.__repr__( self ) + + def _store( self, f ): + "Write our control data to a file object" + self.source._store( f ) + f.write( "\n" ) + DpkgControl._store( self, f ) + +if __name__ == "__main__": + import sys + types = { 'p' : DpkgParagraph, 'c' : DpkgControl, 's' : DpkgSourceControl } + type = sys.argv[ 1 ] + if not types.has_key( type ): + print "Unknown type `%s'!" % type + sys.exit( 1 ) + file = open( sys.argv[ 2 ], "r" ) + data = types[ type ]() + data.load( file ) + if len( sys.argv ) > 3: + para = data[ sys.argv[ 3 ] ] + if len( sys.argv ) > 4: + para._storeField( sys.stdout, para[ sys.argv[ 4 ] ], "" ) + else: + para._store( sys.stdout ) + else: + data._store( sys.stdout ) + +# vim:ts=4:sw=4:et: diff --git a/minidinstall/DpkgDatalist.py b/minidinstall/DpkgDatalist.py new file mode 100644 index 0000000..0c11612 --- /dev/null +++ b/minidinstall/DpkgDatalist.py @@ -0,0 +1,81 @@ +# DpkgDatalist.py +# +# This module implements DpkgDatalist, an abstract class for storing +# a list of objects in a file. Children of this class have to implement +# the load and _store methods. +# +# Copyright 2001 Wichert Akkerman +# +# This file 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 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import os, sys +from UserDict import UserDict +from OrderedDict import OrderedDict +from minidinstall.SafeWriteFile import SafeWriteFile +from types import StringType + +class DpkgDatalistException(Exception): + UNKNOWN = 0 + SYNTAXERROR = 1 + + def __init__(self, message="", reason=UNKNOWN, file=None, line=None): + self.message=message + self.reason=reason + self.filename=file + self.line=line + +class _DpkgDatalist: + def __init__(self, fn=""): + '''Initialize a DpkgDatalist object. An optional argument is a + file from which we load values.''' + + self.filename=fn + if self.filename: + self.load(self.filename) + + def store(self, fn=None): + "Store variable data in a file." + + if fn==None: + fn=self.filename + # Special case for writing to stdout + if not fn: + self._store(sys.stdout) + return + + # Write to a temporary file first + if type(fn) == StringType: + vf=SafeWriteFile(fn+".new", fn, "w") + else: + vf=fn + try: + self._store(vf) + finally: + if type(fn) == StringType: + vf.close() + + +class DpkgDatalist(UserDict, _DpkgDatalist): + def __init__(self, fn=""): + UserDict.__init__(self) + _DpkgDatalist.__init__(self, fn) + + +class DpkgOrderedDatalist(OrderedDict, _DpkgDatalist): + def __init__(self, fn=""): + OrderedDict.__init__(self) + _DpkgDatalist.__init__(self, fn) + +# vim:ts=4:sw=4:et: diff --git a/minidinstall/GPGSigVerifier.py b/minidinstall/GPGSigVerifier.py new file mode 100644 index 0000000..78aeebb --- /dev/null +++ b/minidinstall/GPGSigVerifier.py @@ -0,0 +1,78 @@ +# GPGSigVerifier -*- mode: python; coding: utf-8 -*- + +# A class for verifying signed files + +# Copyright © 2002 Colin Walters + +# This file 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 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import os, re, sys, string, stat +from minidinstall import misc + +class GPGSigVerifierException(Exception): + def __init__(self, value): + self._value = value + def __str__(self): + return `self._value` + +class GPGSigVerificationFailure(Exception): + def __init__(self, value, output): + self._value = value + self._output = output + def __str__(self): + return `self._value` + + def getOutput(self): + return self._output + +class GPGSigVerifier: + def __init__(self, keyrings, gpgv=None): + self._keyrings = keyrings + if gpgv is None: + gpgv = '/usr/bin/gpgv' + if not os.access(gpgv, os.X_OK): + raise GPGSigVerifierException("Couldn't execute \"%s\"" % (gpgv,)) + self._gpgv = gpgv + + def verify(self, filename, sigfilename=None): + (stdin, stdout) = os.pipe() + pid = os.fork() + if pid == 0: + os.close(stdin) + misc.dup2(stdout, 1) + misc.dup2(stdout, 2) + args = [] + for keyring in self._keyrings: + args.append('--keyring') + args.append(keyring) + if sigfilename: + args.append(sigfilename) + args = [self._gpgv] + args + [filename] + os.execv(self._gpgv, args) + os.exit(1) + os.close(stdout) + output = os.fdopen(stdin).readlines() + (pid, status) = os.waitpid(pid, 0) + if not (status is None or (os.WIFEXITED(status) and os.WEXITSTATUS(status) == 0)): + if os.WIFEXITED(status): + msg = "gpgv exited with error code %d" % (os.WEXITSTATUS(status),) + elif os.WIFSTOPPED(status): + msg = "gpgv stopped unexpectedly with signal %d" % (os.WSTOPSIG(status),) + elif os.WIFSIGNALED(status): + msg = "gpgv died with signal %d" % (os.WTERMSIG(status),) + raise GPGSigVerificationFailure(msg, output) + return output + +# vim:ts=4:sw=4:et: diff --git a/minidinstall/OrderedDict.py b/minidinstall/OrderedDict.py new file mode 100644 index 0000000..fa3f276 --- /dev/null +++ b/minidinstall/OrderedDict.py @@ -0,0 +1,76 @@ +# OrderedDict.py +# +# This class functions almost exactly like UserDict. However, when using +# the sequence methods, it returns items in the same order in which they +# were added, instead of some random order. +# +# Copyright 2001 Adam Heath +# +# This file 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 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +from UserDict import UserDict + +class OrderedDict(UserDict): + __order=[] + + def __init__(self, dict=None): + UserDict.__init__(self) + self.__order=[] + if dict is not None and dict.__class__ is not None: + self.update(dict) + + def __cmp__(self, dict): + if isinstance(dict, OrderedDict): + ret=cmp(self.__order, dict.__order) + if not ret: + ret=UserDict.__cmp__(self, dict) + return ret + else: + return UserDict.__cmp__(self, dict) + + def __setitem__(self, key, value): + if not self.has_key(key): + self.__order.append(key) + UserDict.__setitem__(self, key, value) + + def __delitem__(self, key): + if self.has_key(key): + del self.__order[self.__order.index(key)] + UserDict.__delitem__(self, key) + + def clear(self): + self.__order=[] + UserDict.clear(self) + + def copy(self): + if self.__class__ is OrderedDict: + return OrderedDict(self) + import copy + return copy.copy(self) + + def keys(self): + return self.__order + + def items(self): + return map(lambda x, self=self: (x, self.__getitem__(x)), self.__order) + + def values(self): + return map(lambda x, self=self: self.__getitem__(x), self.__order) + + def update(self, dict): + for k, v in dict.items(): + self.__setitem__(k, v) + +# vim:ts=4:sw=4:et: diff --git a/minidinstall/SafeWriteFile.py b/minidinstall/SafeWriteFile.py new file mode 100755 index 0000000..1777d36 --- /dev/null +++ b/minidinstall/SafeWriteFile.py @@ -0,0 +1,81 @@ +# SafeWriteFile.py +# +# This file is a writable file object. It writes to a specified newname, +# and when closed, renames the file to the realname. If the object is +# deleted, without being closed, this rename isn't done. If abort() is +# called, it also disables the rename. +# +# Copyright 2001 Adam Heath +# +# This file 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 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +from types import StringType +from shutil import copy2 +from string import find +from os import rename + +class ObjectNotAllowed(Exception): + pass + + +class InvalidMode(Exception): + pass + + +class SafeWriteFile: + def __init__(self, newname, realname, mode="w", bufsize=-1): + + if type(newname)!=StringType: + raise ObjectNotAllowed(newname) + if type(realname)!=StringType: + raise ObjectNotAllowed(realname) + + if find(mode, "r")>=0: + raise InvalidMode(mode) + if find(mode, "a")>=0 or find(mode, "+") >= 0: + copy2(realname, newname) + self.fobj=open(newname, mode, bufsize) + self.newname=newname + self.realname=realname + self.__abort=0 + + def close(self): + self.fobj.close() + if not (self.closed and self.__abort): + rename(self.newname, self.realname) + + def abort(self): + self.__abort=1 + + def __del__(self): + self.abort() + del self.fobj + + def __getattr__(self, attr): + try: + return self.__dict__[attr] + except: + return eval("self.fobj." + attr) + + +if __name__ == "__main__": + import time + f=SafeWriteFile("sf.new", "sf.data") + f.write("test\n") + f.flush() + time.sleep(1) + f.close() + +# vim:ts=4:sw=4:et: diff --git a/minidinstall/SignedFile.py b/minidinstall/SignedFile.py new file mode 100755 index 0000000..648186e --- /dev/null +++ b/minidinstall/SignedFile.py @@ -0,0 +1,107 @@ +# SignedFile -*- mode: python; coding: utf-8 -*- + +# SignedFile offers a subset of file object operations, and is +# designed to transparently handle files with PGP signatures. + +# Copyright © 2002 Colin Walters +# +# This file 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 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import re,string + +class SignedFile: + _stream = None + _eof = 0 + _signed = 0 + _signature = None + _signatureversion = None + _initline = None + def __init__(self, stream): + self._stream = stream + line = stream.readline() + if (line == "-----BEGIN PGP SIGNED MESSAGE-----\n"): + self._signed = 1 + while (1): + line = stream.readline() + if (len(line) == 0 or line == '\n'): + break + else: + self._initline = line + + def readline(self): + if self._eof: + return '' + if self._initline: + line = self._initline + self._initline = None + else: + line = self._stream.readline() + if not self._signed: + return line + elif line == "-----BEGIN PGP SIGNATURE-----\n": + self._eof = 1 + self._signature = [] + self._signatureversion = self._stream.readline() + self._stream.readline() # skip blank line + while 1: + line = self._stream.readline() + if len(line) == 0 or line == "-----END PGP SIGNATURE-----\n": + break + self._signature.append(line) + self._signature = string.join + return '' + return line + + def readlines(self): + ret = [] + while 1: + line = self.readline() + if (line != ''): + ret.append(line) + else: + break + return ret + + def close(self): + self._stream.close() + + def getSigned(self): + return self._signed + + def getSignature(self): + return self._signature + + def getSignatureVersion(self): + return self._signatureversion + +if __name__=="__main__": + import sys + if len(sys.argv) == 0: + print "Need one file as an argument" + sys.exit(1) + filename = sys.argv[1] + f=SignedFile(open(filename)) + if f.getSigned(): + print "**** SIGNED ****" + else: + print "**** NOT SIGNED ****" + lines=f.readlines() + print lines + if not f.getSigned(): + assert(len(lines) == len(actuallines)) + else: + print "Signature: %s" % (f.getSignature()) + +# vim:ts=4:sw=4:et: diff --git a/minidinstall/__init__.py b/minidinstall/__init__.py new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/minidinstall/__init__.py @@ -0,0 +1 @@ + diff --git a/minidinstall/misc.py b/minidinstall/misc.py new file mode 100644 index 0000000..eb52d00 --- /dev/null +++ b/minidinstall/misc.py @@ -0,0 +1,36 @@ +# misc -*- mode: python; coding: utf-8 -*- + +# misc tools for mini-dinstall + +# Copyright © 2004 Thomas Viehmann + +# This file 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 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +import os, errno, time + +def dup2(fd,fd2): + # dup2 with EBUSY retries (cf. dup2(2) and Debian bug #265513) + success = 0 + tries = 0 + while (not success): + try: + os.dup2(fd,fd2) + success = 1 + except OSError, e: + if (e.errno != errno.EBUSY) or (tries >= 3): + raise + # wait 0-2 seconds befor next try + time.sleep(tries) + tries += 1 diff --git a/minidinstall/version.py b/minidinstall/version.py new file mode 100644 index 0000000..545a8ba --- /dev/null +++ b/minidinstall/version.py @@ -0,0 +1 @@ +pkg_version = "0.6.22" diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..13d80a5 --- /dev/null +++ b/setup.py @@ -0,0 +1,33 @@ +#!/usr/bin/python +# +# setup.py - [ install script for mini-dinstall ] +# Copyright (c) 2007 Christoph Goehre +# +# This file is part of the mini-dinstall package. +# +# 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 2 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. +# + +from distutils.core import setup + +setup(name = 'mini-dinstall', + author = 'Colin Walters', + author_email = 'walters@debian.org', + maintainer = 'Christoph Goehre', + maintainer_email = 'christoph.goehre@gmx.de', + scripts = [ 'mini-dinstall' ], + packages = [ 'minidinstall' ], + url = 'http://alioth.debian.org/projects/mini-dinstall/', +) -- cgit v1.2.3