queue.py 55.7 KB
Newer Older
J
New.  
James Troup 已提交
1
#!/usr/bin/env python
2
# vim:set et sw=4:
J
New.  
James Troup 已提交
3

J
Joerg Jaspert 已提交
4 5 6 7 8 9 10 11
"""
Queue utility functions for dak

@contact: Debian FTP Master <ftpmaster@debian.org>
@copyright: 2001 - 2006 James Troup <james@nocrew.org>
@copyright: 2009  Joerg Jaspert <joerg@debian.org>
@license: GNU General Public License version 2 or later
"""
J
New.  
James Troup 已提交
12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28

# 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-1307  USA

###############################################################################

J
Joerg Jaspert 已提交
29 30 31 32 33 34 35 36 37 38 39
import cPickle
import errno
import os
import pg
import stat
import sys
import time
import apt_inst
import apt_pkg
import utils
import database
J
Joerg Jaspert 已提交
40
from dak_exceptions import *
M
Mark Hymers 已提交
41
from regexes import re_default_answer, re_fdnic, re_bin_only_nmu
J
New.  
James Troup 已提交
42

43
from types import *
J
New.  
James Troup 已提交
44 45

###############################################################################
46 47 48 49

# Determine what parts in a .changes are NEW

def determine_new(changes, files, projectB, warn=1):
J
Joerg Jaspert 已提交
50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68
    """
    Determine what parts in a C{changes} file are NEW.

    @type changes: Upload.Pkg.changes dict
    @param changes: Changes dictionary

    @type files: Upload.Pkg.files dict
    @param files: Files dictionary

    @type projectB: pgobject
    @param projectB: DB handle

    @type warn: bool
    @param warn: Warn if overrides are added for (old)stable

    @rtype: dict
    @return: dictionary of NEW components.

    """
69 70 71
    new = {}

    # Build up a list of potentially new things
J
Joerg Jaspert 已提交
72 73
    for file_entry in files.keys():
        f = files[file_entry]
74 75 76 77 78 79
        # Skip byhand elements
        if f["type"] == "byhand":
            continue
        pkg = f["package"]
        priority = f["priority"]
        section = f["section"]
J
Joerg Jaspert 已提交
80
        file_type = get_type(f)
81 82
        component = f["component"]

J
Joerg Jaspert 已提交
83
        if file_type == "dsc":
84 85 86 87 88
            priority = "source"
        if not new.has_key(pkg):
            new[pkg] = {}
            new[pkg]["priority"] = priority
            new[pkg]["section"] = section
J
Joerg Jaspert 已提交
89
            new[pkg]["type"] = file_type
90 91 92 93
            new[pkg]["component"] = component
            new[pkg]["files"] = []
        else:
            old_type = new[pkg]["type"]
J
Joerg Jaspert 已提交
94
            if old_type != file_type:
95 96 97 98
                # source gets trumped by deb or udeb
                if old_type == "dsc":
                    new[pkg]["priority"] = priority
                    new[pkg]["section"] = section
J
Joerg Jaspert 已提交
99
                    new[pkg]["type"] = file_type
100
                    new[pkg]["component"] = component
J
Joerg Jaspert 已提交
101
        new[pkg]["files"].append(file_entry)
102 103 104 105 106 107 108 109 110 111 112
        if f.has_key("othercomponents"):
            new[pkg]["othercomponents"] = f["othercomponents"]

    for suite in changes["suite"].keys():
        suite_id = database.get_suite_id(suite)
        for pkg in new.keys():
            component_id = database.get_component_id(new[pkg]["component"])
            type_id = database.get_override_type_id(new[pkg]["type"])
            q = projectB.query("SELECT package FROM override WHERE package = '%s' AND suite = %s AND component = %s AND type = %s" % (pkg, suite_id, component_id, type_id))
            ql = q.getresult()
            if ql:
J
Joerg Jaspert 已提交
113 114 115
                for file_entry in new[pkg]["files"]:
                    if files[file_entry].has_key("new"):
                        del files[file_entry]["new"]
116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
                del new[pkg]

    if warn:
        if changes["suite"].has_key("stable"):
            print "WARNING: overrides will be added for stable!"
            if changes["suite"].has_key("oldstable"):
                print "WARNING: overrides will be added for OLDstable!"
        for pkg in new.keys():
            if new[pkg].has_key("othercomponents"):
                print "WARNING: %s already present in %s distribution." % (pkg, new[pkg]["othercomponents"])

    return new

################################################################################

J
Joerg Jaspert 已提交
131 132 133 134 135 136 137 138 139 140 141
def get_type(file):
    """
    Get the file type of C{file}

    @type file: dict
    @param file: file entry

    @rtype: string
    @return: filetype

    """
142
    # Determine the type
J
Joerg Jaspert 已提交
143 144 145
    if file.has_key("dbtype"):
        file_type = file["dbtype"]
    elif file["type"] in [ "orig.tar.gz", "orig.tar.bz2", "tar.gz", "tar.bz2", "diff.gz", "diff.bz2", "dsc" ]:
J
Joerg Jaspert 已提交
146
        file_type = "dsc"
147
    else:
J
Joerg Jaspert 已提交
148
        utils.fubar("invalid type (%s) for new.  Dazed, confused and sure as heck not continuing." % (file_type))
149 150

    # Validate the override type
J
Joerg Jaspert 已提交
151
    type_id = database.get_override_type_id(file_type)
152
    if type_id == -1:
J
Joerg Jaspert 已提交
153
        utils.fubar("invalid type (%s) for new.  Say wha?" % (file_type))
154

J
Joerg Jaspert 已提交
155
    return file_type
156 157 158

################################################################################

J
Joerg Jaspert 已提交
159

160 161

def check_valid(new):
J
Joerg Jaspert 已提交
162 163 164 165 166 167 168 169 170 171 172
    """
    Check if section and priority for NEW packages exist in database.
    Additionally does sanity checks:
      - debian-installer packages have to be udeb (or source)
      - non debian-installer packages can not be udeb
      - source priority can only be assigned to dsc file types

    @type new: dict
    @param new: Dict of new packages with their section, priority and type.

    """
173 174 175
    for pkg in new.keys():
        section = new[pkg]["section"]
        priority = new[pkg]["priority"]
J
Joerg Jaspert 已提交
176
        file_type = new[pkg]["type"]
177 178 179 180
        new[pkg]["section id"] = database.get_section_id(section)
        new[pkg]["priority id"] = database.get_priority_id(new[pkg]["priority"])
        # Sanity checks
        di = section.find("debian-installer") != -1
181
        if (di and file_type not in ("udeb", "dsc")) or (not di and file_type == "udeb"):
182
            new[pkg]["section id"] = -1
J
Joerg Jaspert 已提交
183 184
        if (priority == "source" and file_type != "dsc") or \
           (priority != "source" and file_type == "dsc"):
185 186 187
            new[pkg]["priority id"] = -1


J
New.  
James Troup 已提交
188 189 190
###############################################################################

class Pkg:
J
Joerg Jaspert 已提交
191
    """ Convenience wrapper to carry around all the package information """
J
New.  
James Troup 已提交
192
    def __init__(self, **kwds):
193
        self.__dict__.update(kwds)
J
New.  
James Troup 已提交
194 195

    def update(self, **kwds):
196
        self.__dict__.update(kwds)
J
New.  
James Troup 已提交
197 198 199

###############################################################################

200
class Upload:
J
Joerg Jaspert 已提交
201 202
    """
    Everything that has to do with an upload processed.
J
New.  
James Troup 已提交
203

J
Joerg Jaspert 已提交
204
    """
J
New.  
James Troup 已提交
205
    def __init__(self, Cnf):
J
Joerg Jaspert 已提交
206 207 208 209 210
        """
        Initialize various variables and the global substitution template mappings.
        Also connect to the DB and initialize the Database module.

        """
211 212 213
        self.Cnf = Cnf
        self.accept_count = 0
        self.accept_bytes = 0L
