3 # Installs Debian packaes
4 # Copyright (C) 2000, 2001 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.39 2001-04-13 21:21:22 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"];
144 changes["changedby822"] = Cnf["Dinstall::MyEmailAddress"];
146 # Parse the .changes field into a dictionary
148 changes = utils.parse_changes(filename, 0)
149 except utils.cant_open_exc:
150 reject_message = "Rejected: can't read changes file '%s'.\n" % (filename)
152 except utils.changes_parse_error_exc, line:
153 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
156 # Parse the Files field from the .changes into another dictionary
158 files = utils.build_file_list(changes, "");
159 except utils.changes_parse_error_exc, line:
160 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line);
162 # Check for mandatory fields
163 for i in ("source", "binary", "architecture", "version", "distribution","maintainer", "files"):
164 if not changes.has_key(i):
165 reject_message = "Rejected: Missing field `%s' in changes file.\n" % (i)
166 return 0 # Avoid <undef> errors during later tests
168 # Override the Distribution: field if appropriate
169 if Cnf["Dinstall::Options::Override-Distribution"] != "":
170 reject_message = reject_message + "Warning: Distribution was overriden from %s to %s.\n" % (changes["distribution"], Cnf["Dinstall::Options::Override-Distribution"])
171 changes["distribution"] = Cnf["Dinstall::Options::Override-Distribution"]
173 # Split multi-value fields into a lower-level dictionary
174 for i in ("architecture", "distribution", "binary", "closes"):
175 o = changes.get(i, "")
179 for j in string.split(o):
182 # Fix the Maintainer: field to be RFC822 compatible
183 (changes["maintainer822"], changes["maintainername"], changes["maintaineremail"]) = utils.fix_maintainer (changes["maintainer"])
185 # Fix the Changed-By: field to be RFC822 compatible; if it exists.
186 (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]) = utils.fix_maintainer(changes.get("changed-by",""));
188 # Ensure all the values in Closes: are numbers
189 if changes.has_key("closes"):
190 for i in changes["closes"].keys():
191 if re_isanum.match (i) == None:
192 reject_message = reject_message + "Rejected: `%s' from Closes field isn't a number.\n" % (i)
194 # Map frozen to unstable if frozen doesn't exist
195 if changes["distribution"].has_key("frozen") and not Cnf.has_key("Suite::Frozen"):
196 del changes["distribution"]["frozen"]
197 changes["distribution"]["unstable"] = 1;
198 reject_message = reject_message + "Mapping frozen to unstable.\n"
200 # Map testing to unstable
201 if changes["distribution"].has_key("testing"):
202 del changes["distribution"]["testing"]
203 changes["distribution"]["unstable"] = 1;
204 reject_message = reject_message + "Mapping testing to unstable.\n"
206 # Ensure target distributions exist
207 for i in changes["distribution"].keys():
208 if not Cnf.has_key("Suite::%s" % (i)):
209 reject_message = reject_message + "Rejected: Unknown distribution `%s'.\n" % (i)
211 # Ensure there _is_ a target distribution
212 if changes["distribution"].keys() == []:
213 reject_message = reject_message + "Rejected: huh? Distribution field is empty in changes file.\n";
215 # Map unreleased arches from stable to unstable
216 if changes["distribution"].has_key("stable"):
217 for i in changes["architecture"].keys():
218 if not Cnf.has_key("Suite::Stable::Architectures::%s" % (i)):
219 reject_message = reject_message + "Mapping stable to unstable for unreleased arch `%s'.\n" % (i)
220 del changes["distribution"]["stable"]
221 changes["distribution"]["unstable"] = 1;
223 # Map arches not being released from frozen to unstable
224 if changes["distribution"].has_key("frozen"):
225 for i in changes["architecture"].keys():
226 if not Cnf.has_key("Suite::Frozen::Architectures::%s" % (i)):
227 reject_message = reject_message + "Mapping frozen to unstable for non-releasing arch `%s'.\n" % (i)
228 del changes["distribution"]["frozen"]
229 changes["distribution"]["unstable"] = 1;
231 # Handle uploads to stable
232 if changes["distribution"].has_key("stable"):
233 # If running from within proposed-updates; assume an install to stable
234 if string.find(os.getcwd(), 'proposed-updates') != -1:
235 # FIXME: should probably remove anything that != stable
236 for i in ("frozen", "unstable"):
237 if changes["distribution"].has_key(i):
238 reject_message = reject_message + "Removing %s from distribution list.\n"
239 del changes["distribution"][i]
240 changes["stable upload"] = 1;
241 # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool
242 file = files.keys()[0];
243 if os.access(file, os.R_OK) == 0:
244 pool_dir = Cnf["Dir::PoolDir"] + '/' + utils.poolify(changes["source"], files[file]["component"]);
246 # Otherwise (normal case) map stable to updates
248 reject_message = reject_message + "Mapping stable to updates.\n";
249 del changes["distribution"]["stable"];
250 changes["distribution"]["proposed-updates"] = 1;
252 # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
253 changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
254 changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
256 if string.find(reject_message, "Rejected:") != -1:
262 global reject_message
264 archive = utils.where_am_i();
266 for file in files.keys():
267 # Check the file is readable
268 if os.access(file,os.R_OK) == 0:
269 reject_message = reject_message + "Rejected: Can't read `%s'.\n" % (file)
270 files[file]["type"] = "unreadable";
272 # If it's byhand skip remaining checks
273 if files[file]["section"] == "byhand":
274 files[file]["byhand"] = 1;
275 files[file]["type"] = "byhand";
276 # Checks for a binary package...
277 elif utils.re_isadeb.match(file) != None:
278 files[file]["type"] = "deb";
280 # Extract package information using dpkg-deb
282 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
284 reject_message = reject_message + "Rejected: %s: debExtractControl() raised %s.\n" % (file, sys.exc_type);
285 # Can't continue, none of the checks on control would work.
288 # Check for mandatory fields
289 if control.Find("Package") == None:
290 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
291 if control.Find("Architecture") == None:
292 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
293 if control.Find("Version") == None:
294 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
296 # Ensure the package name matches the one give in the .changes
297 if not changes["binary"].has_key(control.Find("Package", "")):
298 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
300 # Validate the architecture
301 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
302 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
304 # Check the architecture matches the one given in the .changes
305 if not changes["architecture"].has_key(control.Find("Architecture", "")):
306 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
307 # Check the section & priority match those given in the .changes (non-fatal)
308 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
309 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"])
310 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
311 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"])
313 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
315 files[file]["package"] = control.Find("Package");
316 files[file]["architecture"] = control.Find("Architecture");
317 files[file]["version"] = control.Find("Version");
318 files[file]["maintainer"] = control.Find("Maintainer", "");
319 if file[-5:] == ".udeb":
320 files[file]["dbtype"] = "udeb";
321 elif file[-4:] == ".deb":
322 files[file]["dbtype"] = "deb";
324 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
325 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
326 files[file]["source"] = control.Find("Source", "");
327 if files[file]["source"] == "":
328 files[file]["source"] = files[file]["package"];
329 # Checks for a source package...
331 m = utils.re_issource.match(file)
333 files[file]["package"] = m.group(1)
334 files[file]["version"] = m.group(2)
335 files[file]["type"] = m.group(3)
337 # Ensure the source package name matches the Source filed in the .changes
338 if changes["source"] != files[file]["package"]:
339 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
341 # Ensure the source version matches the version in the .changes file
342 if files[file]["type"] == "orig.tar.gz":
343 changes_version = changes["chopversion2"]
345 changes_version = changes["chopversion"]
346 if changes_version != files[file]["version"]:
347 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
349 # Ensure the .changes lists source in the Architecture field
350 if not changes["architecture"].has_key("source"):
351 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
353 # Check the signature of a .dsc file
354 if files[file]["type"] == "dsc":
355 check_signature(file)
357 files[file]["fullname"] = file
359 # Not a binary or source package? Assume byhand...
361 files[file]["byhand"] = 1;
362 files[file]["type"] = "byhand";
364 files[file]["oldfiles"] = {}
365 for suite in changes["distribution"].keys():
367 if files[file].has_key("byhand"):
370 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
371 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
374 # See if the package is NEW
375 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
376 files[file]["new"] = 1
378 # Find any old binary packages
379 if files[file]["type"] == "deb":
380 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"
381 % (files[file]["package"], suite, files[file]["architecture"]))
382 oldfiles = q.dictresult()
383 for oldfile in oldfiles:
384 files[file]["oldfiles"][suite] = oldfile
385 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
386 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
387 reject_message = reject_message + "Rejected: %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
388 # Check for existing copies of the file
389 if not changes.has_key("stable upload"):
390 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"]))
391 if q.getresult() != []:
392 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
394 # Find any old .dsc files
395 elif files[file]["type"] == "dsc":
396 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"
397 % (files[file]["package"], suite))
398 oldfiles = q.dictresult()
399 if len(oldfiles) >= 1:
400 files[file]["oldfiles"][suite] = oldfiles[0]
402 # Validate the component
403 component = files[file]["component"];
404 component_id = db_access.get_component_id(component);
405 if component_id == -1:
406 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
409 # Validate the priority
410 if string.find(files[file]["priority"],'/') != -1:
411 reject_message = reject_message + "Rejected: file '%s' has invalid priority '%s' [contains '/'].\n" % (file, files[file]["priority"]);
413 # Check the md5sum & size against existing files (if any)
414 location = Cnf["Dir::PoolDir"];
415 files[file]["location id"] = db_access.get_location_id (location, component, archive);
417 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
418 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
420 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
422 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
423 files[file]["files id"] = files_id
425 # Check for packages that have moved from one component to another
426 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
427 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
430 if string.find(reject_message, "Rejected:") != -1:
435 ###############################################################################
438 global dsc, dsc_files, reject_message, reprocess, orig_tar_id, orig_tar_location, legacy_source_untouchable;
440 for file in files.keys():
441 if files[file]["type"] == "dsc":
443 dsc = utils.parse_changes(file, 1)
444 except utils.cant_open_exc:
445 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (file)
447 except utils.changes_parse_error_exc, line:
448 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (file, line)
450 except utils.invalid_dsc_format_exc, line:
451 reject_message = reject_message + "Rejected: syntax error in .dsc file '%s', line %s.\n" % (file, line)
454 dsc_files = utils.build_file_list(dsc, 1)
455 except utils.no_files_exc:
456 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
458 except utils.changes_parse_error_exc, line:
459 reject_message = "Rejected: error parsing .dsc file '%s', can't grok: %s.\n" % (file, line);
462 # Try and find all files mentioned in the .dsc. This has
463 # to work harder to cope with the multiple possible
464 # locations of an .orig.tar.gz.
465 for dsc_file in dsc_files.keys():
466 if files.has_key(dsc_file):
467 actual_md5 = files[dsc_file]["md5sum"];
468 actual_size = int(files[dsc_file]["size"]);
469 found = "%s in incoming" % (dsc_file)
470 # Check the file does not already exist in the archive
471 if not changes.has_key("stable upload"):
472 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));
474 # "It has not broken them. It has fixed a
475 # brokenness. Your crappy hack exploited a
476 # bug in the old dinstall.
478 # "(Come on! I thought it was always obvious
479 # that one just doesn't release different
480 # files with the same name and version.)"
481 # -- ajk@ on d-devel@l.d.o
483 if q.getresult() != []:
484 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
485 elif dsc_file[-12:] == ".orig.tar.gz":
487 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));
491 # Unfortunately, we make get more than one match
492 # here if, for example, the package was in potato
493 # but had a -sa upload in woody. So we need to a)
494 # choose the right one and b) mark all wrong ones
495 # as excluded from the source poolification (to
496 # avoid file overwrites).
498 x = ql[0]; # default to something sane in case we don't match any or have only one
502 old_file = i[0] + i[1];
503 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
504 actual_size = os.stat(old_file)[stat.ST_SIZE];
505 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
508 legacy_source_untouchable[i[3]] = "";
510 old_file = x[0] + x[1];
511 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
512 actual_size = os.stat(old_file)[stat.ST_SIZE];
515 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
518 if suite_type == "legacy" or suite_type == "legacy-mixed":
519 orig_tar_location = "legacy";
521 orig_tar_location = x[4];
523 # Not there? Check in Incoming...
524 # [See comment above process_it() for explanation
525 # of why this is necessary...]
526 if os.access(dsc_file, os.R_OK) != 0:
527 files[dsc_file] = {};
528 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
529 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
530 files[dsc_file]["section"] = files[file]["section"];
531 files[dsc_file]["priority"] = files[file]["priority"];
532 files[dsc_file]["component"] = files[file]["component"];
533 files[dsc_file]["type"] = "orig.tar.gz";
537 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);
540 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming.\n" % (file, dsc_file);
542 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
543 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
544 if actual_size != int(dsc_files[dsc_file]["size"]):
545 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
547 if string.find(reject_message, "Rejected:") != -1:
552 ###############################################################################
554 # Some cunning stunt broke dpkg-source in dpkg 1.8{,.1}; detect the
555 # resulting bad source packages and reject them.
557 # Even more amusingly the fix in 1.8.1.1 didn't actually fix the
558 # problem just changed the symptoms.
561 global dsc, dsc_files, reject_message, reprocess;
563 for filename in files.keys():
564 if files[filename]["type"] == "diff.gz":
565 file = gzip.GzipFile(filename, 'r');
566 for line in file.readlines():
567 if re_bad_diff.search(line):
568 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";
571 if string.find(reject_message, "Rejected:") != -1:
576 ###############################################################################
578 def check_md5sums ():
579 global reject_message;
581 for file in files.keys():
583 file_handle = utils.open_file(file,"r");
584 except utils.cant_open_exc:
587 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
588 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
590 def check_override ():
593 # Only check section & priority on sourceful uploads
594 if not changes["architecture"].has_key("source"):
598 for file in files.keys():
599 if not files[file].has_key("new") and files[file]["type"] == "deb":
600 section = files[file]["section"];
601 override_section = files[file]["override section"];
602 if section != override_section and section != "-":
603 # Ignore this; it's a common mistake and not worth whining about
604 if string.lower(section) == "non-us/main" and string.lower(override_section) == "non-us":
606 summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
607 priority = files[file]["priority"];
608 override_priority = files[file]["override priority"];
609 if priority != override_priority and priority != "-":
610 summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
615 Subst["__SUMMARY__"] = summary;
616 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.override-disparity","r").read());
617 utils.send_mail (mail_message, "")
619 #####################################################################################################################
621 # Set up the per-package template substitution mappings
623 def update_subst (changes_filename):
626 if changes.has_key("architecture"):
627 Subst["__ARCHITECTURE__"] = string.join(changes["architecture"].keys(), ' ' );
629 Subst["__ARCHITECTURE__"] = "Unknown";
630 Subst["__CHANGES_FILENAME__"] = os.path.basename(changes_filename);
631 Subst["__FILE_CONTENTS__"] = changes.get("filecontents", "");
633 # For source uploads the Changed-By field wins; otherwise Maintainer wins.
634 if changes["architecture"].has_key("source") and changes["changedby822"] != "" and (changes["changedby822"] != changes["maintainer822"]):
635 Subst["__MAINTAINER_FROM__"] = changes["changedby822"];
636 Subst["__MAINTAINER_TO__"] = changes["changedby822"] + ", " + changes["maintainer822"];
637 Subst["__MAINTAINER__"] = changes.get("changed-by", "Unknown");
639 Subst["__MAINTAINER_FROM__"] = changes["maintainer822"];
640 Subst["__MAINTAINER_TO__"] = changes["maintainer822"];
641 Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown");
643 Subst["__REJECT_MESSAGE__"] = reject_message;
644 Subst["__SOURCE__"] = changes.get("source", "Unknown");
645 Subst["__VERSION__"] = changes.get("version", "Unknown");
647 #####################################################################################################################
649 def action (changes_filename):
650 byhand = confirm = suites = summary = new = "";
652 # changes["distribution"] may not exist in corner cases
653 # (e.g. unreadable changes files)
654 if not changes.has_key("distribution"):
655 changes["distribution"] = {};
657 for suite in changes["distribution"].keys():
658 if Cnf.has_key("Suite::%s::Confirm"):
659 confirm = confirm + suite + ", "
660 suites = suites + suite + ", "
661 confirm = confirm[:-2]
664 for file in files.keys():
665 if files[file].has_key("byhand"):
667 summary = summary + file + " byhand\n"
668 elif files[file].has_key("new"):
670 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
671 if files[file].has_key("othercomponents"):
672 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
673 if files[file]["type"] == "deb":
674 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
676 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
677 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
678 summary = summary + file + "\n to " + destination + "\n"
680 short_summary = summary;
682 # This is for direport's benefit...
683 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
685 if confirm or byhand or new:
686 summary = summary + "Changes: " + f;
688 summary = summary + announce (short_summary, 0)
690 (prompt, answer) = ("", "XXX")
691 if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
694 if string.find(reject_message, "Rejected") != -1:
696 modified_time = time.time()-os.path.getmtime(changes_filename);
697 except: # i.e. ignore errors like 'file does not exist';
699 if modified_time < 86400:
700 print "SKIP (too new)\n" + reject_message,;
701 prompt = "[S]kip, Manual reject, Quit ?";
703 print "REJECT\n" + reject_message,;
704 prompt = "[R]eject, Manual reject, Skip, Quit ?";
705 if Cnf["Dinstall::Options::Automatic"]:
708 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
709 prompt = "[S]kip, New ack, Manual reject, Quit ?";
710 if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
713 print "BYHAND\n" + reject_message + summary,;
714 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
716 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
717 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
719 print "INSTALL\n" + reject_message + summary,;
720 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
721 if Cnf["Dinstall::Options::Automatic"]:
724 while string.find(prompt, answer) == -1:
726 answer = utils.our_raw_input()
727 m = re_default_answer.match(prompt)
730 answer = string.upper(answer[:1])
733 reject (changes_filename, "");
735 manual_reject (changes_filename);
737 install (changes_filename, summary, short_summary);
739 acknowledge_new (changes_filename, summary);
743 #####################################################################################################################
745 def install (changes_filename, summary, short_summary):
746 global install_count, install_bytes, Subst;
748 # Stable uploads are a special case
749 if changes.has_key("stable upload"):
750 stable_install (changes_filename, summary, short_summary);
755 archive = utils.where_am_i();
757 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
758 projectB.query("BEGIN WORK");
760 # Add the .dsc file to the DB
761 for file in files.keys():
762 if files[file]["type"] == "dsc":
763 package = dsc["source"]
764 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
765 maintainer = dsc["maintainer"]
766 maintainer = string.replace(maintainer, "'", "\\'")
767 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
768 filename = files[file]["pool name"] + file;
769 dsc_location_id = files[file]["location id"];
770 if not files[file]["files id"]:
771 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
772 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
773 % (package, version, maintainer_id, files[file]["files id"]))
775 for suite in changes["distribution"].keys():
776 suite_id = db_access.get_suite_id(suite);
777 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
779 # Add the source files to the DB (files and dsc_files)
780 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
781 for dsc_file in dsc_files.keys():
782 filename = files[file]["pool name"] + dsc_file;
783 # If the .orig.tar.gz is already in the pool, it's
784 # files id is stored in dsc_files by check_dsc().
785 files_id = dsc_files[dsc_file].get("files id", None);
787 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
788 # FIXME: needs to check for -1/-2 and or handle exception
790 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
791 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
793 # Add the .deb files to the DB
794 for file in files.keys():
795 if files[file]["type"] == "deb":
796 package = files[file]["package"]
797 version = files[file]["version"]
798 maintainer = files[file]["maintainer"]
799 maintainer = string.replace(maintainer, "'", "\\'")
800 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
801 architecture = files[file]["architecture"]
802 architecture_id = db_access.get_architecture_id (architecture);
803 type = files[file]["dbtype"];
804 dsc_component = files[file]["component"]
805 source = files[file]["source"]
807 if string.find(source, "(") != -1:
808 m = utils.re_extract_src_version.match(source)
810 source_version = m.group(2)
811 if not source_version:
812 source_version = version
813 filename = files[file]["pool name"] + file;
814 if not files[file]["files id"]:
815 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
816 source_id = db_access.get_source_id (source, source_version);
818 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
819 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
821 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
822 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
823 for suite in changes["distribution"].keys():
824 suite_id = db_access.get_suite_id(suite);
825 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
827 # If the .orig.tar.gz is in a legacy directory we need to poolify
828 # it, so that apt-get source (and anything else that goes by the
829 # "Directory:" field in the Sources.gz file) works.
830 if orig_tar_id != None and orig_tar_location == "legacy":
831 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));
834 # Is this an old upload superseded by a newer -sa upload? (See check_dsc() for details)
835 if legacy_source_untouchable.has_key(qid["files_id"]):
837 # First move the files to the new location
838 legacy_filename = qid["path"]+qid["filename"];
839 pool_location = utils.poolify (changes["source"], files[file]["component"]);
840 pool_filename = pool_location + os.path.basename(qid["filename"]);
841 destination = Cnf["Dir::PoolDir"] + pool_location
842 utils.move(legacy_filename, destination);
843 # Then Update the DB's files table
844 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
846 # If this is a sourceful diff only upload that is moving non-legacy
847 # cross-component we need to copy the .orig.tar.gz into the new
848 # component too for the same reasons as above.
850 if changes["architecture"].has_key("source") and orig_tar_id != None and \
851 orig_tar_location != "legacy" and orig_tar_location != dsc_location_id:
852 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));
853 ql = q.getresult()[0];
854 old_filename = ql[0] + ql[1];
857 new_filename = utils.poolify (changes["source"], dsc_component) + os.path.basename(old_filename);
858 new_files_id = db_access.get_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
859 if new_files_id == None:
860 utils.copy(old_filename, Cnf["Dir::PoolDir"] + new_filename);
861 new_files_id = db_access.set_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
862 projectB.query("UPDATE dsc_files SET file = %s WHERE source = %s AND file = %s" % (new_files_id, source_id, orig_tar_id));
864 # Install the files into the pool
865 for file in files.keys():
866 if files[file].has_key("byhand"):
868 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
869 destdir = os.path.dirname(destination)
870 utils.move (file, destination)
871 install_bytes = install_bytes + float(files[file]["size"])
873 # Copy the .changes file across for suite which need it.
874 for suite in changes["distribution"].keys():
875 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
876 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
878 projectB.query("COMMIT WORK");
881 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
883 sys.stderr.write("W: couldn't move changes file '%s' to DONE directory [Got %s].\n" % (os.path.basename(changes_filename), sys.exc_type));
885 install_count = install_count + 1;
887 if not Cnf["Dinstall::Options::No-Mail"]:
888 Subst["__SUITE__"] = "";
889 Subst["__SUMMARY__"] = summary;
890 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.installed","r").read());
891 utils.send_mail (mail_message, "")
892 announce (short_summary, 1)
895 #####################################################################################################################
897 def stable_install (changes_filename, summary, short_summary):
898 global install_count, install_bytes, Subst;
900 print "Installing to stable."
902 archive = utils.where_am_i();
904 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
905 projectB.query("BEGIN WORK");
907 # Add the .dsc file to the DB
908 for file in files.keys():
909 if files[file]["type"] == "dsc":
910 package = dsc["source"]
911 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
912 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
915 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version));
917 source_id = ql[0][0];
918 suite_id = db_access.get_suite_id('proposed-updates');
919 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
920 suite_id = db_access.get_suite_id('stable');
921 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
923 # Add the .deb files to the DB
924 for file in files.keys():
925 if files[file]["type"] == "deb":
926 package = files[file]["package"]
927 version = files[file]["version"]
928 architecture = files[file]["architecture"]
929 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))
932 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture));
934 binary_id = ql[0][0];
935 suite_id = db_access.get_suite_id('proposed-updates');
936 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
937 suite_id = db_access.get_suite_id('stable');
938 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
940 projectB.query("COMMIT WORK");
942 utils.move (changes_filename, Cnf["Rhona::Morgue"] + os.path.basename(changes_filename));
944 # Update the Stable ChangeLog file
946 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
947 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
948 if os.path.exists(new_changelog_filename):
949 os.unlink (new_changelog_filename);
951 new_changelog = utils.open_file(new_changelog_filename, 'w');
952 for file in files.keys():
953 if files[file]["type"] == "deb":
954 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
955 elif utils.re_issource.match(file) != None:
956 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
958 new_changelog.write("%s\n" % (file));
959 chop_changes = re_fdnic.sub("\n", changes["changes"]);
960 new_changelog.write(chop_changes + '\n\n');
961 if os.access(changelog_filename, os.R_OK) != 0:
962 changelog = utils.open_file(changelog_filename, 'r');
963 new_changelog.write(changelog.read());
964 new_changelog.close();
965 if os.access(changelog_filename, os.R_OK) != 0:
966 os.unlink(changelog_filename);
967 utils.move(new_changelog_filename, changelog_filename);
969 install_count = install_count + 1;
971 if not Cnf["Dinstall::Options::No-Mail"]:
972 Subst["__SUITE__"] = " into stable";
973 Subst["__SUMMARY__"] = summary;
974 utils.send_mail (mail_message, "")
975 announce (short_summary, 1)
977 #####################################################################################################################
979 def reject (changes_filename, manual_reject_mail_filename):
984 base_changes_filename = os.path.basename(changes_filename);
985 reason_filename = re_changes.sub("reason", base_changes_filename);
986 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
988 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
990 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
992 sys.stderr.write("W: couldn't reject changes file '%s' [Got %s].\n" % (base_changes_filename, sys.exc_type));
994 for file in files.keys():
995 if os.path.exists(file):
997 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
999 sys.stderr.write("W: couldn't reject file '%s' [Got %s].\n" % (file, sys.exc_type));
1002 # If this is not a manual rejection generate the .reason file and rejection mail message
1003 if manual_reject_mail_filename == "":
1004 if os.path.exists(reject_filename):
1005 os.unlink(reject_filename);
1006 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1007 os.write(fd, reject_message);
1009 Subst["__MANUAL_REJECT_MESSAGE__"] = "";
1010 Subst["__CC__"] = "X-Katie-Rejection: automatic (moo)";
1011 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1012 else: # Have a manual rejection file to use
1013 reject_mail_message = ""; # avoid <undef>'s
1015 # Send the rejection mail if appropriate
1016 if not Cnf["Dinstall::Options::No-Mail"]:
1017 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
1019 ##################################################################
1021 def manual_reject (changes_filename):
1024 # Build up the rejection email
1025 user_email_address = utils.whoami() + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
1026 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
1028 Subst["__MANUAL_REJECT_MESSAGE__"] = manual_reject_message;
1029 Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"];
1030 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1032 # Write the rejection email out as the <foo>.reason file
1033 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
1034 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
1035 if os.path.exists(reject_filename):
1036 os.unlink(reject_filename);
1037 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1038 os.write(fd, reject_mail_message);
1041 # If we weren't given one, spawn an editor so the user can add one in
1042 if manual_reject_message == "":
1043 result = os.system("vi +6 %s" % (reject_file))
1045 sys.stderr.write ("vi invocation failed for `%s'!\n" % (reject_file))
1048 # Then process it as if it were an automatic rejection
1049 reject (changes_filename, reject_filename)
1051 #####################################################################################################################
1053 def acknowledge_new (changes_filename, summary):
1054 global new_ack_new, Subst;
1056 changes_filename = os.path.basename(changes_filename);
1058 new_ack_new[changes_filename] = 1;
1060 if new_ack_old.has_key(changes_filename):
1061 print "Ack already sent.";
1064 print "Sending new ack.";
1065 if not Cnf["Dinstall::Options::No-Mail"]:
1066 Subst["__SUMMARY__"] = summary;
1067 new_ack_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.new","r").read());
1068 utils.send_mail(new_ack_message,"");
1070 #####################################################################################################################
1072 def announce (short_summary, action):
1075 # Only do announcements for source uploads with a recent dpkg-dev installed
1076 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
1081 Subst["__SHORT_SUMMARY__"] = short_summary;
1083 for dist in changes["distribution"].keys():
1084 list = Cnf.Find("Suite::%s::Announce" % (dist))
1085 if list == "" or lists_done.has_key(list):
1087 lists_done[list] = 1
1088 summary = summary + "Announcing to %s\n" % (list)
1091 Subst["__ANNOUNCE_LIST_ADDRESS__"] = list;
1092 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.announce","r").read());
1093 utils.send_mail (mail_message, "")
1095 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
1096 bugs = changes["closes"].keys()
1098 # changes["changedbyname"] == dsc_name is probably never true, but better
1100 if dsc_name == changes["maintainername"] and (changes["changedbyname"] == "" or changes["changedbyname"] == dsc_name):
1101 summary = summary + "Closing bugs: "
1103 summary = summary + "%s " % (bug)
1105 Subst["__BUG_NUMBER__"] = bug;
1106 if changes["distribution"].has_key("stable"):
1107 Subst["__STABLE_WARNING__"] = """
1108 Note that this package is not part of the released stable Debian
1109 distribution. It may have dependencies on other unreleased software,
1110 or other instabilities. Please take care if you wish to install it.
1111 The update will eventually make its way into the next released Debian
1114 Subst["__STABLE_WARNING__"] = "";
1115 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-close","r").read());
1116 utils.send_mail (mail_message, "")
1118 summary = summary + "Setting bugs to severity fixed: "
1119 control_message = ""
1121 summary = summary + "%s " % (bug)
1122 control_message = control_message + "tag %s + fixed\n" % (bug)
1123 if action and control_message != "":
1124 Subst["__CONTROL_MESSAGE__"] = control_message;
1125 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-nmu-fixed","r").read());
1126 utils.send_mail (mail_message, "")
1127 summary = summary + "\n"
1131 ###############################################################################
1133 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1134 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1135 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1136 # processed it during it's checks of -2. If -1 has been deleted or
1137 # otherwise not checked by da-install, the .orig.tar.gz will not have
1138 # been checked at all. To get round this, we force the .orig.tar.gz
1139 # into the .changes structure and reprocess the .changes file.
1141 def process_it (changes_file):
1142 global reprocess, orig_tar_id, orig_tar_location, changes, dsc, dsc_files, files, reject_message;
1144 # Reset some globals
1151 orig_tar_location = "";
1152 legacy_source_untouchable = {};
1153 reject_message = "";
1155 # Absolutize the filename to avoid the requirement of being in the
1156 # same directory as the .changes file.
1157 changes_file = os.path.abspath(changes_file);
1159 # And since handling of installs to stable munges with the CWD;
1160 # save and restore it.
1163 check_signature (changes_file);
1164 check_changes (changes_file);
1172 update_subst(changes_file);
1173 action(changes_file);
1178 ###############################################################################
1181 global Cnf, projectB, install_bytes, new_ack_old, Subst
1185 Cnf = apt_pkg.newConfiguration();
1186 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1188 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1189 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1190 ('h',"help","Dinstall::Options::Help"),
1191 ('k',"ack-new","Dinstall::Options::Ack-New"),
1192 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1193 ('n',"no-action","Dinstall::Options::No-Action"),
1194 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1195 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1196 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1197 ('v',"version","Dinstall::Options::Version")];
1199 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1201 if Cnf["Dinstall::Options::Help"]:
1204 if Cnf["Dinstall::Options::Version"]:
1205 print "katie version 0.0000000000";
1208 postgresql_user = None; # Default == Connect as user running program.
1210 # -n/--dry-run invalidates some other options which would involve things happening
1211 if Cnf["Dinstall::Options::No-Action"]:
1212 Cnf["Dinstall::Options::Automatic"] = ""
1213 Cnf["Dinstall::Options::Ack-New"] = ""
1214 postgresql_user = Cnf["DB::ROUser"];
1216 projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1218 db_access.init(Cnf, projectB);
1220 # Check that we aren't going to clash with the daily cron job
1222 if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1223 sys.stderr.write("Archive maintenance in progress. Try again later.\n");
1226 # Obtain lock if not in no-action mode
1228 if not Cnf["Dinstall::Options::No-Action"]:
1229 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1230 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1232 # Read in the list of already-acknowledged NEW packages
1233 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1235 for line in new_ack_list.readlines():
1236 new_ack_old[line[:-1]] = 1;
1237 new_ack_list.close();
1239 # Initialize the substitution template mapping global
1241 Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"];
1242 Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"];
1243 bcc = "X-Katie: $Revision: 1.39 $"
1244 if Cnf.has_key("Dinstall::Bcc"):
1245 Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"]);
1247 Subst["__BCC__"] = bcc;
1248 Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"];
1249 Subst["__KATIE_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"];
1251 # Process the changes files
1252 for changes_file in changes_files:
1253 print "\n" + changes_file;
1254 process_it (changes_file);
1258 if install_count > 1:
1260 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1262 # Write out the list of already-acknowledged NEW packages
1263 if Cnf["Dinstall::Options::Ack-New"]:
1264 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1265 for i in new_ack_new.keys():
1266 new_ack_list.write(i+'\n')
1267 new_ack_list.close()
1270 if __name__ == '__main__':