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

4 5
# Queue utility functions for dak
# Copyright (C) 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
J
New.  
James Troup 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

# 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

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

23
import cPickle, errno, os, pg, re, stat, sys, time
24
import apt_inst, apt_pkg
25
import utils, database
J
Joerg Jaspert 已提交
26
from dak_exceptions import *
J
New.  
James Troup 已提交
27

28
from types import *
J
New.  
James Troup 已提交
29 30 31

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

32 33 34 35
re_isanum = re.compile (r"^\d+$")
re_default_answer = re.compile(r"\[(.*)\]")
re_fdnic = re.compile(r"\n\n")
re_bin_only_nmu = re.compile(r"\+b\d+$")
J
James Troup 已提交
36

37 38 39 40 41 42 43 44
################################################################################

# Determine what parts in a .changes are NEW

def determine_new(changes, files, projectB, warn=1):
    new = {}

    # Build up a list of potentially new things
J
Joerg Jaspert 已提交
45 46
    for file_entry in files.keys():
        f = files[file_entry]
47 48 49 50 51 52
        # Skip byhand elements
        if f["type"] == "byhand":
            continue
        pkg = f["package"]
        priority = f["priority"]
        section = f["section"]
J
Joerg Jaspert 已提交
53
        file_type = get_type(f)
54 55
        component = f["component"]

J
Joerg Jaspert 已提交
56
        if file_type == "dsc":
57 58 59 60 61
            priority = "source"
        if not new.has_key(pkg):
            new[pkg] = {}
            new[pkg]["priority"] = priority
            new[pkg]["section"] = section
J
Joerg Jaspert 已提交
62
            new[pkg]["type"] = file_type
63 64 65 66
            new[pkg]["component"] = component
            new[pkg]["files"] = []
        else:
            old_type = new[pkg]["type"]
J
Joerg Jaspert 已提交
67
            if old_type != file_type:
68 69 70 71
                # source gets trumped by deb or udeb
                if old_type == "dsc":
                    new[pkg]["priority"] = priority
                    new[pkg]["section"] = section
J
Joerg Jaspert 已提交
72
                    new[pkg]["type"] = file_type
73
                    new[pkg]["component"] = component
J
Joerg Jaspert 已提交
74
        new[pkg]["files"].append(file_entry)
75 76 77 78 79 80 81 82 83 84 85
        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 已提交
86 87 88
                for file_entry in new[pkg]["files"]:
                    if files[file_entry].has_key("new"):
                        del files[file_entry]["new"]
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106
                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

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

def get_type(f):
    # Determine the type
    if f.has_key("dbtype"):
J
Joerg Jaspert 已提交
107
        file_type = f["dbtype"]
108
    elif f["type"] in [ "orig.tar.gz", "orig.tar.bz2", "tar.gz", "tar.bz2", "diff.gz", "diff.bz2", "dsc" ]:
J
Joerg Jaspert 已提交
109
        file_type = "dsc"
110
    else:
J
Joerg Jaspert 已提交
111
        utils.fubar("invalid type (%s) for new.  Dazed, confused and sure as heck not continuing." % (file_type))
112 113

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

J
Joerg Jaspert 已提交
118
    return file_type
119 120 121 122 123 124 125 126 127

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

# check if section/priority values are valid

def check_valid(new):
    for pkg in new.keys():
        section = new[pkg]["section"]
        priority = new[pkg]["priority"]
J
Joerg Jaspert 已提交
128
        file_type = new[pkg]["type"]
129 130 131 132
        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
133
        if (di and file_type not in ("udeb", "dsc")) or (not di and file_type == "udeb"):
134
            new[pkg]["section id"] = -1
J
Joerg Jaspert 已提交
135 136
        if (priority == "source" and file_type != "dsc") or \
           (priority != "source" and file_type == "dsc"):
137 138 139
            new[pkg]["priority id"] = -1


J
New.  
James Troup 已提交
140 141 142 143 144 145
###############################################################################

# Convenience wrapper to carry around all the package information in

class Pkg:
    def __init__(self, **kwds):
146
        self.__dict__.update(kwds)
J
New.  
James Troup 已提交
147 148

    def update(self, **kwds):
149
        self.__dict__.update(kwds)
J
New.  
James Troup 已提交
150 151 152

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

153
class Upload:
J
New.  
James Troup 已提交
154 155

    def __init__(self, Cnf):
156 157 158
        self.Cnf = Cnf
        self.accept_count = 0
        self.accept_bytes = 0L
J
New.  
James Troup 已提交
159
        self.pkg = Pkg(changes = {}, dsc = {}, dsc_files = {}, files = {},
160
                       legacy_source_untouchable = {})
J
New.  
James Troup 已提交
161 162

        # Initialize the substitution template mapping global
163 164 165 166
        Subst = self.Subst = {}
        Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"]
        Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"]
        Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"]
167
        Subst["__DAK_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"]
J
New.  
James Troup 已提交
168

169
        self.projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
