utils.py 45.4 KB
Newer Older
1
#!/usr/bin/env python
2
# vim:set et ts=4 sw=4:
3

J
James Troup 已提交
4
# Utility functions
5
# Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006  James Troup <james@nocrew.org>
6 7

################################################################################
J
James Troup 已提交
8 9 10 11 12 13 14 15 16 17 18 19 20 21 22

# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA

23 24
################################################################################

25
import codecs, commands, email.Header, os, pwd, re, select, socket, shutil, \
26
       sys, tempfile, traceback, stat
27
import apt_pkg
J
James Troup 已提交
28
import database
J
Joerg Jaspert 已提交
29
from dak_exceptions import *
30 31

################################################################################
J
James Troup 已提交
32 33

re_comments = re.compile(r"\#.*")
34 35
re_no_epoch = re.compile(r"^\d+\:")
re_no_revision = re.compile(r"-[^-]+$")
J
James Troup 已提交
36 37
re_arch_from_filename = re.compile(r"/binary-[^/]+/")
re_extract_src_version = re.compile (r"(\S+)\s*\((.*)\)")
38 39
re_isadeb = re.compile (r"(.+?)_(.+?)_(.+)\.u?deb$")
re_issource = re.compile (r"(.+)_(.+?)\.(orig\.tar\.gz|diff\.gz|tar\.gz|dsc)$")
40

41 42 43
re_single_line_field = re.compile(r"^(\S*)\s*:\s*(.*)")
re_multi_line_field = re.compile(r"^\s(.*)")
re_taint_free = re.compile(r"^[-+~/\.\w]+$")
44

45
re_parse_maintainer = re.compile(r"^\s*(\S.*\S)\s*\<([^\>]+)\>")
46
re_gpg_uid = re.compile('^uid.*<([^>]*)>')
J
James Troup 已提交
47

48
re_srchasver = re.compile(r"^(\S+)\s+\((\S+)\)$")
49
re_verwithext = re.compile(r"^(\d+)(?:\.(\d+))(?:\s+\((\S+)\))?$")
J
James Troup 已提交
50

51 52
re_srchasver = re.compile(r"^(\S+)\s+\((\S+)\)$")

53 54
default_config = "/etc/dak/dak.conf"
default_apt_config = "/etc/dak/apt.conf"
J
James Troup 已提交
55

T
Thomas Viehmann 已提交
56
alias_cache = None
57
key_uid_email_cache = {}
T
Thomas Viehmann 已提交
58

59 60 61 62
# (hashname, function, earliest_changes_version)
known_hashes = [("sha1", apt_pkg.sha1sum, (1, 8)),
                ("sha256", apt_pkg.sha256sum, (1, 8))]

63
################################################################################
J
James Troup 已提交
64

65
def open_file(filename, mode='r'):
J
James Troup 已提交
66
    try:
67
        f = open(filename, mode)
J
James Troup 已提交
68
    except IOError:
J
Joerg Jaspert 已提交
69
        raise CantOpenError, filename
J
James Troup 已提交
70 71
    return f

72
################################################################################
J
James Troup 已提交
73

74 75
def our_raw_input(prompt=""):
    if prompt:
76 77
        sys.stdout.write(prompt)
    sys.stdout.flush()
J
James Troup 已提交
78
    try:
79 80
        ret = raw_input()
        return ret
J
James Troup 已提交
81
    except EOFError:
82 83
        sys.stderr.write("\nUser interrupt (^D).\n")
        raise SystemExit
J
James Troup 已提交
84

85
################################################################################
J
James Troup 已提交
86

J
James Troup 已提交
87
def extract_component_from_section(section):
88
    component = ""
J
James Troup 已提交
89

90
    if section.find('/') != -1:
91
        component = section.split('/')[0]
92 93

    # Expand default component
J
James Troup 已提交
94
    if component == "":
95
        if Cnf.has_key("Component::%s" % section):
96
            component = section
97
        else:
98
            component = "main"
J
James Troup 已提交
99

100
    return (section, component)
J
James Troup 已提交
101

102 103
################################################################################

104
def parse_deb822(contents, signing_rules=0):
105 106
    error = ""
    changes = {}
107

108 109
    # Split the lines in the input, keeping the linebreaks.
    lines = contents.splitlines(True)
J
James Troup 已提交
110

111
    if len(lines) == 0:
J
Joerg Jaspert 已提交
112
        raise ParseChangesError, "[Empty changes file]"
113

J
James Troup 已提交
114 115
    # Reindex by line number so we can easily verify the format of
    # .dsc files...
116 117
    index = 0
    indexed_lines = {}
J
James Troup 已提交
118
    for line in lines:
119 120
        index += 1
        indexed_lines[index] = line[:-1]
J
James Troup 已提交
121

122
    inside_signature = 0
J
James Troup 已提交
123

124 125 126
    num_of_lines = len(indexed_lines.keys())
    index = 0
    first = -1
127
    while index < num_of_lines:
128 129
        index += 1
        line = indexed_lines[index]
J
James Troup 已提交
130
        if line == "":
131
            if signing_rules == 1:
132
                index += 1
133
                if index > num_of_lines:
J
Joerg Jaspert 已提交
134
                    raise InvalidDscError, index
135
                line = indexed_lines[index]
136
                if not line.startswith("-----BEGIN PGP SIGNATURE"):
J
Joerg Jaspert 已提交
137
                    raise InvalidDscError, index
138 139
                inside_signature = 0
                break
140
            else:
141
                continue
142
        if line.startswith("-----BEGIN PGP SIGNATURE"):
143
            break
144
        if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
145
            inside_signature = 1
146
            if signing_rules == 1:
147
                while index < num_of_lines and line != "":
148 149 150
                    index += 1
                    line = indexed_lines[index]
            continue
151
        # If we're not inside the signed data, don't process anything
152
        if signing_rules >= 0 and not inside_signature:
153 154
            continue
        slf = re_single_line_field.match(line)
J
James Troup 已提交
155
        if slf:
156 157
            field = slf.groups()[0].lower()
            changes[field] = slf.groups()[1]
158
            first = 1
159
            continue
J
James Troup 已提交
160
        if line == " .":
161 162 163
            changes[field] += '\n'
            continue
        mlf = re_multi_line_field.match(line)
J
James Troup 已提交
164
        if mlf:
165
            if first == -1:
J
Joerg Jaspert 已提交
166
                raise ParseChangesError, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
167
            if first == 1 and changes[field] != "":
168 169
                changes[field] += '\n'
            first = 0
170
            changes[field] += mlf.groups()[0] + '\n'
171
            continue
172
        error += line
J
James Troup 已提交
173

174
    if signing_rules == 1 and inside_signature:
J
Joerg Jaspert 已提交
175
        raise InvalidDscError, index
J
James Troup 已提交
176

177
    changes["filecontents"] = "".join(lines)
J
James Troup 已提交
178

179 180
    if changes.has_key("source"):
        # Strip the source version in brackets from the source field,
181
        # put it in the "source-version" field instead.
182
        srcver = re_srchasver.search(changes["source"])
183
        if srcver:
184
            changes["source"] = srcver.group(1)
185
            changes["source-version"] = srcver.group(2)
186

187
    if error:
J
Joerg Jaspert 已提交
188
        raise ParseChangesError, error
J
James Troup 已提交
189

190
    return changes
J
James Troup 已提交
191

192
################################################################################
J
James Troup 已提交
193

194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231
def parse_changes(filename, signing_rules=0):
    """Parses a changes file and returns a dictionary where each field is a
key.  The mandatory first argument is the filename of the .changes
file.

signing_rules is an optional argument:

 o If signing_rules == -1, no signature is required.
 o If signing_rules == 0 (the default), a signature is required.
 o If signing_rules == 1, it turns on the same strict format checking
   as dpkg-source.

The rules for (signing_rules == 1)-mode are:

  o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
    followed by any PGP header data and must end with a blank line.

  o The data section must end with a blank line and must be followed by
    "-----BEGIN PGP SIGNATURE-----".
"""

    changes_in = open_file(filename)
    content = changes_in.read()
    changes_in.close()
    return parse_deb822(content, signing_rules)

################################################################################

def hash_key(hashname):
    return '%ssum' % hashname

################################################################################

def create_hash(where, files, hashname, hashfunc):
    """create_hash extends the passed files dict with the given hash by
    iterating over all files on disk and passing them to the hashing
    function given."""

232
    rejmsg = []
233
    for f in files.keys():
234 235 236 237 238
        try:
            file_handle = open_file(f)
        except CantOpenError:
            rejmsg.append("Could not open file %s for checksumming" % (f))

239
        files[f][hash_key(hashname)] = hashfunc(file_handle)
240

241
        file_handle.close()
242 243 244 245
    return rejmsg

################################################################################

246 247 248 249
def check_hash(where, files, hashname, hashfunc):
    """check_hash checks the given hash in the files dict against the actual
    files on disk.  The hash values need to be present consistently in
    all file entries.  It does not modify its input in any way."""
250

251 252
    rejmsg = []
    for f in files.keys():
253 254
        try:
            file_handle = open_file(f)
255 256 257 258 259 260 261 262 263 264 265

            # Check for the hash entry, to not trigger a KeyError.
            if not files[f].has_key(hash_key(hashname)):
                rejmsg.append("%s: misses %s checksum in %s" % (f, hashname,
                    where))
                continue

            # Actually check the hash for correctness.
            if hashfunc(file_handle) != files[f][hash_key(hashname)]:
                rejmsg.append("%s: %s check failed in %s" % (f, hashname,
                    where))
266
        except CantOpenError:
267
            # XXX: IS THIS THE BLOODY CASE WHEN THE FILE'S IN THE POOL!?
268
            continue
269 270 271
        finally:
            file_handle.close()
    return rejmsg
272

273 274 275 276 277 278 279 280
################################################################################

def check_size(where, files):
    """check_size checks the file sizes in the passed files dict against the
    files on disk."""

    rejmsg = []
    for f in files.keys():
281
        actual_size = os.stat(f)[stat.ST_SIZE]
282
        size = int(files[f]["size"])
283 284 285
        if size != actual_size:
            rejmsg.append("%s: actual file size (%s) does not match size (%s) in %s"
                   % (f, actual_size, size, where))
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327
    return rejmsg

################################################################################

def check_hash_fields(what, manifest):
    """check_hash_fields ensures that there are no checksum fields in the
    given dict that we do not know about."""

    rejmsg = []
    hashes = map(lambda x: x[0], known_hashes)
    for field in manifest:
        if field.startswith("checksums-"):
            hashname = field.split("-",1)[1]
            if hashname not in hashes:
                rejmsg.append("Unsupported checksum field for %s "\
                    "in %s" % (hashname, what))
    return rejmsg

################################################################################

def _ensure_changes_hash(changes, format, version, files, hashname, hashfunc):
    if format >= version:
        # The version should contain the specified hash.
        func = check_hash

        # Import hashes from the changes
        rejmsg = parse_checksums(".changes", files, changes, hashname)
        if len(rejmsg) > 0:
            return rejmsg
    else:
        # We need to calculate the hash because it can't possibly
        # be in the file.
        func = create_hash
    return func(".changes", files, hashname, hashfunc)

# We could add the orig which might be in the pool to the files dict to
# access the checksums easily.

def _ensure_dsc_hash(dsc, dsc_files, hashname, hashfunc):
    """ensure_dsc_hashes' task is to ensure that each and every *present* hash
    in the dsc is correct, i.e. identical to the changes file and if necessary
    the pool.  The latter task is delegated to check_hash."""
328

