queue.py 49.5 KB
Newer Older
J
New.  
James Troup 已提交
1 2
#!/usr/bin/env python

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

# 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

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

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

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

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

31 32 33 34
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 已提交
35

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

# 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 已提交
44 45
    for file_entry in files.keys():
        f = files[file_entry]
46 47 48 49 50 51
        # Skip byhand elements
        if f["type"] == "byhand":
            continue
        pkg = f["package"]
        priority = f["priority"]
        section = f["section"]
J
Joerg Jaspert 已提交
52
        file_type = get_type(f)
53 54
        component = f["component"]

J
Joerg Jaspert 已提交
55
        if file_type == "dsc":
56 57 58 59 60
            priority = "source"
        if not new.has_key(pkg):
            new[pkg] = {}
            new[pkg]["priority"] = priority
            new[pkg]["section"] = section
J
Joerg Jaspert 已提交
61
            new[pkg]["type"] = file_type
62 63 64 65
            new[pkg]["component"] = component
            new[pkg]["files"] = []
        else:
            old_type = new[pkg]["type"]
J
Joerg Jaspert 已提交
66
            if old_type != file_type:
67 68 69 70
                # source gets trumped by deb or udeb
                if old_type == "dsc":
                    new[pkg]["priority"] = priority
                    new[pkg]["section"] = section
J
Joerg Jaspert 已提交
71
                    new[pkg]["type"] = file_type
72
                    new[pkg]["component"] = component
J
Joerg Jaspert 已提交
73
        new[pkg]["files"].append(file_entry)
74 75 76 77 78 79 80 81 82 83 84
        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 已提交
85 86 87
                for file_entry in new[pkg]["files"]:
                    if files[file_entry].has_key("new"):
                        del files[file_entry]["new"]
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
                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 已提交
106
        file_type = f["dbtype"]
107
    elif f["type"] in [ "orig.tar.gz", "orig.tar.bz2", "tar.gz", "tar.bz2", "diff.gz", "diff.bz2", "dsc" ]:
J
Joerg Jaspert 已提交
108
        file_type = "dsc"
109
    else:
J
Joerg Jaspert 已提交
110
        utils.fubar("invalid type (%s) for new.  Dazed, confused and sure as heck not continuing." % (file_type))
111 112

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

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

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

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


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

# Convenience wrapper to carry around all the package information in

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

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

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

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

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

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

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

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

    def init_vars (self):
        for i in [ "changes", "dsc", "files", "dsc_files", "legacy_source_untouchable" ]:
175 176 177 178
            exec "self.pkg.%s.clear();" % (i)
        self.pkg.orig_tar_id = None
        self.pkg.orig_tar_location = ""
        self.pkg.orig_tar_gz = None
J
New.  
James Troup 已提交
179 180 181 182

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

    def update_vars (self):
183
        dump_filename = self.pkg.changes_file[:-8]+".dak"
184 185
        dump_file = utils.open_file(dump_filename)
        p = cPickle.Unpickler(dump_file)
J
New.  
James Troup 已提交
186
        for i in [ "changes", "dsc", "files", "dsc_files", "legacy_source_untouchable" ]:
187
            exec "self.pkg.%s.update(p.load());" % (i)
J
New.  
James Troup 已提交
188
        for i in [ "orig_tar_id", "orig_tar_location" ]:
189 190
            exec "self.pkg.%s = p.load();" % (i)
        dump_file.close()
J
New.  
James Troup 已提交
191 192 193

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

194 195 196
    # 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 已提交
197 198 199 200

    def dump_vars(self, dest_dir):
        for i in [ "changes", "dsc", "files", "dsc_files",
                   "legacy_source_untouchable", "orig_tar_id", "orig_tar_location" ]:
201
            exec "%s = self.pkg.%s;" % (i,i)
202
        dump_filename = os.path.join(dest_dir,self.pkg.changes_file[:-8] + ".dak")
203
        dump_file = utils.open_file(dump_filename, 'w')
J
James Troup 已提交
204
        try:
205
            os.chmod(dump_filename, 0660)
J
James Troup 已提交
206 207
        except OSError, e:
            if errno.errorcode[e.errno] == 'EPERM':
208
                perms = stat.S_IMODE(os.stat(dump_filename)[stat.ST_MODE])
J
James Troup 已提交
209
                if perms & stat.S_IROTH:
210
                    utils.fubar("%s is world readable and chmod failed." % (dump_filename))
J
James Troup 已提交
211
            else:
212
                raise
J
James Troup 已提交
213