J
Joerg Jaspert 已提交
214
        self.reject_message = ""
J
legacy  
Joerg Jaspert 已提交
215
        self.pkg = Pkg(changes = {}, dsc = {}, dsc_files = {}, files = {})
J
New.  
James Troup 已提交
216 217

        # Initialize the substitution template mapping global
218 219 220 221
        Subst = self.Subst = {}
        Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"]
        Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"]
        Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"]
222
        Subst["__DAK_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"]
J
New.  
James Troup 已提交
223

224
        self.projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
225
        database.init(Cnf, self.projectB)
J
New.  
James Troup 已提交
226 227 228 229

    ###########################################################################

    def init_vars (self):
J
Joerg Jaspert 已提交
230
        """ Reset a number of entries from our Pkg object. """
231 232 233 234
        self.pkg.changes.clear()
        self.pkg.dsc.clear()
        self.pkg.files.clear()
        self.pkg.dsc_files.clear()
235 236 237
        self.pkg.orig_tar_id = None
        self.pkg.orig_tar_location = ""
        self.pkg.orig_tar_gz = None
J
New.  
James Troup 已提交
238 239 240 241

    ###########################################################################

    def update_vars (self):
J
Joerg Jaspert 已提交
242 243 244
        """
        Update our Pkg object by reading a previously created cPickle .dak dumpfile.
        """
245
        dump_filename = self.pkg.changes_file[:-8]+".dak"
246 247
        dump_file = utils.open_file(dump_filename)
        p = cPickle.Unpickler(dump_file)
248 249 250 251 252 253 254 255 256

        self.pkg.changes.update(p.load())
        self.pkg.dsc.update(p.load())
        self.pkg.files.update(p.load())
        self.pkg.dsc_files.update(p.load())

        self.pkg.orig_tar_id = p.load()
        self.pkg.orig_tar_location = p.load()

257
        dump_file.close()
J
New.  
James Troup 已提交
258 259 260 261 262

    ###########################################################################


    def dump_vars(self, dest_dir):
J
Joerg Jaspert 已提交
263 264 265 266 267 268 269 270 271 272 273
        """
        Dump our Pkg object into a cPickle file.

        @type dest_dir: string
        @param dest_dir: Path where the dumpfile should be stored

        @note: This could just dump the dictionaries as is, but I'd like to avoid this so
               there's some idea of what process-accepted & process-new use from
               process-unchecked. (JT)

        """
274 275 276 277 278 279 280 281

        changes = self.pkg.changes
        dsc = self.pkg.dsc
        files = self.pkg.files
        dsc_files = self.pkg.dsc_files
        orig_tar_id = self.pkg.orig_tar_id
        orig_tar_location = self.pkg.orig_tar_location

282
        dump_filename = os.path.join(dest_dir,self.pkg.changes_file[:-8] + ".dak")
283
        dump_file = utils.open_file(dump_filename, 'w')
284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
        try:
            os.chmod(dump_filename, 0664)
        except OSError, e:
            # chmod may fail when the dumpfile is not owned by the user
            # invoking dak (like e.g. when NEW is processed by a member
            # of ftpteam)
            if errno.errorcode[e.errno] == 'EPERM':
                perms = stat.S_IMODE(os.stat(dump_filename)[stat.ST_MODE])
                # security precaution, should never happen unless a weird
                # umask is set anywhere
                if perms & stat.S_IWOTH:
                    utils.fubar("%s is world writable and chmod failed." % \
                        (dump_filename,))
                # ignore the failed chmod otherwise as the file should
                # already have the right privileges and is just, at worst,
                # unreadable for world
            else:
                raise
J
James Troup 已提交
302

303
        p = cPickle.Pickler(dump_file, 1)
304 305 306 307 308
        d_changes = {}
        d_dsc = {}
        d_files = {}
        d_dsc_files = {}

J
New.  
James Troup 已提交
309
        ## files
J
Joerg Jaspert 已提交
310 311
        for file_entry in files.keys():
            d_files[file_entry] = {}
J
New.  
James Troup 已提交
312
            for i in [ "package", "version", "architecture", "type", "size",
P
Philipp Kern 已提交
313 314 315 316
                       "md5sum", "sha1sum", "sha256sum", "component",
                       "location id", "source package", "source version",
                       "maintainer", "dbtype", "files id", "new",
                       "section", "priority", "othercomponents",
317
                       "pool name", "original component" ]:
J
Joerg Jaspert 已提交
318 319
                if files[file_entry].has_key(i):
                    d_files[file_entry][i] = files[file_entry][i]
J
New.  
James Troup 已提交
320 321
        ## changes
        # Mandatory changes fields
322 323 324 325 326
        for i in [ "distribution", "source", "architecture", "version",
                   "maintainer", "urgency", "fingerprint", "changedby822",
                   "changedby2047", "changedbyname", "maintainer822",
                   "maintainer2047", "maintainername", "maintaineremail",
                   "closes", "changes" ]:
327
            d_changes[i] = changes[i]
J
New.  
James Troup 已提交
328
        # Optional changes fields
329 330
        for i in [ "changed-by", "filecontents", "format", "process-new note", "adv id", "distribution-version",
                   "sponsoremail" ]:
J
James Troup 已提交
331
            if changes.has_key(i):
332
                d_changes[i] = changes[i]
J
New.  
James Troup 已提交
333
        ## dsc
334
        for i in [ "source", "version", "maintainer", "fingerprint",
A
Anthony Towns 已提交
335
                   "uploaders", "bts changelog", "dm-upload-allowed" ]:
J
New.  
James Troup 已提交
336
            if dsc.has_key(i):
337
                d_dsc[i] = dsc[i]
J
New.  
James Troup 已提交
338
        ## dsc_files
J
Joerg Jaspert 已提交
339 340
        for file_entry in dsc_files.keys():
            d_dsc_files[file_entry] = {}
J
New.  
James Troup 已提交
341 342
            # Mandatory dsc_files fields
            for i in [ "size", "md5sum" ]:
J
Joerg Jaspert 已提交
343
                d_dsc_files[file_entry][i] = dsc_files[file_entry][i]
J
New.  
James Troup 已提交
344 345
            # Optional dsc_files fields
            for i in [ "files id" ]:
J
Joerg Jaspert 已提交
346 347
                if dsc_files[file_entry].has_key(i):
                    d_dsc_files[file_entry][i] = dsc_files[file_entry][i]
J
New.  
James Troup 已提交
348 349

        for i in [ d_changes, d_dsc, d_files, d_dsc_files,
J
legacy  
Joerg Jaspert 已提交
350
                   orig_tar_id, orig_tar_location ]:
351 352
            p.dump(i)
        dump_file.close()
J
New.  
James Troup 已提交
353 354 355 356 357 358

    ###########################################################################

    # Set up the per-package template substitution mappings

    def update_subst (self, reject_message = ""):
J
Joerg Jaspert 已提交
359 360
        """ Set up the per-package template substitution mappings """

361 362
        Subst = self.Subst
        changes = self.pkg.changes
363
        # If 'dak process-unchecked' crashed out in the right place, architecture may still be a string.
J
New.  
James Troup 已提交
364
        if not changes.has_key("architecture") or not isinstance(changes["architecture"], DictType):
365
            changes["architecture"] = { "Unknown" : "" }
366 367
        # and maintainer2047 may not exist.
        if not changes.has_key("maintainer2047"):
368
            changes["maintainer2047"] = self.Cnf["Dinstall::MyEmailAddress"]
J
New.  
James Troup 已提交
369

370 371 372
        Subst["__ARCHITECTURE__"] = " ".join(changes["architecture"].keys())
        Subst["__CHANGES_FILENAME__"] = os.path.basename(self.pkg.changes_file)
        Subst["__FILE_CONTENTS__"] = changes.get("filecontents", "")