329 330 331 332 333 334 335
    rejmsg = []
    if not dsc.has_key('Checksums-%s' % (hashname,)):
        return rejmsg
    # Import hashes from the dsc
    parse_checksums(".dsc", dsc_files, dsc, hashname)
    # And check it...
    rejmsg.extend(check_hash(".dsc", dsc_files, hashname, hashfunc))
336 337 338 339
    return rejmsg

################################################################################

M
Mark Hymers 已提交
340
def ensure_hashes(changes, dsc, files, dsc_files):
341 342 343 344
    rejmsg = []

    # Make sure we recognise the format of the Files: field in the .changes
    format = changes.get("format", "0.0").split(".", 1)
M
Mark Hymers 已提交
345 346 347 348 349
    if len(format) == 2:
        format = int(format[0]), int(format[1])
    else:
        format = int(float(format[0])), 0

350 351 352 353 354 355 356 357 358 359 360 361 362
    # We need to deal with the original changes blob, as the fields we need
    # might not be in the changes dict serialised into the .dak anymore.
    orig_changes = parse_deb822(changes['filecontents'])

    # Copy the checksums over to the current changes dict.  This will keep
    # the existing modifications to it intact.
    for field in orig_changes:
        if field.startswith('checksums-'):
            changes[field] = orig_changes[field]

    # Check for unsupported hashes
    rejmsg.extend(check_hash_fields(".changes", changes))
    rejmsg.extend(check_hash_fields(".dsc", dsc))
363 364 365

    # We have to calculate the hash if we have an earlier changes version than
    # the hash appears in rather than require it exist in the changes file
366 367 368 369 370 371
    for hashname, hashfunc, version in known_hashes:
        rejmsg.extend(_ensure_changes_hash(changes, format, version, files,
            hashname, hashfunc))
        if "source" in changes["architecture"]:
            rejmsg.extend(_ensure_dsc_hash(dsc, dsc_files, hashname,
                hashfunc))
372

373
    return rejmsg
374

375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395
def parse_checksums(where, files, manifest, hashname):
    rejmsg = []
    field = 'checksums-%s' % hashname
    if not field in manifest:
        return rejmsg
    input = manifest[field]
    for line in input.split('\n'):
        if not line:
            break
        hash, size, file = line.strip().split(' ')
        if not files.has_key(file):
            rejmsg.append("%s: not present in files but in checksums-%s in %s" %
                (file, hashname, where))
        if not files[file]["size"] == size:
            rejmsg.append("%s: size differs for files and checksums-%s entry "\
                "in %s" % (file, hashname, where))
        files[file][hash_key(hashname)] = hash
    for f in files.keys():
        if not files[f].has_key(hash_key(hashname)):
            rejmsg.append("%s: no entry in checksums-%s in %s" % (file,
                hashname, where))
396 397 398 399
    return rejmsg

################################################################################

J
James Troup 已提交
400 401
# Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl

402
def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
403
    files = {}
404 405

    # Make sure we have a Files: field to parse...
406
    if not changes.has_key(field):
J
Joerg Jaspert 已提交
407
        raise NoFilesFieldError
408 409

    # Make sure we recognise the format of the Files: field
410 411
    format = re_verwithext.search(changes.get("format", "0.0"))
    if not format:
J
Joerg Jaspert 已提交
412
        raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
413 414 415 416

    format = format.groups()
    if format[1] == None:
        format = int(float(format[0])), 0, format[2]
417
    else:
418 419 420
        format = int(format[0]), int(format[1]), format[2]
    if format[2] == None:
        format = format[:2]
421 422 423

    if is_a_dsc:
        if format != (1,0):
J
Joerg Jaspert 已提交
424
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
425 426
    else:
        if (format < (1,5) or format > (1,8)):
J
Joerg Jaspert 已提交
427
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
428
        if field != "files" and format < (1,8):
J
Joerg Jaspert 已提交
429
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
430 431

    includes_section = (not is_a_dsc) and field == "files"
J
James Troup 已提交
432

433
    # Parse each entry/line:
434
    for i in changes[field].split('\n'):
435
        if not i:
436 437 438
            break
        s = i.split()
        section = priority = ""
J
sync  
James Troup 已提交
439
        try:
440
            if includes_section:
441
                (md5, size, section, priority, name) = s
442 443
            else:
                (md5, size, name) = s
J
sync  
James Troup 已提交
444
        except ValueError:
J
Joerg Jaspert 已提交
445
            raise ParseChangesError, i
J
James Troup 已提交
446

447
        if section == "":
448
            section = "-"
449
        if priority == "":
450
            priority = "-"
J
James Troup 已提交
451

452
        (section, component) = extract_component_from_section(section)
J
James Troup 已提交
453

454
        files[name] = Dict(size=size, section=section,
455
                           priority=priority, component=component)
456
        files[name][hashname] = md5
J
James Troup 已提交
457 458 459

    return files

460
################################################################################
J
James Troup 已提交
461

462 463 464 465
def force_to_utf8(s):
    """Forces a string to UTF-8.  If the string isn't already UTF-8,
it's assumed to be ISO-8859-1."""
    try:
466 467
        unicode(s, 'utf-8')
        return s
468
    except UnicodeError:
469 470
        latin1_s = unicode(s,'iso8859-1')
        return latin1_s.encode('utf-8')
471 472 473 474 475

def rfc2047_encode(s):
    """Encodes a (header) string per RFC2047 if necessary.  If the
string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
    try:
476
        codecs.lookup('ascii')[1](s)
477
        return s
478
    except UnicodeError:
479
        pass
480
    try:
481
        codecs.lookup('utf-8')[1](s)
482 483
        h = email.Header.Header(s, 'utf-8', 998)
        return str(h)
484
    except UnicodeError:
485 486
        h = email.Header.Header(s, 'iso-8859-1', 998)
        return str(h)
487 488 489 490 491 492

################################################################################

# <Culus> 'The standard sucks, but my tool is supposed to interoperate
#          with it. I know - I'll fix the suckage and make things
#          incompatible!'
J
James Troup 已提交
493

J
James Troup 已提交
494
def fix_maintainer (maintainer):
495 496 497 498 499 500 501 502 503 504 505
    """Parses a Maintainer or Changed-By field and returns:
  (1) an RFC822 compatible version,
  (2) an RFC2047 compatible version,
  (3) the name
  (4) the email