214
        p = cPickle.Pickler(dump_file, 1)
J
New.  
James Troup 已提交
215
        for i in [ "d_changes", "d_dsc", "d_files", "d_dsc_files" ]:
216
            exec "%s = {}" % i
J
New.  
James Troup 已提交
217
        ## files
J
Joerg Jaspert 已提交
218 219
        for file_entry in files.keys():
            d_files[file_entry] = {}
J
New.  
James Troup 已提交
220 221 222
            for i in [ "package", "version", "architecture", "type", "size",
                       "md5sum", "component", "location id", "source package",
                       "source version", "maintainer", "dbtype", "files id",
223
                       "new", "section", "priority", "othercomponents",
224
                       "pool name", "original component" ]:
J
Joerg Jaspert 已提交
225 226
                if files[file_entry].has_key(i):
                    d_files[file_entry][i] = files[file_entry][i]
J
New.  
James Troup 已提交
227 228
        ## changes
        # Mandatory changes fields
229 230 231 232 233
        for i in [ "distribution", "source", "architecture", "version",
                   "maintainer", "urgency", "fingerprint", "changedby822",
                   "changedby2047", "changedbyname", "maintainer822",
                   "maintainer2047", "maintainername", "maintaineremail",
                   "closes", "changes" ]:
234
            d_changes[i] = changes[i]
J
New.  
James Troup 已提交
235
        # Optional changes fields
236 237
        for i in [ "changed-by", "filecontents", "format", "process-new note", "adv id", "distribution-version",
                   "sponsoremail" ]:
J
James Troup 已提交
238
            if changes.has_key(i):
239
                d_changes[i] = changes[i]
J
New.  
James Troup 已提交
240
        ## dsc
241
        for i in [ "source", "version", "maintainer", "fingerprint",
A
Anthony Towns 已提交
242
                   "uploaders", "bts changelog", "dm-upload-allowed" ]:
J
New.  
James Troup 已提交
243
            if dsc.has_key(i):
244
                d_dsc[i] = dsc[i]
J
New.  
James Troup 已提交
245
        ## dsc_files
J
Joerg Jaspert 已提交
246 247
        for file_entry in dsc_files.keys():
            d_dsc_files[file_entry] = {}
J
New.  
James Troup 已提交
248 249
            # Mandatory dsc_files fields
            for i in [ "size", "md5sum" ]:
J
Joerg Jaspert 已提交
250
                d_dsc_files[file_entry][i] = dsc_files[file_entry][i]
J
New.  
James Troup 已提交
251 252
            # Optional dsc_files fields
            for i in [ "files id" ]:
J
Joerg Jaspert 已提交
253 254
                if dsc_files[file_entry].has_key(i):
                    d_dsc_files[file_entry][i] = dsc_files[file_entry][i]
J
New.  
James Troup 已提交
255 256 257

        for i in [ d_changes, d_dsc, d_files, d_dsc_files,
                   legacy_source_untouchable, orig_tar_id, orig_tar_location ]:
258 259
            p.dump(i)
        dump_file.close()
J
New.  
James Troup 已提交
260 261 262 263 264 265

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

    # Set up the per-package template substitution mappings

    def update_subst (self, reject_message = ""):
266 267
        Subst = self.Subst
        changes = self.pkg.changes
268
        # If 'dak process-unchecked' crashed out in the right place, architecture may still be a string.
J
New.  
James Troup 已提交
269
        if not changes.has_key("architecture") or not isinstance(changes["architecture"], DictType):
270
            changes["architecture"] = { "Unknown" : "" }
271 272
        # and maintainer2047 may not exist.
        if not changes.has_key("maintainer2047"):
273
            changes["maintainer2047"] = self.Cnf["Dinstall::MyEmailAddress"]
J
New.  
James Troup 已提交
274

275 276 277
        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 已提交
278 279 280

        # 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"]):
281
            Subst["__MAINTAINER_FROM__"] = changes["changedby2047"]
282
            Subst["__MAINTAINER_TO__"] = "%s, %s" % (changes["changedby2047"],
283 284
                                                     changes["maintainer2047"])
            Subst["__MAINTAINER__"] = changes.get("changed-by", "Unknown")
J
New.  
James Troup 已提交
285
        else:
286 287 288
            Subst["__MAINTAINER_FROM__"] = changes["maintainer2047"]
            Subst["__MAINTAINER_TO__"] = changes["maintainer2047"]
            Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown")
J
Joerg Jaspert 已提交
289 290 291 292

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

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

