3 # Installs Debian packaes
4 # Copyright (C) 2000, 2001 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.31 2001-03-14 05:12:53 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 # 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 ###############################################################################
50 reject_footer = """If you don't understand why your files were rejected, or if the
51 override file requires editing, reply to this email.
53 Your rejected files are in incoming/REJECT/. (Some may also be in
54 incoming/ if your .changes file was unparsable.) If only some of the
55 files need to repaired, you may move any good files back to incoming/.
56 Please remove any bad files from incoming/REJECT/."""
58 new_ack_footer = """Your package contains new components which requires manual editing of
59 the override file. It is ok otherwise, so please be patient. New
60 packages are usually added to the override file about once a week.
62 You may have gotten the distribution wrong. You'll get warnings above
63 if files already exist in other distributions."""
65 installed_footer = """If the override file requires editing, file a bug on ftp.debian.org.
67 Thank you for your contribution to Debian GNU."""
69 #########################################################################################
85 legacy_source_untouchable = {};
87 #########################################################################################
89 def usage (exit_code):
90 print """Usage: dinstall [OPTION]... [CHANGES]...
91 -a, --automatic automatic run
92 -D, --debug=VALUE turn on debugging
93 -h, --help show this help and exit.
94 -k, --ack-new acknowledge new packages !! for cron.daily only !!
95 -m, --manual-reject=MSG manual reject with `msg'
96 -n, --no-action don't do anything
97 -p, --no-lock don't check lockfile !! for cron.daily only !!
98 -u, --distribution=DIST override distribution to `dist'
99 -v, --version display the version number and exit"""
102 def check_signature (filename):
103 global reject_message
105 (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))
107 reject_message = "Rejected: GPG signature check failed on `%s'.\n%s\n" % (os.path.basename(filename), output)
111 #####################################################################################################################
113 # See if a given package is in the override table
115 def in_override_p (package, component, suite, binary_type, file):
118 if binary_type == "": # must be source
123 # Override suite name; used for example with proposed-updates
124 if Cnf.Find("Suite::%s::OverrideSuite" % (suite)) != "":
125 suite = Cnf["Suite::%s::OverrideSuite" % (suite)];
127 # Avoid <undef> on unknown distributions
128 suite_id = db_access.get_suite_id(suite);
131 component_id = db_access.get_component_id(component);
132 type_id = db_access.get_override_type_id(type);
134 # FIXME: nasty non-US speficic hack
135 if string.lower(component[:7]) == "non-us/":
136 component = component[7:];
138 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"
139 % (package, suite_id, component_id, type_id));
140 result = q.getresult();
141 # If checking for a source package fall back on the binary override type
142 if type == "dsc" and not result:
143 type_id = db_access.get_override_type_id("deb");
144 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"
145 % (package, suite_id, component_id, type_id));
146 result = q.getresult();
148 # Remember the section and priority so we can check them later if appropriate
150 files[file]["override section"] = result[0][0];
151 files[file]["override priority"] = result[0][1];
155 #####################################################################################################################
157 def check_changes(filename):
158 global reject_message, changes, files
160 # Default in case we bail out
161 changes["maintainer822"] = Cnf["Dinstall::MyEmailAddress"];
163 # Parse the .changes field into a dictionary
165 changes = utils.parse_changes(filename, 0)
166 except utils.cant_open_exc:
167 reject_message = "Rejected: can't read changes file '%s'.\n" % (filename)
169 except utils.changes_parse_error_exc, line:
170 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
173 # Parse the Files field from the .changes into another dictionary
175 files = utils.build_file_list(changes, "");
176 except utils.changes_parse_error_exc, line:
177 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line);
179 # Check for mandatory fields
180 for i in ("source", "binary", "architecture", "version", "distribution","maintainer", "files"):
181 if not changes.has_key(i):
182 reject_message = "Rejected: Missing field `%s' in changes file.\n" % (i)
183 return 0 # Avoid <undef> errors during later tests
185 # Override the Distribution: field if appropriate
186 if Cnf["Dinstall::Options::Override-Distribution"] != "":
187 reject_message = reject_message + "Warning: Distribution was overriden from %s to %s.\n" % (changes["distribution"], Cnf["Dinstall::Options::Override-Distribution"])
188 changes["distribution"] = Cnf["Dinstall::Options::Override-Distribution"]
190 # Split multi-value fields into a lower-level dictionary
191 for i in ("architecture", "distribution", "binary", "closes"):
192 o = changes.get(i, "")
196 for j in string.split(o):
199 # Fix the Maintainer: field to be RFC822 compatible
200 (changes["maintainer822"], changes["maintainername"], changes["maintaineremail"]) = utils.fix_maintainer (changes["maintainer"])
202 # Fix the Changed-By: field to be RFC822 compatible; if it exists.
203 (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]) = utils.fix_maintainer(changes.get("changed-by",""));
205 # For source uploads the Changed-By field wins; otherwise Maintainer wins.
206 if changes["architecture"].has_key("source"):
207 changes["uploader822"] = "To: %s\nCc: %s" % (changes["changedby822"], changes["maintainer822"]);
208 # changes["uploadername"], changes["uploaderemail"]) = (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]);
210 # Ensure all the values in Closes: are numbers
211 if changes.has_key("closes"):
212 for i in changes["closes"].keys():
213 if re_isanum.match (i) == None:
214 reject_message = reject_message + "Rejected: `%s' from Closes field isn't a number.\n" % (i)
216 # Map frozen to unstable if frozen doesn't exist
217 if changes["distribution"].has_key("frozen") and not Cnf.has_key("Suite::Frozen"):
218 del changes["distribution"]["frozen"]
219 changes["distribution"]["unstable"] = 1;
220 reject_message = reject_message + "Mapping frozen to unstable.\n"
222 # Map testing to unstable
223 if changes["distribution"].has_key("testing"):
224 del changes["distribution"]["testing"]
225 changes["distribution"]["unstable"] = 1;
226 reject_message = reject_message + "Mapping testing to unstable.\n"
228 # Ensure target distributions exist
229 for i in changes["distribution"].keys():
230 if not Cnf.has_key("Suite::%s" % (i)):
231 reject_message = reject_message + "Rejected: Unknown distribution `%s'.\n" % (i)
233 # Ensure there _is_ a target distribution
234 if changes["distribution"].keys() == []:
235 reject_message = reject_message + "Rejected: huh? Distribution field is empty in changes file.\n";
237 # Map unreleased arches from stable to unstable
238 if changes["distribution"].has_key("stable"):
239 for i in changes["architecture"].keys():
240 if not Cnf.has_key("Suite::Stable::Architectures::%s" % (i)):
241 reject_message = reject_message + "Mapping stable to unstable for unreleased arch `%s'.\n" % (i)
242 del changes["distribution"]["stable"]
243 changes["distribution"]["unstable"] = 1;
245 # Map arches not being released from frozen to unstable
246 if changes["distribution"].has_key("frozen"):
247 for i in changes["architecture"].keys():
248 if not Cnf.has_key("Suite::Frozen::Architectures::%s" % (i)):
249 reject_message = reject_message + "Mapping frozen to unstable for non-releasing arch `%s'.\n" % (i)
250 del changes["distribution"]["frozen"]
251 changes["distribution"]["unstable"] = 1;
253 # Handle uploads to stable
254 if changes["distribution"].has_key("stable"):
255 # If running from within proposed-updates; assume an install to stable
256 if string.find(os.getcwd(), 'proposed-updates') != -1:
257 # FIXME: should probably remove anything that != stable
258 for i in ("frozen", "unstable"):
259 if changes["distribution"].has_key(i):
260 reject_message = reject_message + "Removing %s from distribution list.\n"
261 del changes["distribution"][i]
262 changes["stable upload"] = 1;
263 # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool
264 file = files.keys()[0];
265 if os.access(file, os.R_OK) == 0:
266 pool_dir = Cnf["Dir::PoolDir"] + '/' + utils.poolify(changes["source"], files[file]["component"]);
268 # Otherwise (normal case) map stable to updates
270 reject_message = reject_message + "Mapping stable to updates.\n";
271 del changes["distribution"]["stable"];
272 changes["distribution"]["proposed-updates"] = 1;
274 # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
275 changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
276 changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
278 if string.find(reject_message, "Rejected:") != -1:
284 global reject_message
286 archive = utils.where_am_i();
288 for file in files.keys():
289 # Check the file is readable
290 if os.access(file,os.R_OK) == 0:
291 reject_message = reject_message + "Rejected: Can't read `%s'.\n" % (file)
292 files[file]["type"] = "unreadable";
294 # If it's byhand skip remaining checks
295 if files[file]["section"] == "byhand":
296 files[file]["byhand"] = 1;
297 files[file]["type"] = "byhand";
298 # Checks for a binary package...
299 elif utils.re_isadeb.match(file) != None:
300 files[file]["type"] = "deb";
302 # Extract package information using dpkg-deb
304 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
306 reject_message = reject_message + "Rejected: %s: debExtractControl() raised %s.\n" % (file, sys.exc_type);
307 # Can't continue, none of the checks on control would work.
310 # Check for mandatory fields
311 if control.Find("Package") == None:
312 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
313 if control.Find("Architecture") == None:
314 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
315 if control.Find("Version") == None:
316 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
318 # Ensure the package name matches the one give in the .changes
319 if not changes["binary"].has_key(control.Find("Package", "")):
320 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
322 # Validate the architecture
323 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
324 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
326 # Check the architecture matches the one given in the .changes
327 if not changes["architecture"].has_key(control.Find("Architecture", "")):
328 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
329 # Check the section & priority match those given in the .changes (non-fatal)
330 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
331 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"])
332 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
333 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"])
335 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
337 files[file]["package"] = control.Find("Package");
338 files[file]["architecture"] = control.Find("Architecture");
339 files[file]["version"] = control.Find("Version");
340 files[file]["maintainer"] = control.Find("Maintainer", "");
341 if file[-5:] == ".udeb":
342 files[file]["dbtype"] = "udeb";
343 elif file[-4:] == ".deb":
344 files[file]["dbtype"] = "deb";
346 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
347 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
348 files[file]["source"] = control.Find("Source", "");
349 if files[file]["source"] == "":
350 files[file]["source"] = files[file]["package"];
351 # Checks for a source package...
353 m = utils.re_issource.match(file)
355 files[file]["package"] = m.group(1)
356 files[file]["version"] = m.group(2)
357 files[file]["type"] = m.group(3)
359 # Ensure the source package name matches the Source filed in the .changes
360 if changes["source"] != files[file]["package"]:
361 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
363 # Ensure the source version matches the version in the .changes file
364 if files[file]["type"] == "orig.tar.gz":
365 changes_version = changes["chopversion2"]
367 changes_version = changes["chopversion"]
368 if changes_version != files[file]["version"]:
369 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
371 # Ensure the .changes lists source in the Architecture field
372 if not changes["architecture"].has_key("source"):
373 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
375 # Check the signature of a .dsc file
376 if files[file]["type"] == "dsc":
377 check_signature(file)
379 files[file]["fullname"] = file
381 # Not a binary or source package? Assume byhand...
383 files[file]["byhand"] = 1;
384 files[file]["type"] = "byhand";
386 files[file]["oldfiles"] = {}
387 for suite in changes["distribution"].keys():
389 if files[file].has_key("byhand"):
392 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
393 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
396 # See if the package is NEW
397 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
398 files[file]["new"] = 1
400 # Find any old binary packages
401 if files[file]["type"] == "deb":
402 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"
403 % (files[file]["package"], suite, files[file]["architecture"]))
404 oldfiles = q.dictresult()
405 for oldfile in oldfiles:
406 files[file]["oldfiles"][suite] = oldfile
407 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
408 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
409 reject_message = reject_message + "Rejected: %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
410 # Check for existing copies of the file
411 if not changes.has_key("stable upload"):
412 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"]))
413 if q.getresult() != []:
414 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
416 # Find any old .dsc files
417 elif files[file]["type"] == "dsc":
418 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"
419 % (files[file]["package"], suite))
420 oldfiles = q.dictresult()
421 if len(oldfiles) >= 1:
422 files[file]["oldfiles"][suite] = oldfiles[0]
424 # Validate the component
425 component = files[file]["component"];
426 component_id = db_access.get_component_id(component);
427 if component_id == -1:
428 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
431 # Validate the priority
432 if string.find(files[file]["priority"],'/') != -1:
433 reject_message = reject_message + "Rejected: file '%s' has invalid priority '%s' [contains '/'].\n" % (file, files[file]["priority"]);
435 # Check the md5sum & size against existing files (if any)
436 location = Cnf["Dir::PoolDir"];
437 files[file]["location id"] = db_access.get_location_id (location, component, archive);
439 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
440 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
442 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
444 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
445 files[file]["files id"] = files_id
447 # Check for packages that have moved from one component to another
448 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
449 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
452 if string.find(reject_message, "Rejected:") != -1:
457 ###############################################################################
460 global dsc, dsc_files, reject_message, reprocess, orig_tar_id, legacy_source_untouchable;
462 for file in files.keys():
463 if files[file]["type"] == "dsc":
465 dsc = utils.parse_changes(file, 1)
466 except utils.cant_open_exc:
467 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (file)
469 except utils.changes_parse_error_exc, line:
470 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (file, line)
472 except utils.invalid_dsc_format_exc, line:
473 reject_message = reject_message + "Rejected: syntax error in .dsc file '%s', line %s.\n" % (file, line)
476 dsc_files = utils.build_file_list(dsc, 1)
477 except utils.no_files_exc:
478 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
480 except utils.changes_parse_error_exc, line:
481 reject_message = "Rejected: error parsing .dsc file '%s', can't grok: %s.\n" % (file, line);
484 # Try and find all files mentioned in the .dsc. This has
485 # to work harder to cope with the multiple possible
486 # locations of an .orig.tar.gz.
487 for dsc_file in dsc_files.keys():
488 if files.has_key(dsc_file):
489 actual_md5 = files[dsc_file]["md5sum"];
490 actual_size = int(files[dsc_file]["size"]);
491 found = "%s in incoming" % (dsc_file)
492 # Check the file does not already exist in the archive
493 if not changes.has_key("stable upload"):
494 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));
496 # "It has not broken them. It has fixed a
497 # brokenness. Your crappy hack exploited a
498 # bug in the old dinstall.
500 # "(Come on! I thought it was always obvious
501 # that one just doesn't release different
502 # files with the same name and version.)"
503 # -- ajk@ on d-devel@l.d.o
505 if q.getresult() != []:
506 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
507 elif dsc_file[-12:] == ".orig.tar.gz":
509 q = projectB.query("SELECT l.path, f.filename, l.type, 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));
513 # Unfortunately, we make get more than one match
514 # here if, for example, the package was in potato
515 # but had a -sa upload in woody. So we need to a)
516 # choose the right one and b) mark all wrong ones
517 # as excluded from the source poolification (to
518 # avoid file overwrites).
520 x = ql[0]; # default to something sane in case we don't match any or have only one
524 old_file = i[0] + i[1];
525 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
526 actual_size = os.stat(old_file)[stat.ST_SIZE];
527 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
530 legacy_source_untouchable[i[3]] = "";
532 old_file = x[0] + x[1];
533 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
534 actual_size = os.stat(old_file)[stat.ST_SIZE];
537 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
539 if suite_type == "legacy" or suite_type == "legacy-mixed":
542 # Not there? Check in Incoming...
543 # [See comment above process_it() for explanation
544 # of why this is necessary...]
545 if os.access(dsc_file, os.R_OK) != 0:
546 files[dsc_file] = {};
547 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
548 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
549 files[dsc_file]["section"] = files[file]["section"];
550 files[dsc_file]["priority"] = files[file]["priority"];
551 files[dsc_file]["component"] = files[file]["component"];
552 files[dsc_file]["type"] = "orig.tar.gz";
556 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);
559 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming.\n" % (file, dsc_file);
561 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
562 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
563 if actual_size != int(dsc_files[dsc_file]["size"]):
564 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
566 if string.find(reject_message, "Rejected:") != -1:
571 ###############################################################################
573 # Some cunning stunt broke dpkg-source in dpkg 1.8{,.1}; detect the
574 # resulting bad source packages and reject them.
576 # Even more amusingly the fix in 1.8.1.1 didn't actually fix the
577 # problem just changed the symptoms.
580 global dsc, dsc_files, reject_message, reprocess, orig_tar_id;
582 for filename in files.keys():
583 if files[filename]["type"] == "diff.gz":
584 file = gzip.GzipFile(filename, 'r');
585 for line in file.readlines():
586 if re_bad_diff.search(line):
587 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";
590 if string.find(reject_message, "Rejected:") != -1:
595 ###############################################################################
597 def check_md5sums ():
598 global reject_message;
600 for file in files.keys():
602 file_handle = utils.open_file(file,"r");
603 except utils.cant_open_exc:
606 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
607 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
609 def check_override ():
610 # Only check section & priority on sourceful uploads
611 if not changes["architecture"].has_key("source"):
615 for file in files.keys():
616 if not files[file].has_key("new") and files[file]["type"] == "deb":
617 section = files[file]["section"];
618 override_section = files[file]["override section"];
619 if section != override_section and section != "-":
620 # Ignore this; it's a common mistake and not worth whining about
621 if string.lower(section) == "non-us/main" and string.lower(override_section) == "non-us":
623 summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
624 priority = files[file]["priority"];
625 override_priority = files[file]["override priority"];
626 if priority != override_priority and priority != "-":
627 summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
632 mail_message = """Return-Path: %s
635 Bcc: troup@auric.debian.org
636 Subject: %s override disparity
638 There are disparities between your recently installed upload and the
639 override file for the following file(s):
642 Either the package or the override file is incorrect. If you think
643 the override is correct and the package wrong please fix the package
644 so that this disparity is fixed in the next upload. If you feel the
645 override is incorrect then please reply to this mail and explain why.
648 Debian distribution maintenance software
650 (This message was generated automatically; if you believe that there
651 is a problem with it please contact the archive administrators by
652 mailing ftpmaster@debian.org)
653 """ % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes["source"], summary);
654 utils.send_mail (mail_message, "")
656 #####################################################################################################################
658 def action (changes_filename):
659 byhand = confirm = suites = summary = new = "";
661 # changes["distribution"] may not exist in corner cases
662 # (e.g. unreadable changes files)
663 if not changes.has_key("distribution"):
664 changes["distribution"] = {};
666 for suite in changes["distribution"].keys():
667 if Cnf.has_key("Suite::%s::Confirm"):
668 confirm = confirm + suite + ", "
669 suites = suites + suite + ", "
670 confirm = confirm[:-2]
673 for file in files.keys():
674 if files[file].has_key("byhand"):
676 summary = summary + file + " byhand\n"
677 elif files[file].has_key("new"):
679 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
680 if files[file].has_key("othercomponents"):
681 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
682 if files[file]["type"] == "deb":
683 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
685 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
686 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
687 summary = summary + file + "\n to " + destination + "\n"
689 short_summary = summary;
691 # This is for direport's benefit...
692 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
694 if confirm or byhand or new:
695 summary = summary + "Changes: " + f;
697 summary = summary + announce (short_summary, 0)
699 (prompt, answer) = ("", "XXX")
700 if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
703 if string.find(reject_message, "Rejected") != -1:
704 if time.time()-os.path.getmtime(changes_filename) < 86400:
705 print "SKIP (too new)\n" + reject_message,;
706 prompt = "[S]kip, Manual reject, Quit ?";
708 print "REJECT\n" + reject_message,;
709 prompt = "[R]eject, Manual reject, Skip, Quit ?";
710 if Cnf["Dinstall::Options::Automatic"]:
713 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
714 prompt = "[S]kip, New ack, Manual reject, Quit ?";
715 if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
718 print "BYHAND\n" + reject_message + summary,;
719 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
721 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
722 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
724 print "INSTALL\n" + reject_message + summary,;
725 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
726 if Cnf["Dinstall::Options::Automatic"]:
729 while string.find(prompt, answer) == -1:
731 answer = utils.our_raw_input()
732 m = re_default_answer.match(prompt)
735 answer = string.upper(answer[:1])
738 reject (changes_filename, "");
740 manual_reject (changes_filename);
742 install (changes_filename, summary, short_summary);
744 acknowledge_new (changes_filename, summary);
748 #####################################################################################################################
750 def install (changes_filename, summary, short_summary):
751 global install_count, install_bytes
753 # Stable uploads are a special case
754 if changes.has_key("stable upload"):
755 stable_install (changes_filename, summary, short_summary);
760 archive = utils.where_am_i();
762 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
763 projectB.query("BEGIN WORK");
765 # Add the .dsc file to the DB
766 for file in files.keys():
767 if files[file]["type"] == "dsc":
768 package = dsc["source"]
769 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
770 maintainer = dsc["maintainer"]
771 maintainer = string.replace(maintainer, "'", "\\'")
772 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
773 filename = files[file]["pool name"] + file;
774 dsc_location_id = files[file]["location id"];
775 if not files[file]["files id"]:
776 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
777 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
778 % (package, version, maintainer_id, files[file]["files id"]))
780 for suite in changes["distribution"].keys():
781 suite_id = db_access.get_suite_id(suite);
782 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
784 # Add the source files to the DB (files and dsc_files)
785 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
786 for dsc_file in dsc_files.keys():
787 filename = files[file]["pool name"] + dsc_file;
788 # If the .orig.tar.gz is already in the pool, it's
789 # files id is stored in dsc_files by check_dsc().
790 files_id = dsc_files[dsc_file].get("files id", None);
792 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
793 # FIXME: needs to check for -1/-2 and or handle exception
795 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
796 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
798 # Add the .deb files to the DB
799 for file in files.keys():
800 if files[file]["type"] == "deb":
801 package = files[file]["package"]
802 version = files[file]["version"]
803 maintainer = files[file]["maintainer"]
804 maintainer = string.replace(maintainer, "'", "\\'")
805 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
806 architecture = files[file]["architecture"]
807 architecture_id = db_access.get_architecture_id (architecture);
808 type = files[file]["dbtype"];
809 component = files[file]["component"]
810 source = files[file]["source"]
812 if string.find(source, "(") != -1:
813 m = utils.re_extract_src_version.match(source)
815 source_version = m.group(2)
816 if not source_version:
817 source_version = version
818 filename = files[file]["pool name"] + file;
819 if not files[file]["files id"]:
820 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
821 source_id = db_access.get_source_id (source, source_version);
823 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
824 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
826 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
827 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
828 for suite in changes["distribution"].keys():
829 suite_id = db_access.get_suite_id(suite);
830 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
832 # If the .orig.tar.gz is in a legacy directory we need to poolify
833 # it, so that apt-get source (and anything else that goes by the
834 # "Directory:" field in the Sources.gz file) works.
835 if orig_tar_id != None:
836 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));
839 # Is this an old upload superseded by a newer -sa upload? (See check_dsc() for details)
840 if legacy_source_untouchable.has_key(qid["files_id"]):
842 # First move the files to the new location
843 legacy_filename = qid["path"]+qid["filename"];
844 pool_location = utils.poolify (changes["source"], files[file]["component"]);
845 pool_filename = pool_location + os.path.basename(qid["filename"]);
846 destination = Cnf["Dir::PoolDir"] + pool_location
847 utils.move(legacy_filename, destination);
848 # Then Update the DB's files table
849 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
851 # Install the files into the pool
852 for file in files.keys():
853 if files[file].has_key("byhand"):
855 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
856 destdir = os.path.dirname(destination)
857 utils.move (file, destination)
858 install_bytes = install_bytes + float(files[file]["size"])
860 # Copy the .changes file across for suite which need it.
861 for suite in changes["distribution"].keys():
862 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
863 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
865 projectB.query("COMMIT WORK");
868 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
870 sys.stderr.write("W: couldn't move changes file '%s' to DONE directory [Got %s].\n" % (os.path.basename(changes_filename), sys.exc_type));
872 install_count = install_count + 1;
874 if not Cnf["Dinstall::Options::No-Mail"]:
875 mail_message = """Return-Path: %s
878 Bcc: troup@auric.debian.org
879 Subject: %s INSTALLED
885 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
886 utils.send_mail (mail_message, "")
887 announce (short_summary, 1)
890 #####################################################################################################################
892 def stable_install (changes_filename, summary, short_summary):
893 global install_count, install_bytes
895 print "Installing to stable."
897 archive = utils.where_am_i();
899 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
900 projectB.query("BEGIN WORK");
902 # Add the .dsc file to the DB
903 for file in files.keys():
904 if files[file]["type"] == "dsc":
905 package = dsc["source"]
906 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
907 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
910 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version));
912 source_id = ql[0][0];
913 suite_id = db_access.get_suite_id('proposed-updates');
914 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
915 suite_id = db_access.get_suite_id('stable');
916 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
918 # Add the .deb files to the DB
919 for file in files.keys():
920 if files[file]["type"] == "deb":
921 package = files[file]["package"]
922 version = files[file]["version"]
923 architecture = files[file]["architecture"]
924 q = projectB.query("SELECT b.id FROM binaries b, architecture a WHERE b.package = '%s' AND b.version = '%s' AND (a.arch_string = '%s' OR a.arch_string = 'all') AND b.architecture = a.id" % (package, version, architecture))
927 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture));
929 binary_id = ql[0][0];
930 suite_id = db_access.get_suite_id('proposed-updates');
931 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
932 suite_id = db_access.get_suite_id('stable');
933 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
935 projectB.query("COMMIT WORK");
937 utils.move (changes_filename, Cnf["Rhona::Morgue"] + os.path.basename(changes_filename));
939 # Update the Stable ChangeLog file
941 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
942 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
943 if os.path.exists(new_changelog_filename):
944 os.unlink (new_changelog_filename);
946 new_changelog = utils.open_file(new_changelog_filename, 'w');
947 for file in files.keys():
948 if files[file]["type"] == "deb":
949 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
950 elif utils.re_issource.match(file) != None:
951 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
953 new_changelog.write("%s\n" % (file));
954 chop_changes = re_fdnic.sub("\n", changes["changes"]);
955 new_changelog.write(chop_changes + '\n\n');
956 if os.access(changelog_filename, os.R_OK) != 0:
957 changelog = utils.open_file(changelog_filename, 'r');
958 new_changelog.write(changelog.read());
959 new_changelog.close();
960 if os.access(changelog_filename, os.R_OK) != 0:
961 os.unlink(changelog_filename);
962 utils.move(new_changelog_filename, changelog_filename);
964 install_count = install_count + 1;
966 if not Cnf["Dinstall::Options::No-Mail"]:
967 mail_message = """Return-Path: %s
970 Bcc: troup@auric.debian.org
971 Subject: %s INSTALLED into stable
977 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
978 utils.send_mail (mail_message, "")
979 announce (short_summary, 1)
981 #####################################################################################################################
983 def reject (changes_filename, manual_reject_mail_filename):
986 base_changes_filename = os.path.basename(changes_filename);
987 reason_filename = re_changes.sub("reason", base_changes_filename);
988 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
990 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
992 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
994 sys.stderr.write("W: couldn't reject changes file '%s' [Got %s].\n" % (base_changes_filename, sys.exc_type));
996 for file in files.keys():
997 if os.path.exists(file):
999 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
1001 sys.stderr.write("W: couldn't reject file '%s' [Got %s].\n" % (file, sys.exc_type));
1004 # If this is not a manual rejection generate the .reason file and rejection mail message
1005 if manual_reject_mail_filename == "":
1006 if os.path.exists(reject_filename):
1007 os.unlink(reject_filename);
1008 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1009 os.write(fd, reject_message);
1011 reject_mail_message = """From: %s
1013 Bcc: troup@auric.debian.org
1014 Subject: %s REJECTED
1018 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, reject_footer);
1019 else: # Have a manual rejection file to use
1020 reject_mail_message = ""; # avoid <undef>'s
1022 # Send the rejection mail if appropriate
1023 if not Cnf["Dinstall::Options::No-Mail"]:
1024 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
1026 ##################################################################
1028 def manual_reject (changes_filename):
1029 # Build up the rejection email
1030 user_email_address = string.replace(string.split(pwd.getpwuid(os.getuid())[4],',')[0], '.', '')
1031 user_email_address = user_email_address + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
1032 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
1034 reject_mail_message = """From: %s
1037 Bcc: troup@auric.debian.org
1038 Subject: %s REJECTED
1043 %s""" % (user_email_address, Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), manual_reject_message, reject_message, reject_footer)
1045 # Write the rejection email out as the <foo>.reason file
1046 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
1047 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
1048 if os.path.exists(reject_filename):
1049 os.unlink(reject_filename);
1050 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1051 os.write(fd, reject_mail_message);
1054 # If we weren't given one, spawn an editor so the user can add one in
1055 if manual_reject_message == "":
1056 result = os.system("vi +6 %s" % (reject_file))
1058 sys.stderr.write ("vi invocation failed for `%s'!\n" % (reject_file))
1061 # Then process it as if it were an automatic rejection
1062 reject (changes_filename, reject_filename)
1064 #####################################################################################################################
1066 def acknowledge_new (changes_filename, summary):
1069 changes_filename = os.path.basename(changes_filename);
1071 new_ack_new[changes_filename] = 1;
1073 if new_ack_old.has_key(changes_filename):
1074 print "Ack already sent.";
1077 print "Sending new ack.";
1078 if not Cnf["Dinstall::Options::No-Mail"]:
1079 new_ack_message = """Return-Path: %s
1082 Bcc: troup@auric.debian.org
1086 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes_filename, summary, new_ack_footer);
1087 utils.send_mail(new_ack_message,"");
1089 #####################################################################################################################
1091 def announce (short_summary, action):
1092 # Only do announcements for source uploads with a recent dpkg-dev installed
1093 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
1099 for dist in changes["distribution"].keys():
1100 list = Cnf.Find("Suite::%s::Announce" % (dist))
1101 if list == None or lists_done.has_key(list):
1103 lists_done[list] = 1
1104 summary = summary + "Announcing to %s\n" % (list)
1107 mail_message = """Return-Path: %s
1110 Bcc: troup@auric.debian.org
1111 Subject: Installed %s %s (%s)
1117 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], list, changes["source"], changes["version"], string.join(changes["architecture"].keys(), ' ' ),
1118 changes["filecontents"], short_summary)
1119 utils.send_mail (mail_message, "")
1121 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
1122 bugs = changes["closes"].keys()
1124 # changes["changedbyname"] == dsc_name is probably never true, but better
1126 if dsc_name == changes["maintainername"] and (changes["changedbyname"] == "" or changes["changedbyname"] == dsc_name):
1127 summary = summary + "Closing bugs: "
1129 summary = summary + "%s " % (bug)
1131 mail_message = """Return-Path: %s
1133 To: %s-close@bugs.debian.org
1134 Bcc: troup@auric.debian.org
1135 Subject: Bug#%s: fixed in %s %s
1137 We believe that the bug you reported is fixed in the latest version of
1138 %s, which has been installed in the Debian FTP archive:
1140 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], bug, bug, changes["source"], changes["version"], changes["source"], short_summary)
1142 if changes["distribution"].has_key("stable"):
1143 mail_message = mail_message + """Note that this package is not part of the released stable Debian
1144 distribution. It may have dependencies on other unreleased software,
1145 or other instabilities. Please take care if you wish to install it.
1146 The update will eventually make its way into the next released Debian
1149 mail_message = mail_message + """A summary of the changes between this version and the previous one is
1152 Thank you for reporting the bug, which will now be closed. If you
1153 have further comments please address them to %s@bugs.debian.org,
1154 and the maintainer will reopen the bug report if appropriate.
1156 Debian distribution maintenance software
1158 %s (supplier of updated %s package)
1160 (This message was generated automatically at their request; if you
1161 believe that there is a problem with it please contact the archive
1162 administrators by mailing ftpmaster@debian.org)
1165 %s""" % (bug, changes["maintainer"], changes["source"], changes["filecontents"])
1167 utils.send_mail (mail_message, "")
1169 summary = summary + "Setting bugs to severity fixed: "
1170 control_message = ""
1172 summary = summary + "%s " % (bug)
1173 control_message = control_message + "tag %s + fixed\n" % (bug)
1174 if action and control_message != "":
1175 mail_message = """Return-Path: %s
1177 To: control@bugs.debian.org
1178 Bcc: troup@auric.debian.org, %s
1179 Subject: Fixed in NMU of %s %s
1184 This message was generated automatically in response to a
1185 non-maintainer upload. The .changes file follows.
1188 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes["maintainer822"], changes["source"], changes["version"], control_message, changes["filecontents"])
1189 utils.send_mail (mail_message, "")
1190 summary = summary + "\n"
1194 ###############################################################################
1196 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1197 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1198 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1199 # processed it during it's checks of -2. If -1 has been deleted or
1200 # otherwise not checked by da-install, the .orig.tar.gz will not have
1201 # been checked at all. To get round this, we force the .orig.tar.gz
1202 # into the .changes structure and reprocess the .changes file.
1204 def process_it (changes_file):
1205 global reprocess, orig_tar_id, changes, dsc, dsc_files, files, reject_message;
1207 # Reset some globals
1214 legacy_source_untouchable = {};
1215 reject_message = "";
1218 # Absolutize the filename to avoid the requirement of being in the
1219 # same directory as the .changes file.
1220 changes_file = os.path.abspath(changes_file);
1222 # And since handling of installs to stable munges with the CWD;
1223 # save and restore it.
1226 check_signature (changes_file);
1227 check_changes (changes_file);
1235 action(changes_file);
1240 ###############################################################################
1243 global Cnf, projectB, install_bytes, new_ack_old
1247 Cnf = apt_pkg.newConfiguration();
1248 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1250 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1251 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1252 ('h',"help","Dinstall::Options::Help"),
1253 ('k',"ack-new","Dinstall::Options::Ack-New"),
1254 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1255 ('n',"no-action","Dinstall::Options::No-Action"),
1256 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1257 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1258 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1259 ('v',"version","Dinstall::Options::Version")];
1261 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1263 if Cnf["Dinstall::Options::Help"]:
1266 if Cnf["Dinstall::Options::Version"]:
1267 print "katie version 0.0000000000";
1270 postgresql_user = None; # Default == Connect as user running program.
1272 # -n/--dry-run invalidates some other options which would involve things happening
1273 if Cnf["Dinstall::Options::No-Action"]:
1274 Cnf["Dinstall::Options::Automatic"] = ""
1275 Cnf["Dinstall::Options::Ack-New"] = ""
1276 postgresql_user = Cnf["DB::ROUser"];
1278 projectB = pg.connect('projectb', Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1280 db_access.init(Cnf, projectB);
1282 # Check that we aren't going to clash with the daily cron job
1284 if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1285 sys.stderr.write("Archive maintenance in progress. Try again later.\n");
1288 # Obtain lock if not in no-action mode
1290 if not Cnf["Dinstall::Options::No-Action"]:
1291 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1292 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1294 # Read in the list of already-acknowledged NEW packages
1295 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1297 for line in new_ack_list.readlines():
1298 new_ack_old[line[:-1]] = 1;
1299 new_ack_list.close();
1301 # Process the changes files
1302 for changes_file in changes_files:
1303 print "\n" + changes_file;
1304 process_it (changes_file);
1308 if install_count > 1:
1310 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1312 # Write out the list of already-acknowledged NEW packages
1313 if Cnf["Dinstall::Options::Ack-New"]:
1314 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1315 for i in new_ack_new.keys():
1316 new_ack_list.write(i+'\n')
1317 new_ack_list.close()
1320 if __name__ == '__main__':