]> git.decadent.org.uk Git - dak.git/blobdiff - daklib/checks.py
Add by-hash support
[dak.git] / daklib / checks.py
index b7931f2f6d8e60a2b893f6a304b357fe88cefd31..9916cc755fab492a35e7763692f1ccc6cdde416d 100644 (file)
@@ -31,11 +31,12 @@ from daklib.regexes import *
 from daklib.textutils import fix_maintainer, ParseMaintError
 import daklib.lintian as lintian
 import daklib.utils as utils
 from daklib.textutils import fix_maintainer, ParseMaintError
 import daklib.lintian as lintian
 import daklib.utils as utils
-from daklib.upload import InvalidHashException
+import daklib.upload
 
 import apt_inst
 import apt_pkg
 from apt_pkg import version_compare
 
 import apt_inst
 import apt_pkg
 from apt_pkg import version_compare
+import datetime
 import errno
 import os
 import subprocess
 import errno
 import os
 import subprocess
@@ -56,7 +57,7 @@ class Reject(Exception):
     """exception raised by failing checks"""
     pass
 
     """exception raised by failing checks"""
     pass
 
-class RejectStupidMaintainerException(Exception):
+class RejectExternalFilesMismatch(Reject):
     """exception raised by failing the external hashes check"""
 
     def __str__(self):
     """exception raised by failing the external hashes check"""
 
     def __str__(self):
@@ -112,13 +113,11 @@ class SignatureAndHashesCheck(Check):
     def check_replay(self, upload):
         # Use private session as we want to remember having seen the .changes
         # in all cases.
     def check_replay(self, upload):
         # Use private session as we want to remember having seen the .changes
         # in all cases.
-        session = DBConn().session()
+        session = upload.session
         history = SignatureHistory.from_signed_file(upload.changes)
         r = history.query(session)
         if r is not None:
         history = SignatureHistory.from_signed_file(upload.changes)
         r = history.query(session)
         if r is not None:
-            raise Reject('Signature for changes file was already seen at {0}'.format(r.seen))
-        session.add(history)
-        session.commit()
+            raise Reject('Signature for changes file was already seen at {0}.\nPlease refresh the signature of the changes file if you want to upload it again.'.format(r.seen))
         return True
 
     """Check signature of changes and dsc file (if included in upload)
         return True
 
     """Check signature of changes and dsc file (if included in upload)
@@ -162,15 +161,32 @@ class SignatureAndHashesCheck(Check):
         try:
             for f in files:
                 f.check(upload.directory)
         try:
             for f in files:
                 f.check(upload.directory)
-        except IOError as e:
-            if e.errno == errno.ENOENT:
-                raise Reject('{0} refers to non-existing file: {1}\n'
-                             'Perhaps you need to include it in your upload?'
-                             .format(filename, os.path.basename(e.filename)))
-            raise
-        except InvalidHashException as e:
+        except daklib.upload.FileDoesNotExist as e:
+            raise Reject('{0}: {1}\n'
+                         'Perhaps you need to include the file in your upload?'
+                         .format(filename, unicode(e)))
+        except daklib.upload.UploadException as e:
             raise Reject('{0}: {1}'.format(filename, unicode(e)))
 
             raise Reject('{0}: {1}'.format(filename, unicode(e)))
 
+class SignatureTimestampCheck(Check):
+    """Check timestamp of .changes signature"""
+    def check(self, upload):
+        changes = upload.changes
+
+        now = datetime.datetime.utcnow()
+        timestamp = changes.signature_timestamp
+        age = now - timestamp
+
+        age_max = datetime.timedelta(days=365)
+        age_min = datetime.timedelta(days=-7)
+
+        if age > age_max:
+            raise Reject('{0}: Signature from {1} is too old (maximum age is {2} days)'.format(changes.filename, timestamp, age_max.days))
+        if age < age_min:
+            raise Reject('{0}: Signature from {1} is too far in the future (tolerance is {2} days)'.format(changes.filename, timestamp, abs(age_min.days)))
+
+        return True
+
 class ChangesCheck(Check):
     """Check changes file for syntax errors."""
     def check(self, upload):
 class ChangesCheck(Check):
     """Check changes file for syntax errors."""
     def check(self, upload):
@@ -240,16 +256,16 @@ class ExternalHashesCheck(Check):
             return
 
         if ext_size != f.size:
             return
 
         if ext_size != f.size:
-            raise RejectStupidMaintainerException(f.filename, 'size', f.size, ext_size)
+            raise RejectExternalFilesMismatch(f.filename, 'size', f.size, ext_size)
 
         if ext_md5sum != f.md5sum:
 
         if ext_md5sum != f.md5sum:
-            raise RejectStupidMaintainerException(f.filename, 'md5sum', f.md5sum, ext_md5sum)
+            raise RejectExternalFilesMismatch(f.filename, 'md5sum', f.md5sum, ext_md5sum)
 
         if ext_sha1sum != f.sha1sum:
 
         if ext_sha1sum != f.sha1sum:
-            raise RejectStupidMaintainerException(f.filename, 'sha1sum', f.sha1sum, ext_sha1sum)
+            raise RejectExternalFilesMismatch(f.filename, 'sha1sum', f.sha1sum, ext_sha1sum)
 
         if ext_sha256sum != f.sha256sum:
 
         if ext_sha256sum != f.sha256sum:
-            raise RejectStupidMaintainerException(f.filename, 'sha256sum', f.sha256sum, ext_sha256sum)
+            raise RejectExternalFilesMismatch(f.filename, 'sha256sum', f.sha256sum, ext_sha256sum)
 
     def check(self, upload):
         cnf = Config()
 
     def check(self, upload):
         cnf = Config()
