utils.py 36.9 KB
Newer Older
1 2
#!/usr/bin/env python

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

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

# 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

22 23
################################################################################

24
import codecs, commands, email.Header, os, pwd, re, select, socket, shutil, \
25 26
       string, sys, tempfile, traceback
import apt_pkg
J
James Troup 已提交
27
import database
28 29

################################################################################
J
James Troup 已提交
30 31

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

39 40 41
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]+$")
42

43
re_parse_maintainer = re.compile(r"^\s*(\S.*\S)\s*\<([^\>]+)\>")
J
James Troup 已提交
44

45 46 47 48 49 50
changes_parse_error_exc = "Can't parse line in .changes file"
invalid_dsc_format_exc = "Invalid .dsc file"
nk_format_exc = "Unknown Format: in .changes file"
no_files_exc = "No Files: field in .dsc or .changes file."
cant_open_exc = "Can't open file"
unknown_hostname_exc = "Unknown hostname"
J
James Troup 已提交
51
cant_overwrite_exc = "Permission denied; can't overwrite existent file."
52 53 54
file_exists_exc = "Destination file exists"
sendmail_failed_exc = "Sendmail invocation failed"
tried_too_hard_exc = "Tried too hard to find a free filename."
55

56 57
default_config = "/etc/dak/dak.conf"
default_apt_config = "/etc/dak/apt.conf"
J
James Troup 已提交
58

59
################################################################################
J
James Troup 已提交
60

61 62
class Error(Exception):
    """Base class for exceptions in this module."""
63
    pass
64 65 66 67 68 69 70 71 72

class ParseMaintError(Error):
    """Exception raised for errors in parsing a maintainer field.

    Attributes:
       message -- explanation of the error
    """

    def __init__(self, message):
73 74
        self.args = message,
        self.message = message
75 76 77

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

78
def open_file(filename, mode='r'):
J
James Troup 已提交
79
    try:
80
	f = open(filename, mode)
J
James Troup 已提交
81
    except IOError:
82
        raise cant_open_exc, filename
J
James Troup 已提交
83 84
    return f

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

87 88
def our_raw_input(prompt=""):
    if prompt:
89 90
        sys.stdout.write(prompt)
    sys.stdout.flush()
J
James Troup 已提交
91
    try:
92 93
        ret = raw_input()
        return ret
J
James Troup 已提交
94
    except EOFError:
95 96
        sys.stderr.write("\nUser interrupt (^D).\n")
        raise SystemExit
J
James Troup 已提交
97

98
################################################################################
J
James Troup 已提交
99

100 101 102
def str_isnum (s):
    for c in s:
        if c not in string.digits:
103 104
            return 0
    return 1
105

106
################################################################################
107

J
James Troup 已提交
108
def extract_component_from_section(section):
109
    component = ""
J
James Troup 已提交
110

111
    if section.find('/') != -1:
112
        component = section.split('/')[0]
113
    if component.lower() == "non-us" and section.find('/') != -1:
114
        s = component + '/' + section.split('/')[1]
115
        if Cnf.has_key("Component::%s" % s): # Avoid e.g. non-US/libs
116
            component = s
J
James Troup 已提交
117

118
    if section.lower() == "non-us":
119
        component = "non-US/main"
120 121

    # non-US prefix is case insensitive
122
    if component.lower()[:6] == "non-us":
123
        component = "non-US"+component[6:]
124 125

    # Expand default component
J
James Troup 已提交
126
    if component == "":
127
        if Cnf.has_key("Component::%s" % section):
128
            component = section
129
        else:
130
            component = "main"
131
    elif component == "non-US":
132
        component = "non-US/main"
J
James Troup 已提交
133

134
    return (section, component)
J
James Troup 已提交
135

136 137
################################################################################

138 139 140 141
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.
J
James Troup 已提交
142

