3 # Installs Debian packaes
4 # Copyright (C) 2000, 2001 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.33 2001-03-21 01:05:07 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 legacy_source_untouchable = {};
66 #########################################################################################
68 def usage (exit_code):
69 print """Usage: dinstall [OPTION]... [CHANGES]...
70 -a, --automatic automatic run
71 -D, --debug=VALUE turn on debugging
72 -h, --help show this help and exit.
73 -k, --ack-new acknowledge new packages !! for cron.daily only !!
74 -m, --manual-reject=MSG manual reject with `msg'
75 -n, --no-action don't do anything
76 -p, --no-lock don't check lockfile !! for cron.daily only !!
77 -u, --distribution=DIST override distribution to `dist'
78 -v, --version display the version number and exit"""
81 #########################################################################################
83 def check_signature (filename):
86 (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))
88 reject_message = "Rejected: GPG signature check failed on `%s'.\n%s\n" % (os.path.basename(filename), output)
92 #####################################################################################################################
94 # See if a given package is in the override table
96 def in_override_p (package, component, suite, binary_type, file):
99 if binary_type == "": # must be source
104 # Override suite name; used for example with proposed-updates
105 if Cnf.Find("Suite::%s::OverrideSuite" % (suite)) != "":
106 suite = Cnf["Suite::%s::OverrideSuite" % (suite)];
108 # Avoid <undef> on unknown distributions
109 suite_id = db_access.get_suite_id(suite);
112 component_id = db_access.get_component_id(component);
113 type_id = db_access.get_override_type_id(type);
115 # FIXME: nasty non-US speficic hack
116 if string.lower(component[:7]) == "non-us/":
117 component = component[7:];
119 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"
120 % (package, suite_id, component_id, type_id));
121 result = q.getresult();
122 # If checking for a source package fall back on the binary override type
123 if type == "dsc" and not result:
124 type_id = db_access.get_override_type_id("deb");
125 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"
126 % (package, suite_id, component_id, type_id));
127 result = q.getresult();
129 # Remember the section and priority so we can check them later if appropriate
131 files[file]["override section"] = result[0][0];
132 files[file]["override priority"] = result[0][1];
136 #####################################################################################################################
138 def check_changes(filename):
139 global reject_message, changes, files
141 # Default in case we bail out
142 changes["maintainer822"] = Cnf["Dinstall::MyEmailAddress"];
144 # Parse the .changes field into a dictionary
146 changes = utils.parse_changes(filename, 0)
147 except utils.cant_open_exc:
148 reject_message = "Rejected: can't read changes file '%s'.\n" % (filename)
150 except utils.changes_parse_error_exc, line:
151 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
154 # Parse the Files field from the .changes into another dictionary
156 files = utils.build_file_list(changes, "");
157 except utils.changes_parse_error_exc, line:
158 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line);
160 # Check for mandatory fields
161 for i in ("source", "binary", "architecture", "version", "distribution","maintainer", "files"):
162 if not changes.has_key(i):
163 reject_message = "Rejected: Missing field `%s' in changes file.\n" % (i)
164 return 0 # Avoid <undef> errors during later tests
166 # Override the Distribution: field if appropriate
167 if Cnf["Dinstall::Options::Override-Distribution"] != "":
168 reject_message = reject_message + "Warning: Distribution was overriden from %s to %s.\n" % (changes["distribution"], Cnf["Dinstall::Options::Override-Distribution"])
169 changes["distribution"] = Cnf["Dinstall::Options::Override-Distribution"]
171 # Split multi-value fields into a lower-level dictionary
172 for i in ("architecture", "distribution", "binary", "closes"):
173 o = changes.get(i, "")
177 for j in string.split(o):
180 # Fix the Maintainer: field to be RFC822 compatible
181 (changes["maintainer822"], changes["maintainername"], changes["maintaineremail"]) = utils.fix_maintainer (changes["maintainer"])
183 # Fix the Changed-By: field to be RFC822 compatible; if it exists.
184 (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]) = utils.fix_maintainer(changes.get("changed-by",""));
186 # For source uploads the Changed-By field wins; otherwise Maintainer wins.
187 if changes["architecture"].has_key("source"):
188 changes["uploader822"] = "To: %s\nCc: %s" % (changes["changedby822"], changes["maintainer822"]);
189 # changes["uploadername"], changes["uploaderemail"]) = (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]);
191 # Ensure all the values in Closes: are numbers
192 if changes.has_key("closes"):
193 for i in changes["closes"].keys():
194 if re_isanum.match (i) == None:
195 reject_message = reject_message + "Rejected: `%s' from Closes field isn't a number.\n" % (i)
197 # Map frozen to unstable if frozen doesn't exist
198 if changes["distribution"].has_key("frozen") and not Cnf.has_key("Suite::Frozen"):
199 del changes["distribution"]["frozen"]
200 changes["distribution"]["unstable"] = 1;
201 reject_message = reject_message + "Mapping frozen to unstable.\n"
203 # Map testing to unstable
204 if changes["distribution"].has_key("testing"):
205 del changes["distribution"]["testing"]
206 changes["distribution"]["unstable"] = 1;
207 reject_message = reject_message + "Mapping testing to unstable.\n"
209 # Ensure target distributions exist
210 for i in changes["distribution"].keys():
211 if not Cnf.has_key("Suite::%s" % (i)):
212 reject_message = reject_message + "Rejected: Unknown distribution `%s'.\n" % (i)
214 # Ensure there _is_ a target distribution
215 if changes["distribution"].keys() == []:
216 reject_message = reject_message + "Rejected: huh? Distribution field is empty in changes file.\n";
218 # Map unreleased arches from stable to unstable
219 if changes["distribution"].has_key("stable"):
220 for i in changes["architecture"].keys():
221 if not Cnf.has_key("Suite::Stable::Architectures::%s" % (i)):
222 reject_message = reject_message + "Mapping stable to unstable for unreleased arch `%s'.\n" % (i)
223 del changes["distribution"]["stable"]
224 changes["distribution"]["unstable"] = 1;
226 # Map arches not being released from frozen to unstable
227 if changes["distribution"].has_key("frozen"):
228 for i in changes["architecture"].keys():
229 if not Cnf.has_key("Suite::Frozen::Architectures::%s" % (i)):
230 reject_message = reject_message + "Mapping frozen to unstable for non-releasing arch `%s'.\n" % (i)
231 del changes["distribution"]["frozen"]
232 changes["distribution"]["unstable"] = 1;
234 # Handle uploads to stable
235 if changes["distribution"].has_key("stable"):
236 # If running from within proposed-updates; assume an install to stable
237 if string.find(os.getcwd(), 'proposed-updates') != -1:
238 # FIXME: should probably remove anything that != stable
239 for i in ("frozen", "unstable"):
240 if changes["distribution"].has_key(i):
241 reject_message = reject_message + "Removing %s from distribution list.\n"
242 del changes["distribution"][i]
243 changes["stable upload"] = 1;
244 # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool
245 file = files.keys()[0];
246 if os.access(file, os.R_OK) == 0:
247 pool_dir = Cnf["Dir::PoolDir"] + '/' + utils.poolify(changes["source"], files[file]["component"]);
249 # Otherwise (normal case) map stable to updates
251 reject_message = reject_message + "Mapping stable to updates.\n";
252 del changes["distribution"]["stable"];
253 changes["distribution"]["proposed-updates"] = 1;
255 # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
256 changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
257 changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
259 if string.find(reject_message, "Rejected:") != -1:
265 global reject_message
267 archive = utils.where_am_i();
269 for file in files.keys():
270 # Check the file is readable
271 if os.access(file,os.R_OK) == 0:
272 reject_message = reject_message + "Rejected: Can't read `%s'.\n" % (file)
273 files[file]["type"] = "unreadable";
275 # If it's byhand skip remaining checks
276 if files[file]["section"] == "byhand":
277 files[file]["byhand"] = 1;
278 files[file]["type"] = "byhand";
279 # Checks for a binary package...
280 elif utils.re_isadeb.match(file) != None:
281 files[file]["type"] = "deb";
283 # Extract package information using dpkg-deb
285 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
287 reject_message = reject_message + "Rejected: %s: debExtractControl() raised %s.\n" % (file, sys.exc_type);
288 # Can't continue, none of the checks on control would work.
291 # Check for mandatory fields
292 if control.Find("Package") == None:
293 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
294 if control.Find("Architecture") == None:
295 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
296 if control.Find("Version") == None:
297 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
299 # Ensure the package name matches the one give in the .changes
300 if not changes["binary"].has_key(control.Find("Package", "")):
301 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
303 # Validate the architecture
304 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
305 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
307 # Check the architecture matches the one given in the .changes
308 if not changes["architecture"].has_key(control.Find("Architecture", "")):
309 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
310 # Check the section & priority match those given in the .changes (non-fatal)
311 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
312 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"])
313 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
314 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"])
316 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
318 files[file]["package"] = control.Find("Package");
319 files[file]["architecture"] = control.Find("Architecture");
320 files[file]["version"] = control.Find("Version");
321 files[file]["maintainer"] = control.Find("Maintainer", "");
322 if file[-5:] == ".udeb":
323 files[file]["dbtype"] = "udeb";
324 elif file[-4:] == ".deb":
325 files[file]["dbtype"] = "deb";
327 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
328 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
329 files[file]["source"] = control.Find("Source", "");
330 if files[file]["source"] == "":
331 files[file]["source"] = files[file]["package"];
332 # Checks for a source package...
334 m = utils.re_issource.match(file)
336 files[file]["package"] = m.group(1)
337 files[file]["version"] = m.group(2)
338 files[file]["type"] = m.group(3)
340 # Ensure the source package name matches the Source filed in the .changes
341 if changes["source"] != files[file]["package"]:
342 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
344 # Ensure the source version matches the version in the .changes file
345 if files[file]["type"] == "orig.tar.gz":
346 changes_version = changes["chopversion2"]
348 changes_version = changes["chopversion"]
349 if changes_version != files[file]["version"]:
350 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
352 # Ensure the .changes lists source in the Architecture field
353 if not changes["architecture"].has_key("source"):
354 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
356 # Check the signature of a .dsc file
357 if files[file]["type"] == "dsc":
358 check_signature(file)
360 files[file]["fullname"] = file
362 # Not a binary or source package? Assume byhand...
364 files[file]["byhand"] = 1;
365 files[file]["type"] = "byhand";
367 files[file]["oldfiles"] = {}
368 for suite in changes["distribution"].keys():
370 if files[file].has_key("byhand"):
373 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
374 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
377 # See if the package is NEW
378 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
379 files[file]["new"] = 1
381 # Find any old binary packages
382 if files[file]["type"] == "deb":
383 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"
384 % (files[file]["package"], suite, files[file]["architecture"]))
385 oldfiles = q.dictresult()
386 for oldfile in oldfiles:
387 files[file]["oldfiles"][suite] = oldfile
388 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
389 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
390 reject_message = reject_message + "Rejected: %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
391 # Check for existing copies of the file
392 if not changes.has_key("stable upload"):
393 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"]))
394 if q.getresult() != []:
395 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
397 # Find any old .dsc files
398 elif files[file]["type"] == "dsc":
399 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"
400 % (files[file]["package"], suite))
401 oldfiles = q.dictresult()
402 if len(oldfiles) >= 1:
403 files[file]["oldfiles"][suite] = oldfiles[0]
405 # Validate the component
406 component = files[file]["component"];
407 component_id = db_access.get_component_id(component);
408 if component_id == -1:
409 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
412 # Validate the priority
413 if string.find(files[file]["priority"],'/') != -1:
414 reject_message = reject_message + "Rejected: file '%s' has invalid priority '%s' [contains '/'].\n" % (file, files[file]["priority"]);
416 # Check the md5sum & size against existing files (if any)
417 location = Cnf["Dir::PoolDir"];
418 files[file]["location id"] = db_access.get_location_id (location, component, archive);
420 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
421 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
423 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
425 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
426 files[file]["files id"] = files_id
428 # Check for packages that have moved from one component to another
429 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
430 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
433 if string.find(reject_message, "Rejected:") != -1:
438 ###############################################################################
441 global dsc, dsc_files, reject_message, reprocess, orig_tar_id, legacy_source_untouchable;
443 for file in files.keys():
444 if files[file]["type"] == "dsc":
446 dsc = utils.parse_changes(file, 1)
447 except utils.cant_open_exc:
448 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (file)
450 except utils.changes_parse_error_exc, line:
451 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (file, line)
453 except utils.invalid_dsc_format_exc, line:
454 reject_message = reject_message + "Rejected: syntax error in .dsc file '%s', line %s.\n" % (file, line)
457 dsc_files = utils.build_file_list(dsc, 1)
458 except utils.no_files_exc:
459 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
461 except utils.changes_parse_error_exc, line:
462 reject_message = "Rejected: error parsing .dsc file '%s', can't grok: %s.\n" % (file, line);
465 # Try and find all files mentioned in the .dsc. This has
466 # to work harder to cope with the multiple possible
467 # locations of an .orig.tar.gz.
468 for dsc_file in dsc_files.keys():
469 if files.has_key(dsc_file):
470 actual_md5 = files[dsc_file]["md5sum"];
471 actual_size = int(files[dsc_file]["size"]);
472 found = "%s in incoming" % (dsc_file)
473 # Check the file does not already exist in the archive
474 if not changes.has_key("stable upload"):
475 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));
477 # "It has not broken them. It has fixed a
478 # brokenness. Your crappy hack exploited a
479 # bug in the old dinstall.
481 # "(Come on! I thought it was always obvious
482 # that one just doesn't release different
483 # files with the same name and version.)"
484 # -- ajk@ on d-devel@l.d.o
486 if q.getresult() != []:
487 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
488 elif dsc_file[-12:] == ".orig.tar.gz":
490 q = projectB.query("SELECT l.path, f.filename, l.type, f.id FROM files f, location l WHERE (f.filename ~ '/%s$' OR f.filename = '%s') AND l.id = f.location" % (utils.regex_safe(dsc_file), dsc_file));
494 # Unfortunately, we make get more than one match
495 # here if, for example, the package was in potato
496 # but had a -sa upload in woody. So we need to a)
497 # choose the right one and b) mark all wrong ones
498 # as excluded from the source poolification (to
499 # avoid file overwrites).
501 x = ql[0]; # default to something sane in case we don't match any or have only one
505 old_file = i[0] + i[1];
506 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
507 actual_size = os.stat(old_file)[stat.ST_SIZE];
508 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
511 legacy_source_untouchable[i[3]] = "";
513 old_file = x[0] + x[1];
514 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
515 actual_size = os.stat(old_file)[stat.ST_SIZE];
518 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
520 if suite_type == "legacy" or suite_type == "legacy-mixed":
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, orig_tar_id;
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 Subst["__ARCHITECTURE__"] = string.join(changes["architecture"].keys(), ' ' );
627 Subst["__CHANGES_FILENAME__"] = os.path.basename(changes_filename);
628 Subst["__FILE_CONTENTS__"] = changes["filecontents"];
629 Subst["__MAINTAINER_ADDRESS__"] = changes["maintainer822"];
630 Subst["__MAINTAINER__"] = changes["maintainer"];
631 Subst["__REJECT_MESSAGE__"] = reject_message;
632 Subst["__SOURCE__"] = changes["source"];
633 Subst["__VERSION__"] = changes["version"];
635 #####################################################################################################################
637 def action (changes_filename):
638 byhand = confirm = suites = summary = new = "";
640 # changes["distribution"] may not exist in corner cases
641 # (e.g. unreadable changes files)
642 if not changes.has_key("distribution"):
643 changes["distribution"] = {};
645 for suite in changes["distribution"].keys():
646 if Cnf.has_key("Suite::%s::Confirm"):
647 confirm = confirm + suite + ", "
648 suites = suites + suite + ", "
649 confirm = confirm[:-2]
652 for file in files.keys():
653 if files[file].has_key("byhand"):
655 summary = summary + file + " byhand\n"
656 elif files[file].has_key("new"):
658 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
659 if files[file].has_key("othercomponents"):
660 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
661 if files[file]["type"] == "deb":
662 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
664 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
665 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
666 summary = summary + file + "\n to " + destination + "\n"
668 short_summary = summary;
670 # This is for direport's benefit...
671 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
673 if confirm or byhand or new:
674 summary = summary + "Changes: " + f;
676 summary = summary + announce (short_summary, 0)
678 (prompt, answer) = ("", "XXX")
679 if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
682 if string.find(reject_message, "Rejected") != -1:
683 if time.time()-os.path.getmtime(changes_filename) < 86400:
684 print "SKIP (too new)\n" + reject_message,;
685 prompt = "[S]kip, Manual reject, Quit ?";
687 print "REJECT\n" + reject_message,;
688 prompt = "[R]eject, Manual reject, Skip, Quit ?";
689 if Cnf["Dinstall::Options::Automatic"]:
692 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
693 prompt = "[S]kip, New ack, Manual reject, Quit ?";
694 if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
697 print "BYHAND\n" + reject_message + summary,;
698 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
700 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
701 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
703 print "INSTALL\n" + reject_message + summary,;
704 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
705 if Cnf["Dinstall::Options::Automatic"]:
708 while string.find(prompt, answer) == -1:
710 answer = utils.our_raw_input()
711 m = re_default_answer.match(prompt)
714 answer = string.upper(answer[:1])
717 reject (changes_filename, "");
719 manual_reject (changes_filename);
721 install (changes_filename, summary, short_summary);
723 acknowledge_new (changes_filename, summary);
727 #####################################################################################################################
729 def install (changes_filename, summary, short_summary):
730 global install_count, install_bytes, Subst;
732 # Stable uploads are a special case
733 if changes.has_key("stable upload"):
734 stable_install (changes_filename, summary, short_summary);
739 archive = utils.where_am_i();
741 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
742 projectB.query("BEGIN WORK");
744 # Add the .dsc file to the DB
745 for file in files.keys():
746 if files[file]["type"] == "dsc":
747 package = dsc["source"]
748 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
749 maintainer = dsc["maintainer"]
750 maintainer = string.replace(maintainer, "'", "\\'")
751 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
752 filename = files[file]["pool name"] + file;
753 dsc_location_id = files[file]["location id"];
754 if not files[file]["files id"]:
755 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
756 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
757 % (package, version, maintainer_id, files[file]["files id"]))
759 for suite in changes["distribution"].keys():
760 suite_id = db_access.get_suite_id(suite);
761 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
763 # Add the source files to the DB (files and dsc_files)
764 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
765 for dsc_file in dsc_files.keys():
766 filename = files[file]["pool name"] + dsc_file;
767 # If the .orig.tar.gz is already in the pool, it's
768 # files id is stored in dsc_files by check_dsc().
769 files_id = dsc_files[dsc_file].get("files id", None);
771 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
772 # FIXME: needs to check for -1/-2 and or handle exception
774 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
775 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
777 # Add the .deb files to the DB
778 for file in files.keys():
779 if files[file]["type"] == "deb":
780 package = files[file]["package"]
781 version = files[file]["version"]
782 maintainer = files[file]["maintainer"]
783 maintainer = string.replace(maintainer, "'", "\\'")
784 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
785 architecture = files[file]["architecture"]
786 architecture_id = db_access.get_architecture_id (architecture);
787 type = files[file]["dbtype"];
788 component = files[file]["component"]
789 source = files[file]["source"]
791 if string.find(source, "(") != -1:
792 m = utils.re_extract_src_version.match(source)
794 source_version = m.group(2)
795 if not source_version:
796 source_version = version
797 filename = files[file]["pool name"] + file;
798 if not files[file]["files id"]:
799 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
800 source_id = db_access.get_source_id (source, source_version);
802 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
803 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
805 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
806 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
807 for suite in changes["distribution"].keys():
808 suite_id = db_access.get_suite_id(suite);
809 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
811 # If the .orig.tar.gz is in a legacy directory we need to poolify
812 # it, so that apt-get source (and anything else that goes by the
813 # "Directory:" field in the Sources.gz file) works.
814 if orig_tar_id != None:
815 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));
818 # Is this an old upload superseded by a newer -sa upload? (See check_dsc() for details)
819 if legacy_source_untouchable.has_key(qid["files_id"]):
821 # First move the files to the new location
822 legacy_filename = qid["path"]+qid["filename"];
823 pool_location = utils.poolify (changes["source"], files[file]["component"]);
824 pool_filename = pool_location + os.path.basename(qid["filename"]);
825 destination = Cnf["Dir::PoolDir"] + pool_location
826 utils.move(legacy_filename, destination);
827 # Then Update the DB's files table
828 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
830 # Install the files into the pool
831 for file in files.keys():
832 if files[file].has_key("byhand"):
834 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
835 destdir = os.path.dirname(destination)
836 utils.move (file, destination)
837 install_bytes = install_bytes + float(files[file]["size"])
839 # Copy the .changes file across for suite which need it.
840 for suite in changes["distribution"].keys():
841 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
842 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
844 projectB.query("COMMIT WORK");
847 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
849 sys.stderr.write("W: couldn't move changes file '%s' to DONE directory [Got %s].\n" % (os.path.basename(changes_filename), sys.exc_type));
851 install_count = install_count + 1;
853 if not Cnf["Dinstall::Options::No-Mail"]:
854 Subst["__SUITE__"] = "";
855 Subst["__SUMMARY__"] = summary;
856 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.installed","r").read());
857 utils.send_mail (mail_message, "")
858 announce (short_summary, 1)
861 #####################################################################################################################
863 def stable_install (changes_filename, summary, short_summary):
864 global install_count, install_bytes, Subst;
866 print "Installing to stable."
868 archive = utils.where_am_i();
870 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
871 projectB.query("BEGIN WORK");
873 # Add the .dsc file to the DB
874 for file in files.keys():
875 if files[file]["type"] == "dsc":
876 package = dsc["source"]
877 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
878 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
881 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version));
883 source_id = ql[0][0];
884 suite_id = db_access.get_suite_id('proposed-updates');
885 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
886 suite_id = db_access.get_suite_id('stable');
887 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
889 # Add the .deb files to the DB
890 for file in files.keys():
891 if files[file]["type"] == "deb":
892 package = files[file]["package"]
893 version = files[file]["version"]
894 architecture = files[file]["architecture"]
895 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))
898 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture));
900 binary_id = ql[0][0];
901 suite_id = db_access.get_suite_id('proposed-updates');
902 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
903 suite_id = db_access.get_suite_id('stable');
904 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
906 projectB.query("COMMIT WORK");
908 utils.move (changes_filename, Cnf["Rhona::Morgue"] + os.path.basename(changes_filename));
910 # Update the Stable ChangeLog file
912 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
913 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
914 if os.path.exists(new_changelog_filename):
915 os.unlink (new_changelog_filename);
917 new_changelog = utils.open_file(new_changelog_filename, 'w');
918 for file in files.keys():
919 if files[file]["type"] == "deb":
920 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
921 elif utils.re_issource.match(file) != None:
922 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
924 new_changelog.write("%s\n" % (file));
925 chop_changes = re_fdnic.sub("\n", changes["changes"]);
926 new_changelog.write(chop_changes + '\n\n');
927 if os.access(changelog_filename, os.R_OK) != 0:
928 changelog = utils.open_file(changelog_filename, 'r');
929 new_changelog.write(changelog.read());
930 new_changelog.close();
931 if os.access(changelog_filename, os.R_OK) != 0:
932 os.unlink(changelog_filename);
933 utils.move(new_changelog_filename, changelog_filename);
935 install_count = install_count + 1;
937 if not Cnf["Dinstall::Options::No-Mail"]:
938 Subst["__SUITE__"] = "into stable";
939 Subst["__SUMMARY__"] = summary;
940 utils.send_mail (mail_message, "")
941 announce (short_summary, 1)
943 #####################################################################################################################
945 def reject (changes_filename, manual_reject_mail_filename):
950 base_changes_filename = os.path.basename(changes_filename);
951 reason_filename = re_changes.sub("reason", base_changes_filename);
952 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
954 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
956 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
958 sys.stderr.write("W: couldn't reject changes file '%s' [Got %s].\n" % (base_changes_filename, sys.exc_type));
960 for file in files.keys():
961 if os.path.exists(file):
963 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
965 sys.stderr.write("W: couldn't reject file '%s' [Got %s].\n" % (file, sys.exc_type));
968 # If this is not a manual rejection generate the .reason file and rejection mail message
969 if manual_reject_mail_filename == "":
970 if os.path.exists(reject_filename):
971 os.unlink(reject_filename);
972 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
973 os.write(fd, reject_message);
975 Subst["__MANUAL_REJECT_MESSAGE__"] = "";
976 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
977 else: # Have a manual rejection file to use
978 reject_mail_message = ""; # avoid <undef>'s
980 # Send the rejection mail if appropriate
981 if not Cnf["Dinstall::Options::No-Mail"]:
982 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
984 ##################################################################
986 def manual_reject (changes_filename):
989 # Build up the rejection email
990 user_email_address = string.replace(string.split(pwd.getpwuid(os.getuid())[4],',')[0], '.', '')
991 user_email_address = user_email_address + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
992 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
994 Subst["__MANUAL_REJECT_MESSAGE__"] = manual_reject_message;
995 reject_mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.rejected","r").read());
997 # Write the rejection email out as the <foo>.reason file
998 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
999 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
1000 if os.path.exists(reject_filename):
1001 os.unlink(reject_filename);
1002 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1003 os.write(fd, reject_mail_message);
1006 # If we weren't given one, spawn an editor so the user can add one in
1007 if manual_reject_message == "":
1008 result = os.system("vi +6 %s" % (reject_file))
1010 sys.stderr.write ("vi invocation failed for `%s'!\n" % (reject_file))
1013 # Then process it as if it were an automatic rejection
1014 reject (changes_filename, reject_filename)
1016 #####################################################################################################################
1018 def acknowledge_new (changes_filename, summary):
1019 global new_ack_new, Subst;
1021 changes_filename = os.path.basename(changes_filename);
1023 new_ack_new[changes_filename] = 1;
1025 if new_ack_old.has_key(changes_filename):
1026 print "Ack already sent.";
1029 print "Sending new ack.";
1030 if not Cnf["Dinstall::Options::No-Mail"]:
1031 Subst["__SUMMARY__"] = summary;
1032 new_ack_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.new","r").read());
1033 utils.send_mail(new_ack_message,"");
1035 #####################################################################################################################
1037 def announce (short_summary, action):
1040 # Only do announcements for source uploads with a recent dpkg-dev installed
1041 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
1046 Subst["__SHORT_SUMMARY__"] = short_summary;
1048 for dist in changes["distribution"].keys():
1049 list = Cnf.Find("Suite::%s::Announce" % (dist))
1050 if list == "" or lists_done.has_key(list):
1052 lists_done[list] = 1
1053 summary = summary + "Announcing to %s\n" % (list)
1056 Subst["__ANNOUNCE_LIST_ADDRESS__"] = list;
1057 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.announce","r").read());
1058 utils.send_mail (mail_message, "")
1060 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
1061 bugs = changes["closes"].keys()
1063 # changes["changedbyname"] == dsc_name is probably never true, but better
1065 if dsc_name == changes["maintainername"] and (changes["changedbyname"] == "" or changes["changedbyname"] == dsc_name):
1066 summary = summary + "Closing bugs: "
1068 summary = summary + "%s " % (bug)
1070 Subst["__BUG_NUMBER__"] = bug;
1071 if changes["distribution"].has_key("stable"):
1072 Subst["__STABLE_WARNING__"] = """
1073 Note that this package is not part of the released stable Debian
1074 distribution. It may have dependencies on other unreleased software,
1075 or other instabilities. Please take care if you wish to install it.
1076 The update will eventually make its way into the next released Debian
1079 Subst["__STABLE_WARNING__"] = "";
1080 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-close","r").read());
1081 utils.send_mail (mail_message, "")
1083 summary = summary + "Setting bugs to severity fixed: "
1084 control_message = ""
1086 summary = summary + "%s " % (bug)
1087 control_message = control_message + "tag %s + fixed\n" % (bug)
1088 if action and control_message != "":
1089 Subst["__CONTROL_MESSAGE__"] = control_message;
1090 mail_message = utils.TemplateSubst(Subst,open(Cnf["Dir::TemplatesDir"]+"/katie.bug-nmu-fixed","r").read());
1091 utils.send_mail (mail_message, "")
1092 summary = summary + "\n"
1096 ###############################################################################
1098 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1099 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1100 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1101 # processed it during it's checks of -2. If -1 has been deleted or
1102 # otherwise not checked by da-install, the .orig.tar.gz will not have
1103 # been checked at all. To get round this, we force the .orig.tar.gz
1104 # into the .changes structure and reprocess the .changes file.
1106 def process_it (changes_file):
1107 global reprocess, orig_tar_id, changes, dsc, dsc_files, files, reject_message;
1109 # Reset some globals
1116 legacy_source_untouchable = {};
1117 reject_message = "";
1120 # Absolutize the filename to avoid the requirement of being in the
1121 # same directory as the .changes file.
1122 changes_file = os.path.abspath(changes_file);
1124 # And since handling of installs to stable munges with the CWD;
1125 # save and restore it.
1128 check_signature (changes_file);
1129 check_changes (changes_file);
1137 update_subst(changes_file);
1138 action(changes_file);
1143 ###############################################################################
1146 global Cnf, projectB, install_bytes, new_ack_old, Subst
1150 Cnf = apt_pkg.newConfiguration();
1151 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1153 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1154 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1155 ('h',"help","Dinstall::Options::Help"),
1156 ('k',"ack-new","Dinstall::Options::Ack-New"),
1157 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1158 ('n',"no-action","Dinstall::Options::No-Action"),
1159 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1160 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1161 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1162 ('v',"version","Dinstall::Options::Version")];
1164 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1166 if Cnf["Dinstall::Options::Help"]:
1169 if Cnf["Dinstall::Options::Version"]:
1170 print "katie version 0.0000000000";
1173 postgresql_user = None; # Default == Connect as user running program.
1175 # -n/--dry-run invalidates some other options which would involve things happening
1176 if Cnf["Dinstall::Options::No-Action"]:
1177 Cnf["Dinstall::Options::Automatic"] = ""
1178 Cnf["Dinstall::Options::Ack-New"] = ""
1179 postgresql_user = Cnf["DB::ROUser"];
1181 projectB = pg.connect(Cnf["DB::Name"], Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1183 db_access.init(Cnf, projectB);
1185 # Check that we aren't going to clash with the daily cron job
1187 if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1188 sys.stderr.write("Archive maintenance in progress. Try again later.\n");
1191 # Obtain lock if not in no-action mode
1193 if not Cnf["Dinstall::Options::No-Action"]:
1194 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1195 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1197 # Read in the list of already-acknowledged NEW packages
1198 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1200 for line in new_ack_list.readlines():
1201 new_ack_old[line[:-1]] = 1;
1202 new_ack_list.close();
1204 # Initialize the substitution template mapping global
1206 Subst["__ADMIN_ADDRESS__"] = Cnf["Dinstall::MyAdminAddress"];
1207 Subst["__BUG_SERVER__"] = Cnf["Dinstall::BugServer"];
1208 bcc = "X-Katie: $Id: katie,v 1.33 2001-03-21 01:05:07 troup Exp $"
1209 if Cnf.has_key("Dinstall::Bcc"):
1210 Subst["__BCC__"] = bcc + "\nBcc: %s" % (Cnf["Dinstall::Bcc"]);
1212 Subst["__BCC__"] = bcc;
1213 Subst["__DISTRO__"] = Cnf["Dinstall::MyDistribution"];
1214 Subst["__KATIE_ADDRESS__"] = Cnf["Dinstall::MyEmailAddress"];
1216 # Process the changes files
1217 for changes_file in changes_files:
1218 print "\n" + changes_file;
1219 process_it (changes_file);
1223 if install_count > 1:
1225 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1227 # Write out the list of already-acknowledged NEW packages
1228 if Cnf["Dinstall::Options::Ack-New"]:
1229 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1230 for i in new_ack_new.keys():
1231 new_ack_list.write(i+'\n')
1232 new_ack_list.close()
1235 if __name__ == '__main__':