170
        database.init(Cnf, self.projectB)
J
New.  
James Troup 已提交
171 172 173 174

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

    def init_vars (self):
175 176 177 178 179
        self.pkg.changes.clear()
        self.pkg.dsc.clear()
        self.pkg.files.clear()
        self.pkg.dsc_files.clear()
        self.pkg.legacy_source_untouchable.clear()
180 181 182
        self.pkg.orig_tar_id = None
        self.pkg.orig_tar_location = ""
        self.pkg.orig_tar_gz = None
J
New.  
James Troup 已提交
183 184 185 186

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

    def update_vars (self):
187
        dump_filename = self.pkg.changes_file[:-8]+".dak"
188 189
        dump_file = utils.open_file(dump_filename)
        p = cPickle.Unpickler(dump_file)
190 191 192 193 194 195 196 197 198 199

        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.legacy_source_untouchable.update(p.load())

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

200
        dump_file.close()
J
New.  
James Troup 已提交
201 202 203

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

204 205 206
    # 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
J
New.  
James Troup 已提交
207 208

    def dump_vars(self, dest_dir):
209 210 211 212 213 214 215 216 217

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

218
        dump_filename = os.path.join(dest_dir,self.pkg.changes_file[:-8] + ".dak")
219
        dump_file = utils.open_file(dump_filename, 'w')
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
        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 已提交
238

239
        p = cPickle.Pickler(dump_file, 1)
240 241 242 243 244
        d_changes = {}
        d_dsc = {}
        d_files = {}
        d_dsc_files = {}

J
New.  
James Troup 已提交
245
        ## files
J
Joerg Jaspert 已提交
246 247
        for file_entry in files.keys():
            d_files[file_entry] = {}
J
New.  
James Troup 已提交
248
            for i in [ "package", "version", "architecture", "type", "size",
P
Philipp Kern 已提交
249 250 251 252
                       "md5sum", "sha1sum", "sha256sum", "component",
                       "location id", "source package", "source version",
                       "maintainer", "dbtype", "files id", "new",
                       "section", "priority", "othercomponents",
253
                       "pool name", "original component" ]:
J
Joerg Jaspert 已提交
254 255
                if files[file_entry].has_key(i):
                    d_files[file_entry][i] = files[file_entry][i]
J
New.  
James Troup 已提交
256 257
        ## changes
        # Mandatory changes fields
258 259 260 261 262
        for i in [ "distribution", "source", "architecture", "version",
                   "maintainer", "urgency", "fingerprint", "changedby822",
                   "changedby2047", "changedbyname", "maintainer822",
                   "maintainer2047", "maintainername", "maintaineremail",
                   "closes", "changes" ]:
263
            d_changes[i] = changes[i]
J
New.  
James Troup 已提交
264
        # Optional changes fields
265 266
        for i in [ "changed-by", "filecontents", "format", "process-new note", "adv id", "distribution-version",
                   "sponsoremail" ]:
J
James Troup 已提交
267
            if changes.has_key(i):
268
                d_changes[i] = changes[i]
J
New.  
James Troup 已提交
269
        ## dsc
270
        for i in [ "source", "version", "maintainer", "fingerprint",
A
Anthony Towns 已提交
271
                   "uploaders", "bts changelog", "dm-upload-allowed" ]:
J
New.  
James Troup 已提交
272
            if dsc.has_key(i):
273
                d_dsc[i] = dsc[i]
J
New.  
James Troup 已提交
274
        ## dsc_files
J
Joerg Jaspert 已提交
275 276
        for file_entry in dsc_files.keys():
            d_dsc_files[file_entry] = {}
J
New.  
James Troup 已提交
277 278
            # Mandatory dsc_files fields
            for i in [ "size", "md5sum" ]:
J
Joerg Jaspert 已提交
279
                d_dsc_files[file_entry][i] = dsc_files[file_entry][i]
J
New.  
James Troup 已提交
280 281
            # Optional dsc_files fields
            for i in [ "files id" ]:
J
Joerg Jaspert 已提交
282 283
                if dsc_files[file_entry].has_key(i):
                    d_dsc_files[file_entry][i] = dsc_files[file_entry][i]
J
New.  
James Troup 已提交
284 285 286

        for i in [ d_changes, d_dsc, d_files, d_dsc_files,
                   legacy_source_untouchable, orig_tar_id, orig_tar_location ]:
287 288
            p.dump(i)
        dump_file.close()
J
New.  
James Troup 已提交
289 290 291 292 293 294

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

    # Set up the per-package template substitution mappings

    def update_subst (self, reject_message = ""):
295 296
        Subst = self.Subst
        changes = self.pkg.changes
297
        # If 'dak process-unchecked' crashed out in the right place, architecture may still be a string.
J
New.  
James Troup 已提交
298
        if not changes.has_key("architecture") or not isinstance(changes["architecture"], DictType):
299
            changes["architecture"] = { "Unknown" : "" }
300 301
        # and maintainer2047 may not exist.
        if not changes.has_key("maintainer2047"):
