X-Git-Url: https://git.decadent.org.uk/gitweb/?a=blobdiff_plain;f=katie;h=03c3298d722fe0cb5509b8fe6727f323695e48e2;hb=bdbd8d4b06f3e5316be3a239b511c6d34e781105;hp=5659d34ac78314b66d80ec5d0d40b74213b293e2;hpb=0b8f604ea48e6ffeadc2687bf289901d5981aaba;p=dak.git diff --git a/katie b/katie index 5659d34a..03c3298d 100755 --- a/katie +++ b/katie @@ -2,7 +2,7 @@ # Installs Debian packaes # Copyright (C) 2000, 2001 James Troup -# $Id: katie,v 1.35 2001-03-24 03:30:16 troup Exp $ +# $Id: katie,v 1.50 2001-06-24 23:17:43 troup 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 @@ -32,17 +32,19 @@ ######################################################################################### -import FCNTL, commands, fcntl, getopt, gzip, os, pg, pwd, re, shutil, stat, string, sys, tempfile, time +import FCNTL, commands, fcntl, getopt, gzip, os, pg, pwd, re, shutil, stat, string, sys, tempfile, time, traceback import apt_inst, apt_pkg import utils, db_access ############################################################################### -re_isanum = re.compile (r'^\d+$'); -re_changes = re.compile (r'changes$'); +re_isanum = re.compile (r"^\d+$"); +re_changes = re.compile (r"changes$"); re_default_answer = re.compile(r"\[(.*)\]"); re_fdnic = re.compile("\n\n"); re_bad_diff = re.compile("^[\-\+][\-\+][\-\+] /dev/null"); +re_bin_only_nmu_of_mu = re.compile("\.\d+\.\d+$"); +re_bin_only_nmu_of_nmu = re.compile("\.\d+$"); ######################################################################################### @@ -63,6 +65,7 @@ orig_tar_id = None; orig_tar_location = ""; legacy_source_untouchable = {}; Subst = {}; +nmu = None; ######################################################################################### @@ -90,7 +93,75 @@ def check_signature (filename): return 0 return 1 -##################################################################################################################### +###################################################################################################### + +class nmu_p: + # Read in the group maintainer override file + def __init__ (self): + self.group_maint = {}; + if Cnf.get("Dinstall::GroupOverrideFilename"): + filename = Cnf["Dir::OverrideDir"] + Cnf["Dinstall::GroupOverrideFilename"]; + file = utils.open_file(filename, 'r'); + for line in file.readlines(): + line = string.strip(utils.re_comments.sub('', line)); + if line != "": + self.group_maint[line] = 1; + file.close(); + + def is_an_nmu (self, changes, dsc): + (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"])); + # changes["changedbyname"] == dsc_name is probably never true, but better safe than sorry + if dsc_name == changes["maintainername"] and (changes["changedbyname"] == "" or changes["changedbyname"] == dsc_name): + return 0; + + if dsc.has_key("uploaders"): + uploaders = string.split(dsc["uploaders"], ","); + uploadernames = {}; + for i in uploaders: + (rfc822, name, email) = utils.fix_maintainer (string.strip(i)); + uploadernames[name] = ""; + if uploadernames.has_key(changes["changedbyname"]): + return 0; + + # Some group maintained packages (e.g. Debian QA) are never NMU's + if self.group_maint.has_key(changes["maintainername"]): + return 0; + + return 1; + +###################################################################################################### + +# Ensure that source exists somewhere in the archive for the binary +# upload being processed. +# +# (1) exact match => 1.0-3 +# (2) Bin-only NMU of an MU => 1.0-3.0.1 +# (3) Bin-only NMU of a sourceful-NMU => 1.0-3.1.1 + +def source_exists (package, source_version): + q = projectB.query("SELECT s.version FROM source s WHERE s.source = '%s'" % (package)); + + # Reduce the query results to a list of version numbers + ql = map(lambda x: x[0], q.getresult()); + + # Try (1) + if ql.count(source_version): + return 1; + + # Try (2) + orig_source_version = re_bin_only_nmu_of_mu.sub('', source_version); + if ql.count(orig_source_version): + return 1; + + # Try (3) + orig_source_version = re_bin_only_nmu_of_nmu.sub('', source_version); + if ql.count(orig_source_version): + return 1; + + # No source found... + return 0; + +###################################################################################################### # See if a given package is in the override table @@ -140,8 +211,10 @@ def check_changes(filename): global reject_message, changes, files # Default in case we bail out - changes["maintainer822"] = Cnf["Dinstall::MyEmailAddress"]; - + changes["maintainer822"] = Cnf["Dinstall::MyEmailAddress"]; + changes["changedby822"] = Cnf["Dinstall::MyEmailAddress"]; + changes["architecture"] = {}; + # Parse the .changes field into a dictionary try: changes = utils.parse_changes(filename, 0) @@ -184,11 +257,6 @@ def check_changes(filename): # Fix the Changed-By: field to be RFC822 compatible; if it exists. (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]) = utils.fix_maintainer(changes.get("changed-by","")); - # For source uploads the Changed-By field wins; otherwise Maintainer wins. - if changes["architecture"].has_key("source"): - changes["uploader822"] = "To: %s\nCc: %s" % (changes["changedby822"], changes["maintainer822"]); - # changes["uploadername"], changes["uploaderemail"]) = (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]); - # Ensure all the values in Closes: are numbers if changes.has_key("closes"): for i in changes["closes"].keys(): @@ -239,7 +307,7 @@ def check_changes(filename): # FIXME: should probably remove anything that != stable for i in ("frozen", "unstable"): if changes["distribution"].has_key(i): - reject_message = reject_message + "Removing %s from distribution list.\n" + reject_message = reject_message + "Removing %s from distribution list.\n" % (i) del changes["distribution"][i] changes["stable upload"] = 1; # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool @@ -270,7 +338,11 @@ def check_files(): for file in files.keys(): # Check the file is readable if os.access(file,os.R_OK) == 0: - reject_message = reject_message + "Rejected: Can't read `%s'.\n" % (file) + if os.path.exists(file): + reject_message = reject_message + "Rejected: Can't read `%s'. [permission denied]\n" % (file) + else: + reject_message = reject_message + "Rejected: Can't read `%s'. [file not found]\n" % (file) + files[file]["type"] = "unreadable"; continue # If it's byhand skip remaining checks @@ -330,6 +402,18 @@ def check_files(): files[file]["source"] = control.Find("Source", ""); if files[file]["source"] == "": files[file]["source"] = files[file]["package"]; + # Get the source version + source = files[file]["source"]; + source_version = "" + if string.find(source, "(") != -1: + m = utils.re_extract_src_version.match(source) + source = m.group(1) + source_version = m.group(2) + if not source_version: + source_version = files[file]["version"]; + files[file]["source package"] = source; + files[file]["source version"] = source_version; + # Checks for a source package... else: m = utils.re_issource.match(file) @@ -379,8 +463,8 @@ def check_files(): if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file): files[file]["new"] = 1 - # Find any old binary packages if files[file]["type"] == "deb": + # Find any old binary packages 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" % (files[file]["package"], suite, files[file]["architecture"])) oldfiles = q.dictresult() @@ -395,6 +479,16 @@ def check_files(): if q.getresult() != []: reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file) + # Check for existent source + # FIXME: this is no longer per suite + if changes["architecture"].has_key("source"): + source_version = files[file]["source version"]; + if source_version != changes["version"]: + reject_message = reject_message + "Rejected: source version (%s) for %s doesn't match changes version %s.\n" % (files[file]["source version"], file, changes["version"]); + else: + if not source_exists (files[file]["source package"], source_version): + reject_message = reject_message + "Rejected: no source found for %s %s (%s).\n" % (files[file]["source package"], source_version, file); + # Find any old .dsc files elif files[file]["type"] == "dsc": 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" @@ -527,7 +621,7 @@ def check_dsc (): # Not there? Check in Incoming... # [See comment above process_it() for explanation # of why this is necessary...] - if os.access(dsc_file, os.R_OK) != 0: + if os.path.exists(dsc_file): files[dsc_file] = {}; files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE]; files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"]; @@ -626,16 +720,27 @@ def check_override (): def update_subst (changes_filename): global Subst; - + if changes.has_key("architecture"): Subst["__ARCHITECTURE__"] = string.join(changes["architecture"].keys(), ' ' ); + else: + Subst["__ARCHITECTURE__"] = "Unknown"; Subst["__CHANGES_FILENAME__"] = os.path.basename(changes_filename); - Subst["__FILE_CONTENTS__"] = changes.get("filecontents"); - Subst["__MAINTAINER_ADDRESS__"] = changes["maintainer822"]; - Subst["__MAINTAINER__"] = changes.get("maintainer"); + Subst["__FILE_CONTENTS__"] = changes.get("filecontents", ""); + + # For source uploads the Changed-By field wins; otherwise Maintainer wins. + if changes["architecture"].has_key("source") and changes["changedby822"] != "" and (changes["changedby822"] != changes["maintainer822"]): + Subst["__MAINTAINER_FROM__"] = changes["changedby822"]; + Subst["__MAINTAINER_TO__"] = changes["changedby822"] + ", " + changes["maintainer822"]; + Subst["__MAINTAINER__"] = changes.get("changed-by", "Unknown"); + else: + Subst["__MAINTAINER_FROM__"] = changes["maintainer822"]; + Subst["__MAINTAINER_TO__"] = changes["maintainer822"]; + Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown"); + Subst["__REJECT_MESSAGE__"] = reject_message; - Subst["__SOURCE__"] = changes.get("source"); - Subst["__VERSION__"] = changes.get("version"); + Subst["__SOURCE__"] = changes.get("source", "Unknown"); + Subst["__VERSION__"] = changes.get("version", "Unknown"); ##################################################################################################################### @@ -685,7 +790,11 @@ def action (changes_filename): answer = 'S' if string.find(reject_message, "Rejected") != -1: - if time.time()-os.path.getmtime(changes_filename) < 86400: + try: + modified_time = time.time()-os.path.getmtime(changes_filename); + except: # i.e. ignore errors like 'file does not exist'; + modified_time = 0; + if modified_time < 86400: print "SKIP (too new)\n" + reject_message,; prompt = "[S]kip, Manual reject, Quit ?"; else: @@ -791,14 +900,8 @@ def install (changes_filename, summary, short_summary): architecture_id = db_access.get_architecture_id (architecture); type = files[file]["dbtype"]; dsc_component = files[file]["component"] - source = files[file]["source"] - source_version = "" - if string.find(source, "(") != -1: - m = utils.re_extract_src_version.match(source) - source = m.group(1) - source_version = m.group(2) - if not source_version: - source_version = version + source = files[file]["source package"] + source_version = files[file]["source version"]; filename = files[file]["pool name"] + file; if not files[file]["files id"]: files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"]) @@ -869,7 +972,7 @@ def install (changes_filename, summary, short_summary): try: utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename)) except: - sys.stderr.write("W: couldn't move changes file '%s' to DONE directory [Got %s].\n" % (os.path.basename(changes_filename), sys.exc_type)); + utils.warn("couldn't move changes file '%s' to DONE directory. [Got %s]" % (os.path.basename(changes_filename), sys.exc_type)); install_count = install_count + 1; @@ -901,8 +1004,7 @@ def stable_install (changes_filename, summary, short_summary): q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version)) ql = q.getresult() if ql == []: - sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version)); - sys.exit(1); + utils.fubar("[INTERNAL ERROR] couldn't find '%s' (%s) in source table." % (package, version)); source_id = ql[0][0]; suite_id = db_access.get_suite_id('proposed-updates'); projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id)); @@ -918,8 +1020,7 @@ def stable_install (changes_filename, summary, short_summary): 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)) ql = q.getresult() if ql == []: - sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture)); - sys.exit(1); + utils.fubar("[INTERNAL ERROR] couldn't find '%s' (%s for %s architecture) in binaries table." % (package, version, architecture)); binary_id = ql[0][0]; suite_id = db_access.get_suite_id('proposed-updates'); projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id)); @@ -928,7 +1029,8 @@ def stable_install (changes_filename, summary, short_summary): projectB.query("COMMIT WORK"); - utils.move (changes_filename, Cnf["Rhona::Morgue"] + os.path.basename(changes_filename)); + # FIXME + utils.move (changes_filename, Cnf["Dir::Morgue"] + '/katie/' + os.path.basename(changes_filename)); # Update the Stable ChangeLog file @@ -960,6 +1062,7 @@ def stable_install (changes_filename, summary, short_summary): if not Cnf["Dinstall::Options::No-Mail"]: Subst["__SUITE__"] = " into stable"; Subst["__SUMMARY__"] = summary; + mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.installed","r").read()); utils.send_mail (mail_message, "") announce (short_summary, 1) @@ -978,14 +1081,14 @@ def reject (changes_filename, manual_reject_mail_filename): try: utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename)); except: - sys.stderr.write("W: couldn't reject changes file '%s' [Got %s].\n" % (base_changes_filename, sys.exc_type)); + utils.warn("couldn't reject changes file '%s'. [Got %s]" % (base_changes_filename, sys.exc_type)); pass; for file in files.keys(): if os.path.exists(file): try: utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file)); except: - sys.stderr.write("W: couldn't reject file '%s' [Got %s].\n" % (file, sys.exc_type)); + utils.warn("couldn't reject file '%s'. [Got %s]" % (file, sys.exc_type)); pass; # If this is not a manual rejection generate the .reason file and rejection mail message @@ -996,6 +1099,7 @@ def reject (changes_filename, manual_reject_mail_filename): os.write(fd, reject_message); os.close(fd); Subst["__MANUAL_REJECT_MESSAGE__"] = ""; + Subst["__CC__"] = "X-Katie-Rejection: automatic (moo)"; reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read()); else: # Have a manual rejection file to use reject_mail_message = ""; # avoid 's @@ -1010,11 +1114,11 @@ def manual_reject (changes_filename): global Subst; # Build up the rejection email - user_email_address = string.replace(string.split(pwd.getpwuid(os.getuid())[4],',')[0], '.', '') - user_email_address = user_email_address + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"]) + user_email_address = utils.whoami() + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"]) manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "") Subst["__MANUAL_REJECT_MESSAGE__"] = manual_reject_message; + Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"]; reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read()); # Write the rejection email out as the .reason file @@ -1028,10 +1132,9 @@ def manual_reject (changes_filename): # If we weren't given one, spawn an editor so the user can add one in if manual_reject_message == "": - result = os.system("vi +6 %s" % (reject_file)) + result = os.system("vi +6 %s" % (reject_filename)) if result != 0: - sys.stderr.write ("vi invocation failed for `%s'!\n" % (reject_file)) - sys.exit(result) + utils.fubar("vi invocation failed for `%s'!" % (reject_filename), result); # Then process it as if it were an automatic rejection reject (changes_filename, reject_filename) @@ -1080,12 +1183,9 @@ def announce (short_summary, action): mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.announce","r").read()); utils.send_mail (mail_message, "") - (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"])); bugs = changes["closes"].keys() bugs.sort() - # changes["changedbyname"] == dsc_name is probably never true, but better - # safe than sorry - if dsc_name == changes["maintainername"] and (changes["changedbyname"] == "" or changes["changedbyname"] == dsc_name): + if not nmu.is_an_nmu(changes, dsc): summary = summary + "Closing bugs: " for bug in bugs: summary = summary + "%s " % (bug) @@ -1148,14 +1248,19 @@ def process_it (changes_file): # save and restore it. cwd = os.getcwd(); - check_signature (changes_file); - check_changes (changes_file); - while reprocess: - reprocess = 0; - check_files (); - check_md5sums (); - check_dsc (); - check_diff (); + try: + check_signature (changes_file); + check_changes (changes_file); + while reprocess: + reprocess = 0; + check_files (); + check_md5sums (); + check_dsc (); + check_diff (); + except: + print "ERROR"; + traceback.print_exc(file=sys.stdout); + pass; update_subst(changes_file); action(changes_file); @@ -1166,7 +1271,7 @@ def process_it (changes_file): ############################################################################### def main(): - global Cnf, projectB, install_bytes, new_ack_old, Subst + global Cnf, projectB, install_bytes, new_ack_old, Subst, nmu apt_pkg.init(); @@ -1208,8 +1313,7 @@ def main(): # Check that we aren't going to clash with the daily cron job if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]: - sys.stderr.write("Archive maintenance in progress. Try again later.\n"); - sys.exit(2); + utils.fubar("Archive maintenance in progress. Try again later."); # Obtain lock if not in no-action mode @@ -1228,7 +1332,7 @@ def main(): Subst = {} Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"]; Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"]; - bcc = "X-Katie: $Revision: 1.35 $" + bcc = "X-Katie: $Revision: 1.50 $" if Cnf.has_key("Dinstall::Bcc"): Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"]); else: @@ -1236,6 +1340,12 @@ def main(): Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"]; Subst["__KATIE_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"]; + # Read in the group-maint override file + nmu = nmu_p(); + + # Sort the .changes files so that we process sourceful ones first + changes_files.sort(utils.changes_compare); + # Process the changes files for changes_file in changes_files: print "\n" + changes_file;