3 # Installs Debian packaes
4 # Copyright (C) 2000, 2001 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.45 2001-06-20 18:47:12 troup Exp $
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 # Originally based almost entirely on dinstall by Guy Maor <maor@debian.org>
23 #########################################################################################
25 # Cartman: "I'm trying to make the best of a bad situation, I don't
26 # need to hear crap from a bunch of hippy freaks living in
27 # denial. Screw you guys, I'm going home."
29 # Kyle: "But Cartman, we're trying to..."
31 # Cartman: "uhh.. screw you guys... home."
33 #########################################################################################
35 import FCNTL, commands, fcntl, getopt, gzip, os, pg, pwd, re, shutil, stat, string, sys, tempfile, time, traceback
36 import apt_inst, apt_pkg
37 import utils, db_access
39 ###############################################################################
41 re_isanum = re.compile (r"^\d+$");
42 re_changes = re.compile (r"changes$");
43 re_default_answer = re.compile(r"\[(.*)\]");
44 re_fdnic = re.compile("\n\n");
45 re_bad_diff = re.compile("^[\-\+][\-\+][\-\+] /dev/null");
46 re_bin_only_nmu = re.compile("\.\d+\.\d+$");
48 #########################################################################################
64 orig_tar_location = "";
65 legacy_source_untouchable = {};
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 if os.path.exists(file):
272 reject_message = reject_message + "Rejected: Can't read `%s'. [permission denied]\n" % (file)
274 reject_message = reject_message + "Rejected: Can't read `%s'. [file not found]\n" % (file)
276 files[file]["type"] = "unreadable";
278 # If it's byhand skip remaining checks
279 if files[file]["section"] == "byhand":
280 files[file]["byhand"] = 1;
281 files[file]["type"] = "byhand";
282 # Checks for a binary package...
283 elif utils.re_isadeb.match(file) != None:
284 files[file]["type"] = "deb";
286 # Extract package information using dpkg-deb
288 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
290 reject_message = reject_message + "Rejected: %s: debExtractControl() raised %s.\n" % (file, sys.exc_type);
291 # Can't continue, none of the checks on control would work.
294 # Check for mandatory fields
295 if control.Find("Package") == None:
296 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
297 if control.Find("Architecture") == None:
298 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
299 if control.Find("Version") == None:
300 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
302 # Ensure the package name matches the one give in the .changes
303 if not changes["binary"].has_key(control.Find("Package", "")):
304 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
306 # Validate the architecture
307 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
308 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
310 # Check the architecture matches the one given in the .changes
311 if not changes["architecture"].has_key(control.Find("Architecture", "")):
312 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
313 # Check the section & priority match those given in the .changes (non-fatal)
314 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
315 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"])
316 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
317 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"])
319 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
321 files[file]["package"] = control.Find("Package");
322 files[file]["architecture"] = control.Find("Architecture");
323 files[file]["version"] = control.Find("Version");
324 files[file]["maintainer"] = control.Find("Maintainer", "");
325 if file[-5:] == ".udeb":
326 files[file]["dbtype"] = "udeb";
327 elif file[-4:] == ".deb":
328 files[file]["dbtype"] = "deb";
330 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
331 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
332 files[file]["source"] = control.Find("Source", "");
333 if files[file]["source"] == "":
334 files[file]["source"] = files[file]["package"];
335 # Get the source version
336 source = files[file]["source"];
338 if string.find(source, "(") != -1:
339 m = utils.re_extract_src_version.match(source)
341 source_version = m.group(2)
342 if not source_version:
343 source_version = files[file]["version"];
344 files[file]["source package"] = source;
345 files[file]["source version"] = source_version;
347 # Checks for a source package...
349 m = utils.re_issource.match(file)
351 files[file]["package"] = m.group(1)
352 files[file]["version"] = m.group(2)
353 files[file]["type"] = m.group(3)
355 # Ensure the source package name matches the Source filed in the .changes
356 if changes["source"] != files[file]["package"]:
357 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
359 # Ensure the source version matches the version in the .changes file
360 if files[file]["type"] == "orig.tar.gz":
361 changes_version = changes["chopversion2"]
363 changes_version = changes["chopversion"]
364 if changes_version != files[file]["version"]:
365 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
367 # Ensure the .changes lists source in the Architecture field
368 if not changes["architecture"].has_key("source"):
369 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
371 # Check the signature of a .dsc file
372 if files[file]["type"] == "dsc":
373 check_signature(file)
375 files[file]["fullname"] = file
377 # Not a binary or source package? Assume byhand...
379 files[file]["byhand"] = 1;
380 files[file]["type"] = "byhand";
382 files[file]["oldfiles"] = {}
383 for suite in changes["distribution"].keys():
385 if files[file].has_key("byhand"):
388 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
389 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
392 # See if the package is NEW
393 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
394 files[file]["new"] = 1
396 if files[file]["type"] == "deb":
397 # Find any old binary packages
398 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"
399 % (files[file]["package"], suite, files[file]["architecture"]))
400 oldfiles = q.dictresult()
401 for oldfile in oldfiles:
402 files[file]["oldfiles"][suite] = oldfile
403 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
404 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
405 reject_message = reject_message + "Rejected: %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
406 # Check for existing copies of the file
407 if not changes.has_key("stable upload"):
408 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"]))
409 if q.getresult() != []:
410 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
412 # Check for existent source
413 # FIXME: this is no longer per suite
414 if changes["architecture"].has_key("source"):
415 source_version = files[file]["source version"];
416 if source_version != changes["version"]:
417 reject_message = reject_message + "Rejected: source version (%s) for %s doesn't match changes version %s.\n" % (files[file]["sourceversion"], file, changes["version"]);
419 q = projectB.query("SELECT s.version FROM source s WHERE s.source = '%s'" % (files[file]["source package"]));
420 ql = map(lambda x: x[0], q.getresult());
421 if ql.count(source_version) == 0:
422 # Maybe it's a binary only NMU ?
423 if re_bin_only_nmu.search(source_version):
424 orig_source_version = re_bin_only_nmu.sub('', source_version);
425 if ql.count(orig_source_version) == 0:
426 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);
428 reject_message = reject_message + "Rejected: no source version (%s) found in %s for %s (%s).\n" % (source_version, suite, files[file]["source package"], file);
430 # Find any old .dsc files
431 elif files[file]["type"] == "dsc":
432 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"
433 % (files[file]["package"], suite))
434 oldfiles = q.dictresult()
435 if len(oldfiles) >= 1:
436 files[file]["oldfiles"][suite] = oldfiles[0]
438 # Validate the component
439 component = files[file]["component"];
440 component_id = db_access.get_component_id(component);
441 if component_id == -1:
442 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
445 # Validate the priority
446 if string.find(files[file]["priority"],'/') != -1:
447 reject_message = reject_message + "Rejected: file '%s' has invalid priority '%s' [contains '/'].\n" % (file, files[file]["priority"]);
449 # Check the md5sum & size against existing files (if any)
450 location = Cnf["Dir::PoolDir"];
451 files[file]["location id"] = db_access.get_location_id (location, component, archive);
453 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
454 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
456 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
458 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
459 files[file]["files id"] = files_id
461 # Check for packages that have moved from one component to another
462 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
463 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
466 if string.find(reject_message, "Rejected:") != -1:
471 ###############################################################################
474 global dsc, dsc_files, reject_message, reprocess, orig_tar_id, orig_tar_location, legacy_source_untouchable;
476 for file in files.keys():
477 if files[file]["type"] == "dsc":
479 dsc = utils.parse_changes(file, 1)
480 except utils.cant_open_exc:
481 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (file)
483 except utils.changes_parse_error_exc, line:
484 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (file, line)
486 except utils.invalid_dsc_format_exc, line:
487 reject_message = reject_message + "Rejected: syntax error in .dsc file '%s', line %s.\n" % (file, line)
490 dsc_files = utils.build_file_list(dsc, 1)
491 except utils.no_files_exc:
492 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
494 except utils.changes_parse_error_exc, line:
495 reject_message = "Rejected: error parsing .dsc file '%s', can't grok: %s.\n" % (file, line);
498 # Try and find all files mentioned in the .dsc. This has
499 # to work harder to cope with the multiple possible
500 # locations of an .orig.tar.gz.
501 for dsc_file in dsc_files.keys():
502 if files.has_key(dsc_file):
503 actual_md5 = files[dsc_file]["md5sum"];
504 actual_size = int(files[dsc_file]["size"]);
505 found = "%s in incoming" % (dsc_file)
506 # Check the file does not already exist in the archive
507 if not changes.has_key("stable upload"):
508 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));
510 # "It has not broken them. It has fixed a
511 # brokenness. Your crappy hack exploited a
512 # bug in the old dinstall.
514 # "(Come on! I thought it was always obvious
515 # that one just doesn't release different
516 # files with the same name and version.)"
517 # -- ajk@ on d-devel@l.d.o
519 if q.getresult() != []:
520 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
521 elif dsc_file[-12:] == ".orig.tar.gz":
523 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));
527 # Unfortunately, we make get more than one match
528 # here if, for example, the package was in potato
529 # but had a -sa upload in woody. So we need to a)
530 # choose the right one and b) mark all wrong ones
531 # as excluded from the source poolification (to
532 # avoid file overwrites).
534 x = ql[0]; # default to something sane in case we don't match any or have only one
538 old_file = i[0] + i[1];
539 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
540 actual_size = os.stat(old_file)[stat.ST_SIZE];
541 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
544 legacy_source_untouchable[i[3]] = "";
546 old_file = x[0] + x[1];
547 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
548 actual_size = os.stat(old_file)[stat.ST_SIZE];
551 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
554 if suite_type == "legacy" or suite_type == "legacy-mixed":
555 orig_tar_location = "legacy";
557 orig_tar_location = x[4];
559 # Not there? Check in Incoming...
560 # [See comment above process_it() for explanation
561 # of why this is necessary...]
562 if os.path.exists(dsc_file):
563 files[dsc_file] = {};
564 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
565 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
566 files[dsc_file]["section"] = files[file]["section"];
567 files[dsc_file]["priority"] = files[file]["priority"];
568 files[dsc_file]["component"] = files[file]["component"];
569 files[dsc_file]["type"] = "orig.tar.gz";
573 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);
576 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming.\n" % (file, dsc_file);
578 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
579 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
580 if actual_size != int(dsc_files[dsc_file]["size"]):
581 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
583 if string.find(reject_message, "Rejected:") != -1:
588 ###############################################################################
590 # Some cunning stunt broke dpkg-source in dpkg 1.8{,.1}; detect the
591 # resulting bad source packages and reject them.
593 # Even more amusingly the fix in 1.8.1.1 didn't actually fix the
594 # problem just changed the symptoms.
597 global dsc, dsc_files, reject_message, reprocess;
599 for filename in files.keys():
600 if files[filename]["type"] == "diff.gz":
601 file = gzip.GzipFile(filename, 'r');
602 for line in file.readlines():
603 if re_bad_diff.search(line):
604 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";
607 if string.find(reject_message, "Rejected:") != -1:
612 ###############################################################################
614 def check_md5sums ():
615 global reject_message;
617 for file in files.keys():
619 file_handle = utils.open_file(file,"r");
620 except utils.cant_open_exc:
623 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
624 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
626 def check_override ():
629 # Only check section & priority on sourceful uploads
630 if not changes["architecture"].has_key("source"):
634 for file in files.keys():
635 if not files[file].has_key("new") and files[file]["type"] == "deb":
636 section = files[file]["section"];
637 override_section = files[file]["override section"];
638 if section != override_section and section != "-":
639 # Ignore this; it's a common mistake and not worth whining about
640 if string.lower(section) == "non-us/main" and string.lower(override_section) == "non-us":
642 summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
643 priority = files[file]["priority"];
644 override_priority = files[file]["override priority"];
645 if priority != override_priority and priority != "-":
646 summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
651 Subst["__SUMMARY__"] = summary;
652 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.override-disparity","r").read());
653 utils.send_mail (mail_message, "")
655 #####################################################################################################################
657 # Set up the per-package template substitution mappings
659 def update_subst (changes_filename):
662 if changes.has_key("architecture"):
663 Subst["__ARCHITECTURE__"] = string.join(changes["architecture"].keys(), ' ' );
665 Subst["__ARCHITECTURE__"] = "Unknown";
666 Subst["__CHANGES_FILENAME__"] = os.path.basename(changes_filename);
667 Subst["__FILE_CONTENTS__"] = changes.get("filecontents", "");
669 # For source uploads the Changed-By field wins; otherwise Maintainer wins.
670 if changes["architecture"].has_key("source") and changes["changedby822"] != "" and (changes["changedby822"] != changes["maintainer822"]):
671 Subst["__MAINTAINER_FROM__"] = changes["changedby822"];
672 Subst["__MAINTAINER_TO__"] = changes["changedby822"] + ", " + changes["maintainer822"];
673 Subst["__MAINTAINER__"] = changes.get("changed-by", "Unknown");
675 Subst["__MAINTAINER_FROM__"] = changes["maintainer822"];
676 Subst["__MAINTAINER_TO__"] = changes["maintainer822"];
677 Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown");
679 Subst["__REJECT_MESSAGE__"] = reject_message;
680 Subst["__SOURCE__"] = changes.get("source", "Unknown");
681 Subst["__VERSION__"] = changes.get("version", "Unknown");
683 #####################################################################################################################
685 def action (changes_filename):
686 byhand = confirm = suites = summary = new = "";
688 # changes["distribution"] may not exist in corner cases
689 # (e.g. unreadable changes files)
690 if not changes.has_key("distribution"):
691 changes["distribution"] = {};
693 for suite in changes["distribution"].keys():
694 if Cnf.has_key("Suite::%s::Confirm"):
695 confirm = confirm + suite + ", "
696 suites = suites + suite + ", "
697 confirm = confirm[:-2]
700 for file in files.keys():
701 if files[file].has_key("byhand"):
703 summary = summary + file + " byhand\n"
704 elif files[file].has_key("new"):
706 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
707 if files[file].has_key("othercomponents"):
708 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
709 if files[file]["type"] == "deb":
710 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
712 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
713 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
714 summary = summary + file + "\n to " + destination + "\n"
716 short_summary = summary;
718 # This is for direport's benefit...
719 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
721 if confirm or byhand or new:
722 summary = summary + "Changes: " + f;
724 summary = summary + announce (short_summary, 0)
726 (prompt, answer) = ("", "XXX")
727 if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
730 if string.find(reject_message, "Rejected") != -1:
732 modified_time = time.time()-os.path.getmtime(changes_filename);
733 except: # i.e. ignore errors like 'file does not exist';
735 if modified_time < 86400:
736 print "SKIP (too new)\n" + reject_message,;
737 prompt = "[S]kip, Manual reject, Quit ?";
739 print "REJECT\n" + reject_message,;
740 prompt = "[R]eject, Manual reject, Skip, Quit ?";
741 if Cnf["Dinstall::Options::Automatic"]:
744 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
745 prompt = "[S]kip, New ack, Manual reject, Quit ?";
746 if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
749 print "BYHAND\n" + reject_message + summary,;
750 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
752 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
753 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
755 print "INSTALL\n" + reject_message + summary,;
756 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
757 if Cnf["Dinstall::Options::Automatic"]:
760 while string.find(prompt, answer) == -1:
762 answer = utils.our_raw_input()
763 m = re_default_answer.match(prompt)
766 answer = string.upper(answer[:1])
769 reject (changes_filename, "");
771 manual_reject (changes_filename);
773 install (changes_filename, summary, short_summary);
775 acknowledge_new (changes_filename, summary);
779 #####################################################################################################################
781 def install (changes_filename, summary, short_summary):
782 global install_count, install_bytes, Subst;
784 # Stable uploads are a special case
785 if changes.has_key("stable upload"):
786 stable_install (changes_filename, summary, short_summary);
791 archive = utils.where_am_i();
793 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
794 projectB.query("BEGIN WORK");
796 # Add the .dsc file to the DB
797 for file in files.keys():
798 if files[file]["type"] == "dsc":
799 package = dsc["source"]
800 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
801 maintainer = dsc["maintainer"]
802 maintainer = string.replace(maintainer, "'", "\\'")
803 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
804 filename = files[file]["pool name"] + file;
805 dsc_location_id = files[file]["location id"];
806 if not files[file]["files id"]:
807 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
808 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
809 % (package, version, maintainer_id, files[file]["files id"]))
811 for suite in changes["distribution"].keys():
812 suite_id = db_access.get_suite_id(suite);
813 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
815 # Add the source files to the DB (files and dsc_files)
816 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
817 for dsc_file in dsc_files.keys():
818 filename = files[file]["pool name"] + dsc_file;
819 # If the .orig.tar.gz is already in the pool, it's
820 # files id is stored in dsc_files by check_dsc().
821 files_id = dsc_files[dsc_file].get("files id", None);
823 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
824 # FIXME: needs to check for -1/-2 and or handle exception
826 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
827 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
829 # Add the .deb files to the DB
830 for file in files.keys():
831 if files[file]["type"] == "deb":
832 package = files[file]["package"]
833 version = files[file]["version"]
834 maintainer = files[file]["maintainer"]
835 maintainer = string.replace(maintainer, "'", "\\'")
836 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
837 architecture = files[file]["architecture"]
838 architecture_id = db_access.get_architecture_id (architecture);
839 type = files[file]["dbtype"];
840 dsc_component = files[file]["component"]
841 source = files[file]["source package"]
842 source_version = files[file]["source version"];
843 filename = files[file]["pool name"] + file;
844 if not files[file]["files id"]:
845 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
846 source_id = db_access.get_source_id (source, source_version);
848 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
849 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
851 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
852 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
853 for suite in changes["distribution"].keys():
854 suite_id = db_access.get_suite_id(suite);
855 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
857 # If the .orig.tar.gz is in a legacy directory we need to poolify
858 # it, so that apt-get source (and anything else that goes by the
859 # "Directory:" field in the Sources.gz file) works.
860 if orig_tar_id != None and orig_tar_location == "legacy":
861 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));
864 # Is this an old upload superseded by a newer -sa upload? (See check_dsc() for details)
865 if legacy_source_untouchable.has_key(qid["files_id"]):
867 # First move the files to the new location
868 legacy_filename = qid["path"]+qid["filename"];
869 pool_location = utils.poolify (changes["source"], files[file]["component"]);
870 pool_filename = pool_location + os.path.basename(qid["filename"]);
871 destination = Cnf["Dir::PoolDir"] + pool_location
872 utils.move(legacy_filename, destination);
873 # Then Update the DB's files table
874 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
876 # If this is a sourceful diff only upload that is moving non-legacy
877 # cross-component we need to copy the .orig.tar.gz into the new
878 # component too for the same reasons as above.
880 if changes["architecture"].has_key("source") and orig_tar_id != None and \
881 orig_tar_location != "legacy" and orig_tar_location != dsc_location_id:
882 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));
883 ql = q.getresult()[0];
884 old_filename = ql[0] + ql[1];
887 new_filename = utils.poolify (changes["source"], dsc_component) + os.path.basename(old_filename);
888 new_files_id = db_access.get_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
889 if new_files_id == None:
890 utils.copy(old_filename, Cnf["Dir::PoolDir"] + new_filename);
891 new_files_id = db_access.set_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
892 projectB.query("UPDATE dsc_files SET file = %s WHERE source = %s AND file = %s" % (new_files_id, source_id, orig_tar_id));
894 # Install the files into the pool
895 for file in files.keys():
896 if files[file].has_key("byhand"):
898 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
899 destdir = os.path.dirname(destination)
900 utils.move (file, destination)
901 install_bytes = install_bytes + float(files[file]["size"])
903 # Copy the .changes file across for suite which need it.
904 for suite in changes["distribution"].keys():
905 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
906 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
908 projectB.query("COMMIT WORK");
911 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
913 sys.stderr.write("W: couldn't move changes file '%s' to DONE directory [Got %s].\n" % (os.path.basename(changes_filename), sys.exc_type));
915 install_count = install_count + 1;
917 if not Cnf["Dinstall::Options::No-Mail"]:
918 Subst["__SUITE__"] = "";
919 Subst["__SUMMARY__"] = summary;
920 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.installed","r").read());
921 utils.send_mail (mail_message, "")
922 announce (short_summary, 1)
925 #####################################################################################################################
927 def stable_install (changes_filename, summary, short_summary):
928 global install_count, install_bytes, Subst;
930 print "Installing to stable."
932 archive = utils.where_am_i();
934 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
935 projectB.query("BEGIN WORK");
937 # Add the .dsc file to the DB
938 for file in files.keys():
939 if files[file]["type"] == "dsc":
940 package = dsc["source"]
941 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
942 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
945 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version));
947 source_id = ql[0][0];
948 suite_id = db_access.get_suite_id('proposed-updates');
949 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
950 suite_id = db_access.get_suite_id('stable');
951 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
953 # Add the .deb files to the DB
954 for file in files.keys():
955 if files[file]["type"] == "deb":
956 package = files[file]["package"]
957 version = files[file]["version"]
958 architecture = files[file]["architecture"]
959 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))
962 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture));
964 binary_id = ql[0][0];
965 suite_id = db_access.get_suite_id('proposed-updates');
966 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
967 suite_id = db_access.get_suite_id('stable');
968 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
970 projectB.query("COMMIT WORK");
973 utils.move (changes_filename, Cnf["Dir::Morgue"] + '/katie/' + os.path.basename(changes_filename));
975 # Update the Stable ChangeLog file
977 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
978 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
979 if os.path.exists(new_changelog_filename):
980 os.unlink (new_changelog_filename);
982 new_changelog = utils.open_file(new_changelog_filename, 'w');
983 for file in files.keys():
984 if files[file]["type"] == "deb":
985 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
986 elif utils.re_issource.match(file) != None:
987 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
989 new_changelog.write("%s\n" % (file));
990 chop_changes = re_fdnic.sub("\n", changes["changes"]);
991 new_changelog.write(chop_changes + '\n\n');
992 if os.access(changelog_filename, os.R_OK) != 0:
993 changelog = utils.open_file(changelog_filename, 'r');
994 new_changelog.write(changelog.read());
995 new_changelog.close();
996 if os.access(changelog_filename, os.R_OK) != 0:
997 os.unlink(changelog_filename);
998 utils.move(new_changelog_filename, changelog_filename);
1000 install_count = install_count + 1;
1002 if not Cnf["Dinstall::Options::No-Mail"]:
1003 Subst["__SUITE__"] = " into stable";
1004 Subst["__SUMMARY__"] = summary;
1005 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.installed","r").read());
1006 utils.send_mail (mail_message, "")
1007 announce (short_summary, 1)
1009 #####################################################################################################################
1011 def reject (changes_filename, manual_reject_mail_filename):
1014 print "Rejecting.\n"
1016 base_changes_filename = os.path.basename(changes_filename);
1017 reason_filename = re_changes.sub("reason", base_changes_filename);
1018 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
1020 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
1022 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
1024 sys.stderr.write("W: couldn't reject changes file '%s' [Got %s].\n" % (base_changes_filename, sys.exc_type));
1026 for file in files.keys():
1027 if os.path.exists(file):
1029 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
1031 sys.stderr.write("W: couldn't reject file '%s' [Got %s].\n" % (file, sys.exc_type));
1034 # If this is not a manual rejection generate the .reason file and rejection mail message
1035 if manual_reject_mail_filename == "":
1036 if os.path.exists(reject_filename):
1037 os.unlink(reject_filename);
1038 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1039 os.write(fd, reject_message);
1041 Subst["__MANUAL_REJECT_MESSAGE__"] = "";
1042 Subst["__CC__"] = "X-Katie-Rejection: automatic (moo)";
1043 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1044 else: # Have a manual rejection file to use
1045 reject_mail_message = ""; # avoid <undef>'s
1047 # Send the rejection mail if appropriate
1048 if not Cnf["Dinstall::Options::No-Mail"]:
1049 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
1051 ##################################################################
1053 def manual_reject (changes_filename):
1056 # Build up the rejection email
1057 user_email_address = utils.whoami() + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
1058 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
1060 Subst["__MANUAL_REJECT_MESSAGE__"] = manual_reject_message;
1061 Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"];
1062 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1064 # Write the rejection email out as the <foo>.reason file
1065 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
1066 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
1067 if os.path.exists(reject_filename):
1068 os.unlink(reject_filename);
1069 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1070 os.write(fd, reject_mail_message);
1073 # If we weren't given one, spawn an editor so the user can add one in
1074 if manual_reject_message == "":
1075 result = os.system("vi +6 %s" % (reject_filename))
1077 sys.stderr.write ("vi invocation failed for `%s'!\n" % (reject_filename))
1080 # Then process it as if it were an automatic rejection
1081 reject (changes_filename, reject_filename)
1083 #####################################################################################################################
1085 def acknowledge_new (changes_filename, summary):
1086 global new_ack_new, Subst;
1088 changes_filename = os.path.basename(changes_filename);
1090 new_ack_new[changes_filename] = 1;
1092 if new_ack_old.has_key(changes_filename):
1093 print "Ack already sent.";
1096 print "Sending new ack.";
1097 if not Cnf["Dinstall::Options::No-Mail"]:
1098 Subst["__SUMMARY__"] = summary;
1099 new_ack_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.new","r").read());
1100 utils.send_mail(new_ack_message,"");
1102 #####################################################################################################################
1104 def announce (short_summary, action):
1107 # Only do announcements for source uploads with a recent dpkg-dev installed
1108 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
1113 Subst["__SHORT_SUMMARY__"] = short_summary;
1115 for dist in changes["distribution"].keys():
1116 list = Cnf.Find("Suite::%s::Announce" % (dist))
1117 if list == "" or lists_done.has_key(list):
1119 lists_done[list] = 1
1120 summary = summary + "Announcing to %s\n" % (list)
1123 Subst["__ANNOUNCE_LIST_ADDRESS__"] = list;
1124 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.announce","r").read());
1125 utils.send_mail (mail_message, "")
1127 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
1128 bugs = changes["closes"].keys()
1130 # changes["changedbyname"] == dsc_name is probably never true, but better
1132 if dsc_name == changes["maintainername"] and (changes["changedbyname"] == "" or changes["changedbyname"] == dsc_name):
1133 summary = summary + "Closing bugs: "
1135 summary = summary + "%s " % (bug)
1137 Subst["__BUG_NUMBER__"] = bug;
1138 if changes["distribution"].has_key("stable"):
1139 Subst["__STABLE_WARNING__"] = """
1140 Note that this package is not part of the released stable Debian
1141 distribution. It may have dependencies on other unreleased software,
1142 or other instabilities. Please take care if you wish to install it.
1143 The update will eventually make its way into the next released Debian
1146 Subst["__STABLE_WARNING__"] = "";
1147 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-close","r").read());
1148 utils.send_mail (mail_message, "")
1150 summary = summary + "Setting bugs to severity fixed: "
1151 control_message = ""
1153 summary = summary + "%s " % (bug)
1154 control_message = control_message + "tag %s + fixed\n" % (bug)
1155 if action and control_message != "":
1156 Subst["__CONTROL_MESSAGE__"] = control_message;
1157 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-nmu-fixed","r").read());
1158 utils.send_mail (mail_message, "")
1159 summary = summary + "\n"
1163 ###############################################################################
1165 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1166 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1167 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1168 # processed it during it's checks of -2. If -1 has been deleted or
1169 # otherwise not checked by da-install, the .orig.tar.gz will not have
1170 # been checked at all. To get round this, we force the .orig.tar.gz
1171 # into the .changes structure and reprocess the .changes file.
1173 def process_it (changes_file):
1174 global reprocess, orig_tar_id, orig_tar_location, changes, dsc, dsc_files, files, reject_message;
1176 # Reset some globals
1183 orig_tar_location = "";
1184 legacy_source_untouchable = {};
1185 reject_message = "";
1187 # Absolutize the filename to avoid the requirement of being in the
1188 # same directory as the .changes file.
1189 changes_file = os.path.abspath(changes_file);
1191 # And since handling of installs to stable munges with the CWD;
1192 # save and restore it.
1196 check_signature (changes_file);
1197 check_changes (changes_file);
1206 traceback.print_exc(file=sys.stdout);
1209 update_subst(changes_file);
1210 action(changes_file);
1215 ###############################################################################
1218 global Cnf, projectB, install_bytes, new_ack_old, Subst
1222 Cnf = apt_pkg.newConfiguration();
1223 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1225 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1226 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1227 ('h',"help","Dinstall::Options::Help"),
1228 ('k',"ack-new","Dinstall::Options::Ack-New"),
1229 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1230 ('n',"no-action","Dinstall::Options::No-Action"),
1231 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1232 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1233 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1234 ('v',"version","Dinstall::Options::Version")];
1236 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1238 if Cnf["Dinstall::Options::Help"]:
1241 if Cnf["Dinstall::Options::Version"]:
1242 print "katie version 0.0000000000";
1245 postgresql_user = None; # Default == Connect as user running program.
1247 # -n/--dry-run invalidates some other options which would involve things happening
1248 if Cnf["Dinstall::Options::No-Action"]:
1249 Cnf["Dinstall::Options::Automatic"] = ""
1250 Cnf["Dinstall::Options::Ack-New"] = ""
1251 postgresql_user = Cnf["DB::ROUser"];
1253 projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1255 db_access.init(Cnf, projectB);
1257 # Check that we aren't going to clash with the daily cron job
1259 if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1260 sys.stderr.write("Archive maintenance in progress. Try again later.\n");
1263 # Obtain lock if not in no-action mode
1265 if not Cnf["Dinstall::Options::No-Action"]:
1266 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1267 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1269 # Read in the list of already-acknowledged NEW packages
1270 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1272 for line in new_ack_list.readlines():
1273 new_ack_old[line[:-1]] = 1;
1274 new_ack_list.close();
1276 # Initialize the substitution template mapping global
1278 Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"];
1279 Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"];
1280 bcc = "X-Katie: $Revision: 1.45 $"
1281 if Cnf.has_key("Dinstall::Bcc"):
1282 Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"]);
1284 Subst["__BCC__"] = bcc;
1285 Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"];
1286 Subst["__KATIE_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"];
1288 # Sort the .changes files so that we process sourceful ones first
1289 changes_files.sort(utils.changes_compare);
1291 # Process the changes files
1292 for changes_file in changes_files:
1293 print "\n" + changes_file;
1294 process_it (changes_file);
1298 if install_count > 1:
1300 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1302 # Write out the list of already-acknowledged NEW packages
1303 if Cnf["Dinstall::Options::Ack-New"]:
1304 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1305 for i in new_ack_new.keys():
1306 new_ack_list.write(i+'\n')
1307 new_ack_list.close()
1310 if __name__ == '__main__':