296 297
        # Apply any global override of the Maintainer field
        if self.Cnf.get("Dinstall::OverrideMaintainer"):
298 299
            Subst["__MAINTAINER_TO__"] = self.Cnf["Dinstall::OverrideMaintainer"]
            Subst["__MAINTAINER_FROM__"] = self.Cnf["Dinstall::OverrideMaintainer"]
300

301 302 303
        Subst["__REJECT_MESSAGE__"] = reject_message
        Subst["__SOURCE__"] = changes.get("source", "Unknown")
        Subst["__VERSION__"] = changes.get("version", "Unknown")
J
New.  
James Troup 已提交
304 305 306 307

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

    def build_summaries(self):
308 309
        changes = self.pkg.changes
        files = self.pkg.files
J
New.  
James Troup 已提交
310

311
        byhand = summary = new = ""
J
New.  
James Troup 已提交
312 313 314 315

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

318
        override_summary ="";
319 320
        file_keys = files.keys()
        file_keys.sort()
J
Joerg Jaspert 已提交
321 322
        for file_entry in file_keys:
            if files[file_entry].has_key("byhand"):
J
New.  
James Troup 已提交
323
                byhand = 1
J
Joerg Jaspert 已提交
324 325
                summary += file_entry + " byhand\n"
            elif files[file_entry].has_key("new"):
J
New.  
James Troup 已提交
326
                new = 1
J
Joerg Jaspert 已提交
327 328 329 330 331
                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)
332
                    summary += apt_pkg.ParseSection(apt_inst.debExtractControl(deb_fh))["Description"] + '\n'
333
                    deb_fh.close()
J
New.  
James Troup 已提交
334
            else:
J
Joerg Jaspert 已提交
335 336 337 338 339 340
                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"]:
341 342
                    # (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 已提交
343 344 345
                    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 已提交
346

347
        short_summary = summary
J
New.  
James Troup 已提交
348 349

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

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

355 356
        summary += "\n\nOverride entries for your package:\n" + override_summary + "\n"

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

359
        return (summary, short_summary)
J
New.  
James Troup 已提交
360 361 362

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

363
    def close_bugs (self, summary, action):
364 365 366
        changes = self.pkg.changes
        Subst = self.Subst
        Cnf = self.Cnf
J
New.  
James Troup 已提交
367

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

370
        if not bugs:
371
            return summary
J
New.  
James Troup 已提交
372

373
        bugs.sort()
374 375 376 377 378 379 380
        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__"] = """
