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