3 # Installs Debian packaes
4 # Copyright (C) 2000, 2001 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.38 2001-04-13 20:18:41 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["changedby822"], 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 # Ensure all the values in Closes: are numbers
188 if changes.has_key("closes"):
189 for i in changes["closes"].keys():
190 if re_isanum.match (i) == None:
191 reject_message = reject_message + "Rejected: `%s' from Closes field isn't a number.\n" % (i)
193 # Map frozen to unstable if frozen doesn't exist
194 if changes["distribution"].has_key("frozen") and not Cnf.has_key("Suite::Frozen"):
195 del changes["distribution"]["frozen"]
196 changes["distribution"]["unstable"] = 1;
197 reject_message = reject_message + "Mapping frozen to unstable.\n"
199 # Map testing to unstable
200 if changes["distribution"].has_key("testing"):
201 del changes["distribution"]["testing"]
202 changes["distribution"]["unstable"] = 1;
203 reject_message = reject_message + "Mapping testing to unstable.\n"
205 # Ensure target distributions exist
206 for i in changes["distribution"].keys():
207 if not Cnf.has_key("Suite::%s" % (i)):
208 reject_message = reject_message + "Rejected: Unknown distribution `%s'.\n" % (i)
210 # Ensure there _is_ a target distribution
211 if changes["distribution"].keys() == []:
212 reject_message = reject_message + "Rejected: huh? Distribution field is empty in changes file.\n";
214 # Map unreleased arches from stable to unstable
215 if changes["distribution"].has_key("stable"):
216 for i in changes["architecture"].keys():
217 if not Cnf.has_key("Suite::Stable::Architectures::%s" % (i)):
218 reject_message = reject_message + "Mapping stable to unstable for unreleased arch `%s'.\n" % (i)
219 del changes["distribution"]["stable"]
220 changes["distribution"]["unstable"] = 1;
222 # Map arches not being released from frozen to unstable
223 if changes["distribution"].has_key("frozen"):
224 for i in changes["architecture"].keys():
225 if not Cnf.has_key("Suite::Frozen::Architectures::%s" % (i)):
226 reject_message = reject_message + "Mapping frozen to unstable for non-releasing arch `%s'.\n" % (i)
227 del changes["distribution"]["frozen"]
228 changes["distribution"]["unstable"] = 1;
230 # Handle uploads to stable
231 if changes["distribution"].has_key("stable"):
232 # If running from within proposed-updates; assume an install to stable
233 if string.find(os.getcwd(), 'proposed-updates') != -1:
234 # FIXME: should probably remove anything that != stable
235 for i in ("frozen", "unstable"):
236 if changes["distribution"].has_key(i):
237 reject_message = reject_message + "Removing %s from distribution list.\n"
238 del changes["distribution"][i]
239 changes["stable upload"] = 1;
240 # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool
241 file = files.keys()[0];
242 if os.access(file, os.R_OK) == 0:
243 pool_dir = Cnf["Dir::PoolDir"] + '/' + utils.poolify(changes["source"], files[file]["component"]);
245 # Otherwise (normal case) map stable to updates
247 reject_message = reject_message + "Mapping stable to updates.\n";
248 del changes["distribution"]["stable"];
249 changes["distribution"]["proposed-updates"] = 1;
251 # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
252 changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
253 changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
255 if string.find(reject_message, "Rejected:") != -1:
261 global reject_message
263 archive = utils.where_am_i();
265 for file in files.keys():
266 # Check the file is readable
267 if os.access(file,os.R_OK) == 0:
268 reject_message = reject_message + "Rejected: Can't read `%s'.\n" % (file)
269 files[file]["type"] = "unreadable";
271 # If it's byhand skip remaining checks
272 if files[file]["section"] == "byhand":
273 files[file]["byhand"] = 1;
274 files[file]["type"] = "byhand";
275 # Checks for a binary package...
276 elif utils.re_isadeb.match(file) != None:
277 files[file]["type"] = "deb";
279 # Extract package information using dpkg-deb
281 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
283 reject_message = reject_message + "Rejected: %s: debExtractControl() raised %s.\n" % (file, sys.exc_type);
284 # Can't continue, none of the checks on control would work.
287 # Check for mandatory fields
288 if control.Find("Package") == None:
289 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
290 if control.Find("Architecture") == None:
291 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
292 if control.Find("Version") == None:
293 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
295 # Ensure the package name matches the one give in the .changes
296 if not changes["binary"].has_key(control.Find("Package", "")):
297 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
299 # Validate the architecture
300 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
301 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
303 # Check the architecture matches the one given in the .changes
304 if not changes["architecture"].has_key(control.Find("Architecture", "")):
305 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
306 # Check the section & priority match those given in the .changes (non-fatal)
307 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
308 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"])
309 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
310 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"])
312 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
314 files[file]["package"] = control.Find("Package");
315 files[file]["architecture"] = control.Find("Architecture");
316 files[file]["version"] = control.Find("Version");
317 files[file]["maintainer"] = control.Find("Maintainer", "");
318 if file[-5:] == ".udeb":
319 files[file]["dbtype"] = "udeb";
320 elif file[-4:] == ".deb":
321 files[file]["dbtype"] = "deb";
323 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
324 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
325 files[file]["source"] = control.Find("Source", "");
326 if files[file]["source"] == "":
327 files[file]["source"] = files[file]["package"];
328 # Checks for a source package...
330 m = utils.re_issource.match(file)
332 files[file]["package"] = m.group(1)
333 files[file]["version"] = m.group(2)
334 files[file]["type"] = m.group(3)
336 # Ensure the source package name matches the Source filed in the .changes
337 if changes["source"] != files[file]["package"]:
338 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
340 # Ensure the source version matches the version in the .changes file
341 if files[file]["type"] == "orig.tar.gz":
342 changes_version = changes["chopversion2"]
344 changes_version = changes["chopversion"]
345 if changes_version != files[file]["version"]:
346 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
348 # Ensure the .changes lists source in the Architecture field
349 if not changes["architecture"].has_key("source"):
350 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
352 # Check the signature of a .dsc file
353 if files[file]["type"] == "dsc":
354 check_signature(file)
356 files[file]["fullname"] = file
358 # Not a binary or source package? Assume byhand...
360 files[file]["byhand"] = 1;
361 files[file]["type"] = "byhand";
363 files[file]["oldfiles"] = {}
364 for suite in changes["distribution"].keys():
366 if files[file].has_key("byhand"):
369 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
370 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
373 # See if the package is NEW
374 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
375 files[file]["new"] = 1
377 # Find any old binary packages
378 if files[file]["type"] == "deb":
379 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"
380 % (files[file]["package"], suite, files[file]["architecture"]))
381 oldfiles = q.dictresult()
382 for oldfile in oldfiles:
383 files[file]["oldfiles"][suite] = oldfile
384 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
385 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
386 reject_message = reject_message + "Rejected: %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
387 # Check for existing copies of the file
388 if not changes.has_key("stable upload"):
389 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"]))
390 if q.getresult() != []:
391 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
393 # Find any old .dsc files
394 elif files[file]["type"] == "dsc":
395 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"
396 % (files[file]["package"], suite))
397 oldfiles = q.dictresult()
398 if len(oldfiles) >= 1:
399 files[file]["oldfiles"][suite] = oldfiles[0]
401 # Validate the component
402 component = files[file]["component"];
403 component_id = db_access.get_component_id(component);
404 if component_id == -1:
405 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
408 # Validate the priority
409 if string.find(files[file]["priority"],'/') != -1:
410 reject_message = reject_message + "Rejected: file '%s' has invalid priority '%s' [contains '/'].\n" % (file, files[file]["priority"]);
412 # Check the md5sum & size against existing files (if any)
413 location = Cnf["Dir::PoolDir"];
414 files[file]["location id"] = db_access.get_location_id (location, component, archive);
416 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
417 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
419 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
421 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
422 files[file]["files id"] = files_id
424 # Check for packages that have moved from one component to another
425 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
426 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
429 if string.find(reject_message, "Rejected:") != -1:
434 ###############################################################################
437 global dsc, dsc_files, reject_message, reprocess, orig_tar_id, orig_tar_location, legacy_source_untouchable;
439 for file in files.keys():
440 if files[file]["type"] == "dsc":
442 dsc = utils.parse_changes(file, 1)
443 except utils.cant_open_exc:
444 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (file)
446 except utils.changes_parse_error_exc, line:
447 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (file, line)
449 except utils.invalid_dsc_format_exc, line:
450 reject_message = reject_message + "Rejected: syntax error in .dsc file '%s', line %s.\n" % (file, line)
453 dsc_files = utils.build_file_list(dsc, 1)
454 except utils.no_files_exc:
455 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
457 except utils.changes_parse_error_exc, line:
458 reject_message = "Rejected: error parsing .dsc file '%s', can't grok: %s.\n" % (file, line);
461 # Try and find all files mentioned in the .dsc. This has
462 # to work harder to cope with the multiple possible
463 # locations of an .orig.tar.gz.
464 for dsc_file in dsc_files.keys():
465 if files.has_key(dsc_file):
466 actual_md5 = files[dsc_file]["md5sum"];
467 actual_size = int(files[dsc_file]["size"]);
468 found = "%s in incoming" % (dsc_file)
469 # Check the file does not already exist in the archive
470 if not changes.has_key("stable upload"):
471 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));
473 # "It has not broken them. It has fixed a
474 # brokenness. Your crappy hack exploited a
475 # bug in the old dinstall.
477 # "(Come on! I thought it was always obvious
478 # that one just doesn't release different
479 # files with the same name and version.)"
480 # -- ajk@ on d-devel@l.d.o
482 if q.getresult() != []:
483 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
484 elif dsc_file[-12:] == ".orig.tar.gz":
486 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));
490 # Unfortunately, we make get more than one match
491 # here if, for example, the package was in potato
492 # but had a -sa upload in woody. So we need to a)
493 # choose the right one and b) mark all wrong ones
494 # as excluded from the source poolification (to
495 # avoid file overwrites).
497 x = ql[0]; # default to something sane in case we don't match any or have only one
501 old_file = i[0] + i[1];
502 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
503 actual_size = os.stat(old_file)[stat.ST_SIZE];
504 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
507 legacy_source_untouchable[i[3]] = "";
509 old_file = x[0] + x[1];
510 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
511 actual_size = os.stat(old_file)[stat.ST_SIZE];
514 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
517 if suite_type == "legacy" or suite_type == "legacy-mixed":
518 orig_tar_location = "legacy";
520 orig_tar_location = x[4];
522 # Not there? Check in Incoming...
523 # [See comment above process_it() for explanation
524 # of why this is necessary...]
525 if os.access(dsc_file, os.R_OK) != 0:
526 files[dsc_file] = {};
527 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
528 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
529 files[dsc_file]["section"] = files[file]["section"];
530 files[dsc_file]["priority"] = files[file]["priority"];
531 files[dsc_file]["component"] = files[file]["component"];
532 files[dsc_file]["type"] = "orig.tar.gz";
536 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);
539 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming.\n" % (file, dsc_file);
541 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
542 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
543 if actual_size != int(dsc_files[dsc_file]["size"]):
544 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
546 if string.find(reject_message, "Rejected:") != -1:
551 ###############################################################################
553 # Some cunning stunt broke dpkg-source in dpkg 1.8{,.1}; detect the
554 # resulting bad source packages and reject them.
556 # Even more amusingly the fix in 1.8.1.1 didn't actually fix the
557 # problem just changed the symptoms.
560 global dsc, dsc_files, reject_message, reprocess;
562 for filename in files.keys():
563 if files[filename]["type"] == "diff.gz":
564 file = gzip.GzipFile(filename, 'r');
565 for line in file.readlines():
566 if re_bad_diff.search(line):
567 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";
570 if string.find(reject_message, "Rejected:") != -1:
575 ###############################################################################
577 def check_md5sums ():
578 global reject_message;
580 for file in files.keys():
582 file_handle = utils.open_file(file,"r");
583 except utils.cant_open_exc:
586 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
587 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
589 def check_override ():
592 # Only check section & priority on sourceful uploads
593 if not changes["architecture"].has_key("source"):
597 for file in files.keys():
598 if not files[file].has_key("new") and files[file]["type"] == "deb":
599 section = files[file]["section"];
600 override_section = files[file]["override section"];
601 if section != override_section and section != "-":
602 # Ignore this; it's a common mistake and not worth whining about
603 if string.lower(section) == "non-us/main" and string.lower(override_section) == "non-us":
605 summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
606 priority = files[file]["priority"];
607 override_priority = files[file]["override priority"];
608 if priority != override_priority and priority != "-":
609 summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
614 Subst["__SUMMARY__"] = summary;
615 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.override-disparity","r").read());
616 utils.send_mail (mail_message, "")
618 #####################################################################################################################
620 # Set up the per-package template substitution mappings
622 def update_subst (changes_filename):
625 if changes.has_key("architecture"):
626 Subst["__ARCHITECTURE__"] = string.join(changes["architecture"].keys(), ' ' );
628 Subst["__ARCHITECTURE__"] = "Unknown";
629 Subst["__CHANGES_FILENAME__"] = os.path.basename(changes_filename);
630 Subst["__FILE_CONTENTS__"] = changes.get("filecontents", "");
632 # For source uploads the Changed-By field wins; otherwise Maintainer wins.
633 if changes["architecture"].has_key("source") and changes["changedby822"] != "" and (changes["changedby822"] != changes["maintainer822"]):
634 Subst["__MAINTAINER_FROM__"] = changes["changedby822"];
635 Subst["__MAINTAINER_TO__"] = changes["changedby822"] + ", " + changes["maintainer822"];
636 Subst["__MAINTAINER__"] = changes["changedby"];
638 Subst["__MAINTAINER_FROM__"] = changes["maintainer822"];
639 Subst["__MAINTAINER_TO__"] = changes["maintainer822"];
640 Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown");
642 Subst["__REJECT_MESSAGE__"] = reject_message;
643 Subst["__SOURCE__"] = changes.get("source", "Unknown");
644 Subst["__VERSION__"] = changes.get("version", "Unknown");
646 #####################################################################################################################
648 def action (changes_filename):
649 byhand = confirm = suites = summary = new = "";
651 # changes["distribution"] may not exist in corner cases
652 # (e.g. unreadable changes files)
653 if not changes.has_key("distribution"):
654 changes["distribution"] = {};
656 for suite in changes["distribution"].keys():
657 if Cnf.has_key("Suite::%s::Confirm"):
658 confirm = confirm + suite + ", "
659 suites = suites + suite + ", "
660 confirm = confirm[:-2]
663 for file in files.keys():
664 if files[file].has_key("byhand"):
666 summary = summary + file + " byhand\n"
667 elif files[file].has_key("new"):
669 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
670 if files[file].has_key("othercomponents"):
671 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
672 if files[file]["type"] == "deb":
673 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
675 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
676 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
677 summary = summary + file + "\n to " + destination + "\n"
679 short_summary = summary;
681 # This is for direport's benefit...
682 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
684 if confirm or byhand or new:
685 summary = summary + "Changes: " + f;
687 summary = summary + announce (short_summary, 0)
689 (prompt, answer) = ("", "XXX")
690 if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
693 if string.find(reject_message, "Rejected") != -1:
695 modified_time = time.time()-os.path.getmtime(changes_filename);
696 except: # i.e. ignore errors like 'file does not exist';
698 if modified_time < 86400:
699 print "SKIP (too new)\n" + reject_message,;
700 prompt = "[S]kip, Manual reject, Quit ?";
702 print "REJECT\n" + reject_message,;
703 prompt = "[R]eject, Manual reject, Skip, Quit ?";
704 if Cnf["Dinstall::Options::Automatic"]:
707 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
708 prompt = "[S]kip, New ack, Manual reject, Quit ?";
709 if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
712 print "BYHAND\n" + reject_message + summary,;
713 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
715 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
716 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
718 print "INSTALL\n" + reject_message + summary,;
719 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
720 if Cnf["Dinstall::Options::Automatic"]:
723 while string.find(prompt, answer) == -1:
725 answer = utils.our_raw_input()
726 m = re_default_answer.match(prompt)
729 answer = string.upper(answer[:1])
732 reject (changes_filename, "");
734 manual_reject (changes_filename);
736 install (changes_filename, summary, short_summary);
738 acknowledge_new (changes_filename, summary);
742 #####################################################################################################################
744 def install (changes_filename, summary, short_summary):
745 global install_count, install_bytes, Subst;
747 # Stable uploads are a special case
748 if changes.has_key("stable upload"):
749 stable_install (changes_filename, summary, short_summary);
754 archive = utils.where_am_i();
756 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
757 projectB.query("BEGIN WORK");
759 # Add the .dsc file to the DB
760 for file in files.keys():
761 if files[file]["type"] == "dsc":
762 package = dsc["source"]
763 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
764 maintainer = dsc["maintainer"]
765 maintainer = string.replace(maintainer, "'", "\\'")
766 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
767 filename = files[file]["pool name"] + file;
768 dsc_location_id = files[file]["location id"];
769 if not files[file]["files id"]:
770 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
771 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
772 % (package, version, maintainer_id, files[file]["files id"]))
774 for suite in changes["distribution"].keys():
775 suite_id = db_access.get_suite_id(suite);
776 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
778 # Add the source files to the DB (files and dsc_files)
779 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
780 for dsc_file in dsc_files.keys():
781 filename = files[file]["pool name"] + dsc_file;
782 # If the .orig.tar.gz is already in the pool, it's
783 # files id is stored in dsc_files by check_dsc().
784 files_id = dsc_files[dsc_file].get("files id", None);
786 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
787 # FIXME: needs to check for -1/-2 and or handle exception
789 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
790 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
792 # Add the .deb files to the DB
793 for file in files.keys():
794 if files[file]["type"] == "deb":
795 package = files[file]["package"]
796 version = files[file]["version"]
797 maintainer = files[file]["maintainer"]
798 maintainer = string.replace(maintainer, "'", "\\'")
799 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
800 architecture = files[file]["architecture"]
801 architecture_id = db_access.get_architecture_id (architecture);
802 type = files[file]["dbtype"];
803 dsc_component = files[file]["component"]
804 source = files[file]["source"]
806 if string.find(source, "(") != -1:
807 m = utils.re_extract_src_version.match(source)
809 source_version = m.group(2)
810 if not source_version:
811 source_version = version
812 filename = files[file]["pool name"] + file;
813 if not files[file]["files id"]:
814 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
815 source_id = db_access.get_source_id (source, source_version);
817 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
818 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
820 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
821 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
822 for suite in changes["distribution"].keys():
823 suite_id = db_access.get_suite_id(suite);
824 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
826 # If the .orig.tar.gz is in a legacy directory we need to poolify
827 # it, so that apt-get source (and anything else that goes by the
828 # "Directory:" field in the Sources.gz file) works.
829 if orig_tar_id != None and orig_tar_location == "legacy":
830 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));
833 # Is this an old upload superseded by a newer -sa upload? (See check_dsc() for details)
834 if legacy_source_untouchable.has_key(qid["files_id"]):
836 # First move the files to the new location
837 legacy_filename = qid["path"]+qid["filename"];
838 pool_location = utils.poolify (changes["source"], files[file]["component"]);
839 pool_filename = pool_location + os.path.basename(qid["filename"]);
840 destination = Cnf["Dir::PoolDir"] + pool_location
841 utils.move(legacy_filename, destination);
842 # Then Update the DB's files table
843 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
845 # If this is a sourceful diff only upload that is moving non-legacy
846 # cross-component we need to copy the .orig.tar.gz into the new
847 # component too for the same reasons as above.
849 if changes["architecture"].has_key("source") and orig_tar_id != None and \
850 orig_tar_location != "legacy" and orig_tar_location != dsc_location_id:
851 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));
852 ql = q.getresult()[0];
853 old_filename = ql[0] + ql[1];
856 new_filename = utils.poolify (changes["source"], dsc_component) + os.path.basename(old_filename);
857 new_files_id = db_access.get_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
858 if new_files_id == None:
859 utils.copy(old_filename, Cnf["Dir::PoolDir"] + new_filename);
860 new_files_id = db_access.set_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
861 projectB.query("UPDATE dsc_files SET file = %s WHERE source = %s AND file = %s" % (new_files_id, source_id, orig_tar_id));
863 # Install the files into the pool
864 for file in files.keys():
865 if files[file].has_key("byhand"):
867 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
868 destdir = os.path.dirname(destination)
869 utils.move (file, destination)
870 install_bytes = install_bytes + float(files[file]["size"])
872 # Copy the .changes file across for suite which need it.
873 for suite in changes["distribution"].keys():
874 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
875 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
877 projectB.query("COMMIT WORK");
880 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
882 sys.stderr.write("W: couldn't move changes file '%s' to DONE directory [Got %s].\n" % (os.path.basename(changes_filename), sys.exc_type));
884 install_count = install_count + 1;
886 if not Cnf["Dinstall::Options::No-Mail"]:
887 Subst["__SUITE__"] = "";
888 Subst["__SUMMARY__"] = summary;
889 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.installed","r").read());
890 utils.send_mail (mail_message, "")
891 announce (short_summary, 1)
894 #####################################################################################################################
896 def stable_install (changes_filename, summary, short_summary):
897 global install_count, install_bytes, Subst;
899 print "Installing to stable."
901 archive = utils.where_am_i();
903 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
904 projectB.query("BEGIN WORK");
906 # Add the .dsc file to the DB
907 for file in files.keys():
908 if files[file]["type"] == "dsc":
909 package = dsc["source"]
910 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
911 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
914 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version));
916 source_id = ql[0][0];
917 suite_id = db_access.get_suite_id('proposed-updates');
918 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
919 suite_id = db_access.get_suite_id('stable');
920 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
922 # Add the .deb files to the DB
923 for file in files.keys():
924 if files[file]["type"] == "deb":
925 package = files[file]["package"]
926 version = files[file]["version"]
927 architecture = files[file]["architecture"]
928 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))
931 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture));
933 binary_id = ql[0][0];
934 suite_id = db_access.get_suite_id('proposed-updates');
935 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
936 suite_id = db_access.get_suite_id('stable');
937 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
939 projectB.query("COMMIT WORK");
941 utils.move (changes_filename, Cnf["Rhona::Morgue"] + os.path.basename(changes_filename));
943 # Update the Stable ChangeLog file
945 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
946 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
947 if os.path.exists(new_changelog_filename):
948 os.unlink (new_changelog_filename);
950 new_changelog = utils.open_file(new_changelog_filename, 'w');
951 for file in files.keys():
952 if files[file]["type"] == "deb":
953 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
954 elif utils.re_issource.match(file) != None:
955 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
957 new_changelog.write("%s\n" % (file));
958 chop_changes = re_fdnic.sub("\n", changes["changes"]);
959 new_changelog.write(chop_changes + '\n\n');
960 if os.access(changelog_filename, os.R_OK) != 0:
961 changelog = utils.open_file(changelog_filename, 'r');
962 new_changelog.write(changelog.read());
963 new_changelog.close();
964 if os.access(changelog_filename, os.R_OK) != 0:
965 os.unlink(changelog_filename);
966 utils.move(new_changelog_filename, changelog_filename);
968 install_count = install_count + 1;
970 if not Cnf["Dinstall::Options::No-Mail"]:
971 Subst["__SUITE__"] = " into stable";
972 Subst["__SUMMARY__"] = summary;
973 utils.send_mail (mail_message, "")
974 announce (short_summary, 1)
976 #####################################################################################################################
978 def reject (changes_filename, manual_reject_mail_filename):
983 base_changes_filename = os.path.basename(changes_filename);
984 reason_filename = re_changes.sub("reason", base_changes_filename);
985 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
987 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
989 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
991 sys.stderr.write("W: couldn't reject changes file '%s' [Got %s].\n" % (base_changes_filename, sys.exc_type));
993 for file in files.keys():
994 if os.path.exists(file):
996 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
998 sys.stderr.write("W: couldn't reject file '%s' [Got %s].\n" % (file, sys.exc_type));
1001 # If this is not a manual rejection generate the .reason file and rejection mail message
1002 if manual_reject_mail_filename == "":
1003 if os.path.exists(reject_filename):
1004 os.unlink(reject_filename);
1005 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1006 os.write(fd, reject_message);
1008 Subst["__MANUAL_REJECT_MESSAGE__"] = "";
1009 Subst["__CC__"] = "X-Katie-Rejection: automatic (moo)";
1010 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1011 else: # Have a manual rejection file to use
1012 reject_mail_message = ""; # avoid <undef>'s
1014 # Send the rejection mail if appropriate
1015 if not Cnf["Dinstall::Options::No-Mail"]:
1016 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
1018 ##################################################################
1020 def manual_reject (changes_filename):
1023 # Build up the rejection email
1024 user_email_address = utils.whoami() + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
1025 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
1027 Subst["__MANUAL_REJECT_MESSAGE__"] = manual_reject_message;
1028 Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"];
1029 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1031 # Write the rejection email out as the <foo>.reason file
1032 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
1033 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
1034 if os.path.exists(reject_filename):
1035 os.unlink(reject_filename);
1036 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1037 os.write(fd, reject_mail_message);
1040 # If we weren't given one, spawn an editor so the user can add one in
1041 if manual_reject_message == "":
1042 result = os.system("vi +6 %s" % (reject_file))
1044 sys.stderr.write ("vi invocation failed for `%s'!\n" % (reject_file))
1047 # Then process it as if it were an automatic rejection
1048 reject (changes_filename, reject_filename)
1050 #####################################################################################################################
1052 def acknowledge_new (changes_filename, summary):
1053 global new_ack_new, Subst;
1055 changes_filename = os.path.basename(changes_filename);
1057 new_ack_new[changes_filename] = 1;
1059 if new_ack_old.has_key(changes_filename):
1060 print "Ack already sent.";
1063 print "Sending new ack.";
1064 if not Cnf["Dinstall::Options::No-Mail"]:
1065 Subst["__SUMMARY__"] = summary;
1066 new_ack_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.new","r").read());
1067 utils.send_mail(new_ack_message,"");
1069 #####################################################################################################################
1071 def announce (short_summary, action):
1074 # Only do announcements for source uploads with a recent dpkg-dev installed
1075 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
1080 Subst["__SHORT_SUMMARY__"] = short_summary;
1082 for dist in changes["distribution"].keys():
1083 list = Cnf.Find("Suite::%s::Announce" % (dist))
1084 if list == "" or lists_done.has_key(list):
1086 lists_done[list] = 1
1087 summary = summary + "Announcing to %s\n" % (list)
1090 Subst["__ANNOUNCE_LIST_ADDRESS__"] = list;
1091 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.announce","r").read());
1092 utils.send_mail (mail_message, "")
1094 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
1095 bugs = changes["closes"].keys()
1097 # changes["changedbyname"] == dsc_name is probably never true, but better
1099 if dsc_name == changes["maintainername"] and (changes["changedbyname"] == "" or changes["changedbyname"] == dsc_name):
1100 summary = summary + "Closing bugs: "
1102 summary = summary + "%s " % (bug)
1104 Subst["__BUG_NUMBER__"] = bug;
1105 if changes["distribution"].has_key("stable"):
1106 Subst["__STABLE_WARNING__"] = """
1107 Note that this package is not part of the released stable Debian
1108 distribution. It may have dependencies on other unreleased software,
1109 or other instabilities. Please take care if you wish to install it.
1110 The update will eventually make its way into the next released Debian
1113 Subst["__STABLE_WARNING__"] = "";
1114 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-close","r").read());
1115 utils.send_mail (mail_message, "")
1117 summary = summary + "Setting bugs to severity fixed: "
1118 control_message = ""
1120 summary = summary + "%s " % (bug)
1121 control_message = control_message + "tag %s + fixed\n" % (bug)
1122 if action and control_message != "":
1123 Subst["__CONTROL_MESSAGE__"] = control_message;
1124 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-nmu-fixed","r").read());
1125 utils.send_mail (mail_message, "")
1126 summary = summary + "\n"
1130 ###############################################################################
1132 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1133 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1134 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1135 # processed it during it's checks of -2. If -1 has been deleted or
1136 # otherwise not checked by da-install, the .orig.tar.gz will not have
1137 # been checked at all. To get round this, we force the .orig.tar.gz
1138 # into the .changes structure and reprocess the .changes file.
1140 def process_it (changes_file):
1141 global reprocess, orig_tar_id, orig_tar_location, changes, dsc, dsc_files, files, reject_message;
1143 # Reset some globals
1150 orig_tar_location = "";
1151 legacy_source_untouchable = {};
1152 reject_message = "";
1154 # Absolutize the filename to avoid the requirement of being in the
1155 # same directory as the .changes file.
1156 changes_file = os.path.abspath(changes_file);
1158 # And since handling of installs to stable munges with the CWD;
1159 # save and restore it.
1162 check_signature (changes_file);
1163 check_changes (changes_file);
1171 update_subst(changes_file);
1172 action(changes_file);
1177 ###############################################################################
1180 global Cnf, projectB, install_bytes, new_ack_old, Subst
1184 Cnf = apt_pkg.newConfiguration();
1185 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1187 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1188 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1189 ('h',"help","Dinstall::Options::Help"),
1190 ('k',"ack-new","Dinstall::Options::Ack-New"),
1191 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1192 ('n',"no-action","Dinstall::Options::No-Action"),
1193 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1194 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1195 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1196 ('v',"version","Dinstall::Options::Version")];
1198 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1200 if Cnf["Dinstall::Options::Help"]:
1203 if Cnf["Dinstall::Options::Version"]:
1204 print "katie version 0.0000000000";
1207 postgresql_user = None; # Default == Connect as user running program.
1209 # -n/--dry-run invalidates some other options which would involve things happening
1210 if Cnf["Dinstall::Options::No-Action"]:
1211 Cnf["Dinstall::Options::Automatic"] = ""
1212 Cnf["Dinstall::Options::Ack-New"] = ""
1213 postgresql_user = Cnf["DB::ROUser"];
1215 projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1217 db_access.init(Cnf, projectB);
1219 # Check that we aren't going to clash with the daily cron job
1221 if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1222 sys.stderr.write("Archive maintenance in progress. Try again later.\n");
1225 # Obtain lock if not in no-action mode
1227 if not Cnf["Dinstall::Options::No-Action"]:
1228 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1229 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1231 # Read in the list of already-acknowledged NEW packages
1232 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1234 for line in new_ack_list.readlines():
1235 new_ack_old[line[:-1]] = 1;
1236 new_ack_list.close();
1238 # Initialize the substitution template mapping global
1240 Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"];
1241 Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"];
1242 bcc = "X-Katie: $Revision: 1.38 $"
1243 if Cnf.has_key("Dinstall::Bcc"):
1244 Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"]);
1246 Subst["__BCC__"] = bcc;
1247 Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"];
1248 Subst["__KATIE_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"];
1250 # Process the changes files
1251 for changes_file in changes_files:
1252 print "\n" + changes_file;
1253 process_it (changes_file);
1257 if install_count > 1:
1259 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1261 # Write out the list of already-acknowledged NEW packages
1262 if Cnf["Dinstall::Options::Ack-New"]:
1263 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1264 for i in new_ack_new.keys():
1265 new_ack_list.write(i+'\n')
1266 new_ack_list.close()
1269 if __name__ == '__main__':