381 382 383 384
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
385
distribution."""
386 387 388 389 390 391
                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)
392
        summary += "\n"
393

394
        return summary
395 396 397 398

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

    def announce (self, short_summary, action):
399 400 401
        Subst = self.Subst
        Cnf = self.Cnf
        changes = self.pkg.changes
402 403 404

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

407 408 409
        lists_done = {}
        summary = ""
        Subst["__SHORT_SUMMARY__"] = short_summary
410 411

        for dist in changes["distribution"].keys():
J
Joerg Jaspert 已提交
412 413
            announce_list = Cnf.Find("Suite::%s::Announce" % (dist))
            if announce_list == "" or lists_done.has_key(announce_list):
414
                continue
J
Joerg Jaspert 已提交
415 416
            lists_done[announce_list] = 1
            summary += "Announcing to %s\n" % (announce_list)
417 418

            if action:
J
Joerg Jaspert 已提交
419
                Subst["__ANNOUNCE_LIST_ADDRESS__"] = announce_list
420
                if Cnf.get("Dinstall::TrackingServer") and changes["architecture"].has_key("source"):
421
                    Subst["__ANNOUNCE_LIST_ADDRESS__"] = Subst["__ANNOUNCE_LIST_ADDRESS__"] + "\nBcc: %s@%s" % (changes["source"], Cnf["Dinstall::TrackingServer"])
422
                mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/process-unchecked.announce")
423
                utils.send_mail (mail_message)
424

J
James Troup 已提交
425
        if Cnf.FindB("Dinstall::CloseBugs"):
426
            summary = self.close_bugs(summary, action)
427

428
        return summary
J
New.  
James Troup 已提交
429 430 431 432

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

    def accept (self, summary, short_summary):
433 434 435 436 437 438
        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 已提交
439 440

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

443
        self.dump_vars(Cnf["Dir::Queue::Accepted"])
J
New.  
James Troup 已提交
444 445

        # Move all the files into the accepted directory
446 447
        utils.move(changes_file, Cnf["Dir::Queue::Accepted"])
        file_keys = files.keys()
J
Joerg Jaspert 已提交
448 449 450
        for file_entry in file_keys:
            utils.move(file_entry, Cnf["Dir::Queue::Accepted"])
            self.accept_bytes += float(files[file_entry]["size"])
451
        self.accept_count += 1
J
New.  
James Troup 已提交
452 453 454 455

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

462 463 464 465 466 467 468 469 470 471 472 473

        ## 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"],
474 475 476 477
                                                    dotprefix=1, perms=0644)
                version_history = utils.open_file(temp_filename, 'w')
                version_history.write(dsc["bts changelog"])
                version_history.close()
478
                filename = "%s/%s" % (Cnf["Dir::Queue::BTSVersionTrack"],
479 480
                                      changes_file[:-8]+".versions")
                os.rename(temp_filename, filename)
481 482 483

            # Write out the binary -> source mapping.
            temp_filename = utils.temp_filename(Cnf["Dir::Queue::BTSVersionTrack"],
484 485
                                                dotprefix=1, perms=0644)
            debinfo = utils.open_file(temp_filename, 'w')
J
Joerg Jaspert 已提交
486 487
            for file_entry in file_keys:
                f = files[file_entry]
488 489 490
                if f["type"] == "deb":
                    line = " ".join([f["package"], f["version"],
                                     f["architecture"], f["source package"],
491 492 493
                                     f["source version"]])
                    debinfo.write(line+"\n")
            debinfo.close()
494
            filename = "%s/%s" % (Cnf["Dir::Queue::BTSVersionTrack"],
495 496
                                  changes_file[:-8]+".debinfo")
            os.rename(temp_filename, filename)
497

498 499 500 501 502
        self.queue_build("accepted", Cnf["Dir::Queue::Accepted"])

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

    def queue_build (self, queue, path):
503 504 505 506 507 508 509 510
        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()

511
        ## Special support to enable clean auto-building of queued packages
512
        queue_id = database.get_or_set_queue_id(queue)
513

514
        self.projectB.query("BEGIN WORK")
515
        for suite in changes["distribution"].keys():
516
            if suite not in Cnf.ValueList("Dinstall::QueueBuildSuites"):
517
                continue
518
            suite_id = database.get_suite_id(suite)
519
            dest_dir = Cnf["Dir::QueueBuild"]
520
            if Cnf.FindB("Dinstall::SecurityQueueBuild"):
521
                dest_dir = os.path.join(dest_dir, suite)
J
Joerg Jaspert 已提交
522 523 524
            for file_entry in file_keys:
                src = os.path.join(path, file_entry)
                dest = os.path.join(dest_dir, file_entry)
525
                if Cnf.FindB("Dinstall::SecurityQueueBuild"):
526
                    # Copy it since the original won't be readable by www-data
527
                    utils.copy(src, dest)
528 529
                else:
                    # Create a symlink to it
530
                    os.symlink(src, dest)
J
James Troup 已提交
531
                # Add it to the list of packages for later processing by apt-ftparchive
532
                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 已提交
533 534
            # If the .orig.tar.gz is in the pool, create a symlink to
            # it (if one doesn't already exist)
535 536 537
            if self.pkg.orig_tar_id:
                # Determine the .orig.tar.gz file name
                for dsc_file in self.pkg.dsc_files.keys():
538
                    if dsc_file.endswith(".orig.tar.gz"):
539 540
                        filename = dsc_file
                dest = os.path.join(dest_dir, filename)
541 542 543
                # If it doesn't exist, create a symlink
                if not os.path.exists(dest):
                    # Find the .orig.tar.gz in the pool
544 545
                    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()
546
                    if not ql:
547 548 549
                        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 已提交
550
                    # Add it to the list of packages for later processing by apt-ftparchive
551
                    self.projectB.query("INSERT INTO queue_build (suite, queue, filename, in_queue) VALUES (%s, %s, '%s', 't')" % (suite_id, queue_id, dest))
552 553
                # if it does, update things to ensure it's not removed prematurely
                else:
554
                    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 已提交
555

556
        self.projectB.query("COMMIT WORK")
557

J
New.  
James Troup 已提交
558 559 560
    ###########################################################################

    def check_override (self):
561 562 563 564
        Subst = self.Subst
        changes = self.pkg.changes
        files = self.pkg.files
        Cnf = self.Cnf
J
New.  
James Troup 已提交
565

566 567 568 569 570 571 572
        # 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"]:
573
            return
J
New.  
James Troup 已提交
574

575 576 577
        summary = ""
        file_keys = files.keys()
        file_keys.sort()
J
Joerg Jaspert 已提交
578 579 580 581
        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"]
582
                if section.lower() != override_section.lower() and section != "-":
J
Joerg Jaspert 已提交
583 584 585
                    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 已提交
586
                if priority != override_priority and priority != "-":
J
Joerg Jaspert 已提交
587
                    summary += "%s: package says priority is %s, override says %s.\n" % (file_entry, priority, override_priority)
J
New.  
James Troup 已提交
588 589

        if summary == "":
590
            return
J
New.  
James Troup 已提交
591

592
        Subst["__SUMMARY__"] = summary
593
        mail_message = utils.TemplateSubst(Subst,self.Cnf["Dir::Templates"]+"/process-unchecked.override-disparity")
594
        utils.send_mail(mail_message)
J
New.  
James Troup 已提交
595 596 597

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

598 599 600 601 602
    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 已提交
603 604 605

        Cnf = self.Cnf

J
Joerg Jaspert 已提交
606
        for file_entry in files:
J
New.  
James Troup 已提交
607
            # Skip any files which don't exist or which we don't have permission to copy.
J
Joerg Jaspert 已提交
608
            if os.access(file_entry,os.R_OK) == 0:
609
                continue
J
Joerg Jaspert 已提交
610
            dest_file = os.path.join(Cnf["Dir::Queue::Reject"], file_entry)
J
New.  
James Troup 已提交
611
            try:
612
                dest_fd = os.open(dest_file, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
J
New.  
James Troup 已提交
613 614 615
            except OSError, e:
                # File exists?  Let's try and move it to the morgue
                if errno.errorcode[e.errno] == 'EEXIST':
J
Joerg Jaspert 已提交
616
                    morgue_file = os.path.join(Cnf["Dir::Morgue"],Cnf["Dir::MorgueReject"],file_entry)
J
New.  
James Troup 已提交
617
                    try:
618
                        morgue_file = utils.find_next_free(morgue_file)
J
Joerg Jaspert 已提交
619
                    except NoFreeFilenameError:
J
New.  
James Troup 已提交
620 621
                        # Something's either gone badly Pete Tong, or
                        # someone is trying to exploit us.
J
Joerg Jaspert 已提交
622
                        utils.warn("**WARNING** failed to move %s from the reject directory to the morgue." % (file_entry))
623 624
                        return
                    utils.move(dest_file, morgue_file, perms=0660)
J
New.  
James Troup 已提交
625
                    try:
626
                        dest_fd = os.open(dest_file, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
J
New.  
James Troup 已提交
627 628
                    except OSError, e:
                        # Likewise
J
Joerg Jaspert 已提交
629
                        utils.warn("**WARNING** failed to claim %s in the reject directory." % (file_entry))
630
                        return
J
New.  
James Troup 已提交
631
                else:
632
                    raise
J
New.  
James Troup 已提交
633 634
            # If we got here, we own the destination file, so we can
            # safely overwrite it.
J
Joerg Jaspert 已提交
635
            utils.move(file_entry, dest_file, 1, perms=0660)
636
            os.close(dest_fd)
637

J
New.  
James Troup 已提交
638 639 640
    ###########################################################################

    def do_reject (self, manual = 0, reject_message = ""):
J
James Troup 已提交
641 642 643
        # 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:
644
            temp_filename = utils.temp_filename()
J
James Troup 已提交
645
            editor = os.environ.get("EDITOR","vi")
646
            answer = 'E'
J
James Troup 已提交
647 648
            while answer == 'E':
                os.system("%s %s" % (editor, temp_filename))
649 650 651 652 653
                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 已提交
654
                prompt = "[R]eject, Edit, Abandon, Quit ?"
655
                answer = "XXX"
656
                while prompt.find(answer) == -1:
657 658
                    answer = utils.our_raw_input(prompt)
                    m = re_default_answer.search(prompt)
J
James Troup 已提交
659
                    if answer == "":
660 661 662
                        answer = m.group(1)
                    answer = answer[:1].upper()
            os.unlink(temp_filename)
J
James Troup 已提交
663
            if answer == 'A':
664
                return 1
J
James Troup 已提交
665
            elif answer == 'Q':
666
                sys.exit(0)
J
James Troup 已提交
667

J
New.  
James Troup 已提交
668 669
        print "Rejecting.\n"

670 671 672
        Cnf = self.Cnf
        Subst = self.Subst
        pkg = self.pkg
J
New.  
James Troup 已提交
673

674 675
        reason_filename = pkg.changes_file[:-8] + ".reason"
        reason_filename = Cnf["Dir::Queue::Reject"] + '/' + reason_filename
J
New.  
James Troup 已提交
676 677

        # Move all the files into the reject directory
678 679
        reject_files = pkg.files.keys() + [pkg.changes_file]
        self.force_reject(reject_files)
J
New.  
James Troup 已提交
680 681 682

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

        if not manual:
688 689
            Subst["__REJECTOR_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"]
            Subst["__MANUAL_REJECT_MESSAGE__"] = ""
690
            Subst["__CC__"] = "X-DAK-Rejection: automatic (moo)\nX-Katie-Rejection: automatic (moo)"
691
            os.write(reason_fd, reject_message)
692
            reject_mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/queue.rejected")
J
New.  
James Troup 已提交
693 694
        else:
            # Build up the rejection email
695
            user_email_address = utils.whoami() + " <%s>" % (Cnf["Dinstall::MyAdminAddress"])
J
New.  
James Troup 已提交
696

697 698 699
            Subst["__REJECTOR_ADDRESS__"] = user_email_address
            Subst["__MANUAL_REJECT_MESSAGE__"] = reject_message
            Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"]
700
            reject_mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/queue.rejected")
J
New.  
James Troup 已提交
701
            # Write the rejection email out as the <foo>.reason file
702
            os.write(reason_fd, reject_mail_message)
J
James Troup 已提交
703

704
        os.close(reason_fd)
J
New.  
James Troup 已提交
705 706 707

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

710 711
        self.Logger.log(["rejected", pkg.changes_file])
        return 0
J
New.  
James Troup 已提交
712 713 714 715 716 717 718

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

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

721
    def source_exists (self, package, source_version, suites = ["any"]):
722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741
        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]))
742 743 744
            q = self.projectB.query(que)

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

            # Try (1)
748
            if source_version in ql:
749 750 751
                continue

            # Try (2)
752
            orig_source_version = re_bin_only_nmu.sub('', source_version)
753
            if orig_source_version in ql:
754 755 756 757
                continue

            # No source found...
            okay = 0
758 759
            break
        return okay
J
New.  
James Troup 已提交
760 761

    ################################################################################
762

J
New.  
James Troup 已提交
763
    def in_override_p (self, package, component, suite, binary_type, file):
764
        files = self.pkg.files
J
New.  
James Troup 已提交
765 766

        if binary_type == "": # must be source
J
Joerg Jaspert 已提交
767
            file_type = "dsc"
J
New.  
James Troup 已提交
768
        else:
J
Joerg Jaspert 已提交
769
            file_type = binary_type
J
New.  
James Troup 已提交
770 771 772

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

        # Avoid <undef> on unknown distributions
776
        suite_id = database.get_suite_id(suite)
J
New.  
James Troup 已提交
777
        if suite_id == -1:
778
            return None
779
        component_id = database.get_component_id(component)
J
Joerg Jaspert 已提交
780
        type_id = database.get_override_type_id(file_type)
J
New.  
James Troup 已提交
781 782

        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"
783 784
                           % (package, suite_id, component_id, type_id))
        result = q.getresult()
J
New.  
James Troup 已提交
785
        # If checking for a source package fall back on the binary override type
J
Joerg Jaspert 已提交
786
        if file_type == "dsc" and not result:
787 788
            deb_type_id = database.get_override_type_id("deb")
            udeb_type_id = database.get_override_type_id("udeb")
789
            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"
790 791
                               % (package, suite_id, component_id, deb_type_id, udeb_type_id))
            result = q.getresult()
J
New.  
James Troup 已提交
792 793

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

798
        return result
J
New.  
James Troup 已提交
799 800 801 802 803

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

    def reject (self, str, prefix="Rejected: "):
        if str:
804 805 806
            # 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:
807 808
                self.reject_message += "\n"
            self.reject_message += prefix + str
J
New.  
James Troup 已提交
809

810 811
    ################################################################################

812 813 814 815
    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:
816
            if s in [ x.lower() for x in anysuite ]:
817 818 819 820 821 822
                if not anyversion or apt_pkg.VersionCompare(anyversion, v) <= 0:
                    anyversion=v
        return anyversion

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

823 824 825 826 827 828 829
    def cross_suite_version_check(self, query_result, file, new_version):
        """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():
