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

J
James Troup 已提交
3
# Utility functions
4
# Copyright (C) 2000, 2001, 2002, 2003, 2004  James Troup <james@nocrew.org>
J
James Troup 已提交
5
# $Id: utils.py,v 1.62 2004-01-21 03:48:58 troup Exp $
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 commands, os, pwd, re, select, socket, shutil, string, sys, tempfile, traceback;
26 27 28 29
import apt_pkg;
import db_access;

################################################################################
J
James Troup 已提交
30 31 32 33 34 35

re_comments = re.compile(r"\#.*")
re_no_epoch = re.compile(r"^\d*\:")
re_no_revision = re.compile(r"\-[^-]*$")
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

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

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

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

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

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

61
def open_file(filename, mode='r'):
J
James Troup 已提交
62 63 64
    try:
	f = open(filename, mode);
    except IOError:
65
        raise cant_open_exc, filename;
J
James Troup 已提交
66 67
    return f

68
################################################################################
J
James Troup 已提交
69

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

81
################################################################################
J
James Troup 已提交
82

83 84 85 86 87 88
def str_isnum (s):
    for c in s:
        if c not in string.digits:
            return 0;
    return 1;

89
################################################################################
90

J
James Troup 已提交
91 92
def extract_component_from_section(section):
    component = "";
J
James Troup 已提交
93

94 95 96 97
    if section.find('/') != -1:
        component = section.split('/')[0];
    if component.lower() == "non-us" and section.count('/') > 0:
        s = component + '/' + section.split('/')[1];
98
        if Cnf.has_key("Component::%s" % s): # Avoid e.g. non-US/libs
J
James Troup 已提交
99
            component = s;
J
James Troup 已提交
100

101
    if section.lower() == "non-us":
J
James Troup 已提交
102
        component = "non-US/main";
103 104

    # non-US prefix is case insensitive
105
    if component.lower()[:6] == "non-us":
106 107 108
        component = "non-US"+component[6:];

    # Expand default component
J
James Troup 已提交
109
    if component == "":
110
        if Cnf.has_key("Component::%s" % section):
111 112 113 114
            component = section;
        else:
            component = "main";
    elif component == "non-US":
J
James Troup 已提交
115 116 117 118
        component = "non-US/main";

    return (section, component);

119 120 121 122 123
################################################################################

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

125 126
# dsc_whitespace_rules is an optional boolean argument which defaults
# to off.  If true, it turns on strict format checking to avoid
J
James Troup 已提交
127 128 129 130 131
# allowing in source packages which are unextracable by the
# inappropriately fragile dpkg-source.
#
# The rules are:
#
132 133
#   o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
#     followed by any PGP header data and must end with a blank line.
J
James Troup 已提交
134
#
135 136
#   o The data section must end with a blank line and must be followed by
#     "-----BEGIN PGP SIGNATURE-----".
J
James Troup 已提交
137

138
def parse_changes(filename, dsc_whitespace_rules=0):
J
James Troup 已提交
139
    error = "";
J
James Troup 已提交
140
    changes = {};
141 142

    changes_in = open_file(filename);
J
James Troup 已提交
143
    lines = changes_in.readlines();
J
James Troup 已提交
144

145
    if not lines:
146 147
	raise changes_parse_error_exc, "[Empty changes file]";

J
James Troup 已提交
148 149 150 151
    # Reindex by line number so we can easily verify the format of
    # .dsc files...
    index = 0;
    indexed_lines = {};
J
James Troup 已提交
152
    for line in lines:
153
        index += 1;
J
James Troup 已提交
154 155 156 157
        indexed_lines[index] = line[:-1];

    inside_signature = 0;

158
    num_of_lines = len(indexed_lines.keys());
J
James Troup 已提交
159
    index = 0;
160
    first = -1;
161
    while index < num_of_lines:
162
        index += 1;
J
James Troup 已提交
163 164 165
        line = indexed_lines[index];
        if line == "":
            if dsc_whitespace_rules:
166
                index += 1;
167
                if index > num_of_lines:
J
James Troup 已提交
168 169
                    raise invalid_dsc_format_exc, index;
                line = indexed_lines[index];
170
                if not line.startswith("-----BEGIN PGP SIGNATURE"):
J
James Troup 已提交
171 172 173
                    raise invalid_dsc_format_exc, index;
                inside_signature = 0;
                break;
174 175
            else:
                continue;
176
        if line.startswith("-----BEGIN PGP SIGNATURE"):