J
New.  
James Troup 已提交
373 374 375

        # For source uploads the Changed-By field wins; otherwise Maintainer wins.
        if changes["architecture"].has_key("source") and changes["changedby822"] != "" and (changes["changedby822"] != changes["maintainer822"]):
376
            Subst["__MAINTAINER_FROM__"] = changes["changedby2047"]
377
            Subst["__MAINTAINER_TO__"] = "%s, %s" % (changes["changedby2047"],
378 379
                                                     changes["maintainer2047"])
            Subst["__MAINTAINER__"] = changes.get("changed-by", "Unknown")
J
New.  
James Troup 已提交
380
        else:
381 382 383
            Subst["__MAINTAINER_FROM__"] = changes["maintainer2047"]
            Subst["__MAINTAINER_TO__"] = changes["maintainer2047"]
            Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown")
J
Joerg Jaspert 已提交
384 385 386 387

        if "sponsoremail" in changes:
            Subst["__MAINTAINER_TO__"] += ", %s"%changes["sponsoremail"]

J
New.  
James Troup 已提交
388
        if self.Cnf.has_key("Dinstall::TrackingServer") and changes.has_key("source"):
389
            Subst["__MAINTAINER_TO__"] += "\nBcc: %s@%s" % (changes["source"], self.Cnf["Dinstall::TrackingServer"])
J
New.  
James Troup 已提交
390

391 392
        # Apply any global override of the Maintainer field
        if self.Cnf.get("Dinstall::OverrideMaintainer"):
393 394
            Subst["__MAINTAINER_TO__"] = self.Cnf["Dinstall::OverrideMaintainer"]
            Subst["__MAINTAINER_FROM__"] = self.Cnf["Dinstall::OverrideMaintainer"]
395

396 397 398
        Subst["__REJECT_MESSAGE__"] = reject_message
        Subst["__SOURCE__"] = changes.get("source", "Unknown")
        Subst["__VERSION__"] = changes.get("version", "Unknown")
J
New.  
James Troup 已提交
399 400 401 402

    ###########################################################################

    def build_summaries(self):
J
Joerg Jaspert 已提交
403
        """ Build a summary of changes the upload introduces. """
404 405
        changes = self.pkg.changes
        files = self.pkg.files
J
New.  
James Troup 已提交
406

407
        byhand = summary = new = ""
J
New.  
James Troup 已提交
408 409 410 411

        # changes["distribution"] may not exist in corner cases
        # (e.g. unreadable changes files)
        if not changes.has_key("distribution") or not isinstance(changes["distribution"], DictType):
412
            changes["distribution"] = {}
J
New.  
James Troup 已提交
413

J
Various  
Joerg Jaspert 已提交
414
        override_summary =""
415 416
        file_keys = files.keys()
        file_keys.sort()
J
Joerg Jaspert 已提交
417 418
        for file_entry in file_keys:
            if files[file_entry].has_key("byhand"):
J
New.  
James Troup 已提交
419
                byhand = 1
J
Joerg Jaspert 已提交
420 421
                summary += file_entry + " byhand\n"
            elif files[file_entry].has_key("new"):
J
New.  
James Troup 已提交
422
                new = 1
J
Joerg Jaspert 已提交
423 424 425 426 427
                summary += "(new) %s %s %s\n" % (file_entry, files[file_entry]["priority"], files[file_entry]["section"])
                if files[file_entry].has_key("othercomponents"):
                    summary += "WARNING: Already present in %s distribution.\n" % (files[file_entry]["othercomponents"])
                if files[file_entry]["type"] == "deb":
                    deb_fh = utils.open_file(file_entry)
428
                    summary += apt_pkg.ParseSection(apt_inst.debExtractControl(deb_fh))["Description"] + '\n'
429
                    deb_fh.close()
J
New.  
James Troup 已提交
430
            else:
J
Joerg Jaspert 已提交
431 432 433 434 435 436
                files[file_entry]["pool name"] = utils.poolify (changes.get("source",""), files[file_entry]["component"])
                destination = self.Cnf["Dir::PoolRoot"] + files[file_entry]["pool name"] + file_entry
                summary += file_entry + "\n  to " + destination + "\n"
                if not files[file_entry].has_key("type"):
                    files[file_entry]["type"] = "unknown"
                if files[file_entry]["type"] in ["deb", "udeb", "dsc"]:
437 438
                    # (queue/unchecked), there we have override entries already, use them
                    # (process-new), there we dont have override entries, use the newly generated ones.
J
Joerg Jaspert 已提交
439 440 441
                    override_prio = files[file_entry].get("override priority", files[file_entry]["priority"])
                    override_sect = files[file_entry].get("override section", files[file_entry]["section"])
                    override_summary += "%s - %s %s\n" % (file_entry, override_prio, override_sect)
J
New.  
James Troup 已提交
442

443
        short_summary = summary
J
New.  
James Troup 已提交
444 445

        # This is for direport's benefit...
446
        f = re_fdnic.sub("\n .\n", changes.get("changes",""))
J
New.  
James Troup 已提交
447 448

        if byhand or new:
449
            summary += "Changes: " + f
J
New.  
James Troup 已提交
450

451 452
        summary += "\n\nOverride entries for your package:\n" + override_summary + "\n"

453
        summary += self.announce(short_summary, 0)
J
New.  
James Troup 已提交
454

455
        return (summary, short_summary)
J
New.  
James Troup 已提交
456 457 458

    ###########################################################################

459
    def close_bugs (self, summary, action):
J
Joerg Jaspert 已提交
460 461 462 463 464 465 466 467 468 469 470 471 472 473
        """
        Send mail to close bugs as instructed by the closes field in the changes file.
        Also add a line to summary if any work was done.

        @type summary: string
        @param summary: summary text, as given by L{build_summaries}

        @type action: bool
        @param action: Set to false no real action will be done.

        @rtype: string
        @return: summary. If action was taken, extended by the list of closed bugs.

        """
474 475 476
        changes = self.pkg.changes
        Subst = self.Subst
        Cnf = self.Cnf
J
New.  
James Troup 已提交
477

478
        bugs = changes["closes"].keys()
J
New.  
James Troup 已提交
479

480
        if not bugs:
481
            return summary
J
New.  
James Troup 已提交
482

483
        bugs.sort()
484 485 486 487 488 489 490
        summary += "Closing bugs: "
        for bug in bugs:
            summary += "%s " % (bug)
            if action:
                Subst["__BUG_NUMBER__"] = bug
                if changes["distribution"].has_key("stable"):
                    Subst["__STABLE_WARNING__"] = """
491 492 493 494
Note that this package is not part of the released stable Debian
distribution.  It may have dependencies on other unreleased software,
or other instabilities.  Please take care if you wish to install it.
The update will eventually make its way into the next released Debian
495
distribution."""
496 497 498 499 500 501
                else:
                    Subst["__STABLE_WARNING__"] = ""
                    mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/process-unchecked.bug-close")
                    utils.send_mail (mail_message)
        if action:
            self.Logger.log(["closing bugs"]+bugs)
502
        summary += "\n"
503

504
        return summary
505 506 507 508

    ###########################################################################

    def announce (self, short_summary, action):
J
Joerg Jaspert 已提交
509 510 511 512 513 514 515 516 517 518 519 520 521
        """
        Send an announce mail about a new upload.

        @type short_summary: string
        @param short_summary: Short summary text to include in the mail

        @type action: bool
        @param action: Set to false no real action will be done.

        @rtype: string
        @return: Textstring about action taken.

        """
522 523 524
        Subst = self.Subst
        Cnf = self.Cnf
        changes = self.pkg.changes
525 526 527

        # Only do announcements for source uploads with a recent dpkg-dev installed
        if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
528
            return ""
J
New.  
James Troup 已提交
529

530 531 532
        lists_done = {}
        summary = ""
        Subst["__SHORT_SUMMARY__"] = short_summary
533 534

        for dist in changes["distribution"].keys():
J
Joerg Jaspert 已提交
535 536
            announce_list = Cnf.Find("Suite::%s::Announce" % (dist))
            if announce_list == "" or lists_done.has_key(announce_list):