143 144 145 146 147 148 149 150 151 152 153 154 155 156 157
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-----".
"""
J
James Troup 已提交
158

159 160
    error = ""
    changes = {}
161

162 163
    changes_in = open_file(filename)
    lines = changes_in.readlines()
J
James Troup 已提交
164

165
    if not lines:
166
	raise changes_parse_error_exc, "[Empty changes file]"
167

J
James Troup 已提交
168 169
    # Reindex by line number so we can easily verify the format of
    # .dsc files...
170 171
    index = 0
    indexed_lines = {}
J
James Troup 已提交
172
    for line in lines:
173 174
        index += 1
        indexed_lines[index] = line[:-1]
J
James Troup 已提交
175

176
    inside_signature = 0
J
James Troup 已提交
177

178 179 180
    num_of_lines = len(indexed_lines.keys())
    index = 0
    first = -1
181
    while index < num_of_lines:
182 183
        index += 1
        line = indexed_lines[index]
J
James Troup 已提交
184
        if line == "":
185
            if signing_rules == 1:
186
                index += 1
187
                if index > num_of_lines:
188 189
                    raise invalid_dsc_format_exc, index
                line = indexed_lines[index]
190
                if not line.startswith("-----BEGIN PGP SIGNATURE"):
191 192 193
                    raise invalid_dsc_format_exc, index
                inside_signature = 0
                break
194
            else:
195
                continue
196
        if line.startswith("-----BEGIN PGP SIGNATURE"):
197
            break
198
        if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
199
            inside_signature = 1
200
            if signing_rules == 1:
201
                while index < num_of_lines and line != "":
202 203 204
                    index += 1
                    line = indexed_lines[index]
            continue
205
        # If we're not inside the signed data, don't process anything
206
        if signing_rules >= 0 and not inside_signature:
207 208
            continue
        slf = re_single_line_field.match(line)
J
James Troup 已提交
209
        if slf:
210 211 212 213
            field = slf.groups()[0].lower()
            changes[field] = slf.groups()[1]
	    first = 1
            continue
J
James Troup 已提交
214
        if line == " .":
215 216 217
            changes[field] += '\n'
            continue
        mlf = re_multi_line_field.match(line)
J
James Troup 已提交
218
        if mlf:
219
            if first == -1:
220
                raise changes_parse_error_exc, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
221
            if first == 1 and changes[field] != "":
222 223 224 225 226
                changes[field] += '\n'
            first = 0
	    changes[field] += mlf.groups()[0] + '\n'
            continue
	error += line
J
James Troup 已提交
227

228
    if signing_rules == 1 and inside_signature:
229
        raise invalid_dsc_format_exc, index
J
James Troup 已提交
230

231 232
    changes_in.close()
    changes["filecontents"] = "".join(lines)
J
James Troup 已提交
233

234
    if error:
235
	raise changes_parse_error_exc, error
J
James Troup 已提交
236

237
    return changes
J
James Troup 已提交
238

239
################################################################################
J
James Troup 已提交
240 241 242

# Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl

243
def build_file_list(changes, is_a_dsc=0):
244
    files = {}
245 246 247

    # Make sure we have a Files: field to parse...
    if not changes.has_key("files"):
248
	raise no_files_exc
249 250

    # Make sure we recognise the format of the Files: field
251
    format = changes.get("format", "")
J
James Troup 已提交
252
    if format != "":
253
	format = float(format)
254
    if not is_a_dsc and (format < 1.5 or format > 2.0):
255
	raise nk_format_exc, format
J
James Troup 已提交
256

257 258 259
    # Parse each entry/line:
    for i in changes["files"].split('\n'):
        if not i:
260 261 262
            break
        s = i.split()
        section = priority = ""
J
sync  
James Troup 已提交
263
        try:
264
            if is_a_dsc:
265
                (md5, size, name) = s
J
sync  
James Troup 已提交
266
            else:
267
                (md5, size, section, priority, name) = s
J
sync  
James Troup 已提交
268
        except ValueError:
269
            raise changes_parse_error_exc, i
J
James Troup 已提交
270

271
        if section == "":
272
            section = "-"
273
        if priority == "":
274
            priority = "-"
J
James Troup 已提交
275

276
        (section, component) = extract_component_from_section(section)
J
James Troup 已提交
277

278
        files[name] = Dict(md5sum=md5, size=size, section=section,
279
                           priority=priority, component=component)
J
James Troup 已提交
280 281 282

    return files

283
################################################################################
J
James Troup 已提交
284

285 286 287 288
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:
289 290
        unicode(s, 'utf-8')
        return s
291
    except UnicodeError:
292 293
        latin1_s = unicode(s,'iso8859-1')
        return latin1_s.encode('utf-8')
294 295 296 297 298

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:
299
        codecs.lookup('ascii')[1](s)
300
        return s
301
    except UnicodeError:
302
        pass
303
    try:
304
        codecs.lookup('utf-8')[1](s)
305 306
        h = email.Header.Header(s, 'utf-8', 998)
        return str(h)
307
    except UnicodeError:
308 309
        h = email.Header.Header(s, 'iso-8859-1', 998)
        return str(h)
310 311 312 313 314 315

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

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

J
James Troup 已提交
317
def fix_maintainer (maintainer):
318 319 320 321 322 323 324 325 326 327 328
    """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:
329
        return ('', '', '', '')
330

331
    if maintainer.find("<") == -1:
332 333
        email = maintainer
        name = ""
334
    elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
335 336
        email = maintainer[1:-1]
        name = ""
337
    else:
338
        m = re_parse_maintainer.match(maintainer)
339 340
        if not m:
            raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
341 342
        name = m.group(1)
        email = m.group(2)
343 344

    # Get an RFC2047 compliant version of the name
345
    rfc2047_name = rfc2047_encode(name)
346 347

    # Force the name to be UTF-8
348
    name = force_to_utf8(name)
349 350

    if name.find(',') != -1 or name.find('.') != -1:
351 352
        rfc822_maint = "%s (%s)" % (email, name)
        rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
353
    else:
354 355
        rfc822_maint = "%s <%s>" % (name, email)
        rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
356 357 358 359

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

360
    return (rfc822_maint, rfc2047_maint, name, email)
J
James Troup 已提交
361

362
################################################################################
J
James Troup 已提交
363 364

# sendmail wrapper, takes _either_ a message string or a file as arguments
365
def send_mail (message, filename=""):
J
James Troup 已提交
366
	# If we've been passed a string dump it into a temporary file
367
	if message:
368 369 370 371
            filename = tempfile.mktemp()
            fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
            os.write (fd, message)
            os.close (fd)
J
James Troup 已提交
372

J
James Troup 已提交
373
	# Invoke sendmail