302
            changes["maintainer2047"] = self.Cnf["Dinstall::MyEmailAddress"]
J
New.  
James Troup 已提交
303

304 305 306
        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 已提交
307 308 309

        # 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"]):
310
            Subst["__MAINTAINER_FROM__"] = changes["changedby2047"]
311
            Subst["__MAINTAINER_TO__"] = "%s, %s" % (changes["changedby2047"],
312 313
                                                     changes["maintainer2047"])
            Subst["__MAINTAINER__"] = changes.get("changed-by", "Unknown")
J
New.  
James Troup 已提交
314
        else:
315 316 317
            Subst["__MAINTAINER_FROM__"] = changes["maintainer2047"]
            Subst["__MAINTAINER_TO__"] = changes["maintainer2047"]
            Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown")
J
Joerg Jaspert 已提交
318 319 320 321

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

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

325 326
        # Apply any global override of the Maintainer field
        if self.Cnf.get("Dinstall::OverrideMaintainer"):
327 328
            Subst["__MAINTAINER_TO__"] = self.Cnf["Dinstall::OverrideMaintainer"]
            Subst["__MAINTAINER_FROM__"] = self.Cnf["Dinstall::OverrideMaintainer"]
329

330 331 332
        Subst["__REJECT_MESSAGE__"] = reject_message
        Subst["__SOURCE__"] = changes.get("source", "Unknown")
        Subst["__VERSION__"] = changes.get("version", "Unknown")
J
New.  
James Troup 已提交
333 334 335 336

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

    def build_summaries(self):
337 338
        changes = self.pkg.changes
        files = self.pkg.files
J
New.  
James Troup 已提交
339

340
        byhand = summary = new = ""
J
New.  
James Troup 已提交
341 342 343 344

        # 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):
345
            changes["distribution"] = {}
J
New.  
James Troup 已提交
346

347
        override_summary ="";
348 349
        file_keys = files.keys()
        file_keys.sort()
J
Joerg Jaspert 已提交
350 351
        for file_entry in file_keys:
            if files[file_entry].has_key("byhand"):
J
New.  
James Troup 已提交
352
                byhand = 1
J
Joerg Jaspert 已提交
353 354
                summary += file_entry + " byhand\n"
            elif files[file_entry].has_key("new"):
J
New.  
James Troup 已提交
355
                new = 1
J
Joerg Jaspert 已提交
356 357 358 359 360
                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)
361
                    summary += apt_pkg.ParseSection(apt_inst.debExtractControl(deb_fh))["Description"] + '\n'
362
                    deb_fh.close()
J
New.  
James Troup 已提交
363
            else:
J
Joerg Jaspert 已提交
364 365 366 367 368 369
                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"]:
370 371
                    # (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 已提交
372 373 374
                    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 已提交
375

376
        short_summary = summary
J
New.  
James Troup 已提交
377 378

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

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

384 385
        summary += "\n\nOverride entries for your package:\n" + override_summary + "\n"

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

388
        return (summary, short_summary)
J
New.  
James Troup 已提交
389 390 391

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

392
    def close_bugs (self, summary, action):
393 394 395
        changes = self.pkg.changes
        Subst = self.Subst
        Cnf = self.Cnf
J
New.  
James Troup 已提交
396

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

399
        if not bugs:
400
            return summary
J
New.  
James Troup 已提交
401

402
        bugs.sort()
403 404 405 406 407 408 409
        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__"] = """
