3 # Installs Debian packaes
4 # Copyright (C) 2000 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.26 2001-01-31 03:36: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, 0)
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 # Override the Distribution: field if appropriate
186 if Cnf["Dinstall::Options::Override-Distribution"] != "":
187 reject_message = reject_message + "Warning: Distribution was overriden from %s to %s.\n" % (changes["distribution"], Cnf["Dinstall::Options::Override-Distribution"])
188 changes["distribution"] = Cnf["Dinstall::Options::Override-Distribution"]
190 # Split multi-value fields into a lower-level dictionary
191 for i in ("architecture", "distribution", "binary", "closes"):
192 o = changes.get(i, "")
196 for j in string.split(o):
199 # Fix the Maintainer: field to be RFC822 compatible
200 (changes["maintainer822"], changes["maintainername"], changes["maintaineremail"]) = utils.fix_maintainer (changes["maintainer"])
202 # Fix the Changed-By: field to be RFC822 compatible; if it exists.
203 (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]) = utils.fix_maintainer(changes.get("changed-by",""));
205 # For source uploads the Changed-By field wins; otherwise Maintainer wins.
206 if changes["architecture"].has_key("source"):
207 changes["uploader822"] = "To: %s\nCc: %s" % (changes["changedby822"], changes["maintainer822"]);
208 # changes["uploadername"], changes["uploaderemail"]) = (changes["changedby822"], changes["changedbyname"], changes["changedbyemail"]);
210 # Ensure all the values in Closes: are numbers
211 if changes.has_key("closes"):
212 for i in changes["closes"].keys():
213 if re_isanum.match (i) == None:
214 reject_message = reject_message + "Rejected: `%s' from Closes field isn't a number.\n" % (i)
216 # Map frozen to unstable if frozen doesn't exist
217 if changes["distribution"].has_key("frozen") and not Cnf.has_key("Suite::Frozen"):
218 del changes["distribution"]["frozen"]
219 changes["distribution"]["unstable"] = 1;
220 reject_message = reject_message + "Mapping frozen to unstable.\n"
222 # Map testing to unstable
223 if changes["distribution"].has_key("testing"):
224 del changes["distribution"]["testing"]
225 changes["distribution"]["unstable"] = 1;
226 reject_message = reject_message + "Mapping testing to unstable.\n"
228 # Ensure target distributions exist
229 for i in changes["distribution"].keys():
230 if not Cnf.has_key("Suite::%s" % (i)):
231 reject_message = reject_message + "Rejected: Unknown distribution `%s'.\n" % (i)
233 # Ensure there _is_ a target distribution
234 if changes["distribution"].keys() == []:
235 reject_message = reject_message + "Rejected: huh? Distribution field is empty in changes file.\n";
237 # Map unreleased arches from stable to unstable
238 if changes["distribution"].has_key("stable"):
239 for i in changes["architecture"].keys():
240 if not Cnf.has_key("Suite::Stable::Architectures::%s" % (i)):
241 reject_message = reject_message + "Mapping stable to unstable for unreleased arch `%s'.\n" % (i)
242 del changes["distribution"]["stable"]
243 changes["distribution"]["unstable"] = 1;
245 # Map arches not being released from frozen to unstable
246 if changes["distribution"].has_key("frozen"):
247 for i in changes["architecture"].keys():
248 if not Cnf.has_key("Suite::Frozen::Architectures::%s" % (i)):
249 reject_message = reject_message + "Mapping frozen to unstable for non-releasing arch `%s'.\n" % (i)
250 del changes["distribution"]["frozen"]
251 changes["distribution"]["unstable"] = 1;
253 # Handle uploads to stable
254 if changes["distribution"].has_key("stable"):
255 # If running from within proposed-updates; assume an install to stable
256 if string.find(os.getcwd(), 'proposed-updates') != -1:
257 # FIXME: should probably remove anything that != stable
258 for i in ("frozen", "unstable"):
259 if changes["distribution"].has_key(i):
260 reject_message = reject_message + "Removing %s from distribution list.\n"
261 del changes["distribution"][i]
262 changes["stable upload"] = 1;
263 # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool
264 file = files.keys()[0];
265 if os.access(file, os.R_OK) == 0:
266 pool_dir = Cnf["Dir::PoolDir"] + '/' + utils.poolify(changes["source"], files[file]["component"]);
268 # Otherwise (normal case) map stable to updates
270 reject_message = reject_message + "Mapping stable to updates.\n";
271 del changes["distribution"]["stable"];
272 changes["distribution"]["proposed-updates"] = 1;
274 # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
275 changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
276 changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
278 if string.find(reject_message, "Rejected:") != -1:
284 global reject_message
286 archive = utils.where_am_i();
288 for file in files.keys():
289 # Check the file is readable
290 if os.access(file,os.R_OK) == 0:
291 reject_message = reject_message + "Rejected: Can't read `%s'.\n" % (file)
292 files[file]["type"] = "unreadable";
294 # If it's byhand skip remaining checks
295 if files[file]["section"] == "byhand":
296 files[file]["byhand"] = 1;
297 files[file]["type"] = "byhand";
298 # Checks for a binary package...
299 elif re_isadeb.match(file) != None:
300 files[file]["type"] = "deb";
302 # Extract package information using dpkg-deb
304 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
306 reject_message = reject_message + "Rejected: %s: debExtractControl() raised %s.\n" % (file, sys.exc_type);
307 # Can't continue, none of the checks on control would work.
310 # Check for mandatory fields
311 if control.Find("Package") == None:
312 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
313 if control.Find("Architecture") == None:
314 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
315 if control.Find("Version") == None:
316 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
318 # Ensure the package name matches the one give in the .changes
319 if not changes["binary"].has_key(control.Find("Package", "")):
320 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
322 # Validate the architecture
323 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
324 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
326 # Check the architecture matches the one given in the .changes
327 if not changes["architecture"].has_key(control.Find("Architecture", "")):
328 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
329 # Check the section & priority match those given in the .changes (non-fatal)
330 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
331 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"])
332 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
333 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"])
335 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
337 files[file]["package"] = control.Find("Package");
338 files[file]["architecture"] = control.Find("Architecture");
339 files[file]["version"] = control.Find("Version");
340 files[file]["maintainer"] = control.Find("Maintainer", "");
341 if file[-5:] == ".udeb":
342 files[file]["dbtype"] = "udeb";
343 elif file[-4:] == ".deb":
344 files[file]["dbtype"] = "deb";
346 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
347 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
348 files[file]["source"] = control.Find("Source", "");
349 if files[file]["source"] == "":
350 files[file]["source"] = files[file]["package"];
351 # Checks for a source package...
353 m = re_issource.match(file)
355 files[file]["package"] = m.group(1)
356 files[file]["version"] = m.group(2)
357 files[file]["type"] = m.group(3)
359 # Ensure the source package name matches the Source filed in the .changes
360 if changes["source"] != files[file]["package"]:
361 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
363 # Ensure the source version matches the version in the .changes file
364 if files[file]["type"] == "orig.tar.gz":
365 changes_version = changes["chopversion2"]
367 changes_version = changes["chopversion"]
368 if changes_version != files[file]["version"]:
369 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
371 # Ensure the .changes lists source in the Architecture field
372 if not changes["architecture"].has_key("source"):
373 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
375 # Check the signature of a .dsc file
376 if files[file]["type"] == "dsc":
377 check_signature(file)
379 files[file]["fullname"] = file
381 # Not a binary or source package? Assume byhand...
383 files[file]["byhand"] = 1;
384 files[file]["type"] = "byhand";
386 files[file]["oldfiles"] = {}
387 for suite in changes["distribution"].keys():
389 if files[file].has_key("byhand"):
392 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
393 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
396 # See if the package is NEW
397 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype",""), file):
398 files[file]["new"] = 1
400 # Find any old binary packages
401 if files[file]["type"] == "deb":
402 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"
403 % (files[file]["package"], suite, files[file]["architecture"]))
404 oldfiles = q.dictresult()
405 for oldfile in oldfiles:
406 files[file]["oldfiles"][suite] = oldfile
407 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
408 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
409 reject_message = reject_message + "Rejected: %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
410 # Check for existing copies of the file
411 if not changes.has_key("stable upload"):
412 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"]))
413 if q.getresult() != []:
414 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
416 # Find any old .dsc files
417 elif files[file]["type"] == "dsc":
418 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"
419 % (files[file]["package"], suite))
420 oldfiles = q.dictresult()
421 if len(oldfiles) >= 1:
422 files[file]["oldfiles"][suite] = oldfiles[0]
424 # Validate the component
425 component = files[file]["component"];
426 component_id = db_access.get_component_id(component);
427 if component_id == -1:
428 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
431 # Check the md5sum & size against existing files (if any)
432 location = Cnf["Dir::PoolDir"];
433 files[file]["location id"] = db_access.get_location_id (location, component, archive);
435 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
436 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
438 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
440 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
441 files[file]["files id"] = files_id
443 # Check for packages that have moved from one component to another
444 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
445 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
448 if string.find(reject_message, "Rejected:") != -1:
453 ###############################################################################
456 global dsc, dsc_files, reject_message, reprocess, orig_tar_id, legacy_source_untouchable;
458 for file in files.keys():
459 if files[file]["type"] == "dsc":
461 dsc = utils.parse_changes(file, 1)
462 except utils.cant_open_exc:
463 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (filename)
465 except utils.changes_parse_error_exc, line:
466 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
468 except utils.invalid_dsc_format_exc, line:
469 reject_message = reject_message + "Rejected: syntax error in .dsc file '%s', line %s.\n" % (filename, line)
472 dsc_files = utils.build_file_list(dsc, 1)
473 except utils.no_files_exc:
474 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
476 except utils.changes_parse_error_exc, line:
477 reject_message = "Rejected: error parsing .dsc file '%s', can't grok: %s.\n" % (filename, line);
480 # Try and find all files mentioned in the .dsc. This has
481 # to work harder to cope with the multiple possible
482 # locations of an .orig.tar.gz.
483 for dsc_file in dsc_files.keys():
484 if files.has_key(dsc_file):
485 actual_md5 = files[dsc_file]["md5sum"];
486 actual_size = int(files[dsc_file]["size"]);
487 found = "%s in incoming" % (dsc_file)
488 # Check the file does not already exist in the archive
489 if not changes.has_key("stable upload"):
490 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));
492 # "It has not broken them. It has fixed a
493 # brokenness. Your crappy hack exploited a
494 # bug in the old dinstall.
496 # "(Come on! I thought it was always obvious
497 # that one just doesn't release different
498 # files with the same name and version.)"
499 # -- ajk@ on d-devel@l.d.o
501 if q.getresult() != []:
502 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
503 elif dsc_file[-12:] == ".orig.tar.gz":
505 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));
509 # Unfortunately, we make get more than one match
510 # here if, for example, the package was in potato
511 # but had a -sa upload in woody. So we need to a)
512 # choose the right one and b) mark all wrong ones
513 # as excluded from the source poolification (to
514 # avoid file overwrites).
516 x = ql[0]; # default to something sane in case we don't match any or have only one
520 old_file = i[0] + i[1];
521 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
522 actual_size = os.stat(old_file)[stat.ST_SIZE];
523 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
526 legacy_source_untouchable[i[3]] = "";
528 old_file = x[0] + x[1];
529 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
530 actual_size = os.stat(old_file)[stat.ST_SIZE];
533 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
535 if suite_type == "legacy" or suite_type == "legacy-mixed":
538 # Not there? Check in Incoming...
539 # [See comment above process_it() for explanation
540 # of why this is necessary...]
541 if os.access(dsc_file, os.R_OK) != 0:
542 files[dsc_file] = {};
543 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
544 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
545 files[dsc_file]["section"] = files[file]["section"];
546 files[dsc_file]["priority"] = files[file]["priority"];
547 files[dsc_file]["component"] = files[file]["component"];
548 files[dsc_file]["type"] = "orig.tar.gz";
552 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);
555 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming.\n" % (file, dsc_file);
557 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
558 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
559 if actual_size != int(dsc_files[dsc_file]["size"]):
560 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
562 if string.find(reject_message, "Rejected:") != -1:
567 ###############################################################################
569 # Some cunning stunt broke dpkg-source in dpkg 1.8{,.1}; detect the
570 # resulting bad source packages and reject them.
572 # Even more amusingly the fix in 1.8.1.1 didn't actually fix the
573 # problem just changed the symptoms.
576 global dsc, dsc_files, reject_message, reprocess, orig_tar_id;
578 for filename in files.keys():
579 if files[filename]["type"] == "diff.gz":
580 file = gzip.GzipFile(filename, 'r');
581 for line in file.readlines():
582 if re_bad_diff.search(line):
583 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";
586 if string.find(reject_message, "Rejected:") != -1:
591 ###############################################################################
593 def check_md5sums ():
594 global reject_message;
596 for file in files.keys():
598 file_handle = utils.open_file(file,"r");
599 except utils.cant_open_exc:
602 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
603 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
605 def check_override ():
606 # Only check section & priority on sourceful uploads
607 if not changes["architecture"].has_key("source"):
611 for file in files.keys():
612 if not files[file].has_key("new") and (files[file]["type"] == "dsc" or files[file]["type"] == "deb"):
613 section = files[file]["section"];
614 override_section = files[file]["override section"];
615 if section != override_section and section != "-":
616 # Ignore this; it's a common mistake and not worth whining about
617 if string.lower(section) == "non-us/main" and string.lower(override_section) == "non-us":
619 summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
620 if files[file]["type"] == "deb": # don't do priority for source
621 priority = files[file]["priority"];
622 override_priority = files[file]["override priority"];
623 if priority != override_priority and priority != "-":
624 summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
629 mail_message = """Return-Path: %s
632 Bcc: troup@auric.debian.org
633 Subject: %s override disparity
635 There are disparities between your recently installed upload and the
636 override file for the following file(s):
639 Either the package or the override file is incorrect. If you think
640 the override is correct and the package wrong please fix the package
641 so that this disparity is fixed in the next upload. If you feel the
642 override is incorrect then please reply to this mail and explain why.
645 Debian distribution maintenance software
647 (This message was generated automatically; if you believe that there
648 is a problem with it please contact the archive administrators by
649 mailing ftpmaster@debian.org)
650 """ % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes["source"], summary);
651 utils.send_mail (mail_message, "")
653 #####################################################################################################################
655 def action (changes_filename):
656 byhand = confirm = suites = summary = new = "";
658 # changes["distribution"] may not exist in corner cases
659 # (e.g. unreadable changes files)
660 if not changes.has_key("distribution"):
661 changes["distribution"] = {};
663 for suite in changes["distribution"].keys():
664 if Cnf.has_key("Suite::%s::Confirm"):
665 confirm = confirm + suite + ", "
666 suites = suites + suite + ", "
667 confirm = confirm[:-2]
670 for file in files.keys():
671 if files[file].has_key("byhand"):
673 summary = summary + file + " byhand\n"
674 elif files[file].has_key("new"):
676 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
677 if files[file].has_key("othercomponents"):
678 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
679 if files[file]["type"] == "deb":
680 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
682 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
683 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
684 summary = summary + file + "\n to " + destination + "\n"
686 short_summary = summary;
688 # This is for direport's benefit...
689 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
691 if confirm or byhand or new:
692 summary = summary + "Changes: " + f;
694 summary = summary + announce (short_summary, 0)
696 (prompt, answer) = ("", "XXX")
697 if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
700 if string.find(reject_message, "Rejected") != -1:
701 if time.time()-os.path.getmtime(changes_filename) < 86400:
702 print "SKIP (too new)\n" + reject_message,;
703 prompt = "[S]kip, Manual reject, Quit ?";
705 print "REJECT\n" + reject_message,;
706 prompt = "[R]eject, Manual reject, Skip, Quit ?";
707 if Cnf["Dinstall::Options::Automatic"]:
710 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
711 prompt = "[S]kip, New ack, Manual reject, Quit ?";
712 if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
715 print "BYHAND\n" + reject_message + summary,;
716 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
718 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
719 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
721 print "INSTALL\n" + reject_message + summary,;
722 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
723 if Cnf["Dinstall::Options::Automatic"]:
726 while string.find(prompt, answer) == -1:
728 answer = utils.our_raw_input()
729 m = re_default_answer.match(prompt)
732 answer = string.upper(answer[:1])
735 reject (changes_filename, "");
737 manual_reject (changes_filename);
739 install (changes_filename, summary, short_summary);
741 acknowledge_new (changes_filename, summary);
745 #####################################################################################################################
747 def install (changes_filename, summary, short_summary):
748 global install_count, install_bytes
750 # Stable uploads are a special case
751 if changes.has_key("stable upload"):
752 stable_install (changes_filename, summary, short_summary);
757 archive = utils.where_am_i();
759 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
760 projectB.query("BEGIN WORK");
762 # Add the .dsc file to the DB
763 for file in files.keys():
764 if files[file]["type"] == "dsc":
765 package = dsc["source"]
766 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
767 maintainer = dsc["maintainer"]
768 maintainer = string.replace(maintainer, "'", "\\'")
769 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
770 filename = files[file]["pool name"] + file;
771 dsc_location_id = files[file]["location id"];
772 if not files[file]["files id"]:
773 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
774 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
775 % (package, version, maintainer_id, files[file]["files id"]))
777 for suite in changes["distribution"].keys():
778 suite_id = db_access.get_suite_id(suite);
779 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
781 # Add the source files to the DB (files and dsc_files)
782 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
783 for dsc_file in dsc_files.keys():
784 filename = files[file]["pool name"] + dsc_file;
785 # If the .orig.tar.gz is already in the pool, it's
786 # files id is stored in dsc_files by check_dsc().
787 files_id = dsc_files[dsc_file].get("files id", None);
789 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
790 # FIXME: needs to check for -1/-2 and or handle exception
792 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
793 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
795 # Add the .deb files to the DB
796 for file in files.keys():
797 if files[file]["type"] == "deb":
798 package = files[file]["package"]
799 version = files[file]["version"]
800 maintainer = files[file]["maintainer"]
801 maintainer = string.replace(maintainer, "'", "\\'")
802 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
803 architecture = files[file]["architecture"]
804 architecture_id = db_access.get_architecture_id (architecture);
805 type = files[file]["dbtype"];
806 component = files[file]["component"]
807 source = files[file]["source"]
809 if string.find(source, "(") != -1:
810 m = utils.re_extract_src_version.match(source)
812 source_version = m.group(2)
813 if not source_version:
814 source_version = version
815 filename = files[file]["pool name"] + file;
816 if not files[file]["files id"]:
817 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
818 source_id = db_access.get_source_id (source, source_version);
820 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
821 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
823 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
824 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
825 for suite in changes["distribution"].keys():
826 suite_id = db_access.get_suite_id(suite);
827 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
829 # If the .orig.tar.gz is in a legacy directory we need to poolify
830 # it, so that apt-get source (and anything else that goes by the
831 # "Directory:" field in the Sources.gz file) works.
832 if orig_tar_id != None:
833 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));
836 # Is this an old upload superseded by a newer -sa upload? (See check_dsc() for details)
837 if legacy_source_untouchable.has_key(qid["files_id"]):
839 # First move the files to the new location
840 legacy_filename = qid["path"]+qid["filename"];
841 pool_location = utils.poolify (changes["source"], files[file]["component"]);
842 pool_filename = pool_location + os.path.basename(qid["filename"]);
843 destination = Cnf["Dir::PoolDir"] + pool_location
844 utils.move(legacy_filename, destination);
845 # Then Update the DB's files table
846 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
848 # Install the files into the pool
849 for file in files.keys():
850 if files[file].has_key("byhand"):
852 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
853 destdir = os.path.dirname(destination)
854 utils.move (file, destination)
855 install_bytes = install_bytes + float(files[file]["size"])
857 # Copy the .changes file across for suite which need it.
858 for suite in changes["distribution"].keys():
859 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
860 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
862 projectB.query("COMMIT WORK");
864 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
866 install_count = install_count + 1;
868 if not Cnf["Dinstall::Options::No-Mail"]:
869 mail_message = """Return-Path: %s
872 Bcc: troup@auric.debian.org
873 Subject: %s INSTALLED
879 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
880 utils.send_mail (mail_message, "")
881 announce (short_summary, 1)
884 #####################################################################################################################
886 def stable_install (changes_filename, summary, short_summary):
887 global install_count, install_bytes
889 print "Installing to stable."
891 archive = utils.where_am_i();
893 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
894 projectB.query("BEGIN WORK");
896 # Add the .dsc file to the DB
897 for file in files.keys():
898 if files[file]["type"] == "dsc":
899 package = dsc["source"]
900 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
901 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
904 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version));
906 source_id = ql[0][0];
907 suite_id = db_access.get_suite_id('proposed-updates');
908 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
909 suite_id = db_access.get_suite_id('stable');
910 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
912 # Add the .deb files to the DB
913 for file in files.keys():
914 if files[file]["type"] == "deb":
915 package = files[file]["package"]
916 version = files[file]["version"]
917 architecture = files[file]["architecture"]
918 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))
921 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture));
923 binary_id = ql[0][0];
924 suite_id = db_access.get_suite_id('proposed-updates');
925 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
926 suite_id = db_access.get_suite_id('stable');
927 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
929 projectB.query("COMMIT WORK");
931 utils.move (changes_filename, Cnf["Rhona::Morgue"] + os.path.basename(changes_filename));
933 # Update the Stable ChangeLog file
935 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
936 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
937 if os.path.exists(new_changelog_filename):
938 os.unlink (new_changelog_filename);
940 new_changelog = utils.open_file(new_changelog_filename, 'w');
941 for file in files.keys():
942 if files[file]["type"] == "deb":
943 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
944 elif re_issource.match(file) != None:
945 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
947 new_changelog.write("%s\n" % (file));
948 chop_changes = re_fdnic.sub("\n", changes["changes"]);
949 new_changelog.write(chop_changes + '\n\n');
950 if os.access(changelog_filename, os.R_OK) != 0:
951 changelog = utils.open_file(changelog_filename, 'r');
952 new_changelog.write(changelog.read());
953 new_changelog.close();
954 if os.access(changelog_filename, os.R_OK) != 0:
955 os.unlink(changelog_filename);
956 utils.move(new_changelog_filename, changelog_filename);
958 install_count = install_count + 1;
960 if not Cnf["Dinstall::Options::No-Mail"]:
961 mail_message = """Return-Path: %s
964 Bcc: troup@auric.debian.org
965 Subject: %s INSTALLED into stable
971 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
972 utils.send_mail (mail_message, "")
973 announce (short_summary, 1)
975 #####################################################################################################################
977 def reject (changes_filename, manual_reject_mail_filename):
980 base_changes_filename = os.path.basename(changes_filename);
981 reason_filename = re_changes.sub("reason", base_changes_filename);
982 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
984 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
986 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
987 except utils.cant_overwrite_exc:
988 sys.stderr.write("W: couldn't overwrite existing file '%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
990 for file in files.keys():
991 if os.path.exists(file):
993 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
995 sys.stderr.write("W: couldn't reject file '%s' [Got %s].\n" % (file, sys.exc_type));
998 # If this is not a manual rejection generate the .reason file and rejection mail message
999 if manual_reject_mail_filename == "":
1000 if os.path.exists(reject_filename):
1001 os.unlink(reject_filename);
1002 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1003 os.write(fd, reject_message);
1005 reject_mail_message = """From: %s
1007 Bcc: troup@auric.debian.org
1008 Subject: %s REJECTED
1012 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, reject_footer);
1013 else: # Have a manual rejection file to use
1014 reject_mail_message = ""; # avoid <undef>'s
1016 # Send the rejection mail if appropriate
1017 if not Cnf["Dinstall::Options::No-Mail"]:
1018 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
1020 ##################################################################
1022 def manual_reject (changes_filename):
1023 # Build up the rejection email
1024 user_email_address = string.replace(string.split(pwd.getpwuid(os.getuid())[4],',')[0], '.', '')
1025 user_email_address = user_email_address + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
1026 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
1028 reject_mail_message = """From: %s
1031 Bcc: troup@auric.debian.org
1032 Subject: %s REJECTED
1037 %s""" % (user_email_address, Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), manual_reject_message, reject_message, reject_footer)
1039 # Write the rejection email out as the <foo>.reason file
1040 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
1041 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
1042 if os.path.exists(reject_filename):
1043 os.unlink(reject_filename);
1044 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1045 os.write(fd, reject_mail_message);
1048 # If we weren't given one, spawn an editor so the user can add one in
1049 if manual_reject_message == "":
1050 result = os.system("vi +6 %s" % (reject_file))
1052 sys.stderr.write ("vi invocation failed for `%s'!" % (reject_file))
1055 # Then process it as if it were an automatic rejection
1056 reject (changes_filename, reject_filename)
1058 #####################################################################################################################
1060 def acknowledge_new (changes_filename, summary):
1063 changes_filename = os.path.basename(changes_filename);
1065 new_ack_new[changes_filename] = 1;
1067 if new_ack_old.has_key(changes_filename):
1068 print "Ack already sent.";
1071 print "Sending new ack.";
1072 if not Cnf["Dinstall::Options::No-Mail"]:
1073 new_ack_message = """Return-Path: %s
1076 Bcc: troup@auric.debian.org
1080 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes_filename, summary, new_ack_footer);
1081 utils.send_mail(new_ack_message,"");
1083 #####################################################################################################################
1085 def announce (short_summary, action):
1086 # Only do announcements for source uploads with a recent dpkg-dev installed
1087 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
1093 for dist in changes["distribution"].keys():
1094 list = Cnf.Find("Suite::%s::Announce" % (dist))
1095 if list == None or lists_done.has_key(list):
1097 lists_done[list] = 1
1098 summary = summary + "Announcing to %s\n" % (list)
1101 mail_message = """Return-Path: %s
1104 Bcc: troup@auric.debian.org
1105 Subject: Installed %s %s (%s)
1111 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], list, changes["source"], changes["version"], string.join(changes["architecture"].keys(), ' ' ),
1112 changes["filecontents"], short_summary)
1113 utils.send_mail (mail_message, "")
1115 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
1116 bugs = changes["closes"].keys()
1118 if dsc_name == changes["maintainername"]:
1119 summary = summary + "Closing bugs: "
1121 summary = summary + "%s " % (bug)
1123 mail_message = """Return-Path: %s
1125 To: %s-close@bugs.debian.org
1126 Bcc: troup@auric.debian.org
1127 Subject: Bug#%s: fixed in %s %s
1129 We believe that the bug you reported is fixed in the latest version of
1130 %s, which has been installed in the Debian FTP archive:
1132 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], bug, bug, changes["source"], changes["version"], changes["source"], short_summary)
1134 if changes["distribution"].has_key("stable"):
1135 mail_message = mail_message + """Note that this package is not part of the released stable Debian
1136 distribution. It may have dependencies on other unreleased software,
1137 or other instabilities. Please take care if you wish to install it.
1138 The update will eventually make its way into the next released Debian
1141 mail_message = mail_message + """A summary of the changes between this version and the previous one is
1144 Thank you for reporting the bug, which will now be closed. If you
1145 have further comments please address them to %s@bugs.debian.org,
1146 and the maintainer will reopen the bug report if appropriate.
1148 Debian distribution maintenance software
1150 %s (supplier of updated %s package)
1152 (This message was generated automatically at their request; if you
1153 believe that there is a problem with it please contact the archive
1154 administrators by mailing ftpmaster@debian.org)
1157 %s""" % (bug, changes["maintainer"], changes["source"], changes["filecontents"])
1159 utils.send_mail (mail_message, "")
1161 summary = summary + "Setting bugs to severity fixed: "
1162 control_message = ""
1164 summary = summary + "%s " % (bug)
1165 control_message = control_message + "severity %s fixed\n" % (bug)
1166 if action and control_message != "":
1167 mail_message = """Return-Path: %s
1169 To: control@bugs.debian.org
1170 Bcc: troup@auric.debian.org, %s
1171 Subject: Fixed in NMU of %s %s
1176 This message was generated automatically in response to a
1177 non-maintainer upload. The .changes file follows.
1180 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes["maintainer822"], changes["source"], changes["version"], control_message, changes["filecontents"])
1181 utils.send_mail (mail_message, "")
1182 summary = summary + "\n"
1186 ###############################################################################
1188 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1189 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1190 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1191 # processed it during it's checks of -2. If -1 has been deleted or
1192 # otherwise not checked by da-install, the .orig.tar.gz will not have
1193 # been checked at all. To get round this, we force the .orig.tar.gz
1194 # into the .changes structure and reprocess the .changes file.
1196 def process_it (changes_file):
1197 global reprocess, orig_tar_id, changes, dsc, dsc_files, files, reject_message;
1199 # Reset some globals
1206 legacy_source_untouchable = {};
1207 reject_message = "";
1210 # Absolutize the filename to avoid the requirement of being in the
1211 # same directory as the .changes file.
1212 changes_file = os.path.abspath(changes_file);
1214 # And since handling of installs to stable munges with the CWD;
1215 # save and restore it.
1218 check_signature (changes_file);
1219 check_changes (changes_file);
1227 action(changes_file);
1232 ###############################################################################
1235 global Cnf, projectB, install_bytes, new_ack_old
1239 Cnf = apt_pkg.newConfiguration();
1240 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1242 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1243 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1244 ('h',"help","Dinstall::Options::Help"),
1245 ('k',"ack-new","Dinstall::Options::Ack-New"),
1246 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1247 ('n',"no-action","Dinstall::Options::No-Action"),
1248 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1249 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1250 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1251 ('v',"version","Dinstall::Options::Version")];
1253 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1255 if Cnf["Dinstall::Options::Help"]:
1258 if Cnf["Dinstall::Options::Version"]:
1259 print "katie version 0.0000000000";
1262 postgresql_user = None; # Default == Connect as user running program.
1264 # -n/--dry-run invalidates some other options which would involve things happening
1265 if Cnf["Dinstall::Options::No-Action"]:
1266 Cnf["Dinstall::Options::Automatic"] = ""
1267 Cnf["Dinstall::Options::Ack-New"] = ""
1268 postgresql_user = Cnf["DB::ROUser"];
1270 projectB = pg.connect('projectb', Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1272 db_access.init(Cnf, projectB);
1274 # Check that we aren't going to clash with the daily cron job
1276 if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1277 sys.stderr.write("Archive maintenance in progress. Try again later.\n");
1280 # Obtain lock if not in no-action mode
1282 if not Cnf["Dinstall::Options::No-Action"]:
1283 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1284 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1286 # Read in the list of already-acknowledged NEW packages
1287 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1289 for line in new_ack_list.readlines():
1290 new_ack_old[line[:-1]] = 1;
1291 new_ack_list.close();
1293 # Process the changes files
1294 for changes_file in changes_files:
1295 print "\n" + changes_file;
1296 process_it (changes_file);
1300 if install_count > 1:
1302 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1304 # Write out the list of already-acknowledged NEW packages
1305 if Cnf["Dinstall::Options::Ack-New"]:
1306 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1307 for i in new_ack_new.keys():
1308 new_ack_list.write(i+'\n')
1309 new_ack_list.close()
1312 if __name__ == '__main__':