J
James Troup 已提交
177
            break;
178
        if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
J
James Troup 已提交
179
            inside_signature = 1;
J
James Troup 已提交
180
            if dsc_whitespace_rules:
181
                while index < num_of_lines and line != "":
182
                    index += 1;
J
James Troup 已提交
183
                    line = indexed_lines[index];
J
James Troup 已提交
184
            continue;
185 186 187
        # If we're not inside the signed data, don't process anything
        if not inside_signature:
            continue;
188
        slf = re_single_line_field.match(line);
J
James Troup 已提交
189
        if slf:
190
            field = slf.groups()[0].lower();
J
James Troup 已提交
191
            changes[field] = slf.groups()[1];
192
	    first = 1;
J
James Troup 已提交
193
            continue;
J
James Troup 已提交
194
        if line == " .":
195
            changes[field] += '\n';
J
James Troup 已提交
196
            continue;
197
        mlf = re_multi_line_field.match(line);
J
James Troup 已提交
198
        if mlf:
199 200
            if first == -1:
                raise changes_parse_error_exc, "'%s'\n [Multi-line field continuing on from nothing?]" % (line);
201
            if first == 1 and changes[field] != "":
202
                changes[field] += '\n';
203
            first = 0;
204
	    changes[field] += mlf.groups()[0] + '\n';
J
James Troup 已提交
205
            continue;
206
	error += line;
J
James Troup 已提交
207 208 209

    if dsc_whitespace_rules and inside_signature:
        raise invalid_dsc_format_exc, index;
J
James Troup 已提交
210

J
James Troup 已提交
211
    changes_in.close();
212
    changes["filecontents"] = "".join(lines);
J
James Troup 已提交
213

214
    if error:
J
James Troup 已提交
215
	raise changes_parse_error_exc, error;
J
James Troup 已提交
216

J
James Troup 已提交
217 218
    return changes;

219
################################################################################
J
James Troup 已提交
220 221 222

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

223
def build_file_list(changes, is_a_dsc=0):
224 225 226 227 228 229 230 231
    files = {};

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

    # Make sure we recognise the format of the Files: field
    format = changes.get("format", "");
J
James Troup 已提交
232
    if format != "":
233
	format = float(format);
234
    if not is_a_dsc and (format < 1.5 or format > 2.0):
235
	raise nk_format_exc, format;
J
James Troup 已提交
236

237 238 239 240
    # Parse each entry/line:
    for i in changes["files"].split('\n'):
        if not i:
            break;
241
        s = i.split();
J
James Troup 已提交
242
        section = priority = "";
J
sync  
James Troup 已提交
243
        try:
244
            if is_a_dsc:
245
                (md5, size, name) = s;
J
sync  
James Troup 已提交
246
            else:
247
                (md5, size, section, priority, name) = s;
J
sync  
James Troup 已提交
248
        except ValueError:
249
            raise changes_parse_error_exc, i;
J
James Troup 已提交
250

251 252 253 254
        if section == "":
            section = "-";
        if priority == "":
            priority = "-";
J
James Troup 已提交
255

J
James Troup 已提交
256
        (section, component) = extract_component_from_section(section);
J
James Troup 已提交
257

258 259
        files[name] = Dict(md5sum=md5, size=size, section=section,
                           priority=priority, component=component);
J
James Troup 已提交
260 261 262

    return files

263
################################################################################
J
James Troup 已提交
264 265

# Fix the `Maintainer:' field to be an RFC822 compatible address.
266
# cf. Debian Policy Manual (D.2.4)
J
James Troup 已提交
267 268 269 270
#
# 06:28|<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 已提交
271

J
James Troup 已提交
272
def fix_maintainer (maintainer):
273
    m = re_parse_maintainer.match(maintainer);
274 275 276
    rfc822 = maintainer;
    name = "";
    email = "";
J
James Troup 已提交
277
    if m != None and len(m.groups()) == 2:
278 279
        name = m.group(1);
        email = m.group(2);
280
        if name.find(',') != -1 or name.find('.') != -1:
281
            rfc822 = "%s (%s)" % (email, name);
J
James Troup 已提交
282 283
    return (rfc822, name, email)

284
################################################################################
J
James Troup 已提交
285 286

# sendmail wrapper, takes _either_ a message string or a file as arguments
287
def send_mail (message, filename=""):
J
James Troup 已提交
288
	# If we've been passed a string dump it into a temporary file
289
	if message:
J
James Troup 已提交
290 291 292 293 294
            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 已提交
