]> git.decadent.org.uk Git - dak.git/blob - katie.py
New.
[dak.git] / katie.py
1 #!/usr/bin/env python
2
3 # Utility functions for katie
4 # Copyright (C) 2001  James Troup <james@nocrew.org>
5 # $Id: katie.py,v 1.1 2002-02-12 23:08:07 troup Exp $
6
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
11
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15 # GNU General Public License for more details.
16
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
20
21 ###############################################################################
22
23 import cPickle, errno, os, pg, re, stat, string;
24 import utils, db_access;
25 import apt_inst, apt_pkg;
26
27 from types import *;
28 from string import lower;
29
30 ###############################################################################
31
32 re_isanum = re.compile (r"^\d+$");
33 re_default_answer = re.compile(r"\[(.*)\]");
34 re_fdnic = re.compile("\n\n");
35 re_bin_only_nmu_of_mu = re.compile("\.\d+\.\d+$");
36 re_bin_only_nmu_of_nmu = re.compile("\.\d+$");
37
38 ###############################################################################
39
40 # Convenience wrapper to carry around all the package information in
41
42 class Pkg:
43     def __init__(self, **kwds):
44         self.__dict__.update(kwds);
45
46     def update(self, **kwds):
47         self.__dict__.update(kwds);
48
49 ###############################################################################
50
51 class nmu_p:
52     # Read in the group maintainer override file
53     def __init__ (self, Cnf):
54         self.group_maint = {};
55         self.Cnf = Cnf;
56         if Cnf.get("Dinstall::GroupOverrideFilename"):
57             filename = Cnf["Dir::OverrideDir"] + Cnf["Dinstall::GroupOverrideFilename"];
58             file = utils.open_file(filename);
59             for line in file.readlines():
60                 line = lower(string.strip(utils.re_comments.sub('', line)));
61                 if line != "":
62                     self.group_maint[line] = 1;
63             file.close();
64
65     def is_an_nmu (self, pkg):
66         Cnf = self.Cnf;
67         changes = pkg.changes;
68         dsc = pkg.dsc;
69
70         (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (lower(dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"])));
71         # changes["changedbyname"] == dsc_name is probably never true, but better safe than sorry
72         if dsc_name == lower(changes["maintainername"]) and \
73            (changes["changedby822"] == "" or lower(changes["changedbyname"]) == dsc_name):
74             return 0;
75
76         if dsc.has_key("uploaders"):
77             uploaders = string.split(lower(dsc["uploaders"]), ",");
78             uploadernames = {};
79             for i in uploaders:
80                 (rfc822, name, email) = utils.fix_maintainer (string.strip(i));
81                 uploadernames[name] = "";
82             if uploadernames.has_key(lower(changes["changedbyname"])):
83                 return 0;
84
85         # Some group maintained packages (e.g. Debian QA) are never NMU's
86         if self.group_maint.has_key(lower(changes["maintaineremail"])):
87             return 0;
88
89         return 1;
90
91 ###############################################################################
92
93 class Katie:
94
95     def __init__(self, Cnf):
96         self.Cnf = Cnf;
97         self.values = {};
98         # Read in the group-maint override file
99         self.nmu = nmu_p(Cnf);
100         self.accept_count = 0;
101         self.accept_bytes = 0L;
102         self.pkg = Pkg(changes = {}, dsc = {}, dsc_files = {}, files = {},
103                        legacy_source_untouchable = {});
104
105         # Initialize the substitution template mapping global
106         Subst = self.Subst = {};
107         Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"];
108         Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"];
109         Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"];
110         Subst["__KATIE_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"];
111
112         self.projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]));
113         db_access.init(Cnf, self.projectB);
114
115     ###########################################################################
116
117     def init_vars (self):
118         for i in [ "changes", "dsc", "files", "dsc_files", "legacy_source_untouchable" ]:
119             exec "self.pkg.%s.clear();" % (i);
120         self.pkg.orig_tar_id = None;
121         self.pkg.orig_tar_location = "";
122
123     ###########################################################################
124
125     def update_vars (self):
126         dump_filename = self.pkg.changes_file[:-8]+".katie";
127         dump_file = utils.open_file(dump_filename);
128         p = cPickle.Unpickler(dump_file);
129         for i in [ "changes", "dsc", "files", "dsc_files", "legacy_source_untouchable" ]:
130             exec "self.pkg.%s.update(p.load());" % (i);
131         for i in [ "orig_tar_id", "orig_tar_location" ]:
132             exec "self.pkg.%s = p.load();" % (i);
133         dump_file.close();
134
135     ###########################################################################
136
137     # This could just dump the dictionaries as is, but I'd like to avoid
138     # this so there's some idea of what katie & lisa use from jennifer
139
140     def dump_vars(self, dest_dir):
141         for i in [ "changes", "dsc", "files", "dsc_files",
142                    "legacy_source_untouchable", "orig_tar_id", "orig_tar_location" ]:
143             exec "%s = self.pkg.%s;" % (i,i);
144         dump_filename = os.path.join(dest_dir,self.pkg.changes_file[:-8] + ".katie");
145         dump_file = utils.open_file(dump_filename, 'w');
146         p = cPickle.Pickler(dump_file, 1);
147         for i in [ "d_changes", "d_dsc", "d_files", "d_dsc_files" ]:
148             exec "%s = {}" % i;
149         ## files
150         for file in files.keys():
151             d_files[file] = {};
152             for i in [ "package", "version", "architecture", "type", "size",
153                        "md5sum", "component", "location id", "source package",
154                        "source version", "maintainer", "dbtype", "files id",
155                        "new", "section", "priority", "oldfiles", "othercomponents" ]:
156                 if files[file].has_key(i):
157                     d_files[file][i] = files[file][i];
158         ## changes
159         # Mandatory changes fields
160         for i in [ "distribution", "source", "architecture", "version", "maintainer",
161                    "urgency", "fingerprint" ]:
162             d_changes[i] = changes[i];
163         # Optional changes fields
164         for i in [ "changed-by", "changedby822", "maintainer822", "filecontents" ]:
165             d_changes[i] = changes[i];
166         ## dsc
167         for i in [ "source", "version", "maintainer", "fingerprint" ]:
168             if dsc.has_key(i):
169                 d_dsc[i] = dsc[i];
170         ## dsc_files
171         for file in dsc_files.keys():
172             d_dsc_files[file] = {};
173             # Mandatory dsc_files fields
174             for i in [ "size", "md5sum" ]:
175                 d_dsc_files[file][i] = dsc_files[file][i];
176             # Optional dsc_files fields
177             for i in [ "files id" ]:
178                 if dsc_files[file].has_key(i):
179                     d_dsc_files[file][i] = dsc_files[file][i];
180
181         for i in [ d_changes, d_dsc, d_files, d_dsc_files,
182                    legacy_source_untouchable, orig_tar_id, orig_tar_location ]:
183             p.dump(i);
184         dump_file.close();
185
186     ###########################################################################
187
188     # Set up the per-package template substitution mappings
189
190     def update_subst (self, reject_message = ""):
191         Subst = self.Subst;
192         changes = self.pkg.changes;
193         # If jennifer crashed out in the right place, architecture may still be a string.
194         if not changes.has_key("architecture") or not isinstance(changes["architecture"], DictType):
195             changes["architecture"] = { "Unknown" : "" };
196         # and maintainer822 may not exist.
197         if not changes.has_key("maintainer822"):
198             changes["maintainer822"] = self.Cnf["Dinstall::MyEmailAddress"];
199
200         Subst["__ARCHITECTURE__"] = string.join(changes["architecture"].keys(), ' ' );
201         Subst["__CHANGES_FILENAME__"] = os.path.basename(self.pkg.changes_file);
202         Subst["__FILE_CONTENTS__"] = changes.get("filecontents", "");
203
204         # For source uploads the Changed-By field wins; otherwise Maintainer wins.
205         if changes["architecture"].has_key("source") and changes["changedby822"] != "" and (changes["changedby822"] != changes["maintainer822"]):
206             Subst["__MAINTAINER_FROM__"] = changes["changedby822"];
207             Subst["__MAINTAINER_TO__"] = changes["changedby822"] + ", " + changes["maintainer822"];
208             Subst["__MAINTAINER__"] = changes.get("changed-by", "Unknown");
209         else:
210             Subst["__MAINTAINER_FROM__"] = changes["maintainer822"];
211             Subst["__MAINTAINER_TO__"] = changes["maintainer822"];
212             Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown");
213         if self.Cnf.has_key("Dinstall::TrackingServer") and changes.has_key("source"):
214             Subst["__MAINTAINER_TO__"] = Subst["__MAINTAINER_TO__"] + "\nBcc: %s@%s" % (changes["source"], Cnf["Dinstall::TrackingServer"])
215
216         Subst["__REJECT_MESSAGE__"] = reject_message;
217         Subst["__SOURCE__"] = changes.get("source", "Unknown");
218         Subst["__VERSION__"] = changes.get("version", "Unknown");
219
220     ###########################################################################
221
222     def build_summaries(self):
223         changes = self.pkg.changes;
224         files = self.pkg.files;
225
226         byhand = summary = new = "";
227
228         # changes["distribution"] may not exist in corner cases
229         # (e.g. unreadable changes files)
230         if not changes.has_key("distribution") or not isinstance(changes["distribution"], DictType):
231             changes["distribution"] = {};
232
233         file_keys = files.keys();
234         file_keys.sort();
235         for file in file_keys:
236             if files[file].has_key("byhand"):
237                 byhand = 1
238                 summary = summary + file + " byhand\n"
239             elif files[file].has_key("new"):
240                 new = 1
241                 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
242                 if files[file].has_key("othercomponents"):
243                     summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
244                 if files[file]["type"] == "deb":
245                     summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file)))["Description"] + '\n';
246             else:
247                 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
248                 destination = self.Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
249                 summary = summary + file + "\n  to " + destination + "\n"
250
251         short_summary = summary;
252
253         # This is for direport's benefit...
254         f = re_fdnic.sub("\n .\n", changes.get("changes",""));
255
256         if byhand or new:
257             summary = summary + "Changes: " + f;
258
259         summary = summary + self.announce(short_summary, 0)
260
261         return (summary, short_summary);
262
263     ###########################################################################
264
265     def announce (self, short_summary, action):
266         Subst = self.Subst;
267         Cnf = self.Cnf;
268         changes = self.pkg.changes;
269         dsc = self.pkg.dsc;
270
271         # Only do announcements for source uploads with a recent dpkg-dev installed
272         if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
273             return ""
274
275         lists_done = {}
276         summary = ""
277         Subst["__SHORT_SUMMARY__"] = short_summary;
278
279         for dist in changes["distribution"].keys():
280             list = Cnf.Find("Suite::%s::Announce" % (dist))
281             if list == "" or lists_done.has_key(list):
282                 continue
283             lists_done[list] = 1
284             summary = summary + "Announcing to %s\n" % (list)
285
286             if action:
287                 Subst["__ANNOUNCE_LIST_ADDRESS__"] = list;
288                 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/jennifer.announce","r").read());
289                 utils.send_mail (mail_message, "")
290
291         bugs = changes["closes"].keys()
292         bugs.sort()
293         if not self.nmu.is_an_nmu(self.pkg):
294             summary = summary + "Closing bugs: "
295             for bug in bugs:
296                 summary = summary + "%s " % (bug)
297                 if action:
298                     Subst["__BUG_NUMBER__"] = bug;
299                     if changes["distribution"].has_key("stable"):
300                         Subst["__STABLE_WARNING__"] = """
301     Note that this package is not part of the released stable Debian
302     distribution.  It may have dependencies on other unreleased software,
303     or other instabilities.  Please take care if you wish to install it.
304     The update will eventually make its way into the next released Debian
305     distribution."""
306                     else:
307                         Subst["__STABLE_WARNING__"] = "";
308                     mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/jennifer.bug-close","r").read());
309                     utils.send_mail (mail_message, "")
310             if action:
311                 self.Logger.log(["closing bugs"]+bugs);
312         else:                     # NMU
313             summary = summary + "Setting bugs to severity fixed: "
314             control_message = ""
315             for bug in bugs:
316                 summary = summary + "%s " % (bug)
317                 control_message = control_message + "tag %s + fixed\n" % (bug)
318             if action and control_message != "":
319                 Subst["__CONTROL_MESSAGE__"] = control_message;
320                 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/jennifer.bug-nmu-fixed","r").read());
321                 utils.send_mail (mail_message, "")
322             if action:
323                 self.Logger.log(["setting bugs to fixed"]+bugs);
324         summary = summary + "\n"
325
326         return summary
327
328     ###########################################################################
329
330     def accept (self, summary, short_summary):
331         Cnf = self.Cnf;
332         Subst = self.Subst;
333         files = self.pkg.files;
334
335         print "Accepting."
336
337         self.dump_vars(Cnf["Dir::QueueAcceptedDir"]);
338
339         # Move all the files into the accepted directory
340         utils.move(self.pkg.changes_file, Cnf["Dir::QueueAcceptedDir"]);
341         file_keys = files.keys();
342         for file in file_keys:
343             utils.move(file, Cnf["Dir::QueueAcceptedDir"]);
344             self.accept_bytes = self.accept_bytes + float(files[file]["size"])
345         self.accept_count = self.accept_count + 1;
346
347         # Send accept mail, announce to lists, close bugs and check for
348         # override disparities
349         if not Cnf["Dinstall::Options::No-Mail"]:
350             Subst["__SUITE__"] = "";
351             Subst["__SUMMARY__"] = summary;
352             mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/jennifer.accepted","r").read());
353             utils.send_mail(mail_message, "")
354             self.announce(short_summary, 1)
355
356     ###########################################################################
357
358     def check_override (self):
359         Subst = self.Subst;
360         changes = self.pkg.changes;
361         files = self.pkg.files;
362
363         # Only check section & priority on sourceful uploads
364         if not changes["architecture"].has_key("source"):
365             return;
366
367         summary = "";
368         for file in files.keys():
369             if not files[file].has_key("new") and files[file]["type"] == "deb":
370                 section = files[file]["section"];
371                 override_section = files[file]["override section"];
372                 if lower(section) != lower(override_section) and section != "-":
373                     # Ignore this; it's a common mistake and not worth whining about
374                     if lower(section) == "non-us/main" and lower(override_section) == "non-us":
375                         continue;
376                     summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
377                 priority = files[file]["priority"];
378                 override_priority = files[file]["override priority"];
379                 if priority != override_priority and priority != "-":
380                     summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
381
382         if summary == "":
383             return;
384
385         Subst["__SUMMARY__"] = summary;
386         mail_message = utils.TemplateSubst(Subst,utils.open_file(self.Cnf["Dir::TemplatesDir"]+"/jennifer.override-disparity").read());
387         utils.send_mail (mail_message, "");
388
389     ###########################################################################
390
391     def force_move (self, files):
392         """Forcefully move files from the current directory to the reject
393            directory.  If any file already exists it will be moved to the
394            morgue to make way for the new file."""
395
396         Cnf = self.Cnf
397
398         for file in files:
399             # Skip any files which don't exist or which we don't have permission to copy.
400             if os.access(file,os.R_OK) == 0:
401                 continue;
402             dest_file = os.path.join(Cnf["Dir::QueueRejectDir"], file);
403             try:
404                 os.open(dest_file, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
405             except OSError, e:
406                 # File exists?  Let's try and move it to the morgue
407                 if errno.errorcode[e.errno] == 'EEXIST':
408                     morgue_file = os.path.join(Cnf["Dir::Morgue"],Cnf["Dir::MorgueRejectDir"],file);
409                     try:
410                         morgue_file = utils.find_next_free(morgue_file);
411                     except utils.tried_too_hard_exc:
412                         # Something's either gone badly Pete Tong, or
413                         # someone is trying to exploit us.
414                         utils.warn("**WARNING** failed to move %s from the reject directory to the morgue." % (file));
415                         return;
416                     utils.move(dest_file, morgue_file);
417                     try:
418                         os.open(dest_file, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
419                     except OSError, e:
420                         # Likewise
421                         utils.warn("**WARNING** failed to claim %s in the reject directory." % (file));
422                         return;
423                 else:
424                     raise;
425             # If we got here, we own the destination file, so we can
426             # safely overwrite it.
427             utils.move(file, dest_file, 1);
428
429     ###########################################################################
430
431     def do_reject (self, manual = 0, reject_message = ""):
432         print "Rejecting.\n"
433
434         Cnf = self.Cnf;
435         Subst = self.Subst;
436         pkg = self.pkg;
437
438         reason_filename = pkg.changes_file[:-8] + ".reason";
439         reject_filename = Cnf["Dir::QueueRejectDir"] + '/' + reason_filename;
440
441         # Move all the files into the reject directory
442         reject_files = pkg.files.keys() + [pkg.changes_file];
443         self.force_move(reject_files);
444
445         # If we fail here someone is probably trying to exploit the race
446         # so let's just raise an exception ...
447         if os.path.exists(reject_filename):
448             os.unlink(reject_filename);
449         fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
450
451         if not manual:
452             Subst["__REJECTOR_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"];
453             Subst["__MANUAL_REJECT_MESSAGE__"] = "";
454             Subst["__CC__"] = "X-Katie-Rejection: automatic (moo)";
455             os.write(fd, reject_message);
456             os.close(fd);
457         else:
458             # Build up the rejection email
459             user_email_address = utils.whoami() + " <%s>" % (Cnf["Dinstall::MyAdminAddress"]);
460
461             Subst["__REJECTOR_ADDRESS__"] = user_email_address;
462             Subst["__MANUAL_REJECT_MESSAGE__"] = reject_message;
463             Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"];
464             reject_mail_message = utils.TemplateSubst(Subst,utils.open_file(Cnf["Dir::TemplatesDir"]+"/katie.rejected").read());
465
466             # Write the rejection email out as the <foo>.reason file
467             os.write(fd, reject_mail_message);
468             os.close(fd);
469
470             # If we weren't given a manual rejection message, spawn an
471             # editor so the user can add one in...
472             if reject_message == "":
473                 editor = os.environ.get("EDITOR","vi")
474                 result = os.system("%s +6 %s" % (editor, reject_filename))
475                 if result != 0:
476                     utils.fubar("editor invocation failed for '%s'!" % (reject_filename), result);
477
478         # Send the rejection mail if appropriate
479         if not Cnf["Dinstall::Options::No-Mail"]:
480             reject_mail_message = utils.TemplateSubst(Subst,utils.open_file(Cnf["Dir::TemplatesDir"]+"/katie.rejected").read());
481             utils.send_mail (reject_mail_message, "");
482
483         self.Logger.log(["rejected", pkg.changes_file]);
484
485     ################################################################################
486
487     # Ensure that source exists somewhere in the archive for the binary
488     # upload being processed.
489     #
490     # (1) exact match                      => 1.0-3
491     # (2) Bin-only NMU of an MU            => 1.0-3.0.1
492     # (3) Bin-only NMU of a sourceful-NMU  => 1.0-3.1.1
493
494     def source_exists (self, package, source_version):
495         q = self.projectB.query("SELECT s.version FROM source s WHERE s.source = '%s'" % (package));
496
497         # Reduce the query results to a list of version numbers
498         ql = map(lambda x: x[0], q.getresult());
499
500         # Try (1)
501         if ql.count(source_version):
502             return 1;
503
504         # Try (2)
505         orig_source_version = re_bin_only_nmu_of_mu.sub('', source_version);
506         if ql.count(orig_source_version):
507             return 1;
508
509         # Try (3)
510         orig_source_version = re_bin_only_nmu_of_nmu.sub('', source_version);
511         if ql.count(orig_source_version):
512             return 1;
513
514         # No source found...
515         return 0;
516
517     ################################################################################
518
519     def in_override_p (self, package, component, suite, binary_type, file):
520         files = self.pkg.files;
521
522         if binary_type == "": # must be source
523             type = "dsc";
524         else:
525             type = binary_type;
526
527         # Override suite name; used for example with proposed-updates
528         if self.Cnf.Find("Suite::%s::OverrideSuite" % (suite)) != "":
529             suite = self.Cnf["Suite::%s::OverrideSuite" % (suite)];
530
531         # Avoid <undef> on unknown distributions
532         suite_id = db_access.get_suite_id(suite);
533         if suite_id == -1:
534             return None;
535         component_id = db_access.get_component_id(component);
536         type_id = db_access.get_override_type_id(type);
537
538         # FIXME: nasty non-US speficic hack
539         if lower(component[:7]) == "non-us/":
540             component = component[7:];
541
542         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"
543                            % (package, suite_id, component_id, type_id));
544         result = q.getresult();
545         # If checking for a source package fall back on the binary override type
546         if type == "dsc" and not result:
547             type_id = db_access.get_override_type_id("deb");
548             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"
549                                % (package, suite_id, component_id, type_id));
550             result = q.getresult();
551
552         # Remember the section and priority so we can check them later if appropriate
553         if result != []:
554             files[file]["override section"] = result[0][0];
555             files[file]["override priority"] = result[0][1];
556
557         return result;
558
559     ################################################################################
560
561     def reject (self, str, prefix="Rejected: "):
562         if str:
563             self.reject_message = self.reject_message + prefix + str + "\n";
564
565     def check_binaries_against_db(self, file, suite):
566         self.reject_message = "";
567         files = self.pkg.files;
568
569         # Find any old binary packages
570         q = self.projectB.query("SELECT b.id, b.version, f.filename, l.path, c.name  FROM binaries b, bin_associations ba, suite s, location l, component c, architecture a, files f WHERE b.package = '%s' AND s.suite_name = '%s' AND (a.arch_string = '%s' OR a.arch_string = 'all') AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id AND f.location = l.id AND l.component = c.id AND b.file = f.id"
571                            % (files[file]["package"], suite, files[file]["architecture"]))
572         for oldfile in q.dictresult():
573             files[file]["oldfiles"][suite] = oldfile;
574             # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
575             if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
576                 reject("%s Old version `%s' >= new version `%s'." % (file, oldfile["version"], files[file]["version"]));
577         # Check for any existing copies of the file
578         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"]))
579         if q.getresult() != []:
580             reject("can not overwrite existing copy of '%s' already in the archive." % (file));
581
582         return self.reject_message;
583
584     ################################################################################
585
586     def check_source_against_db(self, file):
587         """Ensure source is newer than existing source in target suites."""
588         self.reject_message = "";
589         changes = self.pkg.changes;
590         dsc = self.pkg.dsc;
591
592         package = dsc.get("source");
593         new_version = dsc.get("version");
594         for suite in changes["distribution"].keys():
595             q = self.projectB.query("SELECT s.version FROM source s, src_associations sa, suite su WHERE s.source = '%s' AND su.suite_name = '%s' AND sa.source = s.id AND sa.suite = su.id"
596                                % (package, suite));
597             ql = map(lambda x: x[0], q.getresult());
598             for old_version in ql:
599                 if apt_pkg.VersionCompare(new_version, old_version) != 1:
600                     reject("%s: Old version `%s' >= new version `%s'." % (file, old_version, new_version));
601         return self.reject_message;
602
603     ################################################################################
604
605     def check_dsc_against_db(self, file):
606         self.reject_message = "";
607         files = self.pkg.files;
608         dsc_files = self.pkg.dsc_files;
609         legacy_source_untouchable = self.pkg.legacy_source_untouchable;
610         orig_tar_gz = None;
611
612         # Try and find all files mentioned in the .dsc.  This has
613         # to work harder to cope with the multiple possible
614         # locations of an .orig.tar.gz.
615         for dsc_file in dsc_files.keys():
616             if files.has_key(dsc_file):
617                 actual_md5 = files[dsc_file]["md5sum"];
618                 actual_size = int(files[dsc_file]["size"]);
619                 found = "%s in incoming" % (dsc_file)
620                 # Check the file does not already exist in the archive
621                 q = self.projectB.query("SELECT f.id FROM files f, location l WHERE (f.filename ~ '/%s$' OR f.filename = '%s') AND l.id = f.location" % (utils.regex_safe(dsc_file), dsc_file));
622
623                 # "It has not broken them.  It has fixed a
624                 # brokenness.  Your crappy hack exploited a bug in
625                 # the old dinstall.
626                 #
627                 # "(Come on!  I thought it was always obvious that
628                 # one just doesn't release different files with
629                 # the same name and version.)"
630                 #                        -- ajk@ on d-devel@l.d.o
631
632                 if q.getresult() != []:
633                     reject("can not overwrite existing copy of '%s' already in the archive." % (dsc_file));
634             elif dsc_file[-12:] == ".orig.tar.gz":
635                 # Check in the pool
636                 q = self.projectB.query("SELECT l.path, f.filename, l.type, f.id, l.id FROM files f, location l WHERE (f.filename ~ '/%s$' OR f.filename = '%s') AND l.id = f.location" % (utils.regex_safe(dsc_file), dsc_file));
637                 ql = q.getresult();
638
639                 if ql != []:
640                     # Unfortunately, we make get more than one
641                     # match here if, for example, the package was
642                     # in potato but had a -sa upload in woody.  So
643                     # we need to choose the right one.
644
645                     x = ql[0]; # default to something sane in case we don't match any or have only one
646
647                     if len(ql) > 1:
648                         for i in ql:
649                             old_file = i[0] + i[1];
650                             actual_md5 = apt_pkg.md5sum(utils.open_file(old_file));
651                             actual_size = os.stat(old_file)[stat.ST_SIZE];
652                             if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
653                                 x = i;
654                             else:
655                                 legacy_source_untouchable[i[3]] = "";
656
657                     old_file = x[0] + x[1];
658                     actual_md5 = apt_pkg.md5sum(utils.open_file(old_file));
659                     actual_size = os.stat(old_file)[stat.ST_SIZE];
660                     found = old_file;
661                     suite_type = x[2];
662                     dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
663                     # See install() in katie...
664                     self.pkg.orig_tar_id = x[3];
665                     if suite_type == "legacy" or suite_type == "legacy-mixed":
666                         self.pkg.orig_tar_location = "legacy";
667                     else:
668                         self.pkg.orig_tar_location = x[4];
669                 else:
670
671                     # Not there? Check in Incoming...
672                     # [See comment above jennifer's process_it() for
673                     # explanation of why this is necessary...]
674                     orig_tar_gz = self.pkg.directory + '/' + dsc_file;
675                     if os.path.exists(orig_tar_gz):
676                         return (self.reject_message, orig_tar_gz);
677                     else:
678                         reject("%s refers to %s, but I can't find it in Incoming or in the pool." % (file, dsc_file));
679                         continue;
680             else:
681                 reject("%s refers to %s, but I can't find it in Incoming." % (file, dsc_file));
682                 continue;
683             if actual_md5 != dsc_files[dsc_file]["md5sum"]:
684                 reject("md5sum for %s doesn't match %s." % (found, file));
685             if actual_size != int(dsc_files[dsc_file]["size"]):
686                 reject("size for %s doesn't match %s." % (found, file));
687
688         return (self.reject_message, orig_tar_gz);