830 831
            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)) ]
832 833
            # Enforce "must be newer than target suite" even if conffile omits it
            if target_suite not in must_be_newer_than:
834
                must_be_newer_than.append(target_suite)
835
            for entry in query_result:
836 837
                existent_version = entry[0]
                suite = entry[1]
838
                if suite in must_be_newer_than and \
839
                   apt_pkg.VersionCompare(new_version, existent_version) < 1:
840
                    self.reject("%s: old version (%s) in %s >= new version (%s) targeted at %s." % (file, existent_version, suite, new_version, target_suite))
841
                if suite in must_be_older_than and \
842
                   apt_pkg.VersionCompare(new_version, existent_version) > -1:
843 844 845
                    ch = self.pkg.changes
                    cansave = 0
                    if ch.get('distribution-version', {}).has_key(suite):
846
                    # we really use the other suite, ignoring the conflicting one ...
847
                        addsuite = ch["distribution-version"][suite]
848

849 850
                        add_version = self.get_anyversion(query_result, addsuite)
                        target_version = self.get_anyversion(query_result, target_suite)
851

852 853 854 855 856 857 858 859 860 861 862
                        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: ")
863 864
                            self.pkg.changes.setdefault("propdistribution", {})
                            self.pkg.changes["propdistribution"][addsuite] = 1
