utils.py 45.7 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 255
        try:
            file_handle = open_file(f)
256 257 258 259 260 261 262 263 264 265 266

            # 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))
267
        except CantOpenError:
268 269
            # TODO: This happens when the file is in the pool.
            warn("Cannot open file %s" % f)
270
            continue
271
        finally:
272 273
            if file_handle:
                file_handle.close()
274
    return rejmsg
275

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

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():
284 285 286 287 288 289 290 291 292
        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]
293
        size = int(files[f]["size"])
294 295 296
        if size != actual_size:
            rejmsg.append("%s: actual file size (%s) does not match size (%s) in %s"
                   % (f, actual_size, size, where))
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 328 329 330 331 332 333 334 335 336 337 338
    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."""
339

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

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

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

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

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

384
    return rejmsg
385

386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406
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))
407 408 409 410
    return rejmsg

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

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

413
def build_file_list(changes, is_a_dsc=0, field="files", hashname="md5sum"):
414
    files = {}
415 416

    # Make sure we have a Files: field to parse...
417
    if not changes.has_key(field):
J
Joerg Jaspert 已提交
418
        raise NoFilesFieldError
419 420

    # Make sure we recognise the format of the Files: field
421 422
    format = re_verwithext.search(changes.get("format", "0.0"))
    if not format:
J
Joerg Jaspert 已提交
423
        raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
424 425 426 427

    format = format.groups()
    if format[1] == None:
        format = int(float(format[0])), 0, format[2]
428
    else:
429 430 431
        format = int(format[0]), int(format[1]), format[2]
    if format[2] == None:
        format = format[:2]
432 433 434

    if is_a_dsc:
        if format != (1,0):
J
Joerg Jaspert 已提交
435
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
436 437
    else:
        if (format < (1,5) or format > (1,8)):
J
Joerg Jaspert 已提交
438
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
439
        if field != "files" and format < (1,8):
J
Joerg Jaspert 已提交
440
            raise UnknownFormatError, "%s" % (changes.get("format","0.0"))
441 442

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

444
    # Parse each entry/line:
445
    for i in changes[field].split('\n'):
446
        if not i:
447 448 449
            break
        s = i.split()
        section = priority = ""
J
sync  
James Troup 已提交
450
        try:
451
            if includes_section:
452
                (md5, size, section, priority, name) = s
453 454
            else:
                (md5, size, name) = s
J
sync  
James Troup 已提交
455
        except ValueError:
J
Joerg Jaspert 已提交
456
            raise ParseChangesError, i
J
James Troup 已提交
457

458
        if section == "":
459
            section = "-"
460
        if priority == "":
461
            priority = "-"
J
James Troup 已提交
462

463
        (section, component) = extract_component_from_section(section)
J
James Troup 已提交
464

465
        files[name] = Dict(size=size, section=section,
466
                           priority=priority, component=component)
467
        files[name][hashname] = md5
J
James Troup 已提交
468 469 470

    return files

471
################################################################################
J
James Troup 已提交
472

473 474 475 476
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:
477 478
        unicode(s, 'utf-8')
        return s
479
    except UnicodeError:
480 481
        latin1_s = unicode(s,'iso8859-1')
        return latin1_s.encode('utf-8')
482 483 484 485 486

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:
487
        codecs.lookup('ascii')[1](s)
488
        return s
489
    except UnicodeError:
490
        pass
491
    try:
492
        codecs.lookup('utf-8')[1](s)
493 494
        h = email.Header.Header(s, 'utf-8', 998)
        return str(h)
495
    except UnicodeError:
496 497
        h = email.Header.Header(s, 'iso-8859-1', 998)
        return str(h)
498 499 500 501 502 503

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

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

J
James Troup 已提交
505
def fix_maintainer (maintainer):
506 507 508 509 510 511 512 513 514 515 516
    """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:
517
        return ('', '', '', '')
518

519
    if maintainer.find("<") == -1:
520 521
        email = maintainer
        name = ""
522
    elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
523 524
        email = maintainer[1:-1]
        name = ""
525
    else:
526
        m = re_parse_maintainer.match(maintainer)
527 528
        if not m:
            raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
529 530
        name = m.group(1)
        email = m.group(2)
531 532

    # Get an RFC2047 compliant version of the name
533
    rfc2047_name = rfc2047_encode(name)
534 535

    # Force the name to be UTF-8
536
    name = force_to_utf8(name)
537 538

    if name.find(',') != -1 or name.find('.') != -1:
539 540
        rfc822_maint = "%s (%s)" % (email, name)
        rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
541
    else:
542 543
        rfc822_maint = "%s <%s>" % (name, email)
        rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
