]> git.decadent.org.uk Git - dak.git/blob - katie
Map testing to unstable
[dak.git] / katie
1 #!/usr/bin/env python
2
3 # Installs Debian packaes
4 # Copyright (C) 2000  James Troup <james@nocrew.org>
5 # $Id: katie,v 1.16 2000-12-20 08:25:56 troup Exp $
6
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.
11
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.
16
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
20
21 # Based (almost entirely) on dinstall by Guy Maor <maor@debian.org>
22
23 #########################################################################################
24
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."
28 #
29 #    Kyle: "But Cartman, we're trying to..."
30 #
31 #    Cartman: "uhh.. screw you guys... home."
32
33 #########################################################################################
34
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
38
39 ###############################################################################
40
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");
53
54 ###############################################################################
55
56 #
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.
59
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/."""
64 #
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.
68
69 You may have gotten the distribution wrong.  You'll get warnings above
70 if files already exist in other distributions."""
71 #
72 installed_footer = """If the override file requires editing, file a bug on ftp.debian.org.
73
74 Thank you for your contribution to Debian GNU."""
75
76 #########################################################################################
77
78 # Globals
79 Cnf = None;
80 reject_message = "";
81 changes = {};
82 dsc = {};
83 dsc_files = {};
84 files = {};
85 projectB = None;
86 new_ack_new = {};
87 new_ack_old = {};
88 overrides = {};
89 install_count = 0;
90 install_bytes = 0.0;
91 reprocess = 0;
92 orig_tar_id = None;
93
94 #########################################################################################
95
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'"""
106     sys.exit(exit_code)
107
108 def check_signature (filename):
109     global reject_message
110
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))
112     if (result != 0):
113         reject_message = "Rejected: GPG signature check failed on `%s'.\n%s\n" % (filename, output)
114         return 0
115     return 1
116
117 #####################################################################################################################
118
119 def read_override_file (filename, suite, component, binary_type):
120     global overrides;
121
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
128     file.close()
129
130
131 # See if a given package is in the override file.  Caches and only loads override files on demand.
132
133 def in_override_p (package, component, suite, binary_type):
134     global overrides;
135
136     if binary_type == "" or binary_type == "deb":
137         binary_type = "-";
138
139     # Avoid <undef> on unknown distributions
140     if db_access.get_suite_id(suite) == -1:
141         return None;
142
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);
156         else: # all others.
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);
160             else:
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);
164
165     return overrides[suite][component][binary_type].get(package, None);
166
167 #####################################################################################################################
168
169 def check_changes(filename):
170     global reject_message, changes, files
171
172     # Parse the .changes field into a dictionary [FIXME - need to trap errors, pass on to reject_message etc.]
173     try:
174         changes = utils.parse_changes(filename)
175     except utils.cant_open_exc:
176         reject_message = "Rejected: can't read changes file '%s'.\n" % (filename)
177         return 0;
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"];
181         return 0;
182
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, "")
185
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
191
192     # Fix the Maintainer: field to be RFC822 compatible
193     (changes["maintainer822"], changes["maintainername"], changes["maintaineremail"]) = utils.fix_maintainer (changes["maintainer"])
194
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"]
199
200     # Split multi-value fields into a lower-level dictionary
201     for i in ("architecture", "distribution", "binary", "closes"):
202         o = changes.get(i, "")
203         if o != "":
204             del changes[i]
205         changes[i] = {}
206         for j in string.split(o):
207             changes[i][j] = 1
208
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)
214
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"
219
220     # Map testing to unstable
221     if changes["distribution"].has_key("testing"):
222         del changes["distribution"]["testing"]
223         reject_message = reject_message + "Mapping testing to unstable.\n"
224
225     # Ensure target distributions exist
226     for i in changes["distribution"].keys():
227         if not Cnf.has_key("Suite::%s" % (i)):
228             reject_message = reject_message + "Rejected: Unknown distribution `%s'.\n" % (i)
229
230     # Map unreleased arches from stable to unstable
231     if changes["distribution"].has_key("stable"):
232         for i in changes["architecture"].keys():
233             if not Cnf.has_key("Suite::Stable::Architectures::%s" % (i)):
234                 reject_message = reject_message + "Mapping stable to unstable for unreleased arch `%s'.\n" % (i)
235                 del changes["distribution"]["stable"]
236     
237     # Map arches not being released from frozen to unstable
238     if changes["distribution"].has_key("frozen"):
239         for i in changes["architecture"].keys():
240             if not Cnf.has_key("Suite::Frozen::Architectures::%s" % (i)):
241                 reject_message = reject_message + "Mapping frozen to unstable for non-releasing arch `%s'.\n" % (i)
242                 del changes["distribution"]["frozen"]
243
244     # Handle uploads to stable
245     if changes["distribution"].has_key("stable"):
246         # If running from within proposed-updates; assume an install to stable
247         if string.find(os.getcwd(), 'proposed-updates') != -1:
248             # FIXME: should probably remove anything that != stable
249             for i in ("frozen", "unstable"):
250                 if changes["distribution"].has_key(i):
251                     reject_message = reject_message + "Removing %s from distribution list.\n"
252                     del changes["distribution"][i]
253             changes["stable upload"] = 1;
254             # If we can't find a file from the .changes; assume it's a package already in the pool and move into the pool
255             file = files.keys()[0];
256             if os.access(file, os.R_OK) == 0:
257                 pool_dir = Cnf["Dir::PoolDir"] + '/' + utils.poolify(changes["source"], files[file]["component"]);
258                 os.chdir(pool_dir);
259         # Otherwise (normal case) map stable to updates
260         else:
261             reject_message = reject_message + "Mapping stable to updates.\n";
262             del changes["distribution"]["stable"];
263             changes["distribution"]["proposed-updates"] = 1;
264
265     # chopversion = no epoch; chopversion2 = no epoch and no revision (e.g. for .orig.tar.gz comparison)
266     changes["chopversion"] = utils.re_no_epoch.sub('', changes["version"])
267     changes["chopversion2"] = utils.re_no_revision.sub('', changes["chopversion"])
268     
269     if string.find(reject_message, "Rejected:") != -1:
270         return 0
271     else: 
272         return 1
273
274 def check_files():
275     global reject_message
276     
277     archive = utils.where_am_i();
278
279     for file in files.keys():
280         # Check the file is readable
281         if os.access(file,os.R_OK) == 0:
282             reject_message = reject_message + "Rejected: Can't read `%s'.\n" % (file)
283             files[file]["type"] = "unreadable";
284             continue
285         # If it's byhand skip remaining checks
286         if files[file]["section"] == "byhand":
287             files[file]["byhand"] = 1;
288             files[file]["type"] = "byhand";
289         # Checks for a binary package...
290         elif re_isadeb.match(file) != None:
291             # Extract package information using dpkg-deb
292             control = apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))
293
294             # Check for mandatory fields
295             if control.Find("Package") == None:
296                 reject_message = reject_message + "Rejected: %s: No package field in control.\n" % (file)
297             if control.Find("Architecture") == None:
298                 reject_message = reject_message + "Rejected: %s: No architecture field in control.\n" % (file)
299             if control.Find("Version") == None:
300                 reject_message = reject_message + "Rejected: %s: No version field in control.\n" % (file)
301                 
302             # Ensure the package name matches the one give in the .changes
303             if not changes["binary"].has_key(control.Find("Package", "")):
304                 reject_message = reject_message + "Rejected: %s: control file lists name as `%s', which isn't in changes file.\n" % (file, control.Find("Package", ""))
305
306             # Validate the architecture
307             if not Cnf.has_key("Suite::Unstable::Architectures::%s" % (control.Find("Architecture", ""))):
308                 reject_message = reject_message + "Rejected: Unknown architecture '%s'.\n" % (control.Find("Architecture", ""))
309
310             # Check the architecture matches the one given in the .changes
311             if not changes["architecture"].has_key(control.Find("Architecture", "")):
312                 reject_message = reject_message + "Rejected: %s: control file lists arch as `%s', which isn't in changes file.\n" % (file, control.Find("Architecture", ""))
313             # Check the section & priority match those given in the .changes (non-fatal)
314             if control.Find("Section") != None and files[file]["section"] != "" and files[file]["section"] != control.Find("Section"):
315                 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"])
316             if control.Find("Priority") != None and files[file]["priority"] != "" and files[file]["priority"] != control.Find("Priority"):
317                 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"])
318
319             epochless_version = utils.re_no_epoch.sub('', control.Find("Version", ""))
320
321             files[file]["package"] = control.Find("Package");
322             files[file]["architecture"] = control.Find("Architecture");
323             files[file]["version"] = control.Find("Version");
324             files[file]["maintainer"] = control.Find("Maintainer", "");
325             if file[-5:] == ".udeb":
326                 files[file]["dbtype"] = "udeb";
327             elif file[-4:] == ".deb":
328                 files[file]["dbtype"] = "deb";
329             else:
330                 reject_message = reject_message + "Rejected: %s is neither a .deb or a .udeb.\n " % (file);
331             files[file]["type"] = "deb";
332             files[file]["fullname"] = "%s_%s_%s.deb" % (control.Find("Package", ""), epochless_version, control.Find("Architecture", ""))
333             files[file]["source"] = control.Find("Source", "");
334             if files[file]["source"] == "":
335                 files[file]["source"] = files[file]["package"];
336         # Checks for a source package...
337         else:
338             m = re_issource.match(file)
339             if m != None:
340                 files[file]["package"] = m.group(1)
341                 files[file]["version"] = m.group(2)
342                 files[file]["type"] = m.group(3)
343                 
344                 # Ensure the source package name matches the Source filed in the .changes
345                 if changes["source"] != files[file]["package"]:
346                     reject_message = reject_message + "Rejected: %s: changes file doesn't say %s for Source\n" % (file, files[file]["package"])
347
348                 # Ensure the source version matches the version in the .changes file
349                 if files[file]["type"] == "orig.tar.gz":
350                     changes_version = changes["chopversion2"]
351                 else:
352                     changes_version = changes["chopversion"]
353                 if changes_version != files[file]["version"]:
354                     reject_message = reject_message + "Rejected: %s: should be %s according to changes file.\n" % (file, changes_version)
355
356                 # Ensure the .changes lists source in the Architecture field
357                 if not changes["architecture"].has_key("source"):
358                     reject_message = reject_message + "Rejected: %s: changes file doesn't list `source' in Architecture field.\n" % (file)
359
360                 # Check the signature of a .dsc file
361                 if files[file]["type"] == "dsc":
362                     check_signature(file)
363
364                 files[file]["fullname"] = file
365
366             # Not a binary or source package?  Assume byhand...
367             else:
368                 files[file]["byhand"] = 1;
369                 files[file]["type"] = "byhand";
370                 
371         files[file]["oldfiles"] = {}
372         for suite in changes["distribution"].keys():
373             # Skip byhand
374             if files[file].has_key("byhand"):
375                 continue
376
377             if Cnf.has_key("Suite:%s::Components" % (suite)) and not Cnf.has_key("Suite::%s::Components::%s" % (suite, files[file]["component"])):
378                 reject_message = reject_message + "Rejected: unknown component `%s' for suite `%s'.\n" % (files[file]["component"], suite)
379                 continue
380
381             # See if the package is NEW
382             if not in_override_p(files[file]["package"], files[file]["component"], suite, files[file].get("dbtype","")):
383                 files[file]["new"] = 1
384                 
385             # Find any old binary packages
386             if files[file]["type"] == "deb":
387                 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"
388                                    % (files[file]["package"], suite, files[file]["architecture"]))
389                 oldfiles = q.dictresult()
390                 for oldfile in oldfiles:
391                     files[file]["oldfiles"][suite] = oldfile
392                     # Check versions [NB: per-suite only; no cross-suite checking done (yet)]
393                     if apt_pkg.VersionCompare(files[file]["version"], oldfile["version"]) != 1:
394                         if Cnf["Dinstall::Options::No-Version-Check"]:
395                             reject_message = reject_message + "Overriden rejection"
396                         else:
397                             reject_message = reject_message + "Rejected"
398                         reject_message = reject_message + ": %s Old version `%s' >= new version `%s'.\n" % (file, oldfile["version"], files[file]["version"])
399                 # Check for existing copies of the file
400                 if not changes.has_key("stable upload"):
401                     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"]))
402                     if q.getresult() != []:
403                         reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (file)
404
405             # Find any old .dsc files
406             elif files[file]["type"] == "dsc":
407                 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"
408                                    % (files[file]["package"], suite))
409                 oldfiles = q.dictresult()
410                 if len(oldfiles) >= 1:
411                     files[file]["oldfiles"][suite] = oldfiles[0]
412
413             # Validate the component
414             component = files[file]["component"];
415             component_id = db_access.get_component_id(component);
416             if component_id == -1:
417                 reject_message = reject_message + "Rejected: file '%s' has unknown component '%s'.\n" % (file, component);
418                 continue;
419             
420             # Check the md5sum & size against existing files (if any)
421             location = Cnf["Dir::PoolDir"];
422             files[file]["location id"] = db_access.get_location_id (location, component, archive);
423             
424             files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"]);
425             files_id = db_access.get_files_id(files[file]["pool name"] + file, files[file]["size"], files[file]["md5sum"], files[file]["location id"]);
426             if files_id == -1:
427                 reject_message = reject_message + "Rejected: INTERNAL ERROR, get_files_id() returned multiple matches for %s.\n" % (file)
428             elif files_id == -2:
429                 reject_message = reject_message + "Rejected: md5sum and/or size mismatch on existing copy of %s.\n" % (file)
430             files[file]["files id"] = files_id
431
432             # Check for packages that have moved from one component to another
433             if files[file]["oldfiles"].has_key(suite) and files[file]["oldfiles"][suite]["name"] != files[file]["component"]:
434                 files[file]["othercomponents"] = files[file]["oldfiles"][suite]["name"];
435
436                 
437     if string.find(reject_message, "Rejected:") != -1:
438         return 0
439     else: 
440         return 1
441
442 ###############################################################################
443
444 def check_dsc ():
445     global dsc, dsc_files, reject_message, reprocess, orig_tar_id;
446
447     for file in files.keys():
448         if files[file]["type"] == "dsc":
449             try:
450                 dsc = utils.parse_changes(file)
451             except utils.cant_open_exc:
452                 reject_message = reject_message + "Rejected: can't read changes file '%s'.\n" % (filename)
453                 return 0;
454             except utils.changes_parse_error_exc, line:
455                 reject_message = reject_message + "Rejected: error parsing changes file '%s', can't grok: %s.\n" % (filename, line)
456                 return 0;
457             try:
458                 dsc_files = utils.build_file_list(dsc, 1)
459             except utils.no_files_exc:
460                 reject_message = reject_message + "Rejected: no Files: field in .dsc file.\n";
461                 continue;
462
463             # Try and find all files mentioned in the .dsc.  This has
464             # to work harder to cope with the multiple possible
465             # locations of an .orig.tar.gz.
466             for dsc_file in dsc_files.keys():
467                 if files.has_key(dsc_file):
468                     actual_md5 = files[dsc_file]["md5sum"];
469                     actual_size = int(files[dsc_file]["size"]);
470                     found = "%s in incoming" % (dsc_file)
471                     # Check the file does not already exist in the archive
472                     if not changes.has_key("stable upload"):
473                         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)));
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":
477                     # Check in the pool
478                     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)));
479                     ql = q.getresult();
480                     if len(ql) > 0:
481                         old_file = ql[0][0] + ql[0][1];
482                         actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
483                         actual_size = os.stat(old_file)[stat.ST_SIZE];
484                         found = old_file;
485                         suite_type = ql[0][2];
486                         dsc_files[dsc_file]["files id"] = ql[0][3]; # need this for updating dsc_files in install()
487                         # See install()...
488                         if suite_type == "legacy" or suite_type == "legacy-mixed":
489                             orig_tar_id = ql[0][3];
490                     else:
491                         # Not there? Check in Incoming...
492                         # [See comment above process_it() for explanation
493                         #  of why this is necessary...]
494                         if os.access(dsc_file, os.R_OK) != 0:
495                             files[dsc_file] = {};
496                             files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
497                             files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
498                             files[dsc_file]["section"] = files[file]["section"];
499                             files[dsc_file]["priority"] = files[file]["priority"];
500                             files[dsc_file]["component"] = files[file]["component"];
501                             reprocess = 1;
502                             return 1;
503                         else:
504                             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);
505                             continue;
506                 else:
507                     reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming." % (file, dsc_file);
508                     continue;
509                 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
510                     reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file);
511                 if actual_size != int(dsc_files[dsc_file]["size"]):
512                     reject_message = reject_message + "Rejected: size for %s doesn't match %s.\n" % (found, file);
513
514     if string.find(reject_message, "Rejected:") != -1:
515         return 0
516     else: 
517         return 1
518
519 ###############################################################################
520
521 def check_md5sums ():
522     global reject_message;
523
524     for file in files.keys():
525         try:
526             file_handle = utils.open_file(file,"r");
527         except utils.cant_open_exc:
528             pass;
529         else:
530             if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
531                 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
532
533 #####################################################################################################################
534
535 def action (changes_filename):
536     byhand = confirm = suites = summary = new = "";
537
538     # changes["distribution"] may not exist in corner cases
539     # (e.g. unreadable changes files)
540     if not changes.has_key("distribution"):
541         changes["distribution"] = {};
542     
543     for suite in changes["distribution"].keys():
544         if Cnf.has_key("Suite::%s::Confirm"):
545             confirm = confirm + suite + ", "
546         suites = suites + suite + ", "
547     confirm = confirm[:-2]
548     suites = suites[:-2]
549
550     for file in files.keys():
551         if files[file].has_key("byhand"):
552             byhand = 1
553             summary = summary + file + " byhand\n"
554         elif files[file].has_key("new"):
555             new = 1
556             summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
557             if files[file].has_key("othercomponents"):
558                 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
559             if files[file]["type"] == "deb":
560                 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
561         else:
562             files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
563             destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
564             summary = summary + file + "\n  to " + destination + "\n"
565
566     short_summary = summary;
567
568     # This is for direport's benefit...
569     f = re_fdnic.sub("\n .\n", changes.get("changes",""));
570
571     if confirm or byhand or new:
572         summary = summary + "Changes: " + f;
573
574     summary = summary + announce (short_summary, 0)
575     
576     (prompt, answer) = ("", "XXX")
577     if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
578         answer = 'S'
579
580     if string.find(reject_message, "Rejected") != -1:
581         if time.time()-os.path.getmtime(changes_filename) < 86400:
582             print "SKIP (too new)\n" + reject_message,;
583             prompt = "[S]kip, Manual reject, Quit ?";
584         else:
585             print "REJECT\n" + reject_message,;
586             prompt = "[R]eject, Manual reject, Skip, Quit ?";
587             if Cnf["Dinstall::Options::Automatic"]:
588                 answer = 'R';
589     elif new:
590         print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
591         prompt = "[S]kip, New ack, Manual reject, Quit ?";
592         if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
593             answer = 'N';
594     elif byhand:
595         print "BYHAND\n" + reject_message + summary,;
596         prompt = "[I]nstall, Manual reject, Skip, Quit ?";
597     elif confirm:
598         print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
599         prompt = "[I]nstall, Manual reject, Skip, Quit ?";
600     else:
601         print "INSTALL\n" + reject_message + summary,;
602         prompt = "[I]nstall, Manual reject, Skip, Quit ?";
603         if Cnf["Dinstall::Options::Automatic"]:
604             answer = 'I';
605
606     while string.find(prompt, answer) == -1:
607         print prompt,;
608         answer = utils.our_raw_input()
609         m = re_default_answer.match(prompt)
610         if answer == "":
611             answer = m.group(1)
612         answer = string.upper(answer[:1])
613
614     if answer == 'R':
615         reject (changes_filename, "");
616     elif answer == 'M':
617         manual_reject (changes_filename);
618     elif answer == 'I':
619         install (changes_filename, summary, short_summary);
620     elif answer == 'N':
621         acknowledge_new (changes_filename, summary);
622     elif answer == 'Q':
623         sys.exit(0)
624
625 #####################################################################################################################
626
627 def install (changes_filename, summary, short_summary):
628     global install_count, install_bytes
629
630     # Stable uploads are a special case
631     if changes.has_key("stable upload"):
632         stable_install (changes_filename, summary, short_summary);
633         return;
634     
635     print "Installing."
636
637     archive = utils.where_am_i();
638
639     # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
640     projectB.query("BEGIN WORK");
641
642     # Add the .dsc file to the DB
643     for file in files.keys():
644         if files[file]["type"] == "dsc":
645             package = dsc["source"]
646             version = dsc["version"]  # NB: not files[file]["version"], that has no epoch
647             maintainer = dsc["maintainer"]
648             maintainer = string.replace(maintainer, "'", "\\'")
649             maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
650             filename = files[file]["pool name"] + file;
651             dsc_location_id = files[file]["location id"];
652             if not files[file]["files id"]:
653                 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
654             projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
655                            % (package, version, maintainer_id, files[file]["files id"]))
656             
657             for suite in changes["distribution"].keys():
658                 suite_id = db_access.get_suite_id(suite);
659                 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
660
661             # Add the source files to the DB (files and dsc_files)
662             projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
663             for dsc_file in dsc_files.keys():
664                 filename = files[file]["pool name"] + dsc_file;
665                 # If the .orig.tar.gz is already in the pool, it's
666                 # files id is stored in dsc_files by check_dsc().
667                 files_id = dsc_files[dsc_file].get("files id", None);
668                 if files_id == None:
669                     files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
670                 # FIXME: needs to check for -1/-2 and or handle exception
671                 if files_id == None:
672                     files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
673                 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
674             
675     # Add the .deb files to the DB
676     for file in files.keys():
677         if files[file]["type"] == "deb":
678             package = files[file]["package"]
679             version = files[file]["version"]
680             maintainer = files[file]["maintainer"]
681             maintainer = string.replace(maintainer, "'", "\\'")
682             maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
683             architecture = files[file]["architecture"]
684             architecture_id = db_access.get_architecture_id (architecture);
685             type = files[file]["dbtype"];
686             component = files[file]["component"]
687             source = files[file]["source"]
688             source_version = ""
689             if string.find(source, "(") != -1:
690                 m = utils.re_extract_src_version.match(source)
691                 source = m.group(1)
692                 source_version = m.group(2)
693             if not source_version:
694                 source_version = version
695             filename = files[file]["pool name"] + file;
696             if not files[file]["files id"]:
697                 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
698             source_id = db_access.get_source_id (source, source_version);
699             if source_id:
700                 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
701                                % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
702             else:
703                 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
704                                % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
705             for suite in changes["distribution"].keys():
706                 suite_id = db_access.get_suite_id(suite);
707                 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
708
709     # If the .orig.tar.gz is in a legacy directory we need to poolify
710     # it, so that apt-get source (and anything else that goes by the
711     # "Directory:" field in the Sources.gz file) works.
712     if orig_tar_id != None:
713         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));
714         qd = q.dictresult();
715         for qid in qd:
716             # First move the files to the new location
717             legacy_filename = qid["path"]+qid["filename"];
718             pool_location = utils.poolify (changes["source"], files[file]["component"]);
719             pool_filename = pool_location + os.path.basename(qid["filename"]);
720             destination = Cnf["Dir::PoolDir"] + pool_location
721             utils.move(legacy_filename, destination);
722             # Then Update the DB's files table
723             q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
724
725     # Install the files into the pool
726     for file in files.keys():
727         if files[file].has_key("byhand"):
728             continue
729         destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
730         destdir = os.path.dirname(destination)
731         utils.move (file, destination)
732         install_bytes = install_bytes + float(files[file]["size"])
733
734     # Copy the .changes file across for suite which need it.
735     for suite in changes["distribution"].keys():
736         if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
737             utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
738
739     projectB.query("COMMIT WORK");
740
741     utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
742
743     install_count = install_count + 1;
744
745     if not Cnf["Dinstall::Options::No-Mail"]:
746         mail_message = """Return-Path: %s
747 From: %s
748 To: %s
749 Bcc: troup@auric.debian.org
750 Subject: %s INSTALLED
751
752 %s
753 Installing:
754 %s
755
756 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
757         utils.send_mail (mail_message, "")
758         announce (short_summary, 1)
759
760 #####################################################################################################################
761
762 def stable_install (changes_filename, summary, short_summary):
763     global install_count, install_bytes
764     
765     print "Installing to stable."
766
767     archive = utils.where_am_i();
768
769     # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
770     projectB.query("BEGIN WORK");
771
772     # Add the .dsc file to the DB
773     for file in files.keys():
774         if files[file]["type"] == "dsc":
775             package = dsc["source"]
776             version = dsc["version"]  # NB: not files[file]["version"], that has no epoch
777             q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
778             ql = q.getresult()
779             if ql == []:
780                 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version));
781                 sys.exit(1);
782             source_id = ql[0][0];
783             suite_id = db_access.get_suite_id('proposed-updates');
784             projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
785             suite_id = db_access.get_suite_id('stable');
786             projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
787                 
788     # Add the .deb files to the DB
789     for file in files.keys():
790         if files[file]["type"] == "deb":
791             package = files[file]["package"]
792             version = files[file]["version"]
793             architecture = files[file]["architecture"]
794             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))
795             ql = q.getresult()
796             if ql == []:
797                 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture));
798                 sys.exit(1);
799             binary_id = ql[0][0];
800             suite_id = db_access.get_suite_id('proposed-updates');
801             projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
802             suite_id = db_access.get_suite_id('stable');
803             projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
804
805     projectB.query("COMMIT WORK");
806
807     utils.move (changes_filename, Cnf["Rhona::Morgue"] + os.path.basename(changes_filename));
808
809     # Update the Stable ChangeLog file
810
811     new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
812     changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
813     if os.path.exists(new_changelog_filename):
814         os.unlink (new_changelog_filename);
815     
816     new_changelog = utils.open_file(new_changelog_filename, 'w');
817     for file in files.keys():
818         if files[file]["type"] == "deb":
819             new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
820         elif re_issource.match(file) != None:
821             new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
822         else:
823             new_changelog.write("%s\n" % (file));
824     chop_changes = re_fdnic.sub("\n", changes["changes"]);
825     new_changelog.write(chop_changes + '\n\n');
826     if os.access(changelog_filename, os.R_OK) != 0:
827         changelog = utils.open_file(changelog_filename, 'r');
828         new_changelog.write(changelog.read());
829     new_changelog.close();
830     if os.access(changelog_filename, os.R_OK) != 0:
831         os.unlink(changelog_filename);
832     utils.move(new_changelog_filename, changelog_filename);
833
834     install_count = install_count + 1;
835
836     if not Cnf["Dinstall::Options::No-Mail"]:
837         mail_message = """Return-Path: %s
838 From: %s
839 To: %s
840 Bcc: troup@auric.debian.org
841 Subject: %s INSTALLED into stable
842
843 %s
844 Installing:
845 %s
846
847 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
848         utils.send_mail (mail_message, "")
849         announce (short_summary, 1)
850
851 #####################################################################################################################
852
853 def reject (changes_filename, manual_reject_mail_filename):
854     print "Rejecting.\n"
855
856     base_changes_filename = os.path.basename(changes_filename);
857     reason_filename = re_changes.sub("reason", base_changes_filename);
858     reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
859
860     # Move the .changes files and it's contents into REJECT/ (if we can; errors are ignored)
861     try:
862         utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
863     except utils.cant_overwrite_exc:
864         sys.stderr.write("W: couldn't overwrite existing file '%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
865         pass;
866     for file in files.keys():
867         if os.path.exists(file):
868             try:
869                 utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
870             except utils.cant_overwrite_exc:
871                 sys.stderr.write("W: couldn't overwrite existing file '%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
872                 pass;
873
874     # If this is not a manual rejection generate the .reason file and rejection mail message
875     if manual_reject_mail_filename == "":
876         if os.path.exists(reject_filename):
877             os.unlink(reject_filename);
878         fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
879         os.write(fd, reject_message);
880         os.close(fd);
881         reject_mail_message = """From: %s
882 To: %s
883 Bcc: troup@auric.debian.org
884 Subject: %s REJECTED
885
886 %s
887 ===
888 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, reject_footer);
889     else: # Have a manual rejection file to use
890         reject_mail_message = ""; # avoid <undef>'s
891         
892     # Send the rejection mail if appropriate
893     if not Cnf["Dinstall::Options::No-Mail"]:
894         utils.send_mail (reject_mail_message, manual_reject_mail_filename);
895
896 ##################################################################
897
898 def manual_reject (changes_filename):
899     # Build up the rejection email 
900     user_email_address = string.replace(string.split(pwd.getpwuid(os.getuid())[4],',')[0], '.', '')
901     user_email_address = user_email_address + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
902     manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
903
904     reject_mail_message = """From: %s
905 Cc: %s
906 To: %s
907 Bcc: troup@auric.debian.org
908 Subject: %s REJECTED
909
910 %s
911 %s
912 ===
913 %s""" % (user_email_address, Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), manual_reject_message, reject_message, reject_footer)
914     
915     # Write the rejection email out as the <foo>.reason file
916     reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
917     reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
918     if os.path.exists(reject_filename):
919         os.unlink(reject_filename);
920     fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
921     os.write(fd, reject_mail_message);
922     os.close(fd);
923     
924     # If we weren't given one, spawn an editor so the user can add one in
925     if manual_reject_message == "":
926         result = os.system("vi +6 %s" % (reject_file))
927         if result != 0:
928             sys.stderr.write ("vi invocation failed for `%s'!" % (reject_file))
929             sys.exit(result)
930
931     # Then process it as if it were an automatic rejection
932     reject (changes_filename, reject_filename)
933
934 #####################################################################################################################
935  
936 def acknowledge_new (changes_filename, summary):
937     global new_ack_new;
938
939     changes_filename = os.path.basename(changes_filename);
940
941     new_ack_new[changes_filename] = 1;
942
943     if new_ack_old.has_key(changes_filename):
944         print "Ack already sent.";
945         return;
946
947     print "Sending new ack.";
948     if not Cnf["Dinstall::Options::No-Mail"]:
949         new_ack_message = """Return-Path: %s
950 From: %s
951 To: %s
952 Bcc: troup@auric.debian.org
953 Subject: %s is NEW
954
955 %s
956 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes_filename, summary, new_ack_footer);
957         utils.send_mail(new_ack_message,"");
958
959 #####################################################################################################################
960
961 def announce (short_summary, action):
962     # Only do announcements for source uploads with a recent dpkg-dev installed
963     if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
964         return ""
965
966     lists_done = {}
967     summary = ""
968
969     for dist in changes["distribution"].keys():
970         list = Cnf.Find("Suite::%s::Announce" % (dist))
971         if list == None or lists_done.has_key(list):
972             continue
973         lists_done[list] = 1
974         summary = summary + "Announcing to %s\n" % (list)
975
976         if action:
977             mail_message = """Return-Path: %s
978 From: %s
979 To: %s
980 Bcc: troup@auric.debian.org
981 Subject: Installed %s %s (%s)
982
983 %s
984
985 Installed:
986 %s
987 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], list, changes["source"], changes["version"], string.join(changes["architecture"].keys(), ' ' ),
988        changes["filecontents"], short_summary)
989             utils.send_mail (mail_message, "")
990
991     (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
992     bugs = changes["closes"].keys()
993     bugs.sort()
994     if dsc_name == changes["maintainername"]:
995         summary = summary + "Closing bugs: "
996         for bug in bugs:
997             summary = summary + "%s " % (bug)
998             if action:
999                 mail_message = """Return-Path: %s
1000 From: %s
1001 To: %s-close@bugs.debian.org
1002 Bcc: troup@auric.debian.org
1003 Subject: Bug#%s: fixed in %s %s
1004
1005 We believe that the bug you reported is fixed in the latest version of
1006 %s, which has been installed in the Debian FTP archive:
1007
1008 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], bug, bug, changes["source"], changes["version"], changes["source"], short_summary)
1009
1010                 if changes["distribution"].has_key("stable"):
1011                     mail_message = mail_message + """Note that this package is not part of the released stable Debian
1012 distribution.  It may have dependencies on other unreleased software,
1013 or other instabilities.  Please take care if you wish to install it.
1014 The update will eventually make its way into the next released Debian
1015 distribution."""
1016
1017                 mail_message = mail_message + """A summary of the changes between this version and the previous one is
1018 attached.
1019
1020 Thank you for reporting the bug, which will now be closed.  If you
1021 have further comments please address them to %s@bugs.debian.org,
1022 and the maintainer will reopen the bug report if appropriate.
1023
1024 Debian distribution maintenance software
1025 pp.
1026 %s (supplier of updated %s package)
1027
1028 (This message was generated automatically at their request; if you
1029 believe that there is a problem with it please contact the archive
1030 administrators by mailing ftpmaster@debian.org)
1031
1032
1033 %s""" % (bug, changes["maintainer"], changes["source"], changes["filecontents"])
1034
1035                 utils.send_mail (mail_message, "")
1036     else:                     # NMU
1037         summary = summary + "Setting bugs to severity fixed: "
1038         control_message = ""
1039         for bug in bugs:
1040             summary = summary + "%s " % (bug)
1041             control_message = control_message + "severity %s fixed\n" % (bug)
1042         if action and control_message != "":
1043             mail_message = """Return-Path: %s
1044 From: %s
1045 To: control@bugs.debian.org
1046 Bcc: troup@auric.debian.org, %s
1047 Subject: Fixed in NMU of %s %s
1048
1049 %s
1050 quit
1051
1052 This message was generated automatically in response to a
1053 non-maintainer upload.  The .changes file follows.
1054
1055 %s
1056 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes["maintainer822"], changes["source"], changes["version"], control_message, changes["filecontents"])
1057             utils.send_mail (mail_message, "")
1058     summary = summary + "\n"
1059
1060     return summary
1061
1062 ###############################################################################
1063
1064 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1065 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1066 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1067 # processed it during it's checks of -2.  If -1 has been deleted or
1068 # otherwise not checked by da-install, the .orig.tar.gz will not have
1069 # been checked at all.  To get round this, we force the .orig.tar.gz
1070 # into the .changes structure and reprocess the .changes file.
1071
1072 def process_it (changes_file):
1073     global reprocess, orig_tar_id, changes, dsc, dsc_files, files;
1074
1075     reprocess = 1;
1076     orig_tar_id = None;
1077     # Reset some globals
1078     changes = {};
1079     dsc = {};
1080     dsc_files = {};
1081     files = {};
1082     orig_tar_id = None;
1083
1084     # Absolutize the filename to avoid the requirement of being in the
1085     # same directory as the .changes file.
1086     changes_file = os.path.abspath(changes_file);
1087
1088     # And since handling of installs to stable munges with the CWD;
1089     # save and restore it.
1090     cwd = os.getcwd();
1091     
1092     check_signature (changes_file);
1093     check_changes (changes_file);
1094     while reprocess:
1095         reprocess = 0;
1096         check_files ();
1097         check_md5sums ();
1098         check_dsc ();
1099         
1100     action(changes_file);
1101
1102     # Restore CWD
1103     os.chdir(cwd);
1104
1105 ###############################################################################
1106
1107 def main():
1108     global Cnf, projectB, reject_message, install_bytes, new_ack_old
1109
1110     apt_pkg.init();
1111     
1112     Cnf = apt_pkg.newConfiguration();
1113     apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1114
1115     Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1116                  ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1117                  ('h',"help","Dinstall::Options::Help"),
1118                  ('k',"ack-new","Dinstall::Options::Ack-New"),
1119                  ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1120                  ('n',"no-action","Dinstall::Options::No-Action"),
1121                  ('p',"no-lock", "Dinstall::Options::No-Lock"),
1122                  ('r',"no-version-check", "Dinstall::Options::No-Version-Check"),
1123                  ('s',"no-mail", "Dinstall::Options::No-Mail"),
1124                  ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1125                  ('v',"version","Dinstall::Options::Version")];
1126     
1127     changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1128
1129     if Cnf["Dinstall::Options::Help"]:
1130         usage(0);
1131         
1132     if Cnf["Dinstall::Options::Version"]:
1133         print "katie version 0.0000000000";
1134         usage(0);
1135
1136     postgresql_user = None; # Default == Connect as user running program.
1137
1138     # -n/--dry-run invalidates some other options which would involve things happening
1139     if Cnf["Dinstall::Options::No-Action"]:
1140         Cnf["Dinstall::Options::Automatic"] = ""
1141         Cnf["Dinstall::Options::Ack-New"] = ""
1142         postgresql_user = Cnf["DB::ROUser"];
1143
1144     projectB = pg.connect('projectb', Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1145
1146     db_access.init(Cnf, projectB);
1147
1148     # Check that we aren't going to clash with the daily cron job
1149
1150     if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1151         sys.stderr.write("Archive maintenance in progress.  Try again later.\n");
1152         sys.exit(2);
1153     
1154     # Obtain lock if not in no-action mode
1155
1156     if not Cnf["Dinstall::Options::No-Action"]:
1157         lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1158         fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1159
1160     # Read in the list of already-acknowledged NEW packages
1161     new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1162     new_ack_old = {};
1163     for line in new_ack_list.readlines():
1164         new_ack_old[line[:-1]] = 1;
1165     new_ack_list.close();
1166
1167     # Process the changes files
1168     for changes_file in changes_files:
1169         reject_message = ""
1170         print "\n" + changes_file;
1171         process_it (changes_file);
1172
1173     if install_count:
1174         sets = "set"
1175         if install_count > 1:
1176             sets = "sets"
1177         sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1178
1179     # Write out the list of already-acknowledged NEW packages
1180     if Cnf["Dinstall::Options::Ack-New"]:
1181         new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1182         for i in new_ack_new.keys():
1183             new_ack_list.write(i+'\n')
1184         new_ack_list.close()
1185     
1186             
1187 if __name__ == '__main__':
1188     main()
1189