3 # Installs Debian packaes
4 # Copyright (C) 2000, 2001 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.47 2001-06-22 22:53:14 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 = re.compile("\.\d+\.\d+$");
48 #########################################################################################
64 orig_tar_location = "";
65 legacy_source_untouchable = {};
69 #########################################################################################
71 def usage (exit_code):
72 print """Usage: dinstall [OPTION]... [CHANGES]...
73 -a, --automatic automatic run
74 -D, --debug=VALUE turn on debugging
75 -h, --help show this help and exit.
76 -k, --ack-new acknowledge new packages !! for cron.daily only !!
77 -m, --manual-reject=MSG manual reject with `msg'
78 -n, --no-action don't do anything
79 -p, --no-lock don't check lockfile !! for cron.daily only !!
80 -u, --distribution=DIST override distribution to `dist'
81 -v, --version display the version number and exit"""
84 #########################################################################################
86 def check_signature (filename):
89 (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))
91 reject_message = "Rejected: GPG signature check failed on `%s'.\n%s\n" % (os.path.basename(filename), output)
95 ######################################################################################################
98 # Read in the group maintainer override file
100 self.group_maint = {};
101 if Cnf.get("Dinstall::GroupOverrideFilename"):
102 filename = Cnf["Dir::OverrideDir"] + Cnf["Dinstall::GroupOverrideFilename"];
103 file = utils.open_file(filename, 'r');
104 for line in file.readlines():
105 line = string.strip(utils.re_comments.sub('', line));
107 self.group_maint[line] = 1;
110 def is_an_nmu (self, changes, dsc):
111 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
112 # changes["changedbyname"] == dsc_name is probably never true, but better safe than sorry
113 if dsc_name == changes["maintainername"] and (changes["changedbyname"] == "" or changes["changedbyname"] == dsc_name):
116 if dsc.has_key("maintainers"):
117 maintainers = string.split(dsc["maintainers"], ",");
118 maintainernames = {};
119 for i in maintainers:
120 (rfc822, name, email) = utils.fix_maintainer (string.strip(i));
121 maintainernames[name] = "";
122 if maintainernames.has_key(changes["changedbyname"]):
125 # Some group maintained packages (e.g. Debian QA) are never NMU's
126 if self.group_maint.has_key(changes["maintainername"]):
131 ######################################################################################################
133 # See if a given package is in the override table
135 def in_override_p (package, component, suite, binary_type, file):
138 if binary_type == "": # must be source
143 # Override suite name; used for example with proposed-updates
144 if Cnf.Find("Suite::%s::OverrideSuite" % (suite)) != "":
145 suite = Cnf["Suite::%s::OverrideSuite" % (suite)];
147 # Avoid <undef> on unknown distributions
148 suite_id = db_access.get_suite_id(suite);
151 component_id = db_access.get_component_id(component);
152 type_id = db_access.get_override_type_id(type);
154 # FIXME: nasty non-US speficic hack
155 if string.lower(component[:7]) == "non-us/":
156 component = component[7:];
158 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"
159 % (package, suite_id, component_id, type_id));
160 result = q.getresult();
161 # If checking for a source package fall back on the binary override type
162 if type == "dsc" and not result:
163 type_id = db_access.get_override_type_id("deb");
164 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"
165 % (package, suite_id, component_id, type_id));
166 result = q.getresult();
168 # Remember the section and priority so we can check them later if appropriate
170 files[file]["override section"] = result[0][0];
171 files[file]["override priority"] = result[0][1];
175 #####################################################################################################################
177 def check_changes(filename):
178 global reject_message, changes, files
180 # Default in case we bail out
181 changes["maintainer822"] = Cnf["Dinstall::MyEmailAddress"];
182 changes["changedby822"] = Cnf["Dinstall::MyEmailAddress"];
183 changes["architecture"] = {};
185 # Parse the .changes field into a dictionary
187 changes = utils.parse_changes(filename, 0)
188 except utils.cant_open_exc:
189 reject_message = "Rejected: can't read changes file '%s'.\n" % (filename)
191 except utils.changes_parse_error_exc, line:
192 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
195 # Parse the Files field from the .changes into another dictionary
197 files = utils.build_file_list(changes, "");
198 except utils.changes_parse_error_exc, line:
199 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line);
201 # Check for mandatory fields
202 for i in ("source", "binary", "architecture", "version", "distribution","maintainer", "files"):
203 if not changes.has_key(i):
204 reject_message = "Rejected: Missing field `%s' in changes file.\n" % (i)
205 return 0 # Avoid <undef> errors during later tests
207 # Override the Distribution: field if appropriate
208 if Cnf["Dinstall::Options::Override-Distribution"] != "":
209 reject_message = reject_message + "Warning: Distribution was overriden from %s to %s.\n" % (changes["distribution"], Cnf["Dinstall::Options::Override-Distribution"])
210 changes["distribution"] = Cnf["Dinstall::Options::Override-Distribution"]
212 # Split multi-value fields into a lower-level dictionary
213 for i in ("architecture", "distribution", "binary", "closes"):
214 o = changes.get(i, "")
218 for j in string.split(o):
221 # Fix the Maintainer: field to be RFC822 compatible
222 (changes["maintainer822"], changes["maintainername"], changes["maintaineremail"]) = utils.fix_maintainer (changes["maintainer"])
224 # Fix the Changed-By: field to be RFC822 compatible; if it exists.
225 (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]) = utils.fix_maintainer(changes.get("changed-by",""));
227 # Ensure all the values in Closes: are numbers
228 if changes.has_key("closes"):
229 for i in changes["closes"].keys():
230 if re_isanum.match (i) == None:
231 reject_message = reject_message + "Rejected: `%s' from Closes field isn't a number.\n" % (i)
233 # Map frozen to unstable if frozen doesn't exist
234 if changes["distribution"].has_key("frozen") and not Cnf.has_key("Suite::Frozen"):
235 del changes["distribution"]["frozen"]
236 changes["distribution"]["unstable"] = 1;
237 reject_message = reject_message + "Mapping frozen to unstable.\n"
239 # Map testing to unstable
240 if changes["distribution"].has_key("testing"):
241 del changes["distribution"]["testing"]
242 changes["distribution"]["unstable"] = 1;
243 reject_message = reject_message + "Mapping testing to unstable.\n"
245 # Ensure target distributions exist
246 for i in changes["distribution"].keys():
247 if not Cnf.has_key("Suite::%s" % (i)):
248 reject_message = reject_message + "Rejected: Unknown distribution `%s'.\n" % (i)
250 # Ensure there _is_ a target distribution
251 if changes["distribution"].keys() == []:
252 reject_message = reject_message + "Rejected: huh? Distribution field is empty in changes file.\n";
254 # Map unreleased arches from stable to unstable
255 if changes["distribution"].has_key("stable"):
256 for i in changes["architecture"].keys():
257 if not Cnf.has_key("Suite::Stable::Architectures::%s" % (i)):
258 reject_message = reject_message + "Mapping stable to unstable for unreleased arch `%s'.\n" % (i)
259 del changes["distribution"]["stable"]
260 changes["distribution"]["unstable"] = 1;
262 # Map arches not being released from frozen to unstable
263 if changes["distribution"].has_key("frozen"):
264 for i in changes["architecture"].keys():
265 if not Cnf.has_key("Suite::Frozen::Architectures::%s" % (i)):
266 reject_message = reject_message + "Mapping frozen to unstable for non-releasing arch `%s'.\n" % (i)
267 del changes["distribution"]["frozen"]
268 changes["distribution"]["unstable"] = 1;
270 # Handle uploads to stable
271 if changes["distribution"].has_key("stable"):
272 # If running from within proposed-updates; assume an install to stable
273 if string.find(os.getcwd(), 'proposed-updates') != -1:
274 # FIXME: should probably remove anything that != stable
275 for i in ("frozen", "unstable"):
276 if changes["distribution"].has_key(i):
277 reject_message = reject_message + "Removing %s from distribution list.\n" % (i)
278 del changes["distribution"][i]
279 changes["stable upload"] = 1;
280 # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool
281 file = files.keys()[0];
282 if os.access(file, os.R_OK) == 0:
283 pool_dir = Cnf["Dir::PoolDir"] + '/' + utils.poolify(changes["source"], files[file]["component"]);
285 # Otherwise (normal case) map stable to updates
287 reject_message = reject_message + "Mapping stable to updates.\n";
288 del changes["distribution"]["stable"];
289 changes["distribution"]["proposed-updates"] = 1;
291 # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
292 changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
293 changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
295 if string.find(reject_message, "Rejected:") != -1:
301 global reject_message
303 archive = utils.where_am_i();
305 for file in files.keys():
306 # Check the file is readable
307 if os.access(file,os.R_OK) == 0:
308 if os.path.exists(file):
309 reject_message = reject_message + "Rejected: Can't read `%s'. [permission denied]\n" % (file)
311 reject_message = reject_message + "Rejected: Can't read `%s'. [file not found]\n" % (file)
313 files[file]["type"] = "unreadable";
315 # If it's byhand skip remaining checks
316 if files[file]["section"] == "byhand":
317 files[file]["byhand"] = 1;
318 files[file]["type"] = "byhand";
319 # Checks for a binary package...
320 elif utils.re_isadeb.match(file) != None:
321 files[file]["type"] = "deb";
323 # Extract package information using dpkg-deb
325 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
327 reject_message = reject_message + "Rejected: %s: debExtractControl() raised %s.\n" % (file, sys.exc_type);
328 # Can't continue, none of the checks on control would work.
331 # Check for mandatory fields
332 if control.Find("Package") == None:
333 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
334 if control.Find("Architecture") == None:
335 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
336 if control.Find("Version") == None:
337 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
339 # Ensure the package name matches the one give in the .changes
340 if not changes["binary"].has_key(control.Find("Package", "")):
341 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
343 # Validate the architecture
344 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
345 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
347 # Check the architecture matches the one given in the .changes
348 if not changes["architecture"].has_key(control.Find("Architecture", "")):
349 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
350 # Check the section & priority match those given in the .changes (non-fatal)
351 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
352 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"])
353 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
354 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"])
356 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
358 files[file]["package"] = control.Find("Package");
359 files[file]["architecture"] = control.Find("Architecture");
360 files[file]["version"] = control.Find("Version");
361 files[file]["maintainer"] = control.Find("Maintainer", "");
362 if file[-5:] == ".udeb":
363 files[file]["dbtype"] = "udeb";
364 elif file[-4:] == ".deb":
365 files[file]["dbtype"] = "deb";
367 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
368 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
369 files[file]["source"] = control.Find("Source", "");
370 if files[file]["source"] == "":
371 files[file]["source"] = files[file]["package"];
372 # Get the source version
373 source = files[file]["source"];
375 if string.find(source, "(") != -1:
376 m = utils.re_extract_src_version.match(source)
378 source_version = m.group(2)
379 if not source_version:
380 source_version = files[file]["version"];
381 files[file]["source package"] = source;
382 files[file]["source version"] = source_version;
384 # Checks for a source package...
386 m = utils.re_issource.match(file)
388 files[file]["package"] = m.group(1)
389 files[file]["version"] = m.group(2)
390 files[file]["type"] = m.group(3)
392 # Ensure the source package name matches the Source filed in the .changes
393 if changes["source"] != files[file]["package"]:
394 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
396 # Ensure the source version matches the version in the .changes file
397 if files[file]["type"] == "orig.tar.gz":
398 changes_version = changes["chopversion2"]
400 changes_version = changes["chopversion"]
401 if changes_version != files[file]["version"]:
402 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
404 # Ensure the .changes lists source in the Architecture field
405 if not changes["architecture"].has_key("source"):
406 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
408 # Check the signature of a .dsc file
409 if files[file]["type"] == "dsc":
410 check_signature(file)
412 files[file]["fullname"] = file
414 # Not a binary or source package? Assume byhand...
416 files[file]["byhand"] = 1;
417 files[file]["type"] = "byhand";
419 files[file]["oldfiles"] = {}
420 for suite in changes["distribution"].keys():
422 if files[file].has_key("byhand"):
425 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
426 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
429 # See if the package is NEW
430 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
431 files[file]["new"] = 1
433 if files[file]["type"] == "deb":
434 # Find any old binary packages
435 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"
436 % (files[file]["package"], suite, files[file]["architecture"]))
437 oldfiles = q.dictresult()
438 for oldfile in oldfiles:
439 files[file]["oldfiles"][suite] = oldfile
440 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
441 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
442 reject_message = reject_message + "Rejected: %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
443 # Check for existing copies of the file
444 if not changes.has_key("stable upload"):
445 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"]))
446 if q.getresult() != []:
447 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
449 # Check for existent source
450 # FIXME: this is no longer per suite
451 if changes["architecture"].has_key("source"):
452 source_version = files[file]["source version"];
453 if source_version != changes["version"]:
454 reject_message = reject_message + "Rejected: source version (%s) for %s doesn't match changes version %s.\n" % (files[file]["sourceversion"], file, changes["version"]);
456 q = projectB.query("SELECT s.version FROM source s WHERE s.source = '%s'" % (files[file]["source package"]));
457 ql = map(lambda x: x[0], q.getresult());
458 if ql.count(source_version) == 0:
459 # Maybe it's a binary only NMU ?
460 if re_bin_only_nmu.search(source_version):
461 orig_source_version = re_bin_only_nmu.sub('', source_version);
462 if ql.count(orig_source_version) == 0:
463 reject_message = reject_message + "Rejected: no source version (%s [or %s]) found in %s for %s (%s).\n" % (source_version, orig_source_version, suite, files[file]["source package"], file);
465 reject_message = reject_message + "Rejected: no source version (%s) found in %s for %s (%s).\n" % (source_version, suite, files[file]["source package"], file);
467 # Find any old .dsc files
468 elif files[file]["type"] == "dsc":
469 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"
470 % (files[file]["package"], suite))
471 oldfiles = q.dictresult()
472 if len(oldfiles) >= 1:
473 files[file]["oldfiles"][suite] = oldfiles[0]
475 # Validate the component
476 component = files[file]["component"];
477 component_id = db_access.get_component_id(component);
478 if component_id == -1:
479 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
482 # Validate the priority
483 if string.find(files[file]["priority"],'/') != -1:
484 reject_message = reject_message + "Rejected: file '%s' has invalid priority '%s' [contains '/'].\n" % (file, files[file]["priority"]);
486 # Check the md5sum & size against existing files (if any)
487 location = Cnf["Dir::PoolDir"];
488 files[file]["location id"] = db_access.get_location_id (location, component, archive);
490 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
491 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
493 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
495 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
496 files[file]["files id"] = files_id
498 # Check for packages that have moved from one component to another
499 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
500 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
503 if string.find(reject_message, "Rejected:") != -1:
508 ###############################################################################
511 global dsc, dsc_files, reject_message, reprocess, orig_tar_id, orig_tar_location, legacy_source_untouchable;
513 for file in files.keys():
514 if files[file]["type"] == "dsc":
516 dsc = utils.parse_changes(file, 1)
517 except utils.cant_open_exc:
518 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (file)
520 except utils.changes_parse_error_exc, line:
521 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (file, line)
523 except utils.invalid_dsc_format_exc, line:
524 reject_message = reject_message + "Rejected: syntax error in .dsc file '%s', line %s.\n" % (file, line)
527 dsc_files = utils.build_file_list(dsc, 1)
528 except utils.no_files_exc:
529 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
531 except utils.changes_parse_error_exc, line:
532 reject_message = "Rejected: error parsing .dsc file '%s', can't grok: %s.\n" % (file, line);
535 # Try and find all files mentioned in the .dsc. This has
536 # to work harder to cope with the multiple possible
537 # locations of an .orig.tar.gz.
538 for dsc_file in dsc_files.keys():
539 if files.has_key(dsc_file):
540 actual_md5 = files[dsc_file]["md5sum"];
541 actual_size = int(files[dsc_file]["size"]);
542 found = "%s in incoming" % (dsc_file)
543 # Check the file does not already exist in the archive
544 if not changes.has_key("stable upload"):
545 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));
547 # "It has not broken them. It has fixed a
548 # brokenness. Your crappy hack exploited a
549 # bug in the old dinstall.
551 # "(Come on! I thought it was always obvious
552 # that one just doesn't release different
553 # files with the same name and version.)"
554 # -- ajk@ on d-devel@l.d.o
556 if q.getresult() != []:
557 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
558 elif dsc_file[-12:] == ".orig.tar.gz":
560 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));
564 # Unfortunately, we make get more than one match
565 # here if, for example, the package was in potato
566 # but had a -sa upload in woody. So we need to a)
567 # choose the right one and b) mark all wrong ones
568 # as excluded from the source poolification (to
569 # avoid file overwrites).
571 x = ql[0]; # default to something sane in case we don't match any or have only one
575 old_file = i[0] + i[1];
576 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
577 actual_size = os.stat(old_file)[stat.ST_SIZE];
578 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
581 legacy_source_untouchable[i[3]] = "";
583 old_file = x[0] + x[1];
584 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
585 actual_size = os.stat(old_file)[stat.ST_SIZE];
588 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
591 if suite_type == "legacy" or suite_type == "legacy-mixed":
592 orig_tar_location = "legacy";
594 orig_tar_location = x[4];
596 # Not there? Check in Incoming...
597 # [See comment above process_it() for explanation
598 # of why this is necessary...]
599 if os.path.exists(dsc_file):
600 files[dsc_file] = {};
601 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
602 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
603 files[dsc_file]["section"] = files[file]["section"];
604 files[dsc_file]["priority"] = files[file]["priority"];
605 files[dsc_file]["component"] = files[file]["component"];
606 files[dsc_file]["type"] = "orig.tar.gz";
610 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);
613 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming.\n" % (file, dsc_file);
615 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
616 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
617 if actual_size != int(dsc_files[dsc_file]["size"]):
618 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
620 if string.find(reject_message, "Rejected:") != -1:
625 ###############################################################################
627 # Some cunning stunt broke dpkg-source in dpkg 1.8{,.1}; detect the
628 # resulting bad source packages and reject them.
630 # Even more amusingly the fix in 1.8.1.1 didn't actually fix the
631 # problem just changed the symptoms.
634 global dsc, dsc_files, reject_message, reprocess;
636 for filename in files.keys():
637 if files[filename]["type"] == "diff.gz":
638 file = gzip.GzipFile(filename, 'r');
639 for line in file.readlines():
640 if re_bad_diff.search(line):
641 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";
644 if string.find(reject_message, "Rejected:") != -1:
649 ###############################################################################
651 def check_md5sums ():
652 global reject_message;
654 for file in files.keys():
656 file_handle = utils.open_file(file,"r");
657 except utils.cant_open_exc:
660 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
661 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
663 def check_override ():
666 # Only check section & priority on sourceful uploads
667 if not changes["architecture"].has_key("source"):
671 for file in files.keys():
672 if not files[file].has_key("new") and files[file]["type"] == "deb":
673 section = files[file]["section"];
674 override_section = files[file]["override section"];
675 if section != override_section and section != "-":
676 # Ignore this; it's a common mistake and not worth whining about
677 if string.lower(section) == "non-us/main" and string.lower(override_section) == "non-us":
679 summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
680 priority = files[file]["priority"];
681 override_priority = files[file]["override priority"];
682 if priority != override_priority and priority != "-":
683 summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
688 Subst["__SUMMARY__"] = summary;
689 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.override-disparity","r").read());
690 utils.send_mail (mail_message, "")
692 #####################################################################################################################
694 # Set up the per-package template substitution mappings
696 def update_subst (changes_filename):
699 if changes.has_key("architecture"):
700 Subst["__ARCHITECTURE__"] = string.join(changes["architecture"].keys(), ' ' );
702 Subst["__ARCHITECTURE__"] = "Unknown";
703 Subst["__CHANGES_FILENAME__"] = os.path.basename(changes_filename);
704 Subst["__FILE_CONTENTS__"] = changes.get("filecontents", "");
706 # For source uploads the Changed-By field wins; otherwise Maintainer wins.
707 if changes["architecture"].has_key("source") and changes["changedby822"] != "" and (changes["changedby822"] != changes["maintainer822"]):
708 Subst["__MAINTAINER_FROM__"] = changes["changedby822"];
709 Subst["__MAINTAINER_TO__"] = changes["changedby822"] + ", " + changes["maintainer822"];
710 Subst["__MAINTAINER__"] = changes.get("changed-by", "Unknown");
712 Subst["__MAINTAINER_FROM__"] = changes["maintainer822"];
713 Subst["__MAINTAINER_TO__"] = changes["maintainer822"];
714 Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown");
716 Subst["__REJECT_MESSAGE__"] = reject_message;
717 Subst["__SOURCE__"] = changes.get("source", "Unknown");
718 Subst["__VERSION__"] = changes.get("version", "Unknown");
720 #####################################################################################################################
722 def action (changes_filename):
723 byhand = confirm = suites = summary = new = "";
725 # changes["distribution"] may not exist in corner cases
726 # (e.g. unreadable changes files)
727 if not changes.has_key("distribution"):
728 changes["distribution"] = {};
730 for suite in changes["distribution"].keys():
731 if Cnf.has_key("Suite::%s::Confirm"):
732 confirm = confirm + suite + ", "
733 suites = suites + suite + ", "
734 confirm = confirm[:-2]
737 for file in files.keys():
738 if files[file].has_key("byhand"):
740 summary = summary + file + " byhand\n"
741 elif files[file].has_key("new"):
743 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
744 if files[file].has_key("othercomponents"):
745 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
746 if files[file]["type"] == "deb":
747 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
749 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
750 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
751 summary = summary + file + "\n to " + destination + "\n"
753 short_summary = summary;
755 # This is for direport's benefit...
756 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
758 if confirm or byhand or new:
759 summary = summary + "Changes: " + f;
761 summary = summary + announce (short_summary, 0)
763 (prompt, answer) = ("", "XXX")
764 if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
767 if string.find(reject_message, "Rejected") != -1:
769 modified_time = time.time()-os.path.getmtime(changes_filename);
770 except: # i.e. ignore errors like 'file does not exist';
772 if modified_time < 86400:
773 print "SKIP (too new)\n" + reject_message,;
774 prompt = "[S]kip, Manual reject, Quit ?";
776 print "REJECT\n" + reject_message,;
777 prompt = "[R]eject, Manual reject, Skip, Quit ?";
778 if Cnf["Dinstall::Options::Automatic"]:
781 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
782 prompt = "[S]kip, New ack, Manual reject, Quit ?";
783 if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
786 print "BYHAND\n" + reject_message + summary,;
787 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
789 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
790 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
792 print "INSTALL\n" + reject_message + summary,;
793 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
794 if Cnf["Dinstall::Options::Automatic"]:
797 while string.find(prompt, answer) == -1:
799 answer = utils.our_raw_input()
800 m = re_default_answer.match(prompt)
803 answer = string.upper(answer[:1])
806 reject (changes_filename, "");
808 manual_reject (changes_filename);
810 install (changes_filename, summary, short_summary);
812 acknowledge_new (changes_filename, summary);
816 #####################################################################################################################
818 def install (changes_filename, summary, short_summary):
819 global install_count, install_bytes, Subst;
821 # Stable uploads are a special case
822 if changes.has_key("stable upload"):
823 stable_install (changes_filename, summary, short_summary);
828 archive = utils.where_am_i();
830 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
831 projectB.query("BEGIN WORK");
833 # Add the .dsc file to the DB
834 for file in files.keys():
835 if files[file]["type"] == "dsc":
836 package = dsc["source"]
837 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
838 maintainer = dsc["maintainer"]
839 maintainer = string.replace(maintainer, "'", "\\'")
840 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
841 filename = files[file]["pool name"] + file;
842 dsc_location_id = files[file]["location id"];
843 if not files[file]["files id"]:
844 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
845 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
846 % (package, version, maintainer_id, files[file]["files id"]))
848 for suite in changes["distribution"].keys():
849 suite_id = db_access.get_suite_id(suite);
850 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
852 # Add the source files to the DB (files and dsc_files)
853 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
854 for dsc_file in dsc_files.keys():
855 filename = files[file]["pool name"] + dsc_file;
856 # If the .orig.tar.gz is already in the pool, it's
857 # files id is stored in dsc_files by check_dsc().
858 files_id = dsc_files[dsc_file].get("files id", None);
860 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
861 # FIXME: needs to check for -1/-2 and or handle exception
863 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
864 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
866 # Add the .deb files to the DB
867 for file in files.keys():
868 if files[file]["type"] == "deb":
869 package = files[file]["package"]
870 version = files[file]["version"]
871 maintainer = files[file]["maintainer"]
872 maintainer = string.replace(maintainer, "'", "\\'")
873 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
874 architecture = files[file]["architecture"]
875 architecture_id = db_access.get_architecture_id (architecture);
876 type = files[file]["dbtype"];
877 dsc_component = files[file]["component"]
878 source = files[file]["source package"]
879 source_version = files[file]["source version"];
880 filename = files[file]["pool name"] + file;
881 if not files[file]["files id"]:
882 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
883 source_id = db_access.get_source_id (source, source_version);
885 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
886 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
888 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
889 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
890 for suite in changes["distribution"].keys():
891 suite_id = db_access.get_suite_id(suite);
892 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
894 # If the .orig.tar.gz is in a legacy directory we need to poolify
895 # it, so that apt-get source (and anything else that goes by the
896 # "Directory:" field in the Sources.gz file) works.
897 if orig_tar_id != None and orig_tar_location == "legacy":
898 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));
901 # Is this an old upload superseded by a newer -sa upload? (See check_dsc() for details)
902 if legacy_source_untouchable.has_key(qid["files_id"]):
904 # First move the files to the new location
905 legacy_filename = qid["path"]+qid["filename"];
906 pool_location = utils.poolify (changes["source"], files[file]["component"]);
907 pool_filename = pool_location + os.path.basename(qid["filename"]);
908 destination = Cnf["Dir::PoolDir"] + pool_location
909 utils.move(legacy_filename, destination);
910 # Then Update the DB's files table
911 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
913 # If this is a sourceful diff only upload that is moving non-legacy
914 # cross-component we need to copy the .orig.tar.gz into the new
915 # component too for the same reasons as above.
917 if changes["architecture"].has_key("source") and orig_tar_id != None and \
918 orig_tar_location != "legacy" and orig_tar_location != dsc_location_id:
919 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));
920 ql = q.getresult()[0];
921 old_filename = ql[0] + ql[1];
924 new_filename = utils.poolify (changes["source"], dsc_component) + os.path.basename(old_filename);
925 new_files_id = db_access.get_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
926 if new_files_id == None:
927 utils.copy(old_filename, Cnf["Dir::PoolDir"] + new_filename);
928 new_files_id = db_access.set_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
929 projectB.query("UPDATE dsc_files SET file = %s WHERE source = %s AND file = %s" % (new_files_id, source_id, orig_tar_id));
931 # Install the files into the pool
932 for file in files.keys():
933 if files[file].has_key("byhand"):
935 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
936 destdir = os.path.dirname(destination)
937 utils.move (file, destination)
938 install_bytes = install_bytes + float(files[file]["size"])
940 # Copy the .changes file across for suite which need it.
941 for suite in changes["distribution"].keys():
942 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
943 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
945 projectB.query("COMMIT WORK");
948 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
950 utils.warn("couldn't move changes file '%s' to DONE directory. [Got %s]" % (os.path.basename(changes_filename), sys.exc_type));
952 install_count = install_count + 1;
954 if not Cnf["Dinstall::Options::No-Mail"]:
955 Subst["__SUITE__"] = "";
956 Subst["__SUMMARY__"] = summary;
957 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.installed","r").read());
958 utils.send_mail (mail_message, "")
959 announce (short_summary, 1)
962 #####################################################################################################################
964 def stable_install (changes_filename, summary, short_summary):
965 global install_count, install_bytes, Subst;
967 print "Installing to stable."
969 archive = utils.where_am_i();
971 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
972 projectB.query("BEGIN WORK");
974 # Add the .dsc file to the DB
975 for file in files.keys():
976 if files[file]["type"] == "dsc":
977 package = dsc["source"]
978 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
979 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
982 utils.fubar("[INTERNAL ERROR] couldn't find '%s' (%s) in source table." % (package, version));
983 source_id = ql[0][0];
984 suite_id = db_access.get_suite_id('proposed-updates');
985 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
986 suite_id = db_access.get_suite_id('stable');
987 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
989 # Add the .deb files to the DB
990 for file in files.keys():
991 if files[file]["type"] == "deb":
992 package = files[file]["package"]
993 version = files[file]["version"]
994 architecture = files[file]["architecture"]
995 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))
998 utils.fubar("[INTERNAL ERROR] couldn't find '%s' (%s for %s architecture) in binaries table." % (package, version, architecture));
999 binary_id = ql[0][0];
1000 suite_id = db_access.get_suite_id('proposed-updates');
1001 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
1002 suite_id = db_access.get_suite_id('stable');
1003 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
1005 projectB.query("COMMIT WORK");
1008 utils.move (changes_filename, Cnf["Dir::Morgue"] + '/katie/' + os.path.basename(changes_filename));
1010 # Update the Stable ChangeLog file
1012 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
1013 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
1014 if os.path.exists(new_changelog_filename):
1015 os.unlink (new_changelog_filename);
1017 new_changelog = utils.open_file(new_changelog_filename, 'w');
1018 for file in files.keys():
1019 if files[file]["type"] == "deb":
1020 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
1021 elif utils.re_issource.match(file) != None:
1022 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
1024 new_changelog.write("%s\n" % (file));
1025 chop_changes = re_fdnic.sub("\n", changes["changes"]);
1026 new_changelog.write(chop_changes + '\n\n');
1027 if os.access(changelog_filename, os.R_OK) != 0:
1028 changelog = utils.open_file(changelog_filename, 'r');
1029 new_changelog.write(changelog.read());
1030 new_changelog.close();
1031 if os.access(changelog_filename, os.R_OK) != 0:
1032 os.unlink(changelog_filename);
1033 utils.move(new_changelog_filename, changelog_filename);
1035 install_count = install_count + 1;
1037 if not Cnf["Dinstall::Options::No-Mail"]:
1038 Subst["__SUITE__"] = " into stable";
1039 Subst["__SUMMARY__"] = summary;
1040 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.installed","r").read());
1041 utils.send_mail (mail_message, "")
1042 announce (short_summary, 1)
1044 #####################################################################################################################
1046 def reject (changes_filename, manual_reject_mail_filename):
1049 print "Rejecting.\n"
1051 base_changes_filename = os.path.basename(changes_filename);
1052 reason_filename = re_changes.sub("reason", base_changes_filename);
1053 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
1055 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
1057 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
1059 utils.warn("couldn't reject changes file '%s'. [Got %s]" % (base_changes_filename, sys.exc_type));
1061 for file in files.keys():
1062 if os.path.exists(file):
1064 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
1066 utils.warn("couldn't reject file '%s'. [Got %s]" % (file, sys.exc_type));
1069 # If this is not a manual rejection generate the .reason file and rejection mail message
1070 if manual_reject_mail_filename == "":
1071 if os.path.exists(reject_filename):
1072 os.unlink(reject_filename);
1073 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1074 os.write(fd, reject_message);
1076 Subst["__MANUAL_REJECT_MESSAGE__"] = "";
1077 Subst["__CC__"] = "X-Katie-Rejection: automatic (moo)";
1078 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1079 else: # Have a manual rejection file to use
1080 reject_mail_message = ""; # avoid <undef>'s
1082 # Send the rejection mail if appropriate
1083 if not Cnf["Dinstall::Options::No-Mail"]:
1084 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
1086 ##################################################################
1088 def manual_reject (changes_filename):
1091 # Build up the rejection email
1092 user_email_address = utils.whoami() + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
1093 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
1095 Subst["__MANUAL_REJECT_MESSAGE__"] = manual_reject_message;
1096 Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"];
1097 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1099 # Write the rejection email out as the <foo>.reason file
1100 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
1101 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
1102 if os.path.exists(reject_filename):
1103 os.unlink(reject_filename);
1104 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1105 os.write(fd, reject_mail_message);
1108 # If we weren't given one, spawn an editor so the user can add one in
1109 if manual_reject_message == "":
1110 result = os.system("vi +6 %s" % (reject_filename))
1112 utils.fubar("vi invocation failed for `%s'!" % (reject_filename), result);
1114 # Then process it as if it were an automatic rejection
1115 reject (changes_filename, reject_filename)
1117 #####################################################################################################################
1119 def acknowledge_new (changes_filename, summary):
1120 global new_ack_new, Subst;
1122 changes_filename = os.path.basename(changes_filename);
1124 new_ack_new[changes_filename] = 1;
1126 if new_ack_old.has_key(changes_filename):
1127 print "Ack already sent.";
1130 print "Sending new ack.";
1131 if not Cnf["Dinstall::Options::No-Mail"]:
1132 Subst["__SUMMARY__"] = summary;
1133 new_ack_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.new","r").read());
1134 utils.send_mail(new_ack_message,"");
1136 #####################################################################################################################
1138 def announce (short_summary, action):
1141 # Only do announcements for source uploads with a recent dpkg-dev installed
1142 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
1147 Subst["__SHORT_SUMMARY__"] = short_summary;
1149 for dist in changes["distribution"].keys():
1150 list = Cnf.Find("Suite::%s::Announce" % (dist))
1151 if list == "" or lists_done.has_key(list):
1153 lists_done[list] = 1
1154 summary = summary + "Announcing to %s\n" % (list)
1157 Subst["__ANNOUNCE_LIST_ADDRESS__"] = list;
1158 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.announce","r").read());
1159 utils.send_mail (mail_message, "")
1161 bugs = changes["closes"].keys()
1163 if not nmu.is_an_nmu(changes, dsc):
1164 summary = summary + "Closing bugs: "
1166 summary = summary + "%s " % (bug)
1168 Subst["__BUG_NUMBER__"] = bug;
1169 if changes["distribution"].has_key("stable"):
1170 Subst["__STABLE_WARNING__"] = """
1171 Note that this package is not part of the released stable Debian
1172 distribution. It may have dependencies on other unreleased software,
1173 or other instabilities. Please take care if you wish to install it.
1174 The update will eventually make its way into the next released Debian
1177 Subst["__STABLE_WARNING__"] = "";
1178 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-close","r").read());
1179 utils.send_mail (mail_message, "")
1181 summary = summary + "Setting bugs to severity fixed: "
1182 control_message = ""
1184 summary = summary + "%s " % (bug)
1185 control_message = control_message + "tag %s + fixed\n" % (bug)
1186 if action and control_message != "":
1187 Subst["__CONTROL_MESSAGE__"] = control_message;
1188 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-nmu-fixed","r").read());
1189 utils.send_mail (mail_message, "")
1190 summary = summary + "\n"
1194 ###############################################################################
1196 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1197 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1198 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1199 # processed it during it's checks of -2. If -1 has been deleted or
1200 # otherwise not checked by da-install, the .orig.tar.gz will not have
1201 # been checked at all. To get round this, we force the .orig.tar.gz
1202 # into the .changes structure and reprocess the .changes file.
1204 def process_it (changes_file):
1205 global reprocess, orig_tar_id, orig_tar_location, changes, dsc, dsc_files, files, reject_message;
1207 # Reset some globals
1214 orig_tar_location = "";
1215 legacy_source_untouchable = {};
1216 reject_message = "";
1218 # Absolutize the filename to avoid the requirement of being in the
1219 # same directory as the .changes file.
1220 changes_file = os.path.abspath(changes_file);
1222 # And since handling of installs to stable munges with the CWD;
1223 # save and restore it.
1227 check_signature (changes_file);
1228 check_changes (changes_file);
1237 traceback.print_exc(file=sys.stdout);
1240 update_subst(changes_file);
1241 action(changes_file);
1246 ###############################################################################
1249 global Cnf, projectB, install_bytes, new_ack_old, Subst, nmu
1253 Cnf = apt_pkg.newConfiguration();
1254 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1256 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1257 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1258 ('h',"help","Dinstall::Options::Help"),
1259 ('k',"ack-new","Dinstall::Options::Ack-New"),
1260 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1261 ('n',"no-action","Dinstall::Options::No-Action"),
1262 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1263 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1264 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1265 ('v',"version","Dinstall::Options::Version")];
1267 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1269 if Cnf["Dinstall::Options::Help"]:
1272 if Cnf["Dinstall::Options::Version"]:
1273 print "katie version 0.0000000000";
1276 postgresql_user = None; # Default == Connect as user running program.
1278 # -n/--dry-run invalidates some other options which would involve things happening
1279 if Cnf["Dinstall::Options::No-Action"]:
1280 Cnf["Dinstall::Options::Automatic"] = ""
1281 Cnf["Dinstall::Options::Ack-New"] = ""
1282 postgresql_user = Cnf["DB::ROUser"];
1284 projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1286 db_access.init(Cnf, projectB);
1288 # Check that we aren't going to clash with the daily cron job
1290 if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1291 utils.fubar("Archive maintenance in progress. Try again later.");
1293 # Obtain lock if not in no-action mode
1295 if not Cnf["Dinstall::Options::No-Action"]:
1296 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1297 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1299 # Read in the list of already-acknowledged NEW packages
1300 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1302 for line in new_ack_list.readlines():
1303 new_ack_old[line[:-1]] = 1;
1304 new_ack_list.close();
1306 # Initialize the substitution template mapping global
1308 Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"];
1309 Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"];
1310 bcc = "X-Katie: $Revision: 1.47 $"
1311 if Cnf.has_key("Dinstall::Bcc"):
1312 Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"]);
1314 Subst["__BCC__"] = bcc;
1315 Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"];
1316 Subst["__KATIE_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"];
1318 # Read in the group-maint override file
1321 # Sort the .changes files so that we process sourceful ones first
1322 changes_files.sort(utils.changes_compare);
1324 # Process the changes files
1325 for changes_file in changes_files:
1326 print "\n" + changes_file;
1327 process_it (changes_file);
1331 if install_count > 1:
1333 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1335 # Write out the list of already-acknowledged NEW packages
1336 if Cnf["Dinstall::Options::Ack-New"]:
1337 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1338 for i in new_ack_new.keys():
1339 new_ack_list.write(i+'\n')
1340 new_ack_list.close()
1343 if __name__ == '__main__':