3 # Installs Debian packaes
4 # Copyright (C) 2000 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.22 2001-01-23 20:27:35 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, --no-action 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 changes["distribution"]["unstable"] = 1;
211 reject_message = reject_message + "Mapping frozen to unstable.\n"
213 # Map testing to unstable
214 if changes["distribution"].has_key("testing"):
215 del changes["distribution"]["testing"]
216 changes["distribution"]["unstable"] = 1;
217 reject_message = reject_message + "Mapping testing to unstable.\n"
219 # Ensure target distributions exist
220 for i in changes["distribution"].keys():
221 if not Cnf.has_key("Suite::%s" % (i)):
222 reject_message = reject_message + "Rejected: Unknown distribution `%s'.\n" % (i)
224 # Ensure there _is_ a target distribution
225 if changes["distribution"].keys() == []:
226 reject_message = reject_message + "Rejected: huh? Distribution field is empty in changes file.\n";
228 # Map unreleased arches from stable to unstable
229 if changes["distribution"].has_key("stable"):
230 for i in changes["architecture"].keys():
231 if not Cnf.has_key("Suite::Stable::Architectures::%s" % (i)):
232 reject_message = reject_message + "Mapping stable to unstable for unreleased arch `%s'.\n" % (i)
233 del changes["distribution"]["stable"]
234 changes["distribution"]["unstable"] = 1;
236 # Map arches not being released from frozen to unstable
237 if changes["distribution"].has_key("frozen"):
238 for i in changes["architecture"].keys():
239 if not Cnf.has_key("Suite::Frozen::Architectures::%s" % (i)):
240 reject_message = reject_message + "Mapping frozen to unstable for non-releasing arch `%s'.\n" % (i)
241 del changes["distribution"]["frozen"]
242 changes["distribution"]["unstable"] = 1;
244 # Handle uploads to stable
245 if changes["distribution"].has_key("stable"):
246 # If running from within proposed-updates; assume an install to stable
247 if string.find(os.getcwd(), 'proposed-updates') != -1:
248 # FIXME: should probably remove anything that != stable
249 for i in ("frozen", "unstable"):
250 if changes["distribution"].has_key(i):
251 reject_message = reject_message + "Removing %s from distribution list.\n"
252 del changes["distribution"][i]
253 changes["stable upload"] = 1;
254 # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool
255 file = files.keys()[0];
256 if os.access(file, os.R_OK) == 0:
257 pool_dir = Cnf["Dir::PoolDir"] + '/' + utils.poolify(changes["source"], files[file]["component"]);
259 # Otherwise (normal case) map stable to updates
261 reject_message = reject_message + "Mapping stable to updates.\n";
262 del changes["distribution"]["stable"];
263 changes["distribution"]["proposed-updates"] = 1;
265 # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
266 changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
267 changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
269 if string.find(reject_message, "Rejected:") != -1:
275 global reject_message
277 archive = utils.where_am_i();
279 for file in files.keys():
280 # Check the file is readable
281 if os.access(file,os.R_OK) == 0:
282 reject_message = reject_message + "Rejected: Can't read `%s'.\n" % (file)
283 files[file]["type"] = "unreadable";
285 # If it's byhand skip remaining checks
286 if files[file]["section"] == "byhand":
287 files[file]["byhand"] = 1;
288 files[file]["type"] = "byhand";
289 # Checks for a binary package...
290 elif re_isadeb.match(file) != None:
291 files[file]["type"] = "deb";
293 # Extract package information using dpkg-deb
295 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
297 reject_message = reject_message + "Rejected: %s: debExtractControl() raised %s.\n" % (file, sys.exc_type);
298 # Can't continue, none of the checks on control would work.
301 # Check for mandatory fields
302 if control.Find("Package") == None:
303 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
304 if control.Find("Architecture") == None:
305 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
306 if control.Find("Version") == None:
307 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
309 # Ensure the package name matches the one give in the .changes
310 if not changes["binary"].has_key(control.Find("Package", "")):
311 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
313 # Validate the architecture
314 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
315 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
317 # Check the architecture matches the one given in the .changes
318 if not changes["architecture"].has_key(control.Find("Architecture", "")):
319 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
320 # Check the section & priority match those given in the .changes (non-fatal)
321 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
322 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"])
323 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
324 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"])
326 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
328 files[file]["package"] = control.Find("Package");
329 files[file]["architecture"] = control.Find("Architecture");
330 files[file]["version"] = control.Find("Version");
331 files[file]["maintainer"] = control.Find("Maintainer", "");
332 if file[-5:] == ".udeb":
333 files[file]["dbtype"] = "udeb";
334 elif file[-4:] == ".deb":
335 files[file]["dbtype"] = "deb";
337 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
338 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
339 files[file]["source"] = control.Find("Source", "");
340 if files[file]["source"] == "":
341 files[file]["source"] = files[file]["package"];
342 # Checks for a source package...
344 m = re_issource.match(file)
346 files[file]["package"] = m.group(1)
347 files[file]["version"] = m.group(2)
348 files[file]["type"] = m.group(3)
350 # Ensure the source package name matches the Source filed in the .changes
351 if changes["source"] != files[file]["package"]:
352 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
354 # Ensure the source version matches the version in the .changes file
355 if files[file]["type"] == "orig.tar.gz":
356 changes_version = changes["chopversion2"]
358 changes_version = changes["chopversion"]
359 if changes_version != files[file]["version"]:
360 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
362 # Ensure the .changes lists source in the Architecture field
363 if not changes["architecture"].has_key("source"):
364 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
366 # Check the signature of a .dsc file
367 if files[file]["type"] == "dsc":
368 check_signature(file)
370 files[file]["fullname"] = file
372 # Not a binary or source package? Assume byhand...
374 files[file]["byhand"] = 1;
375 files[file]["type"] = "byhand";
377 files[file]["oldfiles"] = {}
378 for suite in changes["distribution"].keys():
380 if files[file].has_key("byhand"):
383 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
384 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
387 # See if the package is NEW
388 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
389 files[file]["new"] = 1
391 # Find any old binary packages
392 if files[file]["type"] == "deb":
393 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"
394 % (files[file]["package"], suite, files[file]["architecture"]))
395 oldfiles = q.dictresult()
396 for oldfile in oldfiles:
397 files[file]["oldfiles"][suite] = oldfile
398 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
399 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
400 if Cnf["Dinstall::Options::No-Version-Check"]:
401 reject_message = reject_message + "Overriden rejection"
403 reject_message = reject_message + "Rejected"
404 reject_message = reject_message + ": %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
405 # Check for existing copies of the file
406 if not changes.has_key("stable upload"):
407 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"]))
408 if q.getresult() != []:
409 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
411 # Find any old .dsc files
412 elif files[file]["type"] == "dsc":
413 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"
414 % (files[file]["package"], suite))
415 oldfiles = q.dictresult()
416 if len(oldfiles) >= 1:
417 files[file]["oldfiles"][suite] = oldfiles[0]
419 # Validate the component
420 component = files[file]["component"];
421 component_id = db_access.get_component_id(component);
422 if component_id == -1:
423 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
426 # Check the md5sum & size against existing files (if any)
427 location = Cnf["Dir::PoolDir"];
428 files[file]["location id"] = db_access.get_location_id (location, component, archive);
430 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
431 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
433 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
435 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
436 files[file]["files id"] = files_id
438 # Check for packages that have moved from one component to another
439 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
440 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
443 if string.find(reject_message, "Rejected:") != -1:
448 ###############################################################################
451 global dsc, dsc_files, reject_message, reprocess, orig_tar_id, legacy_source_untouchable;
453 for file in files.keys():
454 if files[file]["type"] == "dsc":
456 dsc = utils.parse_changes(file)
457 except utils.cant_open_exc:
458 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (filename)
460 except utils.changes_parse_error_exc, line:
461 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
464 dsc_files = utils.build_file_list(dsc, 1)
465 except utils.no_files_exc:
466 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
468 except utils.changes_parse_error_exc, line:
469 reject_message = "Rejected: error parsing .dsc file '%s', can't grok: %s.\n" % (filename, line);
472 # Try and find all files mentioned in the .dsc. This has
473 # to work harder to cope with the multiple possible
474 # locations of an .orig.tar.gz.
475 for dsc_file in dsc_files.keys():
476 if files.has_key(dsc_file):
477 actual_md5 = files[dsc_file]["md5sum"];
478 actual_size = int(files[dsc_file]["size"]);
479 found = "%s in incoming" % (dsc_file)
480 # Check the file does not already exist in the archive
481 if not changes.has_key("stable upload"):
482 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));
484 # "It has not broken them. It has fixed a
485 # brokenness. Your crappy hack exploited a
486 # bug in the old dinstall.
488 # "(Come on! I thought it was always obvious
489 # that one just doesn't release different
490 # files with the same name and version.)"
491 # -- ajk@ on d-devel@l.d.o
493 if q.getresult() != []:
494 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
495 elif dsc_file[-12:] == ".orig.tar.gz":
497 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));
502 # Unfortunately, we make get more than one match
503 # here if, for example, the package was in potato
504 # but had a -sa upload in woody. So we need to a)
505 # choose the right one and b) mark all wrong ones
506 # as excluded from the source poolification (to
507 # avoid file overwrites).
509 x = ql[0]; # default to something sane in case we don't match any or have only one
513 old_file = i[0] + i[1];
514 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
515 actual_size = os.stat(old_file)[stat.ST_SIZE];
516 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
519 legacy_source_untouchable[i[3]] = "";
521 old_file = x[0] + x[1];
522 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
523 actual_size = os.stat(old_file)[stat.ST_SIZE];
526 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
528 if suite_type == "legacy" or suite_type == "legacy-mixed":
531 # Not there? Check in Incoming...
532 # [See comment above process_it() for explanation
533 # of why this is necessary...]
534 if os.access(dsc_file, os.R_OK) != 0:
535 files[dsc_file] = {};
536 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
537 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
538 files[dsc_file]["section"] = files[file]["section"];
539 files[dsc_file]["priority"] = files[file]["priority"];
540 files[dsc_file]["component"] = files[file]["component"];
541 files[dsc_file]["type"] = "orig.tar.gz";
545 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);
548 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming." % (file, dsc_file);
550 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
551 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
552 if actual_size != int(dsc_files[dsc_file]["size"]):
553 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
555 if string.find(reject_message, "Rejected:") != -1:
560 ###############################################################################
562 # Some cunning stunt broke dpkg-source in dpkg 1.8{,.1}; detect the
563 # resulting bad source packages and reject them.
565 # Even more amusingly the fix in 1.8.1.1 didn't actually fix the
566 # problem just changed the symptoms.
569 global dsc, dsc_files, reject_message, reprocess, orig_tar_id;
571 for filename in files.keys():
572 if files[filename]["type"] == "diff.gz":
573 file = gzip.GzipFile(filename, 'r');
574 for line in file.readlines():
575 if re_bad_diff.search(line):
576 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";
579 if string.find(reject_message, "Rejected:") != -1:
584 ###############################################################################
586 def check_md5sums ():
587 global reject_message;
589 for file in files.keys():
591 file_handle = utils.open_file(file,"r");
592 except utils.cant_open_exc:
595 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
596 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
598 def check_override ():
599 # Only check section & priority on sourceful uploads
600 if not changes["architecture"].has_key("source"):
604 for file in files.keys():
605 if not files[file].has_key("new") and (files[file]["type"] == "dsc" or files[file]["type"] == "deb"):
606 section = files[file]["section"];
607 override_section = files[file]["override section"];
608 if section != override_section and section != "-":
609 summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
610 if files[file]["type"] == "deb": # don't do priority for source
611 priority = files[file]["priority"];
612 override_priority = files[file]["override priority"];
613 if priority != override_priority and priority != "-":
614 summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
619 mail_message = """Return-Path: %s
622 Bcc: troup@auric.debian.org
623 Subject: %s override disparity
625 There are disparities between your recently installed upload and the
626 override file for the following file(s):
629 Either the package or the override file is incorrect. If you think
630 the override is correct and the package wrong please fix the package
631 so that this disparity is fixed in the next upload. If you feel the
632 override is incorrect then please reply to this mail and explain why.
635 Debian distribution maintenance software
637 (This message was generated automatically; if you believe that there
638 is a problem with it please contact the archive administrators by
639 mailing ftpmaster@debian.org)
640 """ % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes["source"], summary);
641 utils.send_mail (mail_message, "")
643 #####################################################################################################################
645 def action (changes_filename):
646 byhand = confirm = suites = summary = new = "";
648 # changes["distribution"] may not exist in corner cases
649 # (e.g. unreadable changes files)
650 if not changes.has_key("distribution"):
651 changes["distribution"] = {};
653 for suite in changes["distribution"].keys():
654 if Cnf.has_key("Suite::%s::Confirm"):
655 confirm = confirm + suite + ", "
656 suites = suites + suite + ", "
657 confirm = confirm[:-2]
660 for file in files.keys():
661 if files[file].has_key("byhand"):
663 summary = summary + file + " byhand\n"
664 elif files[file].has_key("new"):
666 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
667 if files[file].has_key("othercomponents"):
668 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
669 if files[file]["type"] == "deb":
670 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
672 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
673 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
674 summary = summary + file + "\n to " + destination + "\n"
676 short_summary = summary;
678 # This is for direport's benefit...
679 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
681 if confirm or byhand or new:
682 summary = summary + "Changes: " + f;
684 summary = summary + announce (short_summary, 0)
686 (prompt, answer) = ("", "XXX")
687 if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
690 if string.find(reject_message, "Rejected") != -1:
691 if time.time()-os.path.getmtime(changes_filename) < 86400:
692 print "SKIP (too new)\n" + reject_message,;
693 prompt = "[S]kip, Manual reject, Quit ?";
695 print "REJECT\n" + reject_message,;
696 prompt = "[R]eject, Manual reject, Skip, Quit ?";
697 if Cnf["Dinstall::Options::Automatic"]:
700 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
701 prompt = "[S]kip, New ack, Manual reject, Quit ?";
702 if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
705 print "BYHAND\n" + reject_message + summary,;
706 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
708 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
709 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
711 print "INSTALL\n" + reject_message + summary,;
712 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
713 if Cnf["Dinstall::Options::Automatic"]:
716 while string.find(prompt, answer) == -1:
718 answer = utils.our_raw_input()
719 m = re_default_answer.match(prompt)
722 answer = string.upper(answer[:1])
725 reject (changes_filename, "");
727 manual_reject (changes_filename);
729 install (changes_filename, summary, short_summary);
731 acknowledge_new (changes_filename, summary);
735 #####################################################################################################################
737 def install (changes_filename, summary, short_summary):
738 global install_count, install_bytes
740 # Stable uploads are a special case
741 if changes.has_key("stable upload"):
742 stable_install (changes_filename, summary, short_summary);
747 archive = utils.where_am_i();
749 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
750 projectB.query("BEGIN WORK");
752 # Add the .dsc file to the DB
753 for file in files.keys():
754 if files[file]["type"] == "dsc":
755 package = dsc["source"]
756 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
757 maintainer = dsc["maintainer"]
758 maintainer = string.replace(maintainer, "'", "\\'")
759 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
760 filename = files[file]["pool name"] + file;
761 dsc_location_id = files[file]["location id"];
762 if not files[file]["files id"]:
763 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
764 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
765 % (package, version, maintainer_id, files[file]["files id"]))
767 for suite in changes["distribution"].keys():
768 suite_id = db_access.get_suite_id(suite);
769 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
771 # Add the source files to the DB (files and dsc_files)
772 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
773 for dsc_file in dsc_files.keys():
774 filename = files[file]["pool name"] + dsc_file;
775 # If the .orig.tar.gz is already in the pool, it's
776 # files id is stored in dsc_files by check_dsc().
777 files_id = dsc_files[dsc_file].get("files id", None);
779 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
780 # FIXME: needs to check for -1/-2 and or handle exception
782 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
783 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
785 # Add the .deb files to the DB
786 for file in files.keys():
787 if files[file]["type"] == "deb":
788 package = files[file]["package"]
789 version = files[file]["version"]
790 maintainer = files[file]["maintainer"]
791 maintainer = string.replace(maintainer, "'", "\\'")
792 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
793 architecture = files[file]["architecture"]
794 architecture_id = db_access.get_architecture_id (architecture);
795 type = files[file]["dbtype"];
796 component = files[file]["component"]
797 source = files[file]["source"]
799 if string.find(source, "(") != -1:
800 m = utils.re_extract_src_version.match(source)
802 source_version = m.group(2)
803 if not source_version:
804 source_version = version
805 filename = files[file]["pool name"] + file;
806 if not files[file]["files id"]:
807 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
808 source_id = db_access.get_source_id (source, source_version);
810 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
811 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
813 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
814 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
815 for suite in changes["distribution"].keys():
816 suite_id = db_access.get_suite_id(suite);
817 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
819 # If the .orig.tar.gz is in a legacy directory we need to poolify
820 # it, so that apt-get source (and anything else that goes by the
821 # "Directory:" field in the Sources.gz file) works.
822 if orig_tar_id != None:
823 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));
826 # Is this an old upload superseded by a newer -sa upload? (See check_dsc() for details)
827 if legacy_source_untouchable.has_key(qid["files_id"]):
829 # First move the files to the new location
830 legacy_filename = qid["path"]+qid["filename"];
831 pool_location = utils.poolify (changes["source"], files[file]["component"]);
832 pool_filename = pool_location + os.path.basename(qid["filename"]);
833 destination = Cnf["Dir::PoolDir"] + pool_location
834 utils.move(legacy_filename, destination);
835 # Then Update the DB's files table
836 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
838 # Install the files into the pool
839 for file in files.keys():
840 if files[file].has_key("byhand"):
842 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
843 destdir = os.path.dirname(destination)
844 utils.move (file, destination)
845 install_bytes = install_bytes + float(files[file]["size"])
847 # Copy the .changes file across for suite which need it.
848 for suite in changes["distribution"].keys():
849 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
850 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
852 projectB.query("COMMIT WORK");
854 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
856 install_count = install_count + 1;
858 if not Cnf["Dinstall::Options::No-Mail"]:
859 mail_message = """Return-Path: %s
862 Bcc: troup@auric.debian.org
863 Subject: %s INSTALLED
869 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
870 utils.send_mail (mail_message, "")
871 announce (short_summary, 1)
874 #####################################################################################################################
876 def stable_install (changes_filename, summary, short_summary):
877 global install_count, install_bytes
879 print "Installing to stable."
881 archive = utils.where_am_i();
883 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
884 projectB.query("BEGIN WORK");
886 # Add the .dsc file to the DB
887 for file in files.keys():
888 if files[file]["type"] == "dsc":
889 package = dsc["source"]
890 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
891 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
894 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version));
896 source_id = ql[0][0];
897 suite_id = db_access.get_suite_id('proposed-updates');
898 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
899 suite_id = db_access.get_suite_id('stable');
900 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
902 # Add the .deb files to the DB
903 for file in files.keys():
904 if files[file]["type"] == "deb":
905 package = files[file]["package"]
906 version = files[file]["version"]
907 architecture = files[file]["architecture"]
908 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))
911 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture));
913 binary_id = ql[0][0];
914 suite_id = db_access.get_suite_id('proposed-updates');
915 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
916 suite_id = db_access.get_suite_id('stable');
917 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
919 projectB.query("COMMIT WORK");
921 utils.move (changes_filename, Cnf["Rhona::Morgue"] + os.path.basename(changes_filename));
923 # Update the Stable ChangeLog file
925 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
926 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
927 if os.path.exists(new_changelog_filename):
928 os.unlink (new_changelog_filename);
930 new_changelog = utils.open_file(new_changelog_filename, 'w');
931 for file in files.keys():
932 if files[file]["type"] == "deb":
933 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
934 elif re_issource.match(file) != None:
935 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
937 new_changelog.write("%s\n" % (file));
938 chop_changes = re_fdnic.sub("\n", changes["changes"]);
939 new_changelog.write(chop_changes + '\n\n');
940 if os.access(changelog_filename, os.R_OK) != 0:
941 changelog = utils.open_file(changelog_filename, 'r');
942 new_changelog.write(changelog.read());
943 new_changelog.close();
944 if os.access(changelog_filename, os.R_OK) != 0:
945 os.unlink(changelog_filename);
946 utils.move(new_changelog_filename, changelog_filename);
948 install_count = install_count + 1;
950 if not Cnf["Dinstall::Options::No-Mail"]:
951 mail_message = """Return-Path: %s
954 Bcc: troup@auric.debian.org
955 Subject: %s INSTALLED into stable
961 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
962 utils.send_mail (mail_message, "")
963 announce (short_summary, 1)
965 #####################################################################################################################
967 def reject (changes_filename, manual_reject_mail_filename):
970 base_changes_filename = os.path.basename(changes_filename);
971 reason_filename = re_changes.sub("reason", base_changes_filename);
972 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
974 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
976 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
977 except utils.cant_overwrite_exc:
978 sys.stderr.write("W: couldn't overwrite existing file '%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
980 for file in files.keys():
981 if os.path.exists(file):
983 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
984 except utils.cant_overwrite_exc:
985 sys.stderr.write("W: couldn't overwrite existing file '%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
988 # If this is not a manual rejection generate the .reason file and rejection mail message
989 if manual_reject_mail_filename == "":
990 if os.path.exists(reject_filename):
991 os.unlink(reject_filename);
992 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
993 os.write(fd, reject_message);
995 reject_mail_message = """From: %s
997 Bcc: troup@auric.debian.org
1002 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, reject_footer);
1003 else: # Have a manual rejection file to use
1004 reject_mail_message = ""; # avoid <undef>'s
1006 # Send the rejection mail if appropriate
1007 if not Cnf["Dinstall::Options::No-Mail"]:
1008 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
1010 ##################################################################
1012 def manual_reject (changes_filename):
1013 # Build up the rejection email
1014 user_email_address = string.replace(string.split(pwd.getpwuid(os.getuid())[4],',')[0], '.', '')
1015 user_email_address = user_email_address + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
1016 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
1018 reject_mail_message = """From: %s
1021 Bcc: troup@auric.debian.org
1022 Subject: %s REJECTED
1027 %s""" % (user_email_address, Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), manual_reject_message, reject_message, reject_footer)
1029 # Write the rejection email out as the <foo>.reason file
1030 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
1031 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
1032 if os.path.exists(reject_filename):
1033 os.unlink(reject_filename);
1034 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1035 os.write(fd, reject_mail_message);
1038 # If we weren't given one, spawn an editor so the user can add one in
1039 if manual_reject_message == "":
1040 result = os.system("vi +6 %s" % (reject_file))
1042 sys.stderr.write ("vi invocation failed for `%s'!" % (reject_file))
1045 # Then process it as if it were an automatic rejection
1046 reject (changes_filename, reject_filename)
1048 #####################################################################################################################
1050 def acknowledge_new (changes_filename, summary):
1053 changes_filename = os.path.basename(changes_filename);
1055 new_ack_new[changes_filename] = 1;
1057 if new_ack_old.has_key(changes_filename):
1058 print "Ack already sent.";
1061 print "Sending new ack.";
1062 if not Cnf["Dinstall::Options::No-Mail"]:
1063 new_ack_message = """Return-Path: %s
1066 Bcc: troup@auric.debian.org
1070 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes_filename, summary, new_ack_footer);
1071 utils.send_mail(new_ack_message,"");
1073 #####################################################################################################################
1075 def announce (short_summary, action):
1076 # Only do announcements for source uploads with a recent dpkg-dev installed
1077 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
1083 for dist in changes["distribution"].keys():
1084 list = Cnf.Find("Suite::%s::Announce" % (dist))
1085 if list == None or lists_done.has_key(list):
1087 lists_done[list] = 1
1088 summary = summary + "Announcing to %s\n" % (list)
1091 mail_message = """Return-Path: %s
1094 Bcc: troup@auric.debian.org
1095 Subject: Installed %s %s (%s)
1101 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], list, changes["source"], changes["version"], string.join(changes["architecture"].keys(), ' ' ),
1102 changes["filecontents"], short_summary)
1103 utils.send_mail (mail_message, "")
1105 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
1106 bugs = changes["closes"].keys()
1108 if dsc_name == changes["maintainername"]:
1109 summary = summary + "Closing bugs: "
1111 summary = summary + "%s " % (bug)
1113 mail_message = """Return-Path: %s
1115 To: %s-close@bugs.debian.org
1116 Bcc: troup@auric.debian.org
1117 Subject: Bug#%s: fixed in %s %s
1119 We believe that the bug you reported is fixed in the latest version of
1120 %s, which has been installed in the Debian FTP archive:
1122 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], bug, bug, changes["source"], changes["version"], changes["source"], short_summary)
1124 if changes["distribution"].has_key("stable"):
1125 mail_message = mail_message + """Note that this package is not part of the released stable Debian
1126 distribution. It may have dependencies on other unreleased software,
1127 or other instabilities. Please take care if you wish to install it.
1128 The update will eventually make its way into the next released Debian
1131 mail_message = mail_message + """A summary of the changes between this version and the previous one is
1134 Thank you for reporting the bug, which will now be closed. If you
1135 have further comments please address them to %s@bugs.debian.org,
1136 and the maintainer will reopen the bug report if appropriate.
1138 Debian distribution maintenance software
1140 %s (supplier of updated %s package)
1142 (This message was generated automatically at their request; if you
1143 believe that there is a problem with it please contact the archive
1144 administrators by mailing ftpmaster@debian.org)
1147 %s""" % (bug, changes["maintainer"], changes["source"], changes["filecontents"])
1149 utils.send_mail (mail_message, "")
1151 summary = summary + "Setting bugs to severity fixed: "
1152 control_message = ""
1154 summary = summary + "%s " % (bug)
1155 control_message = control_message + "severity %s fixed\n" % (bug)
1156 if action and control_message != "":
1157 mail_message = """Return-Path: %s
1159 To: control@bugs.debian.org
1160 Bcc: troup@auric.debian.org, %s
1161 Subject: Fixed in NMU of %s %s
1166 This message was generated automatically in response to a
1167 non-maintainer upload. The .changes file follows.
1170 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes["maintainer822"], changes["source"], changes["version"], control_message, changes["filecontents"])
1171 utils.send_mail (mail_message, "")
1172 summary = summary + "\n"
1176 ###############################################################################
1178 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1179 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1180 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1181 # processed it during it's checks of -2. If -1 has been deleted or
1182 # otherwise not checked by da-install, the .orig.tar.gz will not have
1183 # been checked at all. To get round this, we force the .orig.tar.gz
1184 # into the .changes structure and reprocess the .changes file.
1186 def process_it (changes_file):
1187 global reprocess, orig_tar_id, changes, dsc, dsc_files, files;
1191 # Reset some globals
1197 legacy_source_untouchable = {};
1199 # Absolutize the filename to avoid the requirement of being in the
1200 # same directory as the .changes file.
1201 changes_file = os.path.abspath(changes_file);
1203 # And since handling of installs to stable munges with the CWD;
1204 # save and restore it.
1207 check_signature (changes_file);
1208 check_changes (changes_file);
1216 action(changes_file);
1221 ###############################################################################
1224 global Cnf, projectB, reject_message, install_bytes, new_ack_old
1228 Cnf = apt_pkg.newConfiguration();
1229 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1231 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1232 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1233 ('h',"help","Dinstall::Options::Help"),
1234 ('k',"ack-new","Dinstall::Options::Ack-New"),
1235 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1236 ('n',"no-action","Dinstall::Options::No-Action"),
1237 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1238 ('r',"no-version-check", "Dinstall::Options::No-Version-Check"),
1239 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1240 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1241 ('v',"version","Dinstall::Options::Version")];
1243 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1245 if Cnf["Dinstall::Options::Help"]:
1248 if Cnf["Dinstall::Options::Version"]:
1249 print "katie version 0.0000000000";
1252 postgresql_user = None; # Default == Connect as user running program.
1254 # -n/--dry-run invalidates some other options which would involve things happening
1255 if Cnf["Dinstall::Options::No-Action"]:
1256 Cnf["Dinstall::Options::Automatic"] = ""
1257 Cnf["Dinstall::Options::Ack-New"] = ""
1258 postgresql_user = Cnf["DB::ROUser"];
1260 projectB = pg.connect('projectb', Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1262 db_access.init(Cnf, projectB);
1264 # Check that we aren't going to clash with the daily cron job
1266 if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1267 sys.stderr.write("Archive maintenance in progress. Try again later.\n");
1270 # Obtain lock if not in no-action mode
1272 if not Cnf["Dinstall::Options::No-Action"]:
1273 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1274 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1276 # Read in the list of already-acknowledged NEW packages
1277 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1279 for line in new_ack_list.readlines():
1280 new_ack_old[line[:-1]] = 1;
1281 new_ack_list.close();
1283 # Process the changes files
1284 for changes_file in changes_files:
1286 print "\n" + changes_file;
1287 process_it (changes_file);
1291 if install_count > 1:
1293 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1295 # Write out the list of already-acknowledged NEW packages
1296 if Cnf["Dinstall::Options::Ack-New"]:
1297 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1298 for i in new_ack_new.keys():
1299 new_ack_list.write(i+'\n')
1300 new_ack_list.close()
1303 if __name__ == '__main__':