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