]> git.decadent.org.uk Git - dak.git/blobdiff - daklib/utils.py
Merge branch 'psycopg2' into content_generation
[dak.git] / daklib / utils.py
index 52b902f9ffe84e298fce9fb4c127b935b5376f6a..5e3627969f9b1f69cd8a216afe3b4dd49a5495c3 100755 (executable)
@@ -1,10 +1,12 @@
 #!/usr/bin/env python
 # vim:set et ts=4 sw=4:
 
 #!/usr/bin/env python
 # vim:set et ts=4 sw=4:
 
-# Utility functions
-# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
+"""Utility functions
 
 
-################################################################################
+@contact: Debian FTP Master <ftpmaster@debian.org>
+@copyright: 2000, 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
+@license: GNU General Public License version 2 or later
+"""
 
 # 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
 
 # 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
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
 # along with this program; if not, write to the Free Software
 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
-################################################################################
-
-import codecs, commands, email.Header, os, pwd, re, select, socket, shutil, \
-       sys, tempfile, traceback, stat
+import codecs
+import commands
+import email.Header
+import os
+import pwd
+import select
+import socket
+import shutil
+import sys
+import tempfile
+import traceback
+import stat
 import apt_pkg
 import database
 import time
 from dak_exceptions import *
 import apt_pkg
 import database
 import time
 from dak_exceptions import *
+from regexes import re_html_escaping, html_escaping, re_single_line_field, \
+                    re_multi_line_field, re_srchasver, re_verwithext, \
+                    re_parse_maintainer, re_taint_free, re_gpg_uid
 
 ################################################################################
 
 
 ################################################################################
 
-re_comments = re.compile(r"\#.*")
-re_no_epoch = re.compile(r"^\d+\:")
-re_no_revision = re.compile(r"-[^-]+$")
-re_arch_from_filename = re.compile(r"/binary-[^/]+/")
-re_extract_src_version = re.compile (r"(\S+)\s*\((.*)\)")
-re_isadeb = re.compile (r"(.+?)_(.+?)_(.+)\.u?deb$")
-re_issource = re.compile (r"(.+)_(.+?)\.(orig\.tar\.gz|diff\.gz|tar\.gz|dsc)$")
-
-re_single_line_field = re.compile(r"^(\S*)\s*:\s*(.*)")
-re_multi_line_field = re.compile(r"^\s(.*)")
-re_taint_free = re.compile(r"^[-+~/\.\w]+$")
-
-re_parse_maintainer = re.compile(r"^\s*(\S.*\S)\s*\<([^\>]+)\>")
-re_gpg_uid = re.compile('^uid.*<([^>]*)>')
-
-re_srchasver = re.compile(r"^(\S+)\s+\((\S+)\)$")
-re_verwithext = re.compile(r"^(\d+)(?:\.(\d+))(?:\s+\((\S+)\))?$")
-
-re_srchasver = re.compile(r"^(\S+)\s+\((\S+)\)$")
-
-html_escaping = {'"':'&quot;', '&':'&amp;', '<':'&lt;', '>':'&gt;'}
-re_html_escaping = re.compile('|'.join(map(re.escape, html_escaping.keys())))
+default_config = "/etc/dak/dak.conf"     #: default dak config, defines host properties
+default_apt_config = "/etc/dak/apt.conf" #: default apt config, not normally used
 
 
-default_config = "/etc/dak/dak.conf"
-default_apt_config = "/etc/dak/apt.conf"
-
-alias_cache = None
-key_uid_email_cache = {}
+alias_cache = None        #: Cache for email alias checks
+key_uid_email_cache = {}  #: Cache for email addresses from gpg key uids
 
 # (hashname, function, earliest_changes_version)
 known_hashes = [("sha1", apt_pkg.sha1sum, (1, 8)),
 
 # (hashname, function, earliest_changes_version)
 known_hashes = [("sha1", apt_pkg.sha1sum, (1, 8)),
-                ("sha256", apt_pkg.sha256sum, (1, 8))]
+                ("sha256", apt_pkg.sha256sum, (1, 8))] #: hashes we accept for entries in .changes/.dsc
 
 ################################################################################
 
 def html_escape(s):
 
 ################################################################################
 
 def html_escape(s):