865 866 867 868 869 870 871 872
                            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.
873
                            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: ")
874 875
                            cansave = 1
                        elif apt_pkg.VersionCompare(new_version, add_version) > 0 and \
876
                             apt_pkg.VersionCompare(add_version, target_version) >= 0:
877
                            # propogate!!
878 879 880
                            self.reject("Propogating upload to %s" % (addsuite), "Warning: ")
                            self.pkg.changes.setdefault("propdistribution", {})
                            self.pkg.changes["propdistribution"][addsuite] = 1
881
                            cansave = 1
882

883
                    if not cansave:
884
                        self.reject("%s: old version (%s) in %s <= new version (%s) targeted at %s." % (file, existent_version, suite, new_version, target_suite))
885 886 887 888

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

    def check_binary_against_db(self, file):
889 890
        self.reject_message = ""
        files = self.pkg.files
J
New.  
James Troup 已提交
891

892 893 894 895 896 897 898
        # 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"],
899 900
                                   files[file]["architecture"]))
        self.cross_suite_version_check(q.getresult(), file, files[file]["version"])
901

J
New.  
James Troup 已提交
902
        # Check for any existing copies of the file
903 904 905 906 907 908 909 910
        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():
911
            self.reject("%s: can not overwrite existing copy already in the archive." % (file))
