]> git.donarmstrong.com Git - dak.git/blob - daklib/utils.py
merge from ftp-master
[dak.git] / daklib / utils.py
1 #!/usr/bin/env python
2 # vim:set et ts=4 sw=4:
3
4 """Utility functions
5
6 @contact: Debian FTP Master <ftpmaster@debian.org>
7 @copyright: 2000, 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
8 @license: GNU General Public License version 2 or later
9 """
10
11 # This program is free software; you can redistribute it and/or modify
12 # it under the terms of the GNU General Public License as published by
13 # the Free Software Foundation; either version 2 of the License, or
14 # (at your option) any later version.
15
16 # This program is distributed in the hope that it will be useful,
17 # but WITHOUT ANY WARRANTY; without even the implied warranty of
18 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
19 # GNU General Public License for more details.
20
21 # You should have received a copy of the GNU General Public License
22 # along with this program; if not, write to the Free Software
23 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24
25 import commands
26 import email.Header
27 import os
28 import pwd
29 import select
30 import socket
31 import shutil
32 import sys
33 import tempfile
34 import traceback
35 import stat
36 import apt_pkg
37 import time
38 import re
39 import string
40 import email as modemail
41 import subprocess
42
43 from dbconn import DBConn, get_architecture, get_component, get_suite
44 from dak_exceptions import *
45 from textutils import fix_maintainer
46 from regexes import re_html_escaping, html_escaping, re_single_line_field, \
47                     re_multi_line_field, re_srchasver, re_taint_free, \
48                     re_gpg_uid, re_re_mark, re_whitespace_comment, re_issource, \
49                     re_is_orig_source
50
51 from formats import parse_format, validate_changes_format
52 from srcformats import get_format_from_string
53 from collections import defaultdict
54
55 ################################################################################
56
57 default_config = "/etc/dak/dak.conf"     #: default dak config, defines host properties
58 default_apt_config = "/etc/dak/apt.conf" #: default apt config, not normally used
59
60 alias_cache = None        #: Cache for email alias checks
61 key_uid_email_cache = {}  #: Cache for email addresses from gpg key uids
62
63 # (hashname, function, earliest_changes_version)
64 known_hashes = [("sha1", apt_pkg.sha1sum, (1, 8)),
65                 ("sha256", apt_pkg.sha256sum, (1, 8))] #: hashes we accept for entries in .changes/.dsc
66
67 # Monkeypatch commands.getstatusoutput as it may not return the correct exit
68 # code in lenny's Python. This also affects commands.getoutput and
69 # commands.getstatus.
70 def dak_getstatusoutput(cmd):
71     pipe = subprocess.Popen(cmd, shell=True, universal_newlines=True,
72         stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
73
74     output = "".join(pipe.stdout.readlines())
75
76     if output[-1:] == '\n':
77         output = output[:-1]
78
79     ret = pipe.wait()
80     if ret is None:
81         ret = 0
82
83     return ret, output
84 commands.getstatusoutput = dak_getstatusoutput
85
86 ################################################################################
87
88 def html_escape(s):
89     """ Escape html chars """
90     return re_html_escaping.sub(lambda x: html_escaping.get(x.group(0)), s)
91
92 ################################################################################
93
94 def open_file(filename, mode='r'):
95     """
96     Open C{file}, return fileobject.
97
98     @type filename: string
99     @param filename: path/filename to open
100
101     @type mode: string
102     @param mode: open mode
103
104     @rtype: fileobject
105     @return: open fileobject
106
107     @raise CantOpenError: If IOError is raised by open, reraise it as CantOpenError.
108
109     """
110     try:
111         f = open(filename, mode)
112     except IOError:
113         raise CantOpenError, filename
114     return f
115
116 ################################################################################
117
118 def our_raw_input(prompt=""):
119     if prompt:
120         while 1:
121             try:
122                 sys.stdout.write(prompt)
123                 break
124             except IOError:
125                 pass
126     sys.stdout.flush()
127     try:
128         ret = raw_input()
129         return ret
130     except EOFError:
131         sys.stderr.write("\nUser interrupt (^D).\n")
132         raise SystemExit
133
134 ################################################################################
135
136 def extract_component_from_section(section):
137     component = ""
138
139     if section.find('/') != -1:
140         component = section.split('/')[0]
141
142     # Expand default component
143     if component == "":
144         if Cnf.has_key("Component::%s" % section):
145             component = section
146         else:
147             component = "main"
148
149     return (section, component)
150
151 ################################################################################
152
153 def parse_deb822(contents, signing_rules=0):
154     error = ""
155     changes = {}
156
157     # Split the lines in the input, keeping the linebreaks.
158     lines = contents.splitlines(True)
159
160     if len(lines) == 0:
161         raise ParseChangesError, "[Empty changes file]"
162
163     # Reindex by line number so we can easily verify the format of
164     # .dsc files...
165     index = 0
166     indexed_lines = {}
167     for line in lines:
168         index += 1
169         indexed_lines[index] = line[:-1]
170
171     inside_signature = 0
172
173     num_of_lines = len(indexed_lines.keys())
174     index = 0
175     first = -1
176     while index < num_of_lines:
177         index += 1
178         line = indexed_lines[index]
179         if line == "":
180             if signing_rules == 1:
181                 index += 1
182                 if index > num_of_lines:
183                     raise InvalidDscError, index
184                 line = indexed_lines[index]
185                 if not line.startswith("-----BEGIN PGP SIGNATURE"):
186                     raise InvalidDscError, index
187                 inside_signature = 0
188                 break
189             else:
190                 continue
191         if line.startswith("-----BEGIN PGP SIGNATURE"):
192             break
193         if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
194             inside_signature = 1
195             if signing_rules == 1:
196                 while index < num_of_lines and line != "":
197                     index += 1
198                     line = indexed_lines[index]
199             continue
200         # If we're not inside the signed data, don't process anything
201         if signing_rules >= 0 and not inside_signature:
202             continue
203         slf = re_single_line_field.match(line)
204         if slf:
205             field = slf.groups()[0].lower()
206             changes[field] = slf.groups()[1]
207             first = 1
208             continue
209         if line == " .":
210             changes[field] += '\n'
211             continue
212         mlf = re_multi_line_field.match(line)
213         if mlf:
214             if first == -1:
215                 raise ParseChangesError, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
216             if first == 1 and changes[field] != "":
217                 changes[field] += '\n'
218             first = 0
219             changes[field] += mlf.groups()[0] + '\n'
220             continue
221         error += line
222
223     if signing_rules == 1 and inside_signature:
224         raise InvalidDscError, index
225
226     changes["filecontents"] = "".join(lines)
227
228     if changes.has_key("source"):
229         # Strip the source version in brackets from the source field,
230         # put it in the "source-version" field instead.
231         srcver = re_srchasver.search(changes["source"])
232         if srcver:
233             changes["source"] = srcver.group(1)
234             changes["source-version"] = srcver.group(2)
235
236     if error:
237         raise ParseChangesError, error
238
239     return changes
240
241 ################################################################################
242
243 def parse_changes(filename, signing_rules=0):
244     """
245     Parses a changes file and returns a dictionary where each field is a
246     key.  The mandatory first argument is the filename of the .changes
247     file.
248
249     signing_rules is an optional argument:
250
251       - If signing_rules == -1, no signature is required.
252       - If signing_rules == 0 (the default), a signature is required.
253       - If signing_rules == 1, it turns on the same strict format checking
254         as dpkg-source.
255
256     The rules for (signing_rules == 1)-mode are:
257
258       - The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
259         followed by any PGP header data and must end with a blank line.
260
261       - The data section must end with a blank line and must be followed by
262         "-----BEGIN PGP SIGNATURE-----".
263     """
264
265     changes_in = open_file(filename)
266     content = changes_in.read()
267     changes_in.close()
268     try:
269         unicode(content, 'utf-8')
270     except UnicodeError:
271         raise ChangesUnicodeError, "Changes file not proper utf-8"
272     return parse_deb822(content, signing_rules)
273
274 ################################################################################
275
276 def hash_key(hashname):
277     return '%ssum' % hashname
278
279 ################################################################################
280
281 def create_hash(where, files, hashname, hashfunc):
282     """
283     create_hash extends the passed files dict with the given hash by
284     iterating over all files on disk and passing them to the hashing
285     function given.
286     """
287
288     rejmsg = []
289     for f in files.keys():
290         try:
291             file_handle = open_file(f)
292         except CantOpenError:
293             rejmsg.append("Could not open file %s for checksumming" % (f))
294             continue
295
296         files[f][hash_key(hashname)] = hashfunc(file_handle)
297
298         file_handle.close()
299     return rejmsg
300
301 ################################################################################
302
303 def check_hash(where, files, hashname, hashfunc):
304     """
305     check_hash checks the given hash in the files dict against the actual
306     files on disk.  The hash values need to be present consistently in
307     all file entries.  It does not modify its input in any way.
308     """
309
310     rejmsg = []
311     for f in files.keys():
312         file_handle = None
313         try:
314             try:
315                 file_handle = open_file(f)
316
317                 # Check for the hash entry, to not trigger a KeyError.
318                 if not files[f].has_key(hash_key(hashname)):
319                     rejmsg.append("%s: misses %s checksum in %s" % (f, hashname,
320                         where))
321                     continue
322
323                 # Actually check the hash for correctness.
324                 if hashfunc(file_handle) != files[f][hash_key(hashname)]:
325                     rejmsg.append("%s: %s check failed in %s" % (f, hashname,
326                         where))
327             except CantOpenError:
328                 # TODO: This happens when the file is in the pool.
329                 # warn("Cannot open file %s" % f)
330                 continue
331         finally:
332             if file_handle:
333                 file_handle.close()
334     return rejmsg
335
336 ################################################################################
337
338 def check_size(where, files):
339     """
340     check_size checks the file sizes in the passed files dict against the
341     files on disk.
342     """
343
344     rejmsg = []
345     for f in files.keys():
346         try:
347             entry = os.stat(f)
348         except OSError, exc:
349             if exc.errno == 2:
350                 # TODO: This happens when the file is in the pool.
351                 continue
352             raise
353
354         actual_size = entry[stat.ST_SIZE]
355         size = int(files[f]["size"])
356         if size != actual_size:
357             rejmsg.append("%s: actual file size (%s) does not match size (%s) in %s"
358                    % (f, actual_size, size, where))
359     return rejmsg
360
361 ################################################################################
362
363 def check_dsc_files(dsc_filename, dsc=None, dsc_files=None):
364     """
365     Verify that the files listed in the Files field of the .dsc are
366     those expected given the announced Format.
367
368     @type dsc_filename: string
369     @param dsc_filename: path of .dsc file
370
371     @type dsc: dict
372     @param dsc: the content of the .dsc parsed by C{parse_changes()}
373
374     @type dsc_files: dict
375     @param dsc_files: the file list returned by C{build_file_list()}
376
377     @rtype: list
378     @return: all errors detected
379     """
380     rejmsg = []
381
382     # Parse the file if needed
383     if dsc is None:
384         dsc = parse_changes(dsc_filename, signing_rules=1);
385
386     if dsc_files is None:
387         dsc_files = build_file_list(dsc, is_a_dsc=1)
388
389     # Ensure .dsc lists proper set of source files according to the format
390     # announced
391     has = defaultdict(lambda: 0)
392
393     ftype_lookup = (
394         (r'orig.tar.gz',               ('orig_tar_gz', 'orig_tar')),
395         (r'diff.gz',                   ('debian_diff',)),
396         (r'tar.gz',                    ('native_tar_gz', 'native_tar')),
397         (r'debian\.tar\.(gz|bz2)',     ('debian_tar',)),
398         (r'orig\.tar\.(gz|bz2)',       ('orig_tar',)),
399         (r'tar\.(gz|bz2)',             ('native_tar',)),
400         (r'orig-.+\.tar\.(gz|bz2)',    ('more_orig_tar',)),
401     )
402
403     for f in dsc_files.keys():
404         m = re_issource.match(f)
405         if not m:
406             rejmsg.append("%s: %s in Files field not recognised as source."
407                           % (dsc_filename, f))
408             continue
409
410         # Populate 'has' dictionary by resolving keys in lookup table
411         matched = False
412         for regex, keys in ftype_lookup:
413             if re.match(regex, m.group(3)):
414                 matched = True
415                 for key in keys:
416                     has[key] += 1
417                 break
418
419         # File does not match anything in lookup table; reject
420         if not matched:
421             reject("%s: unexpected source file '%s'" % (dsc_filename, f))
422
423     # Check for multiple files
424     for file_type in ('orig_tar', 'native_tar', 'debian_tar', 'debian_diff'):
425         if has[file_type] > 1:
426             rejmsg.append("%s: lists multiple %s" % (dsc_filename, file_type))
427
428     # Source format specific tests
429     try:
430         format = get_format_from_string(dsc['format'])
431         rejmsg.extend([
432             '%s: %s' % (dsc_filename, x) for x in format.reject_msgs(has)
433         ])
434
435     except UnknownFormatError:
436         # Not an error here for now
437         pass
438
439     return rejmsg
440
441 ################################################################################
442
443 def check_hash_fields(what, manifest):
444     """
445     check_hash_fields ensures that there are no checksum fields in the
446     given dict that we do not know about.
447     """
448
449     rejmsg = []
450     hashes = map(lambda x: x[0], known_hashes)
451     for field in manifest:
452         if field.startswith("checksums-"):
453             hashname = field.split("-",1)[1]
454             if hashname not in hashes:
455                 rejmsg.append("Unsupported checksum field for %s "\
456                     "in %s" % (hashname, what))
457     return rejmsg
458
459 ################################################################################
460
461 def _ensure_changes_hash(changes, format, version, files, hashname, hashfunc):
462     if format >= version:
463         # The version should contain the specified hash.
464         func = check_hash
465
466         # Import hashes from the changes
467         rejmsg = parse_checksums(".changes", files, changes, hashname)
468         if len(rejmsg) > 0:
469             return rejmsg
470     else:
471         # We need to calculate the hash because it can't possibly
472         # be in the file.
473         func = create_hash
474     return func(".changes", files, hashname, hashfunc)
475
476 # We could add the orig which might be in the pool to the files dict to
477 # access the checksums easily.
478
479 def _ensure_dsc_hash(dsc, dsc_files, hashname, hashfunc):
480     """
481     ensure_dsc_hashes' task is to ensure that each and every *present* hash
482     in the dsc is correct, i.e. identical to the changes file and if necessary
483     the pool.  The latter task is delegated to check_hash.
484     """
485
486     rejmsg = []
487     if not dsc.has_key('Checksums-%s' % (hashname,)):
488         return rejmsg
489     # Import hashes from the dsc
490     parse_checksums(".dsc", dsc_files, dsc, hashname)
491     # And check it...
492     rejmsg.extend(check_hash(".dsc", dsc_files, hashname, hashfunc))
493     return rejmsg
494
495 ################################################################################
496
497 def parse_checksums(where, files, manifest, hashname):
498     rejmsg = []
499     field = 'checksums-%s' % hashname
500     if not field in manifest:
501         return rejmsg
502     for line in manifest[field].split('\n'):
503         if not line:
504             break
505         clist = line.strip().split(' ')
506         if len(clist) == 3:
507             checksum, size, checkfile = clist
508         else:
509             rejmsg.append("Cannot parse checksum line [%s]" % (line))
510             continue
511         if not files.has_key(checkfile):
512         # TODO: check for the file's entry in the original files dict, not
513         # the one modified by (auto)byhand and other weird stuff
514         #    rejmsg.append("%s: not present in files but in checksums-%s in %s" %
515         #        (file, hashname, where))
516             continue
517         if not files[checkfile]["size"] == size:
518             rejmsg.append("%s: size differs for files and checksums-%s entry "\
519                 "in %s" % (checkfile, hashname, where))
520             continue
521         files[checkfile][hash_key(hashname)] = checksum
522     for f in files.keys():
523         if not files[f].has_key(hash_key(hashname)):
524             rejmsg.append("%s: no entry in checksums-%s in %s" % (checkfile,
525                 hashname, where))
526     return rejmsg
527
528 ################################################################################
529
530 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
531
532 def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
533     files = {}
534
535     # Make sure we have a Files: field to parse...
536     if not changes.has_key(field):
537         raise NoFilesFieldError
538
539     # Validate .changes Format: field
540     if not is_a_dsc:
541         validate_changes_format(parse_format(changes['format']), field)
542
543     includes_section = (not is_a_dsc) and field == "files"
544
545     # Parse each entry/line:
546     for i in changes[field].split('\n'):
547         if not i:
548             break
549         s = i.split()
550         section = priority = ""
551         try:
552             if includes_section:
553                 (md5, size, section, priority, name) = s
554             else:
555                 (md5, size, name) = s
556         except ValueError:
557             raise ParseChangesError, i
558
559         if section == "":
560             section = "-"
561         if priority == "":
562             priority = "-"
563
564         (section, component) = extract_component_from_section(section)
565
566         files[name] = dict(size=size, section=section,
567                            priority=priority, component=component)
568         files[name][hashname] = md5
569
570     return files
571
572 ################################################################################
573
574 def send_mail (message, filename=""):
575     """sendmail wrapper, takes _either_ a message string or a file as arguments"""
576
577     # Check whether we're supposed to be sending mail
578     if Cnf.has_key("Dinstall::Options::No-Mail") and Cnf["Dinstall::Options::No-Mail"]:
579         return
580
581     # If we've been passed a string dump it into a temporary file
582     if message:
583         (fd, filename) = tempfile.mkstemp()
584         os.write (fd, message)
585         os.close (fd)
586
587     if Cnf.has_key("Dinstall::MailWhiteList") and \
588            Cnf["Dinstall::MailWhiteList"] != "":
589         message_in = open_file(filename)
590         message_raw = modemail.message_from_file(message_in)
591         message_in.close();
592
593         whitelist = [];
594         whitelist_in = open_file(Cnf["Dinstall::MailWhiteList"])
595         try:
596             for line in whitelist_in:
597                 if not re_whitespace_comment.match(line):
598                     if re_re_mark.match(line):
599                         whitelist.append(re.compile(re_re_mark.sub("", line.strip(), 1)))
600                     else:
601                         whitelist.append(re.compile(re.escape(line.strip())))
602         finally:
603             whitelist_in.close()
604
605         # Fields to check.
606         fields = ["To", "Bcc", "Cc"]
607         for field in fields:
608             # Check each field
609             value = message_raw.get(field, None)
610             if value != None:
611                 match = [];
612                 for item in value.split(","):
613                     (rfc822_maint, rfc2047_maint, name, email) = fix_maintainer(item.strip())
614                     mail_whitelisted = 0
615                     for wr in whitelist:
616                         if wr.match(email):
617                             mail_whitelisted = 1
618                             break
619                     if not mail_whitelisted:
620                         print "Skipping %s since it's not in %s" % (item, Cnf["Dinstall::MailWhiteList"])
621                         continue
622                     match.append(item)
623
624                 # Doesn't have any mail in whitelist so remove the header
625                 if len(match) == 0:
626                     del message_raw[field]
627                 else:
628                     message_raw.replace_header(field, ', '.join(match))
629
630         # Change message fields in order if we don't have a To header
631         if not message_raw.has_key("To"):
632             fields.reverse()
633             for field in fields:
634                 if message_raw.has_key(field):
635                     message_raw[fields[-1]] = message_raw[field]
636                     del message_raw[field]
637                     break
638             else:
639                 # Clean up any temporary files
640                 # and return, as we removed all recipients.
641                 if message:
642                     os.unlink (filename);
643                 return;
644
645         fd = os.open(filename, os.O_RDWR|os.O_EXCL, 0700);
646         os.write (fd, message_raw.as_string(True));
647         os.close (fd);
648
649     # Invoke sendmail
650     (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
651     if (result != 0):
652         raise SendmailFailedError, output
653
654     # Clean up any temporary files
655     if message:
656         os.unlink (filename)
657
658 ################################################################################
659
660 def poolify (source, component):
661     if component:
662         component += '/'
663     if source[:3] == "lib":
664         return component + source[:4] + '/' + source + '/'
665     else:
666         return component + source[:1] + '/' + source + '/'
667
668 ################################################################################
669
670 def move (src, dest, overwrite = 0, perms = 0664):
671     if os.path.exists(dest) and os.path.isdir(dest):
672         dest_dir = dest
673     else:
674         dest_dir = os.path.dirname(dest)
675     if not os.path.exists(dest_dir):
676         umask = os.umask(00000)
677         os.makedirs(dest_dir, 02775)
678         os.umask(umask)
679     #print "Moving %s to %s..." % (src, dest)
680     if os.path.exists(dest) and os.path.isdir(dest):
681         dest += '/' + os.path.basename(src)
682     # Don't overwrite unless forced to
683     if os.path.exists(dest):
684         if not overwrite:
685             fubar("Can't move %s to %s - file already exists." % (src, dest))
686         else:
687             if not os.access(dest, os.W_OK):
688                 fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
689     shutil.copy2(src, dest)
690     os.chmod(dest, perms)
691     os.unlink(src)
692
693 def copy (src, dest, overwrite = 0, perms = 0664):
694     if os.path.exists(dest) and os.path.isdir(dest):
695         dest_dir = dest
696     else:
697         dest_dir = os.path.dirname(dest)
698     if not os.path.exists(dest_dir):
699         umask = os.umask(00000)
700         os.makedirs(dest_dir, 02775)
701         os.umask(umask)
702     #print "Copying %s to %s..." % (src, dest)
703     if os.path.exists(dest) and os.path.isdir(dest):
704         dest += '/' + os.path.basename(src)
705     # Don't overwrite unless forced to
706     if os.path.exists(dest):
707         if not overwrite:
708             raise FileExistsError
709         else:
710             if not os.access(dest, os.W_OK):
711                 raise CantOverwriteError
712     shutil.copy2(src, dest)
713     os.chmod(dest, perms)
714
715 ################################################################################
716
717 def where_am_i ():
718     res = socket.gethostbyaddr(socket.gethostname())
719     database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
720     if database_hostname:
721         return database_hostname
722     else:
723         return res[0]
724
725 def which_conf_file ():
726     if os.getenv('DAK_CONFIG'):
727         return os.getenv('DAK_CONFIG')
728
729     res = socket.gethostbyaddr(socket.gethostname())
730     # In case we allow local config files per user, try if one exists
731     if Cnf.FindB("Config::" + res[0] + "::AllowLocalConfig"):
732         homedir = os.getenv("HOME")
733         confpath = os.path.join(homedir, "/etc/dak.conf")
734         if os.path.exists(confpath):
735             apt_pkg.ReadConfigFileISC(Cnf,default_config)
736
737     # We are still in here, so there is no local config file or we do
738     # not allow local files. Do the normal stuff.
739     if Cnf.get("Config::" + res[0] + "::DakConfig"):
740         return Cnf["Config::" + res[0] + "::DakConfig"]
741
742     return default_config
743
744 def which_apt_conf_file ():
745     res = socket.gethostbyaddr(socket.gethostname())
746     # In case we allow local config files per user, try if one exists
747     if Cnf.FindB("Config::" + res[0] + "::AllowLocalConfig"):
748         homedir = os.getenv("HOME")
749         confpath = os.path.join(homedir, "/etc/dak.conf")
750         if os.path.exists(confpath):
751             apt_pkg.ReadConfigFileISC(Cnf,default_config)
752
753     if Cnf.get("Config::" + res[0] + "::AptConfig"):
754         return Cnf["Config::" + res[0] + "::AptConfig"]
755     else:
756         return default_apt_config
757
758 def which_alias_file():
759     hostname = socket.gethostbyaddr(socket.gethostname())[0]
760     aliasfn = '/var/lib/misc/'+hostname+'/forward-alias'
761     if os.path.exists(aliasfn):
762         return aliasfn
763     else:
764         return None
765
766 ################################################################################
767
768 def TemplateSubst(subst_map, filename):
769     """ Perform a substition of template """
770     templatefile = open_file(filename)
771     template = templatefile.read()
772     for k, v in subst_map.iteritems():
773         template = template.replace(k, str(v))
774     templatefile.close()
775     return template
776
777 ################################################################################
778
779 def fubar(msg, exit_code=1):
780     sys.stderr.write("E: %s\n" % (msg))
781     sys.exit(exit_code)
782
783 def warn(msg):
784     sys.stderr.write("W: %s\n" % (msg))
785
786 ################################################################################
787
788 # Returns the user name with a laughable attempt at rfc822 conformancy
789 # (read: removing stray periods).
790 def whoami ():
791     return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
792
793 def getusername ():
794     return pwd.getpwuid(os.getuid())[0]
795
796 ################################################################################
797
798 def size_type (c):
799     t  = " B"
800     if c > 10240:
801         c = c / 1024
802         t = " KB"
803     if c > 10240:
804         c = c / 1024
805         t = " MB"
806     return ("%d%s" % (c, t))
807
808 ################################################################################
809
810 def cc_fix_changes (changes):
811     o = changes.get("architecture", "")
812     if o:
813         del changes["architecture"]
814     changes["architecture"] = {}
815     for j in o.split():
816         changes["architecture"][j] = 1
817
818 def changes_compare (a, b):
819     """ Sort by source name, source version, 'have source', and then by filename """
820     try:
821         a_changes = parse_changes(a)
822     except:
823         return -1
824
825     try:
826         b_changes = parse_changes(b)
827     except:
828         return 1
829
830     cc_fix_changes (a_changes)
831     cc_fix_changes (b_changes)
832
833     # Sort by source name
834     a_source = a_changes.get("source")
835     b_source = b_changes.get("source")
836     q = cmp (a_source, b_source)
837     if q:
838         return q
839
840     # Sort by source version
841     a_version = a_changes.get("version", "0")
842     b_version = b_changes.get("version", "0")
843     q = apt_pkg.VersionCompare(a_version, b_version)
844     if q:
845         return q
846
847     # Sort by 'have source'
848     a_has_source = a_changes["architecture"].get("source")
849     b_has_source = b_changes["architecture"].get("source")
850     if a_has_source and not b_has_source:
851         return -1
852     elif b_has_source and not a_has_source:
853         return 1
854
855     # Fall back to sort by filename
856     return cmp(a, b)
857
858 ################################################################################
859
860 def find_next_free (dest, too_many=100):
861     extra = 0
862     orig_dest = dest
863     while os.path.exists(dest) and extra < too_many:
864         dest = orig_dest + '.' + repr(extra)
865         extra += 1
866     if extra >= too_many:
867         raise NoFreeFilenameError
868     return dest
869
870 ################################################################################
871
872 def result_join (original, sep = '\t'):
873     resultlist = []
874     for i in xrange(len(original)):
875         if original[i] == None:
876             resultlist.append("")
877         else:
878             resultlist.append(original[i])
879     return sep.join(resultlist)
880
881 ################################################################################
882
883 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
884     out = ""
885     for line in str.split('\n'):
886         line = line.strip()
887         if line or include_blank_lines:
888             out += "%s%s\n" % (prefix, line)
889     # Strip trailing new line
890     if out:
891         out = out[:-1]
892     return out
893
894 ################################################################################
895
896 def validate_changes_file_arg(filename, require_changes=1):
897     """
898     'filename' is either a .changes or .dak file.  If 'filename' is a
899     .dak file, it's changed to be the corresponding .changes file.  The
900     function then checks if the .changes file a) exists and b) is
901     readable and returns the .changes filename if so.  If there's a
902     problem, the next action depends on the option 'require_changes'
903     argument:
904
905       - If 'require_changes' == -1, errors are ignored and the .changes
906         filename is returned.
907       - If 'require_changes' == 0, a warning is given and 'None' is returned.
908       - If 'require_changes' == 1, a fatal error is raised.
909
910     """
911     error = None
912
913     orig_filename = filename
914     if filename.endswith(".dak"):
915         filename = filename[:-4]+".changes"
916
917     if not filename.endswith(".changes"):
918         error = "invalid file type; not a changes file"
919     else:
920         if not os.access(filename,os.R_OK):
921             if os.path.exists(filename):
922                 error = "permission denied"
923             else:
924                 error = "file not found"
925
926     if error:
927         if require_changes == 1:
928             fubar("%s: %s." % (orig_filename, error))
929         elif require_changes == 0:
930             warn("Skipping %s - %s" % (orig_filename, error))
931             return None
932         else: # We only care about the .dak file
933             return filename
934     else:
935         return filename
936
937 ################################################################################
938
939 def real_arch(arch):
940     return (arch != "source" and arch != "all")
941
942 ################################################################################
943
944 def join_with_commas_and(list):
945     if len(list) == 0: return "nothing"
946     if len(list) == 1: return list[0]
947     return ", ".join(list[:-1]) + " and " + list[-1]
948
949 ################################################################################
950
951 def pp_deps (deps):
952     pp_deps = []
953     for atom in deps:
954         (pkg, version, constraint) = atom
955         if constraint:
956             pp_dep = "%s (%s %s)" % (pkg, constraint, version)
957         else:
958             pp_dep = pkg
959         pp_deps.append(pp_dep)
960     return " |".join(pp_deps)
961
962 ################################################################################
963
964 def get_conf():
965     return Cnf
966
967 ################################################################################
968
969 def parse_args(Options):
970     """ Handle -a, -c and -s arguments; returns them as SQL constraints """
971     # XXX: This should go away and everything which calls it be converted
972     #      to use SQLA properly.  For now, we'll just fix it not to use
973     #      the old Pg interface though
974     session = DBConn().session()
975     # Process suite
976     if Options["Suite"]:
977         suite_ids_list = []
978         for suitename in split_args(Options["Suite"]):
979             suite = get_suite(suitename, session=session)
980             if suite.suite_id is None:
981                 warn("suite '%s' not recognised." % (suite.suite_name))
982             else:
983                 suite_ids_list.append(suite.suite_id)
984         if suite_ids_list:
985             con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
986         else:
987             fubar("No valid suite given.")
988     else:
989         con_suites = ""
990
991     # Process component
992     if Options["Component"]:
993         component_ids_list = []
994         for componentname in split_args(Options["Component"]):
995             component = get_component(componentname, session=session)
996             if component is None:
997                 warn("component '%s' not recognised." % (componentname))
998             else:
999                 component_ids_list.append(component.component_id)
1000         if component_ids_list:
1001             con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
1002         else:
1003             fubar("No valid component given.")
1004     else:
1005         con_components = ""
1006
1007     # Process architecture
1008     con_architectures = ""
1009     check_source = 0
1010     if Options["Architecture"]:
1011         arch_ids_list = []
1012         for archname in split_args(Options["Architecture"]):
1013             if archname == "source":
1014                 check_source = 1
1015             else:
1016                 arch = get_architecture(archname, session=session)
1017                 if arch is None:
1018                     warn("architecture '%s' not recognised." % (archname))
1019                 else:
1020                     arch_ids_list.append(arch.arch_id)
1021         if arch_ids_list:
1022             con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
1023         else:
1024             if not check_source:
1025                 fubar("No valid architecture given.")
1026     else:
1027         check_source = 1
1028
1029     return (con_suites, con_architectures, con_components, check_source)
1030
1031 ################################################################################
1032
1033 # Inspired(tm) by Bryn Keller's print_exc_plus (See
1034 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
1035
1036 def print_exc():
1037     tb = sys.exc_info()[2]
1038     while tb.tb_next:
1039         tb = tb.tb_next
1040     stack = []
1041     frame = tb.tb_frame
1042     while frame:
1043         stack.append(frame)
1044         frame = frame.f_back
1045     stack.reverse()
1046     traceback.print_exc()
1047     for frame in stack:
1048         print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
1049                                              frame.f_code.co_filename,
1050                                              frame.f_lineno)
1051         for key, value in frame.f_locals.items():
1052             print "\t%20s = " % key,
1053             try:
1054                 print value
1055             except:
1056                 print "<unable to print>"
1057
1058 ################################################################################
1059
1060 def try_with_debug(function):
1061     try:
1062         function()
1063     except SystemExit:
1064         raise
1065     except:
1066         print_exc()
1067
1068 ################################################################################
1069
1070 def arch_compare_sw (a, b):
1071     """
1072     Function for use in sorting lists of architectures.
1073
1074     Sorts normally except that 'source' dominates all others.
1075     """
1076
1077     if a == "source" and b == "source":
1078         return 0
1079     elif a == "source":
1080         return -1
1081     elif b == "source":
1082         return 1
1083
1084     return cmp (a, b)
1085
1086 ################################################################################
1087
1088 def split_args (s, dwim=1):
1089     """
1090     Split command line arguments which can be separated by either commas
1091     or whitespace.  If dwim is set, it will complain about string ending
1092     in comma since this usually means someone did 'dak ls -a i386, m68k
1093     foo' or something and the inevitable confusion resulting from 'm68k'
1094     being treated as an argument is undesirable.
1095     """
1096
1097     if s.find(",") == -1:
1098         return s.split()
1099     else:
1100         if s[-1:] == "," and dwim:
1101             fubar("split_args: found trailing comma, spurious space maybe?")
1102         return s.split(",")
1103
1104 ################################################################################
1105
1106 def gpgv_get_status_output(cmd, status_read, status_write):
1107     """
1108     Our very own version of commands.getouputstatus(), hacked to support
1109     gpgv's status fd.
1110     """
1111
1112     cmd = ['/bin/sh', '-c', cmd]
1113     p2cread, p2cwrite = os.pipe()
1114     c2pread, c2pwrite = os.pipe()
1115     errout, errin = os.pipe()
1116     pid = os.fork()
1117     if pid == 0:
1118         # Child
1119         os.close(0)
1120         os.close(1)
1121         os.dup(p2cread)
1122         os.dup(c2pwrite)
1123         os.close(2)
1124         os.dup(errin)
1125         for i in range(3, 256):
1126             if i != status_write:
1127                 try:
1128                     os.close(i)
1129                 except:
1130                     pass
1131         try:
1132             os.execvp(cmd[0], cmd)
1133         finally:
1134             os._exit(1)
1135
1136     # Parent
1137     os.close(p2cread)
1138     os.dup2(c2pread, c2pwrite)
1139     os.dup2(errout, errin)
1140
1141     output = status = ""
1142     while 1:
1143         i, o, e = select.select([c2pwrite, errin, status_read], [], [])
1144         more_data = []
1145         for fd in i:
1146             r = os.read(fd, 8196)
1147             if len(r) > 0:
1148                 more_data.append(fd)
1149                 if fd == c2pwrite or fd == errin:
1150                     output += r
1151                 elif fd == status_read:
1152                     status += r
1153                 else:
1154                     fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
1155         if not more_data:
1156             pid, exit_status = os.waitpid(pid, 0)
1157             try:
1158                 os.close(status_write)
1159                 os.close(status_read)
1160                 os.close(c2pread)
1161                 os.close(c2pwrite)
1162                 os.close(p2cwrite)
1163                 os.close(errin)
1164                 os.close(errout)
1165             except:
1166                 pass
1167             break
1168
1169     return output, status, exit_status
1170
1171 ################################################################################
1172
1173 def process_gpgv_output(status):
1174     # Process the status-fd output
1175     keywords = {}
1176     internal_error = ""
1177     for line in status.split('\n'):
1178         line = line.strip()
1179         if line == "":
1180             continue
1181         split = line.split()
1182         if len(split) < 2:
1183             internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
1184             continue
1185         (gnupg, keyword) = split[:2]
1186         if gnupg != "[GNUPG:]":
1187             internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
1188             continue
1189         args = split[2:]
1190         if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
1191             internal_error += "found duplicate status token ('%s').\n" % (keyword)
1192             continue
1193         else:
1194             keywords[keyword] = args
1195
1196     return (keywords, internal_error)
1197
1198 ################################################################################
1199
1200 def retrieve_key (filename, keyserver=None, keyring=None):
1201     """
1202     Retrieve the key that signed 'filename' from 'keyserver' and
1203     add it to 'keyring'.  Returns nothing on success, or an error message
1204     on error.
1205     """
1206
1207     # Defaults for keyserver and keyring
1208     if not keyserver:
1209         keyserver = Cnf["Dinstall::KeyServer"]
1210     if not keyring:
1211         keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
1212
1213     # Ensure the filename contains no shell meta-characters or other badness
1214     if not re_taint_free.match(filename):
1215         return "%s: tainted filename" % (filename)
1216
1217     # Invoke gpgv on the file
1218     status_read, status_write = os.pipe()
1219     cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
1220     (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
1221
1222     # Process the status-fd output
1223     (keywords, internal_error) = process_gpgv_output(status)
1224     if internal_error:
1225         return internal_error
1226
1227     if not keywords.has_key("NO_PUBKEY"):
1228         return "didn't find expected NO_PUBKEY in gpgv status-fd output"
1229
1230     fingerprint = keywords["NO_PUBKEY"][0]
1231     # XXX - gpg sucks.  You can't use --secret-keyring=/dev/null as
1232     # it'll try to create a lockfile in /dev.  A better solution might
1233     # be a tempfile or something.
1234     cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
1235           % (Cnf["Dinstall::SigningKeyring"])
1236     cmd += " --keyring %s --keyserver %s --recv-key %s" \
1237            % (keyring, keyserver, fingerprint)
1238     (result, output) = commands.getstatusoutput(cmd)
1239     if (result != 0):
1240         return "'%s' failed with exit code %s" % (cmd, result)
1241
1242     return ""
1243
1244 ################################################################################
1245
1246 def gpg_keyring_args(keyrings=None):
1247     if not keyrings:
1248         keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
1249
1250     return " ".join(["--keyring %s" % x for x in keyrings])
1251
1252 ################################################################################
1253
1254 def check_signature (sig_filename, data_filename="", keyrings=None, autofetch=None):
1255     """
1256     Check the signature of a file and return the fingerprint if the
1257     signature is valid or 'None' if it's not.  The first argument is the
1258     filename whose signature should be checked.  The second argument is a
1259     reject function and is called when an error is found.  The reject()
1260     function must allow for two arguments: the first is the error message,
1261     the second is an optional prefix string.  It's possible for reject()
1262     to be called more than once during an invocation of check_signature().
1263     The third argument is optional and is the name of the files the
1264     detached signature applies to.  The fourth argument is optional and is
1265     a *list* of keyrings to use.  'autofetch' can either be None, True or
1266     False.  If None, the default behaviour specified in the config will be
1267     used.
1268     """
1269
1270     rejects = []
1271
1272     # Ensure the filename contains no shell meta-characters or other badness
1273     if not re_taint_free.match(sig_filename):
1274         rejects.append("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
1275         return (None, rejects)
1276
1277     if data_filename and not re_taint_free.match(data_filename):
1278         rejects.append("!!WARNING!! tainted data filename: '%s'." % (data_filename))
1279         return (None, rejects)
1280
1281     if not keyrings:
1282         keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
1283
1284     # Autofetch the signing key if that's enabled
1285     if autofetch == None:
1286         autofetch = Cnf.get("Dinstall::KeyAutoFetch")
1287     if autofetch:
1288         error_msg = retrieve_key(sig_filename)
1289         if error_msg:
1290             rejects.append(error_msg)
1291             return (None, rejects)
1292
1293     # Build the command line
1294     status_read, status_write = os.pipe()
1295     cmd = "gpgv --status-fd %s %s %s %s" % (
1296         status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)
1297
1298     # Invoke gpgv on the file
1299     (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
1300
1301     # Process the status-fd output
1302     (keywords, internal_error) = process_gpgv_output(status)
1303
1304     # If we failed to parse the status-fd output, let's just whine and bail now
1305     if internal_error:
1306         rejects.append("internal error while performing signature check on %s." % (sig_filename))
1307         rejects.append(internal_error, "")
1308         rejects.append("Please report the above errors to the Archive maintainers by replying to this mail.", "")
1309         return (None, rejects)
1310
1311     # Now check for obviously bad things in the processed output
1312     if keywords.has_key("KEYREVOKED"):
1313         rejects.append("The key used to sign %s has been revoked." % (sig_filename))
1314     if keywords.has_key("BADSIG"):
1315         rejects.append("bad signature on %s." % (sig_filename))
1316     if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1317         rejects.append("failed to check signature on %s." % (sig_filename))
1318     if keywords.has_key("NO_PUBKEY"):
1319         args = keywords["NO_PUBKEY"]
1320         if len(args) >= 1:
1321             key = args[0]
1322         rejects.append("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
1323     if keywords.has_key("BADARMOR"):
1324         rejects.append("ASCII armour of signature was corrupt in %s." % (sig_filename))
1325     if keywords.has_key("NODATA"):
1326         rejects.append("no signature found in %s." % (sig_filename))
1327     if keywords.has_key("EXPKEYSIG"):
1328         args = keywords["EXPKEYSIG"]
1329         if len(args) >= 1:
1330             key = args[0]
1331         rejects.append("Signature made by expired key 0x%s" % (key))
1332     if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1333         args = keywords["KEYEXPIRED"]
1334         expiredate=""
1335         if len(args) >= 1:
1336             timestamp = args[0]
1337             if timestamp.count("T") == 0:
1338                 try:
1339                     expiredate = time.strftime("%Y-%m-%d", time.gmtime(float(timestamp)))
1340                 except ValueError:
1341                     expiredate = "unknown (%s)" % (timestamp)
1342             else:
1343                 expiredate = timestamp
1344         rejects.append("The key used to sign %s has expired on %s" % (sig_filename, expiredate))
1345
1346     if len(rejects) > 0:
1347         return (None, rejects)
1348
1349     # Next check gpgv exited with a zero return code
1350     if exit_status:
1351         rejects.append("gpgv failed while checking %s." % (sig_filename))
1352         if status.strip():
1353             rejects.append(prefix_multi_line_string(status, " [GPG status-fd output:] "))
1354         else:
1355             rejects.append(prefix_multi_line_string(output, " [GPG output:] "))
1356         return (None, rejects)
1357
1358     # Sanity check the good stuff we expect
1359     if not keywords.has_key("VALIDSIG"):
1360         rejects.append("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
1361     else:
1362         args = keywords["VALIDSIG"]
1363         if len(args) < 1:
1364             rejects.append("internal error while checking signature on %s." % (sig_filename))
1365         else:
1366             fingerprint = args[0]
1367     if not keywords.has_key("GOODSIG"):
1368         rejects.append("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
1369     if not keywords.has_key("SIG_ID"):
1370         rejects.append("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
1371
1372     # Finally ensure there's not something we don't recognise
1373     known_keywords = dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
1374                           SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1375                           NODATA="",NOTATION_DATA="",NOTATION_NAME="",KEYEXPIRED="",POLICY_URL="")
1376
1377     for keyword in keywords.keys():
1378         if not known_keywords.has_key(keyword):
1379             rejects.append("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
1380
1381     if len(rejects) > 0:
1382         return (None, rejects)
1383     else:
1384         return (fingerprint, [])
1385
1386 ################################################################################
1387
1388 def gpg_get_key_addresses(fingerprint):
1389     """retreive email addresses from gpg key uids for a given fingerprint"""
1390     addresses = key_uid_email_cache.get(fingerprint)
1391     if addresses != None:
1392         return addresses
1393     addresses = set()
1394     cmd = "gpg --no-default-keyring %s --fingerprint %s" \
1395                 % (gpg_keyring_args(), fingerprint)
1396     (result, output) = commands.getstatusoutput(cmd)
1397     if result == 0:
1398         for l in output.split('\n'):
1399             m = re_gpg_uid.match(l)
1400             if m:
1401                 addresses.add(m.group(1))
1402     key_uid_email_cache[fingerprint] = addresses
1403     return addresses
1404
1405 ################################################################################
1406
1407 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1408
1409 def wrap(paragraph, max_length, prefix=""):
1410     line = ""
1411     s = ""
1412     have_started = 0
1413     words = paragraph.split()
1414
1415     for word in words:
1416         word_size = len(word)
1417         if word_size > max_length:
1418             if have_started:
1419                 s += line + '\n' + prefix
1420             s += word + '\n' + prefix
1421         else:
1422             if have_started:
1423                 new_length = len(line) + word_size + 1
1424                 if new_length > max_length:
1425                     s += line + '\n' + prefix
1426                     line = word
1427                 else:
1428                     line += ' ' + word
1429             else:
1430                 line = word
1431         have_started = 1
1432
1433     if have_started:
1434         s += line
1435
1436     return s
1437
1438 ################################################################################
1439
1440 def clean_symlink (src, dest, root):
1441     """
1442     Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1443     Returns fixed 'src'
1444     """
1445     src = src.replace(root, '', 1)
1446     dest = dest.replace(root, '', 1)
1447     dest = os.path.dirname(dest)
1448     new_src = '../' * len(dest.split('/'))
1449     return new_src + src
1450
1451 ################################################################################
1452
1453 def temp_filename(directory=None, prefix="dak", suffix=""):
1454     """
1455     Return a secure and unique filename by pre-creating it.
1456     If 'directory' is non-null, it will be the directory the file is pre-created in.
1457     If 'prefix' is non-null, the filename will be prefixed with it, default is dak.
1458     If 'suffix' is non-null, the filename will end with it.
1459
1460     Returns a pair (fd, name).
1461     """
1462
1463     return tempfile.mkstemp(suffix, prefix, directory)
1464
1465 ################################################################################
1466
1467 def temp_dirname(parent=None, prefix="dak", suffix=""):
1468     """
1469     Return a secure and unique directory by pre-creating it.
1470     If 'parent' is non-null, it will be the directory the directory is pre-created in.
1471     If 'prefix' is non-null, the filename will be prefixed with it, default is dak.
1472     If 'suffix' is non-null, the filename will end with it.
1473
1474     Returns a pathname to the new directory
1475     """
1476
1477     return tempfile.mkdtemp(suffix, prefix, parent)
1478
1479 ################################################################################
1480
1481 def is_email_alias(email):
1482     """ checks if the user part of the email is listed in the alias file """
1483     global alias_cache
1484     if alias_cache == None:
1485         aliasfn = which_alias_file()
1486         alias_cache = set()
1487         if aliasfn:
1488             for l in open(aliasfn):
1489                 alias_cache.add(l.split(':')[0])
1490     uid = email.split('@')[0]
1491     return uid in alias_cache
1492
1493 ################################################################################
1494
1495 def get_changes_files(from_dir):
1496     """
1497     Takes a directory and lists all .changes files in it (as well as chdir'ing
1498     to the directory; this is due to broken behaviour on the part of p-u/p-a
1499     when you're not in the right place)
1500
1501     Returns a list of filenames
1502     """
1503     try:
1504         # Much of the rest of p-u/p-a depends on being in the right place
1505         os.chdir(from_dir)
1506         changes_files = [x for x in os.listdir(from_dir) if x.endswith('.changes')]
1507     except OSError, e:
1508         fubar("Failed to read list from directory %s (%s)" % (from_dir, e))
1509
1510     return changes_files
1511
1512 ################################################################################
1513
1514 apt_pkg.init()
1515
1516 Cnf = apt_pkg.newConfiguration()
1517 if not os.getenv("DAK_TEST"):
1518     apt_pkg.ReadConfigFileISC(Cnf,default_config)
1519
1520 if which_conf_file() != default_config:
1521     apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())