]> git.decadent.org.uk Git - dak.git/blobdiff - daklib/utils.py
Revert "revert all my stupid commits, we'll try this again later when we have a test...
[dak.git] / daklib / utils.py
old mode 100644 (file)
new mode 100755 (executable)
index a094788..7584524
 ################################################################################
 
 import codecs, commands, email.Header, os, pwd, re, select, socket, shutil, \
-       sys, tempfile, traceback
+       sys, tempfile, traceback, stat
 import apt_pkg
 import database
+from dak_exceptions import *
 
 ################################################################################
 
@@ -48,39 +49,15 @@ re_verwithext = re.compile(r"^(\d+)(?:\.(\d+))(?:\s+\((\S+)\))?$")
 
 re_srchasver = re.compile(r"^(\S+)\s+\((\S+)\)$")
 
-changes_parse_error_exc = "Can't parse line in .changes file"
-invalid_dsc_format_exc = "Invalid .dsc file"
-nk_format_exc = "Unknown Format: in .changes file"
-no_files_exc = "No Files: field in .dsc or .changes file."
-cant_open_exc = "Can't open file"
-unknown_hostname_exc = "Unknown hostname"
-cant_overwrite_exc = "Permission denied; can't overwrite existent file."
-file_exists_exc = "Destination file exists"
-sendmail_failed_exc = "Sendmail invocation failed"
-tried_too_hard_exc = "Tried too hard to find a free filename."
-
 default_config = "/etc/dak/dak.conf"
 default_apt_config = "/etc/dak/apt.conf"
 
 alias_cache = None
 key_uid_email_cache = {}
 
-################################################################################
-
-class Error(Exception):
-    """Base class for exceptions in this module."""
-    pass
-
-class ParseMaintError(Error):
-    """Exception raised for errors in parsing a maintainer field.
-
-    Attributes:
-       message -- explanation of the error
-    """
-
-    def __init__(self, message):
-        self.args = message,
-        self.message = message
+# (hashname, function, earliest_changes_version)
+known_hashes = [("sha1", apt_pkg.sha1sum, (1, 8)),
+                ("sha256", apt_pkg.sha256sum, (1, 8))]
 
 ################################################################################
 
@@ -88,7 +65,7 @@ def open_file(filename, mode='r'):
     try:
         f = open(filename, mode)
     except IOError:
-        raise cant_open_exc, filename
+        raise CantOpenError, filename
     return f
 
 ################################################################################
@@ -151,7 +128,7 @@ The rules for (signing_rules == 1)-mode are:
     lines = changes_in.readlines()
 
     if not lines:
-        raise changes_parse_error_exc, "[Empty changes file]"
+        raise ParseChangesError, "[Empty changes file]"
 
     # Reindex by line number so we can easily verify the format of
     # .dsc files...
@@ -173,10 +150,10 @@ The rules for (signing_rules == 1)-mode are:
             if signing_rules == 1:
                 index += 1
                 if index > num_of_lines:
-                    raise invalid_dsc_format_exc, index
+                    raise InvalidDscError, index
                 line = indexed_lines[index]
                 if not line.startswith("-----BEGIN PGP SIGNATURE"):
-                    raise invalid_dsc_format_exc, index
+                    raise InvalidDscError, index
                 inside_signature = 0
                 break
             else:
@@ -205,7 +182,7 @@ The rules for (signing_rules == 1)-mode are:
         mlf = re_multi_line_field.match(line)
         if mlf:
             if first == -1:
-                raise changes_parse_error_exc, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
+                raise ParseChangesError, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
             if first == 1 and changes[field] != "":
                 changes[field] += '\n'
             first = 0
@@ -214,7 +191,7 @@ The rules for (signing_rules == 1)-mode are:
         error += line
 
     if signing_rules == 1 and inside_signature:
-        raise invalid_dsc_format_exc, index
+        raise InvalidDscError, index
 
     changes_in.close()
     changes["filecontents"] = "".join(lines)
@@ -228,12 +205,122 @@ The rules for (signing_rules == 1)-mode are:
             changes["source-version"] = srcver.group(2)
 
     if error:
-        raise changes_parse_error_exc, error
+        raise ParseChangesError, error
 
     return changes
 
 ################################################################################
 
