queue.py 50.2 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

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

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

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

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

        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()

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

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

203 204 205
    # 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 已提交
206 207

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

        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

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

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

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

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

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

    # Set up the per-package template substitution mappings

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

422
        return summary
423 424 425 426

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

626 627 628 629 630
    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 已提交
631 632 633

        Cnf = self.Cnf

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    ################################################################################
790

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

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

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

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

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

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

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

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

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

838 839
    ################################################################################

840 841 842 843
    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:
844
            if s in [ x.lower() for x in anysuite ]:
845 846 847 848 849 850
                if not anyversion or apt_pkg.VersionCompare(anyversion, v) <= 0:
                    anyversion=v
        return anyversion

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

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

877 878
                        add_version = self.get_anyversion(query_result, addsuite)
                        target_version = self.get_anyversion(query_result, target_suite)
879

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

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

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

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

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

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

941
        return self.reject_message
J
New.  
James Troup 已提交
942 943 944 945

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

    def check_source_against_db(self, file):
946 947
        self.reject_message = ""
        dsc = self.pkg.dsc
J
New.  
James Troup 已提交
948

949 950 951
        # Ensure version is sane
        q = self.projectB.query("""
SELECT s.version, su.suite_name FROM source s, src_associations sa, suite su
952 953
 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"))
954

955
        return self.reject_message
J
New.  
James Troup 已提交
956 957 958

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

959 960 961 962 963
    # **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 已提交
964
    # ensure you haven't just tried to dereference the deleted entry.
965 966
    # **WARNING**

J
New.  
James Troup 已提交
967
    def check_dsc_against_db(self, file):
968 969 970 971 972
        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 已提交
973 974 975 976

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

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

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

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

1026
                if ql:
1027 1028 1029
                    # 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 已提交
1030 1031 1032 1033 1034

                    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:
1035
                            old_file = i[0] + i[1]
1036
                            old_file_fh = utils.open_file(old_file)
1037
                            actual_md5 = apt_pkg.md5sum(old_file_fh)
1038
                            old_file_fh.close()
1039
                            actual_size = os.stat(old_file)[stat.ST_SIZE]
J
New.  
James Troup 已提交
1040
                            if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
1041
                                x = i
J
New.  
James Troup 已提交
1042
                            else:
1043
                                legacy_source_untouchable[i[3]] = ""
J
New.  
James Troup 已提交
1044

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

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

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

1092
        return (self.reject_message, None)
J
James Troup 已提交
1093 1094

    def do_query(self, q):
1095 1096 1097 1098 1099 1100
        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