410 411 412 413
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
414
distribution."""
415 416 417 418 419 420
                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)
421
        summary += "\n"
422

423
        return summary
424 425 426 427

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

    def announce (self, short_summary, action):
428 429 430
        Subst = self.Subst
        Cnf = self.Cnf
        changes = self.pkg.changes
431 432 433

        # 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"):
434
            return ""
J
New.  
James Troup 已提交
435

436 437 438
        lists_done = {}
        summary = ""
        Subst["__SHORT_SUMMARY__"] = short_summary
439 440

        for dist in changes["distribution"].keys():
J
Joerg Jaspert 已提交
441 442
            announce_list = Cnf.Find("Suite::%s::Announce" % (dist))
            if announce_list == "" or lists_done.has_key(announce_list):
443
                continue
J
Joerg Jaspert 已提交
444 445
            lists_done[announce_list] = 1
            summary += "Announcing to %s\n" % (announce_list)
446 447

            if action:
J
Joerg Jaspert 已提交
448
                Subst["__ANNOUNCE_LIST_ADDRESS__"] = announce_list
449
                if Cnf.get("Dinstall::TrackingServer") and changes["architecture"].has_key("source"):
450
                    Subst["__ANNOUNCE_LIST_ADDRESS__"] = Subst["__ANNOUNCE_LIST_ADDRESS__"] + "\nBcc: %s@%s" % (changes["source"], Cnf["Dinstall::TrackingServer"])
451
                mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/process-unchecked.announce")
452
                utils.send_mail (mail_message)
453

J
James Troup 已提交
454
        if Cnf.FindB("Dinstall::CloseBugs"):
455
            summary = self.close_bugs(summary, action)
456

457
        return summary
J
New.  
James Troup 已提交
458 459 460 461

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

    def accept (self, summary, short_summary):
462 463 464 465 466 467
        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 已提交
468 469

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

472
        self.dump_vars(Cnf["Dir::Queue::Accepted"])
J
New.  
James Troup 已提交
473 474

        # Move all the files into the accepted directory
475 476
        utils.move(changes_file, Cnf["Dir::Queue::Accepted"])
        file_keys = files.keys()
J
Joerg Jaspert 已提交
477 478 479
        for file_entry in file_keys:
            utils.move(file_entry, Cnf["Dir::Queue::Accepted"])
            self.accept_bytes += float(files[file_entry]["size"])
480
        self.accept_count += 1
J
New.  
James Troup 已提交
481 482 483 484

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

491 492 493 494 495 496 497 498 499 500 501 502

        ## 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"):

                temp_filename = utils.temp_filename(Cnf["Dir::Queue::BTSVersionTrack"],
503 504 505 506
                                                    dotprefix=1, perms=0644)
                version_history = utils.open_file(temp_filename, 'w')
                version_history.write(dsc["bts changelog"])
                version_history.close()
507
                filename = "%s/%s" % (Cnf["Dir::Queue::BTSVersionTrack"],
508 509
                                      changes_file[:-8]+".versions")
                os.rename(temp_filename, filename)
510 511 512

            # Write out the binary -> source mapping.
            temp_filename = utils.temp_filename(Cnf["Dir::Queue::BTSVersionTrack"],
513 514
                                                dotprefix=1, perms=0644)
            debinfo = utils.open_file(temp_filename, 'w')
J
Joerg Jaspert 已提交
515 516
            for file_entry in file_keys:
                f = files[file_entry]
517 518 519
                if f["type"] == "deb":
                    line = " ".join([f["package"], f["version"],
                                     f["architecture"], f["source package"],
520 521 522
                                     f["source version"]])
                    debinfo.write(line+"\n")
            debinfo.close()
523
            filename = "%s/%s" % (Cnf["Dir::Queue::BTSVersionTrack"],
524 525
                                  changes_file[:-8]+".debinfo")
            os.rename(temp_filename, filename)
526

527 528 529 530 531
        self.queue_build("accepted", Cnf["Dir::Queue::Accepted"])

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

    def queue_build (self, queue, path):
532 533 534 535 536 537 538 539
        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()

540
        ## Special support to enable clean auto-building of queued packages
541
        queue_id = database.get_or_set_queue_id(queue)
542

543
        self.projectB.query("BEGIN WORK")
544
        for suite in changes["distribution"].keys():
545
            if suite not in Cnf.ValueList("Dinstall::QueueBuildSuites"):
546
                continue
547
            suite_id = database.get_suite_id(suite)
548
            dest_dir = Cnf["Dir::QueueBuild"]
549
            if Cnf.FindB("Dinstall::SecurityQueueBuild"):
550
                dest_dir = os.path.join(dest_dir, suite)
J
Joerg Jaspert 已提交
551 552 553
            for file_entry in file_keys:
                src = os.path.join(path, file_entry)
                dest = os.path.join(dest_dir, file_entry)
554
                if Cnf.FindB("Dinstall::SecurityQueueBuild"):
555
                    # Copy it since the original won't be readable by www-data
556
                    utils.copy(src, dest)
557 558
                else:
                    # Create a symlink to it
559
                    os.symlink(src, dest)
J
James Troup 已提交
560
                # Add it to the list of packages for later processing by apt-ftparchive
561
                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 已提交
562 563
            # If the .orig.tar.gz is in the pool, create a symlink to
            # it (if one doesn't already exist)
564 565 566
            if self.pkg.orig_tar_id:
                # Determine the .orig.tar.gz file name
                for dsc_file in self.pkg.dsc_files.keys():
567
                    if dsc_file.endswith(".orig.tar.gz"):
568 569
                        filename = dsc_file
                dest = os.path.join(dest_dir, filename)
570 571 572
                # If it doesn't exist, create a symlink
                if not os.path.exists(dest):
                    # Find the .orig.tar.gz in the pool
573 574
                    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()
575
                    if not ql:
576 577 578
                        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 已提交
579
                    # Add it to the list of packages for later processing by apt-ftparchive
580
                    self.projectB.query("INSERT INTO queue_build (suite, queue, filename, in_queue) VALUES (%s, %s, '%s', 't')" % (suite_id, queue_id, dest))
581 582
                # if it does, update things to ensure it's not removed prematurely
                else:
583
                    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 已提交
584

585
        self.projectB.query("COMMIT WORK")
586

J
New.  
James Troup 已提交
587 588 589
    ###########################################################################

    def check_override (self):
590 591 592 593
        Subst = self.Subst
        changes = self.pkg.changes
        files = self.pkg.files
        Cnf = self.Cnf
J
New.  
James Troup 已提交
594

595 596 597 598 599 600 601
        # 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"]:
602
            return
J
New.  
James Troup 已提交
603

604 605 606
        summary = ""
        file_keys = files.keys()
        file_keys.sort()
J
Joerg Jaspert 已提交
607 608 609 610
        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"]
611
                if section.lower() != override_section.lower() and section != "-":
J
Joerg Jaspert 已提交
612 613 614
                    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 已提交
615
                if priority != override_priority and priority != "-":
J
Joerg Jaspert 已提交
616
                    summary += "%s: package says priority is %s, override says %s.\n" % (file_entry, priority, override_priority)
J
New.  
James Troup 已提交
617 618

        if summary == "":
619
            return
J
New.  
James Troup 已提交
620

621
        Subst["__SUMMARY__"] = summary
622
        mail_message = utils.TemplateSubst(Subst,self.Cnf["Dir::Templates"]+"/process-unchecked.override-disparity")
623
        utils.send_mail(mail_message)
J
New.  
James Troup 已提交
624 625 626

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

627 628 629 630 631
    def force_reject (self, files):
        """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."""
J
New.  
James Troup 已提交
632 633 634

        Cnf = self.Cnf

J
Joerg Jaspert 已提交
635
        for file_entry in files:
J
New.  
James Troup 已提交
636
            # Skip any files which don't exist or which we don't have permission to copy.
J
Joerg Jaspert 已提交
637
            if os.access(file_entry,os.R_OK) == 0:
638
                continue
J
Joerg Jaspert 已提交
639
            dest_file = os.path.join(Cnf["Dir::Queue::Reject"], file_entry)
J
New.  
James Troup 已提交
640
            try:
641
                dest_fd = os.open(dest_file, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
J
New.  
James Troup 已提交
642 643 644
            except OSError, e:
                # File exists?  Let's try and move it to the morgue
                if errno.errorcode[e.errno] == 'EEXIST':
J
Joerg Jaspert 已提交
645
                    morgue_file = os.path.join(Cnf["Dir::Morgue"],Cnf["Dir::MorgueReject"],file_entry)
J
New.  
James Troup 已提交
646
                    try:
647
                        morgue_file = utils.find_next_free(morgue_file)
J
Joerg Jaspert 已提交
648
                    except NoFreeFilenameError:
J
New.  
James Troup 已提交
649 650
                        # Something's either gone badly Pete Tong, or
                        # someone is trying to exploit us.
J
Joerg Jaspert 已提交
651
                        utils.warn("**WARNING** failed to move %s from the reject directory to the morgue." % (file_entry))
652 653
                        return
                    utils.move(dest_file, morgue_file, perms=0660)
J
New.  
James Troup 已提交
654
                    try:
655
                        dest_fd = os.open(dest_file, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
J
New.  
James Troup 已提交
656 657
                    except OSError, e:
                        # Likewise
J
Joerg Jaspert 已提交
658
                        utils.warn("**WARNING** failed to claim %s in the reject directory." % (file_entry))
659
                        return
J
New.  
James Troup 已提交
660
                else:
661
                    raise
J
New.  
James Troup 已提交
662 663
            # If we got here, we own the destination file, so we can
            # safely overwrite it.
J
Joerg Jaspert 已提交
664
            utils.move(file_entry, dest_file, 1, perms=0660)
665
            os.close(dest_fd)
666

J
New.  
James Troup 已提交
667 668 669
    ###########################################################################

    def do_reject (self, manual = 0, reject_message = ""):
J
James Troup 已提交
670 671 672
        # 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:
673
            temp_filename = utils.temp_filename()
J
James Troup 已提交
674
            editor = os.environ.get("EDITOR","vi")
675
            answer = 'E'
J
James Troup 已提交
676 677
            while answer == 'E':
                os.system("%s %s" % (editor, temp_filename))
678 679 680 681 682
                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 已提交
683
                prompt = "[R]eject, Edit, Abandon, Quit ?"
684
                answer = "XXX"
685
                while prompt.find(answer) == -1:
686 687
                    answer = utils.our_raw_input(prompt)
                    m = re_default_answer.search(prompt)
J
James Troup 已提交
688
                    if answer == "":
689 690 691
                        answer = m.group(1)
                    answer = answer[:1].upper()
            os.unlink(temp_filename)
J
James Troup 已提交
692
            if answer == 'A':
693
                return 1
J
James Troup 已提交
694
            elif answer == 'Q':
695
                sys.exit(0)
J
James Troup 已提交
696

J
New.  
James Troup 已提交
697 698
        print "Rejecting.\n"

699 700 701
        Cnf = self.Cnf
        Subst = self.Subst
        pkg = self.pkg
J
New.  
James Troup 已提交
702

703 704
        reason_filename = pkg.changes_file[:-8] + ".reason"
        reason_filename = Cnf["Dir::Queue::Reject"] + '/' + reason_filename
J
New.  
James Troup 已提交
705 706

        # Move all the files into the reject directory
707 708
        reject_files = pkg.files.keys() + [pkg.changes_file]
        self.force_reject(reject_files)
J
New.  
James Troup 已提交
709 710 711

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

        if not manual:
717 718
            Subst["__REJECTOR_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"]
            Subst["__MANUAL_REJECT_MESSAGE__"] = ""
719
            Subst["__CC__"] = "X-DAK-Rejection: automatic (moo)\nX-Katie-Rejection: automatic (moo)"
720
            os.write(reason_fd, reject_message)
721
            reject_mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/queue.rejected")
J
New.  
James Troup 已提交
722 723
        else:
            # Build up the rejection email
724
            user_email_address = utils.whoami() + " <%s>" % (Cnf["Dinstall::MyAdminAddress"])
J
New.  
James Troup 已提交
725

726 727 728
            Subst["__REJECTOR_ADDRESS__"] = user_email_address
            Subst["__MANUAL_REJECT_MESSAGE__"] = reject_message
            Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"]
729
            reject_mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/queue.rejected")
J
New.  
James Troup 已提交
730
            # Write the rejection email out as the <foo>.reason file
731
            os.write(reason_fd, reject_mail_message)
J
James Troup 已提交
732

733
        os.close(reason_fd)
J
New.  
James Troup 已提交
734 735 736

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

739 740
        self.Logger.log(["rejected", pkg.changes_file])
        return 0
J
New.  
James Troup 已提交
741 742 743 744 745 746 747

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

    # Ensure that source exists somewhere in the archive for the binary
    # upload being processed.
    #
    # (1) exact match                      => 1.0-3
R
Ryan Murray 已提交
748
    # (2) Bin-only NMU                     => 1.0-3+b1 , 1.0-3.1+b1
J
New.  
James Troup 已提交
749

750
    def source_exists (self, package, source_version, suites = ["any"]):
751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770
        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]))
771 772 773
            q = self.projectB.query(que)

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

            # Try (1)
777
            if source_version in ql:
778 779 780
                continue

            # Try (2)
781
            orig_source_version = re_bin_only_nmu.sub('', source_version)
782
            if orig_source_version in ql:
783 784 785 786
                continue

            # No source found...
            okay = 0
787 788
            break
        return okay
J
New.  
James Troup 已提交
789 790

    ################################################################################
791

J
New.  
James Troup 已提交
792
    def in_override_p (self, package, component, suite, binary_type, file):
793
        files = self.pkg.files
J
New.  
James Troup 已提交
794 795

        if binary_type == "": # must be source
J
Joerg Jaspert 已提交
796
            file_type = "dsc"
J
New.  
James Troup 已提交
797
        else:
J
Joerg Jaspert 已提交
798
            file_type = binary_type
J
New.  
James Troup 已提交
799 800 801

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

        # Avoid <undef> on unknown distributions
805
        suite_id = database.get_suite_id(suite)
J
New.  
James Troup 已提交
806
        if suite_id == -1:
807
            return None
808
        component_id = database.get_component_id(component)
J
Joerg Jaspert 已提交
809
        type_id = database.get_override_type_id(file_type)
J
New.  
James Troup 已提交
810 811

        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"
812 813
                           % (package, suite_id, component_id, type_id))
        result = q.getresult()
J
New.  
James Troup 已提交
814
        # If checking for a source package fall back on the binary override type
J
Joerg Jaspert 已提交
815
        if file_type == "dsc" and not result:
816 817
            deb_type_id = database.get_override_type_id("deb")
            udeb_type_id = database.get_override_type_id("udeb")
818
            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"
819 820
                               % (package, suite_id, component_id, deb_type_id, udeb_type_id))
            result = q.getresult()
J
New.  
James Troup 已提交
821 822

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

827
        return result
J
New.  
James Troup 已提交
828 829 830 831 832

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

    def reject (self, str, prefix="Rejected: "):
        if str:
833 834 835
            # 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:
836 837
                self.reject_message += "\n"
            self.reject_message += prefix + str
J
New.  
James Troup 已提交
838

839 840
    ################################################################################

841 842 843 844
    def get_anyversion(self, query_result, suite):
        anyversion=None
        anysuite = [suite] + self.Cnf.ValueList("Suite::%s::VersionChecks::Enhances" % (suite))
        for (v, s) in query_result:
845
            if s in [ x.lower() for x in anysuite ]:
846 847 848 849 850 851
                if not anyversion or apt_pkg.VersionCompare(anyversion, v) <= 0:
                    anyversion=v
        return anyversion

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

852 853
    def cross_suite_version_check(self, query_result, file, new_version,
            sourceful=False):
854 855 856 857 858 859
        """Ensure versions are newer than existing packages in target
        suites and that cross-suite version checking rules as
        set out in the conf file are satisfied."""

        # Check versions for each target suite
        for target_suite in self.pkg.changes["distribution"].keys():
860 861
            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)) ]
862 863
            # Enforce "must be newer than target suite" even if conffile omits it
            if target_suite not in must_be_newer_than:
864
                must_be_newer_than.append(target_suite)
865
            for entry in query_result:
866 867
                existent_version = entry[0]
                suite = entry[1]
868
                if suite in must_be_newer_than and sourceful and \
869
                   apt_pkg.VersionCompare(new_version, existent_version) < 1:
870
                    self.reject("%s: old version (%s) in %s >= new version (%s) targeted at %s." % (file, existent_version, suite, new_version, target_suite))
871
                if suite in must_be_older_than and \
872
                   apt_pkg.VersionCompare(new_version, existent_version) > -1:
873 874 875
                    ch = self.pkg.changes
                    cansave = 0
                    if ch.get('distribution-version', {}).has_key(suite):
876
                    # we really use the other suite, ignoring the conflicting one ...
877
                        addsuite = ch["distribution-version"][suite]
878

879 880
                        add_version = self.get_anyversion(query_result, addsuite)
                        target_version = self.get_anyversion(query_result, target_suite)
881

882 883 884 885 886 887 888 889 890 891 892
                        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: ")
893 894
                            self.pkg.changes.setdefault("propdistribution", {})
                            self.pkg.changes["propdistribution"][addsuite] = 1
895 896 897 898 899 900 901 902
                            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.
903
                            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: ")
904 905
                            cansave = 1
                        elif apt_pkg.VersionCompare(new_version, add_version) > 0 and \
906
                             apt_pkg.VersionCompare(add_version, target_version) >= 0:
907
                            # propogate!!
908 909 910
                            self.reject("Propogating upload to %s" % (addsuite), "Warning: ")
                            self.pkg.changes.setdefault("propdistribution", {})
                            self.pkg.changes["propdistribution"][addsuite] = 1
911
                            cansave = 1
912

913
                    if not cansave:
914
                        self.reject("%s: old version (%s) in %s <= new version (%s) targeted at %s." % (file, existent_version, suite, new_version, target_suite))
915 916 917 918

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

    def check_binary_against_db(self, file):
919 920
        self.reject_message = ""
        files = self.pkg.files
J
New.  
James Troup 已提交
921

922 923 924 925 926 927 928
        # 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"],
929
                                   files[file]["architecture"]))
930 931
        self.cross_suite_version_check(q.getresult(), file,
            files[file]["version"], sourceful=False)
932

J
New.  
James Troup 已提交
933
        # Check for any existing copies of the file
934 935 936 937 938 939 940 941
        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():
942
            self.reject("%s: can not overwrite existing copy already in the archive." % (file))
J
New.  
James Troup 已提交
943

944
        return self.reject_message
J
New.  
James Troup 已提交
945 946 947 948

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

    def check_source_against_db(self, file):
949 950
        self.reject_message = ""
        dsc = self.pkg.dsc
J
New.  
James Troup 已提交
951

952 953 954
        # Ensure version is sane
        q = self.projectB.query("""