The name is forced to UTF-8 for both (1) and (3).  If the name field
contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
switched to 'email (name)' format."""
    maintainer = maintainer.strip()
    if not maintainer:
506
        return ('', '', '', '')
507

508
    if maintainer.find("<") == -1:
509 510
        email = maintainer
        name = ""
511
    elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
512 513
        email = maintainer[1:-1]
        name = ""
514
    else:
515
        m = re_parse_maintainer.match(maintainer)
516 517
        if not m:
            raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
518 519
        name = m.group(1)
        email = m.group(2)
520 521

    # Get an RFC2047 compliant version of the name
522
    rfc2047_name = rfc2047_encode(name)
523 524

    # Force the name to be UTF-8
525
    name = force_to_utf8(name)
526 527

    if name.find(',') != -1 or name.find('.') != -1:
528 529
        rfc822_maint = "%s (%s)" % (email, name)
        rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
530
    else:
531 532
        rfc822_maint = "%s <%s>" % (name, email)
        rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
533 534 535 536

    if email.find("@") == -1 and email.find("buildd_") != 0:
        raise ParseMaintError, "No @ found in email address part."

537
    return (rfc822_maint, rfc2047_maint, name, email)
J
James Troup 已提交
538

539
################################################################################
J
James Troup 已提交
540 541

# sendmail wrapper, takes _either_ a message string or a file as arguments
542
def send_mail (message, filename=""):
543 544 545 546 547 548 549 550 551 552
        # If we've been passed a string dump it into a temporary file
    if message:
        filename = tempfile.mktemp()
        fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
        os.write (fd, message)
        os.close (fd)

    # Invoke sendmail
    (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
    if (result != 0):
J
Joerg Jaspert 已提交
553
        raise SendmailFailedError, output
554 555 556 557

    # Clean up any temporary files
    if message:
        os.unlink (filename)
J
James Troup 已提交
558

559
################################################################################
J
James Troup 已提交
560 561

def poolify (source, component):
562
    if component:
563
        component += '/'
J
James Troup 已提交
564
    if source[:3] == "lib":
565
        return component + source[:4] + '/' + source + '/'
J
James Troup 已提交
566
    else:
567
        return component + source[:1] + '/' + source + '/'
J
James Troup 已提交
568

569
################################################################################
J
James Troup 已提交
570

571
def move (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
572
    if os.path.exists(dest) and os.path.isdir(dest):
573
        dest_dir = dest
J
James Troup 已提交
574
    else:
575
        dest_dir = os.path.dirname(dest)
J
James Troup 已提交
576
    if not os.path.exists(dest_dir):
577 578 579
        umask = os.umask(00000)
        os.makedirs(dest_dir, 02775)
        os.umask(umask)
580
    #print "Moving %s to %s..." % (src, dest)
J
James Troup 已提交
581
    if os.path.exists(dest) and os.path.isdir(dest):
582
        dest += '/' + os.path.basename(src)
583 584 585
    # Don't overwrite unless forced to
    if os.path.exists(dest):
        if not overwrite:
586
            fubar("Can't move %s to %s - file already exists." % (src, dest))
587 588
        else:
            if not os.access(dest, os.W_OK):
589 590 591 592
                fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
    shutil.copy2(src, dest)
    os.chmod(dest, perms)
    os.unlink(src)
J
James Troup 已提交
593

594
def copy (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
595
    if os.path.exists(dest) and os.path.isdir(dest):
596
        dest_dir = dest
J
James Troup 已提交
597
    else:
598
        dest_dir = os.path.dirname(dest)
J
James Troup 已提交
599
    if not os.path.exists(dest_dir):
600 601 602
        umask = os.umask(00000)
        os.makedirs(dest_dir, 02775)
        os.umask(umask)
603
    #print "Copying %s to %s..." % (src, dest)
J
James Troup 已提交
604
    if os.path.exists(dest) and os.path.isdir(dest):
605
        dest += '/' + os.path.basename(src)
606 607 608
    # Don't overwrite unless forced to
    if os.path.exists(dest):
        if not overwrite:
J
Joerg Jaspert 已提交
609
            raise FileExistsError
610 611
        else:
            if not os.access(dest, os.W_OK):
J
Joerg Jaspert 已提交
612
                raise CantOverwriteError
613 614
    shutil.copy2(src, dest)
    os.chmod(dest, perms)
J
James Troup 已提交
615

616
################################################################################
J
James Troup 已提交
617 618

def where_am_i ():
619 620
    res = socket.gethostbyaddr(socket.gethostname())
    database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
621
    if database_hostname:
622
        return database_hostname
J
James Troup 已提交
623
    else:
624
        return res[0]
J
James Troup 已提交
625 626

def which_conf_file ():
627
    res = socket.gethostbyaddr(socket.gethostname())
628
    if Cnf.get("Config::" + res[0] + "::DakConfig"):
629
        return Cnf["Config::" + res[0] + "::DakConfig"]
J
James Troup 已提交
630
    else:
631
        return default_config
632 633

def which_apt_conf_file ():
634
    res = socket.gethostbyaddr(socket.gethostname())
635
    if Cnf.get("Config::" + res[0] + "::AptConfig"):
636
        return Cnf["Config::" + res[0] + "::AptConfig"]
637
    else:
638
        return default_apt_config
639

T
Thomas Viehmann 已提交
640 641 642 643 644 645 646 647
def which_alias_file():
    hostname = socket.gethostbyaddr(socket.gethostname())[0]
    aliasfn = '/var/lib/misc/'+hostname+'/forward-alias'
    if os.path.exists(aliasfn):
        return aliasfn
    else:
        return None

648
################################################################################
J
James Troup 已提交
649

650 651 652 653
# Escape characters which have meaning to SQL's regex comparison operator ('~')
# (woefully incomplete)

def regex_safe (s):
654 655
    s = s.replace('+', '\\\\+')
    s = s.replace('.', '\\\\.')
656 657
    return s

658
################################################################################
659

J
James Troup 已提交
660
# Perform a substition of template
661
def TemplateSubst(map, filename):
662 663
    file = open_file(filename)
    template = file.read()
664
    for x in map.keys():
665 666 667
        template = template.replace(x,map[x])
    file.close()
    return template
J
James Troup 已提交
668

669
################################################################################
J
James Troup 已提交
670 671

def fubar(msg, exit_code=1):
672 673
    sys.stderr.write("E: %s\n" % (msg))
    sys.exit(exit_code)
J
James Troup 已提交
674 675

def warn(msg):
676
    sys.stderr.write("W: %s\n" % (msg))
J
James Troup 已提交
677

678
################################################################################
J
James Troup 已提交
679

J
James Troup 已提交
680 681 682
# Returns the user name with a laughable attempt at rfc822 conformancy
# (read: removing stray periods).
def whoami ():
683
    return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
J
James Troup 已提交
684

685
################################################################################
J
James Troup 已提交
686

J
James Troup 已提交
687
def size_type (c):
688
    t  = " B"
689
    if c > 10240:
690 691
        c = c / 1024
        t = " KB"
692
    if c > 10240:
693 694
        c = c / 1024
        t = " MB"
J
James Troup 已提交
695
    return ("%d%s" % (c, t))
696 697 698 699

################################################################################

def cc_fix_changes (changes):
700
    o = changes.get("architecture", "")
701
    if o:
702 703
        del changes["architecture"]
    changes["architecture"] = {}
704
    for j in o.split():
705
        changes["architecture"][j] = 1
706

707
# Sort by source name, source version, 'have source', and then by filename
708 709
def changes_compare (a, b):
    try:
710
        a_changes = parse_changes(a)
711
    except:
712
        return -1
713 714

    try:
715
        b_changes = parse_changes(b)
716
    except:
717
        return 1
J
James Troup 已提交
718

719 720
    cc_fix_changes (a_changes)
    cc_fix_changes (b_changes)
721 722

    # Sort by source name
723 724 725
    a_source = a_changes.get("source")
    b_source = b_changes.get("source")
    q = cmp (a_source, b_source)
726
    if q:
727
        return q
728 729

    # Sort by source version
730 731 732
    a_version = a_changes.get("version", "0")
    b_version = b_changes.get("version", "0")
    q = apt_pkg.VersionCompare(a_version, b_version)
733
    if q:
734
        return q
735

736
    # Sort by 'have source'
737 738
    a_has_source = a_changes["architecture"].get("source")
    b_has_source = b_changes["architecture"].get("source")
739
    if a_has_source and not b_has_source:
740
        return -1
741
    elif b_has_source and not a_has_source:
742
        return 1
743

744
    # Fall back to sort by filename
745
    return cmp(a, b)
746 747

################################################################################
748 749

def find_next_free (dest, too_many=100):
750 751
    extra = 0
    orig_dest = dest
752
    while os.path.exists(dest) and extra < too_many:
753 754
        dest = orig_dest + '.' + repr(extra)
        extra += 1
755
    if extra >= too_many:
J
Joerg Jaspert 已提交
756
        raise NoFreeFilenameError
757
    return dest
J
James Troup 已提交
758

759
################################################################################
760 761

def result_join (original, sep = '\t'):
762
    list = []
763 764
    for i in xrange(len(original)):
        if original[i] == None:
765
            list.append("")
766
        else:
767 768
            list.append(original[i])
    return sep.join(list)
769

770 771
################################################################################

772
def prefix_multi_line_string(str, prefix, include_blank_lines=0):
773
    out = ""
774
    for line in str.split('\n'):
775
        line = line.strip()
776
        if line or include_blank_lines:
777
            out += "%s%s\n" % (prefix, line)
778 779
    # Strip trailing new line
    if out:
780 781
        out = out[:-1]
    return out
782

783
################################################################################
784

785
def validate_changes_file_arg(filename, require_changes=1):
786 787
    """'filename' is either a .changes or .dak file.  If 'filename' is a