J
New.  
James Troup 已提交
912

913
        return self.reject_message
J
New.  
James Troup 已提交
914 915 916 917

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

    def check_source_against_db(self, file):
918 919
        self.reject_message = ""
        dsc = self.pkg.dsc
J
New.  
James Troup 已提交
920

921 922 923
        # Ensure version is sane
        q = self.projectB.query("""
SELECT s.version, su.suite_name FROM source s, src_associations sa, suite su
924 925
 WHERE s.source = '%s' AND sa.source = s.id AND sa.suite = su.id""" % (dsc.get("source")))
        self.cross_suite_version_check(q.getresult(), file, dsc.get("version"))
926

927
        return self.reject_message
J
New.  
James Troup 已提交
928 929 930

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

931 932 933 934 935
    # **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 已提交
936
    # ensure you haven't just tried to dereference the deleted entry.
937 938
    # **WARNING**

J
New.  
James Troup 已提交
939
    def check_dsc_against_db(self, file):
940 941 942 943 944
        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 已提交
945 946 947 948

        # 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.
949 950
        # The ordering on the select is needed to pick the newest orig
        # when it exists in multiple places.
J
New.  
James Troup 已提交
951
        for dsc_file in dsc_files.keys():
952
            found = None
J
New.  
James Troup 已提交
953
            if files.has_key(dsc_file):
954 955
                actual_md5 = files[dsc_file]["md5sum"]
                actual_size = int(files[dsc_file]["size"])
J
New.  
James Troup 已提交
956 957
                found = "%s in incoming" % (dsc_file)
                # Check the file does not already exist in the archive
958
                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))
959
                ql = q.getresult()
960 961
                # Strip out anything that isn't '%s' or '/%s$'
                for i in ql:
962
                    if i[3] != dsc_file and i[3][-(len(dsc_file)+1):] != '/'+dsc_file:
963
                        ql.remove(i)
964

965
                # "[dak] has not broken them.  [dak] has fixed a
J
New.  
James Troup 已提交
966 967 968 969 970 971 972 973
                # 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

974
                if ql:
975
                    # Ignore exact matches for .orig.tar.gz
976
                    match = 0
977
                    if dsc_file.endswith(".orig.tar.gz"):
978
                        for i in ql:
979 980
                            if files.has_key(dsc_file) and \
                               int(files[dsc_file]["size"]) == int(i[0]) and \
981
                               files[dsc_file]["md5sum"] == i[1]:
982 983 984 985
                                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
986 987

                    if not match:
988
                        self.reject("can not overwrite existing copy of '%s' already in the archive." % (dsc_file))
989
            elif dsc_file.endswith(".orig.tar.gz"):
J
New.  
James Troup 已提交
990
                # Check in the pool
991 992
                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()
993 994 995
                # 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:
996
                        ql.remove(i)
J
New.  
James Troup 已提交
997

998
                if ql:
999 1000 1001
                    # 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 已提交
1002 1003 1004 1005 1006

                    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:
1007
                            old_file = i[0] + i[1]
1008
                            old_file_fh = utils.open_file(old_file)
1009
                            actual_md5 = apt_pkg.md5sum(old_file_fh)
1010
                            old_file_fh.close()
1011
                            actual_size = os.stat(old_file)[stat.ST_SIZE]
J
New.  
James Troup 已提交
1012
                            if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
1013
                                x = i
J
New.  
James Troup 已提交
1014
                            else:
1015
                                legacy_source_untouchable[i[3]] = ""
J
New.  
James Troup 已提交
1016

1017
                    old_file = x[0] + x[1]
1018
                    old_file_fh = utils.open_file(old_file)
1019
                    actual_md5 = apt_pkg.md5sum(old_file_fh)
1020
                    old_file_fh.close()
1021 1022 1023
                    actual_size = os.stat(old_file)[stat.ST_SIZE]
                    found = old_file
                    suite_type = x[2]
J
New.  
James Troup 已提交
1024
                    dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
1025
                    # See install() in process-accepted...
1026 1027
                    self.pkg.orig_tar_id = x[3]
                    self.pkg.orig_tar_gz = old_file
J
New.  
James Troup 已提交
1028
                    if suite_type == "legacy" or suite_type == "legacy-mixed":
1029
                        self.pkg.orig_tar_location = "legacy"
J
New.  
James Troup 已提交
1030
                    else:
1031
                        self.pkg.orig_tar_location = x[4]
J
New.  
James Troup 已提交
1032
                else:
1033
                    # Not there? Check the queue directories...
J
New.  
James Troup 已提交
1034

1035
                    in_unchecked = os.path.join(self.Cnf["Dir::Queue::Unchecked"],dsc_file)
1036
                    # See process_it() in 'dak process-unchecked' for explanation of this
1037 1038
                    # in_unchecked check dropped by ajt 2007-08-28, how did that
                    # ever make sense?
1039
                    if os.path.exists(in_unchecked) and False:
1040
                        return (self.reject_message, in_unchecked)
J
New.  
James Troup 已提交
1041
                    else:
J
Joerg Jaspert 已提交
1042 1043
                        for directory in [ "Accepted", "New", "Byhand", "ProposedUpdates", "OldProposedUpdates" ]:
                            in_otherdir = os.path.join(self.Cnf["Dir::Queue::%s" % (directory)],dsc_file)
1044
                            if os.path.exists(in_otherdir):
1045
                                in_otherdir_fh = utils.open_file(in_otherdir)
1046
                                actual_md5 = apt_pkg.md5sum(in_otherdir_fh)
1047
                                in_otherdir_fh.close()
1048 1049 1050
                                actual_size = os.stat(in_otherdir)[stat.ST_SIZE]
                                found = in_otherdir
                                self.pkg.orig_tar_gz = in_otherdir
1051 1052

                    if not found:
1053 1054 1055
                        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 已提交
1056
            else:
1057 1058
                self.reject("%s refers to %s, but I can't find it in the queue." % (file, dsc_file))
                continue
J
New.  
James Troup 已提交
1059
            if actual_md5 != dsc_files[dsc_file]["md5sum"]:
1060
                self.reject("md5sum for %s doesn't match %s." % (found, file))
J
New.  
James Troup 已提交
1061
            if actual_size != int(dsc_files[dsc_file]["size"]):
1062
                self.reject("size for %s doesn't match %s." % (found, file))
J
New.  
James Troup 已提交
1063

1064
        return (self.reject_message, None)
J
James Troup 已提交
1065 1066

    def do_query(self, q):
1067 1068 1069 1070 1071 1072
        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