utils.py 46.2 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
        file_handle = None
254
        try:
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269
            try:
                file_handle = open_file(f)
    
                # 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))
            except CantOpenError:
                # TODO: This happens when the file is in the pool.
J
Shutup  
Joerg Jaspert 已提交
270
                # warn("Cannot open file %s" % f)
271 272
                continue
        finally:
273 274
            if file_handle:
                file_handle.close()
275
    return rejmsg
276

277 278 279 280 281 282 283 284
################################################################################

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():
285 286 287 288 289 290 291 292 293
        try:
            entry = os.stat(f)
        except OSError, exc:
            if exc.errno == 2:
                # TODO: This happens when the file is in the pool.
                continue
            raise

        actual_size = entry[stat.ST_SIZE]
294
        size = int(files[f]["size"])
295 296 297
        if size != actual_size:
            rejmsg.append("%s: actual file size (%s) does not match size (%s) in %s"
                   % (f, actual_size, size, where))
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 328 329 330 331 332 333 334 335 336 337 338 339
    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."""
340

341 342 343 344 345 346 347
    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))
348 349 350 351
    return rejmsg

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

M
Mark Hymers 已提交
352
def ensure_hashes(changes, dsc, files, dsc_files):
353 354 355 356
    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 已提交
357 358 359 360 361
    if len(format) == 2:
        format = int(format[0]), int(format[1])
    else:
        format = int(float(format[0])), 0

362 363 364 365 366 367 368 369 370 371 372 373 374
    # 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))
375 376 377

    # 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
378 379 380 381 382 383
    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))
384

385
    return rejmsg
386

387 388 389 390 391 392 393 394 395 396
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(' ')
397
        if not files.has_key(file):
398 399 400 401
        # TODO: check for the file's entry in the original files dict, not
        # the one modified by (auto)byhand and other weird stuff
        #    rejmsg.append("%s: not present in files but in checksums-%s in %s" %
        #        (file, hashname, where))
402
            continue
403 404 405
        if not files[file]["size"] == size:
            rejmsg.append("%s: size differs for files and checksums-%s entry "\
                "in %s" % (file, hashname, where))
406
            continue
407 408 409 410 411
        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))
412 413 414 415
    return rejmsg

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

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

418
def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
419
    files = {}
420 421

    # Make sure we have a Files: field to parse...
422
    if not changes.has_key(field):
J
Joerg Jaspert 已提交
423
        raise NoFilesFieldError
424 425

    # Make sure we recognise the format of the Files: field
426 427
    format = re_verwithext.search(changes.get("format", "0.0"))
    if not format:
J
Joerg Jaspert 已提交
428
        raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
429 430 431 432

    format = format.groups()
    if format[1] == None:
        format = int(float(format[0])), 0, format[2]
433
    else:
434 435 436
        format = int(format[0]), int(format[1]), format[2]
    if format[2] == None:
        format = format[:2]
437 438

    if is_a_dsc:
439 440 441 442
        # format = (1,0) are the only formats we currently accept,
        # format = (0,0) are missing format headers of which we still
        # have some in the archive.
        if format != (1,0) and format != (0,0):
J
Joerg Jaspert 已提交
443
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
444 445
    else:
        if (format < (1,5) or format > (1,8)):
J
Joerg Jaspert 已提交
446
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
447
        if field != "files" and format < (1,8):
J
Joerg Jaspert 已提交
448
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
449 450

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

452
    # Parse each entry/line:
453
    for i in changes[field].split('\n'):
454
        if not i:
455 456 457
            break
        s = i.split()
        section = priority = ""
J
sync  
James Troup 已提交
458
        try:
459
            if includes_section:
460
                (md5, size, section, priority, name) = s
461 462
            else:
                (md5, size, name) = s
J
sync  
James Troup 已提交
463
        except ValueError:
J
Joerg Jaspert 已提交
464
            raise ParseChangesError, i
J
James Troup 已提交
465

466
        if section == "":
467
            section = "-"
468
        if priority == "":
469
            priority = "-"
J
James Troup 已提交
470

471
        (section, component) = extract_component_from_section(section)
J
James Troup 已提交
472

473
        files[name] = Dict(size=size, section=section,
474
                           priority=priority, component=component)
475
        files[name][hashname] = md5
J
James Troup 已提交
476 477 478

    return files

479
################################################################################
J
James Troup 已提交
480

481 482 483 484
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:
485 486
        unicode(s, 'utf-8')
        return s
487
    except UnicodeError:
488 489
        latin1_s = unicode(s,'iso8859-1')
        return latin1_s.encode('utf-8')
490 491 492 493 494

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:
495
        codecs.lookup('ascii')[1](s)
496
        return s
497
    except UnicodeError:
498
        pass
499
    try:
500
        codecs.lookup('utf-8')[1](s)
501 502
        h = email.Header.Header(s, 'utf-8', 998)
        return str(h)
503
    except UnicodeError:
504 505
        h = email.Header.Header(s, 'iso-8859-1', 998)
        return str(h)
506 507 508 509 510 511

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

# <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 已提交
512

J
James Troup 已提交
513
def fix_maintainer (maintainer):
514 515 516 517 518 519 520 521 522 523 524
    """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:
525
        return ('', '', '', '')
526

527
    if maintainer.find("<") == -1:
528 529
        email = maintainer
        name = ""
530
    elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
531 532
        email = maintainer[1:-1]
        name = ""
533
    else:
534
        m = re_parse_maintainer.match(maintainer)
535 536
        if not m:
            raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
537 538
        name = m.group(1)
        email = m.group(2)
539 540

    # Get an RFC2047 compliant version of the name
541
    rfc2047_name = rfc2047_encode(name)
542 543

    # Force the name to be UTF-8
544
    name = force_to_utf8(name)
545 546

    if name.find(',') != -1 or name.find('.') != -1:
547 548
        rfc822_maint = "%s (%s)" % (email, name)
        rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
549
    else:
550 551
        rfc822_maint = "%s <%s>" % (name, email)
        rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
552 553 554 555

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

556
    return (rfc822_maint, rfc2047_maint, name, email)
J
James Troup 已提交
557

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

# sendmail wrapper, takes _either_ a message string or a file as arguments
561
def send_mail (message, filename=""):
562 563 564 565 566 567 568 569 570 571
        # 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 已提交
572
        raise SendmailFailedError, output
573 574 575 576

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

578
################################################################################
J
James Troup 已提交
579 580

def poolify (source, component):
581
    if component:
582
        component += '/'
J
James Troup 已提交
583
    if source[:3] == "lib":
584
        return component + source[:4] + '/' + source + '/'
J
James Troup 已提交
585
    else:
586
        return component + source[:1] + '/' + source + '/'
J
James Troup 已提交
587

588
################################################################################
J
James Troup 已提交
589

