3 # Installs Debian packaes
4 # Copyright (C) 2000, 2001 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.37 2001-04-03 21:28:20 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");
47 #########################################################################################
63 orig_tar_location = "";
64 legacy_source_untouchable = {};
67 #########################################################################################
69 def usage (exit_code):
70 print """Usage: dinstall [OPTION]... [CHANGES]...
71 -a, --automatic automatic run
72 -D, --debug=VALUE turn on debugging
73 -h, --help show this help and exit.
74 -k, --ack-new acknowledge new packages !! for cron.daily only !!
75 -m, --manual-reject=MSG manual reject with `msg'
76 -n, --no-action don't do anything
77 -p, --no-lock don't check lockfile !! for cron.daily only !!
78 -u, --distribution=DIST override distribution to `dist'
79 -v, --version display the version number and exit"""
82 #########################################################################################
84 def check_signature (filename):
87 (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))
89 reject_message = "Rejected: GPG signature check failed on `%s'.\n%s\n" % (os.path.basename(filename), output)
93 #####################################################################################################################
95 # See if a given package is in the override table
97 def in_override_p (package, component, suite, binary_type, file):
100 if binary_type == "": # must be source
105 # Override suite name; used for example with proposed-updates
106 if Cnf.Find("Suite::%s::OverrideSuite" % (suite)) != "":
107 suite = Cnf["Suite::%s::OverrideSuite" % (suite)];
109 # Avoid <undef> on unknown distributions
110 suite_id = db_access.get_suite_id(suite);
113 component_id = db_access.get_component_id(component);
114 type_id = db_access.get_override_type_id(type);
116 # FIXME: nasty non-US speficic hack
117 if string.lower(component[:7]) == "non-us/":
118 component = component[7:];
120 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"
121 % (package, suite_id, component_id, type_id));
122 result = q.getresult();
123 # If checking for a source package fall back on the binary override type
124 if type == "dsc" and not result:
125 type_id = db_access.get_override_type_id("deb");
126 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"
127 % (package, suite_id, component_id, type_id));
128 result = q.getresult();
130 # Remember the section and priority so we can check them later if appropriate
132 files[file]["override section"] = result[0][0];
133 files[file]["override priority"] = result[0][1];
137 #####################################################################################################################
139 def check_changes(filename):
140 global reject_message, changes, files
142 # Default in case we bail out
143 changes["maintainer822"] = Cnf["Dinstall::MyEmailAddress"];
145 # Parse the .changes field into a dictionary
147 changes = utils.parse_changes(filename, 0)
148 except utils.cant_open_exc:
149 reject_message = "Rejected: can't read changes file '%s'.\n" % (filename)
151 except utils.changes_parse_error_exc, line:
152 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
155 # Parse the Files field from the .changes into another dictionary
157 files = utils.build_file_list(changes, "");
158 except utils.changes_parse_error_exc, line:
159 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line);
161 # Check for mandatory fields
162 for i in ("source", "binary", "architecture", "version", "distribution","maintainer", "files"):
163 if not changes.has_key(i):
164 reject_message = "Rejected: Missing field `%s' in changes file.\n" % (i)
165 return 0 # Avoid <undef> errors during later tests
167 # Override the Distribution: field if appropriate
168 if Cnf["Dinstall::Options::Override-Distribution"] != "":
169 reject_message = reject_message + "Warning: Distribution was overriden from %s to %s.\n" % (changes["distribution"], Cnf["Dinstall::Options::Override-Distribution"])
170 changes["distribution"] = Cnf["Dinstall::Options::Override-Distribution"]
172 # Split multi-value fields into a lower-level dictionary
173 for i in ("architecture", "distribution", "binary", "closes"):
174 o = changes.get(i, "")
178 for j in string.split(o):
181 # Fix the Maintainer: field to be RFC822 compatible
182 (changes["maintainer822"], changes["maintainername"], changes["maintaineremail"]) = utils.fix_maintainer (changes["maintainer"])
184 # Fix the Changed-By: field to be RFC822 compatible; if it exists.
185 (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]) = utils.fix_maintainer(changes.get("changed-by",""));
187 # For source uploads the Changed-By field wins; otherwise Maintainer wins.
188 if changes["architecture"].has_key("source"):
189 changes["uploader822"] = "To: %s\nCc: %s" % (changes["changedby822"], changes["maintainer822"]);
190 # changes["uploadername"], changes["uploaderemail"]) = (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]);
192 # Ensure all the values in Closes: are numbers
193 if changes.has_key("closes"):
194 for i in changes["closes"].keys():
195 if re_isanum.match (i) == None:
196 reject_message = reject_message + "Rejected: `%s' from Closes field isn't a number.\n" % (i)
198 # Map frozen to unstable if frozen doesn't exist
199 if changes["distribution"].has_key("frozen") and not Cnf.has_key("Suite::Frozen"):
200 del changes["distribution"]["frozen"]
201 changes["distribution"]["unstable"] = 1;
202 reject_message = reject_message + "Mapping frozen to unstable.\n"
204 # Map testing to unstable
205 if changes["distribution"].has_key("testing"):
206 del changes["distribution"]["testing"]
207 changes["distribution"]["unstable"] = 1;
208 reject_message = reject_message + "Mapping testing to unstable.\n"
210 # Ensure target distributions exist
211 for i in changes["distribution"].keys():
212 if not Cnf.has_key("Suite::%s" % (i)):
213 reject_message = reject_message + "Rejected: Unknown distribution `%s'.\n" % (i)
215 # Ensure there _is_ a target distribution
216 if changes["distribution"].keys() == []:
217 reject_message = reject_message + "Rejected: huh? Distribution field is empty in changes file.\n";
219 # Map unreleased arches from stable to unstable
220 if changes["distribution"].has_key("stable"):
221 for i in changes["architecture"].keys():
222 if not Cnf.has_key("Suite::Stable::Architectures::%s" % (i)):
223 reject_message = reject_message + "Mapping stable to unstable for unreleased arch `%s'.\n" % (i)
224 del changes["distribution"]["stable"]
225 changes["distribution"]["unstable"] = 1;
227 # Map arches not being released from frozen to unstable
228 if changes["distribution"].has_key("frozen"):
229 for i in changes["architecture"].keys():
230 if not Cnf.has_key("Suite::Frozen::Architectures::%s" % (i)):
231 reject_message = reject_message + "Mapping frozen to unstable for non-releasing arch `%s'.\n" % (i)
232 del changes["distribution"]["frozen"]
233 changes["distribution"]["unstable"] = 1;
235 # Handle uploads to stable
236 if changes["distribution"].has_key("stable"):
237 # If running from within proposed-updates; assume an install to stable
238 if string.find(os.getcwd(), 'proposed-updates') != -1:
239 # FIXME: should probably remove anything that != stable
240 for i in ("frozen", "unstable"):
241 if changes["distribution"].has_key(i):
242 reject_message = reject_message + "Removing %s from distribution list.\n"
243 del changes["distribution"][i]
244 changes["stable upload"] = 1;
245 # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool
246 file = files.keys()[0];
247 if os.access(file, os.R_OK) == 0:
248 pool_dir = Cnf["Dir::PoolDir"] + '/' + utils.poolify(changes["source"], files[file]["component"]);
250 # Otherwise (normal case) map stable to updates
252 reject_message = reject_message + "Mapping stable to updates.\n";
253 del changes["distribution"]["stable"];
254 changes["distribution"]["proposed-updates"] = 1;
256 # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
257 changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
258 changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
260 if string.find(reject_message, "Rejected:") != -1:
266 global reject_message
268 archive = utils.where_am_i();
270 for file in files.keys():
271 # Check the file is readable
272 if os.access(file,os.R_OK) == 0:
273 reject_message = reject_message + "Rejected: Can't read `%s'.\n" % (file)
274 files[file]["type"] = "unreadable";
276 # If it's byhand skip remaining checks
277 if files[file]["section"] == "byhand":
278 files[file]["byhand"] = 1;
279 files[file]["type"] = "byhand";
280 # Checks for a binary package...
281 elif utils.re_isadeb.match(file) != None:
282 files[file]["type"] = "deb";
284 # Extract package information using dpkg-deb
286 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
288 reject_message = reject_message + "Rejected: %s: debExtractControl() raised %s.\n" % (file, sys.exc_type);
289 # Can't continue, none of the checks on control would work.
292 # Check for mandatory fields
293 if control.Find("Package") == None:
294 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
295 if control.Find("Architecture") == None:
296 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
297 if control.Find("Version") == None:
298 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
300 # Ensure the package name matches the one give in the .changes
301 if not changes["binary"].has_key(control.Find("Package", "")):
302 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
304 # Validate the architecture
305 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
306 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
308 # Check the architecture matches the one given in the .changes
309 if not changes["architecture"].has_key(control.Find("Architecture", "")):
310 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
311 # Check the section & priority match those given in the .changes (non-fatal)
312 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
313 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"])
314 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
315 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"])
317 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
319 files[file]["package"] = control.Find("Package");
320 files[file]["architecture"] = control.Find("Architecture");
321 files[file]["version"] = control.Find("Version");
322 files[file]["maintainer"] = control.Find("Maintainer", "");
323 if file[-5:] == ".udeb":
324 files[file]["dbtype"] = "udeb";
325 elif file[-4:] == ".deb":
326 files[file]["dbtype"] = "deb";
328 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
329 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
330 files[file]["source"] = control.Find("Source", "");
331 if files[file]["source"] == "":
332 files[file]["source"] = files[file]["package"];
333 # Checks for a source package...
335 m = utils.re_issource.match(file)
337 files[file]["package"] = m.group(1)
338 files[file]["version"] = m.group(2)
339 files[file]["type"] = m.group(3)
341 # Ensure the source package name matches the Source filed in the .changes
342 if changes["source"] != files[file]["package"]:
343 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
345 # Ensure the source version matches the version in the .changes file
346 if files[file]["type"] == "orig.tar.gz":
347 changes_version = changes["chopversion2"]
349 changes_version = changes["chopversion"]
350 if changes_version != files[file]["version"]:
351 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
353 # Ensure the .changes lists source in the Architecture field
354 if not changes["architecture"].has_key("source"):
355 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
357 # Check the signature of a .dsc file
358 if files[file]["type"] == "dsc":
359 check_signature(file)
361 files[file]["fullname"] = file
363 # Not a binary or source package? Assume byhand...
365 files[file]["byhand"] = 1;
366 files[file]["type"] = "byhand";
368 files[file]["oldfiles"] = {}
369 for suite in changes["distribution"].keys():
371 if files[file].has_key("byhand"):
374 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
375 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
378 # See if the package is NEW
379 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
380 files[file]["new"] = 1
382 # Find any old binary packages
383 if files[file]["type"] == "deb":
384 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"
385 % (files[file]["package"], suite, files[file]["architecture"]))
386 oldfiles = q.dictresult()
387 for oldfile in oldfiles:
388 files[file]["oldfiles"][suite] = oldfile
389 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
390 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
391 reject_message = reject_message + "Rejected: %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
392 # Check for existing copies of the file
393 if not changes.has_key("stable upload"):
394 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"]))
395 if q.getresult() != []:
396 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
398 # Find any old .dsc files
399 elif files[file]["type"] == "dsc":
400 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"
401 % (files[file]["package"], suite))
402 oldfiles = q.dictresult()
403 if len(oldfiles) >= 1:
404 files[file]["oldfiles"][suite] = oldfiles[0]
406 # Validate the component
407 component = files[file]["component"];
408 component_id = db_access.get_component_id(component);
409 if component_id == -1:
410 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
413 # Validate the priority
414 if string.find(files[file]["priority"],'/') != -1:
415 reject_message = reject_message + "Rejected: file '%s' has invalid priority '%s' [contains '/'].\n" % (file, files[file]["priority"]);
417 # Check the md5sum & size against existing files (if any)
418 location = Cnf["Dir::PoolDir"];
419 files[file]["location id"] = db_access.get_location_id (location, component, archive);
421 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
422 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
424 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
426 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
427 files[file]["files id"] = files_id
429 # Check for packages that have moved from one component to another
430 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
431 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
434 if string.find(reject_message, "Rejected:") != -1:
439 ###############################################################################
442 global dsc, dsc_files, reject_message, reprocess, orig_tar_id, orig_tar_location, legacy_source_untouchable;
444 for file in files.keys():
445 if files[file]["type"] == "dsc":
447 dsc = utils.parse_changes(file, 1)
448 except utils.cant_open_exc:
449 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (file)
451 except utils.changes_parse_error_exc, line:
452 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (file, line)
454 except utils.invalid_dsc_format_exc, line:
455 reject_message = reject_message + "Rejected: syntax error in .dsc file '%s', line %s.\n" % (file, line)
458 dsc_files = utils.build_file_list(dsc, 1)
459 except utils.no_files_exc:
460 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
462 except utils.changes_parse_error_exc, line:
463 reject_message = "Rejected: error parsing .dsc file '%s', can't grok: %s.\n" % (file, line);
466 # Try and find all files mentioned in the .dsc. This has
467 # to work harder to cope with the multiple possible
468 # locations of an .orig.tar.gz.
469 for dsc_file in dsc_files.keys():
470 if files.has_key(dsc_file):
471 actual_md5 = files[dsc_file]["md5sum"];
472 actual_size = int(files[dsc_file]["size"]);
473 found = "%s in incoming" % (dsc_file)
474 # Check the file does not already exist in the archive
475 if not changes.has_key("stable upload"):
476 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));
478 # "It has not broken them. It has fixed a
479 # brokenness. Your crappy hack exploited a
480 # bug in the old dinstall.
482 # "(Come on! I thought it was always obvious
483 # that one just doesn't release different
484 # files with the same name and version.)"
485 # -- ajk@ on d-devel@l.d.o
487 if q.getresult() != []:
488 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
489 elif dsc_file[-12:] == ".orig.tar.gz":
491 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));
495 # Unfortunately, we make get more than one match
496 # here if, for example, the package was in potato
497 # but had a -sa upload in woody. So we need to a)
498 # choose the right one and b) mark all wrong ones
499 # as excluded from the source poolification (to
500 # avoid file overwrites).
502 x = ql[0]; # default to something sane in case we don't match any or have only one
506 old_file = i[0] + i[1];
507 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
508 actual_size = os.stat(old_file)[stat.ST_SIZE];
509 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
512 legacy_source_untouchable[i[3]] = "";
514 old_file = x[0] + x[1];
515 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
516 actual_size = os.stat(old_file)[stat.ST_SIZE];
519 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
522 if suite_type == "legacy" or suite_type == "legacy-mixed":
523 orig_tar_location = "legacy";
525 orig_tar_location = x[4];
527 # Not there? Check in Incoming...
528 # [See comment above process_it() for explanation
529 # of why this is necessary...]
530 if os.access(dsc_file, os.R_OK) != 0:
531 files[dsc_file] = {};
532 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
533 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
534 files[dsc_file]["section"] = files[file]["section"];
535 files[dsc_file]["priority"] = files[file]["priority"];
536 files[dsc_file]["component"] = files[file]["component"];
537 files[dsc_file]["type"] = "orig.tar.gz";
541 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);
544 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming.\n" % (file, dsc_file);
546 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
547 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
548 if actual_size != int(dsc_files[dsc_file]["size"]):
549 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
551 if string.find(reject_message, "Rejected:") != -1:
556 ###############################################################################
558 # Some cunning stunt broke dpkg-source in dpkg 1.8{,.1}; detect the
559 # resulting bad source packages and reject them.
561 # Even more amusingly the fix in 1.8.1.1 didn't actually fix the
562 # problem just changed the symptoms.
565 global dsc, dsc_files, reject_message, reprocess;
567 for filename in files.keys():
568 if files[filename]["type"] == "diff.gz":
569 file = gzip.GzipFile(filename, 'r');
570 for line in file.readlines():
571 if re_bad_diff.search(line):
572 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";
575 if string.find(reject_message, "Rejected:") != -1:
580 ###############################################################################
582 def check_md5sums ():
583 global reject_message;
585 for file in files.keys():
587 file_handle = utils.open_file(file,"r");
588 except utils.cant_open_exc:
591 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
592 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
594 def check_override ():
597 # Only check section & priority on sourceful uploads
598 if not changes["architecture"].has_key("source"):
602 for file in files.keys():
603 if not files[file].has_key("new") and files[file]["type"] == "deb":
604 section = files[file]["section"];
605 override_section = files[file]["override section"];
606 if section != override_section and section != "-":
607 # Ignore this; it's a common mistake and not worth whining about
608 if string.lower(section) == "non-us/main" and string.lower(override_section) == "non-us":
610 summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
611 priority = files[file]["priority"];
612 override_priority = files[file]["override priority"];
613 if priority != override_priority and priority != "-":
614 summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
619 Subst["__SUMMARY__"] = summary;
620 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.override-disparity","r").read());
621 utils.send_mail (mail_message, "")
623 #####################################################################################################################
625 # Set up the per-package template substitution mappings
627 def update_subst (changes_filename):
630 if changes.has_key("architecture"):
631 Subst["__ARCHITECTURE__"] = string.join(changes["architecture"].keys(), ' ' );
633 Subst["__ARCHITECTURE__"] = "Unknown";
634 Subst["__CHANGES_FILENAME__"] = os.path.basename(changes_filename);
635 Subst["__FILE_CONTENTS__"] = changes.get("filecontents", "");
636 Subst["__MAINTAINER_ADDRESS__"] = changes["maintainer822"];
637 Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown");
638 Subst["__REJECT_MESSAGE__"] = reject_message;
639 Subst["__SOURCE__"] = changes.get("source", "Unknown");
640 Subst["__VERSION__"] = changes.get("version", "Unknown");
642 #####################################################################################################################
644 def action (changes_filename):
645 byhand = confirm = suites = summary = new = "";
647 # changes["distribution"] may not exist in corner cases
648 # (e.g. unreadable changes files)
649 if not changes.has_key("distribution"):
650 changes["distribution"] = {};
652 for suite in changes["distribution"].keys():
653 if Cnf.has_key("Suite::%s::Confirm"):
654 confirm = confirm + suite + ", "
655 suites = suites + suite + ", "
656 confirm = confirm[:-2]
659 for file in files.keys():
660 if files[file].has_key("byhand"):
662 summary = summary + file + " byhand\n"
663 elif files[file].has_key("new"):
665 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
666 if files[file].has_key("othercomponents"):
667 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
668 if files[file]["type"] == "deb":
669 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
671 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
672 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
673 summary = summary + file + "\n to " + destination + "\n"
675 short_summary = summary;
677 # This is for direport's benefit...
678 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
680 if confirm or byhand or new:
681 summary = summary + "Changes: " + f;
683 summary = summary + announce (short_summary, 0)
685 (prompt, answer) = ("", "XXX")
686 if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
689 if string.find(reject_message, "Rejected") != -1:
691 modified_time = time.time()-os.path.getmtime(changes_filename);
692 except: # i.e. ignore errors like 'file does not exist';
694 if modified_time < 86400:
695 print "SKIP (too new)\n" + reject_message,;
696 prompt = "[S]kip, Manual reject, Quit ?";
698 print "REJECT\n" + reject_message,;
699 prompt = "[R]eject, Manual reject, Skip, Quit ?";
700 if Cnf["Dinstall::Options::Automatic"]:
703 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
704 prompt = "[S]kip, New ack, Manual reject, Quit ?";
705 if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
708 print "BYHAND\n" + reject_message + summary,;
709 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
711 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
712 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
714 print "INSTALL\n" + reject_message + summary,;
715 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
716 if Cnf["Dinstall::Options::Automatic"]:
719 while string.find(prompt, answer) == -1:
721 answer = utils.our_raw_input()
722 m = re_default_answer.match(prompt)
725 answer = string.upper(answer[:1])
728 reject (changes_filename, "");
730 manual_reject (changes_filename);
732 install (changes_filename, summary, short_summary);
734 acknowledge_new (changes_filename, summary);
738 #####################################################################################################################
740 def install (changes_filename, summary, short_summary):
741 global install_count, install_bytes, Subst;
743 # Stable uploads are a special case
744 if changes.has_key("stable upload"):
745 stable_install (changes_filename, summary, short_summary);
750 archive = utils.where_am_i();
752 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
753 projectB.query("BEGIN WORK");
755 # Add the .dsc file to the DB
756 for file in files.keys():
757 if files[file]["type"] == "dsc":
758 package = dsc["source"]
759 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
760 maintainer = dsc["maintainer"]
761 maintainer = string.replace(maintainer, "'", "\\'")
762 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
763 filename = files[file]["pool name"] + file;
764 dsc_location_id = files[file]["location id"];
765 if not files[file]["files id"]:
766 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
767 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
768 % (package, version, maintainer_id, files[file]["files id"]))
770 for suite in changes["distribution"].keys():
771 suite_id = db_access.get_suite_id(suite);
772 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
774 # Add the source files to the DB (files and dsc_files)
775 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
776 for dsc_file in dsc_files.keys():
777 filename = files[file]["pool name"] + dsc_file;
778 # If the .orig.tar.gz is already in the pool, it's
779 # files id is stored in dsc_files by check_dsc().
780 files_id = dsc_files[dsc_file].get("files id", None);
782 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
783 # FIXME: needs to check for -1/-2 and or handle exception
785 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
786 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
788 # Add the .deb files to the DB
789 for file in files.keys():
790 if files[file]["type"] == "deb":
791 package = files[file]["package"]
792 version = files[file]["version"]
793 maintainer = files[file]["maintainer"]
794 maintainer = string.replace(maintainer, "'", "\\'")
795 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
796 architecture = files[file]["architecture"]
797 architecture_id = db_access.get_architecture_id (architecture);
798 type = files[file]["dbtype"];
799 dsc_component = files[file]["component"]
800 source = files[file]["source"]
802 if string.find(source, "(") != -1:
803 m = utils.re_extract_src_version.match(source)
805 source_version = m.group(2)
806 if not source_version:
807 source_version = version
808 filename = files[file]["pool name"] + file;
809 if not files[file]["files id"]:
810 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
811 source_id = db_access.get_source_id (source, source_version);
813 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
814 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
816 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
817 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
818 for suite in changes["distribution"].keys():
819 suite_id = db_access.get_suite_id(suite);
820 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
822 # If the .orig.tar.gz is in a legacy directory we need to poolify
823 # it, so that apt-get source (and anything else that goes by the
824 # "Directory:" field in the Sources.gz file) works.
825 if orig_tar_id != None and orig_tar_location == "legacy":
826 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));
829 # Is this an old upload superseded by a newer -sa upload? (See check_dsc() for details)
830 if legacy_source_untouchable.has_key(qid["files_id"]):
832 # First move the files to the new location
833 legacy_filename = qid["path"]+qid["filename"];
834 pool_location = utils.poolify (changes["source"], files[file]["component"]);
835 pool_filename = pool_location + os.path.basename(qid["filename"]);
836 destination = Cnf["Dir::PoolDir"] + pool_location
837 utils.move(legacy_filename, destination);
838 # Then Update the DB's files table
839 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
841 # If this is a sourceful diff only upload that is moving non-legacy
842 # cross-component we need to copy the .orig.tar.gz into the new
843 # component too for the same reasons as above.
845 if changes["architecture"].has_key("source") and orig_tar_id != None and \
846 orig_tar_location != "legacy" and orig_tar_location != dsc_location_id:
847 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));
848 ql = q.getresult()[0];
849 old_filename = ql[0] + ql[1];
852 new_filename = utils.poolify (changes["source"], dsc_component) + os.path.basename(old_filename);
853 new_files_id = db_access.get_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
854 if new_files_id == None:
855 utils.copy(old_filename, Cnf["Dir::PoolDir"] + new_filename);
856 new_files_id = db_access.set_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
857 projectB.query("UPDATE dsc_files SET file = %s WHERE source = %s AND file = %s" % (new_files_id, source_id, orig_tar_id));
859 # Install the files into the pool
860 for file in files.keys():
861 if files[file].has_key("byhand"):
863 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
864 destdir = os.path.dirname(destination)
865 utils.move (file, destination)
866 install_bytes = install_bytes + float(files[file]["size"])
868 # Copy the .changes file across for suite which need it.
869 for suite in changes["distribution"].keys():
870 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
871 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
873 projectB.query("COMMIT WORK");
876 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
878 sys.stderr.write("W: couldn't move changes file '%s' to DONE directory [Got %s].\n" % (os.path.basename(changes_filename), sys.exc_type));
880 install_count = install_count + 1;
882 if not Cnf["Dinstall::Options::No-Mail"]:
883 Subst["__SUITE__"] = "";
884 Subst["__SUMMARY__"] = summary;
885 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.installed","r").read());
886 utils.send_mail (mail_message, "")
887 announce (short_summary, 1)
890 #####################################################################################################################
892 def stable_install (changes_filename, summary, short_summary):
893 global install_count, install_bytes, Subst;
895 print "Installing to stable."
897 archive = utils.where_am_i();
899 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
900 projectB.query("BEGIN WORK");
902 # Add the .dsc file to the DB
903 for file in files.keys():
904 if files[file]["type"] == "dsc":
905 package = dsc["source"]
906 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
907 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
910 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version));
912 source_id = ql[0][0];
913 suite_id = db_access.get_suite_id('proposed-updates');
914 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
915 suite_id = db_access.get_suite_id('stable');
916 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
918 # Add the .deb files to the DB
919 for file in files.keys():
920 if files[file]["type"] == "deb":
921 package = files[file]["package"]
922 version = files[file]["version"]
923 architecture = files[file]["architecture"]
924 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))
927 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture));
929 binary_id = ql[0][0];
930 suite_id = db_access.get_suite_id('proposed-updates');
931 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
932 suite_id = db_access.get_suite_id('stable');
933 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
935 projectB.query("COMMIT WORK");
937 utils.move (changes_filename, Cnf["Rhona::Morgue"] + os.path.basename(changes_filename));
939 # Update the Stable ChangeLog file
941 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
942 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
943 if os.path.exists(new_changelog_filename):
944 os.unlink (new_changelog_filename);
946 new_changelog = utils.open_file(new_changelog_filename, 'w');
947 for file in files.keys():
948 if files[file]["type"] == "deb":
949 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
950 elif utils.re_issource.match(file) != None:
951 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
953 new_changelog.write("%s\n" % (file));
954 chop_changes = re_fdnic.sub("\n", changes["changes"]);
955 new_changelog.write(chop_changes + '\n\n');
956 if os.access(changelog_filename, os.R_OK) != 0:
957 changelog = utils.open_file(changelog_filename, 'r');
958 new_changelog.write(changelog.read());
959 new_changelog.close();
960 if os.access(changelog_filename, os.R_OK) != 0:
961 os.unlink(changelog_filename);
962 utils.move(new_changelog_filename, changelog_filename);
964 install_count = install_count + 1;
966 if not Cnf["Dinstall::Options::No-Mail"]:
967 Subst["__SUITE__"] = " into stable";
968 Subst["__SUMMARY__"] = summary;
969 utils.send_mail (mail_message, "")
970 announce (short_summary, 1)
972 #####################################################################################################################
974 def reject (changes_filename, manual_reject_mail_filename):
979 base_changes_filename = os.path.basename(changes_filename);
980 reason_filename = re_changes.sub("reason", base_changes_filename);
981 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
983 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
985 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
987 sys.stderr.write("W: couldn't reject changes file '%s' [Got %s].\n" % (base_changes_filename, sys.exc_type));
989 for file in files.keys():
990 if os.path.exists(file):
992 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
994 sys.stderr.write("W: couldn't reject file '%s' [Got %s].\n" % (file, sys.exc_type));
997 # If this is not a manual rejection generate the .reason file and rejection mail message
998 if manual_reject_mail_filename == "":
999 if os.path.exists(reject_filename):
1000 os.unlink(reject_filename);
1001 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1002 os.write(fd, reject_message);
1004 Subst["__MANUAL_REJECT_MESSAGE__"] = "";
1005 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1006 else: # Have a manual rejection file to use
1007 reject_mail_message = ""; # avoid <undef>'s
1009 # Send the rejection mail if appropriate
1010 if not Cnf["Dinstall::Options::No-Mail"]:
1011 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
1013 ##################################################################
1015 def manual_reject (changes_filename):
1018 # Build up the rejection email
1019 user_email_address = string.replace(string.split(pwd.getpwuid(os.getuid())[4],',')[0], '.', '')
1020 user_email_address = user_email_address + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
1021 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
1023 Subst["__MANUAL_REJECT_MESSAGE__"] = manual_reject_message;
1024 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1026 # Write the rejection email out as the <foo>.reason file
1027 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
1028 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
1029 if os.path.exists(reject_filename):
1030 os.unlink(reject_filename);
1031 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1032 os.write(fd, reject_mail_message);
1035 # If we weren't given one, spawn an editor so the user can add one in
1036 if manual_reject_message == "":
1037 result = os.system("vi +6 %s" % (reject_file))
1039 sys.stderr.write ("vi invocation failed for `%s'!\n" % (reject_file))
1042 # Then process it as if it were an automatic rejection
1043 reject (changes_filename, reject_filename)
1045 #####################################################################################################################
1047 def acknowledge_new (changes_filename, summary):
1048 global new_ack_new, Subst;
1050 changes_filename = os.path.basename(changes_filename);
1052 new_ack_new[changes_filename] = 1;
1054 if new_ack_old.has_key(changes_filename):
1055 print "Ack already sent.";
1058 print "Sending new ack.";
1059 if not Cnf["Dinstall::Options::No-Mail"]:
1060 Subst["__SUMMARY__"] = summary;
1061 new_ack_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.new","r").read());
1062 utils.send_mail(new_ack_message,"");
1064 #####################################################################################################################
1066 def announce (short_summary, action):
1069 # Only do announcements for source uploads with a recent dpkg-dev installed
1070 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
1075 Subst["__SHORT_SUMMARY__"] = short_summary;
1077 for dist in changes["distribution"].keys():
1078 list = Cnf.Find("Suite::%s::Announce" % (dist))
1079 if list == "" or lists_done.has_key(list):
1081 lists_done[list] = 1
1082 summary = summary + "Announcing to %s\n" % (list)
1085 Subst["__ANNOUNCE_LIST_ADDRESS__"] = list;
1086 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.announce","r").read());
1087 utils.send_mail (mail_message, "")
1089 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
1090 bugs = changes["closes"].keys()
1092 # changes["changedbyname"] == dsc_name is probably never true, but better
1094 if dsc_name == changes["maintainername"] and (changes["changedbyname"] == "" or changes["changedbyname"] == dsc_name):
1095 summary = summary + "Closing bugs: "
1097 summary = summary + "%s " % (bug)
1099 Subst["__BUG_NUMBER__"] = bug;
1100 if changes["distribution"].has_key("stable"):
1101 Subst["__STABLE_WARNING__"] = """
1102 Note that this package is not part of the released stable Debian
1103 distribution. It may have dependencies on other unreleased software,
1104 or other instabilities. Please take care if you wish to install it.
1105 The update will eventually make its way into the next released Debian
1108 Subst["__STABLE_WARNING__"] = "";
1109 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-close","r").read());
1110 utils.send_mail (mail_message, "")
1112 summary = summary + "Setting bugs to severity fixed: "
1113 control_message = ""
1115 summary = summary + "%s " % (bug)
1116 control_message = control_message + "tag %s + fixed\n" % (bug)
1117 if action and control_message != "":
1118 Subst["__CONTROL_MESSAGE__"] = control_message;
1119 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-nmu-fixed","r").read());
1120 utils.send_mail (mail_message, "")
1121 summary = summary + "\n"
1125 ###############################################################################
1127 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1128 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1129 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1130 # processed it during it's checks of -2. If -1 has been deleted or
1131 # otherwise not checked by da-install, the .orig.tar.gz will not have
1132 # been checked at all. To get round this, we force the .orig.tar.gz
1133 # into the .changes structure and reprocess the .changes file.
1135 def process_it (changes_file):
1136 global reprocess, orig_tar_id, orig_tar_location, changes, dsc, dsc_files, files, reject_message;
1138 # Reset some globals
1145 orig_tar_location = "";
1146 legacy_source_untouchable = {};
1147 reject_message = "";
1149 # Absolutize the filename to avoid the requirement of being in the
1150 # same directory as the .changes file.
1151 changes_file = os.path.abspath(changes_file);
1153 # And since handling of installs to stable munges with the CWD;
1154 # save and restore it.
1157 check_signature (changes_file);
1158 check_changes (changes_file);
1166 update_subst(changes_file);
1167 action(changes_file);
1172 ###############################################################################
1175 global Cnf, projectB, install_bytes, new_ack_old, Subst
1179 Cnf = apt_pkg.newConfiguration();
1180 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1182 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1183 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1184 ('h',"help","Dinstall::Options::Help"),
1185 ('k',"ack-new","Dinstall::Options::Ack-New"),
1186 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1187 ('n',"no-action","Dinstall::Options::No-Action"),
1188 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1189 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1190 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1191 ('v',"version","Dinstall::Options::Version")];
1193 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1195 if Cnf["Dinstall::Options::Help"]:
1198 if Cnf["Dinstall::Options::Version"]:
1199 print "katie version 0.0000000000";
1202 postgresql_user = None; # Default == Connect as user running program.
1204 # -n/--dry-run invalidates some other options which would involve things happening
1205 if Cnf["Dinstall::Options::No-Action"]:
1206 Cnf["Dinstall::Options::Automatic"] = ""
1207 Cnf["Dinstall::Options::Ack-New"] = ""
1208 postgresql_user = Cnf["DB::ROUser"];
1210 projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1212 db_access.init(Cnf, projectB);
1214 # Check that we aren't going to clash with the daily cron job
1216 if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1217 sys.stderr.write("Archive maintenance in progress. Try again later.\n");
1220 # Obtain lock if not in no-action mode
1222 if not Cnf["Dinstall::Options::No-Action"]:
1223 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1224 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1226 # Read in the list of already-acknowledged NEW packages
1227 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1229 for line in new_ack_list.readlines():
1230 new_ack_old[line[:-1]] = 1;
1231 new_ack_list.close();
1233 # Initialize the substitution template mapping global
1235 Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"];
1236 Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"];
1237 bcc = "X-Katie: $Revision: 1.37 $"
1238 if Cnf.has_key("Dinstall::Bcc"):
1239 Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"]);
1241 Subst["__BCC__"] = bcc;
1242 Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"];
1243 Subst["__KATIE_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"];
1245 # Process the changes files
1246 for changes_file in changes_files:
1247 print "\n" + changes_file;
1248 process_it (changes_file);
1252 if install_count > 1:
1254 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1256 # Write out the list of already-acknowledged NEW packages
1257 if Cnf["Dinstall::Options::Ack-New"]:
1258 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1259 for i in new_ack_new.keys():
1260 new_ack_list.write(i+'\n')
1261 new_ack_list.close()
1264 if __name__ == '__main__':