295
	# Invoke sendmail
296
	(result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename));
J
James Troup 已提交
297
	if (result != 0):
J
James Troup 已提交
298 299
            raise sendmail_failed_exc, output;

J
James Troup 已提交
300
	# Clean up any temporary files
301
	if message:
J
James Troup 已提交
302
            os.unlink (filename);
J
James Troup 已提交
303

304
################################################################################
J
James Troup 已提交
305 306

def poolify (source, component):
307
    if component:
308
	component += '/';
J
James Troup 已提交
309
    # FIXME: this is nasty
310
    component = component.lower().replace("non-us/", "non-US/");
J
James Troup 已提交
311 312 313 314 315
    if source[:3] == "lib":
	return component + source[:4] + '/' + source + '/'
    else:
	return component + source[:1] + '/' + source + '/'

316
################################################################################
J
James Troup 已提交
317

318
def move (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
319
    if os.path.exists(dest) and os.path.isdir(dest):
J
James Troup 已提交
320 321 322 323 324 325 326 327
	dest_dir = dest;
    else:
	dest_dir = os.path.dirname(dest);
    if not os.path.exists(dest_dir):
	umask = os.umask(00000);
	os.makedirs(dest_dir, 02775);
	os.umask(umask);
    #print "Moving %s to %s..." % (src, dest);
J
James Troup 已提交
328
    if os.path.exists(dest) and os.path.isdir(dest):
329
	dest += '/' + os.path.basename(src);
330 331 332
    # Don't overwrite unless forced to
    if os.path.exists(dest):
        if not overwrite:
333
            fubar("Can't move %s to %s - file already exists." % (src, dest));
334 335
        else:
            if not os.access(dest, os.W_OK):
336
                fubar("Can't move %s to %s - can't write to existing file." % (src, dest));
J
James Troup 已提交
337
    shutil.copy2(src, dest);
338
    os.chmod(dest, perms);
J
James Troup 已提交
339 340
    os.unlink(src);

341
def copy (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
342
    if os.path.exists(dest) and os.path.isdir(dest):
J
James Troup 已提交
343 344 345 346 347 348 349 350
	dest_dir = dest;
    else:
	dest_dir = os.path.dirname(dest);
    if not os.path.exists(dest_dir):
	umask = os.umask(00000);
	os.makedirs(dest_dir, 02775);
	os.umask(umask);
    #print "Copying %s to %s..." % (src, dest);
J
James Troup 已提交
351
    if os.path.exists(dest) and os.path.isdir(dest):
352
	dest += '/' + os.path.basename(src);
353 354 355 356 357 358 359
    # 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
J
James Troup 已提交
360
    shutil.copy2(src, dest);
361
    os.chmod(dest, perms);
J
James Troup 已提交
362

363
################################################################################
J
James Troup 已提交
364 365 366

def where_am_i ():
    res = socket.gethostbyaddr(socket.gethostname());
367
    database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname");
368 369
    if database_hostname:
	return database_hostname;
J
James Troup 已提交
370
    else:
J
James Troup 已提交
371
        return res[0];
J
James Troup 已提交
372 373

def which_conf_file ():
J
James Troup 已提交
374
    res = socket.gethostbyaddr(socket.gethostname());
375 376
    if Cnf.get("Config::" + res[0] + "::KatieConfig"):
	return Cnf["Config::" + res[0] + "::KatieConfig"]
J
James Troup 已提交
377
    else:
J
James Troup 已提交
378
	return default_config;
379 380

def which_apt_conf_file ():
J
James Troup 已提交
381
    res = socket.gethostbyaddr(socket.gethostname());
382 383
    if Cnf.get("Config::" + res[0] + "::AptConfig"):
	return Cnf["Config::" + res[0] + "::AptConfig"]
384
    else:
J
James Troup 已提交
385
	return default_apt_config;
386

387
################################################################################
J
James Troup 已提交
388

389 390 391 392
# Escape characters which have meaning to SQL's regex comparison operator ('~')
# (woefully incomplete)

def regex_safe (s):
393 394
    s = s.replace('+', '\\\\+');
    s = s.replace('.', '\\\\.');
395 396
    return s

397
################################################################################
398

J
James Troup 已提交
399
# Perform a substition of template
400
def TemplateSubst(map, filename):
401 402 403
    file = open_file(filename);
    template = file.read();
    for x in map.keys():
404
        template = template.replace(x,map[x]);
405 406
    file.close();
    return template;
J
James Troup 已提交
407

408
################################################################################
J
James Troup 已提交
409 410 411 412 413 414 415

def fubar(msg, exit_code=1):
    sys.stderr.write("E: %s\n" % (msg));
    sys.exit(exit_code);

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

417
################################################################################
J
James Troup 已提交
418

J
James Troup 已提交
419 420 421
# Returns the user name with a laughable attempt at rfc822 conformancy
# (read: removing stray periods).
def whoami ():
422
    return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '');
J
James Troup 已提交
423

424
################################################################################
J
James Troup 已提交
425

J
James Troup 已提交
426 427 428 429 430 431 432 433 434
def size_type (c):
    t  = " b";
    if c > 10000:
        c = c / 1000;
        t = " Kb";
    if c > 10000:
        c = c / 1000;
        t = " Mb";
    return ("%d%s" % (c, t))
435 436 437 438

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

def cc_fix_changes (changes):
439 440 441 442
    o = changes.get("architecture", "");
    if o:
        del changes["architecture"];
    changes["architecture"] = {};
443
    for j in o.split():
444
        changes["architecture"][j] = 1;
445

446
# Sort by source name, source version, 'have source', and then by filename
447 448
def changes_compare (a, b):
    try:
449
        a_changes = parse_changes(a);
450
    except:
451 452 453
        return -1;

    try:
454
        b_changes = parse_changes(b);
455
    except:
456
        return 1;
J
James Troup 已提交
457

458 459 460 461 462 463 464 465 466 467 468 469 470 471 472
    cc_fix_changes (a_changes);
    cc_fix_changes (b_changes);

    # Sort by source name
    a_source = a_changes.get("source");
    b_source = b_changes.get("source");
    q = cmp (a_source, b_source);
    if q:
        return q;

    # Sort by source version
    a_version = a_changes.get("version");
    b_version = b_changes.get("version");
    q = apt_pkg.VersionCompare(a_version, b_version);
    if q:
473
        return q;
474

475
    # Sort by 'have source'
476 477
    a_has_source = a_changes["architecture"].get("source");
    b_has_source = b_changes["architecture"].get("source");
478 479 480 481
    if a_has_source and not b_has_source:
        return -1;
    elif b_has_source and not a_has_source:
        return 1;
482

483
    # Fall back to sort by filename
484 485 486
    return cmp(a, b);

################################################################################
487 488 489 490 491 492

def find_next_free (dest, too_many=100):
    extra = 0;
    orig_dest = dest;
    while os.path.exists(dest) and extra < too_many:
        dest = orig_dest + '.' + repr(extra);
493
        extra += 1;
494 495 496
    if extra >= too_many:
        raise tried_too_hard_exc;
    return dest;
J
James Troup 已提交
497

498
################################################################################
499 500 501 502 503 504 505 506

def result_join (original, sep = '\t'):
    list = [];
    for i in xrange(len(original)):
        if original[i] == None:
            list.append("");
        else:
            list.append(original[i]);
507
    return sep.join(list);
508

509 510
################################################################################

511
def prefix_multi_line_string(str, prefix, include_blank_lines=0):
512
    out = "";
513 514
    for line in str.split('\n'):
        line = line.strip();
515
        if line or include_blank_lines:
516
            out += "%s%s\n" % (prefix, line);
517 518 519 520
    # Strip trailing new line
    if out:
        out = out[:-1];
    return out;
521

522
################################################################################
523

524 525 526
def validate_changes_file_arg(file, fatal=1):
    error = None;

527
    orig_filename = file
528
    if file.endswith(".katie"):
529 530
        file = file[:-6]+".changes";

531
    if not file.endswith(".changes"):
532 533 534 535 536 537 538 539 540 541
        error = "invalid file type; not a changes file";
    else:
        if not os.access(file,os.R_OK):
            if os.path.exists(file):
                error = "permission denied";
            else:
                error = "file not found";

    if error:
        if fatal:
542
            fubar("%s: %s." % (orig_filename, error));
543
        else:
544
            warn("Skipping %s - %s" % (orig_filename, error));
545 546 547 548 549 550
            return None;
    else:
        return file;

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

551 552 553 554 555
def real_arch(arch):
    return (arch != "source" and arch != "all");

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

556 557 558
def join_with_commas_and(list):
	if len(list) == 0: return "nothing";
	if len(list) == 1: return list[0];
559
	return ", ".join(list[:-1]) + " and " + list[-1];
560 561 562

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

563 564 565 566 567
def get_conf():
	return Cnf;

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

568 569 570 571 572
# Handle -a, -c and -s arguments; returns them as SQL constraints
def parse_args(Options):
    # Process suite
    if Options["Suite"]:
        suite_ids_list = [];
573
        for suite in split_args(Options["Suite"]):
574 575
            suite_id = db_access.get_suite_id(suite);
            if suite_id == -1:
576
                warn("suite '%s' not recognised." % (suite));
577 578 579
            else:
                suite_ids_list.append(suite_id);
        if suite_ids_list:
580
            con_suites = "AND su.id IN (%s)" % ", ".join(map(str, suite_ids_list));
581 582 583 584 585 586 587 588
        else:
            fubar("No valid suite given.");
    else:
        con_suites = "";

    # Process component
    if Options["Component"]:
        component_ids_list = [];
589
        for component in split_args(Options["Component"]):
590 591 592 593 594 595
            component_id = db_access.get_component_id(component);
            if component_id == -1:
                warn("component '%s' not recognised." % (component));
            else:
                component_ids_list.append(component_id);
        if component_ids_list:
596
            con_components = "AND c.id IN (%s)" % ", ".join(map(str, component_ids_list));
597 598 599 600 601 602 603 604 605 606
        else:
            fubar("No valid component given.");
    else:
        con_components = "";

    # Process architecture
    con_architectures = "";
    if Options["Architecture"]:
        arch_ids_list = [];
        check_source = 0;
607
        for architecture in split_args(Options["Architecture"]):
608 609 610 611 612 613 614 615 616
            if architecture == "source":
                check_source = 1;
            else:
                architecture_id = db_access.get_architecture_id(architecture);
                if architecture_id == -1:
                    warn("architecture '%s' not recognised." % (architecture));
                else:
                    arch_ids_list.append(architecture_id);
        if arch_ids_list:
617
            con_architectures = "AND a.id IN (%s)" % ", ".join(map(str, arch_ids_list));
618 619 620 621 622 623 624 625 626 627
        else:
            if not check_source:
                fubar("No valid architecture given.");
    else:
        check_source = 1;

    return (con_suites, con_architectures, con_components, check_source);

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

628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654
# Inspired(tm) by Bryn Keller's print_exc_plus (See
# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)

def print_exc():
    tb = sys.exc_info()[2];
    while tb.tb_next:
        tb = tb.tb_next;
    stack = [];
    frame = tb.tb_frame;
    while frame:
        stack.append(frame);
        frame = frame.f_back;
    stack.reverse();
    traceback.print_exc();
    for frame in stack:
        print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
                                             frame.f_code.co_filename,
                                             frame.f_lineno);
        for key, value in frame.f_locals.items():
            print "\t%20s = " % key,;
            try:
                print value;
            except:
                print "<unable to print>";

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