590
def move (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
591
    if os.path.exists(dest) and os.path.isdir(dest):
592
        dest_dir = dest
J
James Troup 已提交
593
    else:
594
        dest_dir = os.path.dirname(dest)
J
James Troup 已提交
595
    if not os.path.exists(dest_dir):
596 597 598
        umask = os.umask(00000)
        os.makedirs(dest_dir, 02775)
        os.umask(umask)
599
    #print "Moving %s to %s..." % (src, dest)
J
James Troup 已提交
600
    if os.path.exists(dest) and os.path.isdir(dest):
601
        dest += '/' + os.path.basename(src)
602 603 604
    # Don't overwrite unless forced to
    if os.path.exists(dest):
        if not overwrite:
605
            fubar("Can't move %s to %s - file already exists." % (src, dest))
606 607
        else:
            if not os.access(dest, os.W_OK):
608 609 610 611
                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 已提交
612

613
def copy (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
614
    if os.path.exists(dest) and os.path.isdir(dest):
615
        dest_dir = dest
J
James Troup 已提交
616
    else:
617
        dest_dir = os.path.dirname(dest)
J
James Troup 已提交
618
    if not os.path.exists(dest_dir):
619 620 621
        umask = os.umask(00000)
        os.makedirs(dest_dir, 02775)
        os.umask(umask)
622
    #print "Copying %s to %s..." % (src, dest)
J
James Troup 已提交
623
    if os.path.exists(dest) and os.path.isdir(dest):
624
        dest += '/' + os.path.basename(src)
625 626 627
    # Don't overwrite unless forced to
    if os.path.exists(dest):
        if not overwrite:
J
Joerg Jaspert 已提交
628
            raise FileExistsError
629 630
        else:
            if not os.access(dest, os.W_OK):
J
Joerg Jaspert 已提交
631
                raise CantOverwriteError
632 633
    shutil.copy2(src, dest)
    os.chmod(dest, perms)
J
James Troup 已提交
634

635
################################################################################
J
James Troup 已提交
636 637

def where_am_i ():
638 639
    res = socket.gethostbyaddr(socket.gethostname())
    database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
640
    if database_hostname:
641
        return database_hostname
J
James Troup 已提交
642
    else:
643
        return res[0]
J
James Troup 已提交
644 645

def which_conf_file ():
646
    res = socket.gethostbyaddr(socket.gethostname())
647
    if Cnf.get("Config::" + res[0] + "::DakConfig"):
648
        return Cnf["Config::" + res[0] + "::DakConfig"]
J
James Troup 已提交
649
    else:
650
        return default_config
651 652

def which_apt_conf_file ():
653
    res = socket.gethostbyaddr(socket.gethostname())
654
    if Cnf.get("Config::" + res[0] + "::AptConfig"):
655
        return Cnf["Config::" + res[0] + "::AptConfig"]
656
    else:
657
        return default_apt_config
658

T
Thomas Viehmann 已提交
659 660 661 662 663 664 665 666
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

667
################################################################################
J
James Troup 已提交
668

669 670 671 672
# Escape characters which have meaning to SQL's regex comparison operator ('~')
# (woefully incomplete)

def regex_safe (s):
673 674
    s = s.replace('+', '\\\\+')
    s = s.replace('.', '\\\\.')
675 676
    return s

677
################################################################################
678

J
James Troup 已提交
679
# Perform a substition of template
680
def TemplateSubst(map, filename):
681 682
    file = open_file(filename)
    template = file.read()
683
    for x in map.keys():
684 685 686
        template = template.replace(x,map[x])
    file.close()
    return template
J
James Troup 已提交
687

688
################################################################################
J
James Troup 已提交
689 690

def fubar(msg, exit_code=1):
691 692
    sys.stderr.write("E: %s\n" % (msg))
    sys.exit(exit_code)
J
James Troup 已提交
693 694

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

697
################################################################################
J
James Troup 已提交
698

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

704
################################################################################
J
James Troup 已提交
705

J
James Troup 已提交
706
def size_type (c):
707
    t  = " B"
708
    if c > 10240:
709 710
        c = c / 1024
        t = " KB"
711
    if c > 10240:
712 713
        c = c / 1024
        t = " MB"
J
James Troup 已提交
714
    return ("%d%s" % (c, t))
715 716 717 718

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

def cc_fix_changes (changes):
719
    o = changes.get("architecture", "")
720
    if o:
721 722
        del changes["architecture"]
    changes["architecture"] = {}
723
    for j in o.split():
724
        changes["architecture"][j] = 1
725

726
# Sort by source name, source version, 'have source', and then by filename
727 728
def changes_compare (a, b):
    try:
729
        a_changes = parse_changes(a)
730
    except:
731
        return -1
732 733

    try:
734
        b_changes = parse_changes(b)
735
    except:
736
        return 1
J
James Troup 已提交
737

738 739
    cc_fix_changes (a_changes)
    cc_fix_changes (b_changes)
740 741

    # Sort by source name
742 743 744
    a_source = a_changes.get("source")
    b_source = b_changes.get("source")
    q = cmp (a_source, b_source)
745
    if q:
746
        return q
747 748

    # Sort by source version
749 750 751
    a_version = a_changes.get("version", "0")
    b_version = b_changes.get("version", "0")
    q = apt_pkg.VersionCompare(a_version, b_version)
752
    if q:
753
        return q
754

755
    # Sort by 'have source'
756 757
    a_has_source = a_changes["architecture"].get("source")
    b_has_source = b_changes["architecture"].get("source")
758
    if a_has_source and not b_has_source:
759
        return -1
760
    elif b_has_source and not a_has_source:
761
        return 1
762

763
    # Fall back to sort by filename
764
    return cmp(a, b)
765 766

################################################################################
767 768

def find_next_free (dest, too_many=100):
769 770
    extra = 0
    orig_dest = dest
771
    while os.path.exists(dest) and extra < too_many:
772 773
        dest = orig_dest + '.' + repr(extra)
        extra += 1
774
    if extra >= too_many:
J
Joerg Jaspert 已提交
775
        raise NoFreeFilenameError
776
    return dest
J
James Troup 已提交
777

778
################################################################################
779 780

def result_join (original, sep = '\t'):
781
    list = []
782 783
    for i in xrange(len(original)):
        if original[i] == None:
784
            list.append("")
785
        else:
786 787
            list.append(original[i])
    return sep.join(list)
788

789 790
################################################################################

791
def prefix_multi_line_string(str, prefix, include_blank_lines=0):
792
    out = ""
793
    for line in str.split('\n'):
794
        line = line.strip()
795
        if line or include_blank_lines:
796
            out += "%s%s\n" % (prefix, line)
797 798
    # Strip trailing new line
    if out:
799 800
        out = out[:-1]
    return out
801

802
################################################################################
803

804
def validate_changes_file_arg(filename, require_changes=1):
805 806
    """'filename' is either a .changes or .dak file.  If 'filename' is a
.dak file, it's changed to be the corresponding .changes file.  The
807 808 809 810 811 812 813 814 815 816
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.
"""
817
    error = None
818

819
    orig_filename = filename
820
    if filename.endswith(".dak"):
821
        filename = filename[:-4]+".changes"
822

823
    if not filename.endswith(".changes"):
824
        error = "invalid file type; not a changes file"
825
    else:
826 827
        if not os.access(filename,os.R_OK):
            if os.path.exists(filename):
828
                error = "permission denied"
829
            else:
830
                error = "file not found"
831 832

    if error:
833
        if require_changes == 1:
834
            fubar("%s: %s." % (orig_filename, error))
835
        elif require_changes == 0:
836 837
            warn("Skipping %s - %s" % (orig_filename, error))
            return None
838
        else: # We only care about the .dak file
839
            return filename
840
    else:
841
        return filename
842 843 844

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

845
def real_arch(arch):
846
    return (arch != "source" and arch != "all")
847 848 849

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

850
def join_with_commas_and(list):
851 852 853
    if len(list) == 0: return "nothing"
    if len(list) == 1: return list[0]
    return ", ".join(list[:-1]) + " and " + list[-1]
854 855 856

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

857
def pp_deps (deps):
858
    pp_deps = []
859
    for atom in deps:
860
        (pkg, version, constraint) = atom
861
        if constraint:
862
            pp_dep = "%s (%s %s)" % (pkg, constraint, version)
863
        else:
864 865 866
            pp_dep = pkg
        pp_deps.append(pp_dep)
    return " |".join(pp_deps)
867 868 869

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

870
def get_conf():
871
    return Cnf
872 873 874

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

875 876 877 878
# Handle -a, -c and -s arguments; returns them as SQL constraints
def parse_args(Options):
    # Process suite
    if Options["Suite"]:
879
        suite_ids_list = []
880
        for suite in split_args(Options["Suite"]):
J
James Troup 已提交
881
            suite_id = database.get_suite_id(suite)
882
            if suite_id == -1:
883
                warn("suite '%s' not recognised." % (suite))
884
            else:
885
                suite_ids_list.append(suite_id)
886
        if suite_ids_list:
887
            con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
888
        else:
889
            fubar("No valid suite given.")
890
    else:
891
        con_suites = ""
892 893 894

    # Process component
    if Options["Component"]:
895
        component_ids_list = []
896
        for component in split_args(Options["Component"]):
J
James Troup 已提交
897
            component_id = database.get_component_id(component)
898
            if component_id == -1:
899
                warn("component '%s' not recognised." % (component))
900
            else:
901
                component_ids_list.append(component_id)
902
        if component_ids_list:
903
            con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
904
        else:
905
            fubar("No valid component given.")
906
    else:
907
        con_components = ""
908 909

    # Process architecture
910
    con_architectures = ""
911
    if Options["Architecture"]:
912 913
        arch_ids_list = []
        check_source = 0
914
        for architecture in split_args(Options["Architecture"]):
915
            if architecture == "source":
916
                check_source = 1
917
            else:
J
James Troup 已提交
918
                architecture_id = database.get_architecture_id(architecture)
919
                if architecture_id == -1:
920
                    warn("architecture '%s' not recognised." % (architecture))
921
                else:
922
                    arch_ids_list.append(architecture_id)
923
        if arch_ids_list:
924
            con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
925 926
        else:
            if not check_source:
927
                fubar("No valid architecture given.")
928
    else:
929
        check_source = 1
930

931
    return (con_suites, con_architectures, con_components, check_source)
932 933 934

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

935 936 937 938
# Inspired(tm) by Bryn Keller's print_exc_plus (See
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)

def print_exc():
939
    tb = sys.exc_info()[2]
940
    while tb.tb_next:
941 942 943
        tb = tb.tb_next
    stack = []
    frame = tb.tb_frame
944
    while frame:
945 946 947 948
        stack.append(frame)
        frame = frame.f_back
    stack.reverse()
    traceback.print_exc()
949 950 951
    for frame in stack:
        print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
                                             frame.f_code.co_filename,
952
                                             frame.f_lineno)
