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