537
                continue
J
Joerg Jaspert 已提交
538 539
            lists_done[announce_list] = 1
            summary += "Announcing to %s\n" % (announce_list)
540 541

            if action:
J
Joerg Jaspert 已提交
542
                Subst["__ANNOUNCE_LIST_ADDRESS__"] = announce_list
543
                if Cnf.get("Dinstall::TrackingServer") and changes["architecture"].has_key("source"):
544
                    Subst["__ANNOUNCE_LIST_ADDRESS__"] = Subst["__ANNOUNCE_LIST_ADDRESS__"] + "\nBcc: %s@%s" % (changes["source"], Cnf["Dinstall::TrackingServer"])
545
                mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/process-unchecked.announce")
546
                utils.send_mail (mail_message)
547

J
James Troup 已提交
548
        if Cnf.FindB("Dinstall::CloseBugs"):
549
            summary = self.close_bugs(summary, action)
550

551
        return summary
J
New.  
James Troup 已提交
552 553 554

    ###########################################################################

J
NEW  
Joerg Jaspert 已提交
555
    def accept (self, summary, short_summary, targetdir=None):
J
Joerg Jaspert 已提交
556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
        """
        Accept an upload.

        This moves all files referenced from the .changes into the I{accepted}
        queue, sends the accepted mail, announces to lists, closes bugs and
        also checks for override disparities. If enabled it will write out
        the version history for the BTS Version Tracking and will finally call
        L{queue_build}.

        @type summary: string
        @param summary: Summary text

        @type short_summary: string
        @param short_summary: Short summary

        """

573 574 575 576 577 578
        Cnf = self.Cnf
        Subst = self.Subst
        files = self.pkg.files
        changes = self.pkg.changes
        changes_file = self.pkg.changes_file
        dsc = self.pkg.dsc
J
New.  
James Troup 已提交
579

J
NEW  
Joerg Jaspert 已提交
580 581 582
        if targetdir is None:
            targetdir = Cnf["Dir::Queue::Accepted"]

J
New.  
James Troup 已提交
583
        print "Accepting."
584
        self.Logger.log(["Accepting changes",changes_file])
J
New.  
James Troup 已提交
585

J
NEW  
Joerg Jaspert 已提交
586
        self.dump_vars(targetdir)
J
New.  
James Troup 已提交
587 588

        # Move all the files into the accepted directory
J
NEW  
Joerg Jaspert 已提交
589
        utils.move(changes_file, targetdir)
590
        file_keys = files.keys()
J
Joerg Jaspert 已提交
591
        for file_entry in file_keys:
J
NEW  
Joerg Jaspert 已提交
592
            utils.move(file_entry, targetdir)
J
Joerg Jaspert 已提交
593
            self.accept_bytes += float(files[file_entry]["size"])
594
        self.accept_count += 1
J
New.  
James Troup 已提交
595 596 597 598

        # Send accept mail, announce to lists, close bugs and check for
        # override disparities
        if not Cnf["Dinstall::Options::No-Mail"]:
599 600
            Subst["__SUITE__"] = ""
            Subst["__SUMMARY__"] = summary
601
            mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/process-unchecked.accepted")
602
            utils.send_mail(mail_message)
J
New.  
James Troup 已提交
603 604
            self.announce(short_summary, 1)

605 606 607 608 609 610 611 612 613 614 615

        ## Helper stuff for DebBugs Version Tracking
        if Cnf.Find("Dir::Queue::BTSVersionTrack"):
            # ??? once queue/* is cleared on *.d.o and/or reprocessed
            # the conditionalization on dsc["bts changelog"] should be
            # dropped.

            # Write out the version history from the changelog
            if changes["architecture"].has_key("source") and \
               dsc.has_key("bts changelog"):

J
various  
Joerg Jaspert 已提交
616
                (fd, temp_filename) = utils.temp_filename(Cnf["Dir::Queue::BTSVersionTrack"], prefix=".")
J
fdopen  
Joerg Jaspert 已提交
617
                version_history = os.fdopen(fd, 'w')
618 619
                version_history.write(dsc["bts changelog"])
                version_history.close()
620
                filename = "%s/%s" % (Cnf["Dir::Queue::BTSVersionTrack"],
621 622
                                      changes_file[:-8]+".versions")
                os.rename(temp_filename, filename)
J
Joerg Jaspert 已提交
623
                os.chmod(filename, 0644)
624 625

            # Write out the binary -> source mapping.
J
various  
Joerg Jaspert 已提交
626
            (fd, temp_filename) = utils.temp_filename(Cnf["Dir::Queue::BTSVersionTrack"], prefix=".")
J
fdopen  
Joerg Jaspert 已提交
627
            debinfo = os.fdopen(fd, 'w')
J
Joerg Jaspert 已提交
628 629
            for file_entry in file_keys:
                f = files[file_entry]
630 631 632
                if f["type"] == "deb":
                    line = " ".join([f["package"], f["version"],
                                     f["architecture"], f["source package"],
633 634 635
                                     f["source version"]])
                    debinfo.write(line+"\n")
            debinfo.close()
636
            filename = "%s/%s" % (Cnf["Dir::Queue::BTSVersionTrack"],
637 638
                                  changes_file[:-8]+".debinfo")
            os.rename(temp_filename, filename)
J
Joerg Jaspert 已提交
639
            os.chmod(filename, 0644)
640

J
NEW  
Joerg Jaspert 已提交
641 642 643 644 645 646 647 648 649 650 651 652
        # Its is Cnf["Dir::Queue::Accepted"] here, not targetdir!
        # <Ganneff> we do call queue_build too
        # <mhy> well yes, we'd have had to if we were inserting into accepted
        # <Ganneff> now. thats database only.
        # <mhy> urgh, that's going to get messy
        # <Ganneff> so i make the p-n call to it *also* using accepted/
        # <mhy> but then the packages will be in the queue_build table without the files being there
        # <Ganneff> as the buildd queue is only regenerated whenever unchecked runs
        # <mhy> ah, good point
        # <Ganneff> so it will work out, as unchecked move it over
        # <mhy> that's all completely sick
        # <Ganneff> yes
653 654 655 656 657
        self.queue_build("accepted", Cnf["Dir::Queue::Accepted"])

    ###########################################################################

    def queue_build (self, queue, path):
J
Joerg Jaspert 已提交
658 659 660 661 662 663 664 665 666 667
        """
        Prepare queue_build database table used for incoming autobuild support.

        @type queue: string
        @param queue: queue name

        @type path: string
        @param path: path for the queue file entries/link destinations
        """

668 669 670 671 672 673 674 675
        Cnf = self.Cnf
        Subst = self.Subst
        files = self.pkg.files
        changes = self.pkg.changes
        changes_file = self.pkg.changes_file
        dsc = self.pkg.dsc
        file_keys = files.keys()

676
        ## Special support to enable clean auto-building of queued packages
677
        queue_id = database.get_or_set_queue_id(queue)
678

679
        self.projectB.query("BEGIN WORK")
680
        for suite in changes["distribution"].keys():
681
            if suite not in Cnf.ValueList("Dinstall::QueueBuildSuites"):
682
                continue
683
            suite_id = database.get_suite_id(suite)
684
            dest_dir = Cnf["Dir::QueueBuild"]
685
            if Cnf.FindB("Dinstall::SecurityQueueBuild"):
686
                dest_dir = os.path.join(dest_dir, suite)
J
Joerg Jaspert 已提交
687 688 689
            for file_entry in file_keys:
                src = os.path.join(path, file_entry)
                dest = os.path.join(dest_dir, file_entry)
690
                if Cnf.FindB("Dinstall::SecurityQueueBuild"):
691
                    # Copy it since the original won't be readable by www-data
692
                    utils.copy(src, dest)
693 694
                else:
                    # Create a symlink to it
695
                    os.symlink(src, dest)