374
	(result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
J
James Troup 已提交
375
	if (result != 0):
376
            raise sendmail_failed_exc, output
J
James Troup 已提交
377

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

382
################################################################################
J
James Troup 已提交
383 384

def poolify (source, component):
385
    if component:
386
	component += '/'
J
James Troup 已提交
387
    # FIXME: this is nasty
388
    component = component.lower().replace("non-us/", "non-US/")
J
James Troup 已提交
389 390 391 392 393
    if source[:3] == "lib":
	return component + source[:4] + '/' + source + '/'
    else:
	return component + source[:1] + '/' + source + '/'

394
################################################################################
J
James Troup 已提交
395

396
def move (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
397
    if os.path.exists(dest) and os.path.isdir(dest):
398
	dest_dir = dest
J
James Troup 已提交
399
    else:
400
	dest_dir = os.path.dirname(dest)
J
James Troup 已提交
401
    if not os.path.exists(dest_dir):
402 403 404 405
	umask = os.umask(00000)
	os.makedirs(dest_dir, 02775)
	os.umask(umask)
    #print "Moving %s to %s..." % (src, dest)
J
James Troup 已提交
406
    if os.path.exists(dest) and os.path.isdir(dest):
407
	dest += '/' + os.path.basename(src)
408 409 410
    # Don't overwrite unless forced to
    if os.path.exists(dest):
        if not overwrite:
411
            fubar("Can't move %s to %s - file already exists." % (src, dest))
412 413
        else:
            if not os.access(dest, os.W_OK):
414 415 416 417
                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 已提交
418

419
def copy (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
420
    if os.path.exists(dest) and os.path.isdir(dest):
421
	dest_dir = dest
J
James Troup 已提交
422
    else:
423
	dest_dir = os.path.dirname(dest)
J
James Troup 已提交
424
    if not os.path.exists(dest_dir):
425 426 427 428
	umask = os.umask(00000)
	os.makedirs(dest_dir, 02775)
	os.umask(umask)
    #print "Copying %s to %s..." % (src, dest)
J
James Troup 已提交
429
    if os.path.exists(dest) and os.path.isdir(dest):
430
	dest += '/' + os.path.basename(src)
431 432 433 434 435 436 437
    # Don't overwrite unless forced to
    if os.path.exists(dest):
        if not overwrite:
            raise file_exists_exc
        else:
            if not os.access(dest, os.W_OK):
                raise cant_overwrite_exc
438 439
    shutil.copy2(src, dest)
    os.chmod(dest, perms)
J
James Troup 已提交
440

441
################################################################################
J
James Troup 已提交
442 443

def where_am_i ():
444 445
    res = socket.gethostbyaddr(socket.gethostname())
    database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
446
    if database_hostname:
447
	return database_hostname
J
James Troup 已提交
448
    else:
449
        return res[0]
J
James Troup 已提交
450 451

def which_conf_file ():
452
    res = socket.gethostbyaddr(socket.gethostname())
453 454
    if Cnf.get("Config::" + res[0] + "::DakConfig"):
	return Cnf["Config::" + res[0] + "::DakConfig"]
J
James Troup 已提交
455
    else:
456
	return default_config
457 458

def which_apt_conf_file ():
459
    res = socket.gethostbyaddr(socket.gethostname())
460 461
    if Cnf.get("Config::" + res[0] + "::AptConfig"):
	return Cnf["Config::" + res[0] + "::AptConfig"]
462
    else:
463
	return default_apt_config
464

465
################################################################################
J
James Troup 已提交
466

467 468 469 470
# Escape characters which have meaning to SQL's regex comparison operator ('~')
# (woefully incomplete)

def regex_safe (s):
471 472
    s = s.replace('+', '\\\\+')
    s = s.replace('.', '\\\\.')
473 474
    return s

475
################################################################################
476

J
James Troup 已提交
477
# Perform a substition of template
478
def TemplateSubst(map, filename):
479 480
    file = open_file(filename)
    template = file.read()
481
    for x in map.keys():
482 483 484
        template = template.replace(x,map[x])
    file.close()
    return template
J
James Troup 已提交
485

486
################################################################################
J
James Troup 已提交
487 488

def fubar(msg, exit_code=1):
489 490
    sys.stderr.write("E: %s\n" % (msg))
    sys.exit(exit_code)
J
James Troup 已提交
491 492

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

495
################################################################################
J
James Troup 已提交
496

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

502
################################################################################
J
James Troup 已提交
503

J
James Troup 已提交
504
def size_type (c):
505
    t  = " B"
506
    if c > 10240:
507 508
        c = c / 1024
        t = " KB"
509
    if c > 10240:
510 511
        c = c / 1024
        t = " MB"
J
James Troup 已提交
512
    return ("%d%s" % (c, t))
513 514 515 516

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

def cc_fix_changes (changes):
517
    o = changes.get("architecture", "")
518
    if o:
519 520
        del changes["architecture"]
    changes["architecture"] = {}
521
    for j in o.split():
522
        changes["architecture"][j] = 1
523

524
# Sort by source name, source version, 'have source', and then by filename
525 526
def changes_compare (a, b):
    try:
527
        a_changes = parse_changes(a)
528
    except:
529
        return -1
530 531

    try:
532
        b_changes = parse_changes(b)
533
    except:
534
        return 1
J
James Troup 已提交
535

536 537
    cc_fix_changes (a_changes)
    cc_fix_changes (b_changes)
538 539

    # Sort by source name
540 541 542
    a_source = a_changes.get("source")
    b_source = b_changes.get("source")
    q = cmp (a_source, b_source)
543
    if q:
544
        return q
545 546

    # Sort by source version
547 548 549
    a_version = a_changes.get("version", "0")
    b_version = b_changes.get("version", "0")
    q = apt_pkg.VersionCompare(a_version, b_version)
550
    if q:
551
        return q
552

553
    # Sort by 'have source'
554 555
    a_has_source = a_changes["architecture"].get("source")
    b_has_source = b_changes["architecture"].get("source")
556
    if a_has_source and not b_has_source:
557
        return -1
558
    elif b_has_source and not a_has_source:
559
        return 1
560

561
    # Fall back to sort by filename
562
    return cmp(a, b)
563 564

################################################################################
565 566

def find_next_free (dest, too_many=100):
567 568
    extra = 0
    orig_dest = dest
569
    while os.path.exists(dest) and extra < too_many:
570 571
        dest = orig_dest + '.' + repr(extra)
        extra += 1
572
    if extra >= too_many:
573 574
        raise tried_too_hard_exc
    return dest
J
James Troup 已提交
575

576
################################################################################
577 578

def result_join (original, sep = '\t'):
579
    list = []
580 581
    for i in xrange(len(original)):
        if original[i] == None:
582
            list.append("")
583
        else:
584 585
            list.append(original[i])
    return sep.join(list)
586

587 588
################################################################################

589
def prefix_multi_line_string(str, prefix, include_blank_lines=0):
590
    out = ""
591
    for line in str.split('\n'):
592
        line = line.strip()
593
        if line or include_blank_lines:
594
            out += "%s%s\n" % (prefix, line)
595 596
    # Strip trailing new line
    if out:
597 598
        out = out[:-1]
    return out
599

600
################################################################################
601

602
def validate_changes_file_arg(filename, require_changes=1):
603 604
    """'filename' is either a .changes or .dak file.  If 'filename' is a
.dak file, it's changed to be the corresponding .changes file.  The
605 606 607 608 609 610 611 612 613 614
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.
"""
615
    error = None
616

617
    orig_filename = filename
618
    if filename.endswith(".dak"):
619
        filename = filename[:-6]+".changes"
620

621
    if not filename.endswith(".changes"):
622
        error = "invalid file type; not a changes file"
623
    else:
624 625
        if not os.access(filename,os.R_OK):
            if os.path.exists(filename):
626
                error = "permission denied"
627
            else:
628
                error = "file not found"
629 630

    if error:
631
        if require_changes == 1:
632
            fubar("%s: %s." % (orig_filename, error))
633
        elif require_changes == 0:
634 635
            warn("Skipping %s - %s" % (orig_filename, error))
            return None
636
        else: # We only care about the .dak file
637
            return filename
638
    else:
639
        return filename
640 641 642

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

643
def real_arch(arch):
644
    return (arch != "source" and arch != "all")
645 646 647

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

648
def join_with_commas_and(list):
649 650 651
	if len(list) == 0: return "nothing"
	if len(list) == 1: return list[0]
	return ", ".join(list[:-1]) + " and " + list[-1]
652 653 654

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

655
def pp_deps (deps):
656
    pp_deps = []
657
    for atom in deps:
658
        (pkg, version, constraint) = atom
659
        if constraint:
660
            pp_dep = "%s (%s %s)" % (pkg, constraint, version)
661
        else:
662 663 664
            pp_dep = pkg
        pp_deps.append(pp_dep)
    return " |".join(pp_deps)
665 666 667

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

668
def get_conf():
669
	return Cnf
670 671 672

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

673 674 675 676
# Handle -a, -c and -s arguments; returns them as SQL constraints
def parse_args(Options):
    # Process suite
    if Options["Suite"]:
677
        suite_ids_list = []
678
        for suite in split_args(Options["Suite"]):
J
James Troup 已提交
679
            suite_id = database.get_suite_id(suite)
680
            if suite_id == -1:
681
                warn("suite '%s' not recognised." % (suite))
682
            else:
683
                suite_ids_list.append(suite_id)
684
        if suite_ids_list:
685
            con_suites = "AND su.id IN (%s)" % ", ".join([ str(i) for i in suite_ids_list ])
686
        else:
687
            fubar("No valid suite given.")
688
    else:
689
        con_suites = ""
690 691 692

    # Process component
    if Options["Component"]:
693
        component_ids_list = []
694
        for component in split_args(Options["Component"]):
J
James Troup 已提交
695
            component_id = database.get_component_id(component)
696
            if component_id == -1:
697
                warn("component '%s' not recognised." % (component))
698
            else:
699
                component_ids_list.append(component_id)
700
        if component_ids_list:
701
            con_components = "AND c.id IN (%s)" % ", ".join([ str(i) for i in component_ids_list ])
702
        else:
703
            fubar("No valid component given.")
704
    else:
705
        con_components = ""
706 707

    # Process architecture
708
    con_architectures = ""
709
    if Options["Architecture"]:
710 711
        arch_ids_list = []
        check_source = 0
712
        for architecture in split_args(Options["Architecture"]):
713
            if architecture == "source":
714
                check_source = 1
715
            else:
J
James Troup 已提交
716
                architecture_id = database.get_architecture_id(architecture)
717
                if architecture_id == -1:
718
                    warn("architecture '%s' not recognised." % (architecture))
719
                else:
720
                    arch_ids_list.append(architecture_id)
721
        if arch_ids_list:
722
            con_architectures = "AND a.id IN (%s)" % ", ".join([ str(i) for i in arch_ids_list ])
723 724
        else:
            if not check_source:
725
                fubar("No valid architecture given.")
726
    else:
727
        check_source = 1
728

729
    return (con_suites, con_architectures, con_components, check_source)
730 731 732

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

733 734 735 736
# Inspired(tm) by Bryn Keller's print_exc_plus (See
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)

def print_exc():
737
    tb = sys.exc_info()[2]
738
    while tb.tb_next:
739 740 741
        tb = tb.tb_next
    stack = []
    frame = tb.tb_frame
742
    while frame:
743 744 745 746
        stack.append(frame)
        frame = frame.f_back
    stack.reverse()
    traceback.print_exc()
747 748 749
    for frame in stack:
        print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
                                             frame.f_code.co_filename,
750
                                             frame.f_lineno)
751
        for key, value in frame.f_locals.items():
752
            print "\t%20s = " % key,
753
            try:
754
                print value
755
            except:
756
                print "<unable to print>"
757 758 759

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

J
James Troup 已提交
760 761
def try_with_debug(function):
    try:
762
        function()
J
James Troup 已提交
763
    except SystemExit:
764
        raise
J
James Troup 已提交
765
    except:
766
        print_exc()
J
James Troup 已提交
767 768 769

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

J
James Troup 已提交
770 771 772 773 774
# 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":
775
        return 0
J
James Troup 已提交
776
    elif a == "source":
777
        return -1
J
James Troup 已提交
778
    elif b == "source":
779
        return 1
J
James Troup 已提交
780

781
    return cmp (a, b)
J
James Troup 已提交
782 783 784

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

785 786
# Split command line arguments which can be separated by either commas
# or whitespace.  If dwim is set, it will complain about string ending
787
# in comma since this usually means someone did 'dak ls -a i386, m68k
788 789 790 791 792
# 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:
793
        return s.split()
794 795
    else:
        if s[-1:] == "," and dwim:
796 797
            fubar("split_args: found trailing comma, spurious space maybe?")
        return s.split(",")
798 799 800

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

801 802 803 804 805 806 807
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):
808 809 810 811 812
    cmd = ['/bin/sh', '-c', cmd]
    p2cread, p2cwrite = os.pipe()
    c2pread, c2pwrite = os.pipe()
    errout, errin = os.pipe()
    pid = os.fork()
