3 # Installs Debian packaes
4 # Copyright (C) 2000 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.23 2001-01-27 00:09:36 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 turn on debugging
95 -h, --help show this help and exit.
96 -k, --ack-new acknowledge new packages !! for cron.daily only !!
97 -m, --manual-reject=MSG manual reject with `msg'
98 -n, --no-action don't do anything
99 -p, --no-lock don't check lockfile !! for cron.daily only !!
100 -u, --distribution=DIST override distribution to `dist'
101 -v, --version display the version number and exit"""
104 def check_signature (filename):
105 global reject_message
107 (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))
109 reject_message = "Rejected: GPG signature check failed on `%s'.\n%s\n" % (os.path.basename(filename), output)
113 #####################################################################################################################
115 # See if a given package is in the override table
117 def in_override_p (package, component, suite, binary_type, file):
120 if binary_type == "": # must be source
125 # Override suite name; used for example with proposed-updates
126 if Cnf.Find("Suite::%s::OverrideSuite" % (suite)) != "":
127 suite = Cnf["Suite::%s::OverrideSuite" % (suite)];
129 # Avoid <undef> on unknown distributions
130 suite_id = db_access.get_suite_id(suite);
133 component_id = db_access.get_component_id(component);
134 type_id = db_access.get_override_type_id(type);
136 # FIXME: nasty non-US speficic hack
137 if string.lower(component[:7]) == "non-us/":
138 component = component[7:];
140 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"
141 % (package, suite_id, component_id, type_id));
142 result = q.getresult();
143 # If checking for a source package fall back on the binary override type
144 if type == "dsc" and not result:
145 type_id = db_access.get_override_type_id("deb");
146 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"
147 % (package, suite_id, component_id, type_id));
148 result = q.getresult();
150 # Remember the section and priority so we can check them later if appropriate
152 files[file]["override section"] = result[0][0];
153 files[file]["override priority"] = result[0][1];
157 #####################################################################################################################
159 def check_changes(filename):
160 global reject_message, changes, files
162 # Parse the .changes field into a dictionary [FIXME - need to trap errors, pass on to reject_message etc.]
164 changes = utils.parse_changes(filename)
165 except utils.cant_open_exc:
166 reject_message = "Rejected: can't read changes file '%s'.\n" % (filename)
168 except utils.changes_parse_error_exc, line:
169 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
170 changes["maintainer822"] = Cnf["Dinstall::MyEmailAddress"];
173 # Parse the Files field from the .changes into another dictionary [FIXME need to trap errors as above]
175 files = utils.build_file_list(changes, "");
176 except utils.changes_parse_error_exc, line:
177 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line);
179 # Check for mandatory fields
180 for i in ("source", "binary", "architecture", "version", "distribution","maintainer", "files"):
181 if not changes.has_key(i):
182 reject_message = "Rejected: Missing field `%s' in changes file.\n" % (i)
183 return 0 # Avoid <undef> errors during later tests
185 # Fix the Maintainer: field to be RFC822 compatible
186 (changes["maintainer822"], changes["maintainername"], changes["maintaineremail"]) = utils.fix_maintainer (changes["maintainer"])
188 # Override the Distribution: field if appropriate
189 if Cnf["Dinstall::Options::Override-Distribution"] != "":
190 reject_message = reject_message + "Warning: Distribution was overriden from %s to %s.\n" % (changes["distribution"], Cnf["Dinstall::Options::Override-Distribution"])
191 changes["distribution"] = Cnf["Dinstall::Options::Override-Distribution"]
193 # Split multi-value fields into a lower-level dictionary
194 for i in ("architecture", "distribution", "binary", "closes"):
195 o = changes.get(i, "")
199 for j in string.split(o):
202 # Ensure all the values in Closes: are numbers
203 if changes.has_key("closes"):
204 for i in changes["closes"].keys():
205 if re_isanum.match (i) == None:
206 reject_message = reject_message + "Rejected: `%s' from Closes field isn't a number.\n" % (i)
208 # Map frozen to unstable if frozen doesn't exist
209 if changes["distribution"].has_key("frozen") and not Cnf.has_key("Suite::Frozen"):
210 del changes["distribution"]["frozen"]
211 changes["distribution"]["unstable"] = 1;
212 reject_message = reject_message + "Mapping frozen to unstable.\n"
214 # Map testing to unstable
215 if changes["distribution"].has_key("testing"):
216 del changes["distribution"]["testing"]
217 changes["distribution"]["unstable"] = 1;
218 reject_message = reject_message + "Mapping testing to unstable.\n"
220 # Ensure target distributions exist
221 for i in changes["distribution"].keys():
222 if not Cnf.has_key("Suite::%s" % (i)):
223 reject_message = reject_message + "Rejected: Unknown distribution `%s'.\n" % (i)
225 # Ensure there _is_ a target distribution
226 if changes["distribution"].keys() == []:
227 reject_message = reject_message + "Rejected: huh? Distribution field is empty in changes file.\n";
229 # Map unreleased arches from stable to unstable
230 if changes["distribution"].has_key("stable"):
231 for i in changes["architecture"].keys():
232 if not Cnf.has_key("Suite::Stable::Architectures::%s" % (i)):
233 reject_message = reject_message + "Mapping stable to unstable for unreleased arch `%s'.\n" % (i)
234 del changes["distribution"]["stable"]
235 changes["distribution"]["unstable"] = 1;
237 # Map arches not being released from frozen to unstable
238 if changes["distribution"].has_key("frozen"):
239 for i in changes["architecture"].keys():
240 if not Cnf.has_key("Suite::Frozen::Architectures::%s" % (i)):
241 reject_message = reject_message + "Mapping frozen to unstable for non-releasing arch `%s'.\n" % (i)
242 del changes["distribution"]["frozen"]
243 changes["distribution"]["unstable"] = 1;
245 # Handle uploads to stable
246 if changes["distribution"].has_key("stable"):
247 # If running from within proposed-updates; assume an install to stable
248 if string.find(os.getcwd(), 'proposed-updates') != -1:
249 # FIXME: should probably remove anything that != stable
250 for i in ("frozen", "unstable"):
251 if changes["distribution"].has_key(i):
252 reject_message = reject_message + "Removing %s from distribution list.\n"
253 del changes["distribution"][i]
254 changes["stable upload"] = 1;
255 # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool
256 file = files.keys()[0];
257 if os.access(file, os.R_OK) == 0:
258 pool_dir = Cnf["Dir::PoolDir"] + '/' + utils.poolify(changes["source"], files[file]["component"]);
260 # Otherwise (normal case) map stable to updates
262 reject_message = reject_message + "Mapping stable to updates.\n";
263 del changes["distribution"]["stable"];
264 changes["distribution"]["proposed-updates"] = 1;
266 # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
267 changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
268 changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
270 if string.find(reject_message, "Rejected:") != -1:
276 global reject_message
278 archive = utils.where_am_i();
280 for file in files.keys():
281 # Check the file is readable
282 if os.access(file,os.R_OK) == 0:
283 reject_message = reject_message + "Rejected: Can't read `%s'.\n" % (file)
284 files[file]["type"] = "unreadable";
286 # If it's byhand skip remaining checks
287 if files[file]["section"] == "byhand":
288 files[file]["byhand"] = 1;
289 files[file]["type"] = "byhand";
290 # Checks for a binary package...
291 elif re_isadeb.match(file) != None:
292 files[file]["type"] = "deb";
294 # Extract package information using dpkg-deb
296 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
298 reject_message = reject_message + "Rejected: %s: debExtractControl() raised %s.\n" % (file, sys.exc_type);
299 # Can't continue, none of the checks on control would work.
302 # Check for mandatory fields
303 if control.Find("Package") == None:
304 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
305 if control.Find("Architecture") == None:
306 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
307 if control.Find("Version") == None:
308 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
310 # Ensure the package name matches the one give in the .changes
311 if not changes["binary"].has_key(control.Find("Package", "")):
312 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
314 # Validate the architecture
315 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
316 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
318 # Check the architecture matches the one given in the .changes
319 if not changes["architecture"].has_key(control.Find("Architecture", "")):
320 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
321 # Check the section & priority match those given in the .changes (non-fatal)
322 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
323 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"])
324 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
325 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"])
327 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
329 files[file]["package"] = control.Find("Package");
330 files[file]["architecture"] = control.Find("Architecture");
331 files[file]["version"] = control.Find("Version");
332 files[file]["maintainer"] = control.Find("Maintainer", "");
333 if file[-5:] == ".udeb":
334 files[file]["dbtype"] = "udeb";
335 elif file[-4:] == ".deb":
336 files[file]["dbtype"] = "deb";
338 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
339 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
340 files[file]["source"] = control.Find("Source", "");
341 if files[file]["source"] == "":
342 files[file]["source"] = files[file]["package"];
343 # Checks for a source package...
345 m = re_issource.match(file)
347 files[file]["package"] = m.group(1)
348 files[file]["version"] = m.group(2)
349 files[file]["type"] = m.group(3)
351 # Ensure the source package name matches the Source filed in the .changes
352 if changes["source"] != files[file]["package"]:
353 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
355 # Ensure the source version matches the version in the .changes file
356 if files[file]["type"] == "orig.tar.gz":
357 changes_version = changes["chopversion2"]
359 changes_version = changes["chopversion"]
360 if changes_version != files[file]["version"]:
361 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
363 # Ensure the .changes lists source in the Architecture field
364 if not changes["architecture"].has_key("source"):
365 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
367 # Check the signature of a .dsc file
368 if files[file]["type"] == "dsc":
369 check_signature(file)
371 files[file]["fullname"] = file
373 # Not a binary or source package? Assume byhand...
375 files[file]["byhand"] = 1;
376 files[file]["type"] = "byhand";
378 files[file]["oldfiles"] = {}
379 for suite in changes["distribution"].keys():
381 if files[file].has_key("byhand"):
384 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
385 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
388 # See if the package is NEW
389 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
390 files[file]["new"] = 1
392 # Find any old binary packages
393 if files[file]["type"] == "deb":
394 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"
395 % (files[file]["package"], suite, files[file]["architecture"]))
396 oldfiles = q.dictresult()
397 for oldfile in oldfiles:
398 files[file]["oldfiles"][suite] = oldfile
399 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
400 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
401 reject_message = reject_message + "Rejected: %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
402 # Check for existing copies of the file
403 if not changes.has_key("stable upload"):
404 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"]))
405 if q.getresult() != []:
406 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
408 # Find any old .dsc files
409 elif files[file]["type"] == "dsc":
410 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"
411 % (files[file]["package"], suite))
412 oldfiles = q.dictresult()
413 if len(oldfiles) >= 1:
414 files[file]["oldfiles"][suite] = oldfiles[0]
416 # Validate the component
417 component = files[file]["component"];
418 component_id = db_access.get_component_id(component);
419 if component_id == -1:
420 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
423 # Check the md5sum & size against existing files (if any)
424 location = Cnf["Dir::PoolDir"];
425 files[file]["location id"] = db_access.get_location_id (location, component, archive);
427 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
428 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
430 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
432 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
433 files[file]["files id"] = files_id
435 # Check for packages that have moved from one component to another
436 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
437 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
440 if string.find(reject_message, "Rejected:") != -1:
445 ###############################################################################
448 global dsc, dsc_files, reject_message, reprocess, orig_tar_id, legacy_source_untouchable;
450 for file in files.keys():
451 if files[file]["type"] == "dsc":
453 dsc = utils.parse_changes(file)
454 except utils.cant_open_exc:
455 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (filename)
457 except utils.changes_parse_error_exc, line:
458 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
461 dsc_files = utils.build_file_list(dsc, 1)
462 except utils.no_files_exc:
463 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
465 except utils.changes_parse_error_exc, line:
466 reject_message = "Rejected: error parsing .dsc file '%s', can't grok: %s.\n" % (filename, line);
469 # Try and find all files mentioned in the .dsc. This has
470 # to work harder to cope with the multiple possible
471 # locations of an .orig.tar.gz.
472 for dsc_file in dsc_files.keys():
473 if files.has_key(dsc_file):
474 actual_md5 = files[dsc_file]["md5sum"];
475 actual_size = int(files[dsc_file]["size"]);
476 found = "%s in incoming" % (dsc_file)
477 # Check the file does not already exist in the archive
478 if not changes.has_key("stable upload"):
479 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));
481 # "It has not broken them. It has fixed a
482 # brokenness. Your crappy hack exploited a
483 # bug in the old dinstall.
485 # "(Come on! I thought it was always obvious
486 # that one just doesn't release different
487 # files with the same name and version.)"
488 # -- ajk@ on d-devel@l.d.o
490 if q.getresult() != []:
491 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
492 elif dsc_file[-12:] == ".orig.tar.gz":
494 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));
499 # Unfortunately, we make get more than one match
500 # here if, for example, the package was in potato
501 # but had a -sa upload in woody. So we need to a)
502 # choose the right one and b) mark all wrong ones
503 # as excluded from the source poolification (to
504 # avoid file overwrites).
506 x = ql[0]; # default to something sane in case we don't match any or have only one
510 old_file = i[0] + i[1];
511 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
512 actual_size = os.stat(old_file)[stat.ST_SIZE];
513 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
516 legacy_source_untouchable[i[3]] = "";
518 old_file = x[0] + x[1];
519 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
520 actual_size = os.stat(old_file)[stat.ST_SIZE];
523 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
525 if suite_type == "legacy" or suite_type == "legacy-mixed":
528 # Not there? Check in Incoming...
529 # [See comment above process_it() for explanation
530 # of why this is necessary...]
531 if os.access(dsc_file, os.R_OK) != 0:
532 files[dsc_file] = {};
533 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
534 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
535 files[dsc_file]["section"] = files[file]["section"];
536 files[dsc_file]["priority"] = files[file]["priority"];
537 files[dsc_file]["component"] = files[file]["component"];
538 files[dsc_file]["type"] = "orig.tar.gz";
542 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);
545 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming.\n" % (file, dsc_file);
547 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
548 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
549 if actual_size != int(dsc_files[dsc_file]["size"]):
550 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
552 if string.find(reject_message, "Rejected:") != -1:
557 ###############################################################################
559 # Some cunning stunt broke dpkg-source in dpkg 1.8{,.1}; detect the
560 # resulting bad source packages and reject them.
562 # Even more amusingly the fix in 1.8.1.1 didn't actually fix the
563 # problem just changed the symptoms.
566 global dsc, dsc_files, reject_message, reprocess, orig_tar_id;
568 for filename in files.keys():
569 if files[filename]["type"] == "diff.gz":
570 file = gzip.GzipFile(filename, 'r');
571 for line in file.readlines():
572 if re_bad_diff.search(line):
573 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";
576 if string.find(reject_message, "Rejected:") != -1:
581 ###############################################################################
583 def check_md5sums ():
584 global reject_message;
586 for file in files.keys():
588 file_handle = utils.open_file(file,"r");
589 except utils.cant_open_exc:
592 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
593 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
595 def check_override ():
596 # Only check section & priority on sourceful uploads
597 if not changes["architecture"].has_key("source"):
601 for file in files.keys():
602 if not files[file].has_key("new") and (files[file]["type"] == "dsc" or files[file]["type"] == "deb"):
603 section = files[file]["section"];
604 override_section = files[file]["override section"];
605 if section != override_section and section != "-":
606 # Ignore this; it's a common mistake and not worth whining about
607 if section == "non-US/main" and override_section == "non-US":
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));
985 sys.stderr.write("W: couldn't reject file '%s' [Got %s].\n" % (file, sys.exc_type);
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;
1189 # Reset some globals
1196 legacy_source_untouchable = {};
1197 reject_message = "";
1200 # Absolutize the filename to avoid the requirement of being in the
1201 # same directory as the .changes file.
1202 changes_file = os.path.abspath(changes_file);
1204 # And since handling of installs to stable munges with the CWD;
1205 # save and restore it.
1208 check_signature (changes_file);
1209 check_changes (changes_file);
1217 action(changes_file);
1222 ###############################################################################
1225 global Cnf, projectB, reject_message, install_bytes, new_ack_old
1229 Cnf = apt_pkg.newConfiguration();
1230 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1232 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1233 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1234 ('h',"help","Dinstall::Options::Help"),
1235 ('k',"ack-new","Dinstall::Options::Ack-New"),
1236 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1237 ('n',"no-action","Dinstall::Options::No-Action"),
1238 ('p',"no-lock", "Dinstall::Options::No-Lock"),
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:
1285 print "\n" + changes_file;
1286 process_it (changes_file);
1290 if install_count > 1:
1292 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1294 # Write out the list of already-acknowledged NEW packages
1295 if Cnf["Dinstall::Options::Ack-New"]:
1296 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1297 for i in new_ack_new.keys():
1298 new_ack_list.write(i+'\n')
1299 new_ack_list.close()
1302 if __name__ == '__main__':