3 # Installs Debian packaes
4 # Copyright (C) 2000 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.25 2001-01-28 09:06:44 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));
510 # Unfortunately, we make get more than one match
511 # here if, for example, the package was in potato
512 # but had a -sa upload in woody. So we need to a)
513 # choose the right one and b) mark all wrong ones
514 # as excluded from the source poolification (to
515 # avoid file overwrites).
517 x = ql[0]; # default to something sane in case we don't match any or have only one
521 old_file = i[0] + i[1];
522 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
523 actual_size = os.stat(old_file)[stat.ST_SIZE];
524 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
527 legacy_source_untouchable[i[3]] = "";
529 old_file = x[0] + x[1];
530 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
531 actual_size = os.stat(old_file)[stat.ST_SIZE];
534 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
536 if suite_type == "legacy" or suite_type == "legacy-mixed":
539 # Not there? Check in Incoming...
540 # [See comment above process_it() for explanation
541 # of why this is necessary...]
542 if os.access(dsc_file, os.R_OK) != 0:
543 files[dsc_file] = {};
544 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
545 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
546 files[dsc_file]["section"] = files[file]["section"];
547 files[dsc_file]["priority"] = files[file]["priority"];
548 files[dsc_file]["component"] = files[file]["component"];
549 files[dsc_file]["type"] = "orig.tar.gz";
553 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);
556 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming.\n" % (file, dsc_file);
558 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
559 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
560 if actual_size != int(dsc_files[dsc_file]["size"]):
561 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
563 if string.find(reject_message, "Rejected:") != -1:
568 ###############################################################################
570 # Some cunning stunt broke dpkg-source in dpkg 1.8{,.1}; detect the
571 # resulting bad source packages and reject them.
573 # Even more amusingly the fix in 1.8.1.1 didn't actually fix the
574 # problem just changed the symptoms.
577 global dsc, dsc_files, reject_message, reprocess, orig_tar_id;
579 for filename in files.keys():
580 if files[filename]["type"] == "diff.gz":
581 file = gzip.GzipFile(filename, 'r');
582 for line in file.readlines():
583 if re_bad_diff.search(line):
584 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";
587 if string.find(reject_message, "Rejected:") != -1:
592 ###############################################################################
594 def check_md5sums ():
595 global reject_message;
597 for file in files.keys():
599 file_handle = utils.open_file(file,"r");
600 except utils.cant_open_exc:
603 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
604 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
606 def check_override ():
607 # Only check section & priority on sourceful uploads
608 if not changes["architecture"].has_key("source"):
612 for file in files.keys():
613 if not files[file].has_key("new") and (files[file]["type"] == "dsc" or files[file]["type"] == "deb"):
614 section = files[file]["section"];
615 override_section = files[file]["override section"];
616 if section != override_section and section != "-":
617 # Ignore this; it's a common mistake and not worth whining about
618 if section == "non-US/main" and override_section == "non-US":
620 summary = summary + "%s: section is overridden from %s to %s.\n" % (file, section, override_section);
621 if files[file]["type"] == "deb": # don't do priority for source
622 priority = files[file]["priority"];
623 override_priority = files[file]["override priority"];
624 if priority != override_priority and priority != "-":
625 summary = summary + "%s: priority is overridden from %s to %s.\n" % (file, priority, override_priority);
630 mail_message = """Return-Path: %s
633 Bcc: troup@auric.debian.org
634 Subject: %s override disparity
636 There are disparities between your recently installed upload and the
637 override file for the following file(s):
640 Either the package or the override file is incorrect. If you think
641 the override is correct and the package wrong please fix the package
642 so that this disparity is fixed in the next upload. If you feel the
643 override is incorrect then please reply to this mail and explain why.
646 Debian distribution maintenance software
648 (This message was generated automatically; if you believe that there
649 is a problem with it please contact the archive administrators by
650 mailing ftpmaster@debian.org)
651 """ % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes["source"], summary);
652 utils.send_mail (mail_message, "")
654 #####################################################################################################################
656 def action (changes_filename):
657 byhand = confirm = suites = summary = new = "";
659 # changes["distribution"] may not exist in corner cases
660 # (e.g. unreadable changes files)
661 if not changes.has_key("distribution"):
662 changes["distribution"] = {};
664 for suite in changes["distribution"].keys():
665 if Cnf.has_key("Suite::%s::Confirm"):
666 confirm = confirm + suite + ", "
667 suites = suites + suite + ", "
668 confirm = confirm[:-2]
671 for file in files.keys():
672 if files[file].has_key("byhand"):
674 summary = summary + file + " byhand\n"
675 elif files[file].has_key("new"):
677 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
678 if files[file].has_key("othercomponents"):
679 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
680 if files[file]["type"] == "deb":
681 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
683 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
684 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
685 summary = summary + file + "\n to " + destination + "\n"
687 short_summary = summary;
689 # This is for direport's benefit...
690 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
692 if confirm or byhand or new:
693 summary = summary + "Changes: " + f;
695 summary = summary + announce (short_summary, 0)
697 (prompt, answer) = ("", "XXX")
698 if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
701 if string.find(reject_message, "Rejected") != -1:
702 if time.time()-os.path.getmtime(changes_filename) < 86400:
703 print "SKIP (too new)\n" + reject_message,;
704 prompt = "[S]kip, Manual reject, Quit ?";
706 print "REJECT\n" + reject_message,;
707 prompt = "[R]eject, Manual reject, Skip, Quit ?";
708 if Cnf["Dinstall::Options::Automatic"]:
711 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
712 prompt = "[S]kip, New ack, Manual reject, Quit ?";
713 if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
716 print "BYHAND\n" + reject_message + summary,;
717 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
719 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
720 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
722 print "INSTALL\n" + reject_message + summary,;
723 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
724 if Cnf["Dinstall::Options::Automatic"]:
727 while string.find(prompt, answer) == -1:
729 answer = utils.our_raw_input()
730 m = re_default_answer.match(prompt)
733 answer = string.upper(answer[:1])
736 reject (changes_filename, "");
738 manual_reject (changes_filename);
740 install (changes_filename, summary, short_summary);
742 acknowledge_new (changes_filename, summary);
746 #####################################################################################################################
748 def install (changes_filename, summary, short_summary):
749 global install_count, install_bytes
751 # Stable uploads are a special case
752 if changes.has_key("stable upload"):
753 stable_install (changes_filename, summary, short_summary);
758 archive = utils.where_am_i();
760 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
761 projectB.query("BEGIN WORK");
763 # Add the .dsc file to the DB
764 for file in files.keys():
765 if files[file]["type"] == "dsc":
766 package = dsc["source"]
767 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
768 maintainer = dsc["maintainer"]
769 maintainer = string.replace(maintainer, "'", "\\'")
770 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
771 filename = files[file]["pool name"] + file;
772 dsc_location_id = files[file]["location id"];
773 if not files[file]["files id"]:
774 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
775 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
776 % (package, version, maintainer_id, files[file]["files id"]))
778 for suite in changes["distribution"].keys():
779 suite_id = db_access.get_suite_id(suite);
780 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
782 # Add the source files to the DB (files and dsc_files)
783 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
784 for dsc_file in dsc_files.keys():
785 filename = files[file]["pool name"] + dsc_file;
786 # If the .orig.tar.gz is already in the pool, it's
787 # files id is stored in dsc_files by check_dsc().
788 files_id = dsc_files[dsc_file].get("files id", None);
790 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
791 # FIXME: needs to check for -1/-2 and or handle exception
793 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
794 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
796 # Add the .deb files to the DB
797 for file in files.keys():
798 if files[file]["type"] == "deb":
799 package = files[file]["package"]
800 version = files[file]["version"]
801 maintainer = files[file]["maintainer"]
802 maintainer = string.replace(maintainer, "'", "\\'")
803 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
804 architecture = files[file]["architecture"]
805 architecture_id = db_access.get_architecture_id (architecture);
806 type = files[file]["dbtype"];
807 component = files[file]["component"]
808 source = files[file]["source"]
810 if string.find(source, "(") != -1:
811 m = utils.re_extract_src_version.match(source)
813 source_version = m.group(2)
814 if not source_version:
815 source_version = version
816 filename = files[file]["pool name"] + file;
817 if not files[file]["files id"]:
818 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
819 source_id = db_access.get_source_id (source, source_version);
821 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
822 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
824 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
825 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
826 for suite in changes["distribution"].keys():
827 suite_id = db_access.get_suite_id(suite);
828 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
830 # If the .orig.tar.gz is in a legacy directory we need to poolify
831 # it, so that apt-get source (and anything else that goes by the
832 # "Directory:" field in the Sources.gz file) works.
833 if orig_tar_id != None:
834 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));
837 # Is this an old upload superseded by a newer -sa upload? (See check_dsc() for details)
838 if legacy_source_untouchable.has_key(qid["files_id"]):
840 # First move the files to the new location
841 legacy_filename = qid["path"]+qid["filename"];
842 pool_location = utils.poolify (changes["source"], files[file]["component"]);
843 pool_filename = pool_location + os.path.basename(qid["filename"]);
844 destination = Cnf["Dir::PoolDir"] + pool_location
845 utils.move(legacy_filename, destination);
846 # Then Update the DB's files table
847 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
849 # Install the files into the pool
850 for file in files.keys():
851 if files[file].has_key("byhand"):
853 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
854 destdir = os.path.dirname(destination)
855 utils.move (file, destination)
856 install_bytes = install_bytes + float(files[file]["size"])
858 # Copy the .changes file across for suite which need it.
859 for suite in changes["distribution"].keys():
860 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
861 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
863 projectB.query("COMMIT WORK");
865 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
867 install_count = install_count + 1;
869 if not Cnf["Dinstall::Options::No-Mail"]:
870 mail_message = """Return-Path: %s
873 Bcc: troup@auric.debian.org
874 Subject: %s INSTALLED
880 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
881 utils.send_mail (mail_message, "")
882 announce (short_summary, 1)
885 #####################################################################################################################
887 def stable_install (changes_filename, summary, short_summary):
888 global install_count, install_bytes
890 print "Installing to stable."
892 archive = utils.where_am_i();
894 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
895 projectB.query("BEGIN WORK");
897 # Add the .dsc file to the DB
898 for file in files.keys():
899 if files[file]["type"] == "dsc":
900 package = dsc["source"]
901 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
902 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
905 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version));
907 source_id = ql[0][0];
908 suite_id = db_access.get_suite_id('proposed-updates');
909 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
910 suite_id = db_access.get_suite_id('stable');
911 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
913 # Add the .deb files to the DB
914 for file in files.keys():
915 if files[file]["type"] == "deb":
916 package = files[file]["package"]
917 version = files[file]["version"]
918 architecture = files[file]["architecture"]
919 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))
922 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture));
924 binary_id = ql[0][0];
925 suite_id = db_access.get_suite_id('proposed-updates');
926 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
927 suite_id = db_access.get_suite_id('stable');
928 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
930 projectB.query("COMMIT WORK");
932 utils.move (changes_filename, Cnf["Rhona::Morgue"] + os.path.basename(changes_filename));
934 # Update the Stable ChangeLog file
936 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
937 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
938 if os.path.exists(new_changelog_filename):
939 os.unlink (new_changelog_filename);
941 new_changelog = utils.open_file(new_changelog_filename, 'w');
942 for file in files.keys():
943 if files[file]["type"] == "deb":
944 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
945 elif re_issource.match(file) != None:
946 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
948 new_changelog.write("%s\n" % (file));
949 chop_changes = re_fdnic.sub("\n", changes["changes"]);
950 new_changelog.write(chop_changes + '\n\n');
951 if os.access(changelog_filename, os.R_OK) != 0:
952 changelog = utils.open_file(changelog_filename, 'r');
953 new_changelog.write(changelog.read());
954 new_changelog.close();
955 if os.access(changelog_filename, os.R_OK) != 0:
956 os.unlink(changelog_filename);
957 utils.move(new_changelog_filename, changelog_filename);
959 install_count = install_count + 1;
961 if not Cnf["Dinstall::Options::No-Mail"]:
962 mail_message = """Return-Path: %s
965 Bcc: troup@auric.debian.org
966 Subject: %s INSTALLED into stable
972 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
973 utils.send_mail (mail_message, "")
974 announce (short_summary, 1)
976 #####################################################################################################################
978 def reject (changes_filename, manual_reject_mail_filename):
981 base_changes_filename = os.path.basename(changes_filename);
982 reason_filename = re_changes.sub("reason", base_changes_filename);
983 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
985 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
987 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
988 except utils.cant_overwrite_exc:
989 sys.stderr.write("W: couldn't overwrite existing file '%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
991 for file in files.keys():
992 if os.path.exists(file):
994 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
996 sys.stderr.write("W: couldn't reject file '%s' [Got %s].\n" % (file, sys.exc_type));
999 # If this is not a manual rejection generate the .reason file and rejection mail message
1000 if manual_reject_mail_filename == "":
1001 if os.path.exists(reject_filename):
1002 os.unlink(reject_filename);
1003 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1004 os.write(fd, reject_message);
1006 reject_mail_message = """From: %s
1008 Bcc: troup@auric.debian.org
1009 Subject: %s REJECTED
1013 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, reject_footer);
1014 else: # Have a manual rejection file to use
1015 reject_mail_message = ""; # avoid <undef>'s
1017 # Send the rejection mail if appropriate
1018 if not Cnf["Dinstall::Options::No-Mail"]:
1019 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
1021 ##################################################################
1023 def manual_reject (changes_filename):
1024 # Build up the rejection email
1025 user_email_address = string.replace(string.split(pwd.getpwuid(os.getuid())[4],',')[0], '.', '')
1026 user_email_address = user_email_address + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
1027 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
1029 reject_mail_message = """From: %s
1032 Bcc: troup@auric.debian.org
1033 Subject: %s REJECTED
1038 %s""" % (user_email_address, Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), manual_reject_message, reject_message, reject_footer)
1040 # Write the rejection email out as the <foo>.reason file
1041 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
1042 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
1043 if os.path.exists(reject_filename):
1044 os.unlink(reject_filename);
1045 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
1046 os.write(fd, reject_mail_message);
1049 # If we weren't given one, spawn an editor so the user can add one in
1050 if manual_reject_message == "":
1051 result = os.system("vi +6 %s" % (reject_file))
1053 sys.stderr.write ("vi invocation failed for `%s'!" % (reject_file))
1056 # Then process it as if it were an automatic rejection
1057 reject (changes_filename, reject_filename)
1059 #####################################################################################################################
1061 def acknowledge_new (changes_filename, summary):
1064 changes_filename = os.path.basename(changes_filename);
1066 new_ack_new[changes_filename] = 1;
1068 if new_ack_old.has_key(changes_filename):
1069 print "Ack already sent.";
1072 print "Sending new ack.";
1073 if not Cnf["Dinstall::Options::No-Mail"]:
1074 new_ack_message = """Return-Path: %s
1077 Bcc: troup@auric.debian.org
1081 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes_filename, summary, new_ack_footer);
1082 utils.send_mail(new_ack_message,"");
1084 #####################################################################################################################
1086 def announce (short_summary, action):
1087 # Only do announcements for source uploads with a recent dpkg-dev installed
1088 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
1094 for dist in changes["distribution"].keys():
1095 list = Cnf.Find("Suite::%s::Announce" % (dist))
1096 if list == None or lists_done.has_key(list):
1098 lists_done[list] = 1
1099 summary = summary + "Announcing to %s\n" % (list)
1102 mail_message = """Return-Path: %s
1105 Bcc: troup@auric.debian.org
1106 Subject: Installed %s %s (%s)
1112 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], list, changes["source"], changes["version"], string.join(changes["architecture"].keys(), ' ' ),
1113 changes["filecontents"], short_summary)
1114 utils.send_mail (mail_message, "")
1116 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
1117 bugs = changes["closes"].keys()
1119 if dsc_name == changes["maintainername"]:
1120 summary = summary + "Closing bugs: "
1122 summary = summary + "%s " % (bug)
1124 mail_message = """Return-Path: %s
1126 To: %s-close@bugs.debian.org
1127 Bcc: troup@auric.debian.org
1128 Subject: Bug#%s: fixed in %s %s
1130 We believe that the bug you reported is fixed in the latest version of
1131 %s, which has been installed in the Debian FTP archive:
1133 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], bug, bug, changes["source"], changes["version"], changes["source"], short_summary)
1135 if changes["distribution"].has_key("stable"):
1136 mail_message = mail_message + """Note that this package is not part of the released stable Debian
1137 distribution. It may have dependencies on other unreleased software,
1138 or other instabilities. Please take care if you wish to install it.
1139 The update will eventually make its way into the next released Debian
1142 mail_message = mail_message + """A summary of the changes between this version and the previous one is
1145 Thank you for reporting the bug, which will now be closed. If you
1146 have further comments please address them to %s@bugs.debian.org,
1147 and the maintainer will reopen the bug report if appropriate.
1149 Debian distribution maintenance software
1151 %s (supplier of updated %s package)
1153 (This message was generated automatically at their request; if you
1154 believe that there is a problem with it please contact the archive
1155 administrators by mailing ftpmaster@debian.org)
1158 %s""" % (bug, changes["maintainer"], changes["source"], changes["filecontents"])
1160 utils.send_mail (mail_message, "")
1162 summary = summary + "Setting bugs to severity fixed: "
1163 control_message = ""
1165 summary = summary + "%s " % (bug)
1166 control_message = control_message + "severity %s fixed\n" % (bug)
1167 if action and control_message != "":
1168 mail_message = """Return-Path: %s
1170 To: control@bugs.debian.org
1171 Bcc: troup@auric.debian.org, %s
1172 Subject: Fixed in NMU of %s %s
1177 This message was generated automatically in response to a
1178 non-maintainer upload. The .changes file follows.
1181 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes["maintainer822"], changes["source"], changes["version"], control_message, changes["filecontents"])
1182 utils.send_mail (mail_message, "")
1183 summary = summary + "\n"
1187 ###############################################################################
1189 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1190 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1191 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1192 # processed it during it's checks of -2. If -1 has been deleted or
1193 # otherwise not checked by da-install, the .orig.tar.gz will not have
1194 # been checked at all. To get round this, we force the .orig.tar.gz
1195 # into the .changes structure and reprocess the .changes file.
1197 def process_it (changes_file):
1198 global reprocess, orig_tar_id, changes, dsc, dsc_files, files, reject_message;
1200 # Reset some globals
1207 legacy_source_untouchable = {};
1208 reject_message = "";
1211 # Absolutize the filename to avoid the requirement of being in the
1212 # same directory as the .changes file.
1213 changes_file = os.path.abspath(changes_file);
1215 # And since handling of installs to stable munges with the CWD;
1216 # save and restore it.
1219 check_signature (changes_file);
1220 check_changes (changes_file);
1228 action(changes_file);
1233 ###############################################################################
1236 global Cnf, projectB, install_bytes, new_ack_old
1240 Cnf = apt_pkg.newConfiguration();
1241 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1243 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1244 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1245 ('h',"help","Dinstall::Options::Help"),
1246 ('k',"ack-new","Dinstall::Options::Ack-New"),
1247 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1248 ('n',"no-action","Dinstall::Options::No-Action"),
1249 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1250 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1251 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1252 ('v',"version","Dinstall::Options::Version")];
1254 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1256 if Cnf["Dinstall::Options::Help"]:
1259 if Cnf["Dinstall::Options::Version"]:
1260 print "katie version 0.0000000000";
1263 postgresql_user = None; # Default == Connect as user running program.
1265 # -n/--dry-run invalidates some other options which would involve things happening
1266 if Cnf["Dinstall::Options::No-Action"]:
1267 Cnf["Dinstall::Options::Automatic"] = ""
1268 Cnf["Dinstall::Options::Ack-New"] = ""
1269 postgresql_user = Cnf["DB::ROUser"];
1271 projectB = pg.connect('projectb', Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1273 db_access.init(Cnf, projectB);
1275 # Check that we aren't going to clash with the daily cron job
1277 if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1278 sys.stderr.write("Archive maintenance in progress. Try again later.\n");
1281 # Obtain lock if not in no-action mode
1283 if not Cnf["Dinstall::Options::No-Action"]:
1284 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1285 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1287 # Read in the list of already-acknowledged NEW packages
1288 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1290 for line in new_ack_list.readlines():
1291 new_ack_old[line[:-1]] = 1;
1292 new_ack_list.close();
1294 # Process the changes files
1295 for changes_file in changes_files:
1296 print "\n" + changes_file;
1297 process_it (changes_file);
1301 if install_count > 1:
1303 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1305 # Write out the list of already-acknowledged NEW packages
1306 if Cnf["Dinstall::Options::Ack-New"]:
1307 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1308 for i in new_ack_new.keys():
1309 new_ack_list.write(i+'\n')
1310 new_ack_list.close()
1313 if __name__ == '__main__':