J
James Troup 已提交
655 656 657 658 659 660 661 662 663 664
def try_with_debug(function):
    try:
        function();
    except SystemExit:
        raise;
    except:
        print_exc();

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

J
James Troup 已提交
665 666 667 668 669 670 671 672 673 674 675 676 677 678 679
# 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":
        return 0;
    elif a == "source":
        return -1;
    elif b == "source":
        return 1;

    return cmp (a, b);

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

680 681 682 683 684 685 686 687 688 689 690 691 692 693 694 695
# Split command line arguments which can be separated by either commas
# or whitespace.  If dwim is set, it will complain about string ending
# in comma since this usually means someone did 'madison -a i386, m68k
# 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:
        return s.split();
    else:
        if s[-1:] == "," and dwim:
            fubar("split_args: found trailing comma, spurious space maybe?");
        return s.split(",");

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

696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726
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):
    cmd = ['/bin/sh', '-c', cmd];
    p2cread, p2cwrite = os.pipe();
    c2pread, c2pwrite = os.pipe();
    errout, errin = os.pipe();
    pid = os.fork();
    if pid == 0:
        # Child
        os.close(0);
        os.close(1);
        os.dup(p2cread);
        os.dup(c2pwrite);
        os.close(2);
        os.dup(errin);
        for i in range(3, 256):
            if i != status_write:
                try:
                    os.close(i);
                except:
                    pass;
        try:
            os.execvp(cmd[0], cmd);
        finally:
            os._exit(1);