953
        for key, value in frame.f_locals.items():
954
            print "\t%20s = " % key,
955
            try:
956
                print value
957
            except:
958
                print "<unable to print>"
959 960 961

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

J
James Troup 已提交
962 963
def try_with_debug(function):
    try:
964
        function()
J
James Troup 已提交
965
    except SystemExit:
966
        raise
J
James Troup 已提交
967
    except:
968
        print_exc()
J
James Troup 已提交
969 970 971

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

J
James Troup 已提交
972 973 974 975 976
# 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":
977
        return 0
J
James Troup 已提交
978
    elif a == "source":
979
        return -1
J
James Troup 已提交
980
    elif b == "source":
981
        return 1
J
James Troup 已提交
982

983
    return cmp (a, b)
J
James Troup 已提交
984 985 986

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

987 988
# Split command line arguments which can be separated by either commas
# or whitespace.  If dwim is set, it will complain about string ending
989
# in comma since this usually means someone did 'dak ls -a i386, m68k
990 991 992 993 994
# 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:
995
        return s.split()
996 997
    else:
        if s[-1:] == "," and dwim:
998 999
            fubar("split_args: found trailing comma, spurious space maybe?")
        return s.split(",")
1000 1001 1002

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

1003 1004 1005 1006 1007 1008 1009
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):
1010 1011 1012 1013 1014
    cmd = ['/bin/sh', '-c', cmd]
    p2cread, p2cwrite = os.pipe()
    c2pread, c2pwrite = os.pipe()
    errout, errin = os.pipe()
    pid = os.fork()
