3 # Installs Debian packaes
4 # Copyright (C) 2000 James Troup <james@nocrew.org>
5 # $Id: katie,v 1.18 2001-01-16 21:52:37 troup Exp $
7 # This program is free software; you can redistribute it and/or modify
8 # it under the terms of the GNU General Public License as published by
9 # the Free Software Foundation; either version 2 of the License, or
10 # (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU General Public License for more details.
17 # You should have received a copy of the GNU General Public License
18 # along with this program; if not, write to the Free Software
19 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 # Based (almost entirely) on dinstall by Guy Maor <maor@debian.org>
23 #########################################################################################
25 # Cartman: "I'm trying to make the best of a bad situation, I don't
26 # need to hear crap from a bunch of hippy freaks living in
27 # denial. Screw you guys, I'm going home."
29 # Kyle: "But Cartman, we're trying to..."
31 # Cartman: "uhh.. screw you guys... home."
33 #########################################################################################
35 import FCNTL, commands, fcntl, getopt, gzip, os, pg, pwd, re, shutil, stat, string, sys, tempfile, time
36 import apt_inst, apt_pkg
37 import utils, db_access
39 ###############################################################################
41 re_isanum = re.compile (r'^\d+$');
42 re_isadeb = re.compile (r'.*\.u?deb$');
43 re_issource = re.compile (r'(.+)_(.+?)\.(orig\.tar\.gz|diff\.gz|tar\.gz|dsc)');
44 re_changes = re.compile (r'changes$');
45 re_default_answer = re.compile(r"\[(.*)\]");
46 re_fdnic = re.compile("\n\n");
47 re_bad_diff = re.compile("^[\-\+][\-\+][\-\+] /dev/null");
49 ###############################################################################
52 reject_footer = """If you don't understand why your files were rejected, or if the
53 override file requires editing, reply to this email.
55 Your rejected files are in incoming/REJECT/. (Some may also be in
56 incoming/ if your .changes file was unparsable.) If only some of the
57 files need to repaired, you may move any good files back to incoming/.
58 Please remove any bad files from incoming/REJECT/."""
60 new_ack_footer = """Your package contains new components which requires manual editing of
61 the override file. It is ok otherwise, so please be patient. New
62 packages are usually added to the override file about once a week.
64 You may have gotten the distribution wrong. You'll get warnings above
65 if files already exist in other distributions."""
67 installed_footer = """If the override file requires editing, file a bug on ftp.debian.org.
69 Thank you for your contribution to Debian GNU."""
71 #########################################################################################
87 legacy_source_untouchable = {};
89 #########################################################################################
91 def usage (exit_code):
92 print """Usage: dinstall [OPTION]... [CHANGES]...
93 -a, --automatic automatic run
94 -d, --debug=VALUE debug
95 -k, --ack-new acknowledge new packages
96 -m, --manual-reject=MSG manual reject with `msg'
97 -n, --dry-run don't do anything
98 -p, --no-lock don't check lockfile !! for cron.daily only !!
99 -r, --no-version-check override version check
100 -u, --distribution=DIST override distribution to `dist'"""
103 def check_signature (filename):
104 global reject_message
106 (result, output) = commands.getstatusoutput("gpg --emulate-md-encode-bug --batch --no-options --no-default-keyring --always-trust --keyring=%s --keyring=%s < %s >/dev/null" % (Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"], filename))
108 reject_message = "Rejected: GPG signature check failed on `%s'.\n%s\n" % (filename, output)
112 #####################################################################################################################
114 # See if a given package is in the override table
116 def in_override_p (package, component, suite, binary_type):
119 if binary_type == "": # must be source
124 # Override suite name; used for example with proposed-updates
125 if Cnf.Find("Suite::%s::OverrideSuite" % (suite)) != "":
126 suite = Cnf["Suite::%s::OverrideSuite" % (suite)];
128 # Avoid <undef> on unknown distributions
129 suite_id = db_access.get_suite_id(suite);
132 component_id = db_access.get_component_id(component);
133 type_id = db_access.get_override_type_id(type);
135 # FIXME: nasty non-US speficic hack
136 if string.lower(component[:7]) == "non-us/":
137 component = component[7:];
139 q = projectB.query("SELECT package FROM override WHERE package = '%s' AND suite = %s AND component = %s AND type = %s"
140 % (package, suite_id, component_id, type_id));
141 result = q.getresult();
142 # If checking for a source package fall back on the binary override type
143 if type == "dsc" and not result:
144 type_id = db_access.get_override_type_id("deb");
145 q = projectB.query("SELECT package FROM override WHERE package = '%s' AND suite = %s AND component = %s AND type = %s"
146 % (package, suite_id, component_id, type_id));
147 result = q.getresult();
151 #####################################################################################################################
153 def check_changes(filename):
154 global reject_message, changes, files
156 # Parse the .changes field into a dictionary [FIXME - need to trap errors, pass on to reject_message etc.]
158 changes = utils.parse_changes(filename)
159 except utils.cant_open_exc:
160 reject_message = "Rejected: can't read changes file '%s'.\n" % (filename)
162 except utils.changes_parse_error_exc, line:
163 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
164 changes["maintainer822"] = Cnf["Dinstall::MyEmailAddress"];
167 # Parse the Files field from the .changes into another dictionary [FIXME need to trap errors as above]
169 files = utils.build_file_list(changes, "");
170 except utils.changes_parse_error_exc, line:
171 reject_message = "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line);
173 # Check for mandatory fields
174 for i in ("source", "binary", "architecture", "version", "distribution","maintainer", "files"):
175 if not changes.has_key(i):
176 reject_message = "Rejected: Missing field `%s' in changes file." % (i)
177 return 0 # Avoid <undef> errors during later tests
179 # Fix the Maintainer: field to be RFC822 compatible
180 (changes["maintainer822"], changes["maintainername"], changes["maintaineremail"]) = utils.fix_maintainer (changes["maintainer"])
182 # Override the Distribution: field if appropriate
183 if Cnf["Dinstall::Options::Override-Distribution"] != "":
184 reject_message = reject_message + "Warning: Distribution was overriden from %s to %s.\n" % (changes["distribution"], Cnf["Dinstall::Options::Override-Distribution"])
185 changes["distribution"] = Cnf["Dinstall::Options::Override-Distribution"]
187 # Split multi-value fields into a lower-level dictionary
188 for i in ("architecture", "distribution", "binary", "closes"):
189 o = changes.get(i, "")
193 for j in string.split(o):
196 # Ensure all the values in Closes: are numbers
197 if changes.has_key("closes"):
198 for i in changes["closes"].keys():
199 if re_isanum.match (i) == None:
200 reject_message = reject_message + "Rejected: `%s' from Closes field isn't a number.\n" % (i)
202 # Map frozen to unstable if frozen doesn't exist
203 if changes["distribution"].has_key("frozen") and not Cnf.has_key("Suite::Frozen"):
204 del changes["distribution"]["frozen"]
205 reject_message = reject_message + "Mapping frozen to unstable.\n"
207 # Map testing to unstable
208 if changes["distribution"].has_key("testing"):
209 del changes["distribution"]["testing"]
210 reject_message = reject_message + "Mapping testing to unstable.\n"
212 # Ensure target distributions exist
213 for i in changes["distribution"].keys():
214 if not Cnf.has_key("Suite::%s" % (i)):
215 reject_message = reject_message + "Rejected: Unknown distribution `%s'.\n" % (i)
217 # Map unreleased arches from stable to unstable
218 if changes["distribution"].has_key("stable"):
219 for i in changes["architecture"].keys():
220 if not Cnf.has_key("Suite::Stable::Architectures::%s" % (i)):
221 reject_message = reject_message + "Mapping stable to unstable for unreleased arch `%s'.\n" % (i)
222 del changes["distribution"]["stable"]
224 # Map arches not being released from frozen to unstable
225 if changes["distribution"].has_key("frozen"):
226 for i in changes["architecture"].keys():
227 if not Cnf.has_key("Suite::Frozen::Architectures::%s" % (i)):
228 reject_message = reject_message + "Mapping frozen to unstable for non-releasing arch `%s'.\n" % (i)
229 del changes["distribution"]["frozen"]
231 # Handle uploads to stable
232 if changes["distribution"].has_key("stable"):
233 # If running from within proposed-updates; assume an install to stable
234 if string.find(os.getcwd(), 'proposed-updates') != -1:
235 # FIXME: should probably remove anything that != stable
236 for i in ("frozen", "unstable"):
237 if changes["distribution"].has_key(i):
238 reject_message = reject_message + "Removing %s from distribution list.\n"
239 del changes["distribution"][i]
240 changes["stable upload"] = 1;
241 # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool
242 file = files.keys()[0];
243 if os.access(file, os.R_OK) == 0:
244 pool_dir = Cnf["Dir::PoolDir"] + '/' + utils.poolify(changes["source"], files[file]["component"]);
246 # Otherwise (normal case) map stable to updates
248 reject_message = reject_message + "Mapping stable to updates.\n";
249 del changes["distribution"]["stable"];
250 changes["distribution"]["proposed-updates"] = 1;
252 # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
253 changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
254 changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
256 if string.find(reject_message, "Rejected:") != -1:
262 global reject_message
264 archive = utils.where_am_i();
266 for file in files.keys():
267 # Check the file is readable
268 if os.access(file,os.R_OK) == 0:
269 reject_message = reject_message + "Rejected: Can't read `%s'.\n" % (file)
270 files[file]["type"] = "unreadable";
272 # If it's byhand skip remaining checks
273 if files[file]["section"] == "byhand":
274 files[file]["byhand"] = 1;
275 files[file]["type"] = "byhand";
276 # Checks for a binary package...
277 elif re_isadeb.match(file) != None:
278 # Extract package information using dpkg-deb
279 control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
281 # Check for mandatory fields
282 if control.Find("Package") == None:
283 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
284 if control.Find("Architecture") == None:
285 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
286 if control.Find("Version") == None:
287 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
289 # Ensure the package name matches the one give in the .changes
290 if not changes["binary"].has_key(control.Find("Package", "")):
291 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
293 # Validate the architecture
294 if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
295 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
297 # Check the architecture matches the one given in the .changes
298 if not changes["architecture"].has_key(control.Find("Architecture", "")):
299 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
300 # Check the section & priority match those given in the .changes (non-fatal)
301 if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
302 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"])
303 if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
304 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"])
306 epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
308 files[file]["package"] = control.Find("Package");
309 files[file]["architecture"] = control.Find("Architecture");
310 files[file]["version"] = control.Find("Version");
311 files[file]["maintainer"] = control.Find("Maintainer", "");
312 if file[-5:] == ".udeb":
313 files[file]["dbtype"] = "udeb";
314 elif file[-4:] == ".deb":
315 files[file]["dbtype"] = "deb";
317 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
318 files[file]["type"] = "deb";
319 files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
320 files[file]["source"] = control.Find("Source", "");
321 if files[file]["source"] == "":
322 files[file]["source"] = files[file]["package"];
323 # Checks for a source package...
325 m = re_issource.match(file)
327 files[file]["package"] = m.group(1)
328 files[file]["version"] = m.group(2)
329 files[file]["type"] = m.group(3)
331 # Ensure the source package name matches the Source filed in the .changes
332 if changes["source"] != files[file]["package"]:
333 reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
335 # Ensure the source version matches the version in the .changes file
336 if files[file]["type"] == "orig.tar.gz":
337 changes_version = changes["chopversion2"]
339 changes_version = changes["chopversion"]
340 if changes_version != files[file]["version"]:
341 reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
343 # Ensure the .changes lists source in the Architecture field
344 if not changes["architecture"].has_key("source"):
345 reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
347 # Check the signature of a .dsc file
348 if files[file]["type"] == "dsc":
349 check_signature(file)
351 files[file]["fullname"] = file
353 # Not a binary or source package? Assume byhand...
355 files[file]["byhand"] = 1;
356 files[file]["type"] = "byhand";
358 files[file]["oldfiles"] = {}
359 for suite in changes["distribution"].keys():
361 if files[file].has_key("byhand"):
364 if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
365 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
368 # See if the package is NEW
369 if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype","")):
370 files[file]["new"] = 1
372 # Find any old binary packages
373 if files[file]["type"] == "deb":
374 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"
375 % (files[file]["package"], suite, files[file]["architecture"]))
376 oldfiles = q.dictresult()
377 for oldfile in oldfiles:
378 files[file]["oldfiles"][suite] = oldfile
379 # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
380 if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
381 if Cnf["Dinstall::Options::No-Version-Check"]:
382 reject_message = reject_message + "Overriden rejection"
384 reject_message = reject_message + "Rejected"
385 reject_message = reject_message + ": %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
386 # Check for existing copies of the file
387 if not changes.has_key("stable upload"):
388 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"]))
389 if q.getresult() != []:
390 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
392 # Find any old .dsc files
393 elif files[file]["type"] == "dsc":
394 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"
395 % (files[file]["package"], suite))
396 oldfiles = q.dictresult()
397 if len(oldfiles) >= 1:
398 files[file]["oldfiles"][suite] = oldfiles[0]
400 # Validate the component
401 component = files[file]["component"];
402 component_id = db_access.get_component_id(component);
403 if component_id == -1:
404 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
407 # Check the md5sum & size against existing files (if any)
408 location = Cnf["Dir::PoolDir"];
409 files[file]["location id"] = db_access.get_location_id (location, component, archive);
411 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
412 files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
414 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
416 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
417 files[file]["files id"] = files_id
419 # Check for packages that have moved from one component to another
420 if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
421 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
424 if string.find(reject_message, "Rejected:") != -1:
429 ###############################################################################
432 global dsc, dsc_files, reject_message, reprocess, orig_tar_id, legacy_source_untouchable;
434 for file in files.keys():
435 if files[file]["type"] == "dsc":
437 dsc = utils.parse_changes(file)
438 except utils.cant_open_exc:
439 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (filename)
441 except utils.changes_parse_error_exc, line:
442 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
445 dsc_files = utils.build_file_list(dsc, 1)
446 except utils.no_files_exc:
447 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
449 except utils.changes_parse_error_exc, line:
450 reject_message = "Rejected: error parsing .dsc file '%s', can't grok: %s.\n" % (filename, line);
453 # Try and find all files mentioned in the .dsc. This has
454 # to work harder to cope with the multiple possible
455 # locations of an .orig.tar.gz.
456 for dsc_file in dsc_files.keys():
457 if files.has_key(dsc_file):
458 actual_md5 = files[dsc_file]["md5sum"];
459 actual_size = int(files[dsc_file]["size"]);
460 found = "%s in incoming" % (dsc_file)
461 # Check the file does not already exist in the archive
462 if not changes.has_key("stable upload"):
463 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));
465 # "It has not broken them. It has fixed a
466 # brokenness. Your crappy hack exploited a
467 # bug in the old dinstall.
469 # "(Come on! I thought it was always obvious
470 # that one just doesn't release different
471 # files with the same name and version.)"
472 # -- ajk@ on d-devel@l.d.o
474 if q.getresult() != []:
475 reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
476 elif dsc_file[-12:] == ".orig.tar.gz":
478 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));
483 # Unfortunately, we make get more than one match
484 # here if, for example, the package was in potato
485 # but had a -sa upload in woody. So we need to a)
486 # choose the right one and b) mark all wrong ones
487 # as excluded from the source poolification (to
488 # avoid file overwrites).
490 x = ql[0]; # default to something sane in case we don't match any or have only one
494 old_file = i[0] + i[1];
495 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
496 actual_size = os.stat(old_file)[stat.ST_SIZE];
497 if actual_md5 == dsc_files[dsc_file]["md5sum"] and actual_size == int(dsc_files[dsc_file]["size"]):
500 legacy_source_untouchable[i[3]] = "";
502 old_file = x[0] + x[1];
503 actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
504 actual_size = os.stat(old_file)[stat.ST_SIZE];
507 dsc_files[dsc_file]["files id"] = x[3]; # need this for updating dsc_files in install()
509 if suite_type == "legacy" or suite_type == "legacy-mixed":
512 # Not there? Check in Incoming...
513 # [See comment above process_it() for explanation
514 # of why this is necessary...]
515 if os.access(dsc_file, os.R_OK) != 0:
516 files[dsc_file] = {};
517 files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
518 files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
519 files[dsc_file]["section"] = files[file]["section"];
520 files[dsc_file]["priority"] = files[file]["priority"];
521 files[dsc_file]["component"] = files[file]["component"];
522 files[dsc_file]["type"] = "orig.tar.gz";
526 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);
529 reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming." % (file, dsc_file);
531 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
532 reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
533 if actual_size != int(dsc_files[dsc_file]["size"]):
534 reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
536 if string.find(reject_message, "Rejected:") != -1:
541 ###############################################################################
543 # Some cunning stunt broke dpkg-source in dpkg 1.8{,.1}; detect the
544 # resulting bad source packages and reject them.
546 # Even more amusingly the fix in 1.8.1.1 didn't actually fix the
547 # problem just changed the symptoms.
550 global dsc, dsc_files, reject_message, reprocess, orig_tar_id;
552 for filename in files.keys():
553 if files[filename]["type"] == "diff.gz":
554 file = gzip.GzipFile(filename, 'r');
555 for line in file.readlines():
556 if re_bad_diff.search(line):
557 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";
560 if string.find(reject_message, "Rejected:") != -1:
565 ###############################################################################
567 def check_md5sums ():
568 global reject_message;
570 for file in files.keys():
572 file_handle = utils.open_file(file,"r");
573 except utils.cant_open_exc:
576 if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
577 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
579 #####################################################################################################################
581 def action (changes_filename):
582 byhand = confirm = suites = summary = new = "";
584 # changes["distribution"] may not exist in corner cases
585 # (e.g. unreadable changes files)
586 if not changes.has_key("distribution"):
587 changes["distribution"] = {};
589 for suite in changes["distribution"].keys():
590 if Cnf.has_key("Suite::%s::Confirm"):
591 confirm = confirm + suite + ", "
592 suites = suites + suite + ", "
593 confirm = confirm[:-2]
596 for file in files.keys():
597 if files[file].has_key("byhand"):
599 summary = summary + file + " byhand\n"
600 elif files[file].has_key("new"):
602 summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
603 if files[file].has_key("othercomponents"):
604 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
605 if files[file]["type"] == "deb":
606 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
608 files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
609 destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
610 summary = summary + file + "\n to " + destination + "\n"
612 short_summary = summary;
614 # This is for direport's benefit...
615 f = re_fdnic.sub("\n .\n", changes.get("changes",""));
617 if confirm or byhand or new:
618 summary = summary + "Changes: " + f;
620 summary = summary + announce (short_summary, 0)
622 (prompt, answer) = ("", "XXX")
623 if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
626 if string.find(reject_message, "Rejected") != -1:
627 if time.time()-os.path.getmtime(changes_filename) < 86400:
628 print "SKIP (too new)\n" + reject_message,;
629 prompt = "[S]kip, Manual reject, Quit ?";
631 print "REJECT\n" + reject_message,;
632 prompt = "[R]eject, Manual reject, Skip, Quit ?";
633 if Cnf["Dinstall::Options::Automatic"]:
636 print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
637 prompt = "[S]kip, New ack, Manual reject, Quit ?";
638 if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
641 print "BYHAND\n" + reject_message + summary,;
642 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
644 print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
645 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
647 print "INSTALL\n" + reject_message + summary,;
648 prompt = "[I]nstall, Manual reject, Skip, Quit ?";
649 if Cnf["Dinstall::Options::Automatic"]:
652 while string.find(prompt, answer) == -1:
654 answer = utils.our_raw_input()
655 m = re_default_answer.match(prompt)
658 answer = string.upper(answer[:1])
661 reject (changes_filename, "");
663 manual_reject (changes_filename);
665 install (changes_filename, summary, short_summary);
667 acknowledge_new (changes_filename, summary);
671 #####################################################################################################################
673 def install (changes_filename, summary, short_summary):
674 global install_count, install_bytes
676 # Stable uploads are a special case
677 if changes.has_key("stable upload"):
678 stable_install (changes_filename, summary, short_summary);
683 archive = utils.where_am_i();
685 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
686 projectB.query("BEGIN WORK");
688 # Add the .dsc file to the DB
689 for file in files.keys():
690 if files[file]["type"] == "dsc":
691 package = dsc["source"]
692 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
693 maintainer = dsc["maintainer"]
694 maintainer = string.replace(maintainer, "'", "\\'")
695 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
696 filename = files[file]["pool name"] + file;
697 dsc_location_id = files[file]["location id"];
698 if not files[file]["files id"]:
699 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
700 projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
701 % (package, version, maintainer_id, files[file]["files id"]))
703 for suite in changes["distribution"].keys():
704 suite_id = db_access.get_suite_id(suite);
705 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
707 # Add the source files to the DB (files and dsc_files)
708 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
709 for dsc_file in dsc_files.keys():
710 filename = files[file]["pool name"] + dsc_file;
711 # If the .orig.tar.gz is already in the pool, it's
712 # files id is stored in dsc_files by check_dsc().
713 files_id = dsc_files[dsc_file].get("files id", None);
715 files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
716 # FIXME: needs to check for -1/-2 and or handle exception
718 files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
719 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
721 # Add the .deb files to the DB
722 for file in files.keys():
723 if files[file]["type"] == "deb":
724 package = files[file]["package"]
725 version = files[file]["version"]
726 maintainer = files[file]["maintainer"]
727 maintainer = string.replace(maintainer, "'", "\\'")
728 maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
729 architecture = files[file]["architecture"]
730 architecture_id = db_access.get_architecture_id (architecture);
731 type = files[file]["dbtype"];
732 component = files[file]["component"]
733 source = files[file]["source"]
735 if string.find(source, "(") != -1:
736 m = utils.re_extract_src_version.match(source)
738 source_version = m.group(2)
739 if not source_version:
740 source_version = version
741 filename = files[file]["pool name"] + file;
742 if not files[file]["files id"]:
743 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
744 source_id = db_access.get_source_id (source, source_version);
746 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
747 % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
749 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
750 % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
751 for suite in changes["distribution"].keys():
752 suite_id = db_access.get_suite_id(suite);
753 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
755 # If the .orig.tar.gz is in a legacy directory we need to poolify
756 # it, so that apt-get source (and anything else that goes by the
757 # "Directory:" field in the Sources.gz file) works.
758 if orig_tar_id != None:
759 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));
762 # Is this an old upload superseded by a newer -sa upload? (See check_dsc() for details)
763 if legacy_source_untouchable.has_key(qid["files_id"]):
765 # First move the files to the new location
766 legacy_filename = qid["path"]+qid["filename"];
767 pool_location = utils.poolify (changes["source"], files[file]["component"]);
768 pool_filename = pool_location + os.path.basename(qid["filename"]);
769 destination = Cnf["Dir::PoolDir"] + pool_location
770 utils.move(legacy_filename, destination);
771 # Then Update the DB's files table
772 q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
774 # Install the files into the pool
775 for file in files.keys():
776 if files[file].has_key("byhand"):
778 destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
779 destdir = os.path.dirname(destination)
780 utils.move (file, destination)
781 install_bytes = install_bytes + float(files[file]["size"])
783 # Copy the .changes file across for suite which need it.
784 for suite in changes["distribution"].keys():
785 if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
786 utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
788 projectB.query("COMMIT WORK");
790 utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
792 install_count = install_count + 1;
794 if not Cnf["Dinstall::Options::No-Mail"]:
795 mail_message = """Return-Path: %s
798 Bcc: troup@auric.debian.org
799 Subject: %s INSTALLED
805 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
806 utils.send_mail (mail_message, "")
807 announce (short_summary, 1)
809 #####################################################################################################################
811 def stable_install (changes_filename, summary, short_summary):
812 global install_count, install_bytes
814 print "Installing to stable."
816 archive = utils.where_am_i();
818 # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
819 projectB.query("BEGIN WORK");
821 # Add the .dsc file to the DB
822 for file in files.keys():
823 if files[file]["type"] == "dsc":
824 package = dsc["source"]
825 version = dsc["version"] # NB: not files[file]["version"], that has no epoch
826 q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
829 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version));
831 source_id = ql[0][0];
832 suite_id = db_access.get_suite_id('proposed-updates');
833 projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
834 suite_id = db_access.get_suite_id('stable');
835 projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
837 # Add the .deb files to the DB
838 for file in files.keys():
839 if files[file]["type"] == "deb":
840 package = files[file]["package"]
841 version = files[file]["version"]
842 architecture = files[file]["architecture"]
843 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))
846 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture));
848 binary_id = ql[0][0];
849 suite_id = db_access.get_suite_id('proposed-updates');
850 projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
851 suite_id = db_access.get_suite_id('stable');
852 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
854 projectB.query("COMMIT WORK");
856 utils.move (changes_filename, Cnf["Rhona::Morgue"] + os.path.basename(changes_filename));
858 # Update the Stable ChangeLog file
860 new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
861 changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
862 if os.path.exists(new_changelog_filename):
863 os.unlink (new_changelog_filename);
865 new_changelog = utils.open_file(new_changelog_filename, 'w');
866 for file in files.keys():
867 if files[file]["type"] == "deb":
868 new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
869 elif re_issource.match(file) != None:
870 new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
872 new_changelog.write("%s\n" % (file));
873 chop_changes = re_fdnic.sub("\n", changes["changes"]);
874 new_changelog.write(chop_changes + '\n\n');
875 if os.access(changelog_filename, os.R_OK) != 0:
876 changelog = utils.open_file(changelog_filename, 'r');
877 new_changelog.write(changelog.read());
878 new_changelog.close();
879 if os.access(changelog_filename, os.R_OK) != 0:
880 os.unlink(changelog_filename);
881 utils.move(new_changelog_filename, changelog_filename);
883 install_count = install_count + 1;
885 if not Cnf["Dinstall::Options::No-Mail"]:
886 mail_message = """Return-Path: %s
889 Bcc: troup@auric.debian.org
890 Subject: %s INSTALLED into stable
896 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
897 utils.send_mail (mail_message, "")
898 announce (short_summary, 1)
900 #####################################################################################################################
902 def reject (changes_filename, manual_reject_mail_filename):
905 base_changes_filename = os.path.basename(changes_filename);
906 reason_filename = re_changes.sub("reason", base_changes_filename);
907 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
909 # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
911 utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
912 except utils.cant_overwrite_exc:
913 sys.stderr.write("W: couldn't overwrite existing file '%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
915 for file in files.keys():
916 if os.path.exists(file):
918 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
919 except utils.cant_overwrite_exc:
920 sys.stderr.write("W: couldn't overwrite existing file '%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
923 # If this is not a manual rejection generate the .reason file and rejection mail message
924 if manual_reject_mail_filename == "":
925 if os.path.exists(reject_filename):
926 os.unlink(reject_filename);
927 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
928 os.write(fd, reject_message);
930 reject_mail_message = """From: %s
932 Bcc: troup@auric.debian.org
937 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, reject_footer);
938 else: # Have a manual rejection file to use
939 reject_mail_message = ""; # avoid <undef>'s
941 # Send the rejection mail if appropriate
942 if not Cnf["Dinstall::Options::No-Mail"]:
943 utils.send_mail (reject_mail_message, manual_reject_mail_filename);
945 ##################################################################
947 def manual_reject (changes_filename):
948 # Build up the rejection email
949 user_email_address = string.replace(string.split(pwd.getpwuid(os.getuid())[4],',')[0], '.', '')
950 user_email_address = user_email_address + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
951 manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
953 reject_mail_message = """From: %s
956 Bcc: troup@auric.debian.org
962 %s""" % (user_email_address, Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), manual_reject_message, reject_message, reject_footer)
964 # Write the rejection email out as the <foo>.reason file
965 reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
966 reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
967 if os.path.exists(reject_filename):
968 os.unlink(reject_filename);
969 fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
970 os.write(fd, reject_mail_message);
973 # If we weren't given one, spawn an editor so the user can add one in
974 if manual_reject_message == "":
975 result = os.system("vi +6 %s" % (reject_file))
977 sys.stderr.write ("vi invocation failed for `%s'!" % (reject_file))
980 # Then process it as if it were an automatic rejection
981 reject (changes_filename, reject_filename)
983 #####################################################################################################################
985 def acknowledge_new (changes_filename, summary):
988 changes_filename = os.path.basename(changes_filename);
990 new_ack_new[changes_filename] = 1;
992 if new_ack_old.has_key(changes_filename):
993 print "Ack already sent.";
996 print "Sending new ack.";
997 if not Cnf["Dinstall::Options::No-Mail"]:
998 new_ack_message = """Return-Path: %s
1001 Bcc: troup@auric.debian.org
1005 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes_filename, summary, new_ack_footer);
1006 utils.send_mail(new_ack_message,"");
1008 #####################################################################################################################
1010 def announce (short_summary, action):
1011 # Only do announcements for source uploads with a recent dpkg-dev installed
1012 if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
1018 for dist in changes["distribution"].keys():
1019 list = Cnf.Find("Suite::%s::Announce" % (dist))
1020 if list == None or lists_done.has_key(list):
1022 lists_done[list] = 1
1023 summary = summary + "Announcing to %s\n" % (list)
1026 mail_message = """Return-Path: %s
1029 Bcc: troup@auric.debian.org
1030 Subject: Installed %s %s (%s)
1036 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], list, changes["source"], changes["version"], string.join(changes["architecture"].keys(), ' ' ),
1037 changes["filecontents"], short_summary)
1038 utils.send_mail (mail_message, "")
1040 (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
1041 bugs = changes["closes"].keys()
1043 if dsc_name == changes["maintainername"]:
1044 summary = summary + "Closing bugs: "
1046 summary = summary + "%s " % (bug)
1048 mail_message = """Return-Path: %s
1050 To: %s-close@bugs.debian.org
1051 Bcc: troup@auric.debian.org
1052 Subject: Bug#%s: fixed in %s %s
1054 We believe that the bug you reported is fixed in the latest version of
1055 %s, which has been installed in the Debian FTP archive:
1057 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], bug, bug, changes["source"], changes["version"], changes["source"], short_summary)
1059 if changes["distribution"].has_key("stable"):
1060 mail_message = mail_message + """Note that this package is not part of the released stable Debian
1061 distribution. It may have dependencies on other unreleased software,
1062 or other instabilities. Please take care if you wish to install it.
1063 The update will eventually make its way into the next released Debian
1066 mail_message = mail_message + """A summary of the changes between this version and the previous one is
1069 Thank you for reporting the bug, which will now be closed. If you
1070 have further comments please address them to %s@bugs.debian.org,
1071 and the maintainer will reopen the bug report if appropriate.
1073 Debian distribution maintenance software
1075 %s (supplier of updated %s package)
1077 (This message was generated automatically at their request; if you
1078 believe that there is a problem with it please contact the archive
1079 administrators by mailing ftpmaster@debian.org)
1082 %s""" % (bug, changes["maintainer"], changes["source"], changes["filecontents"])
1084 utils.send_mail (mail_message, "")
1086 summary = summary + "Setting bugs to severity fixed: "
1087 control_message = ""
1089 summary = summary + "%s " % (bug)
1090 control_message = control_message + "severity %s fixed\n" % (bug)
1091 if action and control_message != "":
1092 mail_message = """Return-Path: %s
1094 To: control@bugs.debian.org
1095 Bcc: troup@auric.debian.org, %s
1096 Subject: Fixed in NMU of %s %s
1101 This message was generated automatically in response to a
1102 non-maintainer upload. The .changes file follows.
1105 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes["maintainer822"], changes["source"], changes["version"], control_message, changes["filecontents"])
1106 utils.send_mail (mail_message, "")
1107 summary = summary + "\n"
1111 ###############################################################################
1113 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1114 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1115 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1116 # processed it during it's checks of -2. If -1 has been deleted or
1117 # otherwise not checked by da-install, the .orig.tar.gz will not have
1118 # been checked at all. To get round this, we force the .orig.tar.gz
1119 # into the .changes structure and reprocess the .changes file.
1121 def process_it (changes_file):
1122 global reprocess, orig_tar_id, changes, dsc, dsc_files, files;
1126 # Reset some globals
1132 legacy_source_untouchable = {};
1134 # Absolutize the filename to avoid the requirement of being in the
1135 # same directory as the .changes file.
1136 changes_file = os.path.abspath(changes_file);
1138 # And since handling of installs to stable munges with the CWD;
1139 # save and restore it.
1142 check_signature (changes_file);
1143 check_changes (changes_file);
1151 action(changes_file);
1156 ###############################################################################
1159 global Cnf, projectB, reject_message, install_bytes, new_ack_old
1163 Cnf = apt_pkg.newConfiguration();
1164 apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1166 Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1167 ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1168 ('h',"help","Dinstall::Options::Help"),
1169 ('k',"ack-new","Dinstall::Options::Ack-New"),
1170 ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1171 ('n',"no-action","Dinstall::Options::No-Action"),
1172 ('p',"no-lock", "Dinstall::Options::No-Lock"),
1173 ('r',"no-version-check", "Dinstall::Options::No-Version-Check"),
1174 ('s',"no-mail", "Dinstall::Options::No-Mail"),
1175 ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1176 ('v',"version","Dinstall::Options::Version")];
1178 changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1180 if Cnf["Dinstall::Options::Help"]:
1183 if Cnf["Dinstall::Options::Version"]:
1184 print "katie version 0.0000000000";
1187 postgresql_user = None; # Default == Connect as user running program.
1189 # -n/--dry-run invalidates some other options which would involve things happening
1190 if Cnf["Dinstall::Options::No-Action"]:
1191 Cnf["Dinstall::Options::Automatic"] = ""
1192 Cnf["Dinstall::Options::Ack-New"] = ""
1193 postgresql_user = Cnf["DB::ROUser"];
1195 projectB = pg.connect('projectb', Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1197 db_access.init(Cnf, projectB);
1199 # Check that we aren't going to clash with the daily cron job
1201 if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1202 sys.stderr.write("Archive maintenance in progress. Try again later.\n");
1205 # Obtain lock if not in no-action mode
1207 if not Cnf["Dinstall::Options::No-Action"]:
1208 lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1209 fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1211 # Read in the list of already-acknowledged NEW packages
1212 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1214 for line in new_ack_list.readlines():
1215 new_ack_old[line[:-1]] = 1;
1216 new_ack_list.close();
1218 # Process the changes files
1219 for changes_file in changes_files:
1221 print "\n" + changes_file;
1222 process_it (changes_file);
1226 if install_count > 1:
1228 sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1230 # Write out the list of already-acknowledged NEW packages
1231 if Cnf["Dinstall::Options::Ack-New"]:
1232 new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1233 for i in new_ack_new.keys():
1234 new_ack_list.write(i+'\n')
1235 new_ack_list.close()
1238 if __name__ == '__main__':