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

J
James Troup 已提交
3
# Utility functions
4
# Copyright (C) 2000, 2001, 2002, 2003  James Troup <james@nocrew.org>
5
# $Id: utils.py,v 1.57 2003-03-14 19:05:13 troup Exp $
J
James Troup 已提交
6 7 8 9 10 11 12 13 14 15 16 17 18 19 20

# 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

21 22
################################################################################

23
import commands, os, pwd, re, select, socket, shutil, string, sys, tempfile, traceback;
24 25 26 27
import apt_pkg;
import db_access;

################################################################################
J
James Troup 已提交
28 29 30 31 32 33

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*\((.*)\)")
34 35
re_isadeb = re.compile (r"(.+?)_(.+?)_(.+)\.u?deb$");
re_issource = re.compile (r"(.+)_(.+?)\.(orig\.tar\.gz|diff\.gz|tar\.gz|dsc)$");
36 37 38

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    return (section, component);

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

J
James Troup 已提交
120 121 122 123 124 125 126 127 128 129 130 131 132
# dsc_whitespace_rules turns on strict format checking to avoid
# allowing in source packages which are unextracable by the
# inappropriately fragile dpkg-source.
#
# The rules 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-----".

133
def parse_changes(filename, dsc_whitespace_rules=0):
134
    changes_in = open_file(filename);
J
James Troup 已提交
135
    error = "";
J
James Troup 已提交
136 137
    changes = {};
    lines = changes_in.readlines();
J
James Troup 已提交
138

139
    if not lines:
140 141
	raise changes_parse_error_exc, "[Empty changes file]";

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

    inside_signature = 0;

    indices = indexed_lines.keys()
    index = 0;
154
    first = -1;
J
James Troup 已提交
155
    while index < max(indices):
156
        index += 1;
J
James Troup 已提交
157 158 159
        line = indexed_lines[index];
        if line == "":
            if dsc_whitespace_rules:
160
                index += 1;
J
James Troup 已提交
161 162 163
                if index > max(indices):
                    raise invalid_dsc_format_exc, index;
                line = indexed_lines[index];
164
                if not line.startswith("-----BEGIN PGP SIGNATURE"):
J
James Troup 已提交
165 166 167
                    raise invalid_dsc_format_exc, index;
                inside_signature = 0;
                break;
168
        if line.startswith("-----BEGIN PGP SIGNATURE"):
J
James Troup 已提交
169
            break;
170
        if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
J
James Troup 已提交
171 172 173
            if dsc_whitespace_rules:
                inside_signature = 1;
                while index < max(indices) and line != "":
174
                    index += 1;
J
James Troup 已提交
175
                    line = indexed_lines[index];
J
James Troup 已提交
176
            continue;
177
        slf = re_single_line_field.match(line);
J
James Troup 已提交
178
        if slf:
179
            field = slf.groups()[0].lower();
J
James Troup 已提交
180
            changes[field] = slf.groups()[1];
181
	    first = 1;
J
James Troup 已提交
182
            continue;
J
James Troup 已提交
183
        if line == " .":
184
            changes[field] += '\n';
J
James Troup 已提交
185
            continue;
186
        mlf = re_multi_line_field.match(line);
J
James Troup 已提交
187
        if mlf:
188 189
            if first == -1:
                raise changes_parse_error_exc, "'%s'\n [Multi-line field continuing on from nothing?]" % (line);
190
            if first == 1 and changes[field] != "":
191
                changes[field] += '\n';
192
            first = 0;
193
	    changes[field] += mlf.groups()[0] + '\n';
J
James Troup 已提交
194
            continue;
195
	error += line;
J
James Troup 已提交
196 197 198

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

J
James Troup 已提交
200
    changes_in.close();
201
    changes["filecontents"] = "".join(lines);
J
James Troup 已提交
202

J
James Troup 已提交
203 204
    if error != "":
	raise changes_parse_error_exc, error;
J
James Troup 已提交
205