813 814
    if pid == 0:
        # Child
815 816 817 818 819 820
        os.close(0)
        os.close(1)
        os.dup(p2cread)
        os.dup(c2pwrite)
        os.close(2)
        os.dup(errin)
821 822 823
        for i in range(3, 256):
            if i != status_write:
                try:
824
                    os.close(i)
825
                except:
826
                    pass
827
        try:
828
            os.execvp(cmd[0], cmd)
829
        finally:
830
            os._exit(1)
831

832
    # Parent
833
    os.close(p2cread)
834 835
    os.dup2(c2pread, c2pwrite)
    os.dup2(errout, errin)
836

837
    output = status = ""
838
    while 1:
839 840
        i, o, e = select.select([c2pwrite, errin, status_read], [], [])
        more_data = []
841
        for fd in i:
842
            r = os.read(fd, 8196)
843
            if len(r) > 0:
844
                more_data.append(fd)
845
                if fd == c2pwrite or fd == errin:
846
                    output += r
847
                elif fd == status_read:
848
                    status += r
849
                else:
850
                    fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
851 852 853
        if not more_data:
            pid, exit_status = os.waitpid(pid, 0)
            try:
854 855 856 857 858 859 860
                os.close(status_write)
                os.close(status_read)
                os.close(c2pread)
                os.close(c2pwrite)
                os.close(p2cwrite)
                os.close(errin)
                os.close(errout)
