#!/usr/bin/env python
# Checks Debian packages from Incoming
-# Copyright (C) 2000, 2001, 2002, 2003, 2004 James Troup <james@nocrew.org>
-# $Id: jennifer,v 1.53 2004-11-27 17:59:47 troup Exp $
+# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005 James Troup <james@nocrew.org>
+# $Id: jennifer,v 1.60 2005-11-25 09:35:09 ajt Exp $
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
################################################################################
# Globals
-jennifer_version = "$Revision: 1.53 $";
+jennifer_version = "$Revision: 1.60 $";
Cnf = None;
Options = None;
# Check for mandatory fields
for i in ("source", "binary", "architecture", "version", "distribution",
- "maintainer", "files", "changes"):
+ "maintainer", "files", "changes", "description"):
if not changes.has_key(i):
reject("%s: Missing mandatory field `%s'." % (filename, i));
return 0 # Avoid <undef> errors during later tests
changes["changedbyname"], changes["changedbyemail"]) = \
utils.fix_maintainer (changes.get("changed-by", ""));
except utils.ParseMaintError, msg:
+ (changes["changedby822"], changes["changedby2047"],
+ changes["changedbyname"], changes["changedbyemail"]) = \
+ ("", "", "", "")
reject("%s: Changed-By field ('%s') failed to parse: %s" \
% (filename, changes["changed-by"], msg));
changes["distribution"][dest] = 1;
if type != "silent-map":
reject("Mapping %s to %s." % (source, dest),"");
+ if changes.has_key("distribution-version"):
+ if changes["distribution-version"].has_key(source):
+ changes["distribution-version"][source]=dest
elif type == "map-unreleased":
(source, dest) = args[1:3];
if changes["distribution"].has_key(source):
if changes["distribution"].has_key(suite):
del changes["distribution"][suite];
reject("Ignoring %s as a target suite." % (suite), "Warning: ");
+ elif type == "reject":
+ suite = args[1];
+ if changes["distribution"].has_key(suite):
+ reject("Uploads to %s are not accepted." % (suite));
+ elif type == "propup-version":
+ # give these as "uploaded-to(non-mapped) suites-to-add-when-upload-obsoletes"
+ #
+ # changes["distribution-version"] looks like: {'testing': 'testing-proposed-updates'}
+ if changes["distribution"].has_key(args[1]):
+ changes.setdefault("distribution-version", {})
+ for suite in args[2:]: changes["distribution-version"][suite]=suite
# Ensure there is (still) a target distribution
if changes["distribution"].keys() == []:
################################################################################
+def check_deb_ar(filename, control):
+ """Sanity check the ar of a .deb, i.e. that there is:
+
+ o debian-binary
+ o control.tar.gz
+ o data.tar.gz or data.tar.bz2
+
+in that order, and nothing else. If the third member is a
+data.tar.bz2, an additional check is performed for the required
+Pre-Depends on dpkg (>= 1.10.24)."""
+ cmd = "ar t %s" % (filename)
+ (result, output) = commands.getstatusoutput(cmd)
+ if result != 0:
+ reject("%s: 'ar t' invocation failed." % (filename))
+ reject(utils.prefix_multi_line_string(output, " [ar output:] "), "")
+ chunks = output.split('\n')
+ if len(chunks) != 3:
+ reject("%s: found %d chunks, expected 3." % (filename, len(chunks)))
+ if chunks[0] != "debian-binary":
+ reject("%s: first chunk is '%s', expected 'debian-binary'." % (filename, chunks[0]))
+ if chunks[1] != "control.tar.gz":
+ reject("%s: second chunk is '%s', expected 'control.tar.gz'." % (filename, chunks[1]))
+ if chunks[2] == "data.tar.bz2":
+ # Packages using bzip2 compression must have a Pre-Depends on dpkg >= 1.10.24.
+ found_needed_predep = 0
+ for parsed_dep in apt_pkg.ParseDepends(control.Find("Pre-Depends", "")):
+ for atom in parsed_dep:
+ (dep, version, constraint) = atom
+ if dep != "dpkg" or (constraint != ">=" and constraint != ">>") or \
+ len(parsed_dep) > 1: # or'ed deps don't count
+ continue
+ if (constraint == ">=" and apt_pkg.VersionCompare(version, "1.10.24") < 0) or \
+ (constraint == ">>" and apt_pkg.VersionCompare(version, "1.10.23") < 0):
+ continue
+ found_needed_predep = 1
+ if not found_needed_predep:
+ reject("%s: uses bzip2 compression, but doesn't Pre-Depend on dpkg (>= 1.10.24)" % (filename))
+ elif chunks[2] != "data.tar.gz":
+ reject("%s: third chunk is '%s', expected 'data.tar.gz' or 'data.tar.bz2'." % (filename, chunks[2]))
+
+################################################################################
+
def check_files():
global reprocess
files[file]["type"] = "unreadable";
continue;
# If it's byhand skip remaining checks
- if files[file]["section"] == "byhand":
+ if files[file]["section"] == "byhand" or files[file]["section"] == "raw-installer":
files[file]["byhand"] = 1;
files[file]["type"] = "byhand";
# Checks for a binary package...
# Check the version and for file overwrites
reject(Katie.check_binary_against_db(file),"");
+ check_deb_ar(file, control)
+
# Checks for a source package...
else:
m = utils.re_issource.match(file);
# If there isn't one, we have nothing to do. (We have reject()ed the upload already)
if not dsc_filename:
+ reject("source uploads must contain a dsc file");
return 0;
# Parse the .dsc file
deb_file = utils.open_file(filename);
apt_inst.debExtract(deb_file,tar.callback,"control.tar.gz");
deb_file.seek(0);
- apt_inst.debExtract(deb_file,tar.callback,"data.tar.gz");
+ try:
+ apt_inst.debExtract(deb_file,tar.callback,"data.tar.gz")
+ except SystemError, e:
+ # If we can't find a data.tar.gz, look for data.tar.bz2 instead.
+ if not re.match(r"Cannot f[ui]nd chunk data.tar.gz$", str(e)):
+ raise
+ deb_file.seek(0)
+ apt_inst.debExtract(deb_file,tar.callback,"data.tar.bz2")
deb_file.close();
#
future_files = tar.future_files.keys();
(summary, short_summary) = Katie.build_summaries();
- byhand = new = "";
- for file in files.keys():
- if files[file].has_key("byhand"):
- byhand = 1
- elif files[file].has_key("new"):
- new = 1
+ # q-unapproved hax0ring
+ queues = [ "New", "Byhand" ]
+ queue_info = {
+ "New": { "is": is_new, "process": acknowledge_new },
+ "Byhand" : { "is": is_byhand, "process": do_byhand },
+ }
(prompt, answer) = ("", "XXX")
if Options["No-Action"] or Options["Automatic"]:
answer = 'S'
+ queuekey = ''
+
if reject_message.find("Rejected") != -1:
if upload_too_new():
print "SKIP (too new)\n" + reject_message,;
prompt = "[R]eject, Skip, Quit ?";
if Options["Automatic"]:
answer = 'R';
- elif new:
- print "NEW to %s\n%s%s" % (", ".join(changes["distribution"].keys()), reject_message, summary),;
- prompt = "[N]ew, Skip, Quit ?";
- if Options["Automatic"]:
- answer = 'N';
- elif byhand:
- print "BYHAND\n" + reject_message + summary,;
- prompt = "[B]yhand, Skip, Quit ?";
- if Options["Automatic"]:
- answer = 'B';
else:
- print "ACCEPT\n" + reject_message + summary,;
- prompt = "[A]ccept, Skip, Quit ?";
- if Options["Automatic"]:
- answer = 'A';
+ queue = None
+ for q in queues:
+ if queue_info[q]["is"]():
+ queue = q
+ break
+ if queue:
+ print "%s for %s\n%s%s" % (
+ queue.upper(), ", ".join(changes["distribution"].keys()),
+ reject_message, summary),
+ queuekey = queue[0].upper()
+ if queuekey in "RQSA":
+ queuekey = "D"
+ prompt = "[D]ivert, Skip, Quit ?"
+ else:
+ prompt = "[%s]%s, Skip, Quit ?" % (queuekey, queue[1:].lower())
+ if Options["Automatic"]:
+ answer = queuekey
+ else:
+ print "ACCEPT\n" + reject_message + summary,;
+ prompt = "[A]ccept, Skip, Quit ?";
+ if Options["Automatic"]:
+ answer = 'A';
while prompt.find(answer) == -1:
answer = utils.our_raw_input(prompt);
Katie.do_reject(0, reject_message);
elif answer == 'A':
accept(summary, short_summary);
- elif answer == 'B':
- do_byhand(summary);
- elif answer == 'N':
- acknowledge_new (summary);
+ elif answer == queuekey:
+ queue_info[queue]["process"](summary)
elif answer == 'Q':
sys.exit(0)
################################################################################
+def is_byhand ():
+ for file in files.keys():
+ if files[file].has_key("byhand"):
+ return 1
+ return 0
+
def do_byhand (summary):
print "Moving to BYHAND holding area."
Logger.log(["Moving to byhand", pkg.changes_file]);
################################################################################
+def is_new ():
+ for file in files.keys():
+ if files[file].has_key("new"):
+ return 1
+ return 0
+
def acknowledge_new (summary):
Subst = Katie.Subst;
# Check that we aren't going to clash with the daily cron job
- if not Options["No-Action"] and os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::Root"])) and not Options["No-Lock"]:
+ if not Options["No-Action"] and os.path.exists("%s/daily.lock" % (Cnf["Dir::Lock"])) and not Options["No-Lock"]:
utils.fubar("Archive maintenance in progress. Try again later.");
# Obtain lock if not in no-action mode and initialize the log