+    """ Escape html chars """
     return re_html_escaping.sub(lambda x: html_escaping.get(x.group(0)), s)
 
 ################################################################################
 
 def open_file(filename, mode='r'):
     return re_html_escaping.sub(lambda x: html_escaping.get(x.group(0)), s)
 
 ################################################################################
 
 def open_file(filename, mode='r'):
+    """
+    Open C{file}, return fileobject.
+
+    @type filename: string
+    @param filename: path/filename to open
+
+    @type mode: string
+    @param mode: open mode
+
+    @rtype: fileobject
+    @return: open fileobject
+
+    @raise CantOpenError: If IOError is raised by open, reraise it as CantOpenError.
+
+    """
     try:
         f = open(filename, mode)
     except IOError:
     try:
         f = open(filename, mode)
     except IOError:
@@ -201,25 +207,26 @@ def parse_deb822(contents, signing_rules=0):
 ################################################################################
 
 def parse_changes(filename, signing_rules=0):
 ################################################################################
 
 def parse_changes(filename, signing_rules=0):
-    """Parses a changes file and returns a dictionary where each field is a
-key.  The mandatory first argument is the filename of the .changes
-file.
+    """
+    Parses a changes file and returns a dictionary where each field is a
+    key.  The mandatory first argument is the filename of the .changes
+    file.
 
 
-signing_rules is an optional argument:
+    signing_rules is an optional argument:
 
 
o If signing_rules == -1, no signature is required.
o If signing_rules == 0 (the default), a signature is required.
o If signing_rules == 1, it turns on the same strict format checking
-   as dpkg-source.
     - If signing_rules == -1, no signature is required.
     - If signing_rules == 0 (the default), a signature is required.
     - If signing_rules == 1, it turns on the same strict format checking
+        as dpkg-source.
 
 
-The rules for (signing_rules == 1)-mode are:
+    The rules for (signing_rules == 1)-mode are:
 
 
-  o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
-    followed by any PGP header data and must end with a blank line.
+      - The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
+        followed by any PGP header data and must end with a blank line.
 
 
-  o The data section must end with a blank line and must be followed by
-    "-----BEGIN PGP SIGNATURE-----".
-"""
+      - The data section must end with a blank line and must be followed by
+        "-----BEGIN PGP SIGNATURE-----".
+    """
 
     changes_in = open_file(filename)
     content = changes_in.read()
 
     changes_in = open_file(filename)
     content = changes_in.read()
@@ -234,9 +241,11 @@ def hash_key(hashname):
 ################################################################################
 
 def create_hash(where, files, hashname, hashfunc):
 ################################################################################
 
 def create_hash(where, files, hashname, hashfunc):
-    """create_hash extends the passed files dict with the given hash by
+    """
+    create_hash extends the passed files dict with the given hash by
     iterating over all files on disk and passing them to the hashing
     iterating over all files on disk and passing them to the hashing
-    function given."""
+    function given.
+    """
 
     rejmsg = []
     for f in files.keys():
 
     rejmsg = []
     for f in files.keys():
@@ -253,9 +262,11 @@ def create_hash(where, files, hashname, hashfunc):
 ################################################################################
 
 def check_hash(where, files, hashname, hashfunc):
 ################################################################################
 
 def check_hash(where, files, hashname, hashfunc):
-    """check_hash checks the given hash in the files dict against the actual
+    """
+    check_hash checks the given hash in the files dict against the actual
     files on disk.  The hash values need to be present consistently in
     files on disk.  The hash values need to be present consistently in
-    all file entries.  It does not modify its input in any way."""
+    all file entries.  It does not modify its input in any way.
+    """
 
     rejmsg = []
     for f in files.keys():
 
     rejmsg = []
     for f in files.keys():
@@ -286,8 +297,10 @@ def check_hash(where, files, hashname, hashfunc):
 ################################################################################
 
 def check_size(where, files):
 ################################################################################
 
 def check_size(where, files):
-    """check_size checks the file sizes in the passed files dict against the
-    files on disk."""
+    """
+    check_size checks the file sizes in the passed files dict against the
+    files on disk.
+    """
 
     rejmsg = []
     for f in files.keys():
 
     rejmsg = []
     for f in files.keys():
@@ -309,8 +322,10 @@ def check_size(where, files):
 ################################################################################
 
 def check_hash_fields(what, manifest):
 ################################################################################
 
 def check_hash_fields(what, manifest):