1015 1016
    if pid == 0:
        # Child
1017 1018 1019 1020 1021 1022
        os.close(0)
        os.close(1)
        os.dup(p2cread)
        os.dup(c2pwrite)
        os.close(2)
        os.dup(errin)
1023 1024 1025
        for i in range(3, 256):
            if i != status_write:
                try:
1026
                    os.close(i)
1027
                except:
1028
                    pass
1029
        try:
1030
            os.execvp(cmd[0], cmd)
1031
        finally:
1032
            os._exit(1)
1033

1034
    # Parent
1035
    os.close(p2cread)
1036 1037
    os.dup2(c2pread, c2pwrite)
    os.dup2(errout, errin)
1038

1039
    output = status = ""
1040
    while 1:
1041 1042
        i, o, e = select.select([c2pwrite, errin, status_read], [], [])
        more_data = []
1043
        for fd in i:
1044
            r = os.read(fd, 8196)
1045
            if len(r) > 0:
1046
                more_data.append(fd)
1047
                if fd == c2pwrite or fd == errin:
1048
                    output += r
1049
                elif fd == status_read:
1050
                    status += r
1051
                else:
1052
                    fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
1053 1054 1055
        if not more_data:
            pid, exit_status = os.waitpid(pid, 0)
            try:
1056 1057 1058 1059 1060 1061 1062
                os.close(status_write)
                os.close(status_read)
                os.close(c2pread)
                os.close(c2pwrite)
                os.close(p2cwrite)
                os.close(errin)
                os.close(errout)