@@ -270,13 +286,41 @@ class ExternalHashesCheck(Check):
 class BinaryCheck(Check):
     """Check binary packages for syntax errors."""
     def check(self, upload):
 class BinaryCheck(Check):
     """Check binary packages for syntax errors."""
     def check(self, upload):
+        debug_deb_name_postfix = "-dbgsym"
+        # XXX: Handle dynamic debug section name here
+
         for binary in upload.changes.binaries:
             self.check_binary(upload, binary)
 
         for binary in upload.changes.binaries:
             self.check_binary(upload, binary)
 
-        binary_names = set([ binary.control['Package'] for binary in upload.changes.binaries ])
-        for bn in binary_names:
-            if bn not in upload.changes.binary_names:
-                raise Reject('Package {0} is not mentioned in Binary field in changes'.format(bn))
+        binaries = {binary.control['Package']: binary
+                        for binary in upload.changes.binaries}
+
+        for name, binary in binaries.items():
+            if name in upload.changes.binary_names:
+                # Package is listed in Binary field. Everything is good.
+                pass
+            elif daklib.utils.is_in_debug_section(binary.control):
+                # If we have a binary package in the debug section, we
+                # can allow it to not be present in the Binary field
+                # in the .changes file, so long as its name (without
+                # -dbgsym) is present in the Binary list.
+                if not name.endswith(debug_deb_name_postfix):
+                    raise Reject('Package {0} is in the debug section, but '
+                                 'does not end in {1}.'.format(name, debug_deb_name_postfix))
+
+                # Right, so, it's named properly, let's check that
+                # the corresponding package is in the Binary list
+                origin_package_name = name[:-len(debug_deb_name_postfix)]
+                if origin_package_name not in upload.changes.binary_names:
+                    raise Reject(
+                        "Debug package {debug}'s corresponding binary package "
+                        "{origin} is not present in the Binary field.".format(
+                            debug=name, origin=origin_package_name))
+            else:
+                # Someone was a nasty little hacker and put a package
+                # into the .changes that isn't in debian/control. Bad,
+                # bad person.
+                raise Reject('Package {0} is not mentioned in Binary field in changes'.format(name))
 
         return True
 
 
         return True
 
@@ -284,7 +328,7 @@ class BinaryCheck(Check):
         fn = binary.hashed_file.filename
         control = binary.control
 
         fn = binary.hashed_file.filename
         control = binary.control
 
-        for field in ('Package', 'Architecture', 'Version', 'Description'):
+        for field in ('Package', 'Architecture', 'Version', 'Description', 'Section'):
             if field not in control:
                 raise Reject('{0}: Missing mandatory field {0}.'.format(fn, field))
 
             if field not in control:
                 raise Reject('{0}: Missing mandatory field {0}.'.format(fn, field))
 
@@ -345,6 +389,11 @@ class BinaryCheck(Check):
                 except:
                     raise Reject('{0}: APT could not parse {1} field'.format(fn, field))
 
                 except:
                     raise Reject('{0}: APT could not parse {1} field'.format(fn, field))
 
+        # "Multi-Arch: no" breaks wanna-build, #768353
+        multi_arch = control.get("Multi-Arch")
+        if multi_arch == 'no':
+            raise Reject('{0}: Multi-Arch: no support in Debian is broken (#768353)'.format(fn))
+
 class BinaryTimestampCheck(Check):
     """check timestamps of files in binary packages
 
 class BinaryTimestampCheck(Check):
     """check timestamps of files in binary packages
 
@@ -668,11 +717,24 @@ class NoSourceOnlyCheck(Check):
 
         if not allow_no_arch_indep_uploads \
            and 'all' not in changes.architectures \
 
         if not allow_no_arch_indep_uploads \
            and 'all' not in changes.architectures \
+           and 'experimental' not in changes.distributions \
+           and 'unstable' not in changes.distributions \
+           and 'sid' not in changes.distributions \
            and changes.source.package_list.has_arch_indep_packages():
             raise Reject('Uploads not including architecture-independent packages are not allowed.')
 
         return True
 
            and changes.source.package_list.has_arch_indep_packages():
             raise Reject('Uploads not including architecture-independent packages are not allowed.')
 
         return True
 
+class ArchAllBinNMUCheck(Check):
+    """Check for arch:all binNMUs"""
+    def check(self, upload):
+        changes = upload.changes
+
+        if 'all' in changes.architectures and changes.changes.get('Binary-Only') == 'yes':
+            raise Reject('arch:all binNMUs are not allowed.')
+
+        return True
+
 class LintianCheck(Check):
     """Check package using lintian"""
     def check(self, upload):
 class LintianCheck(Check):
     """Check package using lintian"""
     def check(self, upload):
@@ -747,6 +809,14 @@ class SourceFormatCheck(Check):
         if query.first() is None:
             raise Reject('source format {0} is not allowed in suite {1}'.format(source_format, suite.suite_name))
 
         if query.first() is None:
             raise Reject('source format {0} is not allowed in suite {1}'.format(source_format, suite.suite_name))
 
+class SuiteCheck(Check):
+    def per_suite_check(self, upload, suite):
+        if not suite.accept_source_uploads and upload.changes.source is not None:
+            raise Reject('The suite "{0}" does not accept source uploads.'.format(suite.suite_name))
+        if not suite.accept_binary_uploads and len(upload.changes.binaries) != 0:
+            raise Reject('The suite "{0}" does not accept binary uploads.'.format(suite.suite_name))
+        return True
+
 class SuiteArchitectureCheck(Check):
     def per_suite_check(self, upload, suite):
         session = upload.session
 class SuiteArchitectureCheck(Check):
     def per_suite_check(self, upload, suite):
         session = upload.session