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