diff options
author | Guido Guenther <agx@sigxcpu.org> | 2007-08-23 15:47:20 +0200 |
---|---|---|
committer | Guido Guenther <agx@bogon.sigxcpu.org> | 2007-08-23 15:47:20 +0200 |
commit | e5f5a2ab23ace19c6de39512f76f8fed5f5ad912 (patch) | |
tree | a3a2a1ba762515d37acaf85d9e5c27f81a3ab6c1 /lib |
Imported upstream version 0.6.21upstream/0.6.21debian/0.6.21-0.1upstream
Diffstat (limited to 'lib')
-rw-r--r-- | lib/ChangeFile.py | 109 | ||||
-rw-r--r-- | lib/DebianSigVerifier.py | 35 | ||||
-rw-r--r-- | lib/Dnotify.py | 199 | ||||
-rwxr-xr-x | lib/DpkgControl.py | 156 | ||||
-rw-r--r-- | lib/DpkgDatalist.py | 81 | ||||
-rw-r--r-- | lib/GPGSigVerifier.py | 78 | ||||
-rw-r--r-- | lib/OrderedDict.py | 76 | ||||
-rwxr-xr-x | lib/SafeWriteFile.py | 81 | ||||
-rwxr-xr-x | lib/SignedFile.py | 107 | ||||
-rw-r--r-- | lib/__init__.py | 1 | ||||
-rw-r--r-- | lib/misc.py | 36 | ||||
-rw-r--r-- | lib/version.py | 1 |
12 files changed, 960 insertions, 0 deletions
diff --git a/lib/ChangeFile.py b/lib/ChangeFile.py new file mode 100644 index 0000000..b74e623 --- /dev/null +++ b/lib/ChangeFile.py @@ -0,0 +1,109 @@ +# ChangeFile + +# A class which represents a Debian change file. + +# Copyright 2002 Colin Walters <walters@gnu.org> + +# 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 new file mode 100644 index 0000000..d441a58 --- /dev/null +++ b/lib/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 <walters@gnu.org> + +# 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 new file mode 100644 index 0000000..122e03c --- /dev/null +++ b/lib/Dnotify.py @@ -0,0 +1,199 @@ +# Dnotify -*- mode: python; coding: utf-8 -*- + +# A simple FAM-like beast in Python + +# Copyright © 2002 Colin Walters <walters@gnu.org> + +# 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 new file mode 100755 index 0000000..38147c7 --- /dev/null +++ b/lib/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 <doogie@debian.org> +# +# 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 new file mode 100644 index 0000000..0c11612 --- /dev/null +++ b/lib/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 <wichert@linux.com> +# +# 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 new file mode 100644 index 0000000..78aeebb --- /dev/null +++ b/lib/GPGSigVerifier.py @@ -0,0 +1,78 @@ +# GPGSigVerifier -*- mode: python; coding: utf-8 -*- + +# A class for verifying signed files + +# Copyright © 2002 Colin Walters <walters@gnu.org> + +# 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 new file mode 100644 index 0000000..fa3f276 --- /dev/null +++ b/lib/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 <doogie@debian.org> +# +# 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 new file mode 100755 index 0000000..1777d36 --- /dev/null +++ b/lib/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 <doogie@debian.org> +# +# 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 new file mode 100755 index 0000000..648186e --- /dev/null +++ b/lib/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 <walters@gnu.org> +# +# 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 new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/lib/__init__.py @@ -0,0 +1 @@ + diff --git a/lib/misc.py b/lib/misc.py new file mode 100644 index 0000000..eb52d00 --- /dev/null +++ b/lib/misc.py @@ -0,0 +1,36 @@ +# misc -*- mode: python; coding: utf-8 -*- + +# misc tools for mini-dinstall + +# Copyright © 2004 Thomas Viehmann <tv@beamnet.de> + +# 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 new file mode 100644 index 0000000..316c536 --- /dev/null +++ b/lib/version.py @@ -0,0 +1 @@ +pkg_version = "0.6.21-0.1" |