544 545 546 547

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

548
    return (rfc822_maint, rfc2047_maint, name, email)
J
James Troup 已提交
549

550
################################################################################
J
James Troup 已提交
551 552

# sendmail wrapper, takes _either_ a message string or a file as arguments
553
def send_mail (message, filename=""):
554 555 556 557 558 559 560 561 562 563
        # 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 已提交
564
        raise SendmailFailedError, output
565 566 567 568

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

570
################################################################################
J
James Troup 已提交
571 572

def poolify (source, component):
573
    if component:
574
        component += '/'
J
James Troup 已提交
575
    if source[:3] == "lib":
576
        return component + source[:4] + '/' + source + '/'
J
James Troup 已提交
577
    else:
578
        return component + source[:1] + '/' + source + '/'
J
James Troup 已提交
579

580
################################################################################
J
James Troup 已提交
581

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

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

627
################################################################################
J
James Troup 已提交
628 629

def where_am_i ():
630 631
    res = socket.gethostbyaddr(socket.gethostname())
    database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
632
    if database_hostname:
633
        return database_hostname
J
James Troup 已提交
634
    else:
635
        return res[0]
J
James Troup 已提交
636 637

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

def which_apt_conf_file ():
645
    res = socket.gethostbyaddr(socket.gethostname())
646
    if Cnf.get("Config::" + res[0] + "::AptConfig"):
647
        return Cnf["Config::" + res[0] + "::AptConfig"]
648
    else:
649
        return default_apt_config
650

T
Thomas Viehmann 已提交
651 652 653 654 655 656 657 658
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

659
################################################################################
J
James Troup 已提交
660

661 662 663 664
# Escape characters which have meaning to SQL's regex comparison operator ('~')
# (woefully incomplete)

def regex_safe (s):
665 666
    s = s.replace('+', '\\\\+')
    s = s.replace('.', '\\\\.')
667 668
    return s

669
################################################################################
670

J
James Troup 已提交
671
# Perform a substition of template
672
def TemplateSubst(map, filename):
673 674
    file = open_file(filename)
    template = file.read()
675
    for x in map.keys():
676 677 678
        template = template.replace(x,map[x])
    file.close()
    return template
J
James Troup 已提交
679

680
################################################################################
J
James Troup 已提交
681 682

def fubar(msg, exit_code=1):
683 684
    sys.stderr.write("E: %s\n" % (msg))
    sys.exit(exit_code)
J
James Troup 已提交
685 686

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

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

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

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

J
James Troup 已提交
698
def size_type (c):
699
    t  = " B"
700
    if c > 10240:
701 702
        c = c / 1024
        t = " KB"
703
    if c > 10240:
704 705
        c = c / 1024
        t = " MB"
J
James Troup 已提交
706
    return ("%d%s" % (c, t))
707 708 709 710

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

def cc_fix_changes (changes):
711
    o = changes.get("architecture", "")
712
    if o:
713 714
        del changes["architecture"]
    changes["architecture"] = {}
715
    for j in o.split():
716
        changes["architecture"][j] = 1
717

718
# Sort by source name, source version, 'have source', and then by filename
719 720
def changes_compare (a, b):
    try:
721
        a_changes = parse_changes(a)
722
    except:
723
        return -1
724 725

    try:
726
        b_changes = parse_changes(b)
727
    except:
728
        return 1
J
James Troup 已提交
729

730 731
    cc_fix_changes (a_changes)
    cc_fix_changes (b_changes)
732 733

    # Sort by source name
734 735 736
    a_source = a_changes.get("source")
    b_source = b_changes.get("source")
    q = cmp (a_source, b_source)
737
    if q:
738
        return q
739 740

    # Sort by source version
741 742 743
    a_version = a_changes.get("version", "0")
    b_version = b_changes.get("version", "0")
    q = apt_pkg.VersionCompare(a_version, b_version)
744
    if q:
745
        return q
746

747
    # Sort by 'have source'
748 749
    a_has_source = a_changes["architecture"].get("source")
    b_has_source = b_changes["architecture"].get("source")
750
    if a_has_source and not b_has_source:
751
        return -1
752
    elif b_has_source and not a_has_source:
753
        return 1
754

755
    # Fall back to sort by filename
756
    return cmp(a, b)
757 758

################################################################################
759 760

def find_next_free (dest, too_many=100):
761 762
    extra = 0
    orig_dest = dest
763
    while os.path.exists(dest) and extra < too_many:
764 765
        dest = orig_dest + '.' + repr(extra)
        extra += 1
766
    if extra >= too_many:
J
Joerg Jaspert 已提交
767
        raise NoFreeFilenameError
