3 # Installs Debian packaes
4 # Copyright (C) 2000 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.20 2001-01-21 18:12:50 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 # 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_isadeb = re.compile (r'.*\.u?deb$');
43 re_issource = re.compile (r'(.+)_(.+?)\.(orig\.tar\.gz|diff\.gz|tar\.gz|dsc)');
44 re_changes = re.compile (r'changes$');
45 re_default_answer = re.compile(r"\[(.*)\]");
46 re_fdnic = re.compile("\n\n");
47 re_bad_diff = re.compile("^[\-\+][\-\+][\-\+] /dev/null");
49 ###############################################################################
52 reject_footer = """If you don't understand why your files were rejected, or if the
53 override file requires editing, reply to this email.
55 Your rejected files are in incoming/REJECT/. (Some may also be in
56 incoming/ if your .changes file was unparsable.) If only some of the
57 files need to repaired, you may move any good files back to incoming/.
58 Please remove any bad files from incoming/REJECT/."""
60 new_ack_footer = """Your package contains new components which requires manual editing of
61 the override file. It is ok otherwise, so please be patient. New
62 packages are usually added to the override file about once a week.
64 You may have gotten the distribution wrong. You'll get warnings above
65 if files already exist in other distributions."""
67 installed_footer = """If the override file requires editing, file a bug on ftp.debian.org.
69 Thank you for your contribution to Debian GNU."""
71 #########################################################################################
87 legacy_source_untouchable = {};
89 #########################################################################################
91 def usage (exit_code):
92 print """Usage: dinstall [OPTION]... [CHANGES]...
93 -a, --automatic automatic run
94 -d, --debug=VALUE debug
95 -k, --ack-new acknowledge new packages
96 -m, --manual-reject=MSG manual reject with `msg'
97 -n, --dry-run don't do anything
98 -p, --no-lock don't check lockfile !! for cron.daily only !!
99 -r, --no-version-check override version check
100 -u, --distribution=DIST override distribution to `dist'"""
103 def check_signature (filename):
104 global reject_message
106 (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))
108 reject_message = "Rejected: GPG signature check failed on `%s'.\n%s\n" % (os.path.basename(filename), output)
112 #####################################################################################################################
114 # See if a given package is in the override table
116 def in_override_p (package, component, suite, binary_type, file):
119 if binary_type == "": # must be source
124 # Override suite name; used for example with proposed-updates
125 if Cnf.Find("Suite::%s::OverrideSuite" % (suite)) != "":
126 suite = Cnf["Suite::%s::OverrideSuite" % (suite)];
128 # Avoid <undef> on unknown distributions
129 suite_id = db_access.get_suite_id(suite);
132 component_id = db_access.get_component_id(component);
133 type_id = db_access.get_override_type_id(type);
135 # FIXME: nasty non-US speficic hack
136 if string.lower(component[:7]) == "non-us/":
137 component = component[7:];
139 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"
140 % (package, suite_id, component_id, type_id));
141 result = q.getresult();
142 # If checking for a source package fall back on the binary override type
143 if type == "dsc" and not result:
144 type_id = db_access.get_override_type_id("deb");
145 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"
146 % (package, suite_id, component_id, type_id));
147 result = q.getresult();
149 # Remember the section and priority so we can check them later if appropriate
151 files[file]["override section"] = result[0][0];
152 files[file]["override priority"] = result[0][1];
156 #####################################################################################################################
158 def check_changes(filename):
159 global reject_message, changes, files
161 # Parse the .changes field into a dictionary [FIXME - need to trap errors, pass on to reject_message etc.]
163 changes = utils.parse_changes(filename)
164 except utils.cant_open_exc:
165 reject_message = "Rejected: can't read changes file '%s'.\n" % (filename)
167 except utils.changes_parse_error_exc, line:
168 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
169 changes["maintainer822"] = Cnf["Dinstall::MyEmailAddress"];
172 # Parse the Files field from the .changes into another dictionary [FIXME need to trap errors as above]
174 files = utils.build_file_list(changes, "");
175 except utils.changes_parse_error_exc, line:
176 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line);
178 # Check for mandatory fields
179 for i in ("source", "binary", "architecture", "version", "distribution","maintainer", "files"):
180 if not changes.has_key(i):
181 reject_message = "Rejected: Missing field `%s' in changes file." % (i)
182 return 0 # Avoid <undef> errors during later tests
184 # Fix the Maintainer: field to be RFC822 compatible
185 (changes["maintainer822"], changes["maintainername"], changes["maintaineremail"]) = utils.fix_maintainer (changes["maintainer"])
187 # Override the Distribution: field if appropriate
188 if Cnf["Dinstall::Options::Override-Distribution"] != "":
189 reject_message = reject_message + "Warning: Distribution was overriden from %s to %s.\n" % (changes["distribution"], Cnf["Dinstall::Options::Override-Distribution"])
190 changes["distribution"] = Cnf["Dinstall::Options::Override-Distribution"]
192 # Split multi-value fields into a lower-level dictionary
193 for i in ("architecture", "distribution", "binary", "closes"):
194 o = changes.get(i, "")
198 for j in string.split(o):
201 # Ensure all the values in Closes: are numbers
202 if changes.has_key("closes"):
203 for i in changes["closes"].keys():
204 if re_isanum.match (i) == None:
205 reject_message = reject_message + "Rejected: `%s' from Closes field isn't a number.\n" % (i)
207 # Map frozen to unstable if frozen doesn't exist
208 if changes["distribution"].has_key("frozen") and not Cnf.has_key("Suite::Frozen"):
209 del changes["distribution"]["frozen"]
210 reject_message = reject_message + "Mapping frozen to unstable.\n"
212 # Map testing to unstable
213 if changes["distribution"].has_key("testing"):
214 del changes["distribution"]["testing"]
215 reject_message = reject_message + "Mapping testing to unstable.\n"
217 # Ensure target distributions exist
218 for i in changes["distribution"].keys():
219 if not Cnf.has_key("Suite::%s" % (i)):
220 reject_message = reject_message + "Rejected: Unknown distribution `%s'.\n" % (i)
222 # Map unreleased arches from stable to unstable
223 if changes["distribution"].has_key("stable"):
224 for i in changes["architecture"].keys():
225 if not Cnf.has_key("Suite::Stable::Architectures::%s" % (i)):
226 reject_message = reject_message + "Mapping stable to unstable for unreleased arch `%s'.\n" % (i)
227 del changes["distribution"]["stable"]
229 # Map arches not being released from frozen to unstable
230 if changes["distribution"].has_key("frozen"):
231 for i in changes["architecture"].keys():
232 if not Cnf.has_key("Suite::Frozen::Architectures::%s" % (i)):
233 reject_message = reject_message + "Mapping frozen to unstable for non-releasing arch `%s'.\n" % (i)
234 del changes["distribution"]["frozen"]
236 # Handle uploads to stable
237 if changes["distribution"].has_key("stable"):
238 # If running from within proposed-updates; assume an install to stable
239 if string.find(os.getcwd(), 'proposed-updates') != -1:
240 # FIXME: should probably remove anything that != stable
241 for i in ("frozen", "unstable"):
242 if changes["distribution"].has_key(i):
243 reject_message = reject_message + "Removing %s from distribution list.\n"
244 del changes["distribution"][i]
245 changes["stable upload"] = 1;
246 # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool
247 file = files.keys()[0];
248 if os.access(file, os.R_OK) == 0:
249 pool_dir = Cnf["Dir::PoolDir"] + '/' + utils.poolify(changes["source"], files[file]["component"]);
251 # Otherwise (normal case) map stable to updates
253 reject_message = reject_message + "Mapping stable to updates.\n";
254 del changes["distribution"]["stable"];
255 changes["distribution"]["proposed-updates"] = 1;
257 # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
258 changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
259 changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
261 if string.find(reject_message, "Rejected:") != -1:
267 global reject_message
269 archive = utils.where_am_i();
271 for file in files.keys():
272 # Check the file is readable
273 if os.access(file,os.R_OK) == 0:
274 reject_message = reject_message + "Rejected: Can't read `%s'.\n" % (file)
275 files[file]["type"] = "unreadable";
277 # If it's byhand skip remaining checks
278 if files[file]["section"] == "byhand":
279 files[file]["byhand"] = 1;
280 files[file]["type"] = "byhand";
281 # Checks for a binary package...
282 elif re_isadeb.match(file) != None:
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);
289 # Check for mandatory fields
290 if control.Find("Package") == None:
291 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
292 if control.Find("Architecture") == None:
293 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
294 if control.Find("Version") == None:
295 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
297 # Ensure the package name matches the one give in the .changes
298 if not changes["binary"].has_key(control.Find("Package", "")):
299 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
301 # Validate the architecture
302 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
303 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
305 # Check the architecture matches the one given in the .changes
306 if not changes["architecture"].has_key(control.Find("Architecture", "")):
307 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
308 # Check the section & priority match those given in the .changes (non-fatal)
309 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
310 reject_message = reject_message + "Warning: %s control file lists section as `%s', but changes file has `%s'.\n" % (file, control.Find("Section", ""), files[file]["section"])
311 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
312 reject_message = reject_message + "Warning: %s control file lists priority as `%s', but changes file has `%s'.\n" % (file, control.Find("Priority", ""), files[file]["priority"])
314 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
316 files[file]["package"] = control.Find("Package");
317 files[file]["architecture"] = control.Find("Architecture");
318 files[file]["version"] = control.Find("Version");
319 files[file]["maintainer"] = control.Find("Maintainer", "");
320 if file[-5:] == ".udeb":
321 files[file]["dbtype"] = "udeb";
322 elif file[-4:] == ".deb":
323 files[file]["dbtype"] = "deb";
325 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
326 files[file]["type"] = "deb";
327 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
328 files[file]["source"] = control.Find("Source", "");
329 if files[file]["source"] == "":
330 files[file]["source"] = files[file]["package"];
331 # Checks for a source package...
333 m = re_issource.match(file)
335 files[file]["package"] = m.group(1)
336 files[file]["version"] = m.group(2)
337 files[file]["type"] = m.group(3)
339 # Ensure the source package name matches the Source filed in the .changes
340 if changes["source"] != files[file]["package"]:
341 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
343 # Ensure the source version matches the version in the .changes file
344 if files[file]["type"] == "orig.tar.gz":
345 changes_version = changes["chopversion2"]
347 changes_version = changes["chopversion"]
348 if changes_version != files[file]["version"]:
349 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
351 # Ensure the .changes lists source in the Architecture field
352 if not changes["architecture"].has_key("source"):
353 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
355 # Check the signature of a .dsc file
356 if files[file]["type"] == "dsc":
357 check_signature(file)
359 files[file]["fullname"] = file
361 # Not a binary or source package? Assume byhand...
363 files[file]["byhand"] = 1;
364 files[file]["type"] = "byhand";
366 files[file]["oldfiles"] = {}
367 for suite in changes["distribution"].keys():
369 if files[file].has_key("byhand"):
372 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
373 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
376 # See if the package is NEW
377 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
378 files[file]["new"] = 1
380 # Find any old binary packages
381 if files[file]["type"] == "deb":
382 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"
383 % (files[file]["package"], suite, files[file]["architecture"]))
384 oldfiles = q.dictresult()
385 for oldfile in oldfiles:
386 files[file]["oldfiles"][suite] = oldfile
387 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
388 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
389 if Cnf["Dinstall::Options::No-Version-Check"]:
390 reject_message = reject_message + "Overriden rejection"
392 reject_message = reject_message + "Rejected"
393 reject_message = reject_message + ": %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
394 # Check for existing copies of the file
395 if not changes.has_key("stable upload"):
396 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"]))
397 if q.getresult() != []:
398 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
400 # Find any old .dsc files
401 elif files[file]["type"] == "dsc":
402 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"
403 % (files[file]["package"], suite))
404 oldfiles = q.dictresult()
405 if len(oldfiles) >= 1:
406 files[file]["oldfiles"][suite] = oldfiles[0]
408 # Validate the component
409 component = files[file]["component"];
410 component_id = db_access.get_component_id(component);
411 if component_id == -1:
412 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
415 # Check the md5sum & size against existing files (if any)
416 location = Cnf["Dir::PoolDir"];
417 files[file]["location id"] = db_access.get_location_id (location, component, archive);
419 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
420 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
422 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
424 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
425 files[file]["files id"] = files_id
427 # Check for packages that have moved from one component to another
428 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
429 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
432 if string.find(reject_message, "Rejected:") != -1:
437 ###############################################################################
440 global dsc, dsc_files, reject_message, reprocess, orig_tar_id, legacy_source_untouchable;
442 for file in files.keys():
443 if files[file]["type"] == "dsc":
445 dsc = utils.parse_changes(file)
446 except utils.cant_open_exc:
447 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (filename)
449 except utils.changes_parse_error_exc, line:
450 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
453 dsc_files = utils.build_file_list(dsc, 1)
454 except utils.no_files_exc:
455 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
457 except utils.changes_parse_error_exc, line:
458 reject_message = "Rejected: error parsing .dsc file '%s', can't grok: %s.\n" % (filename, line);
461 # Try and find all files mentioned in the .dsc. This has
462 # to work harder to cope with the multiple possible
463 # locations of an .orig.tar.gz.
464 for dsc_file in dsc_files.keys():
465 if files.has_key(dsc_file):
466 actual_md5 = files[dsc_file]["md5sum"];
467 actual_size = int(files[dsc_file]["size"]);
468 found = "%s in incoming" % (dsc_file)
469 # Check the file does not already exist in the archive
470 if not changes.has_key("stable upload"):
471 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));
473 # "It has not broken them. It has fixed a
474 # brokenness. Your crappy hack exploited a
475 # bug in the old dinstall.
477 # "(Come on! I thought it was always obvious
478 # that one just doesn't release different
479 # files with the same name and version.)"
480 # -- ajk@ on d-devel@l.d.o
482 if q.getresult() != []:
483 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
484 elif dsc_file[-12:] == ".orig.tar.gz":
486 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));
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()
517 if suite_type == "legacy" or suite_type == "legacy-mixed":
520 # Not there? Check in Incoming...
521 # [See comment above process_it() for explanation
522 # of why this is necessary...]
523 if os.access(dsc_file, os.R_OK) != 0:
524 files[dsc_file] = {};
525 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
526 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
527 files[dsc_file]["section"] = files[file]["section"];
528 files[dsc_file]["priority"] = files[file]["priority"];
529 files[dsc_file]["component"] = files[file]["component"];
530 files[dsc_file]["type"] = "orig.tar.gz";
534 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);
537 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming." % (file, dsc_file);
539 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
540 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
541 if actual_size != int(dsc_files[dsc_file]["size"]):
542 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
544 if string.find(reject_message, "Rejected:") != -1:
549 ###############################################################################
551 # Some cunning stunt broke dpkg-source in dpkg 1.8{,.1}; detect the
552 # resulting bad source packages and reject them.
554 # Even more amusingly the fix in 1.8.1.1 didn't actually fix the
555 # problem just changed the symptoms.
558 global dsc, dsc_files, reject_message, reprocess, orig_tar_id;
560 for filename in files.keys():
561 if files[filename]["type"] == "diff.gz":
562 file = gzip.GzipFile(filename, 'r');
563 for line in file.readlines():
564 if re_bad_diff.search(line):
565 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";
568 if string.find(reject_message, "Rejected:") != -1:
573 ###############################################################################
575 def check_md5sums ():
576 global reject_message;
578 for file in files.keys():
580 file_handle = utils.open_file(file,"r");
581 except utils.cant_open_exc:
584 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
585 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
587 def check_override ():
588 # Only check section & priority on sourceful uploads
589 if not changes["architecture"].has_key("source"):
593 for file in files.keys():
594 if not files[file].has_key("new") and (files[file]["type"] == "dsc" or files[file]["type"] == "deb"):
595 section = files[file]["section"];
596 override_section = files[file]["override section"];
597 if section != override_section and section != "-":
598 summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
599 if files[file]["type"] == "deb": # don't do priority for source
600 priority = files[file]["priority"];
601 override_priority = files[file]["override priority"];
602 if priority != override_priority and priority != "-":
603 summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
608 mail_message = """Return-Path: %s
611 Bcc: troup@auric.debian.org
612 Subject: %s override disparity
614 There are disparities between your recently installed upload and the
615 override file for the following file(s):
618 Either the package or the override file is incorrect. If you think
619 the override is correct and the package wrong please fix the package
620 so that this disparity is fixed in the next upload. If you feel the
621 override is incorrect then please reply to this mail and explain why.
624 Debian distribution maintenance software
626 (This message was generated automatically; if you believe that there
627 is a problem with it please contact the archive administrators by
628 mailing ftpmaster@debian.org)
629 """ % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes["source"], summary);
630 utils.send_mail (mail_message, "")
632 #####################################################################################################################
634 def action (changes_filename):
635 byhand = confirm = suites = summary = new = "";
637 # changes["distribution"] may not exist in corner cases
638 # (e.g. unreadable changes files)
639 if not changes.has_key("distribution"):
640 changes["distribution"] = {};
642 for suite in changes["distribution"].keys():
643 if Cnf.has_key("Suite::%s::Confirm"):
644 confirm = confirm + suite + ", "
645 suites = suites + suite + ", "
646 confirm = confirm[:-2]
649 for file in files.keys():
650 if files[file].has_key("byhand"):
652 summary = summary + file + " byhand\n"
653 elif files[file].has_key("new"):
655 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
656 if files[file].has_key("othercomponents"):
657 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
658 if files[file]["type"] == "deb":
659 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
661 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
662 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
663 summary = summary + file + "\n to " + destination + "\n"
665 short_summary = summary;
667 # This is for direport's benefit...
668 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
670 if confirm or byhand or new:
671 summary = summary + "Changes: " + f;
673 summary = summary + announce (short_summary, 0)
675 (prompt, answer) = ("", "XXX")
676 if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
679 if string.find(reject_message, "Rejected") != -1:
680 if time.time()-os.path.getmtime(changes_filename) < 86400:
681 print "SKIP (too new)\n" + reject_message,;
682 prompt = "[S]kip, Manual reject, Quit ?";
684 print "REJECT\n" + reject_message,;
685 prompt = "[R]eject, Manual reject, Skip, Quit ?";
686 if Cnf["Dinstall::Options::Automatic"]:
689 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
690 prompt = "[S]kip, New ack, Manual reject, Quit ?";
691 if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
694 print "BYHAND\n" + reject_message + summary,;
695 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
697 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
698 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
700 print "INSTALL\n" + reject_message + summary,;
701 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
702 if Cnf["Dinstall::Options::Automatic"]:
705 while string.find(prompt, answer) == -1:
707 answer = utils.our_raw_input()
708 m = re_default_answer.match(prompt)
711 answer = string.upper(answer[:1])
714 reject (changes_filename, "");
716 manual_reject (changes_filename);
718 install (changes_filename, summary, short_summary);
720 acknowledge_new (changes_filename, summary);
724 #####################################################################################################################
726 def install (changes_filename, summary, short_summary):
727 global install_count, install_bytes
729 # Stable uploads are a special case
730 if changes.has_key("stable upload"):
731 stable_install (changes_filename, summary, short_summary);
736 archive = utils.where_am_i();
738 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
739 projectB.query("BEGIN WORK");
741 # Add the .dsc file to the DB
742 for file in files.keys():
743 if files[file]["type"] == "dsc":
744 package = dsc["source"]
745 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
746 maintainer = dsc["maintainer"]
747 maintainer = string.replace(maintainer, "'", "\\'")
748 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
749 filename = files[file]["pool name"] + file;
750 dsc_location_id = files[file]["location id"];
751 if not files[file]["files id"]:
752 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
753 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
754 % (package, version, maintainer_id, files[file]["files id"]))
756 for suite in changes["distribution"].keys():
757 suite_id = db_access.get_suite_id(suite);
758 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
760 # Add the source files to the DB (files and dsc_files)
761 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
762 for dsc_file in dsc_files.keys():
763 filename = files[file]["pool name"] + dsc_file;
764 # If the .orig.tar.gz is already in the pool, it's
765 # files id is stored in dsc_files by check_dsc().
766 files_id = dsc_files[dsc_file].get("files id", None);
768 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
769 # FIXME: needs to check for -1/-2 and or handle exception
771 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
772 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
774 # Add the .deb files to the DB
775 for file in files.keys():
776 if files[file]["type"] == "deb":
777 package = files[file]["package"]
778 version = files[file]["version"]
779 maintainer = files[file]["maintainer"]
780 maintainer = string.replace(maintainer, "'", "\\'")
781 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
782 architecture = files[file]["architecture"]
783 architecture_id = db_access.get_architecture_id (architecture);
784 type = files[file]["dbtype"];
785 component = files[file]["component"]
786 source = files[file]["source"]
788 if string.find(source, "(") != -1:
789 m = utils.re_extract_src_version.match(source)
791 source_version = m.group(2)
792 if not source_version:
793 source_version = version
794 filename = files[file]["pool name"] + file;
795 if not files[file]["files id"]:
796 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
797 source_id = db_access.get_source_id (source, source_version);
799 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
800 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
802 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
803 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
804 for suite in changes["distribution"].keys():
805 suite_id = db_access.get_suite_id(suite);
806 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
808 # If the .orig.tar.gz is in a legacy directory we need to poolify
809 # it, so that apt-get source (and anything else that goes by the
810 # "Directory:" field in the Sources.gz file) works.
811 if orig_tar_id != None:
812 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));
815 # Is this an old upload superseded by a newer -sa upload? (See check_dsc() for details)
816 if legacy_source_untouchable.has_key(qid["files_id"]):
818 # First move the files to the new location
819 legacy_filename = qid["path"]+qid["filename"];
820 pool_location = utils.poolify (changes["source"], files[file]["component"]);
821 pool_filename = pool_location + os.path.basename(qid["filename"]);
822 destination = Cnf["Dir::PoolDir"] + pool_location
823 utils.move(legacy_filename, destination);
824 # Then Update the DB's files table
825 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
827 # Install the files into the pool
828 for file in files.keys():
829 if files[file].has_key("byhand"):
831 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
832 destdir = os.path.dirname(destination)
833 utils.move (file, destination)
834 install_bytes = install_bytes + float(files[file]["size"])
836 # Copy the .changes file across for suite which need it.
837 for suite in changes["distribution"].keys():
838 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
839 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
841 projectB.query("COMMIT WORK");
843 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
845 install_count = install_count + 1;
847 if not Cnf["Dinstall::Options::No-Mail"]:
848 mail_message = """Return-Path: %s
851 Bcc: troup@auric.debian.org
852 Subject: %s INSTALLED
858 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
859 utils.send_mail (mail_message, "")
860 announce (short_summary, 1)
863 #####################################################################################################################
865 def stable_install (changes_filename, summary, short_summary):
866 global install_count, install_bytes
868 print "Installing to stable."
870 archive = utils.where_am_i();
872 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
873 projectB.query("BEGIN WORK");
875 # Add the .dsc file to the DB
876 for file in files.keys():
877 if files[file]["type"] == "dsc":
878 package = dsc["source"]
879 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
880 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
883 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version));
885 source_id = ql[0][0];
886 suite_id = db_access.get_suite_id('proposed-updates');
887 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
888 suite_id = db_access.get_suite_id('stable');
889 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
891 # Add the .deb files to the DB
892 for file in files.keys():
893 if files[file]["type"] == "deb":
894 package = files[file]["package"]
895 version = files[file]["version"]
896 architecture = files[file]["architecture"]
897 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))
900 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture));
902 binary_id = ql[0][0];
903 suite_id = db_access.get_suite_id('proposed-updates');
904 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
905 suite_id = db_access.get_suite_id('stable');
906 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
908 projectB.query("COMMIT WORK");
910 utils.move (changes_filename, Cnf["Rhona::Morgue"] + os.path.basename(changes_filename));
912 # Update the Stable ChangeLog file
914 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
915 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
916 if os.path.exists(new_changelog_filename):
917 os.unlink (new_changelog_filename);
919 new_changelog = utils.open_file(new_changelog_filename, 'w');
920 for file in files.keys():
921 if files[file]["type"] == "deb":
922 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
923 elif re_issource.match(file) != None:
924 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
926 new_changelog.write("%s\n" % (file));
927 chop_changes = re_fdnic.sub("\n", changes["changes"]);
928 new_changelog.write(chop_changes + '\n\n');
929 if os.access(changelog_filename, os.R_OK) != 0:
930 changelog = utils.open_file(changelog_filename, 'r');
931 new_changelog.write(changelog.read());
932 new_changelog.close();
933 if os.access(changelog_filename, os.R_OK) != 0:
934 os.unlink(changelog_filename);
935 utils.move(new_changelog_filename, changelog_filename);
937 install_count = install_count + 1;
939 if not Cnf["Dinstall::Options::No-Mail"]:
940 mail_message = """Return-Path: %s
943 Bcc: troup@auric.debian.org
944 Subject: %s INSTALLED into stable
950 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
951 utils.send_mail (mail_message, "")
952 announce (short_summary, 1)
954 #####################################################################################################################
956 def reject (changes_filename, manual_reject_mail_filename):
959 base_changes_filename = os.path.basename(changes_filename);
960 reason_filename = re_changes.sub("reason", base_changes_filename);
961 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
963 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
965 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
966 except utils.cant_overwrite_exc:
967 sys.stderr.write("W: couldn't overwrite existing file '%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
969 for file in files.keys():
970 if os.path.exists(file):
972 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
973 except utils.cant_overwrite_exc:
974 sys.stderr.write("W: couldn't overwrite existing file '%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
977 # If this is not a manual rejection generate the .reason file and rejection mail message
978 if manual_reject_mail_filename == "":
979 if os.path.exists(reject_filename):
980 os.unlink(reject_filename);
981 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
982 os.write(fd, reject_message);
984 reject_mail_message = """From: %s
986 Bcc: troup@auric.debian.org
991 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, reject_footer);
992 else: # Have a manual rejection file to use
993 reject_mail_message = ""; # avoid <undef>'s
995 # Send the rejection mail if appropriate
996 if not Cnf["Dinstall::Options::No-Mail"]:
997 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
999 ##################################################################
1001 def manual_reject (changes_filename):
1002 # Build up the rejection email
1003 user_email_address = string.replace(string.split(pwd.getpwuid(os.getuid())[4],',')[0], '.', '')
1004 user_email_address = user_email_address + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
1005 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
1007 reject_mail_message = """From: %s
1010 Bcc: troup@auric.debian.org
1011 Subject: %s REJECTED
1016 %s""" % (user_email_address, Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), manual_reject_message, reject_message, reject_footer)
1018 # Write the rejection email out as the <foo>.reason file
1019 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
1020 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
1021 if os.path.exists(reject_filename):
1022 os.unlink(reject_filename);
1023 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1024 os.write(fd, reject_mail_message);
1027 # If we weren't given one, spawn an editor so the user can add one in
1028 if manual_reject_message == "":
1029 result = os.system("vi +6 %s" % (reject_file))
1031 sys.stderr.write ("vi invocation failed for `%s'!" % (reject_file))
1034 # Then process it as if it were an automatic rejection
1035 reject (changes_filename, reject_filename)
1037 #####################################################################################################################
1039 def acknowledge_new (changes_filename, summary):
1042 changes_filename = os.path.basename(changes_filename);
1044 new_ack_new[changes_filename] = 1;
1046 if new_ack_old.has_key(changes_filename):
1047 print "Ack already sent.";
1050 print "Sending new ack.";
1051 if not Cnf["Dinstall::Options::No-Mail"]:
1052 new_ack_message = """Return-Path: %s
1055 Bcc: troup@auric.debian.org
1059 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes_filename, summary, new_ack_footer);
1060 utils.send_mail(new_ack_message,"");
1062 #####################################################################################################################
1064 def announce (short_summary, action):
1065 # Only do announcements for source uploads with a recent dpkg-dev installed
1066 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
1072 for dist in changes["distribution"].keys():
1073 list = Cnf.Find("Suite::%s::Announce" % (dist))
1074 if list == None or lists_done.has_key(list):
1076 lists_done[list] = 1
1077 summary = summary + "Announcing to %s\n" % (list)
1080 mail_message = """Return-Path: %s
1083 Bcc: troup@auric.debian.org
1084 Subject: Installed %s %s (%s)
1090 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], list, changes["source"], changes["version"], string.join(changes["architecture"].keys(), ' ' ),
1091 changes["filecontents"], short_summary)
1092 utils.send_mail (mail_message, "")
1094 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
1095 bugs = changes["closes"].keys()
1097 if dsc_name == changes["maintainername"]:
1098 summary = summary + "Closing bugs: "
1100 summary = summary + "%s " % (bug)
1102 mail_message = """Return-Path: %s
1104 To: %s-close@bugs.debian.org
1105 Bcc: troup@auric.debian.org
1106 Subject: Bug#%s: fixed in %s %s
1108 We believe that the bug you reported is fixed in the latest version of
1109 %s, which has been installed in the Debian FTP archive:
1111 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], bug, bug, changes["source"], changes["version"], changes["source"], short_summary)
1113 if changes["distribution"].has_key("stable"):
1114 mail_message = mail_message + """Note that this package is not part of the released stable Debian
1115 distribution. It may have dependencies on other unreleased software,
1116 or other instabilities. Please take care if you wish to install it.
1117 The update will eventually make its way into the next released Debian
1120 mail_message = mail_message + """A summary of the changes between this version and the previous one is
1123 Thank you for reporting the bug, which will now be closed. If you
1124 have further comments please address them to %s@bugs.debian.org,
1125 and the maintainer will reopen the bug report if appropriate.
1127 Debian distribution maintenance software
1129 %s (supplier of updated %s package)
1131 (This message was generated automatically at their request; if you
1132 believe that there is a problem with it please contact the archive
1133 administrators by mailing ftpmaster@debian.org)
1136 %s""" % (bug, changes["maintainer"], changes["source"], changes["filecontents"])
1138 utils.send_mail (mail_message, "")
1140 summary = summary + "Setting bugs to severity fixed: "
1141 control_message = ""
1143 summary = summary + "%s " % (bug)
1144 control_message = control_message + "severity %s fixed\n" % (bug)
1145 if action and control_message != "":
1146 mail_message = """Return-Path: %s
1148 To: control@bugs.debian.org
1149 Bcc: troup@auric.debian.org, %s
1150 Subject: Fixed in NMU of %s %s
1155 This message was generated automatically in response to a
1156 non-maintainer upload. The .changes file follows.
1159 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes["maintainer822"], changes["source"], changes["version"], control_message, changes["filecontents"])
1160 utils.send_mail (mail_message, "")
1161 summary = summary + "\n"
1165 ###############################################################################
1167 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1168 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1169 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1170 # processed it during it's checks of -2. If -1 has been deleted or
1171 # otherwise not checked by da-install, the .orig.tar.gz will not have
1172 # been checked at all. To get round this, we force the .orig.tar.gz
1173 # into the .changes structure and reprocess the .changes file.
1175 def process_it (changes_file):
1176 global reprocess, orig_tar_id, changes, dsc, dsc_files, files;
1180 # Reset some globals
1186 legacy_source_untouchable = {};
1188 # Absolutize the filename to avoid the requirement of being in the
1189 # same directory as the .changes file.
1190 changes_file = os.path.abspath(changes_file);
1192 # And since handling of installs to stable munges with the CWD;
1193 # save and restore it.
1196 check_signature (changes_file);
1197 check_changes (changes_file);
1205 action(changes_file);
1210 ###############################################################################
1213 global Cnf, projectB, reject_message, install_bytes, new_ack_old
1217 Cnf = apt_pkg.newConfiguration();
1218 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1220 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1221 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1222 ('h',"help","Dinstall::Options::Help"),
1223 ('k',"ack-new","Dinstall::Options::Ack-New"),
1224 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1225 ('n',"no-action","Dinstall::Options::No-Action"),
1226 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1227 ('r',"no-version-check", "Dinstall::Options::No-Version-Check"),
1228 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1229 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1230 ('v',"version","Dinstall::Options::Version")];
1232 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1234 if Cnf["Dinstall::Options::Help"]:
1237 if Cnf["Dinstall::Options::Version"]:
1238 print "katie version 0.0000000000";
1241 postgresql_user = None; # Default == Connect as user running program.
1243 # -n/--dry-run invalidates some other options which would involve things happening
1244 if Cnf["Dinstall::Options::No-Action"]:
1245 Cnf["Dinstall::Options::Automatic"] = ""
1246 Cnf["Dinstall::Options::Ack-New"] = ""
1247 postgresql_user = Cnf["DB::ROUser"];
1249 projectB = pg.connect('projectb', Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1251 db_access.init(Cnf, projectB);
1253 # Check that we aren't going to clash with the daily cron job
1255 if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1256 sys.stderr.write("Archive maintenance in progress. Try again later.\n");
1259 # Obtain lock if not in no-action mode
1261 if not Cnf["Dinstall::Options::No-Action"]:
1262 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1263 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1265 # Read in the list of already-acknowledged NEW packages
1266 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1268 for line in new_ack_list.readlines():
1269 new_ack_old[line[:-1]] = 1;
1270 new_ack_list.close();
1272 # Process the changes files
1273 for changes_file in changes_files:
1275 print "\n" + changes_file;
1276 process_it (changes_file);
1280 if install_count > 1:
1282 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1284 # Write out the list of already-acknowledged NEW packages
1285 if Cnf["Dinstall::Options::Ack-New"]:
1286 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1287 for i in new_ack_new.keys():
1288 new_ack_list.write(i+'\n')
1289 new_ack_list.close()
1292 if __name__ == '__main__':