]> git.decadent.org.uk Git - dak.git/blob - daklib/queue.py
35754c8d738a49d9dab87f80f0a1a57556c9b1ed
[dak.git] / daklib / queue.py
1 #!/usr/bin/env python
2 # vim:set et sw=4:
3
4 """
5 Queue utility functions for dak
6
7 @contact: Debian FTP Master <ftpmaster@debian.org>
8 @copyright: 2001 - 2006 James Troup <james@nocrew.org>
9 @copyright: 2009  Joerg Jaspert <joerg@debian.org>
10 @license: GNU General Public License version 2 or later
11 """
12
13 # This program is free software; you can redistribute it and/or modify
14 # it under the terms of the GNU General Public License as published by
15 # the Free Software Foundation; either version 2 of the License, or
16 # (at your option) any later version.
17
18 # This program is distributed in the hope that it will be useful,
19 # but WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 # GNU General Public License for more details.
22
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
26
27 ###############################################################################
28
29 import cPickle
30 import errno
31 import os
32 import pg
33 import stat
34 import sys
35 import time
36 import apt_inst
37 import apt_pkg
38 import utils
39 import database
40 from dak_exceptions import *
41 from regexes import re_default_answer, re_fdnic, re_bin_only_nmu
42
43 from types import *
44
45 ###############################################################################
46
47 # Determine what parts in a .changes are NEW
48
49 def determine_new(changes, files, projectB, warn=1):
50     """
51     Determine what parts in a C{changes} file are NEW.
52
53     @type changes: Upload.Pkg.changes dict
54     @param changes: Changes dictionary
55
56     @type files: Upload.Pkg.files dict
57     @param files: Files dictionary
58
59     @type projectB: pgobject
60     @param projectB: DB handle
61
62     @type warn: bool
63     @param warn: Warn if overrides are added for (old)stable
64
65     @rtype: dict
66     @return: dictionary of NEW components.
67
68     """
69     new = {}
70
71     # Build up a list of potentially new things
72     for file_entry in files.keys():
73         f = files[file_entry]
74         # Skip byhand elements
75         if f["type"] == "byhand":
76             continue
77         pkg = f["package"]
78         priority = f["priority"]
79         section = f["section"]
80         file_type = get_type(f)
81         component = f["component"]
82
83         if file_type == "dsc":
84             priority = "source"
85         if not new.has_key(pkg):
86             new[pkg] = {}
87             new[pkg]["priority"] = priority
88             new[pkg]["section"] = section
89             new[pkg]["type"] = file_type
90             new[pkg]["component"] = component
91             new[pkg]["files"] = []
92         else:
93             old_type = new[pkg]["type"]
94             if old_type != file_type:
95                 # source gets trumped by deb or udeb
96                 if old_type == "dsc":
97                     new[pkg]["priority"] = priority
98                     new[pkg]["section"] = section
99                     new[pkg]["type"] = file_type
100                     new[pkg]["component"] = component
101         new[pkg]["files"].append(file_entry)
102         if f.has_key("othercomponents"):
103             new[pkg]["othercomponents"] = f["othercomponents"]
104
105     for suite in changes["suite"].keys():
106         suite_id = database.get_suite_id(suite)
107         for pkg in new.keys():
108             component_id = database.get_component_id(new[pkg]["component"])
109             type_id = database.get_override_type_id(new[pkg]["type"])
110             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))
111             ql = q.getresult()
112             if ql:
113                 for file_entry in new[pkg]["files"]:
114                     if files[file_entry].has_key("new"):
115                         del files[file_entry]["new"]
116                 del new[pkg]
117
118     if warn:
119         if changes["suite"].has_key("stable"):
120             print "WARNING: overrides will be added for stable!"
121             if changes["suite"].has_key("oldstable"):
122                 print "WARNING: overrides will be added for OLDstable!"
123         for pkg in new.keys():
124             if new[pkg].has_key("othercomponents"):
125                 print "WARNING: %s already present in %s distribution." % (pkg, new[pkg]["othercomponents"])
126
127     return new
128
129 ################################################################################
130
131 def get_type(file):
132     """
133     Get the file type of C{file}
134
135     @type file: dict
136     @param file: file entry
137
138     @rtype: string
139     @return: filetype
140
141     """
142     # Determine the type
143     if file.has_key("dbtype"):
144         file_type = file["dbtype"]
145     elif file["type"] in [ "orig.tar.gz", "orig.tar.bz2", "tar.gz", "tar.bz2", "diff.gz", "diff.bz2", "dsc" ]:
146         file_type = "dsc"
147     else:
148         utils.fubar("invalid type (%s) for new.  Dazed, confused and sure as heck not continuing." % (file_type))
149
150     # Validate the override type
151     type_id = database.get_override_type_id(file_type)
152     if type_id == -1:
153         utils.fubar("invalid type (%s) for new.  Say wha?" % (file_type))
154
155     return file_type
156
157 ################################################################################
158
159
160
161 def check_valid(new):
162     """
163     Check if section and priority for NEW packages exist in database.
164     Additionally does sanity checks:
165       - debian-installer packages have to be udeb (or source)
166       - non debian-installer packages can not be udeb
167       - source priority can only be assigned to dsc file types
168
169     @type new: dict
170     @param new: Dict of new packages with their section, priority and type.
171
172     """
173     for pkg in new.keys():
174         section = new[pkg]["section"]
175         priority = new[pkg]["priority"]
176         file_type = new[pkg]["type"]
177         new[pkg]["section id"] = database.get_section_id(section)
178         new[pkg]["priority id"] = database.get_priority_id(new[pkg]["priority"])
179         # Sanity checks
180         di = section.find("debian-installer") != -1
181         if (di and file_type not in ("udeb", "dsc")) or (not di and file_type == "udeb"):
182             new[pkg]["section id"] = -1
183         if (priority == "source" and file_type != "dsc") or \
184            (priority != "source" and file_type == "dsc"):
185             new[pkg]["priority id"] = -1
186
187
188 ###############################################################################
189
190 class Pkg:
191     """ Convenience wrapper to carry around all the package information """
192     def __init__(self, **kwds):
193         self.__dict__.update(kwds)
194
195     def update(self, **kwds):
196         self.__dict__.update(kwds)
197
198 ###############################################################################
199
200 class Upload:
201     """
202     Everything that has to do with an upload processed.
203
204     """
205     def __init__(self, Cnf):
206         """
207         Initialize various variables and the global substitution template mappings.
208         Also connect to the DB and initialize the Database module.
209
210         """
211         self.Cnf = Cnf
212         self.accept_count = 0
213         self.accept_bytes = 0L
214         self.reject_message = ""
215         self.pkg = Pkg(changes = {}, dsc = {}, dsc_files = {}, files = {})
216
217         # Initialize the substitution template mapping global
218         Subst = self.Subst = {}
219         Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"]
220         Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"]
221         Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"]
222         Subst["__DAK_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"]
223
224         self.projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]))
225         database.init(Cnf, self.projectB)
226
227     ###########################################################################
228
229     def init_vars (self):
230         """ Reset a number of entries from our Pkg object. """
231         self.pkg.changes.clear()
232         self.pkg.dsc.clear()
233         self.pkg.files.clear()
234         self.pkg.dsc_files.clear()
235         self.pkg.orig_tar_id = None
236         self.pkg.orig_tar_location = ""
237         self.pkg.orig_tar_gz = None
238
239     ###########################################################################
240
241     def update_vars (self):
242         """
243         Update our Pkg object by reading a previously created cPickle .dak dumpfile.
244         """
245         dump_filename = self.pkg.changes_file[:-8]+".dak"
246         dump_file = utils.open_file(dump_filename)
247         p = cPickle.Unpickler(dump_file)
248
249         self.pkg.changes.update(p.load())
250         self.pkg.dsc.update(p.load())
251         self.pkg.files.update(p.load())
252         self.pkg.dsc_files.update(p.load())
253
254         self.pkg.orig_tar_id = p.load()
255         self.pkg.orig_tar_location = p.load()
256
257         dump_file.close()
258
259     ###########################################################################
260
261
262     def dump_vars(self, dest_dir):
263         """
264         Dump our Pkg object into a cPickle file.
265
266         @type dest_dir: string
267         @param dest_dir: Path where the dumpfile should be stored
268
269         @note: This could just dump the dictionaries as is, but I'd like to avoid this so
270                there's some idea of what process-accepted & process-new use from
271                process-unchecked. (JT)
272
273         """
274
275         changes = self.pkg.changes
276         dsc = self.pkg.dsc
277         files = self.pkg.files
278         dsc_files = self.pkg.dsc_files
279         orig_tar_id = self.pkg.orig_tar_id
280         orig_tar_location = self.pkg.orig_tar_location
281
282         dump_filename = os.path.join(dest_dir,self.pkg.changes_file[:-8] + ".dak")
283         dump_file = utils.open_file(dump_filename, 'w')
284         try:
285             os.chmod(dump_filename, 0664)
286         except OSError, e:
287             # chmod may fail when the dumpfile is not owned by the user
288             # invoking dak (like e.g. when NEW is processed by a member
289             # of ftpteam)
290             if errno.errorcode[e.errno] == 'EPERM':
291                 perms = stat.S_IMODE(os.stat(dump_filename)[stat.ST_MODE])
292                 # security precaution, should never happen unless a weird
293                 # umask is set anywhere
294                 if perms & stat.S_IWOTH:
295                     utils.fubar("%s is world writable and chmod failed." % \
296                         (dump_filename,))
297                 # ignore the failed chmod otherwise as the file should
298                 # already have the right privileges and is just, at worst,
299                 # unreadable for world
300             else:
301                 raise
302
303         p = cPickle.Pickler(dump_file, 1)
304         d_changes = {}
305         d_dsc = {}
306         d_files = {}
307         d_dsc_files = {}
308
309         ## files
310         for file_entry in files.keys():
311             d_files[file_entry] = {}
312             for i in [ "package", "version", "architecture", "type", "size",
313                        "md5sum", "sha1sum", "sha256sum", "component",
314                        "location id", "source package", "source version",
315                        "maintainer", "dbtype", "files id", "new",
316                        "section", "priority", "othercomponents",
317                        "pool name", "original component" ]:
318                 if files[file_entry].has_key(i):
319                     d_files[file_entry][i] = files[file_entry][i]
320         ## changes
321         # Mandatory changes fields
322         for i in [ "distribution", "source", "architecture", "version",
323                    "maintainer", "urgency", "fingerprint", "changedby822",
324                    "changedby2047", "changedbyname", "maintainer822",
325                    "maintainer2047", "maintainername", "maintaineremail",
326                    "closes", "changes" ]:
327             d_changes[i] = changes[i]
328         # Optional changes fields
329         for i in [ "changed-by", "filecontents", "format", "process-new note", "adv id", "distribution-version",
330                    "sponsoremail" ]:
331             if changes.has_key(i):
332                 d_changes[i] = changes[i]
333         ## dsc
334         for i in [ "source", "version", "maintainer", "fingerprint",
335                    "uploaders", "bts changelog", "dm-upload-allowed" ]:
336             if dsc.has_key(i):
337                 d_dsc[i] = dsc[i]
338         ## dsc_files
339         for file_entry in dsc_files.keys():
340             d_dsc_files[file_entry] = {}
341             # Mandatory dsc_files fields
342             for i in [ "size", "md5sum" ]:
343                 d_dsc_files[file_entry][i] = dsc_files[file_entry][i]
344             # Optional dsc_files fields
345             for i in [ "files id" ]:
346                 if dsc_files[file_entry].has_key(i):
347                     d_dsc_files[file_entry][i] = dsc_files[file_entry][i]
348
349         for i in [ d_changes, d_dsc, d_files, d_dsc_files,
350                    orig_tar_id, orig_tar_location ]:
351             p.dump(i)
352         dump_file.close()
353
354     ###########################################################################
355
356     # Set up the per-package template substitution mappings
357
358     def update_subst (self, reject_message = ""):
359         """ Set up the per-package template substitution mappings """
360
361         Subst = self.Subst
362         changes = self.pkg.changes
363         # If 'dak process-unchecked' crashed out in the right place, architecture may still be a string.
364         if not changes.has_key("architecture") or not isinstance(changes["architecture"], DictType):
365             changes["architecture"] = { "Unknown" : "" }
366         # and maintainer2047 may not exist.
367         if not changes.has_key("maintainer2047"):
368             changes["maintainer2047"] = self.Cnf["Dinstall::MyEmailAddress"]
369
370         Subst["__ARCHITECTURE__"] = " ".join(changes["architecture"].keys())
371         Subst["__CHANGES_FILENAME__"] = os.path.basename(self.pkg.changes_file)
372         Subst["__FILE_CONTENTS__"] = changes.get("filecontents", "")
373
374         # For source uploads the Changed-By field wins; otherwise Maintainer wins.
375         if changes["architecture"].has_key("source") and changes["changedby822"] != "" and (changes["changedby822"] != changes["maintainer822"]):
376             Subst["__MAINTAINER_FROM__"] = changes["changedby2047"]
377             Subst["__MAINTAINER_TO__"] = "%s, %s" % (changes["changedby2047"],
378                                                      changes["maintainer2047"])
379             Subst["__MAINTAINER__"] = changes.get("changed-by", "Unknown")
380         else:
381             Subst["__MAINTAINER_FROM__"] = changes["maintainer2047"]
382             Subst["__MAINTAINER_TO__"] = changes["maintainer2047"]
383             Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown")
384
385         if "sponsoremail" in changes:
386             Subst["__MAINTAINER_TO__"] += ", %s"%changes["sponsoremail"]
387
388         if self.Cnf.has_key("Dinstall::TrackingServer") and changes.has_key("source"):
389             Subst["__MAINTAINER_TO__"] += "\nBcc: %s@%s" % (changes["source"], self.Cnf["Dinstall::TrackingServer"])
390
391         # Apply any global override of the Maintainer field
392         if self.Cnf.get("Dinstall::OverrideMaintainer"):
393             Subst["__MAINTAINER_TO__"] = self.Cnf["Dinstall::OverrideMaintainer"]
394             Subst["__MAINTAINER_FROM__"] = self.Cnf["Dinstall::OverrideMaintainer"]
395
396         Subst["__REJECT_MESSAGE__"] = reject_message
397         Subst["__SOURCE__"] = changes.get("source", "Unknown")
398         Subst["__VERSION__"] = changes.get("version", "Unknown")
399
400     ###########################################################################
401
402     def build_summaries(self):
403         """ Build a summary of changes the upload introduces. """
404         changes = self.pkg.changes
405         files = self.pkg.files
406
407         byhand = summary = new = ""
408
409         # changes["distribution"] may not exist in corner cases
410         # (e.g. unreadable changes files)
411         if not changes.has_key("distribution") or not isinstance(changes["distribution"], DictType):
412             changes["distribution"] = {}
413
414         override_summary =""
415         file_keys = files.keys()
416         file_keys.sort()
417         for file_entry in file_keys:
418             if files[file_entry].has_key("byhand"):
419                 byhand = 1
420                 summary += file_entry + " byhand\n"
421             elif files[file_entry].has_key("new"):
422                 new = 1
423                 summary += "(new) %s %s %s\n" % (file_entry, files[file_entry]["priority"], files[file_entry]["section"])
424                 if files[file_entry].has_key("othercomponents"):
425                     summary += "WARNING: Already present in %s distribution.\n" % (files[file_entry]["othercomponents"])
426                 if files[file_entry]["type"] == "deb":
427                     deb_fh = utils.open_file(file_entry)
428                     summary += apt_pkg.ParseSection(apt_inst.debExtractControl(deb_fh))["Description"] + '\n'
429                     deb_fh.close()
430             else:
431                 files[file_entry]["pool name"] = utils.poolify (changes.get("source",""), files[file_entry]["component"])
432                 destination = self.Cnf["Dir::PoolRoot"] + files[file_entry]["pool name"] + file_entry
433                 summary += file_entry + "\n  to " + destination + "\n"
434                 if not files[file_entry].has_key("type"):
435                     files[file_entry]["type"] = "unknown"
436                 if files[file_entry]["type"] in ["deb", "udeb", "dsc"]:
437                     # (queue/unchecked), there we have override entries already, use them
438                     # (process-new), there we dont have override entries, use the newly generated ones.
439                     override_prio = files[file_entry].get("override priority", files[file_entry]["priority"])
440                     override_sect = files[file_entry].get("override section", files[file_entry]["section"])
441                     override_summary += "%s - %s %s\n" % (file_entry, override_prio, override_sect)
442
443         short_summary = summary
444
445         # This is for direport's benefit...
446         f = re_fdnic.sub("\n .\n", changes.get("changes",""))
447
448         if byhand or new:
449             summary += "Changes: " + f
450
451         summary += "\n\nOverride entries for your package:\n" + override_summary + "\n"
452
453         summary += self.announce(short_summary, 0)
454
455         return (summary, short_summary)
456
457     ###########################################################################
458
459     def close_bugs (self, summary, action):
460         """
461         Send mail to close bugs as instructed by the closes field in the changes file.
462         Also add a line to summary if any work was done.
463
464         @type summary: string
465         @param summary: summary text, as given by L{build_summaries}
466
467         @type action: bool
468         @param action: Set to false no real action will be done.
469
470         @rtype: string
471         @return: summary. If action was taken, extended by the list of closed bugs.
472
473         """
474         changes = self.pkg.changes
475         Subst = self.Subst
476         Cnf = self.Cnf
477
478         bugs = changes["closes"].keys()
479
480         if not bugs:
481             return summary
482
483         bugs.sort()
484         summary += "Closing bugs: "
485         for bug in bugs:
486             summary += "%s " % (bug)
487             if action:
488                 Subst["__BUG_NUMBER__"] = bug
489                 if changes["distribution"].has_key("stable"):
490                     Subst["__STABLE_WARNING__"] = """
491 Note that this package is not part of the released stable Debian
492 distribution.  It may have dependencies on other unreleased software,
493 or other instabilities.  Please take care if you wish to install it.
494 The update will eventually make its way into the next released Debian
495 distribution."""
496                 else:
497                     Subst["__STABLE_WARNING__"] = ""
498                     mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/process-unchecked.bug-close")
499                     utils.send_mail (mail_message)
500         if action:
501             self.Logger.log(["closing bugs"]+bugs)
502         summary += "\n"
503
504         return summary
505
506     ###########################################################################
507
508     def announce (self, short_summary, action):
509         """
510         Send an announce mail about a new upload.
511
512         @type short_summary: string
513         @param short_summary: Short summary text to include in the mail
514
515         @type action: bool
516         @param action: Set to false no real action will be done.
517
518         @rtype: string
519         @return: Textstring about action taken.
520
521         """
522         Subst = self.Subst
523         Cnf = self.Cnf
524         changes = self.pkg.changes
525
526         # Only do announcements for source uploads with a recent dpkg-dev installed
527         if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
528             return ""
529
530         lists_done = {}
531         summary = ""
532         Subst["__SHORT_SUMMARY__"] = short_summary
533
534         for dist in changes["distribution"].keys():
535             announce_list = Cnf.Find("Suite::%s::Announce" % (dist))
536             if announce_list == "" or lists_done.has_key(announce_list):
537                 continue
538             lists_done[announce_list] = 1
539             summary += "Announcing to %s\n" % (announce_list)
540
541             if action:
542                 Subst["__ANNOUNCE_LIST_ADDRESS__"] = announce_list
543                 if Cnf.get("Dinstall::TrackingServer") and changes["architecture"].has_key("source"):
544                     Subst["__ANNOUNCE_LIST_ADDRESS__"] = Subst["__ANNOUNCE_LIST_ADDRESS__"] + "\nBcc: %s@%s" % (changes["source"], Cnf["Dinstall::TrackingServer"])
545                 mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/process-unchecked.announce")
546                 utils.send_mail (mail_message)
547
548         if Cnf.FindB("Dinstall::CloseBugs"):
549             summary = self.close_bugs(summary, action)
550
551         return summary
552
553     ###########################################################################
554
555     def accept (self, summary, short_summary):
556         """
557         Accept an upload.
558
559         This moves all files referenced from the .changes into the I{accepted}
560         queue, sends the accepted mail, announces to lists, closes bugs and
561         also checks for override disparities. If enabled it will write out
562         the version history for the BTS Version Tracking and will finally call
563         L{queue_build}.
564
565         @type summary: string
566         @param summary: Summary text
567
568         @type short_summary: string
569         @param short_summary: Short summary
570
571         """
572
573         Cnf = self.Cnf
574         Subst = self.Subst
575         files = self.pkg.files
576         changes = self.pkg.changes
577         changes_file = self.pkg.changes_file
578         dsc = self.pkg.dsc
579
580         print "Accepting."
581         self.Logger.log(["Accepting changes",changes_file])
582
583         self.dump_vars(Cnf["Dir::Queue::Accepted"])
584
585         # Move all the files into the accepted directory
586         utils.move(changes_file, Cnf["Dir::Queue::Accepted"])
587         file_keys = files.keys()
588         for file_entry in file_keys:
589             utils.move(file_entry, Cnf["Dir::Queue::Accepted"])
590             self.accept_bytes += float(files[file_entry]["size"])
591         self.accept_count += 1
592
593         # Send accept mail, announce to lists, close bugs and check for
594         # override disparities
595         if not Cnf["Dinstall::Options::No-Mail"]:
596             Subst["__SUITE__"] = ""
597             Subst["__SUMMARY__"] = summary
598             mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/process-unchecked.accepted")
599             utils.send_mail(mail_message)
600             self.announce(short_summary, 1)
601
602
603         ## Helper stuff for DebBugs Version Tracking
604         if Cnf.Find("Dir::Queue::BTSVersionTrack"):
605             # ??? once queue/* is cleared on *.d.o and/or reprocessed
606             # the conditionalization on dsc["bts changelog"] should be
607             # dropped.
608
609             # Write out the version history from the changelog
610             if changes["architecture"].has_key("source") and \
611                dsc.has_key("bts changelog"):
612
613                 (fd, temp_filename) = utils.temp_filename(Cnf["Dir::Queue::BTSVersionTrack"], prefix=".")
614                 version_history = os.fdopen(fd, 'w')
615                 version_history.write(dsc["bts changelog"])
616                 version_history.close()
617                 filename = "%s/%s" % (Cnf["Dir::Queue::BTSVersionTrack"],
618                                       changes_file[:-8]+".versions")
619                 os.rename(temp_filename, filename)
620                 os.chmod(filename, 0644)
621
622             # Write out the binary -> source mapping.
623             (fd, temp_filename) = utils.temp_filename(Cnf["Dir::Queue::BTSVersionTrack"], prefix=".")
624             debinfo = os.fdopen(fd, 'w')
625             for file_entry in file_keys:
626                 f = files[file_entry]
627                 if f["type"] == "deb":
628                     line = " ".join([f["package"], f["version"],
629                                      f["architecture"], f["source package"],
630                                      f["source version"]])
631                     debinfo.write(line+"\n")
632             debinfo.close()
633             filename = "%s/%s" % (Cnf["Dir::Queue::BTSVersionTrack"],
634                                   changes_file[:-8]+".debinfo")
635             os.rename(temp_filename, filename)
636             os.chmod(filename, 0644)
637
638         self.queue_build("accepted", Cnf["Dir::Queue::Accepted"])
639
640     ###########################################################################
641
642     def queue_build (self, queue, path):
643         """
644         Prepare queue_build database table used for incoming autobuild support.
645
646         @type queue: string
647         @param queue: queue name
648
649         @type path: string
650         @param path: path for the queue file entries/link destinations
651         """
652
653         Cnf = self.Cnf
654         Subst = self.Subst
655         files = self.pkg.files
656         changes = self.pkg.changes
657         changes_file = self.pkg.changes_file
658         dsc = self.pkg.dsc
659         file_keys = files.keys()
660
661         ## Special support to enable clean auto-building of queued packages
662         queue_id = database.get_or_set_queue_id(queue)
663
664         self.projectB.query("BEGIN WORK")
665         for suite in changes["distribution"].keys():
666             if suite not in Cnf.ValueList("Dinstall::QueueBuildSuites"):
667                 continue
668             suite_id = database.get_suite_id(suite)
669             dest_dir = Cnf["Dir::QueueBuild"]
670             if Cnf.FindB("Dinstall::SecurityQueueBuild"):
671                 dest_dir = os.path.join(dest_dir, suite)
672             for file_entry in file_keys:
673                 src = os.path.join(path, file_entry)
674                 dest = os.path.join(dest_dir, file_entry)
675                 if Cnf.FindB("Dinstall::SecurityQueueBuild"):
676                     # Copy it since the original won't be readable by www-data
677                     utils.copy(src, dest)
678                 else:
679                     # Create a symlink to it
680                     os.symlink(src, dest)
681                 # Add it to the list of packages for later processing by apt-ftparchive
682                 self.projectB.query("INSERT INTO queue_build (suite, queue, filename, in_queue) VALUES (%s, %s, '%s', 't')" % (suite_id, queue_id, dest))
683             # If the .orig.tar.gz is in the pool, create a symlink to
684             # it (if one doesn't already exist)
685             if self.pkg.orig_tar_id:
686                 # Determine the .orig.tar.gz file name
687                 for dsc_file in self.pkg.dsc_files.keys():
688                     if dsc_file.endswith(".orig.tar.gz"):
689                         filename = dsc_file
690                 dest = os.path.join(dest_dir, filename)
691                 # If it doesn't exist, create a symlink
692                 if not os.path.exists(dest):
693                     # Find the .orig.tar.gz in the pool
694                     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))
695                     ql = q.getresult()
696                     if not ql:
697                         utils.fubar("[INTERNAL ERROR] Couldn't find id %s in files table." % (self.pkg.orig_tar_id))
698                     src = os.path.join(ql[0][0], ql[0][1])
699                     os.symlink(src, dest)
700                     # Add it to the list of packages for later processing by apt-ftparchive
701                     self.projectB.query("INSERT INTO queue_build (suite, queue, filename, in_queue) VALUES (%s, %s, '%s', 't')" % (suite_id, queue_id, dest))
702                 # if it does, update things to ensure it's not removed prematurely
703                 else:
704                     self.projectB.query("UPDATE queue_build SET in_queue = 't', last_used = NULL WHERE filename = '%s' AND suite = %s" % (dest, suite_id))
705
706         self.projectB.query("COMMIT WORK")
707
708     ###########################################################################
709
710     def check_override (self):
711         """
712         Checks override entries for validity. Mails "Override disparity" warnings,
713         if that feature is enabled.
714
715         Abandons the check if
716           - this is a non-sourceful upload
717           - override disparity checks are disabled
718           - mail sending is disabled
719
720         """
721         Subst = self.Subst
722         changes = self.pkg.changes
723         files = self.pkg.files
724         Cnf = self.Cnf
725
726         # Abandon the check if:
727         #  a) it's a non-sourceful upload
728         #  b) override disparity checks have been disabled
729         #  c) we're not sending mail
730         if not changes["architecture"].has_key("source") or \
731            not Cnf.FindB("Dinstall::OverrideDisparityCheck") or \
732            Cnf["Dinstall::Options::No-Mail"]:
733             return
734
735         summary = ""
736         file_keys = files.keys()
737         file_keys.sort()
738         for file_entry in file_keys:
739             if not files[file_entry].has_key("new") and files[file_entry]["type"] == "deb":
740                 section = files[file_entry]["section"]
741                 override_section = files[file_entry]["override section"]
742                 if section.lower() != override_section.lower() and section != "-":
743                     summary += "%s: package says section is %s, override says %s.\n" % (file_entry, section, override_section)
744                 priority = files[file_entry]["priority"]
745                 override_priority = files[file_entry]["override priority"]
746                 if priority != override_priority and priority != "-":
747                     summary += "%s: package says priority is %s, override says %s.\n" % (file_entry, priority, override_priority)
748
749         if summary == "":
750             return
751
752         Subst["__SUMMARY__"] = summary
753         mail_message = utils.TemplateSubst(Subst,self.Cnf["Dir::Templates"]+"/process-unchecked.override-disparity")
754         utils.send_mail(mail_message)
755
756     ###########################################################################
757
758     def force_reject (self, files):
759         """
760         Forcefully move files from the current directory to the
761         reject directory.  If any file already exists in the reject
762         directory it will be moved to the morgue to make way for
763         the new file.
764
765         @type files: dict
766         @param files: file dictionary
767
768         """
769
770         Cnf = self.Cnf
771
772         for file_entry in files:
773             # Skip any files which don't exist or which we don't have permission to copy.
774             if os.access(file_entry,os.R_OK) == 0:
775                 continue
776             dest_file = os.path.join(Cnf["Dir::Queue::Reject"], file_entry)
777             try:
778                 dest_fd = os.open(dest_file, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
779             except OSError, e:
780                 # File exists?  Let's try and move it to the morgue
781                 if errno.errorcode[e.errno] == 'EEXIST':
782                     morgue_file = os.path.join(Cnf["Dir::Morgue"],Cnf["Dir::MorgueReject"],file_entry)
783                     try:
784                         morgue_file = utils.find_next_free(morgue_file)
785                     except NoFreeFilenameError:
786                         # Something's either gone badly Pete Tong, or
787                         # someone is trying to exploit us.
788                         utils.warn("**WARNING** failed to move %s from the reject directory to the morgue." % (file_entry))
789                         return
790                     utils.move(dest_file, morgue_file, perms=0660)
791                     try:
792                         dest_fd = os.open(dest_file, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
793                     except OSError, e:
794                         # Likewise
795                         utils.warn("**WARNING** failed to claim %s in the reject directory." % (file_entry))
796                         return
797                 else:
798                     raise
799             # If we got here, we own the destination file, so we can
800             # safely overwrite it.
801             utils.move(file_entry, dest_file, 1, perms=0660)
802             os.close(dest_fd)
803
804     ###########################################################################
805
806     def do_reject (self, manual = 0, reject_message = "", note = ""):
807         """
808         Reject an upload. If called without a reject message or C{manual} is
809         true, spawn an editor so the user can write one.
810
811         @type manual: bool
812         @param manual: manual or automated rejection
813
814         @type reject_message: string
815         @param reject_message: A reject message
816
817         @return: 0
818
819         """
820         # If we weren't given a manual rejection message, spawn an
821         # editor so the user can add one in...
822         if manual and not reject_message:
823             (fd, temp_filename) = utils.temp_filename()
824             temp_file = os.fdopen(fd, 'w')
825             if len(note) > 0:
826                 for line in note:
827                     temp_file.write(line)
828             temp_file.close()
829             editor = os.environ.get("EDITOR","vi")
830             answer = 'E'
831             while answer == 'E':
832                 os.system("%s %s" % (editor, temp_filename))
833                 temp_fh = utils.open_file(temp_filename)
834                 reject_message = "".join(temp_fh.readlines())
835                 temp_fh.close()
836                 print "Reject message:"
837                 print utils.prefix_multi_line_string(reject_message,"  ",include_blank_lines=1)
838                 prompt = "[R]eject, Edit, Abandon, Quit ?"
839                 answer = "XXX"
840                 while prompt.find(answer) == -1:
841                     answer = utils.our_raw_input(prompt)
842                     m = re_default_answer.search(prompt)
843                     if answer == "":
844                         answer = m.group(1)
845                     answer = answer[:1].upper()
846             os.unlink(temp_filename)
847             if answer == 'A':
848                 return 1
849             elif answer == 'Q':
850                 sys.exit(0)
851
852         print "Rejecting.\n"
853
854         Cnf = self.Cnf
855         Subst = self.Subst
856         pkg = self.pkg
857
858         reason_filename = pkg.changes_file[:-8] + ".reason"
859         reason_filename = Cnf["Dir::Queue::Reject"] + '/' + reason_filename
860
861         # Move all the files into the reject directory
862         reject_files = pkg.files.keys() + [pkg.changes_file]
863         self.force_reject(reject_files)
864
865         # If we fail here someone is probably trying to exploit the race
866         # so let's just raise an exception ...
867         if os.path.exists(reason_filename):
868             os.unlink(reason_filename)
869         reason_fd = os.open(reason_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644)
870
871         if not manual:
872             Subst["__REJECTOR_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"]
873             Subst["__MANUAL_REJECT_MESSAGE__"] = ""
874             Subst["__CC__"] = "X-DAK-Rejection: automatic (moo)\nX-Katie-Rejection: automatic (moo)"
875             os.write(reason_fd, reject_message)
876             reject_mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/queue.rejected")
877         else:
878             # Build up the rejection email
879             user_email_address = utils.whoami() + " <%s>" % (Cnf["Dinstall::MyAdminAddress"])
880
881             Subst["__REJECTOR_ADDRESS__"] = user_email_address
882             Subst["__MANUAL_REJECT_MESSAGE__"] = reject_message
883             Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"]
884             reject_mail_message = utils.TemplateSubst(Subst,Cnf["Dir::Templates"]+"/queue.rejected")
885             # Write the rejection email out as the <foo>.reason file
886             os.write(reason_fd, reject_mail_message)
887
888         os.close(reason_fd)
889
890         # Send the rejection mail if appropriate
891         if not Cnf["Dinstall::Options::No-Mail"]:
892             utils.send_mail(reject_mail_message)
893
894         self.Logger.log(["rejected", pkg.changes_file])
895         return 0
896
897     ################################################################################
898
899     def source_exists (self, package, source_version, suites = ["any"]):
900         """
901         Ensure that source exists somewhere in the archive for the binary
902         upload being processed.
903           1. exact match     => 1.0-3
904           2. bin-only NMU    => 1.0-3+b1 , 1.0-3.1+b1
905
906         @type package: string
907         @param package: package source name
908
909         @type source_version: string
910         @param source_version: expected source version
911
912         @type suites: list
913         @param suites: list of suites to check in, default I{any}
914
915         @rtype: int
916         @return: returns 1 if a source with expected version is found, otherwise 0
917
918         """
919         okay = 1
920         for suite in suites:
921             if suite == "any":
922                 que = "SELECT s.version FROM source s WHERE s.source = '%s'" % \
923                     (package)
924             else:
925                 # source must exist in suite X, or in some other suite that's
926                 # mapped to X, recursively... silent-maps are counted too,
927                 # unreleased-maps aren't.
928                 maps = self.Cnf.ValueList("SuiteMappings")[:]
929                 maps.reverse()
930                 maps = [ m.split() for m in maps ]
931                 maps = [ (x[1], x[2]) for x in maps
932                                 if x[0] == "map" or x[0] == "silent-map" ]
933                 s = [suite]
934                 for x in maps:
935                     if x[1] in s and x[0] not in s:
936                         s.append(x[0])
937
938                 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]))
939             q = self.projectB.query(que)
940
941             # Reduce the query results to a list of version numbers
942             ql = [ i[0] for i in q.getresult() ]
943
944             # Try (1)
945             if source_version in ql:
946                 continue
947
948             # Try (2)
949             orig_source_version = re_bin_only_nmu.sub('', source_version)
950             if orig_source_version in ql:
951                 continue
952
953             # No source found...
954             okay = 0
955             break
956         return okay
957
958     ################################################################################
959
960     def in_override_p (self, package, component, suite, binary_type, file):
961         """
962         Check if a package already has override entries in the DB
963
964         @type package: string
965         @param package: package name
966
967         @type component: string
968         @param component: database id of the component, as returned by L{database.get_component_id}
969
970         @type suite: int
971         @param suite: database id of the suite, as returned by L{database.get_suite_id}
972
973         @type binary_type: string
974         @param binary_type: type of the package
975
976         @type file: string
977         @param file: filename we check
978
979         @return: the database result. But noone cares anyway.
980
981         """
982         files = self.pkg.files
983
984         if binary_type == "": # must be source
985             file_type = "dsc"
986         else:
987             file_type = binary_type
988
989         # Override suite name; used for example with proposed-updates
990         if self.Cnf.Find("Suite::%s::OverrideSuite" % (suite)) != "":
991             suite = self.Cnf["Suite::%s::OverrideSuite" % (suite)]
992
993         # Avoid <undef> on unknown distributions
994         suite_id = database.get_suite_id(suite)
995         if suite_id == -1:
996             return None
997         component_id = database.get_component_id(component)
998         type_id = database.get_override_type_id(file_type)
999
1000         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"
1001                            % (package, suite_id, component_id, type_id))
1002         result = q.getresult()
1003         # If checking for a source package fall back on the binary override type
1004         if file_type == "dsc" and not result:
1005             deb_type_id = database.get_override_type_id("deb")
1006             udeb_type_id = database.get_override_type_id("udeb")
1007             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"
1008                                % (package, suite_id, component_id, deb_type_id, udeb_type_id))
1009             result = q.getresult()
1010
1011         # Remember the section and priority so we can check them later if appropriate
1012         if result:
1013             files[file]["override section"] = result[0][0]
1014             files[file]["override priority"] = result[0][1]
1015
1016         return result
1017
1018     ################################################################################
1019
1020     def reject (self, str, prefix="Rejected: "):
1021         """
1022         Add C{str} to reject_message. Adds C{prefix}, by default "Rejected: "
1023
1024         @type str: string
1025         @param str: Reject text
1026
1027         @type prefix: string
1028         @param prefix: Prefix text, default Rejected:
1029
1030         """
1031         if str:
1032             # Unlike other rejects we add new lines first to avoid trailing
1033             # new lines when this message is passed back up to a caller.
1034             if self.reject_message:
1035                 self.reject_message += "\n"
1036             self.reject_message += prefix + str
1037
1038     ################################################################################
1039
1040     def get_anyversion(self, query_result, suite):
1041         """ """
1042         anyversion=None
1043         anysuite = [suite] + self.Cnf.ValueList("Suite::%s::VersionChecks::Enhances" % (suite))
1044         for (v, s) in query_result:
1045             if s in [ x.lower() for x in anysuite ]:
1046                 if not anyversion or apt_pkg.VersionCompare(anyversion, v) <= 0:
1047                     anyversion=v
1048         return anyversion
1049
1050     ################################################################################
1051
1052     def cross_suite_version_check(self, query_result, file, new_version,
1053             sourceful=False):
1054         """
1055         Ensure versions are newer than existing packages in target
1056         suites and that cross-suite version checking rules as
1057         set out in the conf file are satisfied.
1058
1059         """
1060
1061         # Check versions for each target suite
1062         for target_suite in self.pkg.changes["distribution"].keys():
1063             must_be_newer_than = [ i.lower() for i in self.Cnf.ValueList("Suite::%s::VersionChecks::MustBeNewerThan" % (target_suite)) ]
1064             must_be_older_than = [ i.lower() for i in self.Cnf.ValueList("Suite::%s::VersionChecks::MustBeOlderThan" % (target_suite)) ]
1065             # Enforce "must be newer than target suite" even if conffile omits it
1066             if target_suite not in must_be_newer_than:
1067                 must_be_newer_than.append(target_suite)
1068             for entry in query_result:
1069                 existent_version = entry[0]
1070                 suite = entry[1]
1071                 if suite in must_be_newer_than and sourceful and \
1072                    apt_pkg.VersionCompare(new_version, existent_version) < 1:
1073                     self.reject("%s: old version (%s) in %s >= new version (%s) targeted at %s." % (file, existent_version, suite, new_version, target_suite))
1074                 if suite in must_be_older_than and \
1075                    apt_pkg.VersionCompare(new_version, existent_version) > -1:
1076                     ch = self.pkg.changes
1077                     cansave = 0
1078                     if ch.get('distribution-version', {}).has_key(suite):
1079                     # we really use the other suite, ignoring the conflicting one ...
1080                         addsuite = ch["distribution-version"][suite]
1081
1082                         add_version = self.get_anyversion(query_result, addsuite)
1083                         target_version = self.get_anyversion(query_result, target_suite)
1084
1085                         if not add_version:
1086                             # not add_version can only happen if we map to a suite
1087                             # that doesn't enhance the suite we're propup'ing from.
1088                             # so "propup-ver x a b c; map a d" is a problem only if
1089                             # d doesn't enhance a.
1090                             #
1091                             # i think we could always propagate in this case, rather
1092                             # than complaining. either way, this isn't a REJECT issue
1093                             #
1094                             # And - we really should complain to the dorks who configured dak
1095                             self.reject("%s is mapped to, but not enhanced by %s - adding anyways" % (suite, addsuite), "Warning: ")
1096                             self.pkg.changes.setdefault("propdistribution", {})
1097                             self.pkg.changes["propdistribution"][addsuite] = 1
1098                             cansave = 1
1099                         elif not target_version:
1100                             # not targets_version is true when the package is NEW
1101                             # we could just stick with the "...old version..." REJECT
1102                             # for this, I think.
1103                             self.reject("Won't propogate NEW packages.")
1104                         elif apt_pkg.VersionCompare(new_version, add_version) < 0:
1105                             # propogation would be redundant. no need to reject though.
1106                             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: ")
1107                             cansave = 1
1108                         elif apt_pkg.VersionCompare(new_version, add_version) > 0 and \
1109                              apt_pkg.VersionCompare(add_version, target_version) >= 0:
1110                             # propogate!!
1111                             self.reject("Propogating upload to %s" % (addsuite), "Warning: ")
1112                             self.pkg.changes.setdefault("propdistribution", {})
1113                             self.pkg.changes["propdistribution"][addsuite] = 1
1114                             cansave = 1
1115
1116                     if not cansave:
1117                         self.reject("%s: old version (%s) in %s <= new version (%s) targeted at %s." % (file, existent_version, suite, new_version, target_suite))
1118
1119     ################################################################################
1120
1121     def check_binary_against_db(self, file):
1122         """
1123
1124         """
1125         self.reject_message = ""
1126         files = self.pkg.files
1127
1128         # Ensure version is sane
1129         q = self.projectB.query("""
1130 SELECT b.version, su.suite_name FROM binaries b, bin_associations ba, suite su,
1131                                      architecture a
1132  WHERE b.package = '%s' AND (a.arch_string = '%s' OR a.arch_string = 'all')
1133    AND ba.bin = b.id AND ba.suite = su.id AND b.architecture = a.id"""
1134                                 % (files[file]["package"],
1135                                    files[file]["architecture"]))
1136         self.cross_suite_version_check(q.getresult(), file,
1137             files[file]["version"], sourceful=False)
1138
1139         # Check for any existing copies of the file
1140         q = self.projectB.query("""
1141 SELECT b.id FROM binaries b, architecture a
1142  WHERE b.package = '%s' AND b.version = '%s' AND a.arch_string = '%s'
1143    AND a.id = b.architecture"""
1144                                 % (files[file]["package"],
1145                                    files[file]["version"],
1146                                    files[file]["architecture"]))
1147         if q.getresult():
1148             self.reject("%s: can not overwrite existing copy already in the archive." % (file))
1149
1150         return self.reject_message
1151
1152     ################################################################################
1153
1154     def check_source_against_db(self, file):
1155         """
1156         """
1157         self.reject_message = ""
1158         dsc = self.pkg.dsc
1159
1160         # Ensure version is sane
1161         q = self.projectB.query("""
1162 SELECT s.version, su.suite_name FROM source s, src_associations sa, suite su
1163  WHERE s.source = '%s' AND sa.source = s.id AND sa.suite = su.id""" % (dsc.get("source")))
1164         self.cross_suite_version_check(q.getresult(), file, dsc.get("version"),
1165             sourceful=True)
1166
1167         return self.reject_message
1168
1169     ################################################################################
1170
1171
1172     def check_dsc_against_db(self, file):
1173         """
1174
1175         @warning: NB: this function can remove entries from the 'files' index [if
1176          the .orig.tar.gz is a duplicate of the one in the archive]; if
1177          you're iterating over 'files' and call this function as part of
1178          the loop, be sure to add a check to the top of the loop to
1179          ensure you haven't just tried to dereference the deleted entry.
1180
1181         """
1182         self.reject_message = ""
1183         files = self.pkg.files
1184         dsc_files = self.pkg.dsc_files
1185         self.pkg.orig_tar_gz = None
1186
1187         # Try and find all files mentioned in the .dsc.  This has
1188         # to work harder to cope with the multiple possible
1189         # locations of an .orig.tar.gz.
1190         # The ordering on the select is needed to pick the newest orig
1191         # when it exists in multiple places.
1192         for dsc_file in dsc_files.keys():
1193             found = None
1194             if files.has_key(dsc_file):
1195                 actual_md5 = files[dsc_file]["md5sum"]
1196                 actual_size = int(files[dsc_file]["size"])
1197                 found = "%s in incoming" % (dsc_file)
1198                 # Check the file does not already exist in the archive
1199                 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))
1200                 ql = q.getresult()
1201                 # Strip out anything that isn't '%s' or '/%s$'
1202                 for i in ql:
1203                     if i[3] != dsc_file and i[3][-(len(dsc_file)+1):] != '/'+dsc_file:
1204                         ql.remove(i)
1205
1206                 # "[dak] has not broken them.  [dak] has fixed a
1207                 # brokenness.  Your crappy hack exploited a bug in
1208                 # the old dinstall.
1209                 #
1210                 # "(Come on!  I thought it was always obvious that
1211                 # one just doesn't release different files with
1212                 # the same name and version.)"
1213                 #                        -- ajk@ on d-devel@l.d.o
1214
1215                 if ql:
1216                     # Ignore exact matches for .orig.tar.gz
1217                     match = 0
1218                     if dsc_file.endswith(".orig.tar.gz"):
1219                         for i in ql:
1220                             if files.has_key(dsc_file) and \
1221                                int(files[dsc_file]["size"]) == int(i[0]) and \
1222                                files[dsc_file]["md5sum"] == i[1]:
1223                                 self.reject("ignoring %s, since it's already in the archive." % (dsc_file), "Warning: ")
1224                                 del files[dsc_file]
1225                                 self.pkg.orig_tar_gz = i[2] + i[3]
1226                                 match = 1
1227
1228                     if not match:
1229                         self.reject("can not overwrite existing copy of '%s' already in the archive." % (dsc_file))
1230             elif dsc_file.endswith(".orig.tar.gz"):
1231                 # Check in the pool
1232                 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))
1233                 ql = q.getresult()
1234                 # Strip out anything that isn't '%s' or '/%s$'
1235                 for i in ql:
1236                     if i[1] != dsc_file and i[1][-(len(dsc_file)+1):] != '/'+dsc_file:
1237                         ql.remove(i)
1238
1239                 if ql:
1240                     # Unfortunately, we may get more than one match here if,
1241                     # for example, the package was in potato but had an -sa
1242                     # upload in woody.  So we need to choose the right one.
1243
1244                     # default to something sane in case we don't match any or have only one
1245                     x = ql[0]
1246
1247                     if len(ql) > 1:
1248                         for i in ql:
1249                             old_file = i[0] + i[1]
1250                             old_file_fh = utils.open_file(old_file)
1251                             actual_md5 = apt_pkg.md5sum(old_file_fh)
1252                             old_file_fh.close()
1253                             actual_size = os.stat(old_file)[stat.ST_SIZE]
1254                             if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
1255                                 x = i
1256
1257                     old_file = x[0] + x[1]
1258                     old_file_fh = utils.open_file(old_file)
1259                     actual_md5 = apt_pkg.md5sum(old_file_fh)
1260                     old_file_fh.close()
1261                     actual_size = os.stat(old_file)[stat.ST_SIZE]
1262                     found = old_file
1263                     suite_type = x[2]
1264                     # need this for updating dsc_files in install()
1265                     dsc_files[dsc_file]["files id"] = x[3]
1266                     # See install() in process-accepted...
1267                     self.pkg.orig_tar_id = x[3]
1268                     self.pkg.orig_tar_gz = old_file
1269                     self.pkg.orig_tar_location = x[4]
1270                 else:
1271                     # Not there? Check the queue directories...
1272
1273                     in_unchecked = os.path.join(self.Cnf["Dir::Queue::Unchecked"],dsc_file)
1274                     # See process_it() in 'dak process-unchecked' for explanation of this
1275                     # in_unchecked check dropped by ajt 2007-08-28, how did that
1276                     # ever make sense?
1277                     if os.path.exists(in_unchecked) and False:
1278                         return (self.reject_message, in_unchecked)
1279                     else:
1280                         for directory in [ "Accepted", "New", "Byhand", "ProposedUpdates", "OldProposedUpdates" ]:
1281                             in_otherdir = os.path.join(self.Cnf["Dir::Queue::%s" % (directory)],dsc_file)
1282                             if os.path.exists(in_otherdir):
1283                                 in_otherdir_fh = utils.open_file(in_otherdir)
1284                                 actual_md5 = apt_pkg.md5sum(in_otherdir_fh)
1285                                 in_otherdir_fh.close()
1286                                 actual_size = os.stat(in_otherdir)[stat.ST_SIZE]
1287                                 found = in_otherdir
1288                                 self.pkg.orig_tar_gz = in_otherdir
1289
1290                     if not found:
1291                         self.reject("%s refers to %s, but I can't find it in the queue or in the pool." % (file, dsc_file))
1292                         self.pkg.orig_tar_gz = -1
1293                         continue
1294             else:
1295                 self.reject("%s refers to %s, but I can't find it in the queue." % (file, dsc_file))
1296                 continue
1297             if actual_md5 != dsc_files[dsc_file]["md5sum"]:
1298                 self.reject("md5sum for %s doesn't match %s." % (found, file))
1299             if actual_size != int(dsc_files[dsc_file]["size"]):
1300                 self.reject("size for %s doesn't match %s." % (found, file))
1301
1302         return (self.reject_message, None)
1303
1304     def do_query(self, query):
1305         """
1306         Executes a database query. Writes statistics / timing to stderr.
1307
1308         @type query: string
1309         @param query: database query string, passed unmodified
1310
1311         @return: db result
1312
1313         @warning: The query is passed B{unmodified}, so be careful what you use this for.
1314         """
1315         sys.stderr.write("query: \"%s\" ... " % (query))
1316         before = time.time()
1317         r = self.projectB.query(query)
1318         time_diff = time.time()-before
1319         sys.stderr.write("took %.3f seconds.\n" % (time_diff))
1320         return r