.dak file, it's changed to be the corresponding .changes file.  The
788 789 790 791 792 793 794 795 796 797
function then checks if the .changes file a) exists and b) is
readable and returns the .changes filename if so.  If there's a
problem, the next action depends on the option 'require_changes'
argument:

 o If 'require_changes' == -1, errors are ignored and the .changes
                               filename is returned.
 o If 'require_changes' == 0, a warning is given and 'None' is returned.
 o If 'require_changes' == 1, a fatal error is raised.
"""
798
    error = None
799

800
    orig_filename = filename
801
    if filename.endswith(".dak"):
802
        filename = filename[:-4]+".changes"
803

804
    if not filename.endswith(".changes"):
805
        error = "invalid file type; not a changes file"
806
    else:
807 808
        if not os.access(filename,os.R_OK):
            if os.path.exists(filename):
809
                error = "permission denied"
810
            else:
811
                error = "file not found"
812 813

    if error:
814
        if require_changes == 1:
815
            fubar("%s: %s." % (orig_filename, error))
816
        elif require_changes == 0:
817 818
            warn("Skipping %s - %s" % (orig_filename, error))
            return None
819
        else: # We only care about the .dak file
820
            return filename
821
    else:
822
        return filename
823 824 825

################################################################################

826
def real_arch(arch):
827
    return (arch != "source" and arch != "all")
828 829 830

################################################################################

831
def join_with_commas_and(list):
832 833 834
    if len(list) == 0: return "nothing"
    if len(list) == 1: return list[0]
    return ", ".join(list[:-1]) + " and " + list[-1]
835 836 837

################################################################################

838
def pp_deps (deps):
839
    pp_deps = []
840
    for atom in deps:
841
        (pkg, version, constraint) = atom
842
        if constraint:
843
            pp_dep = "%s (%s %s)" % (pkg, constraint, version)
844
        else:
845 846 847
            pp_dep = pkg
        pp_deps.append(pp_dep)
    return " |".join(pp_deps)
848 849 850

################################################################################

851
def get_conf():
852
    return Cnf
853 854 855

################################################################################

856 857 858 859
# Handle -a, -c and -s arguments; returns them as SQL constraints
def parse_args(Options):
    # Process suite
    if Options["Suite"]:
860
        suite_ids_list = []
861
        for suite in split_args(Options["Suite"]):
J
James Troup 已提交
862
            suite_id = database.get_suite_id(suite)
863
            if suite_id == -1:
864
                warn("suite '%s' not recognised." % (suite))
865
            else:
866
                suite_ids_list.append(suite_id)
867
        if suite_ids_list:
868
            con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
869
        else:
870
            fubar("No valid suite given.")
871
    else:
872
        con_suites = ""
873 874 875

    # Process component
    if Options["Component"]:
876
        component_ids_list = []
877
        for component in split_args(Options["Component"]):
J
James Troup 已提交
878
            component_id = database.get_component_id(component)
879
            if component_id == -1:
880
                warn("component '%s' not recognised." % (component))
881
            else:
882
                component_ids_list.append(component_id)
883
        if component_ids_list:
884
            con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
885
        else:
886
            fubar("No valid component given.")
887
    else:
888
        con_components = ""
889 890

    # Process architecture
891
    con_architectures = ""
892
    if Options["Architecture"]:
893 894
        arch_ids_list = []
        check_source = 0
895
        for architecture in split_args(Options["Architecture"]):
896
            if architecture == "source":
897
                check_source = 1
898
            else:
J
James Troup 已提交
899
                architecture_id = database.get_architecture_id(architecture)
900
                if architecture_id == -1:
901
                    warn("architecture '%s' not recognised." % (architecture))
902
                else:
903
                    arch_ids_list.append(architecture_id)
904
        if arch_ids_list:
905
            con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
906 907
        else:
            if not check_source:
908
                fubar("No valid architecture given.")
909
    else:
910
        check_source = 1
911

912
    return (con_suites, con_architectures, con_components, check_source)
913 914 915

################################################################################

916 917 918 919
# Inspired(tm) by Bryn Keller's print_exc_plus (See
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)

def print_exc():
920
    tb = sys.exc_info()[2]
921
    while tb.tb_next:
922 923 924
        tb = tb.tb_next
    stack = []
    frame = tb.tb_frame
925
    while frame:
926 927 928 929
        stack.append(frame)
        frame = frame.f_back
    stack.reverse()
    traceback.print_exc()
930 931 932
    for frame in stack:
        print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
                                             frame.f_code.co_filename,
933
                                             frame.f_lineno)
934
        for key, value in frame.f_locals.items():
935
            print "\t%20s = " % key,
936
            try:
937
                print value
938
            except:
939
                print "<unable to print>"
940 941 942

################################################################################

J
James Troup 已提交
943 944
def try_with_debug(function):
    try:
945
        function()
J
James Troup 已提交
946
    except SystemExit:
947
        raise
J
James Troup 已提交
948
    except:
949
        print_exc()
J
James Troup 已提交
950 951 952

################################################################################

J
James Troup 已提交
953 954 955 956 957
# Function for use in sorting lists of architectures.
# Sorts normally except that 'source' dominates all others.

def arch_compare_sw (a, b):
    if a == "source" and b == "source":
958
        return 0
J
James Troup 已提交
959
    elif a == "source":
960
        return -1
J
James Troup 已提交
961
    elif b == "source":
962
        return 1
J
James Troup 已提交
963

964
    return cmp (a, b)
J
James Troup 已提交
965 966 967

################################################################################

968 969
# Split command line arguments which can be separated by either commas
# or whitespace.  If dwim is set, it will complain about string ending
970
# in comma since this usually means someone did 'dak ls -a i386, m68k
971 972 973 974 975
# foo' or something and the inevitable confusion resulting from 'm68k'
# being treated as an argument is undesirable.

def split_args (s, dwim=1):
    if s.find(",") == -1:
976
        return s.split()
977 978
    else:
        if s[-1:] == "," and dwim:
979 980
            fubar("split_args: found trailing comma, spurious space maybe?")
        return s.split(",")
981 982 983

################################################################################

984 985 986 987 988 989 990
def Dict(**dict): return dict

########################################

# Our very own version of commands.getouputstatus(), hacked to support
# gpgv's status fd.
def gpgv_get_status_output(cmd, status_read, status_write):
991 992 993 994 995
    cmd = ['/bin/sh', '-c', cmd]
    p2cread, p2cwrite = os.pipe()
    c2pread, c2pwrite = os.pipe()
    errout, errin = os.pipe()
    pid = os.fork()
996 997
    if pid == 0:
        # Child
998 999 1000 1001 1002 1003
        os.close(0)
        os.close(1)
        os.dup(p2cread)
        os.dup(c2pwrite)
        os.close(2)
        os.dup(errin)
1004 1005 1006
        for i in range(3, 256):
            if i != status_write:
                try:
1007
                    os.close(i)
1008
                except:
1009
                    pass
1010
        try:
1011
            os.execvp(cmd[0], cmd)
1012
        finally:
1013
            os._exit(1)
1014

1015
    # Parent
1016
    os.close(p2cread)
1017 1018
    os.dup2(c2pread, c2pwrite)
    os.dup2(errout, errin)
1019

1020
    output = status = ""
1021
    while 1:
1022 1023
        i, o, e = select.select([c2pwrite, errin, status_read], [], [])
        more_data = []
1024
        for fd in i:
1025
            r = os.read(fd, 8196)
1026
            if len(r) > 0:
1027
                more_data.append(fd)
1028
                if fd == c2pwrite or fd == errin:
1029
                    output += r
1030
                elif fd == status_read:
1031
                    status += r
1032
                else:
1033
                    fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
1034 1035 1036
        if not more_data:
            pid, exit_status = os.waitpid(pid, 0)
            try:
1037 1038 1039 1040 1041 1042 1043
                os.close(status_write)
                os.close(status_read)
                os.close(c2pread)
                os.close(c2pwrite)
                os.close(p2cwrite)
                os.close(errin)
                os.close(errout)
1044
            except:
1045 1046
                pass
            break
1047

1048
    return output, status, exit_status
1049

1050
################################################################################
1051

1052 1053 1054 1055 1056 1057 1058 1059 1060 1061 1062 1063 1064 1065 1066 1067 1068
def process_gpgv_output(status):
    # Process the status-fd output
    keywords = {}
    internal_error = ""
    for line in status.split('\n'):
        line = line.strip()
        if line == "":
            continue
        split = line.split()
        if len(split) < 2:
            internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
            continue
        (gnupg, keyword) = split[:2]
        if gnupg != "[GNUPG:]":
            internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
            continue
        args = split[2:]
1069
        if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
1070 1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087
            internal_error += "found duplicate status token ('%s').\n" % (keyword)
            continue
        else:
            keywords[keyword] = args

    return (keywords, internal_error)

################################################################################

def retrieve_key (filename, keyserver=None, keyring=None):
    """Retrieve the key that signed 'filename' from 'keyserver' and