727
    # Parent
728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770
    os.close(p2cread)
    os.dup2(c2pread, c2pwrite);
    os.dup2(errout, errin);

    output = status = "";
    while 1:
        i, o, e = select.select([c2pwrite, errin, status_read], [], []);
        more_data = [];
        for fd in i:
            r = os.read(fd, 8196);
            if len(r) > 0:
                more_data.append(fd);
                if fd == c2pwrite or fd == errin:
                    output += r;
                elif fd == status_read:
                    status += r;
                else:
                    fubar("Unexpected file descriptor [%s] returned from select\n" % (fd));
        if not more_data:
            pid, exit_status = os.waitpid(pid, 0)
            try:
                os.close(status_write);
                os.close(status_read);
                os.close(c2pread);
                os.close(c2pwrite);
                os.close(p2cwrite);
                os.close(errin);
                os.close(errout);
            except:
                pass;
            break;

    return output, status, exit_status;

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


def check_signature (filename, reject):
    """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,
771 772
the second is an optional prefix string.  It's possible for reject()
to be called more than once during an invocation of check_signature()."""
773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794 795 796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815

    # Ensure the filename contains no shell meta-characters or other badness
    if not re_taint_free.match(os.path.basename(filename)):
        reject("!!WARNING!! tainted filename: '%s'." % (filename));
        return 0;

    # Invoke gpgv on the file
    status_read, status_write = os.pipe();
    cmd = "gpgv --status-fd %s --keyring %s --keyring %s %s" \
          % (status_write, Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"], filename);
    (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write);

    # Process the status-fd output
    keywords = {};
    bad = 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;

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

    # Now check for obviously bad things in the processed output
    if keywords.has_key("SIGEXPIRED"):
816
        reject("The key used to sign %s has expired." % (filename));
817 818
        bad = 1;
    if keywords.has_key("KEYREVOKED"):
819
        reject("The key used to sign %s has been revoked." % (filename));
820 821 822 823 824 825 826 827
        bad = 1;
    if keywords.has_key("BADSIG"):
        reject("bad signature on %s." % (filename));
        bad = 1;
    if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
        reject("failed to check signature on %s." % (filename));
        bad = 1;
    if keywords.has_key("NO_PUBKEY"):
828 829 830 831
        args = keywords["NO_PUBKEY"];
        if len(args) >= 1:
            key = args[0];
        reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, filename));