-    """check_hash_fields ensures that there are no checksum fields in the
-    given dict that we do not know about."""
+    """
+    check_hash_fields ensures that there are no checksum fields in the
+    given dict that we do not know about.
+    """
 
     rejmsg = []
     hashes = map(lambda x: x[0], known_hashes)
 
     rejmsg = []
     hashes = map(lambda x: x[0], known_hashes)
@@ -343,9 +358,11 @@ def _ensure_changes_hash(changes, format, version, files, hashname, hashfunc):
 # access the checksums easily.
 
 def _ensure_dsc_hash(dsc, dsc_files, hashname, hashfunc):
 # access the checksums easily.
 
 def _ensure_dsc_hash(dsc, dsc_files, hashname, hashfunc):
-    """ensure_dsc_hashes' task is to ensure that each and every *present* hash
+    """
+    ensure_dsc_hashes' task is to ensure that each and every *present* hash
     in the dsc is correct, i.e. identical to the changes file and if necessary
     in the dsc is correct, i.e. identical to the changes file and if necessary
-    the pool.  The latter task is delegated to check_hash."""
+    the pool.  The latter task is delegated to check_hash.
+    """
 
     rejmsg = []
     if not dsc.has_key('Checksums-%s' % (hashname,)):
 
     rejmsg = []
     if not dsc.has_key('Checksums-%s' % (hashname,)):
@@ -398,25 +415,24 @@ def parse_checksums(where, files, manifest, hashname):
     field = 'checksums-%s' % hashname
     if not field in manifest:
         return rejmsg
     field = 'checksums-%s' % hashname
     if not field in manifest:
         return rejmsg
-    input = manifest[field]
-    for line in input.split('\n'):
+    for line in manifest[field].split('\n'):
         if not line:
             break
         if not line:
             break
-        hash, size, file = line.strip().split(' ')
-        if not files.has_key(file):
+        checksum, size, checkfile = line.strip().split(' ')
+        if not files.has_key(checkfile):
         # TODO: check for the file's entry in the original files dict, not
         # the one modified by (auto)byhand and other weird stuff
         #    rejmsg.append("%s: not present in files but in checksums-%s in %s" %
         #        (file, hashname, where))
             continue
         # TODO: check for the file's entry in the original files dict, not
         # the one modified by (auto)byhand and other weird stuff
         #    rejmsg.append("%s: not present in files but in checksums-%s in %s" %
         #        (file, hashname, where))
             continue
-        if not files[file]["size"] == size:
+        if not files[checkfile]["size"] == size:
             rejmsg.append("%s: size differs for files and checksums-%s entry "\
             rejmsg.append("%s: size differs for files and checksums-%s entry "\
-                "in %s" % (file, hashname, where))
+                "in %s" % (checkfile, hashname, where))
             continue
             continue
-        files[file][hash_key(hashname)] = hash
+        files[checkfile][hash_key(hashname)] = checksum
     for f in files.keys():
         if not files[f].has_key(hash_key(hashname)):
     for f in files.keys():
         if not files[f].has_key(hash_key(hashname)):
-            rejmsg.append("%s: no entry in checksums-%s in %s" % (file,
+            rejmsg.append("%s: no entry in checksums-%s in %s" % (checkfile,
                 hashname, where))
     return rejmsg
 
                 hashname, where))
     return rejmsg
 
@@ -488,8 +504,10 @@ def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
 ################################################################################
 
 def force_to_utf8(s):
 ################################################################################
 
 def force_to_utf8(s):
-    """Forces a string to UTF-8.  If the string isn't already UTF-8,
-it's assumed to be ISO-8859-1."""
+    """
+    Forces a string to UTF-8.  If the string isn't already UTF-8,
+    it's assumed to be ISO-8859-1.
+    """
     try:
         unicode(s, 'utf-8')
         return s
     try:
         unicode(s, 'utf-8')
         return s
@@ -498,8 +516,10 @@ it's assumed to be ISO-8859-1."""
         return latin1_s.encode('utf-8')
 
 def rfc2047_encode(s):
         return latin1_s.encode('utf-8')
 
 def rfc2047_encode(s):