add it to 'keyring'.  Returns nothing on success, or an error message
on error."""

    # Defaults for keyserver and keyring
    if not keyserver:
        keyserver = Cnf["Dinstall::KeyServer"]
    if not keyring:
1088
        keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
1089 1090 1091 1092 1093 1094

    # Ensure the filename contains no shell meta-characters or other badness
    if not re_taint_free.match(filename):
        return "%s: tainted filename" % (filename)

    # Invoke gpgv on the file
1095
    status_read, status_write = os.pipe();
1096 1097
    cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
    (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
1098

1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122
    # Process the status-fd output
    (keywords, internal_error) = process_gpgv_output(status)
    if internal_error:
        return internal_error

    if not keywords.has_key("NO_PUBKEY"):
        return "didn't find expected NO_PUBKEY in gpgv status-fd output"

    fingerprint = keywords["NO_PUBKEY"][0]
    # XXX - gpg sucks.  You can't use --secret-keyring=/dev/null as
    # it'll try to create a lockfile in /dev.  A better solution might
    # be a tempfile or something.
    cmd = "gpg --no-default-keyring --secret-keyring=%s --no-options" \
          % (Cnf["Dinstall::SigningKeyring"])
    cmd += " --keyring %s --keyserver %s --recv-key %s" \
           % (keyring, keyserver, fingerprint)
    (result, output) = commands.getstatusoutput(cmd)
    if (result != 0):
        return "'%s' failed with exit code %s" % (cmd, result)

    return ""

################################################################################

1123 1124 1125 1126 1127 1128 1129 1130
def gpg_keyring_args(keyrings=None):
    if not keyrings:
        keyrings = Cnf.ValueList("Dinstall::GPGKeyring")

    return " ".join(["--keyring %s" % x for x in keyrings])

################################################################################

1131
def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
1132 1133 1134 1135 1136
    """Check the signature of a file and return the fingerprint if the