1063
            except:
1064 1065
                pass
            break
1066

1067
    return output, status, exit_status
1068

1069
################################################################################
1070

1071 1072 1073 1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087
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:]
1088
        if keywords.has_key(keyword) and keyword not in [ "NODATA", "SIGEXPIRED", "KEYEXPIRED" ]:
1089 1090 1091 1092 1093 1094 1095 1096 1097 1098 1099 1100 1101 1102 1103 1104 1105 1106
            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:
1107
        keyring = Cnf.ValueList("Dinstall::GPGKeyring")[0]
1108 1109 1110 1111 1112 1113

    # 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
1114
    status_read, status_write = os.pipe();
1115 1116
    cmd = "gpgv --status-fd %s --keyring /dev/null %s" % (status_write, filename)
    (_, status, _) = gpgv_get_status_output(cmd, status_read, status_write)
1117

1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133 1134 1135 1136 1137 1138 1139 1140 1141
    # 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 ""

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

1142 1143 1144 1145 1146 1147 1148 1149
def gpg_keyring_args(keyrings=None):
    if not keyrings:
        keyrings = Cnf.ValueList("Dinstall::GPGKeyring")

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

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

1150
def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
1151 1152 1153 1154 1155
    """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,
1156
the second is an optional prefix string.  It's possible for reject()
1157 1158 1159
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
1160 1161 1162
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."""
1163 1164

    # Ensure the filename contains no shell meta-characters or other badness
1165
    if not re_taint_free.match(sig_filename):
1166 1167
        reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
        return None
1168 1169

    if data_filename and not re_taint_free.match(data_filename):
1170 1171
        reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
        return None
1172 1173

    if not keyrings:
1174
        keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
1175

1176 1177 1178 1179 1180 1181 1182 1183 1184
    # 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

1185
    # Build the command line
1186
    status_read, status_write = os.pipe();
1187 1188 1189
    cmd = "gpgv --status-fd %s %s %s %s" % (
        status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)

1190
    # Invoke gpgv on the file