J
James Troup 已提交
206 207 208 209 210 211
    return changes;

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

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

212
def build_file_list(changes, is_a_dsc=0):
J
James Troup 已提交
213 214 215 216
    files = {}
    format = changes.get("format", "")
    if format != "":
	format = float(format)
217
    if not is_a_dsc and (format < 1.5 or format > 2.0):
218
	raise nk_format_exc, format;
J
James Troup 已提交
219 220 221 222

    # No really, this has happened.  Think 0 length .dsc file.
    if not changes.has_key("files"):
	raise no_files_exc
J
James Troup 已提交
223

224
    for i in changes["files"].split("\n"):
J
James Troup 已提交
225 226
        if i == "":
            break
227
        s = i.split();
J
James Troup 已提交
228
        section = priority = "";
J
sync  
James Troup 已提交
229
        try:
230
            if is_a_dsc:
J
sync  
James Troup 已提交
231 232 233 234 235
                (md5, size, name) = s
            else:
                (md5, size, section, priority, name) = s
        except ValueError:
            raise changes_parse_error_exc, i
J
James Troup 已提交
236 237 238 239

        if section == "": section = "-"
        if priority == "": priority = "-"

J
James Troup 已提交
240
        (section, component) = extract_component_from_section(section);
J
James Troup 已提交
241

J
James Troup 已提交
242 243 244 245 246 247 248 249 250 251 252
        files[name] = { "md5sum" : md5,
                        "size" : size,
                        "section": section,
                        "priority": priority,
                        "component": component }

    return files

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

# Fix the `Maintainer:' field to be an RFC822 compatible address.
253
# cf. Debian Policy Manual (D.2.4)
J
James Troup 已提交
254 255 256 257
#
# 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 已提交
258

J
James Troup 已提交
259
def fix_maintainer (maintainer):
260
    m = re_parse_maintainer.match(maintainer);
261 262 263
    rfc822 = maintainer;
    name = "";
    email = "";
J
James Troup 已提交
264
    if m != None and len(m.groups()) == 2:
265 266
        name = m.group(1);
        email = m.group(2);
267
        if name.find(',') != -1 or name.find('.') != -1:
268
            rfc822 = "%s (%s)" % (email, name);
J
James Troup 已提交
269 270 271 272 273
    return (rfc822, name, email)

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

# sendmail wrapper, takes _either_ a message string or a file as arguments
274
def send_mail (message, filename=""):
J
James Troup 已提交
275 276
	# Sanity check arguments
	if message != "" and filename != "":
J
James Troup 已提交
277 278
            raise send_mail_invalid_args_exc;

J
James Troup 已提交
279 280
	# If we've been passed a string dump it into a temporary file
	if message != "":
J
James Troup 已提交
281 282 283 284 285
            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 已提交
286
	# Invoke sendmail
287
	(result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename));
J
James Troup 已提交
288
	if (result != 0):
J
James Troup 已提交
289 290
            raise sendmail_failed_exc, output;

J
James Troup 已提交
291 292
	# Clean up any temporary files
	if message !="":
J
James Troup 已提交
293
            os.unlink (filename);
J
James Troup 已提交
294 295 296 297 298

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

def poolify (source, component):
    if component != "":
299
	component += '/';
J
James Troup 已提交
300
    # FIXME: this is nasty
301
    component = component.lower().replace('non-us/', 'non-US/');
J
James Troup 已提交
302 303 304 305 306 307 308
    if source[:3] == "lib":
	return component + source[:4] + '/' + source + '/'
    else:
	return component + source[:1] + '/' + source + '/'

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