J
James Troup 已提交
696
                # Add it to the list of packages for later processing by apt-ftparchive
697
                self.projectB.query("INSERT INTO queue_build (suite, queue, filename, in_queue) VALUES (%s, %s, '%s', 't')" % (suite_id, queue_id, dest))
J
James Troup 已提交
698 699
            # If the .orig.tar.gz is in the pool, create a symlink to
            # it (if one doesn't already exist)
700 701 702
            if self.pkg.orig_tar_id:
                # Determine the .orig.tar.gz file name
                for dsc_file in self.pkg.dsc_files.keys():
703
                    if dsc_file.endswith(".orig.tar.gz"):
704 705
                        filename = dsc_file
                dest = os.path.join(dest_dir, filename)
706 707 708
                # If it doesn't exist, create a symlink
                if not os.path.exists(dest):
                    # Find the .orig.tar.gz in the pool
709 710
                    q = self.projectB.query("SELECT l.path, f.filename from location l, files f WHERE f.id = %s and f.location = l.id" % (self.pkg.orig_tar_id))
                    ql = q.getresult()
711
                    if not ql:
712 713 714
                        utils.fubar("[INTERNAL ERROR] Couldn't find id %s in files table." % (self.pkg.orig_tar_id))
                    src = os.path.join(ql[0][0], ql[0][1])
                    os.symlink(src, dest)
J
James Troup 已提交
715
                    # Add it to the list of packages for later processing by apt-ftparchive
716
                    self.projectB.query("INSERT INTO queue_build (suite, queue, filename, in_queue) VALUES (%s, %s, '%s', 't')" % (suite_id, queue_id, dest))
717 718
                # if it does, update things to ensure it's not removed prematurely
                else:
719
                    self.projectB.query("UPDATE queue_build SET in_queue = 't', last_used = NULL WHERE filename = '%s' AND suite = %s" % (dest, suite_id))
J
James Troup 已提交
720

721
        self.projectB.query("COMMIT WORK")
722

J
New.  
James Troup 已提交
723 724 725
    ###########################################################################

    def check_override (self):
J
Joerg Jaspert 已提交
726 727 728 729 730 731 732 733 734 735
        """
        Checks override entries for validity. Mails "Override disparity" warnings,
        if that feature is enabled.

        Abandons the check if
          - this is a non-sourceful upload
          - override disparity checks are disabled
          - mail sending is disabled

        """
736 737 738 739
        Subst = self.Subst
        changes = self.pkg.changes
        files = self.pkg.files
        Cnf = self.Cnf
J
New.  
James Troup 已提交
740

741 742 743 744 745 746 747
        # Abandon the check if:
        #  a) it's a non-sourceful upload
        #  b) override disparity checks have been disabled
        #  c) we're not sending mail
        if not changes["architecture"].has_key("source") or \
           not Cnf.FindB("Dinstall::OverrideDisparityCheck") or \
           Cnf["Dinstall::Options::No-Mail"]:
748
            return
J
New.  
James Troup 已提交
749

750 751 752
        summary = ""
        file_keys = files.keys()
        file_keys.sort()
J
Joerg Jaspert 已提交
753 754 755 756
        for file_entry in file_keys:
            if not files[file_entry].has_key("new") and files[file_entry]["type"] == "deb":
                section = files[file_entry]["section"]
                override_section = files[file_entry]["override section"]
757
                if section.lower() != override_section.lower() and section != "-":
J
Joerg Jaspert 已提交
758 759 760
                    summary += "%s: package says section is %s, override says %s.\n" % (file_entry, section, override_section)
                priority = files[file_entry]["priority"]
                override_priority = files[file_entry]["override priority"]
J
New.  
James Troup 已提交
761
                if priority != override_priority and priority != "-":
J
Joerg Jaspert 已提交
762
                    summary += "%s: package says priority is %s, override says %s.\n" % (file_entry, priority, override_priority)
J
New.  
James Troup 已提交
763 764

        if summary == "":
765
            return
J
New.  
James Troup 已提交
766

767
        Subst["__SUMMARY__"] = summary
768
        mail_message = utils.TemplateSubst(Subst,self.Cnf["Dir::Templates"]+"/process-unchecked.override-disparity")
769
        utils.send_mail(mail_message)
J
New.  
James Troup 已提交
770 771 772

    ###########################################################################

773
    def force_reject (self, files):
J
Joerg Jaspert 已提交
774 775 776 777 778 779 780 781 782 783
        """
        Forcefully move files from the current directory to the
        reject directory.  If any file already exists in the reject
        directory it will be moved to the morgue to make way for
        the new file.

        @type files: dict
        @param files: file dictionary

        """
J
New.  
James Troup 已提交
784 785 786

        Cnf = self.Cnf

J
Joerg Jaspert 已提交
787
        for file_entry in files:
J
New.  
James Troup 已提交
788
            # Skip any files which don't exist or which we don't have permission to copy.
J
Joerg Jaspert 已提交
789
            if os.access(file_entry,os.R_OK) == 0:
790
                continue
J
Joerg Jaspert 已提交
791
            dest_file = os.path.join(Cnf["Dir::Queue::Reject"], file_entry)
J
New.  
James Troup 已提交
792
            try:
793
                dest_fd = os.open(dest_file, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
J
New.  
James Troup 已提交
794 795 796
            except OSError, e:
                # File exists?  Let's try and move it to the morgue
                if errno.errorcode[e.errno] == 'EEXIST':
J
Joerg Jaspert 已提交
797
                    morgue_file = os.path.join(Cnf["Dir::Morgue"],Cnf["Dir::MorgueReject"],file_entry)
J
New.  
James Troup 已提交
798
                    try:
799
                        morgue_file = utils.find_next_free(morgue_file)
J
Joerg Jaspert 已提交
800
                    except NoFreeFilenameError:
J
New.  
James Troup 已提交
801 802
                        # Something's either gone badly Pete Tong, or
                        # someone is trying to exploit us.
J
Joerg Jaspert 已提交
803
                        utils.warn("**WARNING** failed to move %s from the reject directory to the morgue." % (file_entry))
804 805
                        return
                    utils.move(dest_file, morgue_file, perms=0660)
J
New.  
James Troup 已提交
806
                    try:
807
                        dest_fd = os.open(dest_file, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
J
New.  
James Troup 已提交
808 809
                    except OSError, e:
                        # Likewise
J
Joerg Jaspert 已提交
810
                        utils.warn("**WARNING** failed to claim %s in the reject directory." % (file_entry))
811
                        return
J
New.  
James Troup 已提交
812
                else:
813
                    raise
J
New.  
James Troup 已提交
814 815
            # If we got here, we own the destination file, so we can
            # safely overwrite it.
J
Joerg Jaspert 已提交
816
            utils.move(file_entry, dest_file, 1, perms=0660)
817
            os.close(dest_fd)
818

J
New.  
James Troup 已提交
819 820
    ###########################################################################

J
Joerg Jaspert 已提交
821
    def do_reject (self, manual = 0, reject_message = "", note = ""):
J
Joerg Jaspert 已提交
822 823 824 825 826 827 828 829 830 831 832 833 834
        """
        Reject an upload. If called without a reject message or C{manual} is
        true, spawn an editor so the user can write one.

        @type manual: bool
        @param manual: manual or automated rejection

        @type reject_message: string
        @param reject_message: A reject message

        @return: 0

        """
J
James Troup 已提交
835 836 837
        # If we weren't given a manual rejection message, spawn an
        # editor so the user can add one in...
        if manual and not reject_message:
J
various  
Joerg Jaspert 已提交
838
            (fd, temp_filename) = utils.temp_filename()
J
Joerg Jaspert 已提交
839 840 841 842 843
            temp_file = os.fdopen(fd, 'w')
            if len(note) > 0:
                for line in note:
                    temp_file.write(line)
            temp_file.close()
J
James Troup 已提交
844
            editor = os.environ.get("EDITOR","vi")
845
            answer = 'E'
J
James Troup 已提交
846 847
            while answer == 'E':
                os.system("%s %s" % (editor, temp_filename))
848 849 850 851 852
                temp_fh = utils.open_file(temp_filename)
                reject_message = "".join(temp_fh.readlines())
                temp_fh.close()
                print "Reject message:"
                print utils.prefix_multi_line_string(reject_message,"  ",include_blank_lines=1)
J
James Troup 已提交
853
                prompt = "[R]eject, Edit, Abandon, Quit ?"
854
                answer = "XXX"
855
                while prompt.find(answer) == -1:
856 857
                    answer = utils.our_raw_input(prompt)
                    m = re_default_answer.search(prompt)
J
James Troup 已提交
858
                    if answer == "":
859 860 861
                        answer = m.group(1)
                    answer = answer[:1].upper()
            os.unlink(temp_filename)
J
James Troup 已提交
862
            if answer == 'A':
863
                return 1
J
James Troup 已提交
864
            elif answer == 'Q':
865
                sys.exit(0)
J
James Troup 已提交
866

J
New.  
James Troup 已提交
867 868
        print "Rejecting.\n"

869 870 871
        Cnf = self.Cnf
        Subst = self.Subst
        pkg = self.pkg
J
New.  
James Troup 已提交
872

873 874
        reason_filename = pkg.changes_file[:-8] + ".reason"
        reason_filename = Cnf["Dir::Queue::Reject"] + '/' + reason_filename
J
New.  
James Troup 已提交
875 876

        # Move all the files into the reject directory
877 878
        reject_files = pkg.files.keys() + [pkg.changes_file]
        self.force_reject(reject_files)
J
New.  
James Troup 已提交
879 880 881

        # If we fail here someone is probably trying to exploit the race
        # so let's just raise an exception ...
J
James Troup 已提交
882
        if os.path.exists(reason_filename):
883 884
            os.unlink(reason_filename)
        reason_fd = os.open(reason_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
J
New.  
James Troup 已提交
885 886

        if not manual:
887 888
            Subst["__REJECTOR_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"]
            Subst["__MANUAL_REJECT_MESSAGE__"] = ""
889
            Subst["__CC__"] = "X-DAK-Rejection: automatic (moo)\nX-Katie-Rejection: automatic (moo)"
890
            os.write(reason_fd, reject_message)
891
            reject_mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/queue.rejected")
J
New.  
James Troup 已提交
892 893
        else:
            # Build up the rejection email
894
            user_email_address = utils.whoami() + " <%s>" % (Cnf["Dinstall::MyAdminAddress"])
J
New.  
James Troup 已提交
895

896 897 898
            Subst["__REJECTOR_ADDRESS__"] = user_email_address
            Subst["__MANUAL_REJECT_MESSAGE__"] = reject_message
            Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"]
899
            reject_mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/queue.rejected")
J
New.  
James Troup 已提交
900
            # Write the rejection email out as the <foo>.reason file
901
            os.write(reason_fd, reject_mail_message)
J
James Troup 已提交
902

903
        os.close(reason_fd)
J
New.  
James Troup 已提交
904 905 906

        # Send the rejection mail if appropriate
        if not Cnf["Dinstall::Options::No-Mail"]:
907
            utils.send_mail(reject_mail_message)
J
New.  
James Troup 已提交
908

909 910
        self.Logger.log(["rejected", pkg.changes_file])
        return 0
J
New.  
James Troup 已提交
911 912 913

    ################################################################################

914
    def source_exists (self, package, source_version, suites = ["any"]):
J
Joerg Jaspert 已提交
915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933
        """
        Ensure that source exists somewhere in the archive for the binary
        upload being processed.
          1. exact match     => 1.0-3
          2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1

        @type package: string
        @param package: package source name

        @type source_version: string
        @param source_version: expected source version

        @type suites: list
        @param suites: list of suites to check in, default I{any}

        @rtype: int
        @return: returns 1 if a source with expected version is found, otherwise 0

        """
934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953
        okay = 1
        for suite in suites:
            if suite == "any":
                que = "SELECT s.version FROM source s WHERE s.source = '%s'" % \
                    (package)
            else:
                # source must exist in suite X, or in some other suite that's
                # mapped to X, recursively... silent-maps are counted too,
                # unreleased-maps aren't.
                maps = self.Cnf.ValueList("SuiteMappings")[:]
                maps.reverse()
                maps = [ m.split() for m in maps ]
                maps = [ (x[1], x[2]) for x in maps
                                if x[0] == "map" or x[0] == "silent-map" ]
                s = [suite]
                for x in maps:
                    if x[1] in s and x[0] not in s:
                        s.append(x[0])

                que = "SELECT s.version FROM source s JOIN src_associations sa ON (s.id = sa.source) JOIN suite su ON (sa.suite = su.id) WHERE s.source = '%s' AND (%s)" % (package, " OR ".join(["su.suite_name = '%s'" % a for a in s]))
954 955 956
            q = self.projectB.query(que)

            # Reduce the query results to a list of version numbers
957
            ql = [ i[0] for i in q.getresult() ]
958 959

            # Try (1)
960
            if source_version in ql:
961 962 963
                continue

            # Try (2)
964
            orig_source_version = re_bin_only_nmu.sub('', source_version)
965
            if orig_source_version in ql:
966 967 968 969
                continue

            # No source found...
            okay = 0
970 971
            break
        return okay
J
New.  
James Troup 已提交
972 973

    ################################################################################
974

J
New.  
James Troup 已提交
975
    def in_override_p (self, package, component, suite, binary_type, file):
J
Joerg Jaspert 已提交
976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996
        """
        Check if a package already has override entries in the DB

        @type package: string
        @param package: package name

        @type component: string
        @param component: database id of the component, as returned by L{database.get_component_id}

        @type suite: int
        @param suite: database id of the suite, as returned by L{database.get_suite_id}

        @type binary_type: string
        @param binary_type: type of the package

        @type file: string
        @param file: filename we check

        @return: the database result. But noone cares anyway.

        """
997
        files = self.pkg.files
J
New.  
James Troup 已提交
998 999

        if binary_type == "": # must be source
J
Joerg Jaspert 已提交
1000
            file_type = "dsc"
J
New.  
James Troup 已提交
1001
        else:
J
Joerg Jaspert 已提交
1002
            file_type = binary_type
J
New.  
James Troup 已提交
1003 1004 1005

        # Override suite name; used for example with proposed-updates
        if self.Cnf.Find("Suite::%s::OverrideSuite" % (suite)) != "":
1006
            suite = self.Cnf["Suite::%s::OverrideSuite" % (suite)]
J
New.  
James Troup 已提交
1007 1008

        # Avoid <undef> on unknown distributions
1009
        suite_id = database.get_suite_id(suite)
J
New.  
James Troup 已提交
1010
        if suite_id == -1:
1011
            return None
1012
        component_id = database.get_component_id(component)
J
Joerg Jaspert 已提交
1013
        type_id = database.get_override_type_id(file_type)
J
New.  
James Troup 已提交
1014 1015

        q = self.projectB.query("SELECT s.section, p.priority FROM override o, section s, priority p WHERE package = '%s' AND suite = %s AND component = %s AND type = %s AND o.section = s.id AND o.priority = p.id"
1016 1017
                           % (package, suite_id, component_id, type_id))
        result = q.getresult()
J
New.  
James Troup 已提交
1018
        # If checking for a source package fall back on the binary override type
J
Joerg Jaspert 已提交
1019
        if file_type == "dsc" and not result:
1020 1021
            deb_type_id = database.get_override_type_id("deb")
            udeb_type_id = database.get_override_type_id("udeb")
1022
            q = self.projectB.query("SELECT s.section, p.priority FROM override o, section s, priority p WHERE package = '%s' AND suite = %s AND component = %s AND (type = %s OR type = %s) AND o.section = s.id AND o.priority = p.id"
1023 1024
                               % (package, suite_id, component_id, deb_type_id, udeb_type_id))
            result = q.getresult()
J
New.  
James Troup 已提交
1025 1026

        # Remember the section and priority so we can check them later if appropriate
1027
        if result:
1028 1029
            files[file]["override section"] = result[0][0]
            files[file]["override priority"] = result[0][1]
J
New.  
James Troup 已提交
1030

1031
        return result
J
New.  
James Troup 已提交
1032 1033 1034 1035

    ################################################################################

    def reject (self, str, prefix="Rejected: "):
J
Joerg Jaspert 已提交
1036 1037 1038 1039 1040 1041 1042 1043 1044 1045
        """
        Add C{str} to reject_message. Adds C{prefix}, by default "Rejected: "

        @type str: string
        @param str: Reject text

        @type prefix: string
        @param prefix: Prefix text, default Rejected:

        """
J
New.  
James Troup 已提交
1046
        if str:
1047 1048 1049
            # Unlike other rejects we add new lines first to avoid trailing
            # new lines when this message is passed back up to a caller.
            if self.reject_message:
1050 1051
                self.reject_message += "\n"
            self.reject_message += prefix + str
J
New.  
James Troup 已提交
1052

1053 1054
    ################################################################################

1055
    def get_anyversion(self, query_result, suite):
J
Joerg Jaspert 已提交
1056
        """ """
1057 1058 1059
        anyversion=None
        anysuite = [suite] + self.Cnf.ValueList("Suite::%s::VersionChecks::Enhances" % (suite))
        for (v, s) in query_result:
1060
            if s in [ x.lower() for x in anysuite ]:
1061 1062 1063 1064 1065 1066
                if not anyversion or apt_pkg.VersionCompare(anyversion, v) <= 0:
                    anyversion=v
        return anyversion

    ################################################################################

1067 1068
    def cross_suite_version_check(self, query_result, file, new_version,
            sourceful=False):
J
Joerg Jaspert 已提交
1069 1070
        """
        Ensure versions are newer than existing packages in target
1071
        suites and that cross-suite version checking rules as
J
Joerg Jaspert 已提交
1072 1073 1074
        set out in the conf file are satisfied.

        """
1075 1076 1077

        # Check versions for each target suite
        for target_suite in self.pkg.changes["distribution"].keys():
1078 1079
            must_be_newer_than = [ i.lower() for i in self.Cnf.ValueList("Suite::%s::VersionChecks::MustBeNewerThan" % (target_suite)) ]
            must_be_older_than = [ i.lower() for i in self.Cnf.ValueList("Suite::%s::VersionChecks::MustBeOlderThan" % (target_suite)) ]
1080 1081
            # Enforce "must be newer than target suite" even if conffile omits it
            if target_suite not in must_be_newer_than:
1082
                must_be_newer_than.append(target_suite)
1083
            for entry in query_result:
1084 1085
                existent_version = entry[0]
                suite = entry[1]
1086
                if suite in must_be_newer_than and sourceful and \
1087
                   apt_pkg.VersionCompare(new_version, existent_version) < 1:
1088
                    self.reject("%s: old version (%s) in %s >= new version (%s) targeted at %s." % (file, existent_version, suite, new_version, target_suite))
1089
                if suite in must_be_older_than and \
1090
                   apt_pkg.VersionCompare(new_version, existent_version) > -1:
1091 1092 1093
                    ch = self.pkg.changes
                    cansave = 0
                    if ch.get('distribution-version', {}).has_key(suite):
1094
                    # we really use the other suite, ignoring the conflicting one ...
1095
                        addsuite = ch["distribution-version"][suite]
1096

1097 1098
                        add_version = self.get_anyversion(query_result, addsuite)
                        target_version = self.get_anyversion(query_result, target_suite)
1099

1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110
                        if not add_version:
                            # not add_version can only happen if we map to a suite
                            # that doesn't enhance the suite we're propup'ing from.
                            # so "propup-ver x a b c; map a d" is a problem only if
                            # d doesn't enhance a.
                            #
                            # i think we could always propagate in this case, rather
                            # than complaining. either way, this isn't a REJECT issue
                            #
                            # And - we really should complain to the dorks who configured dak
                            self.reject("%s is mapped to, but not enhanced by %s - adding anyways" % (suite, addsuite), "Warning: ")
1111 1112
                            self.pkg.changes.setdefault("propdistribution", {})
                            self.pkg.changes["propdistribution"][addsuite] = 1
1113 1114 1115 1116 1117 1118 1119 1120
                            cansave = 1
                        elif not target_version:
                            # not targets_version is true when the package is NEW
                            # we could just stick with the "...old version..." REJECT
                            # for this, I think.
                            self.reject("Won't propogate NEW packages.")
                        elif apt_pkg.VersionCompare(new_version, add_version) < 0:
                            # propogation would be redundant. no need to reject though.
1121
                            self.reject("ignoring versionconflict: %s: old version (%s) in %s <= new version (%s) targeted at %s." % (file, existent_version, suite, new_version, target_suite), "Warning: ")
1122 1123
                            cansave = 1
                        elif apt_pkg.VersionCompare(new_version, add_version) > 0 and \
1124
                             apt_pkg.VersionCompare(add_version, target_version) >= 0:
1125
                            # propogate!!
1126 1127 1128
                            self.reject("Propogating upload to %s" % (addsuite), "Warning: ")
                            self.pkg.changes.setdefault("propdistribution", {})
                            self.pkg.changes["propdistribution"][addsuite] = 1
1129
                            cansave = 1
1130

1131
                    if not cansave:
1132
                        self.reject("%s: old version (%s) in %s <= new version (%s) targeted at %s." % (file, existent_version, suite, new_version, target_suite))
1133 1134 1135 1136

    ################################################################################

    def check_binary_against_db(self, file):
J
Joerg Jaspert 已提交
1137 1138 1139
        """

        """
1140 1141
        self.reject_message = ""
        files = self.pkg.files
J
New.  
James Troup 已提交
1142

1143 1144 1145 1146 1147 1148 1149
        # Ensure version is sane
        q = self.projectB.query("""
SELECT b.version, su.suite_name FROM binaries b, bin_associations ba, suite su,
                                     architecture a
 WHERE b.package = '%s' AND (a.arch_string = '%s' OR a.arch_string = 'all')
   AND ba.bin = b.id AND ba.suite = su.id AND b.architecture = a.id"""
                                % (files[file]["package"],
1150
                                   files[file]["architecture"]))
1151 1152
        self.cross_suite_version_check(q.getresult(), file,
            files[file]["version"], sourceful=False)
1153

J
New.  
James Troup 已提交
1154
        # Check for any existing copies of the file
1155 1156 1157 1158 1159 1160 1161 1162
        q = self.projectB.query("""
SELECT b.id FROM binaries b, architecture a
 WHERE b.package = '%s' AND b.version = '%s' AND a.arch_string = '%s'
   AND a.id = b.architecture"""
                                % (files[file]["package"],
                                   files[file]["version"],
                                   files[file]["architecture"]))
        if q.getresult():
1163
            self.reject("%s: can not overwrite existing copy already in the archive." % (file))
J
New.  
James Troup 已提交
1164

1165
        return self.reject_message
J
New.  
James Troup 已提交
1166 1167 1168 1169

    ################################################################################

    def check_source_against_db(self, file):
J
Joerg Jaspert 已提交
1170 1171
        """
        """
1172 1173
        self.reject_message = ""
        dsc = self.pkg.dsc
J
New.  
James Troup 已提交
1174

1175 1176 1177
        # Ensure version is sane
        q = self.projectB.query("""
SELECT s.version, su.suite_name FROM source s, src_associations sa, suite su
1178
 WHERE s.source = '%s' AND sa.source = s.id AND sa.suite = su.id""" % (dsc.get("source")))
1179 1180
        self.cross_suite_version_check(q.getresult(), file, dsc.get("version"),
            sourceful=True)
1181

1182
        return self.reject_message
J
New.  
James Troup 已提交
1183 1184 1185

    ################################################################################

1186

J
New.  
James Troup 已提交
1187
    def check_dsc_against_db(self, file):
J
Joerg Jaspert 已提交
1188 1189 1190 1191 1192 1193 1194 1195 1196
        """

        @warning: NB: this function can remove entries from the 'files' index [if
         the .orig.tar.gz is a duplicate of the one in the archive]; if
         you're iterating over 'files' and call this function as part of
         the loop, be sure to add a check to the top of the loop to
         ensure you haven't just tried to dereference the deleted entry.

        """
1197 1198 1199 1200
        self.reject_message = ""
        files = self.pkg.files
        dsc_files = self.pkg.dsc_files
        self.pkg.orig_tar_gz = None
J
New.  
James Troup 已提交
1201 1202 1203 1204

        # Try and find all files mentioned in the .dsc.  This has
        # to work harder to cope with the multiple possible
        # locations of an .orig.tar.gz.
1205 1206
        # The ordering on the select is needed to pick the newest orig
        # when it exists in multiple places.
J
New.  
James Troup 已提交
1207
        for dsc_file in dsc_files.keys():
1208
            found = None
J
New.  
James Troup 已提交
1209
            if files.has_key(dsc_file):
1210 1211
                actual_md5 = files[dsc_file]["md5sum"]
                actual_size = int(files[dsc_file]["size"])
J
New.  
James Troup 已提交
1212 1213
                found = "%s in incoming" % (dsc_file)
                # Check the file does not already exist in the archive
1214
                q = self.projectB.query("SELECT f.size, f.md5sum, l.path, f.filename FROM files f, location l WHERE f.filename LIKE '%%%s%%' AND l.id = f.location ORDER BY f.id DESC" % (dsc_file))
1215
                ql = q.getresult()
1216 1217
                # Strip out anything that isn't '%s' or '/%s$'
                for i in ql:
1218
                    if i[3] != dsc_file and i[3][-(len(dsc_file)+1):] != '/'+dsc_file:
1219
                        ql.remove(i)
1220

1221
                # "[dak] has not broken them.  [dak] has fixed a
J
New.  
James Troup 已提交
1222 1223 1224 1225 1226 1227 1228 1229
                # brokenness.  Your crappy hack exploited a bug in
                # the old dinstall.
                #
                # "(Come on!  I thought it was always obvious that
                # one just doesn't release different files with
                # the same name and version.)"
                #                        -- ajk@ on d-devel@l.d.o

1230
                if ql:
1231
                    # Ignore exact matches for .orig.tar.gz
1232
                    match = 0
1233
                    if dsc_file.endswith(".orig.tar.gz"):
1234
                        for i in ql:
1235 1236
                            if files.has_key(dsc_file) and \
                               int(files[dsc_file]["size"]) == int(i[0]) and \
1237
                               files[dsc_file]["md5sum"] == i[1]:
1238 1239 1240 1241
                                self.reject("ignoring %s, since it's already in the archive." % (dsc_file), "Warning: ")
                                del files[dsc_file]
                                self.pkg.orig_tar_gz = i[2] + i[3]
                                match = 1
1242 1243

                    if not match:
1244
                        self.reject("can not overwrite existing copy of '%s' already in the archive." % (dsc_file))
1245
            elif dsc_file.endswith(".orig.tar.gz"):
J
New.  
James Troup 已提交
1246
                # Check in the pool
1247 1248
                q = self.projectB.query("SELECT l.path, f.filename, l.type, f.id, l.id FROM files f, location l WHERE f.filename LIKE '%%%s%%' AND l.id = f.location" % (dsc_file))
                ql = q.getresult()
1249 1250 1251
                # Strip out anything that isn't '%s' or '/%s$'
                for i in ql:
                    if i[1] != dsc_file and i[1][-(len(dsc_file)+1):] != '/'+dsc_file:
1252
                        ql.remove(i)
J
New.  
James Troup 已提交
1253

1254
                if ql:
1255 1256 1257
                    # Unfortunately, we may get more than one match here if,
                    # for example, the package was in potato but had an -sa
                    # upload in woody.  So we need to choose the right one.
J
New.  
James Troup 已提交
1258

J
Various  
Joerg Jaspert 已提交
1259 1260
                    # default to something sane in case we don't match any or have only one
                    x = ql[0]
J
New.  
James Troup 已提交
1261 1262 1263

                    if len(ql) > 1:
                        for i in ql:
1264
                            old_file = i[0] + i[1]
1265
                            old_file_fh = utils.open_file(old_file)
1266
                            actual_md5 = apt_pkg.md5sum(old_file_fh)
1267
                            old_file_fh.close()
1268
                            actual_size = os.stat(old_file)[stat.ST_SIZE]
J
New.  
James Troup 已提交
1269
                            if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
1270
                                x = i
J
New.  
James Troup 已提交
1271

1272
                    old_file = x[0] + x[1]
1273
                    old_file_fh = utils.open_file(old_file)
1274
                    actual_md5 = apt_pkg.md5sum(old_file_fh)
1275
                    old_file_fh.close()
1276 1277 1278
                    actual_size = os.stat(old_file)[stat.ST_SIZE]
                    found = old_file
                    suite_type = x[2]
J
Various  
Joerg Jaspert 已提交
1279 1280
                    # need this for updating dsc_files in install()
                    dsc_files[dsc_file]["files id"] = x[3]
1281
                    # See install() in process-accepted...
1282 1283
                    self.pkg.orig_tar_id = x[3]
                    self.pkg.orig_tar_gz = old_file
J
legacy  
Joerg Jaspert 已提交
1284
                    self.pkg.orig_tar_location = x[4]
J
New.  
James Troup 已提交
1285
                else:
1286
                    # Not there? Check the queue directories...
J
New.  
James Troup 已提交
1287

1288
                    in_unchecked = os.path.join(self.Cnf["Dir::Queue::Unchecked"],dsc_file)
1289
                    # See process_it() in 'dak process-unchecked' for explanation of this
1290 1291
                    # in_unchecked check dropped by ajt 2007-08-28, how did that
                    # ever make sense?
1292
                    if os.path.exists(in_unchecked) and False:
1293
                        return (self.reject_message, in_unchecked)
J
New.  
James Troup 已提交
1294
                    else:
J
queue  
Joerg Jaspert 已提交
1295
                        for directory in [ "Accepted", "New", "Byhand", "ProposedUpdates", "OldProposedUpdates", "Embargoed", "Unembargoed" ]:
J
Joerg Jaspert 已提交
1296
                            in_otherdir = os.path.join(self.Cnf["Dir::Queue::%s" % (directory)],dsc_file)
1297
                            if os.path.exists(in_otherdir):
1298
                                in_otherdir_fh = utils.open_file(in_otherdir)
1299
                                actual_md5 = apt_pkg.md5sum(in_otherdir_fh)
1300
                                in_otherdir_fh.close()
1301 1302 1303
                                actual_size = os.stat(in_otherdir)[stat.ST_SIZE]
                                found = in_otherdir
                                self.pkg.orig_tar_gz = in_otherdir
1304 1305

                    if not found:
1306 1307 1308
                        self.reject("%s refers to %s, but I can't find it in the queue or in the pool." % (file, dsc_file))
                        self.pkg.orig_tar_gz = -1
                        continue
J
New.  
James Troup 已提交
1309
            else:
1310 1311
                self.reject("%s refers to %s, but I can't find it in the queue." % (file, dsc_file))
                continue
J
New.  
James Troup 已提交
1312
            if actual_md5 != dsc_files[dsc_file]["md5sum"]:
1313
                self.reject("md5sum for %s doesn't match %s." % (found, file))
J
New.  
James Troup 已提交
1314
            if actual_size != int(dsc_files[dsc_file]["size"]):
1315
                self.reject("size for %s doesn't match %s." % (found, file))
J
New.  
James Troup 已提交
1316

1317
        return (self.reject_message, None)