3 # Installs Debian packaes
4 # Copyright (C) 2000, 2001 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.41 2001-05-17 01:21:40 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"];
145 changes["architecture"] = {};
147 # Parse the .changes field into a dictionary
149 changes = utils.parse_changes(filename, 0)
150 except utils.cant_open_exc:
151 reject_message = "Rejected: can't read changes file '%s'.\n" % (filename)
153 except utils.changes_parse_error_exc, line:
154 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
157 # Parse the Files field from the .changes into another dictionary
159 files = utils.build_file_list(changes, "");
160 except utils.changes_parse_error_exc, line:
161 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line);
163 # Check for mandatory fields
164 for i in ("source", "binary", "architecture", "version", "distribution","maintainer", "files"):
165 if not changes.has_key(i):
166 reject_message = "Rejected: Missing field `%s' in changes file.\n" % (i)
167 return 0 # Avoid <undef> errors during later tests
169 # Override the Distribution: field if appropriate
170 if Cnf["Dinstall::Options::Override-Distribution"] != "":
171 reject_message = reject_message + "Warning: Distribution was overriden from %s to %s.\n" % (changes["distribution"], Cnf["Dinstall::Options::Override-Distribution"])
172 changes["distribution"] = Cnf["Dinstall::Options::Override-Distribution"]
174 # Split multi-value fields into a lower-level dictionary
175 for i in ("architecture", "distribution", "binary", "closes"):
176 o = changes.get(i, "")
180 for j in string.split(o):
183 # Fix the Maintainer: field to be RFC822 compatible
184 (changes["maintainer822"], changes["maintainername"], changes["maintaineremail"]) = utils.fix_maintainer (changes["maintainer"])
186 # Fix the Changed-By: field to be RFC822 compatible; if it exists.
187 (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]) = utils.fix_maintainer(changes.get("changed-by",""));
189 # Ensure all the values in Closes: are numbers
190 if changes.has_key("closes"):
191 for i in changes["closes"].keys():
192 if re_isanum.match (i) == None:
193 reject_message = reject_message + "Rejected: `%s' from Closes field isn't a number.\n" % (i)
195 # Map frozen to unstable if frozen doesn't exist
196 if changes["distribution"].has_key("frozen") and not Cnf.has_key("Suite::Frozen"):
197 del changes["distribution"]["frozen"]
198 changes["distribution"]["unstable"] = 1;
199 reject_message = reject_message + "Mapping frozen to unstable.\n"
201 # Map testing to unstable
202 if changes["distribution"].has_key("testing"):
203 del changes["distribution"]["testing"]
204 changes["distribution"]["unstable"] = 1;
205 reject_message = reject_message + "Mapping testing to unstable.\n"
207 # Ensure target distributions exist
208 for i in changes["distribution"].keys():
209 if not Cnf.has_key("Suite::%s" % (i)):
210 reject_message = reject_message + "Rejected: Unknown distribution `%s'.\n" % (i)
212 # Ensure there _is_ a target distribution
213 if changes["distribution"].keys() == []:
214 reject_message = reject_message + "Rejected: huh? Distribution field is empty in changes file.\n";
216 # Map unreleased arches from stable to unstable
217 if changes["distribution"].has_key("stable"):
218 for i in changes["architecture"].keys():
219 if not Cnf.has_key("Suite::Stable::Architectures::%s" % (i)):
220 reject_message = reject_message + "Mapping stable to unstable for unreleased arch `%s'.\n" % (i)
221 del changes["distribution"]["stable"]
222 changes["distribution"]["unstable"] = 1;
224 # Map arches not being released from frozen to unstable
225 if changes["distribution"].has_key("frozen"):
226 for i in changes["architecture"].keys():
227 if not Cnf.has_key("Suite::Frozen::Architectures::%s" % (i)):
228 reject_message = reject_message + "Mapping frozen to unstable for non-releasing arch `%s'.\n" % (i)
229 del changes["distribution"]["frozen"]
230 changes["distribution"]["unstable"] = 1;
232 # Handle uploads to stable
233 if changes["distribution"].has_key("stable"):
234 # If running from within proposed-updates; assume an install to stable
235 if string.find(os.getcwd(), 'proposed-updates') != -1:
236 # FIXME: should probably remove anything that != stable
237 for i in ("frozen", "unstable"):
238 if changes["distribution"].has_key(i):
239 reject_message = reject_message + "Removing %s from distribution list.\n" % (i)
240 del changes["distribution"][i]
241 changes["stable upload"] = 1;
242 # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool
243 file = files.keys()[0];
244 if os.access(file, os.R_OK) == 0:
245 pool_dir = Cnf["Dir::PoolDir"] + '/' + utils.poolify(changes["source"], files[file]["component"]);
247 # Otherwise (normal case) map stable to updates
249 reject_message = reject_message + "Mapping stable to updates.\n";
250 del changes["distribution"]["stable"];
251 changes["distribution"]["proposed-updates"] = 1;
253 # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
254 changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
255 changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
257 if string.find(reject_message, "Rejected:") != -1:
263 global reject_message
265 archive = utils.where_am_i();
267 for file in files.keys():
268 # Check the file is readable
269 if os.access(file,os.R_OK) == 0:
270 reject_message = reject_message + "Rejected: Can't read `%s'.\n" % (file)
271 files[file]["type"] = "unreadable";
273 # If it's byhand skip remaining checks
274 if files[file]["section"] == "byhand":
275 files[file]["byhand"] = 1;
276 files[file]["type"] = "byhand";
277 # Checks for a binary package...
278 elif utils.re_isadeb.match(file) != None:
279 files[file]["type"] = "deb";
281 # Extract package information using dpkg-deb
283 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
285 reject_message = reject_message + "Rejected: %s: debExtractControl() raised %s.\n" % (file, sys.exc_type);
286 # Can't continue, none of the checks on control would work.
289 # Check for mandatory fields
290 if control.Find("Package") == None:
291 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
292 if control.Find("Architecture") == None:
293 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
294 if control.Find("Version") == None:
295 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
297 # Ensure the package name matches the one give in the .changes
298 if not changes["binary"].has_key(control.Find("Package", "")):
299 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
301 # Validate the architecture
302 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
303 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
305 # Check the architecture matches the one given in the .changes
306 if not changes["architecture"].has_key(control.Find("Architecture", "")):
307 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
308 # Check the section & priority match those given in the .changes (non-fatal)
309 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
310 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"])
311 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
312 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"])
314 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
316 files[file]["package"] = control.Find("Package");
317 files[file]["architecture"] = control.Find("Architecture");
318 files[file]["version"] = control.Find("Version");
319 files[file]["maintainer"] = control.Find("Maintainer", "");
320 if file[-5:] == ".udeb":
321 files[file]["dbtype"] = "udeb";
322 elif file[-4:] == ".deb":
323 files[file]["dbtype"] = "deb";
325 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
326 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
327 files[file]["source"] = control.Find("Source", "");
328 if files[file]["source"] == "":
329 files[file]["source"] = files[file]["package"];
330 # Checks for a source package...
332 m = utils.re_issource.match(file)
334 files[file]["package"] = m.group(1)
335 files[file]["version"] = m.group(2)
336 files[file]["type"] = m.group(3)
338 # Ensure the source package name matches the Source filed in the .changes
339 if changes["source"] != files[file]["package"]:
340 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
342 # Ensure the source version matches the version in the .changes file
343 if files[file]["type"] == "orig.tar.gz":
344 changes_version = changes["chopversion2"]
346 changes_version = changes["chopversion"]
347 if changes_version != files[file]["version"]:
348 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
350 # Ensure the .changes lists source in the Architecture field
351 if not changes["architecture"].has_key("source"):
352 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
354 # Check the signature of a .dsc file
355 if files[file]["type"] == "dsc":
356 check_signature(file)
358 files[file]["fullname"] = file
360 # Not a binary or source package? Assume byhand...
362 files[file]["byhand"] = 1;
363 files[file]["type"] = "byhand";
365 files[file]["oldfiles"] = {}
366 for suite in changes["distribution"].keys():
368 if files[file].has_key("byhand"):
371 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
372 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
375 # See if the package is NEW
376 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
377 files[file]["new"] = 1
379 # Find any old binary packages
380 if files[file]["type"] == "deb":
381 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"
382 % (files[file]["package"], suite, files[file]["architecture"]))
383 oldfiles = q.dictresult()
384 for oldfile in oldfiles:
385 files[file]["oldfiles"][suite] = oldfile
386 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
387 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
388 reject_message = reject_message + "Rejected: %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
389 # Check for existing copies of the file
390 if not changes.has_key("stable upload"):
391 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"]))
392 if q.getresult() != []:
393 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
395 # Find any old .dsc files
396 elif files[file]["type"] == "dsc":
397 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"
398 % (files[file]["package"], suite))
399 oldfiles = q.dictresult()
400 if len(oldfiles) >= 1:
401 files[file]["oldfiles"][suite] = oldfiles[0]
403 # Validate the component
404 component = files[file]["component"];
405 component_id = db_access.get_component_id(component);
406 if component_id == -1:
407 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
410 # Validate the priority
411 if string.find(files[file]["priority"],'/') != -1:
412 reject_message = reject_message + "Rejected: file '%s' has invalid priority '%s' [contains '/'].\n" % (file, files[file]["priority"]);
414 # Check the md5sum & size against existing files (if any)
415 location = Cnf["Dir::PoolDir"];
416 files[file]["location id"] = db_access.get_location_id (location, component, archive);
418 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
419 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
421 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
423 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
424 files[file]["files id"] = files_id
426 # Check for packages that have moved from one component to another
427 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
428 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
431 if string.find(reject_message, "Rejected:") != -1:
436 ###############################################################################
439 global dsc, dsc_files, reject_message, reprocess, orig_tar_id, orig_tar_location, legacy_source_untouchable;
441 for file in files.keys():
442 if files[file]["type"] == "dsc":
444 dsc = utils.parse_changes(file, 1)
445 except utils.cant_open_exc:
446 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (file)
448 except utils.changes_parse_error_exc, line:
449 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (file, line)
451 except utils.invalid_dsc_format_exc, line:
452 reject_message = reject_message + "Rejected: syntax error in .dsc file '%s', line %s.\n" % (file, line)
455 dsc_files = utils.build_file_list(dsc, 1)
456 except utils.no_files_exc:
457 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
459 except utils.changes_parse_error_exc, line:
460 reject_message = "Rejected: error parsing .dsc file '%s', can't grok: %s.\n" % (file, line);
463 # Try and find all files mentioned in the .dsc. This has
464 # to work harder to cope with the multiple possible
465 # locations of an .orig.tar.gz.
466 for dsc_file in dsc_files.keys():
467 if files.has_key(dsc_file):
468 actual_md5 = files[dsc_file]["md5sum"];
469 actual_size = int(files[dsc_file]["size"]);
470 found = "%s in incoming" % (dsc_file)
471 # Check the file does not already exist in the archive
472 if not changes.has_key("stable upload"):
473 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));
475 # "It has not broken them. It has fixed a
476 # brokenness. Your crappy hack exploited a
477 # bug in the old dinstall.
479 # "(Come on! I thought it was always obvious
480 # that one just doesn't release different
481 # files with the same name and version.)"
482 # -- ajk@ on d-devel@l.d.o
484 if q.getresult() != []:
485 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
486 elif dsc_file[-12:] == ".orig.tar.gz":
488 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));
492 # Unfortunately, we make get more than one match
493 # here if, for example, the package was in potato
494 # but had a -sa upload in woody. So we need to a)
495 # choose the right one and b) mark all wrong ones
496 # as excluded from the source poolification (to
497 # avoid file overwrites).
499 x = ql[0]; # default to something sane in case we don't match any or have only one
503 old_file = i[0] + i[1];
504 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
505 actual_size = os.stat(old_file)[stat.ST_SIZE];
506 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
509 legacy_source_untouchable[i[3]] = "";
511 old_file = x[0] + x[1];
512 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
513 actual_size = os.stat(old_file)[stat.ST_SIZE];
516 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
519 if suite_type == "legacy" or suite_type == "legacy-mixed":
520 orig_tar_location = "legacy";
522 orig_tar_location = x[4];
524 # Not there? Check in Incoming...
525 # [See comment above process_it() for explanation
526 # of why this is necessary...]
527 if os.access(dsc_file, os.R_OK) != 0:
528 files[dsc_file] = {};
529 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
530 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
531 files[dsc_file]["section"] = files[file]["section"];
532 files[dsc_file]["priority"] = files[file]["priority"];
533 files[dsc_file]["component"] = files[file]["component"];
534 files[dsc_file]["type"] = "orig.tar.gz";
538 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);
541 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming.\n" % (file, dsc_file);
543 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
544 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
545 if actual_size != int(dsc_files[dsc_file]["size"]):
546 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
548 if string.find(reject_message, "Rejected:") != -1:
553 ###############################################################################
555 # Some cunning stunt broke dpkg-source in dpkg 1.8{,.1}; detect the
556 # resulting bad source packages and reject them.
558 # Even more amusingly the fix in 1.8.1.1 didn't actually fix the
559 # problem just changed the symptoms.
562 global dsc, dsc_files, reject_message, reprocess;
564 for filename in files.keys():
565 if files[filename]["type"] == "diff.gz":
566 file = gzip.GzipFile(filename, 'r');
567 for line in file.readlines():
568 if re_bad_diff.search(line):
569 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";
572 if string.find(reject_message, "Rejected:") != -1:
577 ###############################################################################
579 def check_md5sums ():
580 global reject_message;
582 for file in files.keys():
584 file_handle = utils.open_file(file,"r");
585 except utils.cant_open_exc:
588 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
589 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
591 def check_override ():
594 # Only check section & priority on sourceful uploads
595 if not changes["architecture"].has_key("source"):
599 for file in files.keys():
600 if not files[file].has_key("new") and files[file]["type"] == "deb":
601 section = files[file]["section"];
602 override_section = files[file]["override section"];
603 if section != override_section and section != "-":
604 # Ignore this; it's a common mistake and not worth whining about
605 if string.lower(section) == "non-us/main" and string.lower(override_section) == "non-us":
607 summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
608 priority = files[file]["priority"];
609 override_priority = files[file]["override priority"];
610 if priority != override_priority and priority != "-":
611 summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
616 Subst["__SUMMARY__"] = summary;
617 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.override-disparity","r").read());
618 utils.send_mail (mail_message, "")
620 #####################################################################################################################
622 # Set up the per-package template substitution mappings
624 def update_subst (changes_filename):
627 if changes.has_key("architecture"):
628 Subst["__ARCHITECTURE__"] = string.join(changes["architecture"].keys(), ' ' );
630 Subst["__ARCHITECTURE__"] = "Unknown";
631 Subst["__CHANGES_FILENAME__"] = os.path.basename(changes_filename);
632 Subst["__FILE_CONTENTS__"] = changes.get("filecontents", "");
634 # For source uploads the Changed-By field wins; otherwise Maintainer wins.
635 if changes["architecture"].has_key("source") and changes["changedby822"] != "" and (changes["changedby822"] != changes["maintainer822"]):
636 Subst["__MAINTAINER_FROM__"] = changes["changedby822"];
637 Subst["__MAINTAINER_TO__"] = changes["changedby822"] + ", " + changes["maintainer822"];
638 Subst["__MAINTAINER__"] = changes.get("changed-by", "Unknown");
640 Subst["__MAINTAINER_FROM__"] = changes["maintainer822"];
641 Subst["__MAINTAINER_TO__"] = changes["maintainer822"];
642 Subst["__MAINTAINER__"] = changes.get("maintainer", "Unknown");
644 Subst["__REJECT_MESSAGE__"] = reject_message;
645 Subst["__SOURCE__"] = changes.get("source", "Unknown");
646 Subst["__VERSION__"] = changes.get("version", "Unknown");
648 #####################################################################################################################
650 def action (changes_filename):
651 byhand = confirm = suites = summary = new = "";
653 # changes["distribution"] may not exist in corner cases
654 # (e.g. unreadable changes files)
655 if not changes.has_key("distribution"):
656 changes["distribution"] = {};
658 for suite in changes["distribution"].keys():
659 if Cnf.has_key("Suite::%s::Confirm"):
660 confirm = confirm + suite + ", "
661 suites = suites + suite + ", "
662 confirm = confirm[:-2]
665 for file in files.keys():
666 if files[file].has_key("byhand"):
668 summary = summary + file + " byhand\n"
669 elif files[file].has_key("new"):
671 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
672 if files[file].has_key("othercomponents"):
673 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
674 if files[file]["type"] == "deb":
675 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
677 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
678 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
679 summary = summary + file + "\n to " + destination + "\n"
681 short_summary = summary;
683 # This is for direport's benefit...
684 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
686 if confirm or byhand or new:
687 summary = summary + "Changes: " + f;
689 summary = summary + announce (short_summary, 0)
691 (prompt, answer) = ("", "XXX")
692 if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
695 if string.find(reject_message, "Rejected") != -1:
697 modified_time = time.time()-os.path.getmtime(changes_filename);
698 except: # i.e. ignore errors like 'file does not exist';
700 if modified_time < 86400:
701 print "SKIP (too new)\n" + reject_message,;
702 prompt = "[S]kip, Manual reject, Quit ?";
704 print "REJECT\n" + reject_message,;
705 prompt = "[R]eject, Manual reject, Skip, Quit ?";
706 if Cnf["Dinstall::Options::Automatic"]:
709 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
710 prompt = "[S]kip, New ack, Manual reject, Quit ?";
711 if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
714 print "BYHAND\n" + reject_message + summary,;
715 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
717 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
718 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
720 print "INSTALL\n" + reject_message + summary,;
721 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
722 if Cnf["Dinstall::Options::Automatic"]:
725 while string.find(prompt, answer) == -1:
727 answer = utils.our_raw_input()
728 m = re_default_answer.match(prompt)
731 answer = string.upper(answer[:1])
734 reject (changes_filename, "");
736 manual_reject (changes_filename);
738 install (changes_filename, summary, short_summary);
740 acknowledge_new (changes_filename, summary);
744 #####################################################################################################################
746 def install (changes_filename, summary, short_summary):
747 global install_count, install_bytes, Subst;
749 # Stable uploads are a special case
750 if changes.has_key("stable upload"):
751 stable_install (changes_filename, summary, short_summary);
756 archive = utils.where_am_i();
758 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
759 projectB.query("BEGIN WORK");
761 # Add the .dsc file to the DB
762 for file in files.keys():
763 if files[file]["type"] == "dsc":
764 package = dsc["source"]
765 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
766 maintainer = dsc["maintainer"]
767 maintainer = string.replace(maintainer, "'", "\\'")
768 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
769 filename = files[file]["pool name"] + file;
770 dsc_location_id = files[file]["location id"];
771 if not files[file]["files id"]:
772 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
773 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
774 % (package, version, maintainer_id, files[file]["files id"]))
776 for suite in changes["distribution"].keys():
777 suite_id = db_access.get_suite_id(suite);
778 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
780 # Add the source files to the DB (files and dsc_files)
781 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
782 for dsc_file in dsc_files.keys():
783 filename = files[file]["pool name"] + dsc_file;
784 # If the .orig.tar.gz is already in the pool, it's
785 # files id is stored in dsc_files by check_dsc().
786 files_id = dsc_files[dsc_file].get("files id", None);
788 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
789 # FIXME: needs to check for -1/-2 and or handle exception
791 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
792 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
794 # Add the .deb files to the DB
795 for file in files.keys():
796 if files[file]["type"] == "deb":
797 package = files[file]["package"]
798 version = files[file]["version"]
799 maintainer = files[file]["maintainer"]
800 maintainer = string.replace(maintainer, "'", "\\'")
801 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
802 architecture = files[file]["architecture"]
803 architecture_id = db_access.get_architecture_id (architecture);
804 type = files[file]["dbtype"];
805 dsc_component = files[file]["component"]
806 source = files[file]["source"]
808 if string.find(source, "(") != -1:
809 m = utils.re_extract_src_version.match(source)
811 source_version = m.group(2)
812 if not source_version:
813 source_version = version
814 filename = files[file]["pool name"] + file;
815 if not files[file]["files id"]:
816 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
817 source_id = db_access.get_source_id (source, source_version);
819 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
820 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
822 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
823 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
824 for suite in changes["distribution"].keys():
825 suite_id = db_access.get_suite_id(suite);
826 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
828 # If the .orig.tar.gz is in a legacy directory we need to poolify
829 # it, so that apt-get source (and anything else that goes by the
830 # "Directory:" field in the Sources.gz file) works.
831 if orig_tar_id != None and orig_tar_location == "legacy":
832 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));
835 # Is this an old upload superseded by a newer -sa upload? (See check_dsc() for details)
836 if legacy_source_untouchable.has_key(qid["files_id"]):
838 # First move the files to the new location
839 legacy_filename = qid["path"]+qid["filename"];
840 pool_location = utils.poolify (changes["source"], files[file]["component"]);
841 pool_filename = pool_location + os.path.basename(qid["filename"]);
842 destination = Cnf["Dir::PoolDir"] + pool_location
843 utils.move(legacy_filename, destination);
844 # Then Update the DB's files table
845 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
847 # If this is a sourceful diff only upload that is moving non-legacy
848 # cross-component we need to copy the .orig.tar.gz into the new
849 # component too for the same reasons as above.
851 if changes["architecture"].has_key("source") and orig_tar_id != None and \
852 orig_tar_location != "legacy" and orig_tar_location != dsc_location_id:
853 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));
854 ql = q.getresult()[0];
855 old_filename = ql[0] + ql[1];
858 new_filename = utils.poolify (changes["source"], dsc_component) + os.path.basename(old_filename);
859 new_files_id = db_access.get_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
860 if new_files_id == None:
861 utils.copy(old_filename, Cnf["Dir::PoolDir"] + new_filename);
862 new_files_id = db_access.set_files_id(new_filename, file_size, file_md5sum, dsc_location_id);
863 projectB.query("UPDATE dsc_files SET file = %s WHERE source = %s AND file = %s" % (new_files_id, source_id, orig_tar_id));
865 # Install the files into the pool
866 for file in files.keys():
867 if files[file].has_key("byhand"):
869 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
870 destdir = os.path.dirname(destination)
871 utils.move (file, destination)
872 install_bytes = install_bytes + float(files[file]["size"])
874 # Copy the .changes file across for suite which need it.
875 for suite in changes["distribution"].keys():
876 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
877 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
879 projectB.query("COMMIT WORK");
882 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
884 sys.stderr.write("W: couldn't move changes file '%s' to DONE directory [Got %s].\n" % (os.path.basename(changes_filename), sys.exc_type));
886 install_count = install_count + 1;
888 if not Cnf["Dinstall::Options::No-Mail"]:
889 Subst["__SUITE__"] = "";
890 Subst["__SUMMARY__"] = summary;
891 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.installed","r").read());
892 utils.send_mail (mail_message, "")
893 announce (short_summary, 1)
896 #####################################################################################################################
898 def stable_install (changes_filename, summary, short_summary):
899 global install_count, install_bytes, Subst;
901 print "Installing to stable."
903 archive = utils.where_am_i();
905 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
906 projectB.query("BEGIN WORK");
908 # Add the .dsc file to the DB
909 for file in files.keys():
910 if files[file]["type"] == "dsc":
911 package = dsc["source"]
912 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
913 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
916 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version));
918 source_id = ql[0][0];
919 suite_id = db_access.get_suite_id('proposed-updates');
920 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
921 suite_id = db_access.get_suite_id('stable');
922 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
924 # Add the .deb files to the DB
925 for file in files.keys():
926 if files[file]["type"] == "deb":
927 package = files[file]["package"]
928 version = files[file]["version"]
929 architecture = files[file]["architecture"]
930 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))
933 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture));
935 binary_id = ql[0][0];
936 suite_id = db_access.get_suite_id('proposed-updates');
937 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
938 suite_id = db_access.get_suite_id('stable');
939 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
941 projectB.query("COMMIT WORK");
944 utils.move (changes_filename, Cnf["Dir::Morgue"] + '/katie/' + os.path.basename(changes_filename));
946 # Update the Stable ChangeLog file
948 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
949 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
950 if os.path.exists(new_changelog_filename):
951 os.unlink (new_changelog_filename);
953 new_changelog = utils.open_file(new_changelog_filename, 'w');
954 for file in files.keys():
955 if files[file]["type"] == "deb":
956 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
957 elif utils.re_issource.match(file) != None:
958 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
960 new_changelog.write("%s\n" % (file));
961 chop_changes = re_fdnic.sub("\n", changes["changes"]);
962 new_changelog.write(chop_changes + '\n\n');
963 if os.access(changelog_filename, os.R_OK) != 0:
964 changelog = utils.open_file(changelog_filename, 'r');
965 new_changelog.write(changelog.read());
966 new_changelog.close();
967 if os.access(changelog_filename, os.R_OK) != 0:
968 os.unlink(changelog_filename);
969 utils.move(new_changelog_filename, changelog_filename);
971 install_count = install_count + 1;
973 if not Cnf["Dinstall::Options::No-Mail"]:
974 Subst["__SUITE__"] = " into stable";
975 Subst["__SUMMARY__"] = summary;
976 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.installed","r").read());
977 utils.send_mail (mail_message, "")
978 announce (short_summary, 1)
980 #####################################################################################################################
982 def reject (changes_filename, manual_reject_mail_filename):
987 base_changes_filename = os.path.basename(changes_filename);
988 reason_filename = re_changes.sub("reason", base_changes_filename);
989 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
991 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
993 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
995 sys.stderr.write("W: couldn't reject changes file '%s' [Got %s].\n" % (base_changes_filename, sys.exc_type));
997 for file in files.keys():
998 if os.path.exists(file):
1000 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
1002 sys.stderr.write("W: couldn't reject file '%s' [Got %s].\n" % (file, sys.exc_type));
1005 # If this is not a manual rejection generate the .reason file and rejection mail message
1006 if manual_reject_mail_filename == "":
1007 if os.path.exists(reject_filename):
1008 os.unlink(reject_filename);
1009 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1010 os.write(fd, reject_message);
1012 Subst["__MANUAL_REJECT_MESSAGE__"] = "";
1013 Subst["__CC__"] = "X-Katie-Rejection: automatic (moo)";
1014 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1015 else: # Have a manual rejection file to use
1016 reject_mail_message = ""; # avoid <undef>'s
1018 # Send the rejection mail if appropriate
1019 if not Cnf["Dinstall::Options::No-Mail"]:
1020 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
1022 ##################################################################
1024 def manual_reject (changes_filename):
1027 # Build up the rejection email
1028 user_email_address = utils.whoami() + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
1029 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
1031 Subst["__MANUAL_REJECT_MESSAGE__"] = manual_reject_message;
1032 Subst["__CC__"] = "Cc: " + Cnf["Dinstall::MyEmailAddress"];
1033 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
1035 # Write the rejection email out as the <foo>.reason file
1036 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
1037 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
1038 if os.path.exists(reject_filename):
1039 os.unlink(reject_filename);
1040 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1041 os.write(fd, reject_mail_message);
1044 # If we weren't given one, spawn an editor so the user can add one in
1045 if manual_reject_message == "":
1046 result = os.system("vi +6 %s" % (reject_filename))
1048 sys.stderr.write ("vi invocation failed for `%s'!\n" % (reject_filename))
1051 # Then process it as if it were an automatic rejection
1052 reject (changes_filename, reject_filename)
1054 #####################################################################################################################
1056 def acknowledge_new (changes_filename, summary):
1057 global new_ack_new, Subst;
1059 changes_filename = os.path.basename(changes_filename);
1061 new_ack_new[changes_filename] = 1;
1063 if new_ack_old.has_key(changes_filename):
1064 print "Ack already sent.";
1067 print "Sending new ack.";
1068 if not Cnf["Dinstall::Options::No-Mail"]:
1069 Subst["__SUMMARY__"] = summary;
1070 new_ack_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.new","r").read());
1071 utils.send_mail(new_ack_message,"");
1073 #####################################################################################################################
1075 def announce (short_summary, action):
1078 # Only do announcements for source uploads with a recent dpkg-dev installed
1079 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
1084 Subst["__SHORT_SUMMARY__"] = short_summary;
1086 for dist in changes["distribution"].keys():
1087 list = Cnf.Find("Suite::%s::Announce" % (dist))
1088 if list == "" or lists_done.has_key(list):
1090 lists_done[list] = 1
1091 summary = summary + "Announcing to %s\n" % (list)
1094 Subst["__ANNOUNCE_LIST_ADDRESS__"] = list;
1095 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.announce","r").read());
1096 utils.send_mail (mail_message, "")
1098 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
1099 bugs = changes["closes"].keys()
1101 # changes["changedbyname"] == dsc_name is probably never true, but better
1103 if dsc_name == changes["maintainername"] and (changes["changedbyname"] == "" or changes["changedbyname"] == dsc_name):
1104 summary = summary + "Closing bugs: "
1106 summary = summary + "%s " % (bug)
1108 Subst["__BUG_NUMBER__"] = bug;
1109 if changes["distribution"].has_key("stable"):
1110 Subst["__STABLE_WARNING__"] = """
1111 Note that this package is not part of the released stable Debian
1112 distribution. It may have dependencies on other unreleased software,
1113 or other instabilities. Please take care if you wish to install it.
1114 The update will eventually make its way into the next released Debian
1117 Subst["__STABLE_WARNING__"] = "";
1118 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-close","r").read());
1119 utils.send_mail (mail_message, "")
1121 summary = summary + "Setting bugs to severity fixed: "
1122 control_message = ""
1124 summary = summary + "%s " % (bug)
1125 control_message = control_message + "tag %s + fixed\n" % (bug)
1126 if action and control_message != "":
1127 Subst["__CONTROL_MESSAGE__"] = control_message;
1128 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-nmu-fixed","r").read());
1129 utils.send_mail (mail_message, "")
1130 summary = summary + "\n"
1134 ###############################################################################
1136 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1137 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1138 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1139 # processed it during it's checks of -2. If -1 has been deleted or
1140 # otherwise not checked by da-install, the .orig.tar.gz will not have
1141 # been checked at all. To get round this, we force the .orig.tar.gz
1142 # into the .changes structure and reprocess the .changes file.
1144 def process_it (changes_file):
1145 global reprocess, orig_tar_id, orig_tar_location, changes, dsc, dsc_files, files, reject_message;
1147 # Reset some globals
1154 orig_tar_location = "";
1155 legacy_source_untouchable = {};
1156 reject_message = "";
1158 # Absolutize the filename to avoid the requirement of being in the
1159 # same directory as the .changes file.
1160 changes_file = os.path.abspath(changes_file);
1162 # And since handling of installs to stable munges with the CWD;
1163 # save and restore it.
1166 check_signature (changes_file);
1167 check_changes (changes_file);
1175 update_subst(changes_file);
1176 action(changes_file);
1181 ###############################################################################
1184 global Cnf, projectB, install_bytes, new_ack_old, Subst
1188 Cnf = apt_pkg.newConfiguration();
1189 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1191 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1192 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1193 ('h',"help","Dinstall::Options::Help"),
1194 ('k',"ack-new","Dinstall::Options::Ack-New"),
1195 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1196 ('n',"no-action","Dinstall::Options::No-Action"),
1197 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1198 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1199 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1200 ('v',"version","Dinstall::Options::Version")];
1202 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1204 if Cnf["Dinstall::Options::Help"]:
1207 if Cnf["Dinstall::Options::Version"]:
1208 print "katie version 0.0000000000";
1211 postgresql_user = None; # Default == Connect as user running program.
1213 # -n/--dry-run invalidates some other options which would involve things happening
1214 if Cnf["Dinstall::Options::No-Action"]:
1215 Cnf["Dinstall::Options::Automatic"] = ""
1216 Cnf["Dinstall::Options::Ack-New"] = ""
1217 postgresql_user = Cnf["DB::ROUser"];
1219 projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1221 db_access.init(Cnf, projectB);
1223 # Check that we aren't going to clash with the daily cron job
1225 if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1226 sys.stderr.write("Archive maintenance in progress. Try again later.\n");
1229 # Obtain lock if not in no-action mode
1231 if not Cnf["Dinstall::Options::No-Action"]:
1232 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1233 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1235 # Read in the list of already-acknowledged NEW packages
1236 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1238 for line in new_ack_list.readlines():
1239 new_ack_old[line[:-1]] = 1;
1240 new_ack_list.close();
1242 # Initialize the substitution template mapping global
1244 Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"];
1245 Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"];
1246 bcc = "X-Katie: $Revision: 1.41 $"
1247 if Cnf.has_key("Dinstall::Bcc"):
1248 Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"]);
1250 Subst["__BCC__"] = bcc;
1251 Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"];
1252 Subst["__KATIE_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"];
1254 # Process the changes files
1255 for changes_file in changes_files:
1256 print "\n" + changes_file;
1257 process_it (changes_file);
1261 if install_count > 1:
1263 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1265 # Write out the list of already-acknowledged NEW packages
1266 if Cnf["Dinstall::Options::Ack-New"]:
1267 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1268 for i in new_ack_new.keys():
1269 new_ack_list.write(i+'\n')
1270 new_ack_list.close()
1273 if __name__ == '__main__':