-    """Encodes a (header) string per RFC2047 if necessary.  If the
-string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
+    """
+    Encodes a (header) string per RFC2047 if necessary.  If the
+    string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1.
+    """
     try:
         codecs.lookup('ascii')[1](s)
         return s
     try:
         codecs.lookup('ascii')[1](s)
         return s
@@ -520,15 +540,18 @@ string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
 #          incompatible!'
 
 def fix_maintainer (maintainer):
 #          incompatible!'
 
 def fix_maintainer (maintainer):
-    """Parses a Maintainer or Changed-By field and returns:
-  (1) an RFC822 compatible version,
-  (2) an RFC2047 compatible version,
-  (3) the name
-  (4) the email
-
-The name is forced to UTF-8 for both (1) and (3).  If the name field
-contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
-switched to 'email (name)' format."""
+    """
+    Parses a Maintainer or Changed-By field and returns:
+      1. an RFC822 compatible version,
+      2. an RFC2047 compatible version,
+      3. the name
+      4. the email
+
+    The name is forced to UTF-8 for both 1. and 3..  If the name field
+    contains '.' or ',' (as allowed by Debian policy), 1. and 2. are
+    switched to 'email (name)' format.
+
+    """
     maintainer = maintainer.strip()
     if not maintainer:
         return ('', '', '', '')
     maintainer = maintainer.strip()
     if not maintainer:
         return ('', '', '', '')
@@ -566,12 +589,12 @@ switched to 'email (name)' format."""
 
 ################################################################################
 
 
 ################################################################################
 
-# sendmail wrapper, takes _either_ a message string or a file as arguments
 def send_mail (message, filename=""):
 def send_mail (message, filename=""):
-        # If we've been passed a string dump it into a temporary file
+    """sendmail wrapper, takes _either_ a message string or a file as arguments"""
+
+    # If we've been passed a string dump it into a temporary file
     if message:
     if message:
-        filename = tempfile.mktemp()
-        fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
+        (fd, filename) = tempfile.mkstemp()
         os.write (fd, message)
         os.close (fd)
 
         os.write (fd, message)
         os.close (fd)
 
@@ -685,13 +708,13 @@ def regex_safe (s):
 
 ################################################################################
 
 
 ################################################################################
 
-# Perform a substition of template
 def TemplateSubst(map, filename):
 def TemplateSubst(map, filename):
-    file = open_file(filename)
-    template = file.read()
+    """ Perform a substition of template """
+    templatefile = open_file(filename)
+    template = templatefile.read()
     for x in map.keys():
         template = template.replace(x,map[x])
     for x in map.keys():
         template = template.replace(x,map[x])
-    file.close()
+    templatefile.close()
     return template
 
 ################################################################################
     return template
 
 ################################################################################
@@ -732,8 +755,8 @@ def cc_fix_changes (changes):
     for j in o.split():
         changes["architecture"][j] = 1
 
     for j in o.split():
         changes["architecture"][j] = 1
 
-# Sort by source name, source version, 'have source', and then by filename
 def changes_compare (a, b):
 def changes_compare (a, b):
+    """ Sort by source name, source version, 'have source', and then by filename """
     try:
         a_changes = parse_changes(a)
     except:
     try:
         a_changes = parse_changes(a)
     except:
@@ -787,13 +810,13 @@ def find_next_free (dest, too_many=100):
 ################################################################################
 
 def result_join (original, sep = '\t'):
 ################################################################################
 
 def result_join (original, sep = '\t'):
-    list = []
+    resultlist = []
     for i in xrange(len(original)):
         if original[i] == None:
     for i in xrange(len(original)):
         if original[i] == None:
-            list.append("")
+            resultlist.append("")
         else:
         else:
-            list.append(original[i])
-    return sep.join(list)
+            resultlist.append(original[i])
+    return sep.join(resultlist)
 
 ################################################################################
 
 
 ################################################################################
 
@@ -811,18 +834,20 @@ def prefix_multi_line_string(str, prefix, include_blank_lines=0):
 ################################################################################
 
 def validate_changes_file_arg(filename, require_changes=1):
 ################################################################################
 
 def validate_changes_file_arg(filename, require_changes=1):