861
            except:
862 863
                pass
            break
864

865
    return output, status, exit_status
866

867
################################################################################
868

869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900 901 902 903 904 905 906 907 908 909 910 911 912 913 914
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:]
        if keywords.has_key(keyword) and (keyword != "NODATA" and keyword != "SIGEXPIRED"):
            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:
        keyring = Cnf["Dinstall::GPGKeyring"]

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

916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931 932 933 934 935 936 937 938 939 940
    # 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 ""

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

def check_signature (sig_filename, reject, data_filename="", keyrings=None, autofetch=None):
941 942 943 944 945
    """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,
946
the second is an optional prefix string.  It's possible for reject()
947 948 949
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
950 951 952
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."""
953 954

    # Ensure the filename contains no shell meta-characters or other badness
955
    if not re_taint_free.match(sig_filename):
956 957
        reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
        return None
958 959

    if data_filename and not re_taint_free.match(data_filename):
960 961
        reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
        return None
962 963 964

    if not keyrings:
        keyrings = (Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"])
965

966 967 968 969 970 971 972 973 974
    # 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

975 976
    # Build the command line
    status_read, status_write = os.pipe(); 
977
    cmd = "gpgv --status-fd %s" % (status_write)
978
    for keyring in keyrings:
979 980
        cmd += " --keyring %s" % (keyring)
    cmd += " %s %s" % (sig_filename, data_filename)
981
    # Invoke gpgv on the file
982
    (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
983 984

    # Process the status-fd output
985
    (keywords, internal_error) = process_gpgv_output(status)
986 987 988

    # If we failed to parse the status-fd output, let's just whine and bail now
    if internal_error:
989 990 991 992
        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
993

994
    bad = ""
995 996
    # Now check for obviously bad things in the processed output
    if keywords.has_key("SIGEXPIRED"):
997 998
        reject("The key used to sign %s has expired." % (sig_filename))
        bad = 1
999
    if keywords.has_key("KEYREVOKED"):
1000 1001
        reject("The key used to sign %s has been revoked." % (sig_filename))
        bad = 1
1002
    if keywords.has_key("BADSIG"):
1003 1004
        reject("bad signature on %s." % (sig_filename))
        bad = 1
1005
    if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
1006 1007
        reject("failed to check signature on %s." % (sig_filename))
        bad = 1
1008
    if keywords.has_key("NO_PUBKEY"):
1009
        args = keywords["NO_PUBKEY"]
1010
        if len(args) >= 1:
1011 1012 1013
            key = args[0]
        reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
        bad = 1
1014
    if keywords.has_key("BADARMOR"):
1015 1016
        reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
        bad = 1
1017
    if keywords.has_key("NODATA"):
1018 1019
        reject("no signature found in %s." % (sig_filename))
        bad = 1
1020 1021

    if bad:
1022
        return None
1023 1024 1025

    # Next check gpgv exited with a zero return code
    if exit_status:
1026
        reject("gpgv failed while checking %s." % (sig_filename))
1027
        if status.strip():
1028
            reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
1029
        else:
1030 1031
            reject(prefix_multi_line_string(output, " [GPG output:] "), "")
        return None
1032 1033 1034

    # Sanity check the good stuff we expect
    if not keywords.has_key("VALIDSIG"):
1035 1036
        reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
        bad = 1
1037
    else:
1038
        args = keywords["VALIDSIG"]
1039
        if len(args) < 1:
1040 1041
            reject("internal error while checking signature on %s." % (sig_filename))
            bad = 1
1042
        else:
1043
            fingerprint = args[0]
1044
    if not keywords.has_key("GOODSIG"):
1045 1046
        reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
        bad = 1
1047
    if not keywords.has_key("SIG_ID"):
1048 1049
        reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
        bad = 1
1050 1051 1052 1053

    # Finally ensure there's not something we don't recognise
    known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
                          SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
1054
                          NODATA="")
1055 1056 1057

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

    if bad:
1062
        return None
1063
    else:
1064
        return fingerprint
1065 1066 1067

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

1068 1069 1070
# Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603

def wrap(paragraph, max_length, prefix=""):
1071 1072 1073 1074
    line = ""
    s = ""
    have_started = 0
    words = paragraph.split()
1075 1076

    for word in words:
1077
        word_size = len(word)
1078 1079
        if word_size > max_length:
            if have_started:
1080 1081
                s += line + '\n' + prefix
            s += word + '\n' + prefix
1082 1083
        else:
            if have_started:
1084
                new_length = len(line) + word_size + 1
1085
                if new_length > max_length:
1086 1087
                    s += line + '\n' + prefix
                    line = word
1088
                else:
1089
                    line += ' ' + word
1090
            else:
1091 1092
                line = word
        have_started = 1
1093 1094

    if have_started:
1095
        s += line
1096

1097
    return s
1098 1099 1100 1101 1102 1103

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

# Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
# Returns fixed 'src'
def clean_symlink (src, dest, root):
1104 1105 1106 1107 1108
    src = src.replace(root, '', 1)
    dest = dest.replace(root, '', 1)
    dest = os.path.dirname(dest)
    new_src = '../' * len(dest.split('/'))
    return new_src + src
1109 1110 1111

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

1112 1113 1114 1115 1116 1117
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:
1118 1119
        old_tempdir = tempfile.tempdir
        tempfile.tempdir = directory
1120

1121
    filename = tempfile.mktemp()
1122 1123

    if dotprefix:
1124 1125 1126
        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)
1127 1128

    if directory:
1129
        tempfile.tempdir = old_tempdir
1130

1131
    return filename
1132 1133 1134

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

1135
apt_pkg.init()
1136

1137 1138
Cnf = apt_pkg.newConfiguration()
apt_pkg.ReadConfigFileISC(Cnf,default_config)
1139 1140

if which_conf_file() != default_config:
1141
	apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1142 1143

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