+def create_hash (lfiles, key, testfn, basedict = None):
+    rejmsg = []
+    for f in lfiles.keys():
+        try:
+            file_handle = open_file(f)
+        except CantOpenError:
+            rejmsg.append("Could not open file %s for checksumming" % (f))
+
+        # Check hash
+        if basedict and basedict.has_key(f):
+            basedict[f]['%ssum' % key] = testfn(file_handle)
+        file_handle.close()
+
+    return rejmsg
+
+################################################################################
+
+def check_hash (where, lfiles, key, testfn, basedict = None):
+    rejmsg = []
+    if basedict:
+        for f in basedict.keys():
+            if f not in lfiles:
+                rejmsg.append("%s: no %s checksum" % (f, key))
+
+    for f in lfiles.keys():
+        if basedict and f not in basedict:
+            rejmsg.append("%s: extraneous entry in %s checksums" % (f, key))
+
+        try:
+            file_handle = open_file(f)
+        except CantOpenError:
+            continue
+
+        # Check hash
+        if testfn(file_handle) != lfiles[f][key]:
+            rejmsg.append("%s: %s check failed." % (f, key))
+        file_handle.close()
+        # Store the hashes for later use
+        if basedict:
+            basedict[f]['%ssum' % key] = lfiles[f][key]
+        # Check size
+        actual_size = os.stat(f)[stat.ST_SIZE]
+        size = int(lfiles[f]["size"])
+        if size != actual_size:
+            rejmsg.append("%s: actual file size (%s) does not match size (%s) in %s"
+                   % (f, actual_size, size, where))
+
+    return rejmsg
+
+################################################################################
+
+def ensure_hashes(changes, dsc, files, dsc_files):
+    # Make sure we recognise the format of the Files: field
+    format = changes.get("format", "0.0").split(".",1)
+    if len(format) == 2:
+        format = int(format[0]), int(format[1])
+    else:
+        format = int(float(format[0])), 0
+
+    rejmsg = []
+    for x in changes:
+        if x.startswith("checksum-"):
+            h = x.split("-",1)[1]
+            if h not in dict(known_hashes):
+                rejmsg.append("Unsupported checksum field in .changes" % (h))
+
+    for x in dsc:
+        if x.startswith("checksum-"):
+            h = x.split("-",1)[1]
+            if h not in dict(known_hashes):
+                rejmsg.append("Unsupported checksum field in .dsc" % (h))
+
+    # We have to calculate the hash if we have an earlier changes version than
+    # the hash appears in rather than require it exist in the changes file
+    # I hate backwards compatibility
+    for h,f,v in known_hashes:
+        try:
+            if format < v:
+                for m in create_hash(files, h, f, files):
+                    rejmsg.append(m)
+            else:
+                for m in check_hash(".changes %s" % (h), files, h, f, files):
+                    rejmsg.append(m)
+        except NoFilesFieldError:
+            rejmsg.append("No Checksums-%s: field in .changes" % (h))
+        except UnknownFormatError, format:
+            rejmsg.append("%s: unknown format of .changes" % (format))
+        except ParseChangesError, line:
+            rejmsg.append("parse error for Checksums-%s in .changes, can't grok: %s." % (h, line))
+
+        if "source" not in changes["architecture"]: continue
+
+        try:
+            if format < v:
+                for m in create_hash(dsc_files, h, f, dsc_files):
+                    rejmsg.append(m)
+            else:
+                for m in check_hash(".dsc %s" % (h), dsc_files, h, f, dsc_files):
+                    rejmsg.append(m)
+        except UnknownFormatError, format:
+            rejmsg.append("%s: unknown format of .dsc" % (format))
+        except NoFilesFieldError:
+            rejmsg.append("No Checksums-%s: field in .dsc" % (h))
+        except ParseChangesError, line:
+            rejmsg.append("parse error for Checksums-%s in .dsc, can't grok: %s." % (h, line))
+
+    return rejmsg
+
+################################################################################
+
 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
 
 def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
@@ -241,12 +328,12 @@ def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
 
     # Make sure we have a Files: field to parse...
     if not changes.has_key(field):
-        raise no_files_exc
+        raise NoFilesFieldError
 
     # Make sure we recognise the format of the Files: field
     format = re_verwithext.search(changes.get("format", "0.0"))
     if not format:
-        raise nk_format_exc, "%s" % (changes.get("format","0.0"))
+        raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
 
     format = format.groups()
     if format[1] == None:
@@ -258,12 +345,12 @@ def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
 
     if is_a_dsc:
         if format != (1,0):
-            raise nk_format_exc, "%s" % (changes.get("format","0.0"))
+            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
     else:
         if (format < (1,5) or format > (1,8)):
-            raise nk_format_exc, "%s" % (changes.get("format","0.0"))
+            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
         if field != "files" and format < (1,8):
-            raise nk_format_exc, "%s" % (changes.get("format","0.0"))
+            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
 
     includes_section = (not is_a_dsc) and field == "files"
 
@@ -279,7 +366,7 @@ def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
             else:
                 (md5, size, name) = s
         except ValueError:
-            raise changes_parse_error_exc, i
+            raise ParseChangesError, i
 
         if section == "":
             section = "-"
@@ -387,7 +474,7 @@ def send_mail (message, filename=""):
     # Invoke sendmail
     (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
     if (result != 0):
-        raise sendmail_failed_exc, output
+        raise SendmailFailedError, output
 
     # Clean up any temporary files
     if message:
@@ -443,10 +530,10 @@ def copy (src, dest, overwrite = 0, perms = 0664):
     # Don't overwrite unless forced to
     if os.path.exists(dest):
         if not overwrite:
-            raise file_exists_exc
+            raise FileExistsError
         else:
             if not os.access(dest, os.W_OK):
-                raise cant_overwrite_exc
+                raise CantOverwriteError
     shutil.copy2(src, dest)
     os.chmod(dest, perms)
 
@@ -590,7 +677,7 @@ def find_next_free (dest, too_many=100):
         dest = orig_dest + '.' + repr(extra)
         extra += 1
     if extra >= too_many:
-        raise tried_too_hard_exc
+        raise NoFreeFilenameError
     return dest
 
 ################################################################################