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