768
    return dest
J
James Troup 已提交
769

770
################################################################################
771 772

def result_join (original, sep = '\t'):
773
    list = []
774 775
    for i in xrange(len(original)):
        if original[i] == None:
776
            list.append("")
777
        else:
778 779
            list.append(original[i])
    return sep.join(list)
780

781 782
################################################################################

783
def prefix_multi_line_string(str, prefix, include_blank_lines=0):
784
    out = ""
785
    for line in str.split('\n'):
786
        line = line.strip()
787
        if line or include_blank_lines:
788
            out += "%s%s\n" % (prefix, line)
789 790
    # Strip trailing new line
    if out:
791 792
        out = out[:-1]
    return out
793

794
################################################################################
795

796
def validate_changes_file_arg(filename, require_changes=1):
797 798
    """'filename' is either a .changes or .dak file.  If 'filename' is a
.dak file, it's changed to be the corresponding .changes file.  The
799 800 801 802 803 804 805 806 807 808
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.
"""
809
    error = None
810

811
    orig_filename = filename
812
    if filename.endswith(".dak"):
813
        filename = filename[:-4]+".changes"
814

815
    if not filename.endswith(".changes"):
816
        error = "invalid file type; not a changes file"
817
    else:
818 819
        if not os.access(filename,os.R_OK):
            if os.path.exists(filename):
820
                error = "permission denied"
821
            else:
822
                error = "file not found"
823 824

    if error:
825
        if require_changes == 1:
826
            fubar("%s: %s." % (orig_filename, error))
827
        elif require_changes == 0:
828 829
            warn("Skipping %s - %s" % (orig_filename, error))
            return None
830
        else: # We only care about the .dak file
831
            return filename
832
    else:
833
        return filename
834 835 836

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

837
def real_arch(arch):
838
    return (arch != "source" and arch != "all")
839 840 841

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

842
def join_with_commas_and(list):
843 844 845
    if len(list) == 0: return "nothing"
    if len(list) == 1: return list[0]
    return ", ".join(list[:-1]) + " and " + list[-1]
846 847 848

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

849
def pp_deps (deps):
850
    pp_deps = []
851
    for atom in deps:
852
        (pkg, version, constraint) = atom
853
        if constraint:
854
            pp_dep = "%s (%s %s)" % (pkg, constraint, version)
855
        else:
856 857 858
            pp_dep = pkg
        pp_deps.append(pp_dep)
    return " |".join(pp_deps)
859 860 861

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

862
def get_conf():
863
    return Cnf
864 865 866

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

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

    # Process component
    if Options["Component"]:
887
        component_ids_list = []
888
        for component in split_args(Options["Component"]):
J
James Troup 已提交
889
            component_id = database.get_component_id(component)
890
            if component_id == -1:
891
                warn("component '%s' not recognised." % (component))
892
            else:
893
                component_ids_list.append(component_id)
894
        if component_ids_list:
895
            con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
896
        else:
897
            fubar("No valid component given.")
898
    else:
899
        con_components = ""
900 901

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

923
    return (con_suites, con_architectures, con_components, check_source)
924 925 926

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

927 928 929 930
# Inspired(tm) by Bryn Keller's print_exc_plus (See
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)

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

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

J
James Troup 已提交
954 955
def try_with_debug(function):
    try:
956
        function()
J
James Troup 已提交
957
    except SystemExit:
958
        raise
J
James Troup 已提交
959
    except:
960
        print_exc()
J
James Troup 已提交
961 962 963

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

J
James Troup 已提交
964 965 966 967 968
# 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":
969
        return 0
J
James Troup 已提交
970
    elif a == "source":
971
        return -1
J
James Troup 已提交
972
    elif b == "source":
973
        return 1
J
James Troup 已提交
974

975
    return cmp (a, b)
J
James Troup 已提交
976 977 978

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

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

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

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

1026
    # Parent
1027
    os.close(p2cread)
1028 1029
    os.dup2(c2pread, c2pwrite)
    os.dup2(errout, errin)
1030

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

1059
    return output, status, exit_status
1060

1061
################################################################################
1062

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

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

1110 1111 1112 1113 1114 1115 1116 1117 1118 1119 1120 1121 1122 1123 1124 1125 1126 1127 1128 1129 1130 1131 1132 1133
    # 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 ""

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

1134 1135 1136 1137 1138 1139 1140 1141
def gpg_keyring_args(keyrings=None):
    if not keyrings:
        keyrings = Cnf.ValueList("Dinstall::GPGKeyring")

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

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

