]> git.decadent.org.uk Git - dak.git/blob - katie
First working version of rhona.
[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.12 2000-12-18 07:11:25 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                     found = "%s in incoming" % (dsc_file)
465                     # Check the file does not already exist in the archive
466                     if not changes.has_key("stable upload"):
467                         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)));
468                         if q.getresult() != []:
469                             reject_message = reject_message + "Rejected: can not overwrite existing copy of '%s' already in the archive.\n" % (dsc_file)
470                 elif dsc_file[-12:] == ".orig.tar.gz":
471                     # Check in the pool
472                     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)));
473                     ql = q.getresult();
474                     if len(ql) > 0:
475                         old_file = ql[0][0] + ql[0][1];
476                         actual_md5 = apt_pkg.md5sum(utils.open_file(old_file,"r"));
477                         found = old_file;
478                         suite_type = ql[0][2];
479                         dsc_files[dsc_file]["files id"] = ql[0][3]; # need this for updating dsc_files in install()
480                         # See install()...
481                         if suite_type == "legacy" or suite_type == "legacy-mixed":
482                             orig_tar_id = ql[0][3];
483                     else:
484                         # Not there? Check in Incoming...
485                         # [See comment above process_it() for explanation
486                         #  of why this is necessary...]
487                         if os.access(dsc_file, os.R_OK) != 0:
488                             files[dsc_file] = {};
489                             files[dsc_file]["size"] = os.stat(dsc_file)[stat.ST_SIZE];
490                             files[dsc_file]["md5sum"] = dsc_files[dsc_file]["md5sum"];
491                             files[dsc_file]["section"] = files[file]["section"];
492                             files[dsc_file]["priority"] = files[file]["priority"];
493                             files[dsc_file]["component"] = files[file]["component"];
494                             reprocess = 1;
495                             return 1;
496                         else:
497                             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);
498                             continue;
499                 else:
500                     reject_message = reject_message + "Rejected: %s refers to %s, but I can't find it in Incoming." % (file, dsc_file);
501                     continue;
502                 if actual_md5 != dsc_files[dsc_file]["md5sum"]:
503                     reject_message = reject_message + "Rejected: md5sum for %s doesn't match %s.\n" % (found, file)
504
505     if string.find(reject_message, "Rejected:") != -1:
506         return 0
507     else: 
508         return 1
509
510 ###############################################################################
511
512 def check_md5sums ():
513     global reject_message;
514
515     for file in files.keys():
516         try:
517             file_handle = utils.open_file(file,"r");
518         except utils.cant_open_exc:
519             pass;
520         else:
521             if apt_pkg.md5sum(file_handle) != files[file]["md5sum"]:
522                 reject_message = reject_message + "Rejected: md5sum check failed for %s.\n" % (file);
523
524 #####################################################################################################################
525
526 def action (changes_filename):
527     byhand = confirm = suites = summary = new = "";
528
529     # changes["distribution"] may not exist in corner cases
530     # (e.g. unreadable changes files)
531     if not changes.has_key("distribution"):
532         changes["distribution"] = {};
533     
534     for suite in changes["distribution"].keys():
535         if Cnf.has_key("Suite::%s::Confirm"):
536             confirm = confirm + suite + ", "
537         suites = suites + suite + ", "
538     confirm = confirm[:-2]
539     suites = suites[:-2]
540
541     for file in files.keys():
542         if files[file].has_key("byhand"):
543             byhand = 1
544             summary = summary + file + " byhand\n"
545         elif files[file].has_key("new"):
546             new = 1
547             summary = summary + "(new) %s %s %s\n" % (file, files[file]["priority"], files[file]["section"])
548             if files[file].has_key("othercomponents"):
549                 summary = summary + "WARNING: Already present in %s distribution.\n" % (files[file]["othercomponents"])
550             if files[file]["type"] == "deb":
551                 summary = summary + apt_pkg.ParseSection(apt_inst.debExtractControl(utils.open_file(file,"r")))["Description"] + '\n';
552         else:
553             files[file]["pool name"] = utils.poolify (changes["source"], files[file]["component"])
554             destination = Cnf["Dir::PoolRoot"] + files[file]["pool name"] + file
555             summary = summary + file + "\n  to " + destination + "\n"
556
557     short_summary = summary;
558
559     # This is for direport's benefit...
560     f = re_fdnic.sub("\n .\n", changes.get("changes",""));
561
562     if confirm or byhand or new:
563         summary = summary + "Changes: " + f;
564
565     summary = summary + announce (short_summary, 0)
566     
567     (prompt, answer) = ("", "XXX")
568     if Cnf["Dinstall::Options::No-Action"] or Cnf["Dinstall::Options::Automatic"]:
569         answer = 'S'
570
571     if string.find(reject_message, "Rejected") != -1:
572         if time.time()-os.path.getmtime(changes_filename) < 86400:
573             print "SKIP (too new)\n" + reject_message,;
574             prompt = "[S]kip, Manual reject, Quit ?";
575         else:
576             print "REJECT\n" + reject_message,;
577             prompt = "[R]eject, Manual reject, Skip, Quit ?";
578             if Cnf["Dinstall::Options::Automatic"]:
579                 answer = 'R';
580     elif new:
581         print "NEW to %s\n%s%s" % (suites, reject_message, summary),;
582         prompt = "[S]kip, New ack, Manual reject, Quit ?";
583         if Cnf["Dinstall::Options::Automatic"] and Cnf["Dinstall::Options::Ack-New"]:
584             answer = 'N';
585     elif byhand:
586         print "BYHAND\n" + reject_message + summary,;
587         prompt = "[I]nstall, Manual reject, Skip, Quit ?";
588     elif confirm:
589         print "CONFIRM to %s\n%s%s" % (confirm, reject_message, summary),
590         prompt = "[I]nstall, Manual reject, Skip, Quit ?";
591     else:
592         print "INSTALL\n" + reject_message + summary,;
593         prompt = "[I]nstall, Manual reject, Skip, Quit ?";
594         if Cnf["Dinstall::Options::Automatic"]:
595             answer = 'I';
596
597     while string.find(prompt, answer) == -1:
598         print prompt,;
599         answer = utils.our_raw_input()
600         m = re_default_answer.match(prompt)
601         if answer == "":
602             answer = m.group(1)
603         answer = string.upper(answer[:1])
604
605     if answer == 'R':
606         reject (changes_filename, "");
607     elif answer == 'M':
608         manual_reject (changes_filename);
609     elif answer == 'I':
610         install (changes_filename, summary, short_summary);
611     elif answer == 'N':
612         acknowledge_new (changes_filename, summary);
613     elif answer == 'Q':
614         sys.exit(0)
615
616 #####################################################################################################################
617
618 def install (changes_filename, summary, short_summary):
619     global install_count, install_bytes
620
621     # Stable uploads are a special case
622     if changes.has_key("stable upload"):
623         stable_install (changes_filename, summary, short_summary);
624         return;
625     
626     print "Installing."
627
628     archive = utils.where_am_i();
629
630     # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
631     projectB.query("BEGIN WORK");
632
633     # Add the .dsc file to the DB
634     for file in files.keys():
635         if files[file]["type"] == "dsc":
636             package = dsc["source"]
637             version = dsc["version"]  # NB: not files[file]["version"], that has no epoch
638             maintainer = dsc["maintainer"]
639             maintainer = string.replace(maintainer, "'", "\\'")
640             maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
641             filename = files[file]["pool name"] + file;
642             dsc_location_id = files[file]["location id"];
643             if not files[file]["files id"]:
644                 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], dsc_location_id)
645             projectB.query("INSERT INTO source (source, version, maintainer, file) VALUES ('%s', '%s', %d, %d)"
646                            % (package, version, maintainer_id, files[file]["files id"]))
647             
648             for suite in changes["distribution"].keys():
649                 suite_id = db_access.get_suite_id(suite);
650                 projectB.query("INSERT INTO src_associations (suite, source) VALUES (%d, currval('source_id_seq'))" % (suite_id))
651
652             # Add the source files to the DB (files and dsc_files)
653             projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files[file]["files id"]));
654             for dsc_file in dsc_files.keys():
655                 filename = files[file]["pool name"] + dsc_file;
656                 # If the .orig.tar.gz is already in the pool, it's
657                 # files id is stored in dsc_files by check_dsc().
658                 files_id = dsc_files[dsc_file].get("files id", None);
659                 if files_id == None:
660                     files_id = db_access.get_files_id(filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
661                 # FIXME: needs to check for -1/-2 and or handle exception
662                 if files_id == None:
663                     files_id = db_access.set_files_id (filename, dsc_files[dsc_file]["size"], dsc_files[dsc_file]["md5sum"], dsc_location_id);
664                 projectB.query("INSERT INTO dsc_files (source, file) VALUES (currval('source_id_seq'), %d)" % (files_id));
665             
666     # Add the .deb files to the DB
667     for file in files.keys():
668         if files[file]["type"] == "deb":
669             package = files[file]["package"]
670             version = files[file]["version"]
671             maintainer = files[file]["maintainer"]
672             maintainer = string.replace(maintainer, "'", "\\'")
673             maintainer_id = db_access.get_or_set_maintainer_id(maintainer);
674             architecture = files[file]["architecture"]
675             architecture_id = db_access.get_architecture_id (architecture);
676             type = files[file]["dbtype"];
677             component = files[file]["component"]
678             source = files[file]["source"]
679             source_version = ""
680             if string.find(source, "(") != -1:
681                 m = utils.re_extract_src_version.match(source)
682                 source = m.group(1)
683                 source_version = m.group(2)
684             if not source_version:
685                 source_version = version
686             filename = files[file]["pool name"] + file;
687             if not files[file]["files id"]:
688                 files[file]["files id"] = db_access.set_files_id (filename, files[file]["size"], files[file]["md5sum"], files[file]["location id"])
689             source_id = db_access.get_source_id (source, source_version);
690             if source_id:
691                 projectB.query("INSERT INTO binaries (package, version, maintainer, source, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, %d, '%s')"
692                                % (package, version, maintainer_id, source_id, architecture_id, files[file]["files id"], type));
693             else:
694                 projectB.query("INSERT INTO binaries (package, version, maintainer, architecture, file, type) VALUES ('%s', '%s', %d, %d, %d, '%s')"
695                                % (package, version, maintainer_id, architecture_id, files[file]["files id"], type));
696             for suite in changes["distribution"].keys():
697                 suite_id = db_access.get_suite_id(suite);
698                 projectB.query("INSERT INTO bin_associations (suite, bin) VALUES (%d, currval('binaries_id_seq'))" % (suite_id));
699
700     # If the .orig.tar.gz is in a legacy directory we need to poolify
701     # it, so that apt-get source (and anything else that goes by the
702     # "Directory:" field in the Sources.gz file) works.
703     if orig_tar_id != None:
704         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));
705         qd = q.dictresult();
706         for qid in qd:
707             # First move the files to the new location
708             legacy_filename = qid["path"]+qid["filename"];
709             pool_location = utils.poolify (changes["source"], files[file]["component"]);
710             pool_filename = pool_location + os.path.basename(qid["filename"]);
711             destination = Cnf["Dir::PoolDir"] + pool_location
712             utils.move(legacy_filename, destination);
713             # Then Update the DB's files table
714             q = projectB.query("UPDATE files SET filename = '%s', location = '%s' WHERE id = '%s'" % (pool_filename, dsc_location_id, qid["files_id"]));
715
716     # Install the files into the pool
717     for file in files.keys():
718         if files[file].has_key("byhand"):
719             continue
720         destination = Cnf["Dir::PoolDir"] + files[file]["pool name"] + file
721         destdir = os.path.dirname(destination)
722         utils.move (file, destination)
723         install_bytes = install_bytes + float(files[file]["size"])
724
725     # Copy the .changes file across for suite which need it.
726     for suite in changes["distribution"].keys():
727         if Cnf.has_key("Suite::%s::CopyChanges" % (suite)):
728             utils.copy (changes_filename, Cnf["Dir::RootDir"] + Cnf["Suite::%s::CopyChanges" % (suite)]);
729
730     projectB.query("COMMIT WORK");
731
732     utils.move (changes_filename, Cnf["Dir::IncomingDir"] + 'DONE/' + os.path.basename(changes_filename))
733
734     install_count = install_count + 1;
735
736     if not Cnf["Dinstall::Options::No-Mail"]:
737         mail_message = """Return-Path: %s
738 From: %s
739 To: %s
740 Bcc: troup@auric.debian.org
741 Subject: %s INSTALLED
742
743 %s
744 Installing:
745 %s
746
747 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
748         utils.send_mail (mail_message, "")
749         announce (short_summary, 1)
750
751 #####################################################################################################################
752
753 def stable_install (changes_filename, summary, short_summary):
754     global install_count, install_bytes
755     
756     print "Installing to stable."
757
758     archive = utils.where_am_i();
759
760     # Begin a transaction; if we bomb out anywhere between here and the COMMIT WORK below, the DB will not be changed.
761     projectB.query("BEGIN WORK");
762
763     # Add the .dsc file to the DB
764     for file in files.keys():
765         if files[file]["type"] == "dsc":
766             package = dsc["source"]
767             version = dsc["version"]  # NB: not files[file]["version"], that has no epoch
768             q = projectB.query("SELECT id FROM source WHERE source = '%s' AND version = '%s'" % (package, version))
769             ql = q.getresult()
770             if ql == []:
771                 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s) in source table.\n" % (package, version));
772                 sys.exit(1);
773             source_id = ql[0][0];
774             suite_id = db_access.get_suite_id('proposed-updates');
775             projectB.query("DELETE FROM src_associations WHERE suite = '%s' AND source = '%s'" % (suite_id, source_id));
776             suite_id = db_access.get_suite_id('stable');
777             projectB.query("INSERT INTO src_associations (suite, source) VALUES ('%s', '%s')" % (suite_id, source_id));
778                 
779     # Add the .deb files to the DB
780     for file in files.keys():
781         if files[file]["type"] == "deb":
782             package = files[file]["package"]
783             version = files[file]["version"]
784             architecture = files[file]["architecture"]
785             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))
786             ql = q.getresult()
787             if ql == []:
788                 sys.stderr.write("INTERNAL ERROR: couldn't find '%s' (%s for %s architecture) in binaries table.\n" % (package, version, architecture));
789                 sys.exit(1);
790             binary_id = ql[0][0];
791             suite_id = db_access.get_suite_id('proposed-updates');
792             projectB.query("DELETE FROM bin_associations WHERE suite = '%s' AND bin = '%s'" % (suite_id, binary_id));
793             suite_id = db_access.get_suite_id('stable');
794             projectB.query("INSERT INTO bin_associations (suite, bin) VALUES ('%s', '%s')" % (suite_id, binary_id));
795
796     projectB.query("COMMIT WORK");
797
798     utils.move (changes_filename, Cnf["Rhona::Morgue"] + os.path.basename(changes_filename));
799
800     # Update the Stable ChangeLog file
801
802     new_changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + ".ChangeLog";
803     changelog_filename = Cnf["Dir::RootDir"] + Cnf["Suite::Stable::ChangeLogBase"] + "ChangeLog";
804     if os.path.exists(new_changelog_filename):
805         os.unlink (new_changelog_filename);
806     
807     new_changelog = utils.open_file(new_changelog_filename, 'w');
808     for file in files.keys():
809         if files[file]["type"] == "deb":
810             new_changelog.write("stable/%s/binary-%s/%s\n" % (files[file]["component"], files[file]["architecture"], file));
811         elif re_issource.match(file) != None:
812             new_changelog.write("stable/%s/source/%s\n" % (files[file]["component"], file));
813         else:
814             new_changelog.write("%s\n" % (file));
815     chop_changes = re_fdnic.sub("\n", changes["changes"]);
816     new_changelog.write(chop_changes + '\n\n');
817     if os.access(changelog_filename, os.R_OK) != 0:
818         changelog = utils.open_file(changelog_filename, 'r');
819         new_changelog.write(changelog.read());
820     new_changelog.close();
821     if os.access(changelog_filename, os.R_OK) != 0:
822         os.unlink(changelog_filename);
823     utils.move(new_changelog_filename, changelog_filename);
824
825     install_count = install_count + 1;
826
827     if not Cnf["Dinstall::Options::No-Mail"]:
828         mail_message = """Return-Path: %s
829 From: %s
830 To: %s
831 Bcc: troup@auric.debian.org
832 Subject: %s INSTALLED into stable
833
834 %s
835 Installing:
836 %s
837
838 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, summary, installed_footer)
839         utils.send_mail (mail_message, "")
840         announce (short_summary, 1)
841
842 #####################################################################################################################
843
844 def reject (changes_filename, manual_reject_mail_filename):
845     print "Rejecting.\n"
846
847     base_changes_filename = os.path.basename(changes_filename);
848     reason_filename = re_changes.sub("reason", base_changes_filename);
849     reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename);
850
851     # Move the .changes files and it's contents into REJECT/
852     utils.move (changes_filename, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], base_changes_filename));
853     for file in files.keys():
854         if os.access(file,os.R_OK) == 0:
855             utils.move (file, "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], file));
856
857     # If this is not a manual rejection generate the .reason file and rejection mail message
858     if manual_reject_mail_filename == "":
859         if os.path.exists(reject_filename):
860             os.unlink(reject_filename);
861         fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
862         os.write(fd, reject_message);
863         os.close(fd);
864         reject_mail_message = """From: %s
865 To: %s
866 Bcc: troup@auric.debian.org
867 Subject: %s REJECTED
868
869 %s
870 ===
871 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), reject_message, reject_footer);
872     else: # Have a manual rejection file to use
873         reject_mail_message = ""; # avoid <undef>'s
874         
875     # Send the rejection mail if appropriate
876     if not Cnf["Dinstall::Options::No-Mail"]:
877         utils.send_mail (reject_mail_message, manual_reject_mail_filename);
878
879 ##################################################################
880
881 def manual_reject (changes_filename):
882     # Build up the rejection email 
883     user_email_address = string.replace(string.split(pwd.getpwuid(os.getuid())[4],',')[0], '.', '')
884     user_email_address = user_email_address + " <%s@%s>" % (pwd.getpwuid(os.getuid())[0], Cnf["Dinstall::MyHost"])
885     manual_reject_message = Cnf.get("Dinstall::Options::Manual-Reject", "")
886
887     reject_mail_message = """From: %s
888 Cc: %s
889 To: %s
890 Bcc: troup@auric.debian.org
891 Subject: %s REJECTED
892
893 %s
894 %s
895 ===
896 %s""" % (user_email_address, Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], os.path.basename(changes_filename), manual_reject_message, reject_message, reject_footer)
897     
898     # Write the rejection email out as the <foo>.reason file
899     reason_filename = re_changes.sub("reason", os.path.basename(changes_filename));
900     reject_filename = "%s/REJECT/%s" % (Cnf["Dir::IncomingDir"], reason_filename)
901     if os.path.exists(reject_filename):
902         os.unlink(reject_filename);
903     fd = os.open(reject_filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0644);
904     os.write(fd, reject_mail_message);
905     os.close(fd);
906     
907     # If we weren't given one, spawn an editor so the user can add one in
908     if manual_reject_message == "":
909         result = os.system("vi +6 %s" % (reject_file))
910         if result != 0:
911             sys.stderr.write ("vi invocation failed for `%s'!" % (reject_file))
912             sys.exit(result)
913
914     # Then process it as if it were an automatic rejection
915     reject (changes_filename, reject_filename)
916
917 #####################################################################################################################
918  
919 def acknowledge_new (changes_filename, summary):
920     global new_ack_new;
921
922     changes_filename = os.path.basename(changes_filename);
923
924     new_ack_new[changes_filename] = 1;
925
926     if new_ack_old.has_key(changes_filename):
927         print "Ack already sent.";
928         return;
929
930     print "Sending new ack.";
931     if not Cnf["Dinstall::Options::No-Mail"]:
932         new_ack_message = """Return-Path: %s
933 From: %s
934 To: %s
935 Bcc: troup@auric.debian.org
936 Subject: %s is NEW
937
938 %s
939 %s""" % (Cnf["Dinstall::MyEmailAddress"], Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes_filename, summary, new_ack_footer);
940         utils.send_mail(new_ack_message,"");
941
942 #####################################################################################################################
943
944 def announce (short_summary, action):
945     # Only do announcements for source uploads with a recent dpkg-dev installed
946     if float(changes.get("format", 0)) < 1.6 or not changes["architecture"].has_key("source"):
947         return ""
948
949     lists_done = {}
950     summary = ""
951
952     for dist in changes["distribution"].keys():
953         list = Cnf.Find("Suite::%s::Announce" % (dist))
954         if list == None or lists_done.has_key(list):
955             continue
956         lists_done[list] = 1
957         summary = summary + "Announcing to %s\n" % (list)
958
959         if action:
960             mail_message = """Return-Path: %s
961 From: %s
962 To: %s
963 Bcc: troup@auric.debian.org
964 Subject: Installed %s %s (%s)
965
966 %s
967
968 Installed:
969 %s
970 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], list, changes["source"], changes["version"], string.join(changes["architecture"].keys(), ' ' ),
971        changes["filecontents"], short_summary)
972             utils.send_mail (mail_message, "")
973
974     (dsc_rfc822, dsc_name, dsc_email) = utils.fix_maintainer (dsc.get("maintainer",Cnf["Dinstall::MyEmailAddress"]));
975     bugs = changes["closes"].keys()
976     bugs.sort()
977     if dsc_name == changes["maintainername"]:
978         summary = summary + "Closing bugs: "
979         for bug in bugs:
980             summary = summary + "%s " % (bug)
981             if action:
982                 mail_message = """Return-Path: %s
983 From: %s
984 To: %s-close@bugs.debian.org
985 Bcc: troup@auric.debian.org
986 Subject: Bug#%s: fixed in %s %s
987
988 We believe that the bug you reported is fixed in the latest version of
989 %s, which has been installed in the Debian FTP archive:
990
991 %s""" % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], bug, bug, changes["source"], changes["version"], changes["source"], short_summary)
992
993                 if changes["distribution"].has_key("stable"):
994                     mail_message = mail_message + """Note that this package is not part of the released stable Debian
995 distribution.  It may have dependencies on other unreleased software,
996 or other instabilities.  Please take care if you wish to install it.
997 The update will eventually make its way into the next released Debian
998 distribution."""
999
1000                 mail_message = mail_message + """A summary of the changes between this version and the previous one is
1001 attached.
1002
1003 Thank you for reporting the bug, which will now be closed.  If you
1004 have further comments please address them to %s@bugs.debian.org,
1005 and the maintainer will reopen the bug report if appropriate.
1006
1007 Debian distribution maintenance software
1008 pp.
1009 %s (supplier of updated %s package)
1010
1011 (This message was generated automatically at their request; if you
1012 believe that there is a problem with it please contact the archive
1013 administrators by mailing ftpmaster@debian.org)
1014
1015
1016 %s""" % (bug, changes["maintainer"], changes["source"], changes["filecontents"])
1017
1018                 utils.send_mail (mail_message, "")
1019     else:                     # NMU
1020         summary = summary + "Setting bugs to severity fixed: "
1021         control_message = ""
1022         for bug in bugs:
1023             summary = summary + "%s " % (bug)
1024             control_message = control_message + "severity %s fixed\n" % (bug)
1025         if action and control_message != "":
1026             mail_message = """Return-Path: %s
1027 From: %s
1028 To: control@bugs.debian.org
1029 Bcc: troup@auric.debian.org, %s
1030 Subject: Fixed in NMU of %s %s
1031
1032 %s
1033 quit
1034
1035 This message was generated automatically in response to a
1036 non-maintainer upload.  The .changes file follows.
1037
1038 %s
1039 """ % (Cnf["Dinstall::MyEmailAddress"], changes["maintainer822"], changes["maintainer822"], changes["source"], changes["version"], control_message, changes["filecontents"])
1040             utils.send_mail (mail_message, "")
1041     summary = summary + "\n"
1042
1043     return summary
1044
1045 ###############################################################################
1046
1047 # reprocess is necessary for the case of foo_1.2-1 and foo_1.2-2 in
1048 # Incoming. -1 will reference the .orig.tar.gz, but -2 will not.
1049 # dsccheckdistrib() can find the .orig.tar.gz but it will not have
1050 # processed it during it's checks of -2.  If -1 has been deleted or
1051 # otherwise not checked by da-install, the .orig.tar.gz will not have
1052 # been checked at all.  To get round this, we force the .orig.tar.gz
1053 # into the .changes structure and reprocess the .changes file.
1054
1055 def process_it (changes_file):
1056     global reprocess, orig_tar_id, changes, dsc, dsc_files, files;
1057
1058     reprocess = 1;
1059     orig_tar_id = None;
1060     # Reset some globals
1061     changes = {};
1062     dsc = {};
1063     dsc_files = {};
1064     files = {};
1065     orig_tar_id = None;
1066
1067     # Absolutize the filename to avoid the requirement of being in the
1068     # same directory as the .changes file.
1069     changes_file = os.path.abspath(changes_file);
1070
1071     # And since handling of installs to stable munges with the CWD;
1072     # save and restore it.
1073     cwd = os.getcwd();
1074     
1075     check_signature (changes_file);
1076     check_changes (changes_file);
1077     while reprocess:
1078         reprocess = 0;
1079         check_files ();
1080         check_md5sums ();
1081         check_dsc ();
1082         
1083     action(changes_file);
1084
1085     # Restore CWD
1086     os.chdir(cwd);
1087
1088 ###############################################################################
1089
1090 def main():
1091     global Cnf, projectB, reject_message, install_bytes, new_ack_old
1092
1093     apt_pkg.init();
1094     
1095     Cnf = apt_pkg.newConfiguration();
1096     apt_pkg.ReadConfigFileISC(Cnf,utils.which_conf_file());
1097
1098     Arguments = [('a',"automatic","Dinstall::Options::Automatic"),
1099                  ('d',"debug","Dinstall::Options::Debug", "IntVal"),
1100                  ('h',"help","Dinstall::Options::Help"),
1101                  ('k',"ack-new","Dinstall::Options::Ack-New"),
1102                  ('m',"manual-reject","Dinstall::Options::Manual-Reject", "HasArg"),
1103                  ('n',"no-action","Dinstall::Options::No-Action"),
1104                  ('p',"no-lock", "Dinstall::Options::No-Lock"),
1105                  ('r',"no-version-check", "Dinstall::Options::No-Version-Check"),
1106                  ('s',"no-mail", "Dinstall::Options::No-Mail"),
1107                  ('u',"override-distribution", "Dinstall::Options::Override-Distribution", "HasArg"),
1108                  ('v',"version","Dinstall::Options::Version")];
1109     
1110     changes_files = apt_pkg.ParseCommandLine(Cnf,Arguments,sys.argv);
1111
1112     if Cnf["Dinstall::Options::Help"]:
1113         usage(0);
1114         
1115     if Cnf["Dinstall::Options::Version"]:
1116         print "katie version 0.0000000000";
1117         usage(0);
1118
1119     postgresql_user = None; # Default == Connect as user running program.
1120
1121     # -n/--dry-run invalidates some other options which would involve things happening
1122     if Cnf["Dinstall::Options::No-Action"]:
1123         Cnf["Dinstall::Options::Automatic"] = ""
1124         Cnf["Dinstall::Options::Ack-New"] = ""
1125         postgresql_user = Cnf["DB::ROUser"];
1126
1127     projectB = pg.connect('projectb', Cnf["DB::Host"], int(Cnf["DB::Port"]), None, None, postgresql_user);
1128
1129     db_access.init(Cnf, projectB);
1130
1131     # Check that we aren't going to clash with the daily cron job
1132
1133     if os.path.exists("%s/Archive_Maintenance_In_Progress" % (Cnf["Dir::RootDir"])) and not Cnf["Dinstall::Options::No-Lock"]:
1134         sys.stderr.write("Archive maintenance in progress.  Try again later.\n");
1135         sys.exit(2);
1136     
1137     # Obtain lock if not in no-action mode
1138
1139     if not Cnf["Dinstall::Options::No-Action"]:
1140         lock_fd = os.open(Cnf["Dinstall::LockFile"], os.O_RDWR);
1141         fcntl.lockf(lock_fd, FCNTL.F_TLOCK);
1142
1143     # Read in the list of already-acknowledged NEW packages
1144     new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'r');
1145     new_ack_old = {};
1146     for line in new_ack_list.readlines():
1147         new_ack_old[line[:-1]] = 1;
1148     new_ack_list.close();
1149
1150     # Process the changes files
1151     for changes_file in changes_files:
1152         reject_message = ""
1153         print "\n" + changes_file;
1154         process_it (changes_file);
1155
1156     if install_count:
1157         sets = "set"
1158         if install_count > 1:
1159             sets = "sets"
1160         sys.stderr.write("Installed %d package %s, %s.\n" % (install_count, sets, utils.size_type(int(install_bytes))));
1161
1162     # Write out the list of already-acknowledged NEW packages
1163     if Cnf["Dinstall::Options::Ack-New"]:
1164         new_ack_list = utils.open_file(Cnf["Dinstall::NewAckList"],'w')
1165         for i in new_ack_new.keys():
1166             new_ack_list.write(i+'\n')
1167         new_ack_list.close()
1168     
1169             
1170 if __name__ == '__main__':
1171     main()
1172