signature is valid or 'None' if it's not.  The first argument is the
filename whose signature should be checked.  The second argument is a
reject function and is called when an error is found.  The reject()
function must allow for two arguments: the first is the error message,
1137
the second is an optional prefix string.  It's possible for reject()
1138 1139 1140
to be called more than once during an invocation of check_signature().
The third argument is optional and is the name of the files the
detached signature applies to.  The fourth argument is optional and is
1141 1142 1143
a *list* of keyrings to use.  'autofetch' can either be None, True or
False.  If None, the default behaviour specified in the config will be
used."""
1144 1145

    # Ensure the filename contains no shell meta-characters or other badness
1146
    if not re_taint_free.match(sig_filename):
1147 1148
        reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
        return None
1149 1150

    if data_filename and not re_taint_free.match(data_filename):
1151 1152
        reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
        return None
1153 1154

    if not keyrings:
1155
        keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
1156

1157 1158 1159 1160 1161 1162 1163 1164 1165
    # Autofetch the signing key if that's enabled
    if autofetch == None:
        autofetch = Cnf.get("Dinstall::KeyAutoFetch")
    if autofetch:
        error_msg = retrieve_key(sig_filename)
        if error_msg:
            reject(error_msg)
            return None

1166
    # Build the command line
1167
    status_read, status_write = os.pipe();
1168 1169 1170
    cmd = "gpgv --status-fd %s %s %s %s" % (
        status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)

1171
    # Invoke gpgv on the file
1172
    (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
1173 1174

    # Process the status-fd output
1175
    (keywords, internal_error) = process_gpgv_output(status)
1176 1177 1178

    # If we failed to parse the status-fd output, let's just whine and bail now
    if internal_error:
1179 1180 1181 1182
        reject("internal error while performing signature check on %s." % (sig_filename))
        reject(internal_error, "")
        reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
        return None
1183

1184
    bad = ""
1185 1186
    # Now check for obviously bad things in the processed output
    if keywords.has_key("KEYREVOKED"):
1187 1188
        reject("The key used to sign %s has been revoked." % (sig_filename))
        bad = 1
1189
    if keywords.has_key("BADSIG"):
1190 1191
        reject("bad signature on %s." % (sig_filename))
        bad = 1
1192
    if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1193 1194
        reject("failed to check signature on %s." % (sig_filename))
        bad = 1
1195
    if keywords.has_key("NO_PUBKEY"):
1196
        args = keywords["NO_PUBKEY"]
1197
        if len(args) >= 1:
1198 1199 1200
            key = args[0]
        reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
        bad = 1
1201
    if keywords.has_key("BADARMOR"):
1202 1203
        reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
        bad = 1
1204
    if keywords.has_key("NODATA"):
1205 1206
        reject("no signature found in %s." % (sig_filename))
        bad = 1
1207
    if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1208 1209 1210
        args = keywords["KEYEXPIRED"]
        if len(args) >= 1:
            key = args[0]
1211 1212
        reject("The key (0x%s) used to sign %s has expired." % (key, sig_filename))
        bad = 1
1213 1214

    if bad:
1215
        return None
1216 1217 1218

    # Next check gpgv exited with a zero return code
    if exit_status:
1219
        reject("gpgv failed while checking %s." % (sig_filename))
1220
        if status.strip():
1221
            reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1222
        else:
1223 1224
            reject(prefix_multi_line_string(output, " [GPG output:] "), "")
        return None
1225 1226 1227

    # Sanity check the good stuff we expect
    if not keywords.has_key("VALIDSIG"):
1228 1229
        reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
        bad = 1
1230
    else:
1231
        args = keywords["VALIDSIG"]
1232
        if len(args) < 1:
1233 1234
            reject("internal error while checking signature on %s." % (sig_filename))
            bad = 1
1235
        else:
1236
            fingerprint = args[0]
1237
    if not keywords.has_key("GOODSIG"):
1238 1239
        reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
        bad = 1
1240
    if not keywords.has_key("SIG_ID"):
1241 1242
        reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
        bad = 1
1243 1244 1245 1246

    # Finally ensure there's not something we don't recognise
    known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
                          SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1247
                          NODATA="",NOTATION_DATA="",NOTATION_NAME="",KEYEXPIRED="")
1248 1249 1250

    for keyword in keywords.keys():
        if not known_keywords.has_key(keyword):
1251 1252
            reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
            bad = 1
1253 1254

    if bad:
1255
        return None
1256
    else:
1257
        return fingerprint
1258 1259 1260

################################################################################

1261
def gpg_get_key_addresses(fingerprint):
1262 1263 1264 1265 1266 1267 1268 1269 1270 1271 1272 1273 1274 1275 1276
    """retreive email addresses from gpg key uids for a given fingerprint"""
    addresses = key_uid_email_cache.get(fingerprint)
    if addresses != None:
        return addresses
    addresses = set()
    cmd = "gpg --no-default-keyring %s --fingerprint %s" \
                % (gpg_keyring_args(), fingerprint)
    (result, output) = commands.getstatusoutput(cmd)
    if result == 0:
        for l in output.split('\n'):
            m = re_gpg_uid.match(l)
            if m:
                addresses.add(m.group(1))
    key_uid_email_cache[fingerprint] = addresses
    return addresses
1277 1278 1279

################################################################################

1280 1281 1282
# Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603

def wrap(paragraph, max_length, prefix=""):
1283 1284 1285 1286
    line = ""
    s = ""
    have_started = 0
    words = paragraph.split()
1287 1288

    for word in words:
1289
        word_size = len(word)
1290 1291
        if word_size > max_length:
            if have_started:
1292 1293
                s += line + '\n' + prefix
            s += word + '\n' + prefix
1294 1295
        else:
            if have_started:
1296
                new_length = len(line) + word_size + 1
1297
                if new_length > max_length:
1298 1299
                    s += line + '\n' + prefix
                    line = word
1300
                else:
1301
                    line += ' ' + word
1302
            else:
1303 1304
                line = word
        have_started = 1
1305 1306

    if have_started:
1307
        s += line
1308

1309
    return s
1310 1311 1312 1313 1314 1315

################################################################################

# Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
# Returns fixed 'src'
def clean_symlink (src, dest, root):
1316 1317 1318 1319 1320
    src = src.replace(root, '', 1)
    dest = dest.replace(root, '', 1)
    dest = os.path.dirname(dest)
    new_src = '../' * len(dest.split('/'))
    return new_src + src
1321 1322 1323

################################################################################

1324 1325 1326 1327 1328 1329
def temp_filename(directory=None, dotprefix=None, perms=0700):
    """Return a secure and unique filename by pre-creating it.