SELECT s.version, su.suite_name FROM source s, src_associations sa, suite su
955
 WHERE s.source = '%s' AND sa.source = s.id AND sa.suite = su.id""" % (dsc.get("source")))
956 957
        self.cross_suite_version_check(q.getresult(), file, dsc.get("version"),
            sourceful=True)
958

959
        return self.reject_message
J
New.  
James Troup 已提交
960 961 962

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

963 964 965 966 967
    # **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
J
James Troup 已提交
968
    # ensure you haven't just tried to dereference the deleted entry.
969 970
    # **WARNING**

J
New.  
James Troup 已提交
971
    def check_dsc_against_db(self, file):
972 973 974 975 976
        self.reject_message = ""
        files = self.pkg.files
        dsc_files = self.pkg.dsc_files
        legacy_source_untouchable = self.pkg.legacy_source_untouchable
        self.pkg.orig_tar_gz = None
J
New.  
James Troup 已提交
977 978 979 980

        # 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.
981 982
        # The ordering on the select is needed to pick the newest orig
        # when it exists in multiple places.
J
New.  
James Troup 已提交
983
        for dsc_file in dsc_files.keys():
984
            found = None
J
New.  
James Troup 已提交
985
            if files.has_key(dsc_file):
986 987
                actual_md5 = files[dsc_file]["md5sum"]
                actual_size = int(files[dsc_file]["size"])
J
New.  
James Troup 已提交
988 989
                found = "%s in incoming" % (dsc_file)
                # Check the file does not already exist in the archive
990
                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))
991
                ql = q.getresult()
992 993
                # Strip out anything that isn't '%s' or '/%s$'
                for i in ql:
994
                    if i[3] != dsc_file and i[3][-(len(dsc_file)+1):] != '/'+dsc_file:
995
                        ql.remove(i)
996

997
                # "[dak] has not broken them.  [dak] has fixed a
J
New.  
James Troup 已提交
998 999 1000 1001 1002 1003 1004 1005
                # 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

1006
                if ql:
1007
                    # Ignore exact matches for .orig.tar.gz
1008
                    match = 0
1009
                    if dsc_file.endswith(".orig.tar.gz"):
1010
                        for i in ql:
1011 1012
                            if files.has_key(dsc_file) and \
                               int(files[dsc_file]["size"]) == int(i[0]) and \
1013
                               files[dsc_file]["md5sum"] == i[1]:
1014 1015 1016 1017
                                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
1018 1019

                    if not match:
1020
                        self.reject("can not overwrite existing copy of '%s' already in the archive." % (dsc_file))
1021
            elif dsc_file.endswith(".orig.tar.gz"):
J
New.  
James Troup 已提交
1022
                # Check in the pool
1023 1024
                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()
1025 1026 1027
                # 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:
1028
                        ql.remove(i)
J
New.  
James Troup 已提交
1029

1030
                if ql:
1031 1032 1033
                    # 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 已提交
1034 1035 1036 1037 1038

                    x = ql[0]; # default to something sane in case we don't match any or have only one

                    if len(ql) > 1:
                        for i in ql:
1039
                            old_file = i[0] + i[1]
1040
                            old_file_fh = utils.open_file(old_file)
1041
                            actual_md5 = apt_pkg.md5sum(old_file_fh)
1042
                            old_file_fh.close()
1043
                            actual_size = os.stat(old_file)[stat.ST_SIZE]
J
New.  
James Troup 已提交
1044
                            if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
1045
                                x = i
J
New.  
James Troup 已提交
1046
                            else:
1047
                                legacy_source_untouchable[i[3]] = ""
J
New.  
James Troup 已提交
1048

1049
                    old_file = x[0] + x[1]
1050
                    old_file_fh = utils.open_file(old_file)
1051
                    actual_md5 = apt_pkg.md5sum(old_file_fh)
1052
                    old_file_fh.close()
1053 1054 1055
                    actual_size = os.stat(old_file)[stat.ST_SIZE]
                    found = old_file
                    suite_type = x[2]
J
New.  
James Troup 已提交
1056
                    dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
1057
                    # See install() in process-accepted...
1058 1059
                    self.pkg.orig_tar_id = x[3]
                    self.pkg.orig_tar_gz = old_file
J
New.  
James Troup 已提交
1060
                    if suite_type == "legacy" or suite_type == "legacy-mixed":
1061
                        self.pkg.orig_tar_location = "legacy"
J
New.  
James Troup 已提交
1062
                    else:
1063
                        self.pkg.orig_tar_location = x[4]
J
New.  
James Troup 已提交
1064
                else:
1065
                    # Not there? Check the queue directories...
J
New.  
James Troup 已提交
1066

1067
                    in_unchecked = os.path.join(self.Cnf["Dir::Queue::Unchecked"],dsc_file)
1068
                    # See process_it() in 'dak process-unchecked' for explanation of this
1069 1070
                    # in_unchecked check dropped by ajt 2007-08-28, how did that
                    # ever make sense?
1071
                    if os.path.exists(in_unchecked) and False:
1072
                        return (self.reject_message, in_unchecked)
J
New.  
James Troup 已提交
1073
                    else:
J
Joerg Jaspert 已提交
1074 1075
                        for directory in [ "Accepted", "New", "Byhand", "ProposedUpdates", "OldProposedUpdates" ]:
                            in_otherdir = os.path.join(self.Cnf["Dir::Queue::%s" % (directory)],dsc_file)
1076
                            if os.path.exists(in_otherdir):
1077
                                in_otherdir_fh = utils.open_file(in_otherdir)
1078
                                actual_md5 = apt_pkg.md5sum(in_otherdir_fh)
1079
                                in_otherdir_fh.close()
1080 1081 1082
                                actual_size = os.stat(in_otherdir)[stat.ST_SIZE]
                                found = in_otherdir
                                self.pkg.orig_tar_gz = in_otherdir
1083 1084

                    if not found:
1085 1086 1087
                        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 已提交
1088
            else:
1089 1090
                self.reject("%s refers to %s, but I can't find it in the queue." % (file, dsc_file))
                continue
J
New.  
James Troup 已提交
1091
            if actual_md5 != dsc_files[dsc_file]["md5sum"]:
1092
                self.reject("md5sum for %s doesn't match %s." % (found, file))
J
New.  
James Troup 已提交
1093
            if actual_size != int(dsc_files[dsc_file]["size"]):
1094
                self.reject("size for %s doesn't match %s." % (found, file))
J
New.  
James Troup 已提交
1095

1096
        return (self.reject_message, None)
J
James Troup 已提交
1097 1098

    def do_query(self, q):
1099 1100 1101 1102 1103 1104
        sys.stderr.write("query: \"%s\" ... " % (q))
        before = time.time()
        r = self.projectB.query(q)
        time_diff = time.time()-before
        sys.stderr.write("took %.3f seconds.\n" % (time_diff))
        return r