832 833
        bad = 1;
    if keywords.has_key("BADARMOR"):
834
        reject("ASCII armour of signature was corrupt in %s." % (filename));
835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884 885 886
        bad = 1;
    if keywords.has_key("NODATA"):
        reject("no signature found in %s." % (filename));
        bad = 1;

    if bad:
        return None;

    # Next check gpgv exited with a zero return code
    if exit_status:
        reject("gpgv failed while checking %s." % (filename));
        if status.strip():
            reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "");
        else:
            reject(prefix_multi_line_string(output, " [GPG output:] "), "");
        return None;

    # Sanity check the good stuff we expect
    if not keywords.has_key("VALIDSIG"):
        reject("signature on %s does not appear to be valid [No VALIDSIG]." % (filename));
        bad = 1;
    else:
        args = keywords["VALIDSIG"];
        if len(args) < 1:
            reject("internal error while checking signature on %s." % (filename));
            bad = 1;
        else:
            fingerprint = args[0];
    if not keywords.has_key("GOODSIG"):
        reject("signature on %s does not appear to be valid [No GOODSIG]." % (filename));
        bad = 1;
    if not keywords.has_key("SIG_ID"):
        reject("signature on %s does not appear to be valid [No SIG_ID]." % (filename));
        bad = 1;

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

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

    if bad:
        return None;
    else:
        return fingerprint;

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

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 915 916 917 918 919 920 921 922 923 924 925 926 927 928 929 930 931
# Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603

def wrap(paragraph, max_length, prefix=""):
    line = "";
    s = "";
    have_started = 0;
    words = paragraph.split();

    for word in words:
        word_size = len(word);
        if word_size > max_length:
            if have_started:
                s += line + '\n' + prefix;
            s += word + '\n' + prefix;
        else:
            if have_started:
                new_length = len(line) + word_size + 1;
                if new_length > max_length:
                    s += line + '\n' + prefix;
                    line = word;
                else:
                    line += ' ' + word;
            else:
                line = word;
        have_started = 1;

    if have_started:
        s += line;

    return s;

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

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

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

apt_pkg.init();
932 933 934 935 936

Cnf = apt_pkg.newConfiguration();
apt_pkg.ReadConfigFileISC(Cnf,default_config);

if which_conf_file() != default_config:
937
	apt_pkg.ReadConfigFileISC(Cnf,which_conf_file());
938 939

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