3 # Installs Debian packaes
4 # Copyright (C) 2000, 2001 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.42 2001-05-24 18:56:23 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
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 = {};
68 #########################################################################################
70 def usage (exit_code):
71 print """Usage: dinstall [OPTION]... [CHANGES]...
72 -a, --automatic automatic run
73 -D, --debug=VALUE turn on debugging
74 -h, --help show this help and exit.
75 -k, --ack-new acknowledge new packages !! for cron.daily only !!
76 -m, --manual-reject=MSG manual reject with `msg'
77 -n, --no-action don't do anything
78 -p, --no-lock don't check lockfile !! for cron.daily only !!
79 -u, --distribution=DIST override distribution to `dist'
80 -v, --version display the version number and exit"""
83 #########################################################################################
85 def check_signature (filename):
88 (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))
90 reject_message = "Rejected: GPG signature check failed on `%s'.\n%s\n" % (os.path.basename(filename), output)
94 #####################################################################################################################
96 # See if a given package is in the override table
98 def in_override_p (package, component, suite, binary_type, file):
101 if binary_type == "": # must be source
106 # Override suite name; used for example with proposed-updates
107 if Cnf.Find("Suite::%s::OverrideSuite" % (suite)) != "":
108 suite = Cnf["Suite::%s::OverrideSuite" % (suite)];
110 # Avoid <undef> on unknown distributions
111 suite_id = db_access.get_suite_id(suite);
114 component_id = db_access.get_component_id(component);
115 type_id = db_access.get_override_type_id(type);
117 # FIXME: nasty non-US speficic hack
118 if string.lower(component[:7]) == "non-us/":
119 component = component[7:];
121 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"
122 % (package, suite_id, component_id, type_id));
123 result = q.getresult();
124 # If checking for a source package fall back on the binary override type
125 if type == "dsc" and not result:
126 type_id = db_access.get_override_type_id("deb");
127 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"
128 % (package, suite_id, component_id, type_id));
129 result = q.getresult();
131 # Remember the section and priority so we can check them later if appropriate
133 files[file]["override section"] = result[0][0];
134 files[file]["override priority"] = result[0][1];
138 #####################################################################################################################
140 def check_changes(filename):
141 global reject_message, changes, files
143 # Default in case we bail out
144 changes["maintainer822"] = Cnf["Dinstall::MyEmailAddress"];
145 changes["changedby822"] = Cnf["Dinstall::MyEmailAddress"];
146 changes["architecture"] = {};
148 # Parse the .changes field into a dictionary
150 changes = utils.parse_changes(filename, 0)
151 except utils.cant_open_exc:
152 reject_message = "Rejected: can't read changes file '%s'.\n" % (filename)
154 except utils.changes_parse_error_exc, line:
155 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
158 # Parse the Files field from the .changes into another dictionary
160 files = utils.build_file_list(changes, "");
161 except utils.changes_parse_error_exc, line:
162 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line);
164 # Check for mandatory fields
165 for i in ("source", "binary", "architecture", "version", "distribution","maintainer", "files"):
166 if not changes.has_key(i):
167 reject_message = "Rejected: Missing field `%s' in changes file.\n" % (i)
168 return 0 # Avoid <undef> errors during later tests
170 # Override the Distribution: field if appropriate
171 if Cnf["Dinstall::Options::Override-Distribution"] != "":
172 reject_message = reject_message + "Warning: Distribution was overriden from %s to %s.\n" % (changes["distribution"], Cnf["Dinstall::Options::Override-Distribution"])
173 changes["distribution"] = Cnf["Dinstall::Options::Override-Distribution"]
175 # Split multi-value fields into a lower-level dictionary
176 for i in ("architecture", "distribution", "binary", "closes"):
177 o = changes.get(i, "")
181 for j in string.split(o):
184 # Fix the Maintainer: field to be RFC822 compatible
185 (changes["maintainer822"], changes["maintainername"], changes["maintaineremail"]) = utils.fix_maintainer (changes["maintainer"])
187 # Fix the Changed-By: field to be RFC822 compatible; if it exists.
188 (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]) = utils.fix_maintainer(changes.get("changed-by",""));
190 # Ensure all the values in Closes: are numbers
191 if changes.has_key("closes"):
192 for i in changes["closes"].keys():
193 if re_isanum.match (i) == None:
194 reject_message = reject_message + "Rejected: `%s' from Closes field isn't a number.\n" % (i)
196 # Map frozen to unstable if frozen doesn't exist
197 if changes["distribution"].has_key("frozen") and not Cnf.has_key("Suite::Frozen"):
198 del changes["distribution"]["frozen"]
199 changes["distribution"]["unstable"] = 1;
200 reject_message = reject_message + "Mapping frozen to unstable.\n"
202 # Map testing to unstable
203 if changes["distribution"].has_key("testing"):
204 del changes["distribution"]["testing"]
205 changes["distribution"]["unstable"] = 1;
206 reject_message = reject_message + "Mapping testing to unstable.\n"
208 # Ensure target distributions exist
209 for i in changes["distribution"].keys():
210 if not Cnf.has_key("Suite::%s" % (i)):
211 reject_message = reject_message + "Rejected: Unknown distribution `%s'.\n" % (i)
213 # Ensure there _is_ a target distribution
214 if changes["distribution"].keys() == []:
215 reject_message = reject_message + "Rejected: huh? Distribution field is empty in changes file.\n";
217 # Map unreleased arches from stable to unstable
218 if changes["distribution"].has_key("stable"):
219 for i in changes["architecture"].keys():
220 if not Cnf.has_key("Suite::Stable::Architectures::%s" % (i)):
221 reject_message = reject_message + "Mapping stable to unstable for unreleased arch `%s'.\n" % (i)
222 del changes["distribution"]["stable"]
223 changes["distribution"]["unstable"] = 1;
225 # Map arches not being released from frozen to unstable
226 if changes["distribution"].has_key("frozen"):
227 for i in changes["architecture"].keys():
228 if not Cnf.has_key("Suite::Frozen::Architectures::%s" % (i)):
229 reject_message = reject_message + "Mapping frozen to unstable for non-releasing arch `%s'.\n" % (i)
230 del changes["distribution"]["frozen"]
231 changes["distribution"]["unstable"] = 1;
233 # Handle uploads to stable
234 if changes["distribution"].has_key("stable"):
235 # If running from within proposed-updates; assume an install to stable
236 if string.find(os.getcwd(), 'proposed-updates') != -1:
237 # FIXME: should probably remove anything that != stable
238 for i in ("frozen", "unstable"):
239 if changes["distribution"].has_key(i):
240 reject_message = reject_message + "Removing %s from distribution list.\n" % (i)
241 del changes["distribution"][i]
242 changes["stable upload"] = 1;
243 # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool
244 file = files.keys()[0];
245 if os.access(file, os.R_OK) == 0:
246 pool_dir = Cnf["Dir::PoolDir"] + '/' + utils.poolify(changes["source"], files[file]["component"]);
248 # Otherwise (normal case) map stable to updates
250 reject_message = reject_message + "Mapping stable to updates.\n";
251 del changes["distribution"]["stable"];
252 changes["distribution"]["proposed-updates"] = 1;
254 # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
255 changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
256 changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
258 if string.find(reject_message, "Rejected:") != -1:
264 global reject_message
266 archive = utils.where_am_i();
268 for file in files.keys():
269 # Check the file is readable
270 if os.access(file,os.R_OK) == 0:
271 reject_message = reject_message + "Rejected: Can't read `%s'.\n" % (file)
272 files[file]["type"] = "unreadable";
274 # If it's byhand skip remaining checks
275 if files[file]["section"] == "byhand":
276 files[file]["byhand"] = 1;
277 files[file]["type"] = "byhand";
278 # Checks for a binary package...
279 elif utils.re_isadeb.match(file) != None:
280 files[file]["type"] = "deb";
282 # Extract package information using dpkg-deb
284 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
286 reject_message = reject_message + "Rejected: %s: debExtractControl() raised %s.\n" % (file, sys.exc_type);
287 # Can't continue, none of the checks on control would work.
290 # Check for mandatory fields
291 if control.Find("Package") == None:
292 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
293 if control.Find("Architecture") == None:
294 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
295 if control.Find("Version") == None:
296 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
298 # Ensure the package name matches the one give in the .changes
299 if not changes["binary"].has_key(control.Find("Package", "")):
300 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
302 # Validate the architecture
303 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
304 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
306 # Check the architecture matches the one given in the .changes
307 if not changes["architecture"].has_key(control.Find("Architecture", "")):
308 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
309 # Check the section & priority match those given in the .changes (non-fatal)
310 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
311 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"])
312 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
313 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"])
315 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
317 files[file]["package"] = control.Find("Package");
318 files[file]["architecture"] = control.Find("Architecture");
319 files[file]["version"] = control.Find("Version");
320 files[file]["maintainer"] = control.Find("Maintainer", "");
321 if file[-5:] == ".udeb":
322 files[file]["dbtype"] = "udeb";
323 elif file[-4:] == ".deb":
324 files[file]["dbtype"] = "deb";
326 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
327 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
328 files[file]["source"] = control.Find("Source", "");
329 if files[file]["source"] == "":
330 files[file]["source"] = files[file]["package"];
331 # Get the source version
332 source = files[file]["source"];
334 if string.find(source, "(") != -1:
335 m = utils.re_extract_src_version.match(source)
337 source_version = m.group(2)
338 if not source_version:
339 source_version = files[file]["version"];
340 files[file]["source package"] = source;
341 files[file]["source version"] = source_version;
343 # Checks for a source package...
345 m = utils.re_issource.match(file)
347 files[file]["package"] = m.group(1)
348 files[file]["version"] = m.group(2)
349 files[file]["type"] = m.group(3)
351 # Ensure the source package name matches the Source filed in the .changes
352 if changes["source"] != files[file]["package"]:
353 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
355 # Ensure the source version matches the version in the .changes file
356 if files[file]["type"] == "orig.tar.gz":
357 changes_version = changes["chopversion2"]
359 changes_version = changes["chopversion"]
360 if changes_version != files[file]["version"]:
361 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
363 # Ensure the .changes lists source in the Architecture field
364 if not changes["architecture"].has_key("source"):
365 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
367 # Check the signature of a .dsc file
368 if files[file]["type"] == "dsc":
369 check_signature(file)
371 files[file]["fullname"] = file
373 # Not a binary or source package? Assume byhand...
375 files[file]["byhand"] = 1;
376 files[file]["type"] = "byhand";
378 files[file]["oldfiles"] = {}
379 for suite in changes["distribution"].keys():
381 if files[file].has_key("byhand"):
384 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
385 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
388 # See if the package is NEW
389 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
390 files[file]["new"] = 1
392 if files[file]["type"] == "deb":
393 # Find any old binary packages
394 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"
395 % (files[file]["package"], suite, files[file]["architecture"]))
396 oldfiles = q.dictresult()
397 for oldfile in oldfiles:
398 files[file]["oldfiles"][suite] = oldfile
399 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
400 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
401 reject_message = reject_message + "Rejected: %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
402 # Check for existing copies of the file
403 if not changes.has_key("stable upload"):
404 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"]))
405 if q.getresult() != []:
406 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
408 # Check for existent source
409 # FIXME: this is no longer per suite
410 if changes["architecture"].has_key("source"):
411 source_version = files[file]["source version"];
412 if source_version != changes["version"]:
413 reject_message = reject_message + "Rejected: source version (%s) for %s doesn't match changes version %s.\n" % (files[file]["sourceversion"], file, changes["version"]);
415 q = projectB.query("SELECT s.version FROM source s WHERE s.source = '%s'" % (files[file]["source package"]));
416 ql = map(lambda x: x[0], q.getresult());
417 if ql.count(source_version) == 0:
418 # Maybe it's a binary only NMU ?
419 if re_bin_only_nmu.search(source_version):
420 orig_source_version = re_bin_only_nmu.sub('', source_version);
421 if ql.count(orig_source_version) == 0:
422 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);
424 reject_message = reject_message + "Rejected: no source version (%s) found in %s for %s (%s).\n" % (source_version, suite, files[file]["source package"], file);
426 # Find any old .dsc files
427 elif files[file]["type"] == "dsc":
428 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"
429 % (files[file]["package"], suite))
430 oldfiles = q.dictresult()
431 if len(oldfiles) >= 1:
432 files[file]["oldfiles"][suite] = oldfiles[0]
434 # Validate the component
435 component = files[file]["component"];
436 component_id = db_access.get_component_id(component);
437 if component_id == -1:
438 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
441 # Validate the priority
442 if string.find(files[file]["priority"],'/') != -1:
443 reject_message = reject_message + "Rejected: file '%s' has invalid priority '%s' [contains '/'].\n" % (file, files[file]["priority"]);
445 # Check the md5sum & size against existing files (if any)
446 location = Cnf["Dir::PoolDir"];
447 files[file]["location id"] = db_access.get_location_id (location, component, archive);
449 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
450 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
452 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
454 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
455 files[file]["files id"] = files_id
457 # Check for packages that have moved from one component to another
458 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
459 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
462 if string.find(reject_message, "Rejected:") != -1:
467 ###############################################################################
470 global dsc, dsc_files, reject_message, reprocess, orig_tar_id, orig_tar_location, legacy_source_untouchable;
472 for file in files.keys():
473 if files[file]["type"] == "dsc":
475 dsc = utils.parse_changes(file, 1)
476 except utils.cant_open_exc:
477 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (file)
479 except utils.changes_parse_error_exc, line:
480 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (file, line)
482 except utils.invalid_dsc_format_exc, line:
483 reject_message = reject_message + "Rejected: syntax error in .dsc file '%s', line %s.\n" % (file, line)
486 dsc_files = utils.build_file_list(dsc, 1)
487 except utils.no_files_exc:
488 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
490 except utils.changes_parse_error_exc, line:
491 reject_message = "Rejected: error parsing .dsc file '%s', can't grok: %s.\n" % (file, line);
494 # Try and find all files mentioned in the .dsc. This has
495 # to work harder to cope with the multiple possible
496 # locations of an .orig.tar.gz.
497 for dsc_file in dsc_files.keys():
498 if files.has_key(dsc_file):
499 actual_md5 = files[dsc_file]["md5sum"];
500 actual_size = int(files[dsc_file]["size"]);
501 found = "%s in incoming" % (dsc_file)
502 # Check the file does not already exist in the archive
503 if not changes.has_key("stable upload"):
504 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));
506 # "It has not broken them. It has fixed a
507 # brokenness. Your crappy hack exploited a
508 # bug in the old dinstall.
510 # "(Come on! I thought it was always obvious
511 # that one just doesn't release different
512 # files with the same name and version.)"
513 # -- ajk@ on d-devel@l.d.o
515 if q.getresult() != []:
516 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
517 elif dsc_file[-12:] == ".orig.tar.gz":
519 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));
523 # Unfortunately, we make get more than one match
524 # here if, for example, the package was in potato
525 # but had a -sa upload in woody. So we need to a)
526 # choose the right one and b) mark all wrong ones
527 # as excluded from the source poolification (to
528 # avoid file overwrites).
530 x = ql[0]; # default to something sane in case we don't match any or have only one
534 old_file = i[0] + i[1];
535 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
536 actual_size = os.stat(old_file)[stat.ST_SIZE];
537 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
540 legacy_source_untouchable[i[3]] = "";
542 old_file = x[0] + x[1];
543 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
544 actual_size = os.stat(old_file)[stat.ST_SIZE];
547 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
550 if suite_type == "legacy" or suite_type == "legacy-mixed":
551 orig_tar_location = "legacy";
553 orig_tar_location = x[4];
555 # Not there? Check in Incoming...
556 # [See comment above process_it() for explanation
557 # of why this is necessary...]
558 if os.access(dsc_file, os.R_OK) != 0:
559 files[dsc_file] = {};
560 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
561 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
562 files[dsc_file]["section"] = files[file]["section"];
563 files[dsc_file]["priority"] = files[file]["priority"];
564 files[dsc_file]["component"] = files[file]["component"];
565 files[dsc_file]["type"] = "orig.tar.gz";
569 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);
572 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming.\n" % (file, dsc_file);
574 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
575 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
576 if actual_size != int(dsc_files[dsc_file]["size"]):
577 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
579 if string.find(reject_message, "Rejected:") != -1:
584 ###############################################################################
586 # Some cunning stunt broke dpkg-source in dpkg 1.8{,.1}; detect the
587 # resulting bad source packages and reject them.
589 # Even more amusingly the fix in 1.8.1.1 didn't actually fix the
590 # problem just changed the symptoms.
593 global dsc, dsc_files, reject_message, reprocess;
595 for filename in files.keys():
596 if files[filename]["type"] == "diff.gz":
597 file = gzip.GzipFile(filename, 'r');
598 for line in file.readlines():
599 if re_bad_diff.search(line):
600 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";
603 if string.find(reject_message, "Rejected:") != -1:
608 ###############################################################################
610 def check_md5sums ():
611 global reject_message;
613 for file in files.keys():
615 file_handle = utils.open_file(file,"r");
616 except utils.cant_open_exc:
619 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
620 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
622 def check_override ():
625 # Only check section & priority on sourceful uploads
626 if not changes["architecture"].has_key("source"):
630 for file in files.keys():
631 if not files[file].has_key("new") and files[file]["type"] == "deb":
632 section = files[file]["section"];
633 override_section = files[file]["override section"];
634 if section != override_section and section != "-":
635 # Ignore this; it's a common mistake and not worth whining about
636 if string.lower(section) == "non-us/main" and string.lower(override_section) == "non-us":
638 summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
639 priority = files[file]["priority"];
640 override_priority = files[file]["override priority"];
641 if priority != override_priority and priority != "-":
642 summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
647 Subst["__SUMMARY__"] = summary;
648 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.override-disparity","r").read());
649 utils.send_mail (mail_message, "")
651 #####################################################################################################################
653 # Set up the per-package template substitution mappings
655 def update_subst (changes_filename):
658 if changes.has_key("architecture"):
659 Subst["__ARCHITECTURE__"] = string.join(changes["architecture"].keys(), ' ' );
661 Subst["__ARCHITECTURE__"] = "Unknown";
662 Subst["__CHANGES_FILENAME__"] = os.path.basename(changes_filename);
663 Subst["__FILE_CONTENTS__"] = changes.get("filecontents", "");
665 # For source uploads the Changed-By field wins; otherwise Maintainer wins.
666 if changes["architecture"].has_key("source") and changes["changedby822"] != "" and (changes["changedby822"] != changes["maintainer822"]):
667 Subst["__MAINTAINER_FROM__"] = changes["changedby822"];
668 Subst["__MAINTAINER_TO__"] = changes["changedby822"] + ", " + changes["maintainer822"];
669 Subst["__MAINTAINER__"] = changes.get("changed-by", "Unknown");
671 Subst["__MAINTAINER_FROM__"] = changes["maintainer822"];
672 Subst["__MAINTAINER_TO__"] = changes["maintainer822"];
673 Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown");
675 Subst["__REJECT_MESSAGE__"] = reject_message;
676 Subst["__SOURCE__"] = changes.get("source", "Unknown");
677 Subst["__VERSION__"] = changes.get("version", "Unknown");
679 #####################################################################################################################
681 def action (changes_filename):
682 byhand = confirm = suites = summary = new = "";
684 # changes["distribution"] may not exist in corner cases
685 # (e.g. unreadable changes files)
686 if not changes.has_key("distribution"):
687 changes["distribution"] = {};
689 for suite in changes["distribution"].keys():
690 if Cnf.has_key("Suite::%s::Confirm"):
691 confirm = confirm + suite + ", "
692 suites = suites + suite + ", "
693 confirm = confirm[:-2]
696 for file in files.keys():
697 if files[file].has_key("byhand"):
699 summary = summary + file + " byhand\n"
700 elif files[file].has_key("new"):
702 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
703 if files[file].has_key("othercomponents"):
704 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
705 if files[file]["type"] == "deb":
706 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
708 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
709 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
710 summary = summary + file + "\n to " + destination + "\n"
712 short_summary = summary;
714 # This is for direport's benefit...
715 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
717 if confirm or byhand or new:
718 summary = summary + "Changes: " + f;
720 summary = summary + announce (short_summary, 0)
722 (prompt, answer) = ("", "XXX")
723 if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
726 if string.find(reject_message, "Rejected") != -1:
728 modified_time = time.time()-os.path.getmtime(changes_filename);
729 except: # i.e. ignore errors like 'file does not exist';
731 if modified_time < 86400:
732 print "SKIP (too new)\n" + reject_message,;
733 prompt = "[S]kip, Manual reject, Quit ?";
735 print "REJECT\n" + reject_message,;
736 prompt = "[R]eject, Manual reject, Skip, Quit ?";
737 if Cnf["Dinstall::Options::Automatic"]:
740 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
741 prompt = "[S]kip, New ack, Manual reject, Quit ?";
742 if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
745 print "BYHAND\n" + reject_message + summary,;
746 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
748 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
749 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
751 print "INSTALL\n" + reject_message + summary,;
752 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
753 if Cnf["Dinstall::Options::Automatic"]:
756 while string.find(prompt, answer) == -1:
758 answer = utils.our_raw_input()
759 m = re_default_answer.match(prompt)
762 answer = string.upper(answer[:1])
765 reject (changes_filename, "");
767 manual_reject (changes_filename);
769 install (changes_filename, summary, short_summary);
771 acknowledge_new (changes_filename, summary);
775 #####################################################################################################################
777 def install (changes_filename, summary, short_summary):
778 global install_count, install_bytes, Subst;
780 # Stable uploads are a special case
781 if changes.has_key("stable upload"):
782 stable_install (changes_filename, summary, short_summary);
787 archive = utils.where_am_i();
789 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
790 projectB.query("BEGIN WORK");
792 # Add the .dsc file to the DB
793 for file in files.keys():
794 if files[file]["type"] == "dsc":
795 package = dsc["source"]
796 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
797 maintainer = dsc["maintainer"]
798 maintainer = string.replace(maintainer, "'", "\\'")
799 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
800 filename = files[file]["pool name"] + file;
801 dsc_location_id = files[file]["location id"];
802 if not files[file]["files id"]:
803 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
804 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
805 % (package, version, maintainer_id, files[file]["files id"]))
807 for suite in changes["distribution"].keys():
808 suite_id = db_access.get_suite_id(suite);
809 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
811 # Add the source files to the DB (files and dsc_files)
812 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
813 for dsc_file in dsc_files.keys():
814 filename = files[file]["pool name"] + dsc_file;
815 # If the .orig.tar.gz is already in the pool, it's
816 # files id is stored in dsc_files by check_dsc().
817 files_id = dsc_files[dsc_file].get("files id", None);
819 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
820 # FIXME: needs to check for -1/-2 and or handle exception
822 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
823 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
825 # Add the .deb files to the DB
826 for file in files.keys():
827 if files[file]["type"] == "deb":
828 package = files[file]["package"]
829 version = files[file]["version"]
830 maintainer = files[file]["maintainer"]
831 maintainer = string.replace(maintainer, "'", "\\'")
832 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
833 architecture = files[file]["architecture"]
834 architecture_id = db_access.get_architecture_id (architecture);
835 type = files[file]["dbtype"];
836 dsc_component = files[file]["component"]
837 source = files[file]["source package"]
838 source_version = files[file]["source version"];
839 filename = files[file]["pool name"] + file;
840 if not files[file]["files id"]:
841 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
842 source_id = db_access.get_source_id (source, source_version);
844 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
845 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
847 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
848 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
849 for suite in changes["distribution"].keys():
850 suite_id = db_access.get_suite_id(suite);
851 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
853 # If the .orig.tar.gz is in a legacy directory we need to poolify
854 # it, so that apt-get source (and anything else that goes by the
855 # "Directory:" field in the Sources.gz file) works.
856 if orig_tar_id != None and orig_tar_location == "legacy":
857 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));
860 # Is this an old upload superseded by a newer -sa upload? (See check_dsc() for details)
861 if legacy_source_untouchable.has_key(qid["files_id"]):
863 # First move the files to the new location
864 legacy_filename = qid["path"]+qid["filename"];
865 pool_location = utils.poolify (changes["source"], files[file]["component"]);
866 pool_filename = pool_location + os.path.basename(qid["filename"]);
867 destination = Cnf["Dir::PoolDir"] + pool_location
868 utils.move(legacy_filename, destination);
869 # Then Update the DB's files table
870 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
872 # If this is a sourceful diff only upload that is moving non-legacy
873 # cross-component we need to copy the .orig.tar.gz into the new
874 # component too for the same reasons as above.
876 if changes["architecture"].has_key("source") and orig_tar_id != None and \
877 orig_tar_location != "legacy" and orig_tar_location != dsc_location_id:
878 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));
879 ql = q.getresult()[0];
880 old_filename = ql[0] + ql[1];
883 new_filename = utils.poolify (changes["source"], dsc_component) + os.path.basename(old_filename);
884 new_files_id = db_access.get_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
885 if new_files_id == None:
886 utils.copy(old_filename, Cnf["Dir::PoolDir"] + new_filename);
887 new_files_id = db_access.set_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
888 projectB.query("UPDATE dsc_files SET file = %s WHERE source = %s AND file = %s" % (new_files_id, source_id, orig_tar_id));
890 # Install the files into the pool
891 for file in files.keys():
892 if files[file].has_key("byhand"):
894 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
895 destdir = os.path.dirname(destination)
896 utils.move (file, destination)
897 install_bytes = install_bytes + float(files[file]["size"])
899 # Copy the .changes file across for suite which need it.
900 for suite in changes["distribution"].keys():
901 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
902 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
904 projectB.query("COMMIT WORK");
907 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
909 sys.stderr.write("W: couldn't move changes file '%s' to DONE directory [Got %s].\n" % (os.path.basename(changes_filename), sys.exc_type));
911 install_count = install_count + 1;
913 if not Cnf["Dinstall::Options::No-Mail"]:
914 Subst["__SUITE__"] = "";
915 Subst["__SUMMARY__"] = summary;
916 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.installed","r").read());
917 utils.send_mail (mail_message, "")
918 announce (short_summary, 1)
921 #####################################################################################################################
923 def stable_install (changes_filename, summary, short_summary):
924 global install_count, install_bytes, Subst;
926 print "Installing to stable."
928 archive = utils.where_am_i();
930 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
931 projectB.query("BEGIN WORK");
933 # Add the .dsc file to the DB
934 for file in files.keys():
935 if files[file]["type"] == "dsc":
936 package = dsc["source"]
937 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
938 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
941 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version));
943 source_id = ql[0][0];
944 suite_id = db_access.get_suite_id('proposed-updates');
945 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
946 suite_id = db_access.get_suite_id('stable');
947 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
949 # Add the .deb files to the DB
950 for file in files.keys():
951 if files[file]["type"] == "deb":
952 package = files[file]["package"]
953 version = files[file]["version"]
954 architecture = files[file]["architecture"]
955 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))
958 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture));
960 binary_id = ql[0][0];
961 suite_id = db_access.get_suite_id('proposed-updates');
962 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
963 suite_id = db_access.get_suite_id('stable');
964 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
966 projectB.query("COMMIT WORK");
969 utils.move (changes_filename, Cnf["Dir::Morgue"] + '/katie/' + os.path.basename(changes_filename));
971 # Update the Stable ChangeLog file
973 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
974 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
975 if os.path.exists(new_changelog_filename):
976 os.unlink (new_changelog_filename);
978 new_changelog = utils.open_file(new_changelog_filename, 'w');
979 for file in files.keys():
980 if files[file]["type"] == "deb":
981 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
982 elif utils.re_issource.match(file) != None:
983 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
985 new_changelog.write("%s\n" % (file));
986 chop_changes = re_fdnic.sub("\n", changes["changes"]);
987 new_changelog.write(chop_changes + '\n\n');
988 if os.access(changelog_filename, os.R_OK) != 0:
989 changelog = utils.open_file(changelog_filename, 'r');
990 new_changelog.write(changelog.read());
991 new_changelog.close();
992 if os.access(changelog_filename, os.R_OK) != 0:
993 os.unlink(changelog_filename);
994 utils.move(new_changelog_filename, changelog_filename);
996 install_count = install_count + 1;
998 if not Cnf["Dinstall::Options::No-Mail"]:
999 Subst["__SUITE__"] = " into stable";
1000 Subst["__SUMMARY__"] = summary;
1001 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.installed","r").read());
1002 utils.send_mail (mail_message, "")
1003 announce (short_summary, 1)
1005 #####################################################################################################################
1007 def reject (changes_filename, manual_reject_mail_filename):
1010 print "Rejecting.\n"
1012 base_changes_filename = os.path.basename(changes_filename);
1013 reason_filename = re_changes.sub("reason", base_changes_filename);
1014 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
1016 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
1018 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
1020 sys.stderr.write("W: couldn't reject changes file '%s' [Got %s].\n" % (base_changes_filename, sys.exc_type));
1022 for file in files.keys():
1023 if os.path.exists(file):
1025 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
1027 sys.stderr.write("W: couldn't reject file '%s' [Got %s].\n" % (file, sys.exc_type));
1030 # If this is not a manual rejection generate the .reason file and rejection mail message
1031 if manual_reject_mail_filename == "":
1032 if os.path.exists(reject_filename):
1033 os.unlink(reject_filename);
1034 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1035 os.write(fd, reject_message);
1037 Subst["__MANUAL_REJECT_MESSAGE__"] = "";
1038 Subst["__CC__"] = "X-Katie-Rejection: automatic (moo)";
1039 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1040 else: # Have a manual rejection file to use
1041 reject_mail_message = ""; # avoid <undef>'s
1043 # Send the rejection mail if appropriate
1044 if not Cnf["Dinstall::Options::No-Mail"]:
1045 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
1047 ##################################################################
1049 def manual_reject (changes_filename):
1052 # Build up the rejection email
1053 user_email_address = utils.whoami() + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
1054 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
1056 Subst["__MANUAL_REJECT_MESSAGE__"] = manual_reject_message;
1057 Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"];
1058 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1060 # Write the rejection email out as the <foo>.reason file
1061 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
1062 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
1063 if os.path.exists(reject_filename):
1064 os.unlink(reject_filename);
1065 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1066 os.write(fd, reject_mail_message);
1069 # If we weren't given one, spawn an editor so the user can add one in
1070 if manual_reject_message == "":
1071 result = os.system("vi +6 %s" % (reject_filename))
1073 sys.stderr.write ("vi invocation failed for `%s'!\n" % (reject_filename))
1076 # Then process it as if it were an automatic rejection
1077 reject (changes_filename, reject_filename)
1079 #####################################################################################################################
1081 def acknowledge_new (changes_filename, summary):
1082 global new_ack_new, Subst;
1084 changes_filename = os.path.basename(changes_filename);
1086 new_ack_new[changes_filename] = 1;
1088 if new_ack_old.has_key(changes_filename):
1089 print "Ack already sent.";
1092 print "Sending new ack.";
1093 if not Cnf["Dinstall::Options::No-Mail"]:
1094 Subst["__SUMMARY__"] = summary;
1095 new_ack_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.new","r").read());
1096 utils.send_mail(new_ack_message,"");
1098 #####################################################################################################################
1100 def announce (short_summary, action):
1103 # Only do announcements for source uploads with a recent dpkg-dev installed
1104 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
1109 Subst["__SHORT_SUMMARY__"] = short_summary;
1111 for dist in changes["distribution"].keys():
1112 list = Cnf.Find("Suite::%s::Announce" % (dist))
1113 if list == "" or lists_done.has_key(list):
1115 lists_done[list] = 1
1116 summary = summary + "Announcing to %s\n" % (list)
1119 Subst["__ANNOUNCE_LIST_ADDRESS__"] = list;
1120 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.announce","r").read());
1121 utils.send_mail (mail_message, "")
1123 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
1124 bugs = changes["closes"].keys()
1126 # changes["changedbyname"] == dsc_name is probably never true, but better
1128 if dsc_name == changes["maintainername"] and (changes["changedbyname"] == "" or changes["changedbyname"] == dsc_name):
1129 summary = summary + "Closing bugs: "
1131 summary = summary + "%s " % (bug)
1133 Subst["__BUG_NUMBER__"] = bug;
1134 if changes["distribution"].has_key("stable"):
1135 Subst["__STABLE_WARNING__"] = """
1136 Note that this package is not part of the released stable Debian
1137 distribution. It may have dependencies on other unreleased software,
1138 or other instabilities. Please take care if you wish to install it.
1139 The update will eventually make its way into the next released Debian
1142 Subst["__STABLE_WARNING__"] = "";
1143 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-close","r").read());
1144 utils.send_mail (mail_message, "")
1146 summary = summary + "Setting bugs to severity fixed: "
1147 control_message = ""
1149 summary = summary + "%s " % (bug)
1150 control_message = control_message + "tag %s + fixed\n" % (bug)
1151 if action and control_message != "":
1152 Subst["__CONTROL_MESSAGE__"] = control_message;
1153 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-nmu-fixed","r").read());
1154 utils.send_mail (mail_message, "")
1155 summary = summary + "\n"
1159 ###############################################################################
1161 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1162 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1163 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1164 # processed it during it's checks of -2. If -1 has been deleted or
1165 # otherwise not checked by da-install, the .orig.tar.gz will not have
1166 # been checked at all. To get round this, we force the .orig.tar.gz
1167 # into the .changes structure and reprocess the .changes file.
1169 def process_it (changes_file):
1170 global reprocess, orig_tar_id, orig_tar_location, changes, dsc, dsc_files, files, reject_message;
1172 # Reset some globals
1179 orig_tar_location = "";
1180 legacy_source_untouchable = {};
1181 reject_message = "";
1183 # Absolutize the filename to avoid the requirement of being in the
1184 # same directory as the .changes file.
1185 changes_file = os.path.abspath(changes_file);
1187 # And since handling of installs to stable munges with the CWD;
1188 # save and restore it.
1191 check_signature (changes_file);
1192 check_changes (changes_file);
1200 update_subst(changes_file);
1201 action(changes_file);
1206 ###############################################################################
1209 global Cnf, projectB, install_bytes, new_ack_old, Subst
1213 Cnf = apt_pkg.newConfiguration();
1214 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1216 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1217 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1218 ('h',"help","Dinstall::Options::Help"),
1219 ('k',"ack-new","Dinstall::Options::Ack-New"),
1220 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1221 ('n',"no-action","Dinstall::Options::No-Action"),
1222 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1223 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1224 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1225 ('v',"version","Dinstall::Options::Version")];
1227 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1229 if Cnf["Dinstall::Options::Help"]:
1232 if Cnf["Dinstall::Options::Version"]:
1233 print "katie version 0.0000000000";
1236 postgresql_user = None; # Default == Connect as user running program.
1238 # -n/--dry-run invalidates some other options which would involve things happening
1239 if Cnf["Dinstall::Options::No-Action"]:
1240 Cnf["Dinstall::Options::Automatic"] = ""
1241 Cnf["Dinstall::Options::Ack-New"] = ""
1242 postgresql_user = Cnf["DB::ROUser"];
1244 projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1246 db_access.init(Cnf, projectB);
1248 # Check that we aren't going to clash with the daily cron job
1250 if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1251 sys.stderr.write("Archive maintenance in progress. Try again later.\n");
1254 # Obtain lock if not in no-action mode
1256 if not Cnf["Dinstall::Options::No-Action"]:
1257 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1258 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1260 # Read in the list of already-acknowledged NEW packages
1261 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1263 for line in new_ack_list.readlines():
1264 new_ack_old[line[:-1]] = 1;
1265 new_ack_list.close();
1267 # Initialize the substitution template mapping global
1269 Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"];
1270 Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"];
1271 bcc = "X-Katie: $Revision: 1.42 $"
1272 if Cnf.has_key("Dinstall::Bcc"):
1273 Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"]);
1275 Subst["__BCC__"] = bcc;
1276 Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"];
1277 Subst["__KATIE_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"];
1279 # Sort the .changes files so that we process sourceful ones first
1280 changes_files.sort(utils.changes_compare);
1282 # Process the changes files
1283 for changes_file in changes_files:
1284 print "\n" + changes_file;
1285 process_it (changes_file);
1289 if install_count > 1:
1291 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1293 # Write out the list of already-acknowledged NEW packages
1294 if Cnf["Dinstall::Options::Ack-New"]:
1295 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1296 for i in new_ack_new.keys():
1297 new_ack_list.write(i+'\n')
1298 new_ack_list.close()
1301 if __name__ == '__main__':