-    """'filename' is either a .changes or .dak file.  If 'filename' is a
-.dak file, it's changed to be the corresponding .changes file.  The
-function then checks if the .changes file a) exists and b) is
-readable and returns the .changes filename if so.  If there's a
-problem, the next action depends on the option 'require_changes'
-argument:
-
- o If 'require_changes' == -1, errors are ignored and the .changes
-                               filename is returned.
- o If 'require_changes' == 0, a warning is given and 'None' is returned.
- o If 'require_changes' == 1, a fatal error is raised.
-"""
+    """
+    'filename' is either a .changes or .dak file.  If 'filename' is a
+    .dak file, it's changed to be the corresponding .changes file.  The
+    function then checks if the .changes file a) exists and b) is
+    readable and returns the .changes filename if so.  If there's a
+    problem, the next action depends on the option 'require_changes'
+    argument:
+
+      - If 'require_changes' == -1, errors are ignored and the .changes
+        filename is returned.
+      - If 'require_changes' == 0, a warning is given and 'None' is returned.
+      - If 'require_changes' == 1, a fatal error is raised.
+
+    """
     error = None
 
     orig_filename = filename
     error = None
 
     orig_filename = filename
@@ -881,8 +906,8 @@ def get_conf():
 
 ################################################################################
 
 
 ################################################################################
 
-# Handle -a, -c and -s arguments; returns them as SQL constraints
 def parse_args(Options):
 def parse_args(Options):
+    """ Handle -a, -c and -s arguments; returns them as SQL constraints """
     # Process suite
     if Options["Suite"]:
         suite_ids_list = []
     # Process suite
     if Options["Suite"]:
         suite_ids_list = []
@@ -978,10 +1003,13 @@ def try_with_debug(function):
 
 ################################################################################
 
 
 ################################################################################
 
-# Function for use in sorting lists of architectures.
-# Sorts normally except that 'source' dominates all others.
-
 def arch_compare_sw (a, b):
 def arch_compare_sw (a, b):
+    """
+    Function for use in sorting lists of architectures.
+
+    Sorts normally except that 'source' dominates all others.
+    """
+
     if a == "source" and b == "source":
         return 0
     elif a == "source":
     if a == "source" and b == "source":
         return 0
     elif a == "source":
@@ -993,13 +1021,15 @@ def arch_compare_sw (a, b):
 
 ################################################################################
 
 
 ################################################################################
 
-# Split command line arguments which can be separated by either commas
-# or whitespace.  If dwim is set, it will complain about string ending
-# in comma since this usually means someone did 'dak ls -a i386, m68k
-# foo' or something and the inevitable confusion resulting from 'm68k'
-# being treated as an argument is undesirable.
-
 def split_args (s, dwim=1):
 def split_args (s, dwim=1):
+    """
+    Split command line arguments which can be separated by either commas
+    or whitespace.  If dwim is set, it will complain about string ending
+    in comma since this usually means someone did 'dak ls -a i386, m68k
+    foo' or something and the inevitable confusion resulting from 'm68k'
+    being treated as an argument is undesirable.
+    """
+
     if s.find(",") == -1:
         return s.split()
     else:
     if s.find(",") == -1:
         return s.split()
     else:
@@ -1013,9 +1043,12 @@ def Dict(**dict): return dict
 
 ########################################
 
 
 ########################################
 
-# Our very own version of commands.getouputstatus(), hacked to support
-# gpgv's status fd.
 def gpgv_get_status_output(cmd, status_read, status_write):
 def gpgv_get_status_output(cmd, status_read, status_write):
+    """
+    Our very own version of commands.getouputstatus(), hacked to support
+    gpgv's status fd.
+    """
+
     cmd = ['/bin/sh', '-c', cmd]
     p2cread, p2cwrite = os.pipe()
     c2pread, c2pwrite = os.pipe()
     cmd = ['/bin/sh', '-c', cmd]
     p2cread, p2cwrite = os.pipe()
     c2pread, c2pwrite = os.pipe()
@@ -1105,9 +1138,11 @@ def process_gpgv_output(status):
 ################################################################################
 
 def retrieve_key (filename, keyserver=None, keyring=None):
 ################################################################################
 
 def retrieve_key (filename, keyserver=None, keyring=None):
-    """Retrieve the key that signed 'filename' from 'keyserver' and
-add it to 'keyring'.  Returns nothing on success, or an error message
-on error."""
+    """
+    Retrieve the key that signed 'filename' from 'keyserver' and
+    add it to 'keyring'.  Returns nothing on success, or an error message
+    on error.
+    """
 
     # Defaults for keyserver and keyring
     if not keyserver:
 
     # Defaults for keyserver and keyring
     if not keyserver:
@@ -1120,7 +1155,7 @@ on error."""
         return "%s: tainted filename" % (filename)
 
     # Invoke gpgv on the file
         return "%s: tainted filename" % (filename)
 
     # Invoke gpgv on the file
-    status_read, status_write = os.pipe();
+    status_read, status_write = os.pipe()
     cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
     (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
 
     cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
     (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
 
@@ -1157,18 +1192,20 @@ def gpg_keyring_args(keyrings=None):
 ################################################################################
 
 def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
 ################################################################################
 
 def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
-    """Check the signature of a file and return the fingerprint if the
-signature is valid or 'None' if it's not.  The first argument is the
-filename whose signature should be checked.  The second argument is a
-reject function and is called when an error is found.  The reject()
-function must allow for two arguments: the first is the error message,
-the second is an optional prefix string.  It's possible for reject()
-to be called more than once during an invocation of check_signature().
-The third argument is optional and is the name of the files the
-detached signature applies to.  The fourth argument is optional and is
-a *list* of keyrings to use.  'autofetch' can either be None, True or
-False.  If None, the default behaviour specified in the config will be
-used."""
+    """
+    Check the signature of a file and return the fingerprint if the
+    signature is valid or 'None' if it's not.  The first argument is the
+    filename whose signature should be checked.  The second argument is a
+    reject function and is called when an error is found.  The reject()
+    function must allow for two arguments: the first is the error message,
+    the second is an optional prefix string.  It's possible for reject()
+    to be called more than once during an invocation of check_signature().
+    The third argument is optional and is the name of the files the
+    detached signature applies to.  The fourth argument is optional and is
+    a *list* of keyrings to use.  'autofetch' can either be None, True or
+    False.  If None, the default behaviour specified in the config will be
+    used.
+    """
 
     # Ensure the filename contains no shell meta-characters or other badness
     if not re_taint_free.match(sig_filename):
 
     # Ensure the filename contains no shell meta-characters or other badness
     if not re_taint_free.match(sig_filename):
@@ -1192,7 +1229,7 @@ used."""
             return None
 
     # Build the command line
             return None
 
     # Build the command line
-    status_read, status_write = os.pipe();
+    status_read, status_write = os.pipe()
     cmd = "gpgv --status-fd %s %s %s %s" % (
         status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
 
     cmd = "gpgv --status-fd %s %s %s %s" % (
         status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
 
@@ -1349,9 +1386,11 @@ def wrap(paragraph, max_length, prefix=""):
 
 ################################################################################
 
 
 ################################################################################
 
-# Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
-# Returns fixed 'src'
 def clean_symlink (src, dest, root):
 def clean_symlink (src, dest, root):
+    """
+    Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
+    Returns fixed 'src'
+    """
     src = src.replace(root, '', 1)
     dest = dest.replace(root, '', 1)
     dest = os.path.dirname(dest)
     src = src.replace(root, '', 1)
     dest = dest.replace(root, '', 1)
     dest = os.path.dirname(dest)
@@ -1360,32 +1399,22 @@ def clean_symlink (src, dest, root):
 
 ################################################################################
 
 
 ################################################################################
 
-def temp_filename(directory=None, dotprefix=None, perms=0700):
-    """Return a secure and unique filename by pre-creating it.
-If 'directory' is non-null, it will be the directory the file is pre-created in.
-If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
-
-    if directory:
-        old_tempdir = tempfile.tempdir
-        tempfile.tempdir = directory
-
-    filename = tempfile.mktemp()
-
-    if dotprefix:
-        filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename))
-    fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms)
-    os.close(fd)
+def temp_filename(directory=None, prefix="dak", suffix=""):
+    """
+    Return a secure and unique filename by pre-creating it.
+    If 'directory' is non-null, it will be the directory the file is pre-created in.
+    If 'prefix' is non-null, the filename will be prefixed with it, default is dak.
+    If 'suffix' is non-null, the filename will end with it.
 
 
-    if directory:
-        tempfile.tempdir = old_tempdir
+    Returns a pair (fd, name).
+    """
 
 
-    return filename
+    return tempfile.mkstemp(suffix, prefix, directory)
 
 ################################################################################
 
 
 ################################################################################
 
-# checks if the user part of the email is listed in the alias file
-
 def is_email_alias(email):
 def is_email_alias(email):
+    """ checks if the user part of the email is listed in the alias file """
     global alias_cache
     if alias_cache == None:
         aliasfn = which_alias_file()
     global alias_cache
     if alias_cache == None:
         aliasfn = which_alias_file()