3 # Installs Debian packaes
4 # Copyright (C) 2000, 2001 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.55 2001-07-13 19:47:31 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, logging
41 ###############################################################################
43 re_isanum = re.compile (r"^\d+$");
44 re_changes = re.compile (r"changes$");
45 re_default_answer = re.compile(r"\[(.*)\]");
46 re_fdnic = re.compile("\n\n");
47 re_bad_diff = re.compile("^[\-\+][\-\+][\-\+] /dev/null");
48 re_bin_only_nmu_of_mu = re.compile("\.\d+\.\d+$");
49 re_bin_only_nmu_of_nmu = re.compile("\.\d+$");
51 #########################################################################################
69 orig_tar_location = "";
70 legacy_source_untouchable = {};
74 #########################################################################################
76 def usage (exit_code):
77 print """Usage: dinstall [OPTION]... [CHANGES]...
78 -a, --automatic automatic run
79 -D, --debug=VALUE turn on debugging
80 -h, --help show this help and exit.
81 -k, --ack-new acknowledge new packages !! for cron.daily only !!
82 -m, --manual-reject=MSG manual reject with `msg'
83 -n, --no-action don't do anything
84 -p, --no-lock don't check lockfile !! for cron.daily only !!
85 -u, --distribution=DIST override distribution to `dist'
86 -v, --version display the version number and exit"""
89 #########################################################################################
91 def check_signature (filename):
94 (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))
96 reject_message = reject_message + "Rejected: GPG signature check failed on `%s'.\n%s\n" % (os.path.basename(filename), output)
100 ######################################################################################################
103 # Read in the group maintainer override file
105 self.group_maint = {};
106 if Cnf.get("Dinstall::GroupOverrideFilename"):
107 filename = Cnf["Dir::OverrideDir"] + Cnf["Dinstall::GroupOverrideFilename"];
108 file = utils.open_file(filename, 'r');
109 for line in file.readlines():
110 line = string.strip(utils.re_comments.sub('', line));
112 self.group_maint[line] = 1;
115 def is_an_nmu (self, changes, dsc):
116 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
117 # changes["changedbyname"] == dsc_name is probably never true, but better safe than sorry
118 if dsc_name == changes["maintainername"] and (changes["changedby822"] == "" or changes["changedbyname"] == dsc_name):
121 if dsc.has_key("uploaders"):
122 uploaders = string.split(dsc["uploaders"], ",");
125 (rfc822, name, email) = utils.fix_maintainer (string.strip(i));
126 uploadernames[name] = "";
127 if uploadernames.has_key(changes["changedbyname"]):
130 # Some group maintained packages (e.g. Debian QA) are never NMU's
131 if self.group_maint.has_key(changes["maintainername"]):
136 ######################################################################################################
138 # Ensure that source exists somewhere in the archive for the binary
139 # upload being processed.
141 # (1) exact match => 1.0-3
142 # (2) Bin-only NMU of an MU => 1.0-3.0.1
143 # (3) Bin-only NMU of a sourceful-NMU => 1.0-3.1.1
145 def source_exists (package, source_version):
146 q = projectB.query("SELECT s.version FROM source s WHERE s.source = '%s'" % (package));
148 # Reduce the query results to a list of version numbers
149 ql = map(lambda x: x[0], q.getresult());
152 if ql.count(source_version):
156 orig_source_version = re_bin_only_nmu_of_mu.sub('', source_version);
157 if ql.count(orig_source_version):
161 orig_source_version = re_bin_only_nmu_of_nmu.sub('', source_version);
162 if ql.count(orig_source_version):
168 ######################################################################################################
170 # See if a given package is in the override table
172 def in_override_p (package, component, suite, binary_type, file):
175 if binary_type == "": # must be source
180 # Override suite name; used for example with proposed-updates
181 if Cnf.Find("Suite::%s::OverrideSuite" % (suite)) != "":
182 suite = Cnf["Suite::%s::OverrideSuite" % (suite)];
184 # Avoid <undef> on unknown distributions
185 suite_id = db_access.get_suite_id(suite);
188 component_id = db_access.get_component_id(component);
189 type_id = db_access.get_override_type_id(type);
191 # FIXME: nasty non-US speficic hack
192 if string.lower(component[:7]) == "non-us/":
193 component = component[7:];
195 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"
196 % (package, suite_id, component_id, type_id));
197 result = q.getresult();
198 # If checking for a source package fall back on the binary override type
199 if type == "dsc" and not result:
200 type_id = db_access.get_override_type_id("deb");
201 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"
202 % (package, suite_id, component_id, type_id));
203 result = q.getresult();
205 # Remember the section and priority so we can check them later if appropriate
207 files[file]["override section"] = result[0][0];
208 files[file]["override priority"] = result[0][1];
212 #####################################################################################################################
214 def check_changes(filename):
215 global reject_message, changes, files
217 # Default in case we bail out
218 changes["maintainer822"] = Cnf["Dinstall::MyEmailAddress"];
219 changes["changedby822"] = Cnf["Dinstall::MyEmailAddress"];
220 changes["architecture"] = {};
222 # Parse the .changes field into a dictionary
224 changes = utils.parse_changes(filename, 0)
225 except utils.cant_open_exc:
226 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (filename)
228 except utils.changes_parse_error_exc, line:
229 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
232 # Parse the Files field from the .changes into another dictionary
234 files = utils.build_file_list(changes, "");
235 except utils.changes_parse_error_exc, line:
236 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line);
237 except utils.nk_format_exc, format:
238 reject_message = reject_message + "Rejected: unknown format '%s' of changes file '%s'.\n" % (format, filename);
241 # Check for mandatory fields
242 for i in ("source", "binary", "architecture", "version", "distribution", "maintainer", "files"):
243 if not changes.has_key(i):
244 reject_message = reject_message + "Rejected: Missing field `%s' in changes file.\n" % (i)
245 return 0 # Avoid <undef> errors during later tests
247 # Override the Distribution: field if appropriate
248 if Options["Override-Distribution"] != "":
249 reject_message = reject_message + "Warning: Distribution was overriden from %s to %s.\n" % (changes["distribution"], Options["Override-Distribution"])
250 changes["distribution"] = Options["Override-Distribution"]
252 # Split multi-value fields into a lower-level dictionary
253 for i in ("architecture", "distribution", "binary", "closes"):
254 o = changes.get(i, "")
258 for j in string.split(o):
261 # Fix the Maintainer: field to be RFC822 compatible
262 (changes["maintainer822"], changes["maintainername"], changes["maintaineremail"]) = utils.fix_maintainer (changes["maintainer"])
264 # Fix the Changed-By: field to be RFC822 compatible; if it exists.
265 (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]) = utils.fix_maintainer(changes.get("changed-by",""));
267 # Ensure all the values in Closes: are numbers
268 if changes.has_key("closes"):
269 for i in changes["closes"].keys():
270 if re_isanum.match (i) == None:
271 reject_message = reject_message + "Rejected: `%s' from Closes field isn't a number.\n" % (i)
273 # Ensure there _is_ a target distribution
274 if changes["distribution"].keys() == []:
275 reject_message = reject_message + "Rejected: huh? Distribution field is empty in changes file.\n";
277 # Map frozen to unstable if frozen doesn't exist
278 if changes["distribution"].has_key("frozen") and not Cnf.has_key("Suite::Frozen"):
279 del changes["distribution"]["frozen"]
280 changes["distribution"]["unstable"] = 1;
281 reject_message = reject_message + "Mapping frozen to unstable.\n"
283 # Map testing to unstable
284 if changes["distribution"].has_key("testing"):
285 if len(changes["distribution"].keys()) > 1:
286 del changes["distribution"]["testing"];
287 reject_message = reject_message + "Warning: Ignoring testing as a target suite.\n";
289 reject_message = reject_message + "Rejected: invalid distribution 'testing'.\n";
291 # Ensure target distributions exist
292 for i in changes["distribution"].keys():
293 if not Cnf.has_key("Suite::%s" % (i)):
294 reject_message = reject_message + "Rejected: Unknown distribution `%s'.\n" % (i)
296 # Map unreleased arches from stable to unstable
297 if changes["distribution"].has_key("stable"):
298 for i in changes["architecture"].keys():
299 if not Cnf.has_key("Suite::Stable::Architectures::%s" % (i)):
300 reject_message = reject_message + "Mapping stable to unstable for unreleased arch `%s'.\n" % (i)
301 del changes["distribution"]["stable"]
302 changes["distribution"]["unstable"] = 1;
304 # Map arches not being released from frozen to unstable
305 if changes["distribution"].has_key("frozen"):
306 for i in changes["architecture"].keys():
307 if not Cnf.has_key("Suite::Frozen::Architectures::%s" % (i)):
308 reject_message = reject_message + "Mapping frozen to unstable for non-releasing arch `%s'.\n" % (i)
309 del changes["distribution"]["frozen"]
310 changes["distribution"]["unstable"] = 1;
312 # Handle uploads to stable
313 if changes["distribution"].has_key("stable"):
314 # If running from within proposed-updates; assume an install to stable
315 if string.find(os.getcwd(), 'proposed-updates') != -1:
316 # FIXME: should probably remove anything that != stable
317 for i in ("frozen", "unstable"):
318 if changes["distribution"].has_key(i):
319 reject_message = reject_message + "Removing %s from distribution list.\n" % (i)
320 del changes["distribution"][i]
321 changes["stable upload"] = 1;
322 # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool
323 file = files.keys()[0];
324 if os.access(file, os.R_OK) == 0:
325 pool_dir = Cnf["Dir::PoolDir"] + '/' + utils.poolify(changes["source"], files[file]["component"]);
327 # Otherwise (normal case) map stable to updates
329 reject_message = reject_message + "Mapping stable to updates.\n";
330 del changes["distribution"]["stable"];
331 changes["distribution"]["proposed-updates"] = 1;
333 # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
334 changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
335 changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
337 if string.find(reject_message, "Rejected:") != -1:
343 global reject_message
345 archive = utils.where_am_i();
347 for file in files.keys():
348 # Check the file is readable
349 if os.access(file,os.R_OK) == 0:
350 if os.path.exists(file):
351 reject_message = reject_message + "Rejected: Can't read `%s'. [permission denied]\n" % (file)
353 reject_message = reject_message + "Rejected: Can't read `%s'. [file not found]\n" % (file)
355 files[file]["type"] = "unreadable";
357 # If it's byhand skip remaining checks
358 if files[file]["section"] == "byhand":
359 files[file]["byhand"] = 1;
360 files[file]["type"] = "byhand";
361 # Checks for a binary package...
362 elif utils.re_isadeb.match(file) != None:
363 files[file]["type"] = "deb";
365 # Extract package information using dpkg-deb
367 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
369 reject_message = reject_message + "Rejected: %s: debExtractControl() raised %s.\n" % (file, sys.exc_type);
370 # Can't continue, none of the checks on control would work.
373 # Check for mandatory fields
374 if control.Find("Package") == None:
375 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
376 if control.Find("Architecture") == None:
377 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
378 if control.Find("Version") == None:
379 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
381 # Ensure the package name matches the one give in the .changes
382 if not changes["binary"].has_key(control.Find("Package", "")):
383 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
385 # Validate the architecture
386 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
387 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
389 # Check the architecture matches the one given in the .changes
390 if not changes["architecture"].has_key(control.Find("Architecture", "")):
391 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
392 # Check the section & priority match those given in the .changes (non-fatal)
393 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
394 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"])
395 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
396 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"])
398 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
400 files[file]["package"] = control.Find("Package");
401 files[file]["architecture"] = control.Find("Architecture");
402 files[file]["version"] = control.Find("Version");
403 files[file]["maintainer"] = control.Find("Maintainer", "");
404 if file[-5:] == ".udeb":
405 files[file]["dbtype"] = "udeb";
406 elif file[-4:] == ".deb":
407 files[file]["dbtype"] = "deb";
409 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
410 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
411 files[file]["source"] = control.Find("Source", "");
412 if files[file]["source"] == "":
413 files[file]["source"] = files[file]["package"];
414 # Get the source version
415 source = files[file]["source"];
417 if string.find(source, "(") != -1:
418 m = utils.re_extract_src_version.match(source)
420 source_version = m.group(2)
421 if not source_version:
422 source_version = files[file]["version"];
423 files[file]["source package"] = source;
424 files[file]["source version"] = source_version;
426 # Checks for a source package...
428 m = utils.re_issource.match(file)
430 files[file]["package"] = m.group(1)
431 files[file]["version"] = m.group(2)
432 files[file]["type"] = m.group(3)
434 # Ensure the source package name matches the Source filed in the .changes
435 if changes["source"] != files[file]["package"]:
436 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
438 # Ensure the source version matches the version in the .changes file
439 if files[file]["type"] == "orig.tar.gz":
440 changes_version = changes["chopversion2"]
442 changes_version = changes["chopversion"]
443 if changes_version != files[file]["version"]:
444 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
446 # Ensure the .changes lists source in the Architecture field
447 if not changes["architecture"].has_key("source"):
448 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
450 # Check the signature of a .dsc file
451 if files[file]["type"] == "dsc":
452 check_signature(file)
454 files[file]["fullname"] = file
455 files[file]["architecture"] = "source";
457 # Not a binary or source package? Assume byhand...
459 files[file]["byhand"] = 1;
460 files[file]["type"] = "byhand";
462 files[file]["oldfiles"] = {}
463 for suite in changes["distribution"].keys():
465 if files[file].has_key("byhand"):
468 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
469 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
472 # See if the package is NEW
473 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
474 files[file]["new"] = 1
476 if files[file]["type"] == "deb":
477 # Find any old binary packages
478 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' OR a.arch_string = 'all') AND ba.bin = b.id AND ba.suite = s.id AND b.architecture = a.id AND f.location = l.id AND l.component = c.id AND b.file = f.id"
479 % (files[file]["package"], suite, files[file]["architecture"]))
480 oldfiles = q.dictresult()
481 for oldfile in oldfiles:
482 files[file]["oldfiles"][suite] = oldfile
483 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
484 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
485 reject_message = reject_message + "Rejected: %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
486 # Check for existing copies of the file
487 if not changes.has_key("stable upload"):
488 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"]))
489 if q.getresult() != []:
490 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
492 # Check for existent source
493 # FIXME: this is no longer per suite
494 if changes["architecture"].has_key("source"):
495 source_version = files[file]["source version"];
496 if source_version != changes["version"]:
497 reject_message = reject_message + "Rejected: source version (%s) for %s doesn't match changes version %s.\n" % (files[file]["source version"], file, changes["version"]);
499 if not source_exists (files[file]["source package"], source_version):
500 reject_message = reject_message + "Rejected: no source found for %s %s (%s).\n" % (files[file]["source package"], source_version, file);
502 # Find any old .dsc files
503 elif files[file]["type"] == "dsc":
504 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"
505 % (files[file]["package"], suite))
506 oldfiles = q.dictresult()
507 if len(oldfiles) >= 1:
508 files[file]["oldfiles"][suite] = oldfiles[0]
510 # Validate the component
511 component = files[file]["component"];
512 component_id = db_access.get_component_id(component);
513 if component_id == -1:
514 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
517 # Validate the priority
518 if string.find(files[file]["priority"],'/') != -1:
519 reject_message = reject_message + "Rejected: file '%s' has invalid priority '%s' [contains '/'].\n" % (file, files[file]["priority"]);
521 # Check the md5sum & size against existing files (if any)
522 location = Cnf["Dir::PoolDir"];
523 files[file]["location id"] = db_access.get_location_id (location, component, archive);
525 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
526 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
528 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
530 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
531 files[file]["files id"] = files_id
533 # Check for packages that have moved from one component to another
534 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
535 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
538 if string.find(reject_message, "Rejected:") != -1:
543 ###############################################################################
546 global dsc, dsc_files, reject_message, reprocess, orig_tar_id, orig_tar_location, legacy_source_untouchable;
548 for file in files.keys():
549 if files[file]["type"] == "dsc":
551 dsc = utils.parse_changes(file, 1)
552 except utils.cant_open_exc:
553 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (file)
555 except utils.changes_parse_error_exc, line:
556 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (file, line)
558 except utils.invalid_dsc_format_exc, line:
559 reject_message = reject_message + "Rejected: syntax error in .dsc file '%s', line %s.\n" % (file, line)
562 dsc_files = utils.build_file_list(dsc, 1)
563 except utils.no_files_exc:
564 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
566 except utils.changes_parse_error_exc, line:
567 reject_message = reject_message + "Rejected: error parsing .dsc file '%s', can't grok: %s.\n" % (file, line);
570 # Enforce mandatory fields
571 for i in ("format", "source", "version", "binary", "maintainer", "architecture", "files"):
572 if not dsc.has_key(i):
573 reject_message = reject_message + "Rejected: Missing field `%s' in dsc file.\n" % (i)
575 # The dpkg maintainer from hell strikes again! Bumping the
576 # version number of the .dsc breaks extraction by stable's
578 if dsc["format"] != "1.0":
579 reject_message = reject_message + """Rejected: [dpkg-sucks] source package was produced by a broken version
580 of dpkg-dev 1.9.1{3,4}; please rebuild with >= 1.9.15 version
584 # Ensure the version number in the .dsc matches the version number in the .changes
585 epochless_dsc_version = utils.re_no_epoch.sub('', dsc.get("version"));
586 changes_version = files[file]["version"];
587 if epochless_dsc_version != files[file]["version"]:
588 reject_message = reject_message + "Rejected: version ('%s') in .dsc does not match version ('%s') in .changes\n" % (epochless_dsc_version, changes_version);
590 # Try and find all files mentioned in the .dsc. This has
591 # to work harder to cope with the multiple possible
592 # locations of an .orig.tar.gz.
593 for dsc_file in dsc_files.keys():
594 if files.has_key(dsc_file):
595 actual_md5 = files[dsc_file]["md5sum"];
596 actual_size = int(files[dsc_file]["size"]);
597 found = "%s in incoming" % (dsc_file)
598 # Check the file does not already exist in the archive
599 if not changes.has_key("stable upload"):
600 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));
602 # "It has not broken them. It has fixed a
603 # brokenness. Your crappy hack exploited a
604 # bug in the old dinstall.
606 # "(Come on! I thought it was always obvious
607 # that one just doesn't release different
608 # files with the same name and version.)"
609 # -- ajk@ on d-devel@l.d.o
611 if q.getresult() != []:
612 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
613 elif dsc_file[-12:] == ".orig.tar.gz":
615 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));
619 # Unfortunately, we make get more than one match
620 # here if, for example, the package was in potato
621 # but had a -sa upload in woody. So we need to a)
622 # choose the right one and b) mark all wrong ones
623 # as excluded from the source poolification (to
624 # avoid file overwrites).
626 x = ql[0]; # default to something sane in case we don't match any or have only one
630 old_file = i[0] + i[1];
631 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
632 actual_size = os.stat(old_file)[stat.ST_SIZE];
633 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
636 legacy_source_untouchable[i[3]] = "";
638 old_file = x[0] + x[1];
639 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
640 actual_size = os.stat(old_file)[stat.ST_SIZE];
643 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
646 if suite_type == "legacy" or suite_type == "legacy-mixed":
647 orig_tar_location = "legacy";
649 orig_tar_location = x[4];
651 # Not there? Check in Incoming...
652 # [See comment above process_it() for explanation
653 # of why this is necessary...]
654 if os.path.exists(dsc_file):
655 files[dsc_file] = {};
656 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
657 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
658 files[dsc_file]["section"] = files[file]["section"];
659 files[dsc_file]["priority"] = files[file]["priority"];
660 files[dsc_file]["component"] = files[file]["component"];
661 files[dsc_file]["type"] = "orig.tar.gz";
665 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);
668 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming.\n" % (file, dsc_file);
670 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
671 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
672 if actual_size != int(dsc_files[dsc_file]["size"]):
673 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
675 if string.find(reject_message, "Rejected:") != -1:
680 ###############################################################################
682 # Some cunning stunt broke dpkg-source in dpkg 1.8{,.1}; detect the
683 # resulting bad source packages and reject them.
685 # Even more amusingly the fix in 1.8.1.1 didn't actually fix the
686 # problem just changed the symptoms.
689 global dsc, dsc_files, reject_message, reprocess;
691 for filename in files.keys():
692 if files[filename]["type"] == "diff.gz":
693 file = gzip.GzipFile(filename, 'r');
694 for line in file.readlines():
695 if re_bad_diff.search(line):
696 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";
699 if string.find(reject_message, "Rejected:") != -1:
704 ###############################################################################
706 def check_md5sums ():
707 global reject_message;
709 for file in files.keys():
711 file_handle = utils.open_file(file,"r");
712 except utils.cant_open_exc:
715 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
716 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
718 def check_override ():
721 # Only check section & priority on sourceful uploads
722 if not changes["architecture"].has_key("source"):
726 for file in files.keys():
727 if not files[file].has_key("new") and files[file]["type"] == "deb":
728 section = files[file]["section"];
729 override_section = files[file]["override section"];
730 if section != override_section and section != "-":
731 # Ignore this; it's a common mistake and not worth whining about
732 if string.lower(section) == "non-us/main" and string.lower(override_section) == "non-us":
734 summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
735 priority = files[file]["priority"];
736 override_priority = files[file]["override priority"];
737 if priority != override_priority and priority != "-":
738 summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
743 Subst["__SUMMARY__"] = summary;
744 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.override-disparity","r").read());
745 utils.send_mail (mail_message, "")
747 #####################################################################################################################
749 # Set up the per-package template substitution mappings
751 def update_subst (changes_filename):
754 # If katie crashed out in the right place, architecture may still be a string.
755 if not changes.has_key("architecture") or not isinstance(changes["architecture"], DictType):
756 changes["architecture"] = { "Unknown" : "" };
757 # and maintainer822 may not exist.
758 if not changes.has_key("maintainer822"):
759 changes["maintainer822"] = Cnf["Dinstall::MyEmailAddress"];
761 Subst["__ARCHITECTURE__"] = string.join(changes["architecture"].keys(), ' ' );
762 Subst["__CHANGES_FILENAME__"] = os.path.basename(changes_filename);
763 Subst["__FILE_CONTENTS__"] = changes.get("filecontents", "");
765 # For source uploads the Changed-By field wins; otherwise Maintainer wins.
766 if changes["architecture"].has_key("source") and changes["changedby822"] != "" and (changes["changedby822"] != changes["maintainer822"]):
767 Subst["__MAINTAINER_FROM__"] = changes["changedby822"];
768 Subst["__MAINTAINER_TO__"] = changes["changedby822"] + ", " + changes["maintainer822"];
769 Subst["__MAINTAINER__"] = changes.get("changed-by", "Unknown");
771 Subst["__MAINTAINER_FROM__"] = changes["maintainer822"];
772 Subst["__MAINTAINER_TO__"] = changes["maintainer822"];
773 Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown");
775 Subst["__REJECT_MESSAGE__"] = reject_message;
776 Subst["__SOURCE__"] = changes.get("source", "Unknown");
777 Subst["__VERSION__"] = changes.get("version", "Unknown");
779 #####################################################################################################################
781 def action (changes_filename):
782 byhand = confirm = suites = summary = new = "";
784 # changes["distribution"] may not exist in corner cases
785 # (e.g. unreadable changes files)
786 if not changes.has_key("distribution") or not isinstance(changes["distribution"], DictType):
787 changes["distribution"] = {};
789 for suite in changes["distribution"].keys():
790 if Cnf.has_key("Suite::%s::Confirm"):
791 confirm = confirm + suite + ", "
792 suites = suites + suite + ", "
793 confirm = confirm[:-2]
796 for file in files.keys():
797 if files[file].has_key("byhand"):
799 summary = summary + file + " byhand\n"
800 elif files[file].has_key("new"):
802 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
803 if files[file].has_key("othercomponents"):
804 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
805 if files[file]["type"] == "deb":
806 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
808 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
809 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
810 summary = summary + file + "\n to " + destination + "\n"
812 short_summary = summary;
814 # This is for direport's benefit...
815 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
817 if confirm or byhand or new:
818 summary = summary + "Changes: " + f;
820 summary = summary + announce (short_summary, 0)
822 (prompt, answer) = ("", "XXX")
823 if Options["No-Action"] or Options["Automatic"]:
826 if string.find(reject_message, "Rejected") != -1:
828 modified_time = time.time()-os.path.getmtime(changes_filename);
829 except: # i.e. ignore errors like 'file does not exist';
831 if modified_time < 86400:
832 print "SKIP (too new)\n" + reject_message,;
833 prompt = "[S]kip, Manual reject, Quit ?";
835 print "REJECT\n" + reject_message,;
836 prompt = "[R]eject, Manual reject, Skip, Quit ?";
837 if Options["Automatic"]:
840 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
841 prompt = "[S]kip, New ack, Manual reject, Quit ?";
842 if Options["Automatic"] and Options["Ack-New"]:
845 print "BYHAND\n" + reject_message + summary,;
846 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
848 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
849 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
851 print "INSTALL\n" + reject_message + summary,;
852 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
853 if Options["Automatic"]:
856 while string.find(prompt, answer) == -1:
858 answer = utils.our_raw_input()
859 m = re_default_answer.match(prompt)
862 answer = string.upper(answer[:1])
865 reject (changes_filename, "");
867 manual_reject (changes_filename);
869 install (changes_filename, summary, short_summary);
871 acknowledge_new (changes_filename, summary);
875 #####################################################################################################################
877 def install (changes_filename, summary, short_summary):
878 global install_count, install_bytes, Subst;
880 # Stable uploads are a special case
881 if changes.has_key("stable upload"):
882 stable_install (changes_filename, summary, short_summary);
887 Logger.log(["installing changes",changes_filename]);
889 archive = utils.where_am_i();
891 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
892 projectB.query("BEGIN WORK");
894 # Add the .dsc file to the DB
895 for file in files.keys():
896 if files[file]["type"] == "dsc":
897 package = dsc["source"]
898 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
899 maintainer = dsc["maintainer"]
900 maintainer = string.replace(maintainer, "'", "\\'")
901 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
902 filename = files[file]["pool name"] + file;
903 dsc_location_id = files[file]["location id"];
904 if not files[file]["files id"]:
905 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
906 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
907 % (package, version, maintainer_id, files[file]["files id"]))
909 for suite in changes["distribution"].keys():
910 suite_id = db_access.get_suite_id(suite);
911 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
913 # Add the source files to the DB (files and dsc_files)
914 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
915 for dsc_file in dsc_files.keys():
916 filename = files[file]["pool name"] + dsc_file;
917 # If the .orig.tar.gz is already in the pool, it's
918 # files id is stored in dsc_files by check_dsc().
919 files_id = dsc_files[dsc_file].get("files id", None);
921 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
922 # FIXME: needs to check for -1/-2 and or handle exception
924 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
925 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
927 # Add the .deb files to the DB
928 for file in files.keys():
929 if files[file]["type"] == "deb":
930 package = files[file]["package"]
931 version = files[file]["version"]
932 maintainer = files[file]["maintainer"]
933 maintainer = string.replace(maintainer, "'", "\\'")
934 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
935 architecture = files[file]["architecture"]
936 architecture_id = db_access.get_architecture_id (architecture);
937 type = files[file]["dbtype"];
938 dsc_component = files[file]["component"]
939 source = files[file]["source package"]
940 source_version = files[file]["source version"];
941 filename = files[file]["pool name"] + file;
942 if not files[file]["files id"]:
943 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
944 source_id = db_access.get_source_id (source, source_version);
946 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
947 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
949 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
950 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
951 for suite in changes["distribution"].keys():
952 suite_id = db_access.get_suite_id(suite);
953 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
955 # If the .orig.tar.gz is in a legacy directory we need to poolify
956 # it, so that apt-get source (and anything else that goes by the
957 # "Directory:" field in the Sources.gz file) works.
958 if orig_tar_id != None and orig_tar_location == "legacy":
959 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));
962 # Is this an old upload superseded by a newer -sa upload? (See check_dsc() for details)
963 if legacy_source_untouchable.has_key(qid["files_id"]):
965 # First move the files to the new location
966 legacy_filename = qid["path"]+qid["filename"];
967 pool_location = utils.poolify (changes["source"], files[file]["component"]);
968 pool_filename = pool_location + os.path.basename(qid["filename"]);
969 destination = Cnf["Dir::PoolDir"] + pool_location
970 utils.move(legacy_filename, destination);
971 # Then Update the DB's files table
972 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
974 # If this is a sourceful diff only upload that is moving non-legacy
975 # cross-component we need to copy the .orig.tar.gz into the new
976 # component too for the same reasons as above.
978 if changes["architecture"].has_key("source") and orig_tar_id != None and \
979 orig_tar_location != "legacy" and orig_tar_location != dsc_location_id:
980 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));
981 ql = q.getresult()[0];
982 old_filename = ql[0] + ql[1];
985 new_filename = utils.poolify (changes["source"], dsc_component) + os.path.basename(old_filename);
986 new_files_id = db_access.get_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
987 if new_files_id == None:
988 utils.copy(old_filename, Cnf["Dir::PoolDir"] + new_filename);
989 new_files_id = db_access.set_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
990 projectB.query("UPDATE dsc_files SET file = %s WHERE source = %s AND file = %s" % (new_files_id, source_id, orig_tar_id));
992 # Install the files into the pool
993 for file in files.keys():
994 if files[file].has_key("byhand"):
996 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
997 destdir = os.path.dirname(destination)
998 utils.move (file, destination)
999 Logger.log(["installed", file, files[file]["type"], files[file]["size"], files[file]["architecture"]]);
1000 install_bytes = install_bytes + float(files[file]["size"])
1002 # Copy the .changes file across for suite which need it.
1003 for suite in changes["distribution"].keys():
1004 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
1005 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
1007 projectB.query("COMMIT WORK");
1010 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
1012 utils.warn("couldn't move changes file '%s' to DONE directory. [Got %s]" % (os.path.basename(changes_filename), sys.exc_type));
1014 install_count = install_count + 1;
1016 if not Options["No-Mail"]:
1017 Subst["__SUITE__"] = "";
1018 Subst["__SUMMARY__"] = summary;
1019 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.installed","r").read());
1020 utils.send_mail (mail_message, "")
1021 announce (short_summary, 1)
1024 #####################################################################################################################
1026 def stable_install (changes_filename, summary, short_summary):
1027 global install_count, install_bytes, Subst;
1029 print "Installing to stable."
1031 archive = utils.where_am_i();
1033 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
1034 projectB.query("BEGIN WORK");
1036 # Add the .dsc file to the DB
1037 for file in files.keys():
1038 if files[file]["type"] == "dsc":
1039 package = dsc["source"]
1040 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
1041 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
1044 utils.fubar("[INTERNAL ERROR] couldn't find '%s' (%s) in source table." % (package, version));
1045 source_id = ql[0][0];
1046 suite_id = db_access.get_suite_id('proposed-updates');
1047 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
1048 suite_id = db_access.get_suite_id('stable');
1049 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
1051 # Add the .deb files to the DB
1052 for file in files.keys():
1053 if files[file]["type"] == "deb":
1054 package = files[file]["package"]
1055 version = files[file]["version"]
1056 architecture = files[file]["architecture"]
1057 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))
1060 utils.fubar("[INTERNAL ERROR] couldn't find '%s' (%s for %s architecture) in binaries table." % (package, version, architecture));
1061 binary_id = ql[0][0];
1062 suite_id = db_access.get_suite_id('proposed-updates');
1063 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
1064 suite_id = db_access.get_suite_id('stable');
1065 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
1067 projectB.query("COMMIT WORK");
1070 utils.move (changes_filename, Cnf["Dir::Morgue"] + '/katie/' + os.path.basename(changes_filename));
1072 # Update the Stable ChangeLog file
1074 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
1075 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
1076 if os.path.exists(new_changelog_filename):
1077 os.unlink (new_changelog_filename);
1079 new_changelog = utils.open_file(new_changelog_filename, 'w');
1080 for file in files.keys():
1081 if files[file]["type"] == "deb":
1082 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
1083 elif utils.re_issource.match(file) != None:
1084 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
1086 new_changelog.write("%s\n" % (file));
1087 chop_changes = re_fdnic.sub("\n", changes["changes"]);
1088 new_changelog.write(chop_changes + '\n\n');
1089 if os.access(changelog_filename, os.R_OK) != 0:
1090 changelog = utils.open_file(changelog_filename, 'r');
1091 new_changelog.write(changelog.read());
1092 new_changelog.close();
1093 if os.access(changelog_filename, os.R_OK) != 0:
1094 os.unlink(changelog_filename);
1095 utils.move(new_changelog_filename, changelog_filename);
1097 install_count = install_count + 1;
1099 if not Options["No-Mail"]:
1100 Subst["__SUITE__"] = " into stable";
1101 Subst["__SUMMARY__"] = summary;
1102 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.installed","r").read());
1103 utils.send_mail (mail_message, "")
1104 announce (short_summary, 1)
1106 #####################################################################################################################
1108 def reject (changes_filename, manual_reject_mail_filename):
1111 print "Rejecting.\n"
1113 base_changes_filename = os.path.basename(changes_filename);
1114 reason_filename = re_changes.sub("reason", base_changes_filename);
1115 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
1117 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
1119 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
1121 utils.warn("couldn't reject changes file '%s'. [Got %s]" % (base_changes_filename, sys.exc_type));
1123 for file in files.keys():
1124 if os.path.exists(file):
1126 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
1128 utils.warn("couldn't reject file '%s'. [Got %s]" % (file, sys.exc_type));
1131 # If this is not a manual rejection generate the .reason file and rejection mail message
1132 if manual_reject_mail_filename == "":
1133 if os.path.exists(reject_filename):
1134 os.unlink(reject_filename);
1135 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1136 os.write(fd, reject_message);
1138 Subst["__MANUAL_REJECT_MESSAGE__"] = "";
1139 Subst["__CC__"] = "X-Katie-Rejection: automatic (moo)";
1140 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1141 else: # Have a manual rejection file to use
1142 reject_mail_message = ""; # avoid <undef>'s
1144 # Send the rejection mail if appropriate
1145 if not Options["No-Mail"]:
1146 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
1148 Logger.log(["rejected", changes_filename]);
1150 ##################################################################
1152 def manual_reject (changes_filename):
1155 # Build up the rejection email
1156 user_email_address = utils.whoami() + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
1157 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
1159 Subst["__MANUAL_REJECT_MESSAGE__"] = manual_reject_message;
1160 Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"];
1161 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1163 # Write the rejection email out as the <foo>.reason file
1164 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
1165 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
1166 if os.path.exists(reject_filename):
1167 os.unlink(reject_filename);
1168 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1169 os.write(fd, reject_mail_message);
1172 # If we weren't given one, spawn an editor so the user can add one in
1173 if manual_reject_message == "":
1174 result = os.system("vi +6 %s" % (reject_filename))
1176 utils.fubar("vi invocation failed for `%s'!" % (reject_filename), result);
1178 # Then process it as if it were an automatic rejection
1179 reject (changes_filename, reject_filename)
1181 #####################################################################################################################
1183 def acknowledge_new (changes_filename, summary):
1184 global new_ack_new, Subst;
1186 changes_filename = os.path.basename(changes_filename);
1188 new_ack_new[changes_filename] = 1;
1190 if new_ack_old.has_key(changes_filename):
1191 print "Ack already sent.";
1194 print "Sending new ack.";
1195 if not Options["No-Mail"]:
1196 Subst["__SUMMARY__"] = summary;
1197 new_ack_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.new","r").read());
1198 utils.send_mail(new_ack_message,"");
1200 #####################################################################################################################
1202 def announce (short_summary, action):
1205 # Only do announcements for source uploads with a recent dpkg-dev installed
1206 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
1211 Subst["__SHORT_SUMMARY__"] = short_summary;
1213 for dist in changes["distribution"].keys():
1214 list = Cnf.Find("Suite::%s::Announce" % (dist))
1215 if list == "" or lists_done.has_key(list):
1217 lists_done[list] = 1
1218 summary = summary + "Announcing to %s\n" % (list)
1221 Subst["__ANNOUNCE_LIST_ADDRESS__"] = list;
1222 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.announce","r").read());
1223 utils.send_mail (mail_message, "")
1225 bugs = changes["closes"].keys()
1227 if not nmu.is_an_nmu(changes, dsc):
1228 summary = summary + "Closing bugs: "
1230 summary = summary + "%s " % (bug)
1232 Subst["__BUG_NUMBER__"] = bug;
1233 if changes["distribution"].has_key("stable"):
1234 Subst["__STABLE_WARNING__"] = """
1235 Note that this package is not part of the released stable Debian
1236 distribution. It may have dependencies on other unreleased software,
1237 or other instabilities. Please take care if you wish to install it.
1238 The update will eventually make its way into the next released Debian
1241 Subst["__STABLE_WARNING__"] = "";
1242 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-close","r").read());
1243 utils.send_mail (mail_message, "")
1245 Logger.log(["closing bugs"]+bugs);
1247 summary = summary + "Setting bugs to severity fixed: "
1248 control_message = ""
1250 summary = summary + "%s " % (bug)
1251 control_message = control_message + "tag %s + fixed\n" % (bug)
1252 if action and control_message != "":
1253 Subst["__CONTROL_MESSAGE__"] = control_message;
1254 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-nmu-fixed","r").read());
1255 utils.send_mail (mail_message, "")
1257 Logger.log(["setting bugs to fixed"]+bugs);
1258 summary = summary + "\n"
1262 ###############################################################################
1264 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1265 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1266 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1267 # processed it during it's checks of -2. If -1 has been deleted or
1268 # otherwise not checked by da-install, the .orig.tar.gz will not have
1269 # been checked at all. To get round this, we force the .orig.tar.gz
1270 # into the .changes structure and reprocess the .changes file.
1272 def process_it (changes_file):
1273 global reprocess, orig_tar_id, orig_tar_location, changes, dsc, dsc_files, files, reject_message;
1275 # Reset some globals
1282 orig_tar_location = "";
1283 legacy_source_untouchable = {};
1284 reject_message = "";
1286 # Absolutize the filename to avoid the requirement of being in the
1287 # same directory as the .changes file.
1288 changes_file = os.path.abspath(changes_file);
1290 # And since handling of installs to stable munges with the CWD;
1291 # save and restore it.
1295 check_signature (changes_file);
1296 check_changes (changes_file);
1305 traceback.print_exc(file=sys.stdout);
1308 update_subst(changes_file);
1309 action(changes_file);
1314 ###############################################################################
1317 global Cnf, Options, projectB, install_bytes, new_ack_old, Subst, nmu, Logger
1321 Cnf = apt_pkg.newConfiguration();
1322 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1324 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1325 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1326 ('h',"help","Dinstall::Options::Help"),
1327 ('k',"ack-new","Dinstall::Options::Ack-New"),
1328 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1329 ('n',"no-action","Dinstall::Options::No-Action"),
1330 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1331 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1332 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1333 ('v',"version","Dinstall::Options::Version")];
1335 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1336 Options = Cnf.SubTree("Dinstall::Options")
1341 if Options["Version"]:
1342 print "katie version 0.0000000000";
1345 postgresql_user = None; # Default == Connect as user running program.
1347 # -n/--dry-run invalidates some other options which would involve things happening
1348 if Options["No-Action"]:
1349 Options["Automatic"] = "";
1350 Options["Ack-New"] = "";
1351 postgresql_user = Cnf["DB::ROUser"];
1353 projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1355 db_access.init(Cnf, projectB);
1357 # Check that we aren't going to clash with the daily cron job
1359 if not Options["No-Action"] and os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Options["No-Lock"]:
1360 utils.fubar("Archive maintenance in progress. Try again later.");
1362 # Obtain lock if not in no-action mode and initialize the log
1364 if not Options["No-Action"]:
1365 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1366 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1367 Logger = logging.Logger(Cnf, "katie");
1369 # Read in the list of already-acknowledged NEW packages
1370 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1372 for line in new_ack_list.readlines():
1373 new_ack_old[line[:-1]] = 1;
1374 new_ack_list.close();
1376 # Initialize the substitution template mapping global
1378 Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"];
1379 Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"];
1380 bcc = "X-Katie: $Revision: 1.55 $"
1381 if Cnf.has_key("Dinstall::Bcc"):
1382 Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"]);
1384 Subst["__BCC__"] = bcc;
1385 Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"];
1386 Subst["__KATIE_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"];
1388 # Read in the group-maint override file
1391 # Sort the .changes files so that we process sourceful ones first
1392 changes_files.sort(utils.changes_compare);
1394 # Process the changes files
1395 for changes_file in changes_files:
1396 print "\n" + changes_file;
1397 process_it (changes_file);
1401 if install_count > 1:
1403 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1404 Logger.log(["total",install_count,install_bytes]);
1406 # Write out the list of already-acknowledged NEW packages
1407 if Options["Ack-New"]:
1408 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1409 for i in new_ack_new.keys():
1410 new_ack_list.write(i+'\n')
1411 new_ack_list.close()
1413 if not Options["No-Action"]:
1416 if __name__ == '__main__':