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