1191
    (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
1192 1193

    # Process the status-fd output
1194
    (keywords, internal_error) = process_gpgv_output(status)
1195 1196 1197

    # If we failed to parse the status-fd output, let's just whine and bail now
    if internal_error:
1198 1199 1200 1201
        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
1202

1203
    bad = ""
1204 1205
    # Now check for obviously bad things in the processed output
    if keywords.has_key("KEYREVOKED"):
1206 1207
        reject("The key used to sign %s has been revoked." % (sig_filename))
        bad = 1
1208
    if keywords.has_key("BADSIG"):
1209 1210
        reject("bad signature on %s." % (sig_filename))
        bad = 1
1211
    if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1212 1213
        reject("failed to check signature on %s." % (sig_filename))
        bad = 1
1214
    if keywords.has_key("NO_PUBKEY"):
1215
        args = keywords["NO_PUBKEY"]
1216
        if len(args) >= 1:
1217 1218 1219
            key = args[0]
        reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
        bad = 1
1220
    if keywords.has_key("BADARMOR"):
1221 1222
        reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
        bad = 1
1223
    if keywords.has_key("NODATA"):
1224 1225
        reject("no signature found in %s." % (sig_filename))
        bad = 1
1226
    if keywords.has_key("KEYEXPIRED") and not keywords.has_key("GOODSIG"):
1227 1228 1229
        args = keywords["KEYEXPIRED"]
        if len(args) >= 1:
            key = args[0]
1230 1231
        reject("The key (0x%s) used to sign %s has expired." % (key, sig_filename))
        bad = 1
1232 1233

    if bad:
1234
        return None
1235 1236 1237

    # Next check gpgv exited with a zero return code
    if exit_status:
1238
        reject("gpgv failed while checking %s." % (sig_filename))
1239
        if status.strip():
1240
            reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1241
        else:
1242 1243
            reject(prefix_multi_line_string(output, " [GPG output:] "), "")
        return None
1244 1245 1246

    # Sanity check the good stuff we expect
    if not keywords.has_key("VALIDSIG"):
1247 1248
        reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
        bad = 1
1249
    else:
1250
        args = keywords["VALIDSIG"]
1251
        if len(args) < 1:
1252 1253
            reject("internal error while checking signature on %s." % (sig_filename))
            bad = 1
1254
        else:
1255
            fingerprint = args[0]
1256
    if not keywords.has_key("GOODSIG"):
1257 1258
        reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
        bad = 1
1259
    if not keywords.has_key("SIG_ID"):
1260 1261
        reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
        bad = 1
1262 1263 1264 1265

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

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

    if bad:
1274
        return None
1275
    else:
1276
        return fingerprint
1277 1278 1279

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

1280
def gpg_get_key_addresses(fingerprint):
1281 1282 1283 1284 1285 1286 1287 1288 1289 1290 1291 1292 1293 1294 1295
    """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
1296 1297 1298

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

1299 1300 1301
# Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603

def wrap(paragraph, max_length, prefix=""):
1302 1303 1304 1305
    line = ""
    s = ""
    have_started = 0
    words = paragraph.split()
1306 1307

    for word in words:
1308
        word_size = len(word)
1309 1310
        if word_size > max_length:
            if have_started:
1311 1312
                s += line + '\n' + prefix
            s += word + '\n' + prefix
1313 1314
        else:
            if have_started:
1315
                new_length = len(line) + word_size + 1
1316
                if new_length > max_length:
1317 1318
                    s += line + '\n' + prefix
                    line = word
1319
                else:
1320
                    line += ' ' + word
1321
            else:
1322 1323
                line = word
        have_started = 1
1324 1325

    if have_started:
1326
        s += line
1327

1328
    return s
1329 1330 1331 1332 1333 1334

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

# Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
# Returns fixed 'src'
def clean_symlink (src, dest, root):
1335 1336 1337 1338 1339
    src = src.replace(root, '', 1)
    dest = dest.replace(root, '', 1)
    dest = os.path.dirname(dest)
    new_src = '../' * len(dest.split('/'))
    return new_src + src
1340 1341 1342

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

1343 1344 1345 1346 1347 1348
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:
1349 1350
        old_tempdir = tempfile.tempdir
        tempfile.tempdir = directory
1351

1352
    filename = tempfile.mktemp()
1353 1354

    if dotprefix:
1355 1356 1357
        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)
1358 1359

    if directory:
1360
        tempfile.tempdir = old_tempdir
1361

1362
    return filename
1363 1364 1365

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

T
Thomas Viehmann 已提交
1366 1367 1368
# checks if the user part of the email is listed in the alias file

def is_email_alias(email):
T
Thomas Viehmann 已提交
1369 1370 1371 1372 1373 1374 1375 1376 1377
    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 已提交
1378 1379 1380

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

1381
apt_pkg.init()
1382

1383 1384
Cnf = apt_pkg.newConfiguration()
apt_pkg.ReadConfigFileISC(Cnf,default_config)
1385 1386

if which_conf_file() != default_config:
1387
    apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1388 1389

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