3 # Installs Debian packaes
4 # Copyright (C) 2000 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.19 2001-01-18 04:51:10 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" % (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
284 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
286 # Check for mandatory fields
287 if control.Find("Package") == None:
288 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
289 if control.Find("Architecture") == None:
290 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
291 if control.Find("Version") == None:
292 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
294 # Ensure the package name matches the one give in the .changes
295 if not changes["binary"].has_key(control.Find("Package", "")):
296 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
298 # Validate the architecture
299 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
300 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
302 # Check the architecture matches the one given in the .changes
303 if not changes["architecture"].has_key(control.Find("Architecture", "")):
304 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
305 # Check the section & priority match those given in the .changes (non-fatal)
306 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
307 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"])
308 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
309 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"])
311 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
313 files[file]["package"] = control.Find("Package");
314 files[file]["architecture"] = control.Find("Architecture");
315 files[file]["version"] = control.Find("Version");
316 files[file]["maintainer"] = control.Find("Maintainer", "");
317 if file[-5:] == ".udeb":
318 files[file]["dbtype"] = "udeb";
319 elif file[-4:] == ".deb":
320 files[file]["dbtype"] = "deb";
322 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
323 files[file]["type"] = "deb";
324 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
325 files[file]["source"] = control.Find("Source", "");
326 if files[file]["source"] == "":
327 files[file]["source"] = files[file]["package"];
328 # Checks for a source package...
330 m = re_issource.match(file)
332 files[file]["package"] = m.group(1)
333 files[file]["version"] = m.group(2)
334 files[file]["type"] = m.group(3)
336 # Ensure the source package name matches the Source filed in the .changes
337 if changes["source"] != files[file]["package"]:
338 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
340 # Ensure the source version matches the version in the .changes file
341 if files[file]["type"] == "orig.tar.gz":
342 changes_version = changes["chopversion2"]
344 changes_version = changes["chopversion"]
345 if changes_version != files[file]["version"]:
346 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
348 # Ensure the .changes lists source in the Architecture field
349 if not changes["architecture"].has_key("source"):
350 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
352 # Check the signature of a .dsc file
353 if files[file]["type"] == "dsc":
354 check_signature(file)
356 files[file]["fullname"] = file
358 # Not a binary or source package? Assume byhand...
360 files[file]["byhand"] = 1;
361 files[file]["type"] = "byhand";
363 files[file]["oldfiles"] = {}
364 for suite in changes["distribution"].keys():
366 if files[file].has_key("byhand"):
369 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
370 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
373 # See if the package is NEW
374 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
375 files[file]["new"] = 1
377 # Find any old binary packages
378 if files[file]["type"] == "deb":
379 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"
380 % (files[file]["package"], suite, files[file]["architecture"]))
381 oldfiles = q.dictresult()
382 for oldfile in oldfiles:
383 files[file]["oldfiles"][suite] = oldfile
384 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
385 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
386 if Cnf["Dinstall::Options::No-Version-Check"]:
387 reject_message = reject_message + "Overriden rejection"
389 reject_message = reject_message + "Rejected"
390 reject_message = reject_message + ": %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 # Check the md5sum & size against existing files (if any)
413 location = Cnf["Dir::PoolDir"];
414 files[file]["location id"] = db_access.get_location_id (location, component, archive);
416 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
417 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
419 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
421 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
422 files[file]["files id"] = files_id
424 # Check for packages that have moved from one component to another
425 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
426 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
429 if string.find(reject_message, "Rejected:") != -1:
434 ###############################################################################
437 global dsc, dsc_files, reject_message, reprocess, orig_tar_id, legacy_source_untouchable;
439 for file in files.keys():
440 if files[file]["type"] == "dsc":
442 dsc = utils.parse_changes(file)
443 except utils.cant_open_exc:
444 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (filename)
446 except utils.changes_parse_error_exc, line:
447 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
450 dsc_files = utils.build_file_list(dsc, 1)
451 except utils.no_files_exc:
452 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
454 except utils.changes_parse_error_exc, line:
455 reject_message = "Rejected: error parsing .dsc file '%s', can't grok: %s.\n" % (filename, line);
458 # Try and find all files mentioned in the .dsc. This has
459 # to work harder to cope with the multiple possible
460 # locations of an .orig.tar.gz.
461 for dsc_file in dsc_files.keys():
462 if files.has_key(dsc_file):
463 actual_md5 = files[dsc_file]["md5sum"];
464 actual_size = int(files[dsc_file]["size"]);
465 found = "%s in incoming" % (dsc_file)
466 # Check the file does not already exist in the archive
467 if not changes.has_key("stable upload"):
468 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));
470 # "It has not broken them. It has fixed a
471 # brokenness. Your crappy hack exploited a
472 # bug in the old dinstall.
474 # "(Come on! I thought it was always obvious
475 # that one just doesn't release different
476 # files with the same name and version.)"
477 # -- ajk@ on d-devel@l.d.o
479 if q.getresult() != []:
480 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
481 elif dsc_file[-12:] == ".orig.tar.gz":
483 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));
488 # Unfortunately, we make get more than one match
489 # here if, for example, the package was in potato
490 # but had a -sa upload in woody. So we need to a)
491 # choose the right one and b) mark all wrong ones
492 # as excluded from the source poolification (to
493 # avoid file overwrites).
495 x = ql[0]; # default to something sane in case we don't match any or have only one
499 old_file = i[0] + i[1];
500 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
501 actual_size = os.stat(old_file)[stat.ST_SIZE];
502 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
505 legacy_source_untouchable[i[3]] = "";
507 old_file = x[0] + x[1];
508 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
509 actual_size = os.stat(old_file)[stat.ST_SIZE];
512 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
514 if suite_type == "legacy" or suite_type == "legacy-mixed":
517 # Not there? Check in Incoming...
518 # [See comment above process_it() for explanation
519 # of why this is necessary...]
520 if os.access(dsc_file, os.R_OK) != 0:
521 files[dsc_file] = {};
522 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
523 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
524 files[dsc_file]["section"] = files[file]["section"];
525 files[dsc_file]["priority"] = files[file]["priority"];
526 files[dsc_file]["component"] = files[file]["component"];
527 files[dsc_file]["type"] = "orig.tar.gz";
531 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);
534 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming." % (file, dsc_file);
536 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
537 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
538 if actual_size != int(dsc_files[dsc_file]["size"]):
539 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
541 if string.find(reject_message, "Rejected:") != -1:
546 ###############################################################################
548 # Some cunning stunt broke dpkg-source in dpkg 1.8{,.1}; detect the
549 # resulting bad source packages and reject them.
551 # Even more amusingly the fix in 1.8.1.1 didn't actually fix the
552 # problem just changed the symptoms.
555 global dsc, dsc_files, reject_message, reprocess, orig_tar_id;
557 for filename in files.keys():
558 if files[filename]["type"] == "diff.gz":
559 file = gzip.GzipFile(filename, 'r');
560 for line in file.readlines():
561 if re_bad_diff.search(line):
562 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";
565 if string.find(reject_message, "Rejected:") != -1:
570 ###############################################################################
572 def check_md5sums ():
573 global reject_message;
575 for file in files.keys():
577 file_handle = utils.open_file(file,"r");
578 except utils.cant_open_exc:
581 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
582 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
584 def check_override ():
585 # Only check section & priority on sourceful uploads
586 if not changes["architecture"].has_key("source"):
590 for file in files.keys():
591 if not files[file].has_key("new") and (files[file]["type"] == "dsc" or files[file]["type"] == "deb"):
592 section = files[file]["section"];
593 override_section = files[file]["override section"];
594 if section != override_section and section != "-":
595 summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
596 if files[file]["type"] == "deb": # don't do priority for source
597 priority = files[file]["priority"];
598 override_priority = files[file]["override priority"];
599 if priority != override_priority and priority != "-":
600 summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
605 mail_message = """Return-Path: %s
608 Bcc: troup@auric.debian.org
609 Subject: %s override disparity
611 There are disparities between your recently installed upload and the
612 override file for the following file(s):
615 Either the package or the override file is incorrect. If you think
616 the override is correct and the package wrong please fix the package
617 so that this disparity is fixed in the next upload. If you feel the
618 override is incorrect then please reply to this mail and explain why.
621 Debian distribution maintenance software
623 (This message was generated automatically; if you believe that there
624 is a problem with it please contact the archive administrators by
625 mailing ftpmaster@debian.org)
626 """ % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes["source"], summary);
627 utils.send_mail (mail_message, "")
629 #####################################################################################################################
631 def action (changes_filename):
632 byhand = confirm = suites = summary = new = "";
634 # changes["distribution"] may not exist in corner cases
635 # (e.g. unreadable changes files)
636 if not changes.has_key("distribution"):
637 changes["distribution"] = {};
639 for suite in changes["distribution"].keys():
640 if Cnf.has_key("Suite::%s::Confirm"):
641 confirm = confirm + suite + ", "
642 suites = suites + suite + ", "
643 confirm = confirm[:-2]
646 for file in files.keys():
647 if files[file].has_key("byhand"):
649 summary = summary + file + " byhand\n"
650 elif files[file].has_key("new"):
652 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
653 if files[file].has_key("othercomponents"):
654 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
655 if files[file]["type"] == "deb":
656 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
658 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
659 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
660 summary = summary + file + "\n to " + destination + "\n"
662 short_summary = summary;
664 # This is for direport's benefit...
665 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
667 if confirm or byhand or new:
668 summary = summary + "Changes: " + f;
670 summary = summary + announce (short_summary, 0)
672 (prompt, answer) = ("", "XXX")
673 if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
676 if string.find(reject_message, "Rejected") != -1:
677 if time.time()-os.path.getmtime(changes_filename) < 86400:
678 print "SKIP (too new)\n" + reject_message,;
679 prompt = "[S]kip, Manual reject, Quit ?";
681 print "REJECT\n" + reject_message,;
682 prompt = "[R]eject, Manual reject, Skip, Quit ?";
683 if Cnf["Dinstall::Options::Automatic"]:
686 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
687 prompt = "[S]kip, New ack, Manual reject, Quit ?";
688 if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
691 print "BYHAND\n" + reject_message + summary,;
692 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
694 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
695 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
697 print "INSTALL\n" + reject_message + summary,;
698 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
699 if Cnf["Dinstall::Options::Automatic"]:
702 while string.find(prompt, answer) == -1:
704 answer = utils.our_raw_input()
705 m = re_default_answer.match(prompt)
708 answer = string.upper(answer[:1])
711 reject (changes_filename, "");
713 manual_reject (changes_filename);
715 install (changes_filename, summary, short_summary);
717 acknowledge_new (changes_filename, summary);
721 #####################################################################################################################
723 def install (changes_filename, summary, short_summary):
724 global install_count, install_bytes
726 # Stable uploads are a special case
727 if changes.has_key("stable upload"):
728 stable_install (changes_filename, summary, short_summary);
733 archive = utils.where_am_i();
735 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
736 projectB.query("BEGIN WORK");
738 # Add the .dsc file to the DB
739 for file in files.keys():
740 if files[file]["type"] == "dsc":
741 package = dsc["source"]
742 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
743 maintainer = dsc["maintainer"]
744 maintainer = string.replace(maintainer, "'", "\\'")
745 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
746 filename = files[file]["pool name"] + file;
747 dsc_location_id = files[file]["location id"];
748 if not files[file]["files id"]:
749 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
750 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
751 % (package, version, maintainer_id, files[file]["files id"]))
753 for suite in changes["distribution"].keys():
754 suite_id = db_access.get_suite_id(suite);
755 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
757 # Add the source files to the DB (files and dsc_files)
758 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
759 for dsc_file in dsc_files.keys():
760 filename = files[file]["pool name"] + dsc_file;
761 # If the .orig.tar.gz is already in the pool, it's
762 # files id is stored in dsc_files by check_dsc().
763 files_id = dsc_files[dsc_file].get("files id", None);
765 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
766 # FIXME: needs to check for -1/-2 and or handle exception
768 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
769 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
771 # Add the .deb files to the DB
772 for file in files.keys():
773 if files[file]["type"] == "deb":
774 package = files[file]["package"]
775 version = files[file]["version"]
776 maintainer = files[file]["maintainer"]
777 maintainer = string.replace(maintainer, "'", "\\'")
778 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
779 architecture = files[file]["architecture"]
780 architecture_id = db_access.get_architecture_id (architecture);
781 type = files[file]["dbtype"];
782 component = files[file]["component"]
783 source = files[file]["source"]
785 if string.find(source, "(") != -1:
786 m = utils.re_extract_src_version.match(source)
788 source_version = m.group(2)
789 if not source_version:
790 source_version = version
791 filename = files[file]["pool name"] + file;
792 if not files[file]["files id"]:
793 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
794 source_id = db_access.get_source_id (source, source_version);
796 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
797 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
799 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
800 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
801 for suite in changes["distribution"].keys():
802 suite_id = db_access.get_suite_id(suite);
803 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
805 # If the .orig.tar.gz is in a legacy directory we need to poolify
806 # it, so that apt-get source (and anything else that goes by the
807 # "Directory:" field in the Sources.gz file) works.
808 if orig_tar_id != None:
809 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));
812 # Is this an old upload superseded by a newer -sa upload? (See check_dsc() for details)
813 if legacy_source_untouchable.has_key(qid["files_id"]):
815 # First move the files to the new location
816 legacy_filename = qid["path"]+qid["filename"];
817 pool_location = utils.poolify (changes["source"], files[file]["component"]);
818 pool_filename = pool_location + os.path.basename(qid["filename"]);
819 destination = Cnf["Dir::PoolDir"] + pool_location
820 utils.move(legacy_filename, destination);
821 # Then Update the DB's files table
822 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
824 # Install the files into the pool
825 for file in files.keys():
826 if files[file].has_key("byhand"):
828 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
829 destdir = os.path.dirname(destination)
830 utils.move (file, destination)
831 install_bytes = install_bytes + float(files[file]["size"])
833 # Copy the .changes file across for suite which need it.
834 for suite in changes["distribution"].keys():
835 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
836 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
838 projectB.query("COMMIT WORK");
840 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
842 install_count = install_count + 1;
844 if not Cnf["Dinstall::Options::No-Mail"]:
845 mail_message = """Return-Path: %s
848 Bcc: troup@auric.debian.org
849 Subject: %s INSTALLED
855 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
856 utils.send_mail (mail_message, "")
857 announce (short_summary, 1)
860 #####################################################################################################################
862 def stable_install (changes_filename, summary, short_summary):
863 global install_count, install_bytes
865 print "Installing to stable."
867 archive = utils.where_am_i();
869 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
870 projectB.query("BEGIN WORK");
872 # Add the .dsc file to the DB
873 for file in files.keys():
874 if files[file]["type"] == "dsc":
875 package = dsc["source"]
876 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
877 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
880 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version));
882 source_id = ql[0][0];
883 suite_id = db_access.get_suite_id('proposed-updates');
884 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
885 suite_id = db_access.get_suite_id('stable');
886 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
888 # Add the .deb files to the DB
889 for file in files.keys():
890 if files[file]["type"] == "deb":
891 package = files[file]["package"]
892 version = files[file]["version"]
893 architecture = files[file]["architecture"]
894 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))
897 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture));
899 binary_id = ql[0][0];
900 suite_id = db_access.get_suite_id('proposed-updates');
901 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
902 suite_id = db_access.get_suite_id('stable');
903 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
905 projectB.query("COMMIT WORK");
907 utils.move (changes_filename, Cnf["Rhona::Morgue"] + os.path.basename(changes_filename));
909 # Update the Stable ChangeLog file
911 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
912 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
913 if os.path.exists(new_changelog_filename):
914 os.unlink (new_changelog_filename);
916 new_changelog = utils.open_file(new_changelog_filename, 'w');
917 for file in files.keys():
918 if files[file]["type"] == "deb":
919 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
920 elif re_issource.match(file) != None:
921 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
923 new_changelog.write("%s\n" % (file));
924 chop_changes = re_fdnic.sub("\n", changes["changes"]);
925 new_changelog.write(chop_changes + '\n\n');
926 if os.access(changelog_filename, os.R_OK) != 0:
927 changelog = utils.open_file(changelog_filename, 'r');
928 new_changelog.write(changelog.read());
929 new_changelog.close();
930 if os.access(changelog_filename, os.R_OK) != 0:
931 os.unlink(changelog_filename);
932 utils.move(new_changelog_filename, changelog_filename);
934 install_count = install_count + 1;
936 if not Cnf["Dinstall::Options::No-Mail"]:
937 mail_message = """Return-Path: %s
940 Bcc: troup@auric.debian.org
941 Subject: %s INSTALLED into stable
947 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
948 utils.send_mail (mail_message, "")
949 announce (short_summary, 1)
951 #####################################################################################################################
953 def reject (changes_filename, manual_reject_mail_filename):
956 base_changes_filename = os.path.basename(changes_filename);
957 reason_filename = re_changes.sub("reason", base_changes_filename);
958 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
960 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
962 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
963 except utils.cant_overwrite_exc:
964 sys.stderr.write("W: couldn't overwrite existing file '%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
966 for file in files.keys():
967 if os.path.exists(file):
969 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
970 except utils.cant_overwrite_exc:
971 sys.stderr.write("W: couldn't overwrite existing file '%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
974 # If this is not a manual rejection generate the .reason file and rejection mail message
975 if manual_reject_mail_filename == "":
976 if os.path.exists(reject_filename):
977 os.unlink(reject_filename);
978 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
979 os.write(fd, reject_message);
981 reject_mail_message = """From: %s
983 Bcc: troup@auric.debian.org
988 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, reject_footer);
989 else: # Have a manual rejection file to use
990 reject_mail_message = ""; # avoid <undef>'s
992 # Send the rejection mail if appropriate
993 if not Cnf["Dinstall::Options::No-Mail"]:
994 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
996 ##################################################################
998 def manual_reject (changes_filename):
999 # Build up the rejection email
1000 user_email_address = string.replace(string.split(pwd.getpwuid(os.getuid())[4],',')[0], '.', '')
1001 user_email_address = user_email_address + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
1002 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
1004 reject_mail_message = """From: %s
1007 Bcc: troup@auric.debian.org
1008 Subject: %s REJECTED
1013 %s""" % (user_email_address, Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), manual_reject_message, reject_message, reject_footer)
1015 # Write the rejection email out as the <foo>.reason file
1016 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
1017 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
1018 if os.path.exists(reject_filename):
1019 os.unlink(reject_filename);
1020 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1021 os.write(fd, reject_mail_message);
1024 # If we weren't given one, spawn an editor so the user can add one in
1025 if manual_reject_message == "":
1026 result = os.system("vi +6 %s" % (reject_file))
1028 sys.stderr.write ("vi invocation failed for `%s'!" % (reject_file))
1031 # Then process it as if it were an automatic rejection
1032 reject (changes_filename, reject_filename)
1034 #####################################################################################################################
1036 def acknowledge_new (changes_filename, summary):
1039 changes_filename = os.path.basename(changes_filename);
1041 new_ack_new[changes_filename] = 1;
1043 if new_ack_old.has_key(changes_filename):
1044 print "Ack already sent.";
1047 print "Sending new ack.";
1048 if not Cnf["Dinstall::Options::No-Mail"]:
1049 new_ack_message = """Return-Path: %s
1052 Bcc: troup@auric.debian.org
1056 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes_filename, summary, new_ack_footer);
1057 utils.send_mail(new_ack_message,"");
1059 #####################################################################################################################
1061 def announce (short_summary, action):
1062 # Only do announcements for source uploads with a recent dpkg-dev installed
1063 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
1069 for dist in changes["distribution"].keys():
1070 list = Cnf.Find("Suite::%s::Announce" % (dist))
1071 if list == None or lists_done.has_key(list):
1073 lists_done[list] = 1
1074 summary = summary + "Announcing to %s\n" % (list)
1077 mail_message = """Return-Path: %s
1080 Bcc: troup@auric.debian.org
1081 Subject: Installed %s %s (%s)
1087 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], list, changes["source"], changes["version"], string.join(changes["architecture"].keys(), ' ' ),
1088 changes["filecontents"], short_summary)
1089 utils.send_mail (mail_message, "")
1091 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
1092 bugs = changes["closes"].keys()
1094 if dsc_name == changes["maintainername"]:
1095 summary = summary + "Closing bugs: "
1097 summary = summary + "%s " % (bug)
1099 mail_message = """Return-Path: %s
1101 To: %s-close@bugs.debian.org
1102 Bcc: troup@auric.debian.org
1103 Subject: Bug#%s: fixed in %s %s
1105 We believe that the bug you reported is fixed in the latest version of
1106 %s, which has been installed in the Debian FTP archive:
1108 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], bug, bug, changes["source"], changes["version"], changes["source"], short_summary)
1110 if changes["distribution"].has_key("stable"):
1111 mail_message = mail_message + """Note that this package is not part of the released stable Debian
1112 distribution. It may have dependencies on other unreleased software,
1113 or other instabilities. Please take care if you wish to install it.
1114 The update will eventually make its way into the next released Debian
1117 mail_message = mail_message + """A summary of the changes between this version and the previous one is
1120 Thank you for reporting the bug, which will now be closed. If you
1121 have further comments please address them to %s@bugs.debian.org,
1122 and the maintainer will reopen the bug report if appropriate.
1124 Debian distribution maintenance software
1126 %s (supplier of updated %s package)
1128 (This message was generated automatically at their request; if you
1129 believe that there is a problem with it please contact the archive
1130 administrators by mailing ftpmaster@debian.org)
1133 %s""" % (bug, changes["maintainer"], changes["source"], changes["filecontents"])
1135 utils.send_mail (mail_message, "")
1137 summary = summary + "Setting bugs to severity fixed: "
1138 control_message = ""
1140 summary = summary + "%s " % (bug)
1141 control_message = control_message + "severity %s fixed\n" % (bug)
1142 if action and control_message != "":
1143 mail_message = """Return-Path: %s
1145 To: control@bugs.debian.org
1146 Bcc: troup@auric.debian.org, %s
1147 Subject: Fixed in NMU of %s %s
1152 This message was generated automatically in response to a
1153 non-maintainer upload. The .changes file follows.
1156 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes["maintainer822"], changes["source"], changes["version"], control_message, changes["filecontents"])
1157 utils.send_mail (mail_message, "")
1158 summary = summary + "\n"
1162 ###############################################################################
1164 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1165 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1166 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1167 # processed it during it's checks of -2. If -1 has been deleted or
1168 # otherwise not checked by da-install, the .orig.tar.gz will not have
1169 # been checked at all. To get round this, we force the .orig.tar.gz
1170 # into the .changes structure and reprocess the .changes file.
1172 def process_it (changes_file):
1173 global reprocess, orig_tar_id, changes, dsc, dsc_files, files;
1177 # Reset some globals
1183 legacy_source_untouchable = {};
1185 # Absolutize the filename to avoid the requirement of being in the
1186 # same directory as the .changes file.
1187 changes_file = os.path.abspath(changes_file);
1189 # And since handling of installs to stable munges with the CWD;
1190 # save and restore it.
1193 check_signature (changes_file);
1194 check_changes (changes_file);
1202 action(changes_file);
1207 ###############################################################################
1210 global Cnf, projectB, reject_message, install_bytes, new_ack_old
1214 Cnf = apt_pkg.newConfiguration();
1215 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1217 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1218 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1219 ('h',"help","Dinstall::Options::Help"),
1220 ('k',"ack-new","Dinstall::Options::Ack-New"),
1221 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1222 ('n',"no-action","Dinstall::Options::No-Action"),
1223 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1224 ('r',"no-version-check", "Dinstall::Options::No-Version-Check"),
1225 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1226 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1227 ('v',"version","Dinstall::Options::Version")];
1229 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1231 if Cnf["Dinstall::Options::Help"]:
1234 if Cnf["Dinstall::Options::Version"]:
1235 print "katie version 0.0000000000";
1238 postgresql_user = None; # Default == Connect as user running program.
1240 # -n/--dry-run invalidates some other options which would involve things happening
1241 if Cnf["Dinstall::Options::No-Action"]:
1242 Cnf["Dinstall::Options::Automatic"] = ""
1243 Cnf["Dinstall::Options::Ack-New"] = ""
1244 postgresql_user = Cnf["DB::ROUser"];
1246 projectB = pg.connect('projectb', Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1248 db_access.init(Cnf, projectB);
1250 # Check that we aren't going to clash with the daily cron job
1252 if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1253 sys.stderr.write("Archive maintenance in progress. Try again later.\n");
1256 # Obtain lock if not in no-action mode
1258 if not Cnf["Dinstall::Options::No-Action"]:
1259 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1260 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1262 # Read in the list of already-acknowledged NEW packages
1263 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1265 for line in new_ack_list.readlines():
1266 new_ack_old[line[:-1]] = 1;
1267 new_ack_list.close();
1269 # Process the changes files
1270 for changes_file in changes_files:
1272 print "\n" + changes_file;
1273 process_it (changes_file);
1277 if install_count > 1:
1279 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1281 # Write out the list of already-acknowledged NEW packages
1282 if Cnf["Dinstall::Options::Ack-New"]:
1283 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1284 for i in new_ack_new.keys():
1285 new_ack_list.write(i+'\n')
1286 new_ack_list.close()
1289 if __name__ == '__main__':