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