If 'directory' is non-null, it will be the directory the file is pre-created in.
If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""

    if directory:
1330 1331
        old_tempdir = tempfile.tempdir
        tempfile.tempdir = directory
1332

1333
    filename = tempfile.mktemp()
1334 1335

    if dotprefix:
1336 1337 1338
        filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename))
    fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms)
    os.close(fd)
1339 1340

    if directory:
1341
        tempfile.tempdir = old_tempdir
1342

1343
    return filename
1344 1345 1346

################################################################################

T
Thomas Viehmann 已提交
1347 1348 1349
# checks if the user part of the email is listed in the alias file

def is_email_alias(email):
T
Thomas Viehmann 已提交
1350 1351 1352 1353 1354 1355 1356 1357 1358
    global alias_cache
    if alias_cache == None:
        aliasfn = which_alias_file()
        alias_cache = set()
        if aliasfn:
            for l in open(aliasfn):
                alias_cache.add(l.split(':')[0])
    uid = email.split('@')[0]
    return uid in alias_cache
T
Thomas Viehmann 已提交
1359 1360 1361

################################################################################

1362
apt_pkg.init()
1363

1364 1365
Cnf = apt_pkg.newConfiguration()
apt_pkg.ReadConfigFileISC(Cnf,default_config)
1366 1367

if which_conf_file() != default_config:
1368
    apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1369 1370

################################################################################