3 # Installs Debian packaes
4 # Copyright (C) 2000 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.15 2000-12-20 08:15:35 troup Exp $
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 # Based (almost entirely) on dinstall by Guy Maor <maor@debian.org>
23 #########################################################################################
25 # Cartman: "I'm trying to make the best of a bad situation, I don't
26 # need to hear crap from a bunch of hippy freaks living in
27 # denial. Screw you guys, I'm going home."
29 # Kyle: "But Cartman, we're trying to..."
31 # Cartman: "uhh.. screw you guys... home."
33 #########################################################################################
35 import FCNTL, commands, fcntl, getopt, 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_dpackage = re.compile (r'^package:\s*(.*)', re.IGNORECASE);
45 re_darchitecture = re.compile (r'^architecture:\s*(.*)', re.IGNORECASE);
46 re_dversion = re.compile (r'^version:\s*(.*)', re.IGNORECASE);
47 re_dsection = re.compile (r'^section:\s*(.*)', re.IGNORECASE);
48 re_dpriority = re.compile (r'^priority:\s*(.*)', re.IGNORECASE);
49 re_changes = re.compile (r'changes$');
50 re_override_package = re.compile(r'(\S*)\s+.*');
51 re_default_answer = re.compile(r"\[(.*)\]");
52 re_fdnic = re.compile("\n\n");
54 ###############################################################################
57 reject_footer = """If you don't understand why your files were rejected, or if the
58 override file requires editing, reply to this email.
60 Your rejected files are in incoming/REJECT/. (Some may also be in
61 incoming/ if your .changes file was unparsable.) If only some of the
62 files need to repaired, you may move any good files back to incoming/.
63 Please remove any bad files from incoming/REJECT/."""
65 new_ack_footer = """Your package contains new components which requires manual editing of
66 the override file. It is ok otherwise, so please be patient. New
67 packages are usually added to the override file about once a week.
69 You may have gotten the distribution wrong. You'll get warnings above
70 if files already exist in other distributions."""
72 installed_footer = """If the override file requires editing, file a bug on ftp.debian.org.
74 Thank you for your contribution to Debian GNU."""
76 #########################################################################################
94 #########################################################################################
96 def usage (exit_code):
97 print """Usage: dinstall [OPTION]... [CHANGES]...
98 -a, --automatic automatic run
99 -d, --debug=VALUE debug
100 -k, --ack-new acknowledge new packages
101 -m, --manual-reject=MSG manual reject with `msg'
102 -n, --dry-run don't do anything
103 -p, --no-lock don't check lockfile !! for cron.daily only !!
104 -r, --no-version-check override version check
105 -u, --distribution=DIST override distribution to `dist'"""
108 def check_signature (filename):
109 global reject_message
111 (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))
113 reject_message = "Rejected: GPG signature check failed on `%s'.\n%s\n" % (filename, output)
117 #####################################################################################################################
119 def read_override_file (filename, suite, component, binary_type):
122 file = utils.open_file(filename, 'r');
123 for line in file.readlines():
124 line = string.strip(utils.re_comments.sub('', line))
125 override_package = re_override_package.sub(r'\1', line)
126 if override_package != "":
127 overrides[suite][component][binary_type][override_package] = 1
131 # See if a given package is in the override file. Caches and only loads override files on demand.
133 def in_override_p (package, component, suite, binary_type):
136 if binary_type == "" or binary_type == "deb":
139 # Avoid <undef> on unknown distributions
140 if db_access.get_suite_id(suite) == -1:
143 # FIXME: nasty non-US speficic hack
144 if string.lower(component[:7]) == "non-us/":
145 component = component[7:];
146 if not overrides.has_key(suite) or not overrides[suite].has_key(component) or not overrides[suite][component].has_key(binary_type):
147 if not overrides.has_key(suite):
148 overrides[suite] = {}
149 if not overrides[suite].has_key(component):
150 overrides[suite][component] = {}
151 if not overrides[suite][component].has_key(binary_type):
152 overrides[suite][component][binary_type] = {}
153 if Cnf.has_key("Suite::%s::SingleOverrideFile" % (suite)): # legacy mixed suite (i.e. experimental)
154 override_filename = Cnf["Dir::OverrideDir"] + 'override.' + Cnf["Suite::%s::OverrideCodeName" % (suite)];
155 read_override_file (override_filename, suite, component, binary_type);
157 if binary_type == "udeb":
158 override_filename = Cnf["Dir::OverrideDir"] + 'override.' + Cnf["Suite::%s::OverrideCodeName" % (suite)] + '.debian-installer.' + component;
159 read_override_file (override_filename, suite, component, binary_type);
161 for src in ("", ".src"):
162 override_filename = Cnf["Dir::OverrideDir"] + 'override.' + Cnf["Suite::%s::OverrideCodeName" % (suite)] + '.' + component + src;
163 read_override_file (override_filename, suite, component, binary_type);
165 return overrides[suite][component][binary_type].get(package, None);
167 #####################################################################################################################
169 def check_changes(filename):
170 global reject_message, changes, files
172 # Parse the .changes field into a dictionary [FIXME - need to trap errors, pass on to reject_message etc.]
174 changes = utils.parse_changes(filename)
175 except utils.cant_open_exc:
176 reject_message = "Rejected: can't read changes file '%s'.\n" % (filename)
178 except utils.changes_parse_error_exc, line:
179 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
180 changes["maintainer822"] = Cnf["Dinstall::MyEmailAddress"];
183 # Parse the Files field from the .changes into another dictionary [FIXME need to trap errors as above]
184 files = utils.build_file_list(changes, "")
186 # Check for mandatory fields
187 for i in ("source", "binary", "architecture", "version", "distribution","maintainer", "files"):
188 if not changes.has_key(i):
189 reject_message = "Rejected: Missing field `%s' in changes file." % (i)
190 return 0 # Avoid <undef> errors during later tests
192 # Fix the Maintainer: field to be RFC822 compatible
193 (changes["maintainer822"], changes["maintainername"], changes["maintaineremail"]) = utils.fix_maintainer (changes["maintainer"])
195 # Override the Distribution: field if appropriate
196 if Cnf["Dinstall::Options::Override-Distribution"] != "":
197 reject_message = reject_message + "Warning: Distribution was overriden from %s to %s.\n" % (changes["distribution"], Cnf["Dinstall::Options::Override-Distribution"])
198 changes["distribution"] = Cnf["Dinstall::Options::Override-Distribution"]
200 # Split multi-value fields into a lower-level dictionary
201 for i in ("architecture", "distribution", "binary", "closes"):
202 o = changes.get(i, "")
206 for j in string.split(o):
209 # Ensure all the values in Closes: are numbers
210 if changes.has_key("closes"):
211 for i in changes["closes"].keys():
212 if re_isanum.match (i) == None:
213 reject_message = reject_message + "Rejected: `%s' from Closes field isn't a number.\n" % (i)
215 # Map frozen to unstable if frozen doesn't exist
216 if changes["distribution"].has_key("frozen") and not Cnf.has_key("Suite::Frozen"):
217 del changes["distribution"]["frozen"]
218 reject_message = reject_message + "Mapping frozen to unstable.\n"
220 # Ensure target distributions exist
221 for i in changes["distribution"].keys():
222 if not Cnf.has_key("Suite::%s" % (i)):
223 reject_message = reject_message + "Rejected: Unknown distribution `%s'.\n" % (i)
225 # Map unreleased arches from stable to unstable
226 if changes["distribution"].has_key("stable"):
227 for i in changes["architecture"].keys():
228 if not Cnf.has_key("Suite::Stable::Architectures::%s" % (i)):
229 reject_message = reject_message + "Mapping stable to unstable for unreleased arch `%s'.\n" % (i)
230 del changes["distribution"]["stable"]
232 # Map arches not being released from frozen to unstable
233 if changes["distribution"].has_key("frozen"):
234 for i in changes["architecture"].keys():
235 if not Cnf.has_key("Suite::Frozen::Architectures::%s" % (i)):
236 reject_message = reject_message + "Mapping frozen to unstable for non-releasing arch `%s'.\n" % (i)
237 del changes["distribution"]["frozen"]
239 # Handle uploads to stable
240 if changes["distribution"].has_key("stable"):
241 # If running from within proposed-updates; assume an install to stable
242 if string.find(os.getcwd(), 'proposed-updates') != -1:
243 # FIXME: should probably remove anything that != stable
244 for i in ("frozen", "unstable"):
245 if changes["distribution"].has_key(i):
246 reject_message = reject_message + "Removing %s from distribution list.\n"
247 del changes["distribution"][i]
248 changes["stable upload"] = 1;
249 # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool
250 file = files.keys()[0];
251 if os.access(file, os.R_OK) == 0:
252 pool_dir = Cnf["Dir::PoolDir"] + '/' + utils.poolify(changes["source"], files[file]["component"]);
254 # Otherwise (normal case) map stable to updates
256 reject_message = reject_message + "Mapping stable to updates.\n";
257 del changes["distribution"]["stable"];
258 changes["distribution"]["proposed-updates"] = 1;
260 # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
261 changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
262 changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
264 if string.find(reject_message, "Rejected:") != -1:
270 global reject_message
272 archive = utils.where_am_i();
274 for file in files.keys():
275 # Check the file is readable
276 if os.access(file,os.R_OK) == 0:
277 reject_message = reject_message + "Rejected: Can't read `%s'.\n" % (file)
278 files[file]["type"] = "unreadable";
280 # If it's byhand skip remaining checks
281 if files[file]["section"] == "byhand":
282 files[file]["byhand"] = 1;
283 files[file]["type"] = "byhand";
284 # Checks for a binary package...
285 elif re_isadeb.match(file) != None:
286 # Extract package information using dpkg-deb
287 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
289 # Check for mandatory fields
290 if control.Find("Package") == None:
291 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
292 if control.Find("Architecture") == None:
293 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
294 if control.Find("Version") == None:
295 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
297 # Ensure the package name matches the one give in the .changes
298 if not changes["binary"].has_key(control.Find("Package", "")):
299 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
301 # Validate the architecture
302 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
303 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
305 # Check the architecture matches the one given in the .changes
306 if not changes["architecture"].has_key(control.Find("Architecture", "")):
307 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
308 # Check the section & priority match those given in the .changes (non-fatal)
309 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
310 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"])
311 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
312 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"])
314 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
316 files[file]["package"] = control.Find("Package");
317 files[file]["architecture"] = control.Find("Architecture");
318 files[file]["version"] = control.Find("Version");
319 files[file]["maintainer"] = control.Find("Maintainer", "");
320 if file[-5:] == ".udeb":
321 files[file]["dbtype"] = "udeb";
322 elif file[-4:] == ".deb":
323 files[file]["dbtype"] = "deb";
325 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
326 files[file]["type"] = "deb";
327 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
328 files[file]["source"] = control.Find("Source", "");
329 if files[file]["source"] == "":
330 files[file]["source"] = files[file]["package"];
331 # Checks for a source package...
333 m = re_issource.match(file)
335 files[file]["package"] = m.group(1)
336 files[file]["version"] = m.group(2)
337 files[file]["type"] = m.group(3)
339 # Ensure the source package name matches the Source filed in the .changes
340 if changes["source"] != files[file]["package"]:
341 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
343 # Ensure the source version matches the version in the .changes file
344 if files[file]["type"] == "orig.tar.gz":
345 changes_version = changes["chopversion2"]
347 changes_version = changes["chopversion"]
348 if changes_version != files[file]["version"]:
349 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
351 # Ensure the .changes lists source in the Architecture field
352 if not changes["architecture"].has_key("source"):
353 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
355 # Check the signature of a .dsc file
356 if files[file]["type"] == "dsc":
357 check_signature(file)
359 files[file]["fullname"] = file
361 # Not a binary or source package? Assume byhand...
363 files[file]["byhand"] = 1;
364 files[file]["type"] = "byhand";
366 files[file]["oldfiles"] = {}
367 for suite in changes["distribution"].keys():
369 if files[file].has_key("byhand"):
372 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
373 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
376 # See if the package is NEW
377 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype","")):
378 files[file]["new"] = 1
380 # Find any old binary packages
381 if files[file]["type"] == "deb":
382 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"
383 % (files[file]["package"], suite, files[file]["architecture"]))
384 oldfiles = q.dictresult()
385 for oldfile in oldfiles:
386 files[file]["oldfiles"][suite] = oldfile
387 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
388 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
389 if Cnf["Dinstall::Options::No-Version-Check"]:
390 reject_message = reject_message + "Overriden rejection"
392 reject_message = reject_message + "Rejected"
393 reject_message = reject_message + ": %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
394 # Check for existing copies of the file
395 if not changes.has_key("stable upload"):
396 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"]))
397 if q.getresult() != []:
398 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
400 # Find any old .dsc files
401 elif files[file]["type"] == "dsc":
402 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"
403 % (files[file]["package"], suite))
404 oldfiles = q.dictresult()
405 if len(oldfiles) >= 1:
406 files[file]["oldfiles"][suite] = oldfiles[0]
408 # Validate the component
409 component = files[file]["component"];
410 component_id = db_access.get_component_id(component);
411 if component_id == -1:
412 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
415 # Check the md5sum & size against existing files (if any)
416 location = Cnf["Dir::PoolDir"];
417 files[file]["location id"] = db_access.get_location_id (location, component, archive);
419 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
420 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
422 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
424 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
425 files[file]["files id"] = files_id
427 # Check for packages that have moved from one component to another
428 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
429 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
432 if string.find(reject_message, "Rejected:") != -1:
437 ###############################################################################
440 global dsc, dsc_files, reject_message, reprocess, orig_tar_id;
442 for file in files.keys():
443 if files[file]["type"] == "dsc":
445 dsc = utils.parse_changes(file)
446 except utils.cant_open_exc:
447 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (filename)
449 except utils.changes_parse_error_exc, line:
450 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
453 dsc_files = utils.build_file_list(dsc, 1)
454 except utils.no_files_exc:
455 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
458 # Try and find all files mentioned in the .dsc. This has
459 # to work harder to cope with the multiple possible
460 # locations of an .orig.tar.gz.
461 for dsc_file in dsc_files.keys():
462 if files.has_key(dsc_file):
463 actual_md5 = files[dsc_file]["md5sum"];
464 actual_size = int(files[dsc_file]["size"]);
465 found = "%s in incoming" % (dsc_file)
466 # Check the file does not already exist in the archive
467 if not changes.has_key("stable upload"):
468 q = projectB.query("SELECT f.id FROM files f, location l WHERE f.filename ~ '/%s' AND l.id = f.location" % (utils.regex_safe(dsc_file)));
469 if q.getresult() != []:
470 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
471 elif dsc_file[-12:] == ".orig.tar.gz":
473 q = projectB.query("SELECT l.path, f.filename, l.type, f.id FROM files f, location l WHERE f.filename ~ '/%s' AND l.id = f.location" % (utils.regex_safe(dsc_file)));
476 old_file = ql[0][0] + ql[0][1];
477 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
478 actual_size = os.stat(old_file)[stat.ST_SIZE];
480 suite_type = ql[0][2];
481 dsc_files[dsc_file]["files id"] = ql[0][3]; # need this for updating dsc_files in install()
483 if suite_type == "legacy" or suite_type == "legacy-mixed":
484 orig_tar_id = ql[0][3];
486 # Not there? Check in Incoming...
487 # [See comment above process_it() for explanation
488 # of why this is necessary...]
489 if os.access(dsc_file, os.R_OK) != 0:
490 files[dsc_file] = {};
491 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
492 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
493 files[dsc_file]["section"] = files[file]["section"];
494 files[dsc_file]["priority"] = files[file]["priority"];
495 files[dsc_file]["component"] = files[file]["component"];
499 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);
502 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming." % (file, dsc_file);
504 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
505 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
506 if actual_size != int(dsc_files[dsc_file]["size"]):
507 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
509 if string.find(reject_message, "Rejected:") != -1:
514 ###############################################################################
516 def check_md5sums ():
517 global reject_message;
519 for file in files.keys():
521 file_handle = utils.open_file(file,"r");
522 except utils.cant_open_exc:
525 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
526 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
528 #####################################################################################################################
530 def action (changes_filename):
531 byhand = confirm = suites = summary = new = "";
533 # changes["distribution"] may not exist in corner cases
534 # (e.g. unreadable changes files)
535 if not changes.has_key("distribution"):
536 changes["distribution"] = {};
538 for suite in changes["distribution"].keys():
539 if Cnf.has_key("Suite::%s::Confirm"):
540 confirm = confirm + suite + ", "
541 suites = suites + suite + ", "
542 confirm = confirm[:-2]
545 for file in files.keys():
546 if files[file].has_key("byhand"):
548 summary = summary + file + " byhand\n"
549 elif files[file].has_key("new"):
551 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
552 if files[file].has_key("othercomponents"):
553 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
554 if files[file]["type"] == "deb":
555 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
557 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
558 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
559 summary = summary + file + "\n to " + destination + "\n"
561 short_summary = summary;
563 # This is for direport's benefit...
564 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
566 if confirm or byhand or new:
567 summary = summary + "Changes: " + f;
569 summary = summary + announce (short_summary, 0)
571 (prompt, answer) = ("", "XXX")
572 if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
575 if string.find(reject_message, "Rejected") != -1:
576 if time.time()-os.path.getmtime(changes_filename) < 86400:
577 print "SKIP (too new)\n" + reject_message,;
578 prompt = "[S]kip, Manual reject, Quit ?";
580 print "REJECT\n" + reject_message,;
581 prompt = "[R]eject, Manual reject, Skip, Quit ?";
582 if Cnf["Dinstall::Options::Automatic"]:
585 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
586 prompt = "[S]kip, New ack, Manual reject, Quit ?";
587 if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
590 print "BYHAND\n" + reject_message + summary,;
591 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
593 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
594 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
596 print "INSTALL\n" + reject_message + summary,;
597 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
598 if Cnf["Dinstall::Options::Automatic"]:
601 while string.find(prompt, answer) == -1:
603 answer = utils.our_raw_input()
604 m = re_default_answer.match(prompt)
607 answer = string.upper(answer[:1])
610 reject (changes_filename, "");
612 manual_reject (changes_filename);
614 install (changes_filename, summary, short_summary);
616 acknowledge_new (changes_filename, summary);
620 #####################################################################################################################
622 def install (changes_filename, summary, short_summary):
623 global install_count, install_bytes
625 # Stable uploads are a special case
626 if changes.has_key("stable upload"):
627 stable_install (changes_filename, summary, short_summary);
632 archive = utils.where_am_i();
634 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
635 projectB.query("BEGIN WORK");
637 # Add the .dsc file to the DB
638 for file in files.keys():
639 if files[file]["type"] == "dsc":
640 package = dsc["source"]
641 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
642 maintainer = dsc["maintainer"]
643 maintainer = string.replace(maintainer, "'", "\\'")
644 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
645 filename = files[file]["pool name"] + file;
646 dsc_location_id = files[file]["location id"];
647 if not files[file]["files id"]:
648 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
649 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
650 % (package, version, maintainer_id, files[file]["files id"]))
652 for suite in changes["distribution"].keys():
653 suite_id = db_access.get_suite_id(suite);
654 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
656 # Add the source files to the DB (files and dsc_files)
657 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
658 for dsc_file in dsc_files.keys():
659 filename = files[file]["pool name"] + dsc_file;
660 # If the .orig.tar.gz is already in the pool, it's
661 # files id is stored in dsc_files by check_dsc().
662 files_id = dsc_files[dsc_file].get("files id", None);
664 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
665 # FIXME: needs to check for -1/-2 and or handle exception
667 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
668 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
670 # Add the .deb files to the DB
671 for file in files.keys():
672 if files[file]["type"] == "deb":
673 package = files[file]["package"]
674 version = files[file]["version"]
675 maintainer = files[file]["maintainer"]
676 maintainer = string.replace(maintainer, "'", "\\'")
677 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
678 architecture = files[file]["architecture"]
679 architecture_id = db_access.get_architecture_id (architecture);
680 type = files[file]["dbtype"];
681 component = files[file]["component"]
682 source = files[file]["source"]
684 if string.find(source, "(") != -1:
685 m = utils.re_extract_src_version.match(source)
687 source_version = m.group(2)
688 if not source_version:
689 source_version = version
690 filename = files[file]["pool name"] + file;
691 if not files[file]["files id"]:
692 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
693 source_id = db_access.get_source_id (source, source_version);
695 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
696 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
698 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
699 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
700 for suite in changes["distribution"].keys():
701 suite_id = db_access.get_suite_id(suite);
702 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
704 # If the .orig.tar.gz is in a legacy directory we need to poolify
705 # it, so that apt-get source (and anything else that goes by the
706 # "Directory:" field in the Sources.gz file) works.
707 if orig_tar_id != None:
708 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));
711 # First move the files to the new location
712 legacy_filename = qid["path"]+qid["filename"];
713 pool_location = utils.poolify (changes["source"], files[file]["component"]);
714 pool_filename = pool_location + os.path.basename(qid["filename"]);
715 destination = Cnf["Dir::PoolDir"] + pool_location
716 utils.move(legacy_filename, destination);
717 # Then Update the DB's files table
718 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
720 # Install the files into the pool
721 for file in files.keys():
722 if files[file].has_key("byhand"):
724 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
725 destdir = os.path.dirname(destination)
726 utils.move (file, destination)
727 install_bytes = install_bytes + float(files[file]["size"])
729 # Copy the .changes file across for suite which need it.
730 for suite in changes["distribution"].keys():
731 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
732 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
734 projectB.query("COMMIT WORK");
736 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
738 install_count = install_count + 1;
740 if not Cnf["Dinstall::Options::No-Mail"]:
741 mail_message = """Return-Path: %s
744 Bcc: troup@auric.debian.org
745 Subject: %s INSTALLED
751 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
752 utils.send_mail (mail_message, "")
753 announce (short_summary, 1)
755 #####################################################################################################################
757 def stable_install (changes_filename, summary, short_summary):
758 global install_count, install_bytes
760 print "Installing to stable."
762 archive = utils.where_am_i();
764 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
765 projectB.query("BEGIN WORK");
767 # Add the .dsc file to the DB
768 for file in files.keys():
769 if files[file]["type"] == "dsc":
770 package = dsc["source"]
771 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
772 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
775 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version));
777 source_id = ql[0][0];
778 suite_id = db_access.get_suite_id('proposed-updates');
779 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
780 suite_id = db_access.get_suite_id('stable');
781 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
783 # Add the .deb files to the DB
784 for file in files.keys():
785 if files[file]["type"] == "deb":
786 package = files[file]["package"]
787 version = files[file]["version"]
788 architecture = files[file]["architecture"]
789 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))
792 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture));
794 binary_id = ql[0][0];
795 suite_id = db_access.get_suite_id('proposed-updates');
796 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
797 suite_id = db_access.get_suite_id('stable');
798 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
800 projectB.query("COMMIT WORK");
802 utils.move (changes_filename, Cnf["Rhona::Morgue"] + os.path.basename(changes_filename));
804 # Update the Stable ChangeLog file
806 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
807 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
808 if os.path.exists(new_changelog_filename):
809 os.unlink (new_changelog_filename);
811 new_changelog = utils.open_file(new_changelog_filename, 'w');
812 for file in files.keys():
813 if files[file]["type"] == "deb":
814 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
815 elif re_issource.match(file) != None:
816 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
818 new_changelog.write("%s\n" % (file));
819 chop_changes = re_fdnic.sub("\n", changes["changes"]);
820 new_changelog.write(chop_changes + '\n\n');
821 if os.access(changelog_filename, os.R_OK) != 0:
822 changelog = utils.open_file(changelog_filename, 'r');
823 new_changelog.write(changelog.read());
824 new_changelog.close();
825 if os.access(changelog_filename, os.R_OK) != 0:
826 os.unlink(changelog_filename);
827 utils.move(new_changelog_filename, changelog_filename);
829 install_count = install_count + 1;
831 if not Cnf["Dinstall::Options::No-Mail"]:
832 mail_message = """Return-Path: %s
835 Bcc: troup@auric.debian.org
836 Subject: %s INSTALLED into stable
842 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
843 utils.send_mail (mail_message, "")
844 announce (short_summary, 1)
846 #####################################################################################################################
848 def reject (changes_filename, manual_reject_mail_filename):
851 base_changes_filename = os.path.basename(changes_filename);
852 reason_filename = re_changes.sub("reason", base_changes_filename);
853 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
855 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
857 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
858 except utils.cant_overwrite_exc:
859 sys.stderr.write("W: couldn't overwrite existing file '%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
861 for file in files.keys():
862 if os.path.exists(file):
864 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
865 except utils.cant_overwrite_exc:
866 sys.stderr.write("W: couldn't overwrite existing file '%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
869 # If this is not a manual rejection generate the .reason file and rejection mail message
870 if manual_reject_mail_filename == "":
871 if os.path.exists(reject_filename):
872 os.unlink(reject_filename);
873 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
874 os.write(fd, reject_message);
876 reject_mail_message = """From: %s
878 Bcc: troup@auric.debian.org
883 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, reject_footer);
884 else: # Have a manual rejection file to use
885 reject_mail_message = ""; # avoid <undef>'s
887 # Send the rejection mail if appropriate
888 if not Cnf["Dinstall::Options::No-Mail"]:
889 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
891 ##################################################################
893 def manual_reject (changes_filename):
894 # Build up the rejection email
895 user_email_address = string.replace(string.split(pwd.getpwuid(os.getuid())[4],',')[0], '.', '')
896 user_email_address = user_email_address + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
897 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
899 reject_mail_message = """From: %s
902 Bcc: troup@auric.debian.org
908 %s""" % (user_email_address, Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), manual_reject_message, reject_message, reject_footer)
910 # Write the rejection email out as the <foo>.reason file
911 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
912 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
913 if os.path.exists(reject_filename):
914 os.unlink(reject_filename);
915 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
916 os.write(fd, reject_mail_message);
919 # If we weren't given one, spawn an editor so the user can add one in
920 if manual_reject_message == "":
921 result = os.system("vi +6 %s" % (reject_file))
923 sys.stderr.write ("vi invocation failed for `%s'!" % (reject_file))
926 # Then process it as if it were an automatic rejection
927 reject (changes_filename, reject_filename)
929 #####################################################################################################################
931 def acknowledge_new (changes_filename, summary):
934 changes_filename = os.path.basename(changes_filename);
936 new_ack_new[changes_filename] = 1;
938 if new_ack_old.has_key(changes_filename):
939 print "Ack already sent.";
942 print "Sending new ack.";
943 if not Cnf["Dinstall::Options::No-Mail"]:
944 new_ack_message = """Return-Path: %s
947 Bcc: troup@auric.debian.org
951 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes_filename, summary, new_ack_footer);
952 utils.send_mail(new_ack_message,"");
954 #####################################################################################################################
956 def announce (short_summary, action):
957 # Only do announcements for source uploads with a recent dpkg-dev installed
958 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
964 for dist in changes["distribution"].keys():
965 list = Cnf.Find("Suite::%s::Announce" % (dist))
966 if list == None or lists_done.has_key(list):
969 summary = summary + "Announcing to %s\n" % (list)
972 mail_message = """Return-Path: %s
975 Bcc: troup@auric.debian.org
976 Subject: Installed %s %s (%s)
982 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], list, changes["source"], changes["version"], string.join(changes["architecture"].keys(), ' ' ),
983 changes["filecontents"], short_summary)
984 utils.send_mail (mail_message, "")
986 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
987 bugs = changes["closes"].keys()
989 if dsc_name == changes["maintainername"]:
990 summary = summary + "Closing bugs: "
992 summary = summary + "%s " % (bug)
994 mail_message = """Return-Path: %s
996 To: %s-close@bugs.debian.org
997 Bcc: troup@auric.debian.org
998 Subject: Bug#%s: fixed in %s %s
1000 We believe that the bug you reported is fixed in the latest version of
1001 %s, which has been installed in the Debian FTP archive:
1003 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], bug, bug, changes["source"], changes["version"], changes["source"], short_summary)
1005 if changes["distribution"].has_key("stable"):
1006 mail_message = mail_message + """Note that this package is not part of the released stable Debian
1007 distribution. It may have dependencies on other unreleased software,
1008 or other instabilities. Please take care if you wish to install it.
1009 The update will eventually make its way into the next released Debian
1012 mail_message = mail_message + """A summary of the changes between this version and the previous one is
1015 Thank you for reporting the bug, which will now be closed. If you
1016 have further comments please address them to %s@bugs.debian.org,
1017 and the maintainer will reopen the bug report if appropriate.
1019 Debian distribution maintenance software
1021 %s (supplier of updated %s package)
1023 (This message was generated automatically at their request; if you
1024 believe that there is a problem with it please contact the archive
1025 administrators by mailing ftpmaster@debian.org)
1028 %s""" % (bug, changes["maintainer"], changes["source"], changes["filecontents"])
1030 utils.send_mail (mail_message, "")
1032 summary = summary + "Setting bugs to severity fixed: "
1033 control_message = ""
1035 summary = summary + "%s " % (bug)
1036 control_message = control_message + "severity %s fixed\n" % (bug)
1037 if action and control_message != "":
1038 mail_message = """Return-Path: %s
1040 To: control@bugs.debian.org
1041 Bcc: troup@auric.debian.org, %s
1042 Subject: Fixed in NMU of %s %s
1047 This message was generated automatically in response to a
1048 non-maintainer upload. The .changes file follows.
1051 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes["maintainer822"], changes["source"], changes["version"], control_message, changes["filecontents"])
1052 utils.send_mail (mail_message, "")
1053 summary = summary + "\n"
1057 ###############################################################################
1059 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1060 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1061 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1062 # processed it during it's checks of -2. If -1 has been deleted or
1063 # otherwise not checked by da-install, the .orig.tar.gz will not have
1064 # been checked at all. To get round this, we force the .orig.tar.gz
1065 # into the .changes structure and reprocess the .changes file.
1067 def process_it (changes_file):
1068 global reprocess, orig_tar_id, changes, dsc, dsc_files, files;
1072 # Reset some globals
1079 # Absolutize the filename to avoid the requirement of being in the
1080 # same directory as the .changes file.
1081 changes_file = os.path.abspath(changes_file);
1083 # And since handling of installs to stable munges with the CWD;
1084 # save and restore it.
1087 check_signature (changes_file);
1088 check_changes (changes_file);
1095 action(changes_file);
1100 ###############################################################################
1103 global Cnf, projectB, reject_message, install_bytes, new_ack_old
1107 Cnf = apt_pkg.newConfiguration();
1108 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1110 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1111 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1112 ('h',"help","Dinstall::Options::Help"),
1113 ('k',"ack-new","Dinstall::Options::Ack-New"),
1114 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1115 ('n',"no-action","Dinstall::Options::No-Action"),
1116 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1117 ('r',"no-version-check", "Dinstall::Options::No-Version-Check"),
1118 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1119 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1120 ('v',"version","Dinstall::Options::Version")];
1122 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1124 if Cnf["Dinstall::Options::Help"]:
1127 if Cnf["Dinstall::Options::Version"]:
1128 print "katie version 0.0000000000";
1131 postgresql_user = None; # Default == Connect as user running program.
1133 # -n/--dry-run invalidates some other options which would involve things happening
1134 if Cnf["Dinstall::Options::No-Action"]:
1135 Cnf["Dinstall::Options::Automatic"] = ""
1136 Cnf["Dinstall::Options::Ack-New"] = ""
1137 postgresql_user = Cnf["DB::ROUser"];
1139 projectB = pg.connect('projectb', Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1141 db_access.init(Cnf, projectB);
1143 # Check that we aren't going to clash with the daily cron job
1145 if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1146 sys.stderr.write("Archive maintenance in progress. Try again later.\n");
1149 # Obtain lock if not in no-action mode
1151 if not Cnf["Dinstall::Options::No-Action"]:
1152 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1153 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1155 # Read in the list of already-acknowledged NEW packages
1156 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1158 for line in new_ack_list.readlines():
1159 new_ack_old[line[:-1]] = 1;
1160 new_ack_list.close();
1162 # Process the changes files
1163 for changes_file in changes_files:
1165 print "\n" + changes_file;
1166 process_it (changes_file);
1170 if install_count > 1:
1172 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1174 # Write out the list of already-acknowledged NEW packages
1175 if Cnf["Dinstall::Options::Ack-New"]:
1176 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1177 for i in new_ack_new.keys():
1178 new_ack_list.write(i+'\n')
1179 new_ack_list.close()
1182 if __name__ == '__main__':