1142
def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
1143 1144 1145 1146 1147
    """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,
1148
the second is an optional prefix string.  It's possible for reject()
1149 1150 1151
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
1152 1153 1154
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."""
1155 1156

    # Ensure the filename contains no shell meta-characters or other badness
1157
    if not re_taint_free.match(sig_filename):
1158 1159
        reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
        return None
1160 1161

    if data_filename and not re_taint_free.match(data_filename):
1162 1163
        reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
        return None
1164 1165

    if not keyrings:
1166
        keyrings = Cnf.ValueList("Dinstall::GPGKeyring")
1167

1168 1169 1170 1171 1172 1173 1174 1175 1176
    # 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

1177
    # Build the command line
1178
    status_read, status_write = os.pipe();
1179 1180 1181
    cmd = "gpgv --status-fd %s %s %s %s" % (
        status_write, gpg_keyring_args(keyrings), sig_filename, data_filename)

1182
    # Invoke gpgv on the file
1183
    (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
1184 1185

    # Process the status-fd output
1186
    (keywords, internal_error) = process_gpgv_output(status)
1187 1188 1189

    # If we failed to parse the status-fd output, let's just whine and bail now
    if internal_error:
1190 1191 1192 1193
        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
1194

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

    if bad:
1226
        return None
1227 1228 1229

    # Next check gpgv exited with a zero return code
    if exit_status:
1230
        reject("gpgv failed while checking %s." % (sig_filename))
1231
        if status.strip():
1232
            reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1233
        else:
1234 1235
            reject(prefix_multi_line_string(output, " [GPG output:] "), "")
        return None
1236 1237 1238

    # Sanity check the good stuff we expect
    if not keywords.has_key("VALIDSIG"):
1239 1240
        reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
        bad = 1
1241
    else:
1242
        args = keywords["VALIDSIG"]
1243
        if len(args) < 1:
1244 1245
            reject("internal error while checking signature on %s." % (sig_filename))
            bad = 1
1246
        else:
1247
            fingerprint = args[0]
1248
    if not keywords.has_key("GOODSIG"):
1249 1250
        reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
        bad = 1
1251
    if not keywords.has_key("SIG_ID"):
1252 1253
        reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
        bad = 1
1254 1255 1256 1257

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

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

    if bad:
1266
        return None
1267
    else:
1268
        return fingerprint
1269 1270 1271

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

1272
def gpg_get_key_addresses(fingerprint):
1273 1274 1275 1276 1277 1278 1279 1280 1281 1282 1283 1284 1285 1286 1287
    """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
1288 1289 1290

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

1291 1292 1293
# Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603

def wrap(paragraph, max_length, prefix=""):
1294 1295 1296 1297
    line = ""
    s = ""
    have_started = 0
    words = paragraph.split()
1298 1299

    for word in words:
1300
        word_size = len(word)
1301 1302
        if word_size > max_length:
            if have_started:
1303 1304
                s += line + '\n' + prefix
            s += word + '\n' + prefix
1305 1306
        else:
            if have_started:
1307
                new_length = len(line) + word_size + 1
1308
                if new_length > max_length:
1309 1310
                    s += line + '\n' + prefix
                    line = word
1311
                else:
1312
                    line += ' ' + word
1313
            else:
1314 1315
                line = word
        have_started = 1
1316 1317

    if have_started:
1318
        s += line
1319

1320
    return s
1321 1322 1323 1324 1325 1326

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

# Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
# Returns fixed 'src'
def clean_symlink (src, dest, root):
1327 1328 1329 1330 1331
    src = src.replace(root, '', 1)
    dest = dest.replace(root, '', 1)
    dest = os.path.dirname(dest)
    new_src = '../' * len(dest.split('/'))
    return new_src + src
1332 1333 1334

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

1335 1336 1337 1338 1339 1340
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:
1341 1342
        old_tempdir = tempfile.tempdir
        tempfile.tempdir = directory
1343

1344
    filename = tempfile.mktemp()
1345 1346

    if dotprefix:
1347 1348 1349
        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)
1350 1351

    if directory:
1352
        tempfile.tempdir = old_tempdir
1353

1354
    return filename
1355 1356 1357

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

T
Thomas Viehmann 已提交
1358 1359 1360
# checks if the user part of the email is listed in the alias file

def is_email_alias(email):
T
Thomas Viehmann 已提交
1361 1362 1363 1364 1365 1366 1367 1368 1369
    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 已提交
1370 1371 1372

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

1373
apt_pkg.init()
1374

1375 1376
Cnf = apt_pkg.newConfiguration()
apt_pkg.ReadConfigFileISC(Cnf,default_config)
1377 1378

if which_conf_file() != default_config:
1379
    apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1380 1381

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