309
def move (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
310
    if os.path.exists(dest) and os.path.isdir(dest):
J
James Troup 已提交
311 312 313 314 315 316 317 318
	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 已提交
319
    if os.path.exists(dest) and os.path.isdir(dest):
320
	dest += '/' + os.path.basename(src);
321 322 323 324 325 326 327
    # 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 已提交
328
    shutil.copy2(src, dest);
329
    os.chmod(dest, perms);
J
James Troup 已提交
330 331
    os.unlink(src);

332
def copy (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
333
    if os.path.exists(dest) and os.path.isdir(dest):
J
James Troup 已提交
334 335 336 337 338 339 340 341
	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 已提交
342
    if os.path.exists(dest) and os.path.isdir(dest):
343
	dest += '/' + os.path.basename(src);
344 345 346 347 348 349 350
    # 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 已提交
351
    shutil.copy2(src, dest);
352
    os.chmod(dest, perms);
J
James Troup 已提交
353

J
James Troup 已提交
354 355 356 357
######################################################################################

def where_am_i ():
    res = socket.gethostbyaddr(socket.gethostname());
358
    database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname");
359 360
    if database_hostname:
	return database_hostname;
J
James Troup 已提交
361
    else:
J
James Troup 已提交
362
        return res[0];
J
James Troup 已提交
363 364

def which_conf_file ():
J
James Troup 已提交
365
    res = socket.gethostbyaddr(socket.gethostname());
366 367
    if Cnf.get("Config::" + res[0] + "::KatieConfig"):
	return Cnf["Config::" + res[0] + "::KatieConfig"]
J
James Troup 已提交
368
    else:
J
James Troup 已提交
369
	return default_config;
370 371

def which_apt_conf_file ():
J
James Troup 已提交
372
    res = socket.gethostbyaddr(socket.gethostname());
373 374
    if Cnf.get("Config::" + res[0] + "::AptConfig"):
	return Cnf["Config::" + res[0] + "::AptConfig"]
375
    else:
J
James Troup 已提交
376
	return default_apt_config;
377

J
James Troup 已提交
378 379
######################################################################################

380 381 382 383
# Escape characters which have meaning to SQL's regex comparison operator ('~')
# (woefully incomplete)

def regex_safe (s):
384 385
    s = s.replace('+', '\\\\+');
    s = s.replace('.', '\\\\.');
386 387 388 389
    return s

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

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

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

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 已提交
407 408 409

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

J
James Troup 已提交
410 411 412
# Returns the user name with a laughable attempt at rfc822 conformancy
# (read: removing stray periods).
def whoami ():
413
    return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '');
J
James Troup 已提交
414 415 416

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

J
James Troup 已提交
417 418 419 420 421 422 423 424 425
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))
426 427 428 429 430 431 432 433

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

def cc_fix_changes (changes):
    o = changes.get("architecture", "")
    if o != "":
        del changes["architecture"]
    changes["architecture"] = {}
434
    for j in o.split():
435 436
        changes["architecture"][j] = 1

437
# Sort by source name, source version, 'have source', and then by filename
438 439
def changes_compare (a, b):
    try:
440
        a_changes = parse_changes(a);
441
    except:
442 443 444
        return -1;

    try:
445
        b_changes = parse_changes(b);
446
    except:
447
        return 1;
J
James Troup 已提交
448

449 450 451 452 453 454 455 456 457 458 459 460 461 462 463
    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:
464
        return q;
465

466
    # Sort by 'have source'
467 468
    a_has_source = a_changes["architecture"].get("source");
    b_has_source = b_changes["architecture"].get("source");
469 470 471 472
    if a_has_source and not b_has_source:
        return -1;
    elif b_has_source and not a_has_source:
        return 1;
473

474
    # Fall back to sort by filename
475 476 477
    return cmp(a, b);

################################################################################
478 479 480 481 482 483

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);
484
        extra += 1;
485 486 487
    if extra >= too_many:
        raise tried_too_hard_exc;
    return dest;
J
James Troup 已提交
488

489
################################################################################
490 491 492 493 494 495 496 497

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

500 501
################################################################################

502
def prefix_multi_line_string(str, prefix, include_blank_lines=0):
503
    out = "";
504 505
    for line in str.split('\n'):
        line = line.strip();
506
        if line or include_blank_lines:
507
            out += "%s%s\n" % (prefix, line);
508 509 510 511
    # Strip trailing new line
    if out:
        out = out[:-1];
    return out;
512

513
################################################################################
514

515 516 517
def validate_changes_file_arg(file, fatal=1):
    error = None;

518
    orig_filename = file
519
    if file.endswith(".katie"):
520 521
        file = file[:-6]+".changes";

522
    if not file.endswith(".changes"):
523 524 525 526 527 528 529 530 531 532
        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:
533
            fubar("%s: %s." % (orig_filename, error));
534
        else:
535
            warn("Skipping %s - %s" % (orig_filename, error));
536 537 538 539 540 541
            return None;
    else:
        return file;

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

542 543 544 545 546
def real_arch(arch):
    return (arch != "source" and arch != "all");

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

547 548 549
def join_with_commas_and(list):
	if len(list) == 0: return "nothing";
	if len(list) == 1: return list[0];
550
	return ", ".join(list[:-1]) + " and " + list[-1];
551 552 553

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

554 555 556 557 558
def get_conf():
	return Cnf;

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

559 560 561 562 563
# Handle -a, -c and -s arguments; returns them as SQL constraints
def parse_args(Options):
    # Process suite
    if Options["Suite"]:
        suite_ids_list = [];
564
        for suite in split_args(Options["Suite"]):
565 566
            suite_id = db_access.get_suite_id(suite);
            if suite_id == -1:
567
                warn("suite '%s' not recognised." % (suite));
568 569 570
            else:
                suite_ids_list.append(suite_id);
        if suite_ids_list:
571
            con_suites = "AND su.id IN (%s)" % ", ".join(map(str, suite_ids_list));
572 573 574 575 576 577 578 579
        else:
            fubar("No valid suite given.");
    else:
        con_suites = "";

    # Process component
    if Options["Component"]:
        component_ids_list = [];
580
        for component in split_args(Options["Component"]):
581 582 583 584 585 586
            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:
587
            con_components = "AND c.id IN (%s)" % ", ".join(map(str, component_ids_list));
588 589 590 591 592 593 594 595 596 597
        else:
            fubar("No valid component given.");
    else:
        con_components = "";

    # Process architecture
    con_architectures = "";
    if Options["Architecture"]:
        arch_ids_list = [];
        check_source = 0;
598
        for architecture in split_args(Options["Architecture"]):
599 600 601 602 603 604 605 606 607
            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:
608
            con_architectures = "AND a.id IN (%s)" % ", ".join(map(str, arch_ids_list));
609 610 611 612 613 614 615 616 617 618
        else:
            if not check_source:
                fubar("No valid architecture given.");
    else:
        check_source = 1;

    return (con_suites, con_architectures, con_components, check_source);

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

619 620 621 622 623 624 625 626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645
# 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 已提交
646 647 648 649 650 651 652 653 654 655
def try_with_debug(function):
    try:
        function();
    except SystemExit:
        raise;
    except:
        print_exc();

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

J
James Troup 已提交
656 657 658 659 660 661 662 663 664 665 666 667 668 669 670
# 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);

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

671 672 673 674 675 676 677 678 679 680 681 682 683 684 685 686
# 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(",");

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

687 688 689 690 691 692 693 694 695 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 727 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 771 772 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 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 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
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);

    # parent
    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,
the second is an optional prefix string.  It is possible that reject()
is called more than once during an invocation of check_signature()."""

    # 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"):
        reject("key used to sign %s has expired." % (filename));
        bad = 1;
    if keywords.has_key("KEYREVOKED"):
        reject("key used to sign %s has been revoked." % (filename));
        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"):
        reject("key used to sign %s not found in keyring." % (filename));
        bad = 1;
    if keywords.has_key("BADARMOR"):
        reject("ascii armour of signature was corrupt in %s." % (filename));
        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;

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

875 876 877 878 879 880 881 882 883
apt_pkg.init()

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

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

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