3 # Installs Debian packaes
4 # Copyright (C) 2000, 2001 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.50 2001-06-24 23:17:43 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 # Originally based almost entirely on dinstall by Guy Maor <maor@debian.org>
23 #########################################################################################
25 # Cartman: "I'm trying to make the best of a bad situation, I don't
26 # need to hear crap from a bunch of hippy freaks living in
27 # denial. Screw you guys, I'm going home."
29 # Kyle: "But Cartman, we're trying to..."
31 # Cartman: "uhh.. screw you guys... home."
33 #########################################################################################
35 import FCNTL, commands, fcntl, getopt, gzip, os, pg, pwd, re, shutil, stat, string, sys, tempfile, time, traceback
36 import apt_inst, apt_pkg
37 import utils, db_access
39 ###############################################################################
41 re_isanum = re.compile (r"^\d+$");
42 re_changes = re.compile (r"changes$");
43 re_default_answer = re.compile(r"\[(.*)\]");
44 re_fdnic = re.compile("\n\n");
45 re_bad_diff = re.compile("^[\-\+][\-\+][\-\+] /dev/null");
46 re_bin_only_nmu_of_mu = re.compile("\.\d+\.\d+$");
47 re_bin_only_nmu_of_nmu = re.compile("\.\d+$");
49 #########################################################################################
65 orig_tar_location = "";
66 legacy_source_untouchable = {};
70 #########################################################################################
72 def usage (exit_code):
73 print """Usage: dinstall [OPTION]... [CHANGES]...
74 -a, --automatic automatic run
75 -D, --debug=VALUE turn on debugging
76 -h, --help show this help and exit.
77 -k, --ack-new acknowledge new packages !! for cron.daily only !!
78 -m, --manual-reject=MSG manual reject with `msg'
79 -n, --no-action don't do anything
80 -p, --no-lock don't check lockfile !! for cron.daily only !!
81 -u, --distribution=DIST override distribution to `dist'
82 -v, --version display the version number and exit"""
85 #########################################################################################
87 def check_signature (filename):
90 (result, output) = commands.getstatusoutput("gpg --emulate-md-encode-bug --batch --no-options --no-default-keyring --always-trust --keyring=%s --keyring=%s < %s >/dev/null" % (Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"], filename))
92 reject_message = "Rejected: GPG signature check failed on `%s'.\n%s\n" % (os.path.basename(filename), output)
96 ######################################################################################################
99 # Read in the group maintainer override file
101 self.group_maint = {};
102 if Cnf.get("Dinstall::GroupOverrideFilename"):
103 filename = Cnf["Dir::OverrideDir"] + Cnf["Dinstall::GroupOverrideFilename"];
104 file = utils.open_file(filename, 'r');
105 for line in file.readlines():
106 line = string.strip(utils.re_comments.sub('', line));
108 self.group_maint[line] = 1;
111 def is_an_nmu (self, changes, dsc):
112 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
113 # changes["changedbyname"] == dsc_name is probably never true, but better safe than sorry
114 if dsc_name == changes["maintainername"] and (changes["changedbyname"] == "" or changes["changedbyname"] == dsc_name):
117 if dsc.has_key("uploaders"):
118 uploaders = string.split(dsc["uploaders"], ",");
121 (rfc822, name, email) = utils.fix_maintainer (string.strip(i));
122 uploadernames[name] = "";
123 if uploadernames.has_key(changes["changedbyname"]):
126 # Some group maintained packages (e.g. Debian QA) are never NMU's
127 if self.group_maint.has_key(changes["maintainername"]):
132 ######################################################################################################
134 # Ensure that source exists somewhere in the archive for the binary
135 # upload being processed.
137 # (1) exact match => 1.0-3
138 # (2) Bin-only NMU of an MU => 1.0-3.0.1
139 # (3) Bin-only NMU of a sourceful-NMU => 1.0-3.1.1
141 def source_exists (package, source_version):
142 q = projectB.query("SELECT s.version FROM source s WHERE s.source = '%s'" % (package));
144 # Reduce the query results to a list of version numbers
145 ql = map(lambda x: x[0], q.getresult());
148 if ql.count(source_version):
152 orig_source_version = re_bin_only_nmu_of_mu.sub('', source_version);
153 if ql.count(orig_source_version):
157 orig_source_version = re_bin_only_nmu_of_nmu.sub('', source_version);
158 if ql.count(orig_source_version):
164 ######################################################################################################
166 # See if a given package is in the override table
168 def in_override_p (package, component, suite, binary_type, file):
171 if binary_type == "": # must be source
176 # Override suite name; used for example with proposed-updates
177 if Cnf.Find("Suite::%s::OverrideSuite" % (suite)) != "":
178 suite = Cnf["Suite::%s::OverrideSuite" % (suite)];
180 # Avoid <undef> on unknown distributions
181 suite_id = db_access.get_suite_id(suite);
184 component_id = db_access.get_component_id(component);
185 type_id = db_access.get_override_type_id(type);
187 # FIXME: nasty non-US speficic hack
188 if string.lower(component[:7]) == "non-us/":
189 component = component[7:];
191 q = 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"
192 % (package, suite_id, component_id, type_id));
193 result = q.getresult();
194 # If checking for a source package fall back on the binary override type
195 if type == "dsc" and not result:
196 type_id = db_access.get_override_type_id("deb");
197 q = 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"
198 % (package, suite_id, component_id, type_id));
199 result = q.getresult();
201 # Remember the section and priority so we can check them later if appropriate
203 files[file]["override section"] = result[0][0];
204 files[file]["override priority"] = result[0][1];
208 #####################################################################################################################
210 def check_changes(filename):
211 global reject_message, changes, files
213 # Default in case we bail out
214 changes["maintainer822"] = Cnf["Dinstall::MyEmailAddress"];
215 changes["changedby822"] = Cnf["Dinstall::MyEmailAddress"];
216 changes["architecture"] = {};
218 # Parse the .changes field into a dictionary
220 changes = utils.parse_changes(filename, 0)
221 except utils.cant_open_exc:
222 reject_message = "Rejected: can't read changes file '%s'.\n" % (filename)
224 except utils.changes_parse_error_exc, line:
225 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
228 # Parse the Files field from the .changes into another dictionary
230 files = utils.build_file_list(changes, "");
231 except utils.changes_parse_error_exc, line:
232 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line);
234 # Check for mandatory fields
235 for i in ("source", "binary", "architecture", "version", "distribution","maintainer", "files"):
236 if not changes.has_key(i):
237 reject_message = "Rejected: Missing field `%s' in changes file.\n" % (i)
238 return 0 # Avoid <undef> errors during later tests
240 # Override the Distribution: field if appropriate
241 if Cnf["Dinstall::Options::Override-Distribution"] != "":
242 reject_message = reject_message + "Warning: Distribution was overriden from %s to %s.\n" % (changes["distribution"], Cnf["Dinstall::Options::Override-Distribution"])
243 changes["distribution"] = Cnf["Dinstall::Options::Override-Distribution"]
245 # Split multi-value fields into a lower-level dictionary
246 for i in ("architecture", "distribution", "binary", "closes"):
247 o = changes.get(i, "")
251 for j in string.split(o):
254 # Fix the Maintainer: field to be RFC822 compatible
255 (changes["maintainer822"], changes["maintainername"], changes["maintaineremail"]) = utils.fix_maintainer (changes["maintainer"])
257 # Fix the Changed-By: field to be RFC822 compatible; if it exists.
258 (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]) = utils.fix_maintainer(changes.get("changed-by",""));
260 # Ensure all the values in Closes: are numbers
261 if changes.has_key("closes"):
262 for i in changes["closes"].keys():
263 if re_isanum.match (i) == None:
264 reject_message = reject_message + "Rejected: `%s' from Closes field isn't a number.\n" % (i)
266 # Map frozen to unstable if frozen doesn't exist
267 if changes["distribution"].has_key("frozen") and not Cnf.has_key("Suite::Frozen"):
268 del changes["distribution"]["frozen"]
269 changes["distribution"]["unstable"] = 1;
270 reject_message = reject_message + "Mapping frozen to unstable.\n"
272 # Map testing to unstable
273 if changes["distribution"].has_key("testing"):
274 del changes["distribution"]["testing"]
275 changes["distribution"]["unstable"] = 1;
276 reject_message = reject_message + "Mapping testing to unstable.\n"
278 # Ensure target distributions exist
279 for i in changes["distribution"].keys():
280 if not Cnf.has_key("Suite::%s" % (i)):
281 reject_message = reject_message + "Rejected: Unknown distribution `%s'.\n" % (i)
283 # Ensure there _is_ a target distribution
284 if changes["distribution"].keys() == []:
285 reject_message = reject_message + "Rejected: huh? Distribution field is empty in changes file.\n";
287 # Map unreleased arches from stable to unstable
288 if changes["distribution"].has_key("stable"):
289 for i in changes["architecture"].keys():
290 if not Cnf.has_key("Suite::Stable::Architectures::%s" % (i)):
291 reject_message = reject_message + "Mapping stable to unstable for unreleased arch `%s'.\n" % (i)
292 del changes["distribution"]["stable"]
293 changes["distribution"]["unstable"] = 1;
295 # Map arches not being released from frozen to unstable
296 if changes["distribution"].has_key("frozen"):
297 for i in changes["architecture"].keys():
298 if not Cnf.has_key("Suite::Frozen::Architectures::%s" % (i)):
299 reject_message = reject_message + "Mapping frozen to unstable for non-releasing arch `%s'.\n" % (i)
300 del changes["distribution"]["frozen"]
301 changes["distribution"]["unstable"] = 1;
303 # Handle uploads to stable
304 if changes["distribution"].has_key("stable"):
305 # If running from within proposed-updates; assume an install to stable
306 if string.find(os.getcwd(), 'proposed-updates') != -1:
307 # FIXME: should probably remove anything that != stable
308 for i in ("frozen", "unstable"):
309 if changes["distribution"].has_key(i):
310 reject_message = reject_message + "Removing %s from distribution list.\n" % (i)
311 del changes["distribution"][i]
312 changes["stable upload"] = 1;
313 # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool
314 file = files.keys()[0];
315 if os.access(file, os.R_OK) == 0:
316 pool_dir = Cnf["Dir::PoolDir"] + '/' + utils.poolify(changes["source"], files[file]["component"]);
318 # Otherwise (normal case) map stable to updates
320 reject_message = reject_message + "Mapping stable to updates.\n";
321 del changes["distribution"]["stable"];
322 changes["distribution"]["proposed-updates"] = 1;
324 # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
325 changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
326 changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
328 if string.find(reject_message, "Rejected:") != -1:
334 global reject_message
336 archive = utils.where_am_i();
338 for file in files.keys():
339 # Check the file is readable
340 if os.access(file,os.R_OK) == 0:
341 if os.path.exists(file):
342 reject_message = reject_message + "Rejected: Can't read `%s'. [permission denied]\n" % (file)
344 reject_message = reject_message + "Rejected: Can't read `%s'. [file not found]\n" % (file)
346 files[file]["type"] = "unreadable";
348 # If it's byhand skip remaining checks
349 if files[file]["section"] == "byhand":
350 files[file]["byhand"] = 1;
351 files[file]["type"] = "byhand";
352 # Checks for a binary package...
353 elif utils.re_isadeb.match(file) != None:
354 files[file]["type"] = "deb";
356 # Extract package information using dpkg-deb
358 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
360 reject_message = reject_message + "Rejected: %s: debExtractControl() raised %s.\n" % (file, sys.exc_type);
361 # Can't continue, none of the checks on control would work.
364 # Check for mandatory fields
365 if control.Find("Package") == None:
366 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
367 if control.Find("Architecture") == None:
368 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
369 if control.Find("Version") == None:
370 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
372 # Ensure the package name matches the one give in the .changes
373 if not changes["binary"].has_key(control.Find("Package", "")):
374 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
376 # Validate the architecture
377 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
378 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
380 # Check the architecture matches the one given in the .changes
381 if not changes["architecture"].has_key(control.Find("Architecture", "")):
382 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
383 # Check the section & priority match those given in the .changes (non-fatal)
384 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
385 reject_message = reject_message + "Warning: %s control file lists section as `%s', but changes file has `%s'.\n" % (file, control.Find("Section", ""), files[file]["section"])
386 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
387 reject_message = reject_message + "Warning: %s control file lists priority as `%s', but changes file has `%s'.\n" % (file, control.Find("Priority", ""), files[file]["priority"])
389 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
391 files[file]["package"] = control.Find("Package");
392 files[file]["architecture"] = control.Find("Architecture");
393 files[file]["version"] = control.Find("Version");
394 files[file]["maintainer"] = control.Find("Maintainer", "");
395 if file[-5:] == ".udeb":
396 files[file]["dbtype"] = "udeb";
397 elif file[-4:] == ".deb":
398 files[file]["dbtype"] = "deb";
400 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
401 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
402 files[file]["source"] = control.Find("Source", "");
403 if files[file]["source"] == "":
404 files[file]["source"] = files[file]["package"];
405 # Get the source version
406 source = files[file]["source"];
408 if string.find(source, "(") != -1:
409 m = utils.re_extract_src_version.match(source)
411 source_version = m.group(2)
412 if not source_version:
413 source_version = files[file]["version"];
414 files[file]["source package"] = source;
415 files[file]["source version"] = source_version;
417 # Checks for a source package...
419 m = utils.re_issource.match(file)
421 files[file]["package"] = m.group(1)
422 files[file]["version"] = m.group(2)
423 files[file]["type"] = m.group(3)
425 # Ensure the source package name matches the Source filed in the .changes
426 if changes["source"] != files[file]["package"]:
427 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
429 # Ensure the source version matches the version in the .changes file
430 if files[file]["type"] == "orig.tar.gz":
431 changes_version = changes["chopversion2"]
433 changes_version = changes["chopversion"]
434 if changes_version != files[file]["version"]:
435 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
437 # Ensure the .changes lists source in the Architecture field
438 if not changes["architecture"].has_key("source"):
439 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
441 # Check the signature of a .dsc file
442 if files[file]["type"] == "dsc":
443 check_signature(file)
445 files[file]["fullname"] = file
447 # Not a binary or source package? Assume byhand...
449 files[file]["byhand"] = 1;
450 files[file]["type"] = "byhand";
452 files[file]["oldfiles"] = {}
453 for suite in changes["distribution"].keys():
455 if files[file].has_key("byhand"):
458 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
459 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
462 # See if the package is NEW
463 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
464 files[file]["new"] = 1
466 if files[file]["type"] == "deb":
467 # Find any old binary packages
468 q = 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' 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"
469 % (files[file]["package"], suite, files[file]["architecture"]))
470 oldfiles = q.dictresult()
471 for oldfile in oldfiles:
472 files[file]["oldfiles"][suite] = oldfile
473 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
474 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
475 reject_message = reject_message + "Rejected: %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
476 # Check for existing copies of the file
477 if not changes.has_key("stable upload"):
478 q = 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"]))
479 if q.getresult() != []:
480 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
482 # Check for existent source
483 # FIXME: this is no longer per suite
484 if changes["architecture"].has_key("source"):
485 source_version = files[file]["source version"];
486 if source_version != changes["version"]:
487 reject_message = reject_message + "Rejected: source version (%s) for %s doesn't match changes version %s.\n" % (files[file]["source version"], file, changes["version"]);
489 if not source_exists (files[file]["source package"], source_version):
490 reject_message = reject_message + "Rejected: no source found for %s %s (%s).\n" % (files[file]["source package"], source_version, file);
492 # Find any old .dsc files
493 elif files[file]["type"] == "dsc":
494 q = projectB.query("SELECT s.id, s.version, f.filename, l.path, c.name FROM source s, src_associations sa, suite su, location l, component c, files f WHERE s.source = '%s' AND su.suite_name = '%s' AND sa.source = s.id AND sa.suite = su.id AND f.location = l.id AND l.component = c.id AND f.id = s.file"
495 % (files[file]["package"], suite))
496 oldfiles = q.dictresult()
497 if len(oldfiles) >= 1:
498 files[file]["oldfiles"][suite] = oldfiles[0]
500 # Validate the component
501 component = files[file]["component"];
502 component_id = db_access.get_component_id(component);
503 if component_id == -1:
504 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
507 # Validate the priority
508 if string.find(files[file]["priority"],'/') != -1:
509 reject_message = reject_message + "Rejected: file '%s' has invalid priority '%s' [contains '/'].\n" % (file, files[file]["priority"]);
511 # Check the md5sum & size against existing files (if any)
512 location = Cnf["Dir::PoolDir"];
513 files[file]["location id"] = db_access.get_location_id (location, component, archive);
515 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
516 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
518 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
520 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
521 files[file]["files id"] = files_id
523 # Check for packages that have moved from one component to another
524 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
525 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
528 if string.find(reject_message, "Rejected:") != -1:
533 ###############################################################################
536 global dsc, dsc_files, reject_message, reprocess, orig_tar_id, orig_tar_location, legacy_source_untouchable;
538 for file in files.keys():
539 if files[file]["type"] == "dsc":
541 dsc = utils.parse_changes(file, 1)
542 except utils.cant_open_exc:
543 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (file)
545 except utils.changes_parse_error_exc, line:
546 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (file, line)
548 except utils.invalid_dsc_format_exc, line:
549 reject_message = reject_message + "Rejected: syntax error in .dsc file '%s', line %s.\n" % (file, line)
552 dsc_files = utils.build_file_list(dsc, 1)
553 except utils.no_files_exc:
554 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
556 except utils.changes_parse_error_exc, line:
557 reject_message = "Rejected: error parsing .dsc file '%s', can't grok: %s.\n" % (file, line);
560 # Try and find all files mentioned in the .dsc. This has
561 # to work harder to cope with the multiple possible
562 # locations of an .orig.tar.gz.
563 for dsc_file in dsc_files.keys():
564 if files.has_key(dsc_file):
565 actual_md5 = files[dsc_file]["md5sum"];
566 actual_size = int(files[dsc_file]["size"]);
567 found = "%s in incoming" % (dsc_file)
568 # Check the file does not already exist in the archive
569 if not changes.has_key("stable upload"):
570 q = 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));
572 # "It has not broken them. It has fixed a
573 # brokenness. Your crappy hack exploited a
574 # bug in the old dinstall.
576 # "(Come on! I thought it was always obvious
577 # that one just doesn't release different
578 # files with the same name and version.)"
579 # -- ajk@ on d-devel@l.d.o
581 if q.getresult() != []:
582 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
583 elif dsc_file[-12:] == ".orig.tar.gz":
585 q = 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));
589 # Unfortunately, we make get more than one match
590 # here if, for example, the package was in potato
591 # but had a -sa upload in woody. So we need to a)
592 # choose the right one and b) mark all wrong ones
593 # as excluded from the source poolification (to
594 # avoid file overwrites).
596 x = ql[0]; # default to something sane in case we don't match any or have only one
600 old_file = i[0] + i[1];
601 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
602 actual_size = os.stat(old_file)[stat.ST_SIZE];
603 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
606 legacy_source_untouchable[i[3]] = "";
608 old_file = x[0] + x[1];
609 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
610 actual_size = os.stat(old_file)[stat.ST_SIZE];
613 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
616 if suite_type == "legacy" or suite_type == "legacy-mixed":
617 orig_tar_location = "legacy";
619 orig_tar_location = x[4];
621 # Not there? Check in Incoming...
622 # [See comment above process_it() for explanation
623 # of why this is necessary...]
624 if os.path.exists(dsc_file):
625 files[dsc_file] = {};
626 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
627 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
628 files[dsc_file]["section"] = files[file]["section"];
629 files[dsc_file]["priority"] = files[file]["priority"];
630 files[dsc_file]["component"] = files[file]["component"];
631 files[dsc_file]["type"] = "orig.tar.gz";
635 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming or in the pool.\n" % (file, dsc_file);
638 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming.\n" % (file, dsc_file);
640 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
641 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
642 if actual_size != int(dsc_files[dsc_file]["size"]):
643 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
645 if string.find(reject_message, "Rejected:") != -1:
650 ###############################################################################
652 # Some cunning stunt broke dpkg-source in dpkg 1.8{,.1}; detect the
653 # resulting bad source packages and reject them.
655 # Even more amusingly the fix in 1.8.1.1 didn't actually fix the
656 # problem just changed the symptoms.
659 global dsc, dsc_files, reject_message, reprocess;
661 for filename in files.keys():
662 if files[filename]["type"] == "diff.gz":
663 file = gzip.GzipFile(filename, 'r');
664 for line in file.readlines():
665 if re_bad_diff.search(line):
666 reject_message = reject_message + "Rejected: [dpkg-sucks] source package was produced by a broken version of dpkg-dev 1.8.x; please rebuild with >= 1.8.3 version installed.\n";
669 if string.find(reject_message, "Rejected:") != -1:
674 ###############################################################################
676 def check_md5sums ():
677 global reject_message;
679 for file in files.keys():
681 file_handle = utils.open_file(file,"r");
682 except utils.cant_open_exc:
685 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
686 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
688 def check_override ():
691 # Only check section & priority on sourceful uploads
692 if not changes["architecture"].has_key("source"):
696 for file in files.keys():
697 if not files[file].has_key("new") and files[file]["type"] == "deb":
698 section = files[file]["section"];
699 override_section = files[file]["override section"];
700 if section != override_section and section != "-":
701 # Ignore this; it's a common mistake and not worth whining about
702 if string.lower(section) == "non-us/main" and string.lower(override_section) == "non-us":
704 summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
705 priority = files[file]["priority"];
706 override_priority = files[file]["override priority"];
707 if priority != override_priority and priority != "-":
708 summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
713 Subst["__SUMMARY__"] = summary;
714 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.override-disparity","r").read());
715 utils.send_mail (mail_message, "")
717 #####################################################################################################################
719 # Set up the per-package template substitution mappings
721 def update_subst (changes_filename):
724 if changes.has_key("architecture"):
725 Subst["__ARCHITECTURE__"] = string.join(changes["architecture"].keys(), ' ' );
727 Subst["__ARCHITECTURE__"] = "Unknown";
728 Subst["__CHANGES_FILENAME__"] = os.path.basename(changes_filename);
729 Subst["__FILE_CONTENTS__"] = changes.get("filecontents", "");
731 # For source uploads the Changed-By field wins; otherwise Maintainer wins.
732 if changes["architecture"].has_key("source") and changes["changedby822"] != "" and (changes["changedby822"] != changes["maintainer822"]):
733 Subst["__MAINTAINER_FROM__"] = changes["changedby822"];
734 Subst["__MAINTAINER_TO__"] = changes["changedby822"] + ", " + changes["maintainer822"];
735 Subst["__MAINTAINER__"] = changes.get("changed-by", "Unknown");
737 Subst["__MAINTAINER_FROM__"] = changes["maintainer822"];
738 Subst["__MAINTAINER_TO__"] = changes["maintainer822"];
739 Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown");
741 Subst["__REJECT_MESSAGE__"] = reject_message;
742 Subst["__SOURCE__"] = changes.get("source", "Unknown");
743 Subst["__VERSION__"] = changes.get("version", "Unknown");
745 #####################################################################################################################
747 def action (changes_filename):
748 byhand = confirm = suites = summary = new = "";
750 # changes["distribution"] may not exist in corner cases
751 # (e.g. unreadable changes files)
752 if not changes.has_key("distribution"):
753 changes["distribution"] = {};
755 for suite in changes["distribution"].keys():
756 if Cnf.has_key("Suite::%s::Confirm"):
757 confirm = confirm + suite + ", "
758 suites = suites + suite + ", "
759 confirm = confirm[:-2]
762 for file in files.keys():
763 if files[file].has_key("byhand"):
765 summary = summary + file + " byhand\n"
766 elif files[file].has_key("new"):
768 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
769 if files[file].has_key("othercomponents"):
770 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
771 if files[file]["type"] == "deb":
772 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
774 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
775 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
776 summary = summary + file + "\n to " + destination + "\n"
778 short_summary = summary;
780 # This is for direport's benefit...
781 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
783 if confirm or byhand or new:
784 summary = summary + "Changes: " + f;
786 summary = summary + announce (short_summary, 0)
788 (prompt, answer) = ("", "XXX")
789 if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
792 if string.find(reject_message, "Rejected") != -1:
794 modified_time = time.time()-os.path.getmtime(changes_filename);
795 except: # i.e. ignore errors like 'file does not exist';
797 if modified_time < 86400:
798 print "SKIP (too new)\n" + reject_message,;
799 prompt = "[S]kip, Manual reject, Quit ?";
801 print "REJECT\n" + reject_message,;
802 prompt = "[R]eject, Manual reject, Skip, Quit ?";
803 if Cnf["Dinstall::Options::Automatic"]:
806 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
807 prompt = "[S]kip, New ack, Manual reject, Quit ?";
808 if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
811 print "BYHAND\n" + reject_message + summary,;
812 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
814 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
815 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
817 print "INSTALL\n" + reject_message + summary,;
818 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
819 if Cnf["Dinstall::Options::Automatic"]:
822 while string.find(prompt, answer) == -1:
824 answer = utils.our_raw_input()
825 m = re_default_answer.match(prompt)
828 answer = string.upper(answer[:1])
831 reject (changes_filename, "");
833 manual_reject (changes_filename);
835 install (changes_filename, summary, short_summary);
837 acknowledge_new (changes_filename, summary);
841 #####################################################################################################################
843 def install (changes_filename, summary, short_summary):
844 global install_count, install_bytes, Subst;
846 # Stable uploads are a special case
847 if changes.has_key("stable upload"):
848 stable_install (changes_filename, summary, short_summary);
853 archive = utils.where_am_i();
855 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
856 projectB.query("BEGIN WORK");
858 # Add the .dsc file to the DB
859 for file in files.keys():
860 if files[file]["type"] == "dsc":
861 package = dsc["source"]
862 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
863 maintainer = dsc["maintainer"]
864 maintainer = string.replace(maintainer, "'", "\\'")
865 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
866 filename = files[file]["pool name"] + file;
867 dsc_location_id = files[file]["location id"];
868 if not files[file]["files id"]:
869 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
870 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
871 % (package, version, maintainer_id, files[file]["files id"]))
873 for suite in changes["distribution"].keys():
874 suite_id = db_access.get_suite_id(suite);
875 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
877 # Add the source files to the DB (files and dsc_files)
878 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
879 for dsc_file in dsc_files.keys():
880 filename = files[file]["pool name"] + dsc_file;
881 # If the .orig.tar.gz is already in the pool, it's
882 # files id is stored in dsc_files by check_dsc().
883 files_id = dsc_files[dsc_file].get("files id", None);
885 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
886 # FIXME: needs to check for -1/-2 and or handle exception
888 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
889 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
891 # Add the .deb files to the DB
892 for file in files.keys():
893 if files[file]["type"] == "deb":
894 package = files[file]["package"]
895 version = files[file]["version"]
896 maintainer = files[file]["maintainer"]
897 maintainer = string.replace(maintainer, "'", "\\'")
898 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
899 architecture = files[file]["architecture"]
900 architecture_id = db_access.get_architecture_id (architecture);
901 type = files[file]["dbtype"];
902 dsc_component = files[file]["component"]
903 source = files[file]["source package"]
904 source_version = files[file]["source version"];
905 filename = files[file]["pool name"] + file;
906 if not files[file]["files id"]:
907 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
908 source_id = db_access.get_source_id (source, source_version);
910 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
911 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
913 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
914 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
915 for suite in changes["distribution"].keys():
916 suite_id = db_access.get_suite_id(suite);
917 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
919 # If the .orig.tar.gz is in a legacy directory we need to poolify
920 # it, so that apt-get source (and anything else that goes by the
921 # "Directory:" field in the Sources.gz file) works.
922 if orig_tar_id != None and orig_tar_location == "legacy":
923 q = projectB.query("SELECT DISTINCT ON (f.id) l.path, f.filename, f.id as files_id, df.source, df.id as dsc_files_id, f.size, f.md5sum FROM files f, dsc_files df, location l WHERE df.source IN (SELECT source FROM dsc_files WHERE file = %s) AND f.id = df.file AND l.id = f.location AND (l.type = 'legacy' OR l.type = 'legacy-mixed')" % (orig_tar_id));
926 # Is this an old upload superseded by a newer -sa upload? (See check_dsc() for details)
927 if legacy_source_untouchable.has_key(qid["files_id"]):
929 # First move the files to the new location
930 legacy_filename = qid["path"]+qid["filename"];
931 pool_location = utils.poolify (changes["source"], files[file]["component"]);
932 pool_filename = pool_location + os.path.basename(qid["filename"]);
933 destination = Cnf["Dir::PoolDir"] + pool_location
934 utils.move(legacy_filename, destination);
935 # Then Update the DB's files table
936 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
938 # If this is a sourceful diff only upload that is moving non-legacy
939 # cross-component we need to copy the .orig.tar.gz into the new
940 # component too for the same reasons as above.
942 if changes["architecture"].has_key("source") and orig_tar_id != None and \
943 orig_tar_location != "legacy" and orig_tar_location != dsc_location_id:
944 q = projectB.query("SELECT l.path, f.filename, f.size, f.md5sum FROM files f, location l WHERE f.id = %s AND f.location = l.id" % (orig_tar_id));
945 ql = q.getresult()[0];
946 old_filename = ql[0] + ql[1];
949 new_filename = utils.poolify (changes["source"], dsc_component) + os.path.basename(old_filename);
950 new_files_id = db_access.get_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
951 if new_files_id == None:
952 utils.copy(old_filename, Cnf["Dir::PoolDir"] + new_filename);
953 new_files_id = db_access.set_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
954 projectB.query("UPDATE dsc_files SET file = %s WHERE source = %s AND file = %s" % (new_files_id, source_id, orig_tar_id));
956 # Install the files into the pool
957 for file in files.keys():
958 if files[file].has_key("byhand"):
960 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
961 destdir = os.path.dirname(destination)
962 utils.move (file, destination)
963 install_bytes = install_bytes + float(files[file]["size"])
965 # Copy the .changes file across for suite which need it.
966 for suite in changes["distribution"].keys():
967 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
968 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
970 projectB.query("COMMIT WORK");
973 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
975 utils.warn("couldn't move changes file '%s' to DONE directory. [Got %s]" % (os.path.basename(changes_filename), sys.exc_type));
977 install_count = install_count + 1;
979 if not Cnf["Dinstall::Options::No-Mail"]:
980 Subst["__SUITE__"] = "";
981 Subst["__SUMMARY__"] = summary;
982 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.installed","r").read());
983 utils.send_mail (mail_message, "")
984 announce (short_summary, 1)
987 #####################################################################################################################
989 def stable_install (changes_filename, summary, short_summary):
990 global install_count, install_bytes, Subst;
992 print "Installing to stable."
994 archive = utils.where_am_i();
996 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
997 projectB.query("BEGIN WORK");
999 # Add the .dsc file to the DB
1000 for file in files.keys():
1001 if files[file]["type"] == "dsc":
1002 package = dsc["source"]
1003 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
1004 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
1007 utils.fubar("[INTERNAL ERROR] couldn't find '%s' (%s) in source table." % (package, version));
1008 source_id = ql[0][0];
1009 suite_id = db_access.get_suite_id('proposed-updates');
1010 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
1011 suite_id = db_access.get_suite_id('stable');
1012 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
1014 # Add the .deb files to the DB
1015 for file in files.keys():
1016 if files[file]["type"] == "deb":
1017 package = files[file]["package"]
1018 version = files[file]["version"]
1019 architecture = files[file]["architecture"]
1020 q = projectB.query("SELECT b.id FROM binaries b, architecture a WHERE b.package = '%s' AND b.version = '%s' AND (a.arch_string = '%s' OR a.arch_string = 'all') AND b.architecture = a.id" % (package, version, architecture))
1023 utils.fubar("[INTERNAL ERROR] couldn't find '%s' (%s for %s architecture) in binaries table." % (package, version, architecture));
1024 binary_id = ql[0][0];
1025 suite_id = db_access.get_suite_id('proposed-updates');
1026 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
1027 suite_id = db_access.get_suite_id('stable');
1028 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
1030 projectB.query("COMMIT WORK");
1033 utils.move (changes_filename, Cnf["Dir::Morgue"] + '/katie/' + os.path.basename(changes_filename));
1035 # Update the Stable ChangeLog file
1037 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
1038 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
1039 if os.path.exists(new_changelog_filename):
1040 os.unlink (new_changelog_filename);
1042 new_changelog = utils.open_file(new_changelog_filename, 'w');
1043 for file in files.keys():
1044 if files[file]["type"] == "deb":
1045 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
1046 elif utils.re_issource.match(file) != None:
1047 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
1049 new_changelog.write("%s\n" % (file));
1050 chop_changes = re_fdnic.sub("\n", changes["changes"]);
1051 new_changelog.write(chop_changes + '\n\n');
1052 if os.access(changelog_filename, os.R_OK) != 0:
1053 changelog = utils.open_file(changelog_filename, 'r');
1054 new_changelog.write(changelog.read());
1055 new_changelog.close();
1056 if os.access(changelog_filename, os.R_OK) != 0:
1057 os.unlink(changelog_filename);
1058 utils.move(new_changelog_filename, changelog_filename);
1060 install_count = install_count + 1;
1062 if not Cnf["Dinstall::Options::No-Mail"]:
1063 Subst["__SUITE__"] = " into stable";
1064 Subst["__SUMMARY__"] = summary;
1065 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.installed","r").read());
1066 utils.send_mail (mail_message, "")
1067 announce (short_summary, 1)
1069 #####################################################################################################################
1071 def reject (changes_filename, manual_reject_mail_filename):
1074 print "Rejecting.\n"
1076 base_changes_filename = os.path.basename(changes_filename);
1077 reason_filename = re_changes.sub("reason", base_changes_filename);
1078 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
1080 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
1082 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
1084 utils.warn("couldn't reject changes file '%s'. [Got %s]" % (base_changes_filename, sys.exc_type));
1086 for file in files.keys():
1087 if os.path.exists(file):
1089 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
1091 utils.warn("couldn't reject file '%s'. [Got %s]" % (file, sys.exc_type));
1094 # If this is not a manual rejection generate the .reason file and rejection mail message
1095 if manual_reject_mail_filename == "":
1096 if os.path.exists(reject_filename):
1097 os.unlink(reject_filename);
1098 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1099 os.write(fd, reject_message);
1101 Subst["__MANUAL_REJECT_MESSAGE__"] = "";
1102 Subst["__CC__"] = "X-Katie-Rejection: automatic (moo)";
1103 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1104 else: # Have a manual rejection file to use
1105 reject_mail_message = ""; # avoid <undef>'s
1107 # Send the rejection mail if appropriate
1108 if not Cnf["Dinstall::Options::No-Mail"]:
1109 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
1111 ##################################################################
1113 def manual_reject (changes_filename):
1116 # Build up the rejection email
1117 user_email_address = utils.whoami() + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
1118 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
1120 Subst["__MANUAL_REJECT_MESSAGE__"] = manual_reject_message;
1121 Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"];
1122 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1124 # Write the rejection email out as the <foo>.reason file
1125 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
1126 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
1127 if os.path.exists(reject_filename):
1128 os.unlink(reject_filename);
1129 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1130 os.write(fd, reject_mail_message);
1133 # If we weren't given one, spawn an editor so the user can add one in
1134 if manual_reject_message == "":
1135 result = os.system("vi +6 %s" % (reject_filename))
1137 utils.fubar("vi invocation failed for `%s'!" % (reject_filename), result);
1139 # Then process it as if it were an automatic rejection
1140 reject (changes_filename, reject_filename)
1142 #####################################################################################################################
1144 def acknowledge_new (changes_filename, summary):
1145 global new_ack_new, Subst;
1147 changes_filename = os.path.basename(changes_filename);
1149 new_ack_new[changes_filename] = 1;
1151 if new_ack_old.has_key(changes_filename):
1152 print "Ack already sent.";
1155 print "Sending new ack.";
1156 if not Cnf["Dinstall::Options::No-Mail"]:
1157 Subst["__SUMMARY__"] = summary;
1158 new_ack_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.new","r").read());
1159 utils.send_mail(new_ack_message,"");
1161 #####################################################################################################################
1163 def announce (short_summary, action):
1166 # Only do announcements for source uploads with a recent dpkg-dev installed
1167 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
1172 Subst["__SHORT_SUMMARY__"] = short_summary;
1174 for dist in changes["distribution"].keys():
1175 list = Cnf.Find("Suite::%s::Announce" % (dist))
1176 if list == "" or lists_done.has_key(list):
1178 lists_done[list] = 1
1179 summary = summary + "Announcing to %s\n" % (list)
1182 Subst["__ANNOUNCE_LIST_ADDRESS__"] = list;
1183 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.announce","r").read());
1184 utils.send_mail (mail_message, "")
1186 bugs = changes["closes"].keys()
1188 if not nmu.is_an_nmu(changes, dsc):
1189 summary = summary + "Closing bugs: "
1191 summary = summary + "%s " % (bug)
1193 Subst["__BUG_NUMBER__"] = bug;
1194 if changes["distribution"].has_key("stable"):
1195 Subst["__STABLE_WARNING__"] = """
1196 Note that this package is not part of the released stable Debian
1197 distribution. It may have dependencies on other unreleased software,
1198 or other instabilities. Please take care if you wish to install it.
1199 The update will eventually make its way into the next released Debian
1202 Subst["__STABLE_WARNING__"] = "";
1203 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-close","r").read());
1204 utils.send_mail (mail_message, "")
1206 summary = summary + "Setting bugs to severity fixed: "
1207 control_message = ""
1209 summary = summary + "%s " % (bug)
1210 control_message = control_message + "tag %s + fixed\n" % (bug)
1211 if action and control_message != "":
1212 Subst["__CONTROL_MESSAGE__"] = control_message;
1213 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-nmu-fixed","r").read());
1214 utils.send_mail (mail_message, "")
1215 summary = summary + "\n"
1219 ###############################################################################
1221 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1222 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1223 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1224 # processed it during it's checks of -2. If -1 has been deleted or
1225 # otherwise not checked by da-install, the .orig.tar.gz will not have
1226 # been checked at all. To get round this, we force the .orig.tar.gz
1227 # into the .changes structure and reprocess the .changes file.
1229 def process_it (changes_file):
1230 global reprocess, orig_tar_id, orig_tar_location, changes, dsc, dsc_files, files, reject_message;
1232 # Reset some globals
1239 orig_tar_location = "";
1240 legacy_source_untouchable = {};
1241 reject_message = "";
1243 # Absolutize the filename to avoid the requirement of being in the
1244 # same directory as the .changes file.
1245 changes_file = os.path.abspath(changes_file);
1247 # And since handling of installs to stable munges with the CWD;
1248 # save and restore it.
1252 check_signature (changes_file);
1253 check_changes (changes_file);
1262 traceback.print_exc(file=sys.stdout);
1265 update_subst(changes_file);
1266 action(changes_file);
1271 ###############################################################################
1274 global Cnf, projectB, install_bytes, new_ack_old, Subst, nmu
1278 Cnf = apt_pkg.newConfiguration();
1279 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1281 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1282 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1283 ('h',"help","Dinstall::Options::Help"),
1284 ('k',"ack-new","Dinstall::Options::Ack-New"),
1285 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1286 ('n',"no-action","Dinstall::Options::No-Action"),
1287 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1288 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1289 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1290 ('v',"version","Dinstall::Options::Version")];
1292 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1294 if Cnf["Dinstall::Options::Help"]:
1297 if Cnf["Dinstall::Options::Version"]:
1298 print "katie version 0.0000000000";
1301 postgresql_user = None; # Default == Connect as user running program.
1303 # -n/--dry-run invalidates some other options which would involve things happening
1304 if Cnf["Dinstall::Options::No-Action"]:
1305 Cnf["Dinstall::Options::Automatic"] = ""
1306 Cnf["Dinstall::Options::Ack-New"] = ""
1307 postgresql_user = Cnf["DB::ROUser"];
1309 projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1311 db_access.init(Cnf, projectB);
1313 # Check that we aren't going to clash with the daily cron job
1315 if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1316 utils.fubar("Archive maintenance in progress. Try again later.");
1318 # Obtain lock if not in no-action mode
1320 if not Cnf["Dinstall::Options::No-Action"]:
1321 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1322 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1324 # Read in the list of already-acknowledged NEW packages
1325 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1327 for line in new_ack_list.readlines():
1328 new_ack_old[line[:-1]] = 1;
1329 new_ack_list.close();
1331 # Initialize the substitution template mapping global
1333 Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"];
1334 Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"];
1335 bcc = "X-Katie: $Revision: 1.50 $"
1336 if Cnf.has_key("Dinstall::Bcc"):
1337 Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"]);
1339 Subst["__BCC__"] = bcc;
1340 Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"];
1341 Subst["__KATIE_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"];
1343 # Read in the group-maint override file
1346 # Sort the .changes files so that we process sourceful ones first
1347 changes_files.sort(utils.changes_compare);
1349 # Process the changes files
1350 for changes_file in changes_files:
1351 print "\n" + changes_file;
1352 process_it (changes_file);
1356 if install_count > 1:
1358 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1360 # Write out the list of already-acknowledged NEW packages
1361 if Cnf["Dinstall::Options::Ack-New"]:
1362 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1363 for i in new_ack_new.keys():
1364 new_ack_list.write(i+'\n')
1365 new_ack_list.close()
1368 if __name__ == '__main__':