3 # Utility functions for katie
4 # Copyright (C) 2001 James Troup <james@nocrew.org>
5 # $Id: katie.py,v 1.12 2002-04-01 21:20:28 troup Exp $
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.
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.
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
21 ###############################################################################
23 import cPickle, errno, os, pg, re, stat, string, sys, time;
24 import utils, db_access;
25 import apt_inst, apt_pkg;
28 from string import lower;
30 ###############################################################################
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+$");
38 ###############################################################################
40 # Convenience wrapper to carry around all the package information in
43 def __init__(self, **kwds):
44 self.__dict__.update(kwds);
46 def update(self, **kwds):
47 self.__dict__.update(kwds);
49 ###############################################################################
52 # Read in the group maintainer override file
53 def __init__ (self, Cnf):
54 self.group_maint = {};
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)));
62 self.group_maint[line] = 1;
65 def is_an_nmu (self, pkg):
67 changes = pkg.changes;
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):
76 if dsc.has_key("uploaders"):
77 uploaders = string.split(lower(dsc["uploaders"]), ",");
80 (rfc822, name, email) = utils.fix_maintainer (string.strip(i));
81 uploadernames[name] = "";
82 if uploadernames.has_key(lower(changes["changedbyname"])):
85 # Some group maintained packages (e.g. Debian QA) are never NMU's
86 if self.group_maint.has_key(lower(changes["maintaineremail"])):
91 ###############################################################################
95 def __init__(self, Cnf):
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 = {});
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"];
112 self.projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]));
113 db_access.init(Cnf, self.projectB);
115 ###########################################################################
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 = "";
123 ###########################################################################
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);
135 ###########################################################################
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
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" ]:
150 for file in files.keys():
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];
159 # Mandatory changes fields
160 for i in [ "distribution", "source", "architecture", "version", "maintainer",
161 "urgency", "fingerprint", "changedby822", "changedbyname",
162 "maintainername", "maintaineremail", "closes" ]:
163 d_changes[i] = changes[i];
164 # Optional changes fields
165 # FIXME: changes should be mandatory
166 for i in [ "changed-by", "maintainer822", "filecontents", "format", "changes" ]:
167 if changes.has_key(i):
168 d_changes[i] = changes[i];
170 for i in [ "source", "version", "maintainer", "fingerprint", "uploaders" ]:
174 for file in dsc_files.keys():
175 d_dsc_files[file] = {};
176 # Mandatory dsc_files fields
177 for i in [ "size", "md5sum" ]:
178 d_dsc_files[file][i] = dsc_files[file][i];
179 # Optional dsc_files fields
180 for i in [ "files id" ]:
181 if dsc_files[file].has_key(i):
182 d_dsc_files[file][i] = dsc_files[file][i];
184 for i in [ d_changes, d_dsc, d_files, d_dsc_files,
185 legacy_source_untouchable, orig_tar_id, orig_tar_location ]:
189 ###########################################################################
191 # Set up the per-package template substitution mappings
193 def update_subst (self, reject_message = ""):
195 changes = self.pkg.changes;
196 # If jennifer crashed out in the right place, architecture may still be a string.
197 if not changes.has_key("architecture") or not isinstance(changes["architecture"], DictType):
198 changes["architecture"] = { "Unknown" : "" };
199 # and maintainer822 may not exist.
200 if not changes.has_key("maintainer822"):
201 changes["maintainer822"] = self.Cnf["Dinstall::MyEmailAddress"];
203 Subst["__ARCHITECTURE__"] = string.join(changes["architecture"].keys(), ' ' );
204 Subst["__CHANGES_FILENAME__"] = os.path.basename(self.pkg.changes_file);
205 Subst["__FILE_CONTENTS__"] = changes.get("filecontents", "");
207 # For source uploads the Changed-By field wins; otherwise Maintainer wins.
208 if changes["architecture"].has_key("source") and changes["changedby822"] != "" and (changes["changedby822"] != changes["maintainer822"]):
209 Subst["__MAINTAINER_FROM__"] = changes["changedby822"];
210 Subst["__MAINTAINER_TO__"] = changes["changedby822"] + ", " + changes["maintainer822"];
211 Subst["__MAINTAINER__"] = changes.get("changed-by", "Unknown");
213 Subst["__MAINTAINER_FROM__"] = changes["maintainer822"];
214 Subst["__MAINTAINER_TO__"] = changes["maintainer822"];
215 Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown");
216 if self.Cnf.has_key("Dinstall::TrackingServer") and changes.has_key("source"):
217 Subst["__MAINTAINER_TO__"] = Subst["__MAINTAINER_TO__"] + "\nBcc: %s@%s" % (changes["source"], self.Cnf["Dinstall::TrackingServer"])
219 Subst["__REJECT_MESSAGE__"] = reject_message;
220 Subst["__SOURCE__"] = changes.get("source", "Unknown");
221 Subst["__VERSION__"] = changes.get("version", "Unknown");
223 ###########################################################################
225 def build_summaries(self):
226 changes = self.pkg.changes;
227 files = self.pkg.files;
229 byhand = summary = new = "";
231 # changes["distribution"] may not exist in corner cases
232 # (e.g. unreadable changes files)
233 if not changes.has_key("distribution") or not isinstance(changes["distribution"], DictType):
234 changes["distribution"] = {};
236 file_keys = files.keys();
238 for file in file_keys:
239 if files[file].has_key("byhand"):
241 summary = summary + file + " byhand\n"
242 elif files[file].has_key("new"):
244 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
245 if files[file].has_key("othercomponents"):
246 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
247 if files[file]["type"] == "deb":
248 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file)))["Description"] + '\n';
250 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
251 destination = self.Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
252 summary = summary + file + "\n to " + destination + "\n"
254 short_summary = summary;
256 # This is for direport's benefit...
257 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
260 summary = summary + "Changes: " + f;
262 summary = summary + self.announce(short_summary, 0)
264 return (summary, short_summary);
266 ###########################################################################
268 def announce (self, short_summary, action):
271 changes = self.pkg.changes;
274 # Only do announcements for source uploads with a recent dpkg-dev installed
275 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
280 Subst["__SHORT_SUMMARY__"] = short_summary;
282 for dist in changes["distribution"].keys():
283 list = Cnf.Find("Suite::%s::Announce" % (dist))
284 if list == "" or lists_done.has_key(list):
287 summary = summary + "Announcing to %s\n" % (list)
290 Subst["__ANNOUNCE_LIST_ADDRESS__"] = list;
291 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/jennifer.announce","r").read());
292 utils.send_mail (mail_message, "")
294 bugs = changes["closes"].keys()
296 if not self.nmu.is_an_nmu(self.pkg):
297 summary = summary + "Closing bugs: "
299 summary = summary + "%s " % (bug)
301 Subst["__BUG_NUMBER__"] = bug;
302 if changes["distribution"].has_key("stable"):
303 Subst["__STABLE_WARNING__"] = """
304 Note that this package is not part of the released stable Debian
305 distribution. It may have dependencies on other unreleased software,
306 or other instabilities. Please take care if you wish to install it.
307 The update will eventually make its way into the next released Debian
310 Subst["__STABLE_WARNING__"] = "";
311 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/jennifer.bug-close","r").read());
312 utils.send_mail (mail_message, "")
314 self.Logger.log(["closing bugs"]+bugs);
316 summary = summary + "Setting bugs to severity fixed: "
319 summary = summary + "%s " % (bug)
320 control_message = control_message + "tag %s + fixed\n" % (bug)
321 if action and control_message != "":
322 Subst["__CONTROL_MESSAGE__"] = control_message;
323 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/jennifer.bug-nmu-fixed","r").read());
324 utils.send_mail (mail_message, "")
326 self.Logger.log(["setting bugs to fixed"]+bugs);
327 summary = summary + "\n"
331 ###########################################################################
333 def accept (self, summary, short_summary):
336 files = self.pkg.files;
339 self.Logger.log(["Accepting changes",self.pkg.changes_file]);
341 self.dump_vars(Cnf["Dir::QueueAcceptedDir"]);
343 # Move all the files into the accepted directory
344 utils.move(self.pkg.changes_file, Cnf["Dir::QueueAcceptedDir"]);
345 file_keys = files.keys();
346 for file in file_keys:
347 utils.move(file, Cnf["Dir::QueueAcceptedDir"]);
348 self.accept_bytes = self.accept_bytes + float(files[file]["size"])
349 self.accept_count = self.accept_count + 1;
351 # Send accept mail, announce to lists, close bugs and check for
352 # override disparities
353 if not Cnf["Dinstall::Options::No-Mail"]:
354 Subst["__SUITE__"] = "";
355 Subst["__SUMMARY__"] = summary;
356 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/jennifer.accepted","r").read());
357 utils.send_mail(mail_message, "")
358 self.announce(short_summary, 1)
360 ###########################################################################
362 def check_override (self):
364 changes = self.pkg.changes;
365 files = self.pkg.files;
367 # Only check section & priority on sourceful uploads
368 if not changes["architecture"].has_key("source"):
372 for file in files.keys():
373 if not files[file].has_key("new") and files[file]["type"] == "deb":
374 section = files[file]["section"];
375 override_section = files[file]["override section"];
376 if lower(section) != lower(override_section) and section != "-":
377 # Ignore this; it's a common mistake and not worth whining about
378 if lower(section) == "non-us/main" and lower(override_section) == "non-us":
380 summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
381 priority = files[file]["priority"];
382 override_priority = files[file]["override priority"];
383 if priority != override_priority and priority != "-":
384 summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
389 Subst["__SUMMARY__"] = summary;
390 mail_message = utils.TemplateSubst(Subst,utils.open_file(self.Cnf["Dir::TemplatesDir"]+"/jennifer.override-disparity").read());
391 utils.send_mail (mail_message, "");
393 ###########################################################################
395 def force_move (self, files):
396 """Forcefully move files from the current directory to the reject
397 directory. If any file already exists it will be moved to the
398 morgue to make way for the new file."""
403 # Skip any files which don't exist or which we don't have permission to copy.
404 if os.access(file,os.R_OK) == 0:
406 dest_file = os.path.join(Cnf["Dir::QueueRejectDir"], file);
408 os.open(dest_file, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
410 # File exists? Let's try and move it to the morgue
411 if errno.errorcode[e.errno] == 'EEXIST':
412 morgue_file = os.path.join(Cnf["Dir::Morgue"],Cnf["Dir::MorgueRejectDir"],file);
414 morgue_file = utils.find_next_free(morgue_file);
415 except utils.tried_too_hard_exc:
416 # Something's either gone badly Pete Tong, or
417 # someone is trying to exploit us.
418 utils.warn("**WARNING** failed to move %s from the reject directory to the morgue." % (file));
420 utils.move(dest_file, morgue_file, perms=0660);
422 os.open(dest_file, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
425 utils.warn("**WARNING** failed to claim %s in the reject directory." % (file));
429 # If we got here, we own the destination file, so we can
430 # safely overwrite it.
431 utils.move(file, dest_file, 1, perms=0660);
434 ###########################################################################
436 def do_reject (self, manual = 0, reject_message = ""):
443 reason_filename = pkg.changes_file[:-8] + ".reason";
444 reject_filename = Cnf["Dir::QueueRejectDir"] + '/' + reason_filename;
446 # Move all the files into the reject directory
447 reject_files = pkg.files.keys() + [pkg.changes_file];
448 self.force_move(reject_files);
450 # If we fail here someone is probably trying to exploit the race
451 # so let's just raise an exception ...
452 if os.path.exists(reject_filename):
453 os.unlink(reject_filename);
454 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
457 Subst["__REJECTOR_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"];
458 Subst["__MANUAL_REJECT_MESSAGE__"] = "";
459 Subst["__CC__"] = "X-Katie-Rejection: automatic (moo)";
460 os.write(fd, reject_message);
462 reject_mail_message = utils.TemplateSubst(Subst,utils.open_file(Cnf["Dir::TemplatesDir"]+"/katie.rejected").read());
464 # Build up the rejection email
465 user_email_address = utils.whoami() + " <%s>" % (Cnf["Dinstall::MyAdminAddress"]);
467 Subst["__REJECTOR_ADDRESS__"] = user_email_address;
468 Subst["__MANUAL_REJECT_MESSAGE__"] = reject_message;
469 Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"];
470 reject_mail_message = utils.TemplateSubst(Subst,utils.open_file(Cnf["Dir::TemplatesDir"]+"/katie.rejected").read());
472 # Write the rejection email out as the <foo>.reason file
473 os.write(fd, reject_mail_message);
476 # If we weren't given a manual rejection message, spawn an
477 # editor so the user can add one in...
478 if reject_message == "":
479 editor = os.environ.get("EDITOR","vi")
480 result = os.system("%s +6 %s" % (editor, reject_filename))
482 utils.fubar("editor invocation failed for '%s'!" % (reject_filename), result);
483 reject_mail_message = utils.open_file(reject_filename).read();
485 # Send the rejection mail if appropriate
486 if not Cnf["Dinstall::Options::No-Mail"]:
487 utils.send_mail (reject_mail_message, "");
489 self.Logger.log(["rejected", pkg.changes_file]);
491 ################################################################################
493 # Ensure that source exists somewhere in the archive for the binary
494 # upload being processed.
496 # (1) exact match => 1.0-3
497 # (2) Bin-only NMU of an MU => 1.0-3.0.1
498 # (3) Bin-only NMU of a sourceful-NMU => 1.0-3.1.1
500 def source_exists (self, package, source_version):
501 q = self.projectB.query("SELECT s.version FROM source s WHERE s.source = '%s'" % (package));
503 # Reduce the query results to a list of version numbers
504 ql = map(lambda x: x[0], q.getresult());
507 if ql.count(source_version):
511 orig_source_version = re_bin_only_nmu_of_mu.sub('', source_version);
512 if ql.count(orig_source_version):
516 orig_source_version = re_bin_only_nmu_of_nmu.sub('', source_version);
517 if ql.count(orig_source_version):
523 ################################################################################
525 def in_override_p (self, package, component, suite, binary_type, file):
526 files = self.pkg.files;
528 if binary_type == "": # must be source
533 # Override suite name; used for example with proposed-updates
534 if self.Cnf.Find("Suite::%s::OverrideSuite" % (suite)) != "":
535 suite = self.Cnf["Suite::%s::OverrideSuite" % (suite)];
537 # Avoid <undef> on unknown distributions
538 suite_id = db_access.get_suite_id(suite);
541 component_id = db_access.get_component_id(component);
542 type_id = db_access.get_override_type_id(type);
544 # FIXME: nasty non-US speficic hack
545 if lower(component[:7]) == "non-us/":
546 component = component[7:];
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 # If checking for a source package fall back on the binary override type
552 if type == "dsc" and not result:
553 type_id = db_access.get_override_type_id("deb");
554 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"
555 % (package, suite_id, component_id, type_id));
556 result = q.getresult();
558 # Remember the section and priority so we can check them later if appropriate
560 files[file]["override section"] = result[0][0];
561 files[file]["override priority"] = result[0][1];
565 ################################################################################
567 def reject (self, str, prefix="Rejected: "):
569 # Unlike other rejects we add new lines first to avoid trailing
570 # new lines when this message is passed back up to a caller.
571 if self.reject_message:
572 self.reject_message = self.reject_message + "\n";
573 self.reject_message = self.reject_message + prefix + str;
575 def check_binaries_against_db(self, file, suite):
576 self.reject_message = "";
577 files = self.pkg.files;
579 # Find any old binary packages
580 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"
581 % (files[file]["package"], suite, files[file]["architecture"]))
582 for oldfile in q.dictresult():
583 files[file]["oldfiles"][suite] = oldfile;
584 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
585 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
586 self.reject("%s: old version (%s) >= new version (%s)." % (file, oldfile["version"], files[file]["version"]));
587 # Check for any existing copies of the file
588 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"]))
589 if q.getresult() != []:
590 self.reject("can not overwrite existing copy of '%s' already in the archive." % (file));
592 return self.reject_message;
594 ################################################################################
596 def check_source_against_db(self, file):
597 """Ensure source is newer than existing source in target suites."""
598 self.reject_message = "";
599 changes = self.pkg.changes;
602 package = dsc.get("source");
603 new_version = dsc.get("version");
604 for suite in changes["distribution"].keys():
605 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"
607 ql = map(lambda x: x[0], q.getresult());
608 for old_version in ql:
609 if apt_pkg.VersionCompare(new_version, old_version) != 1:
610 self.reject("%s: Old version `%s' >= new version `%s'." % (file, old_version, new_version));
611 return self.reject_message;
613 ################################################################################
615 def check_dsc_against_db(self, file):
616 self.reject_message = "";
617 files = self.pkg.files;
618 dsc_files = self.pkg.dsc_files;
619 legacy_source_untouchable = self.pkg.legacy_source_untouchable;
622 # Try and find all files mentioned in the .dsc. This has
623 # to work harder to cope with the multiple possible
624 # locations of an .orig.tar.gz.
625 for dsc_file in dsc_files.keys():
627 if files.has_key(dsc_file):
628 actual_md5 = files[dsc_file]["md5sum"];
629 actual_size = int(files[dsc_file]["size"]);
630 found = "%s in incoming" % (dsc_file)
631 # Check the file does not already exist in the archive
632 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));
634 # "It has not broken them. It has fixed a
635 # brokenness. Your crappy hack exploited a bug in
638 # "(Come on! I thought it was always obvious that
639 # one just doesn't release different files with
640 # the same name and version.)"
641 # -- ajk@ on d-devel@l.d.o
643 if q.getresult() != []:
644 self.reject("can not overwrite existing copy of '%s' already in the archive." % (dsc_file));
645 elif dsc_file[-12:] == ".orig.tar.gz":
647 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));
651 # Unfortunately, we make get more than one
652 # match here if, for example, the package was
653 # in potato but had a -sa upload in woody. So
654 # we need to choose the right one.
656 x = ql[0]; # default to something sane in case we don't match any or have only one
660 old_file = i[0] + i[1];
661 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file));
662 actual_size = os.stat(old_file)[stat.ST_SIZE];
663 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
666 legacy_source_untouchable[i[3]] = "";
668 old_file = x[0] + x[1];
669 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file));
670 actual_size = os.stat(old_file)[stat.ST_SIZE];
673 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
674 # See install() in katie...
675 self.pkg.orig_tar_id = x[3];
676 if suite_type == "legacy" or suite_type == "legacy-mixed":
677 self.pkg.orig_tar_location = "legacy";
679 self.pkg.orig_tar_location = x[4];
681 # Not there? Check the queue directories...
683 in_unchecked = os.path.join(self.Cnf["Dir::QueueUncheckedDir"],dsc_file);
684 # See process_it() in jennifer for explanation of this
685 if os.path.exists(in_unchecked):
686 return (self.reject_message, in_unchecked);
688 for dir in [ "Accepted", "New", "Byhand" ]:
689 in_otherdir = os.path.join(self.Cnf["Dir::Queue%sDir" % (dir)],dsc_file);
690 if os.path.exists(in_otherdir):
691 actual_md5 = apt_pkg.md5sum(utils.open_file(in_otherdir));
692 actual_size = os.stat(in_otherdir)[stat.ST_SIZE];
696 self.reject("%s refers to %s, but I can't find it in the queue or in the pool." % (file, dsc_file));
699 self.reject("%s refers to %s, but I can't find it in the queue." % (file, dsc_file));
701 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
702 self.reject("md5sum for %s doesn't match %s." % (found, file));
703 if actual_size != int(dsc_files[dsc_file]["size"]):
704 self.reject("size for %s doesn't match %s." % (found, file));
706 return (self.reject_message, orig_tar_gz);
708 def do_query(self, q):
709 sys.stderr.write("query: \"%s\" ... " % (q));
710 before = time.time();
711 r = self.projectB.query(q);
712 time_diff = time.time()-before;
713 sys.stderr.write("took %.3f seconds.\n" % (time_diff));