utils.py 33.3 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>
5
# $Id: utils.py,v 1.66 2004-04-03 02:49:46 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 26 27
import commands, encodings.ascii, encodings.utf_8, encodings.latin_1, \
       email.Header, os, pwd, re, select, socket, shutil, string, sys, \
       tempfile, traceback;
28 29 30 31
import apt_pkg;
import db_access;

################################################################################
J
James Troup 已提交
32 33 34 35 36 37

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

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

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

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

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

61
################################################################################
J
James Troup 已提交
62

63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79
class Error(Exception):
    """Base class for exceptions in this module."""
    pass;

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

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

    def __init__(self, message):
        self.args = message,;
        self.message = message;

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

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

87
################################################################################
J
James Troup 已提交
88

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

100
################################################################################
J
James Troup 已提交
101

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

108
################################################################################
109

J
James Troup 已提交
110 111
def extract_component_from_section(section):
    component = "";
J
James Troup 已提交
112

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

120
    if section.lower() == "non-us":
J
James Troup 已提交
121
        component = "non-US/main";
122 123

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

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

    return (section, component);

138 139 140 141 142
################################################################################

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

144 145
# 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 已提交
146 147 148 149 150
# allowing in source packages which are unextracable by the
# inappropriately fragile dpkg-source.
#
# The rules are:
#
151 152
#   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 已提交
153
#
154 155
#   o The data section must end with a blank line and must be followed by
#     "-----BEGIN PGP SIGNATURE-----".
J
James Troup 已提交
156

157
def parse_changes(filename, dsc_whitespace_rules=0):
J
James Troup 已提交
158
    error = "";
J
James Troup 已提交
159
    changes = {};
160 161

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

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

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

    inside_signature = 0;

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

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

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

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

J
James Troup 已提交
236 237
    return changes;

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

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

242
def build_file_list(changes, is_a_dsc=0):
243 244 245 246 247 248 249 250
    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 已提交
251
    if format != "":
252
	format = float(format);
253
    if not is_a_dsc and (format < 1.5 or format > 2.0):
254
	raise nk_format_exc, format;
J
James Troup 已提交
255

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

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

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

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

    return files

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

284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314
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:
        unicode(s, 'utf-8');
        return s;
    except UnicodeError:
        latin1_s = unicode(s,'iso8859-1');
        return latin1_s.encode('utf-8');

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:
        encodings.ascii.Codec().decode(s);
        return s;
    except UnicodeError:
        pass;
    try:
        encodings.utf_8.Codec().decode(s);
        h = email.Header.Header(s, 'utf-8', 998);
        return str(h);
    except UnicodeError:
        h = email.Header.Header(s, 'iso-8859-1', 998);
        return str(h);

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

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

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

    if maintainer.find("<") == -1 or (maintainer[0] == "<" and \
                                      maintainer[-1:] == ">"):
        email = maintainer;
        name = "";
    else:
        m = re_parse_maintainer.match(maintainer);
        if not m:
            raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
338 339
        name = m.group(1);
        email = m.group(2);
340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357

    # Get an RFC2047 compliant version of the name
    rfc2047_name = rfc2047_encode(name);

    # Force the name to be UTF-8
    name = force_to_utf8(name);

    if name.find(',') != -1 or name.find('.') != -1:
        rfc822_maint = "%s (%s)" % (email, name);
        rfc2047_maint = "%s (%s)" % (email, rfc2047_name);
    else:
        rfc822_maint = "%s <%s>" % (name, email);
        rfc2047_maint = "%s <%s>" % (rfc2047_name, email);

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

    return (rfc822_maint, rfc2047_maint, name, email);
J
James Troup 已提交
358

359
################################################################################
J
James Troup 已提交
360 361

# sendmail wrapper, takes _either_ a message string or a file as arguments
362
def send_mail (message, filename=""):
J
James Troup 已提交
363
	# If we've been passed a string dump it into a temporary file
364
	if message:
J
James Troup 已提交
365 366 367 368 369
            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 已提交
370
	# Invoke sendmail
371
	(result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename));
J
James Troup 已提交
372
	if (result != 0):
J
James Troup 已提交
373 374
            raise sendmail_failed_exc, output;

J
James Troup 已提交
375
	# Clean up any temporary files
376
	if message:
J
James Troup 已提交
377
            os.unlink (filename);
J
James Troup 已提交
378

379
################################################################################
J
James Troup 已提交
380 381

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

391
################################################################################
J
James Troup 已提交
392

393
def move (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
394
    if os.path.exists(dest) and os.path.isdir(dest):
J
James Troup 已提交
395 396 397 398 399 400 401 402
	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 已提交
403
    if os.path.exists(dest) and os.path.isdir(dest):
404
	dest += '/' + os.path.basename(src);
405 406 407
    # Don't overwrite unless forced to
    if os.path.exists(dest):
        if not overwrite:
408
            fubar("Can't move %s to %s - file already exists." % (src, dest));
409 410
        else:
            if not os.access(dest, os.W_OK):
411
                fubar("Can't move %s to %s - can't write to existing file." % (src, dest));
J
James Troup 已提交
412
    shutil.copy2(src, dest);
413
    os.chmod(dest, perms);
J
James Troup 已提交
414 415
    os.unlink(src);

416
def copy (src, dest, overwrite = 0, perms = 0664):
J
James Troup 已提交
417
    if os.path.exists(dest) and os.path.isdir(dest):
J
James Troup 已提交
418 419 420 421 422 423 424 425
	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 已提交
426
    if os.path.exists(dest) and os.path.isdir(dest):
427
	dest += '/' + os.path.basename(src);
428 429 430 431 432 433 434
    # 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 已提交
435
    shutil.copy2(src, dest);
436
    os.chmod(dest, perms);
J
James Troup 已提交
437

438
################################################################################
J
James Troup 已提交
439 440 441

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

def which_conf_file ():
J
James Troup 已提交
449
    res = socket.gethostbyaddr(socket.gethostname());
450 451
    if Cnf.get("Config::" + res[0] + "::KatieConfig"):
	return Cnf["Config::" + res[0] + "::KatieConfig"]
J
James Troup 已提交
452
    else:
J
James Troup 已提交
453
	return default_config;
454 455

def which_apt_conf_file ():
J
James Troup 已提交
456
    res = socket.gethostbyaddr(socket.gethostname());
457 458
    if Cnf.get("Config::" + res[0] + "::AptConfig"):
	return Cnf["Config::" + res[0] + "::AptConfig"]
459
    else:
J
James Troup 已提交
460
	return default_apt_config;
461

462
################################################################################
J
James Troup 已提交
463

464 465 466 467
# Escape characters which have meaning to SQL's regex comparison operator ('~')
# (woefully incomplete)

def regex_safe (s):
468 469
    s = s.replace('+', '\\\\+');
    s = s.replace('.', '\\\\.');
470 471
    return s

472
################################################################################
473

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

483
################################################################################
J
James Troup 已提交
484 485 486 487 488 489 490

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 已提交
491

492
################################################################################
J
James Troup 已提交
493

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

499
################################################################################
J
James Troup 已提交
500

J
James Troup 已提交
501 502 503 504 505 506 507 508 509
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))
510 511 512 513

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

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

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

    try:
529
        b_changes = parse_changes(b);
530
    except:
531
        return 1;
J
James Troup 已提交
532

533 534 535 536 537 538 539 540 541 542 543 544 545 546 547
    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:
548
        return q;
549

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

558
    # Fall back to sort by filename
559 560 561
    return cmp(a, b);

################################################################################
562 563 564 565 566 567

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);
568
        extra += 1;
569 570 571
    if extra >= too_many:
        raise tried_too_hard_exc;
    return dest;
J
James Troup 已提交
572

573
################################################################################
574 575 576 577 578 579 580 581

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

584 585
################################################################################

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

597
################################################################################
598

599 600 601
def validate_changes_file_arg(file, fatal=1):
    error = None;

602
    orig_filename = file
603
    if file.endswith(".katie"):
604 605
        file = file[:-6]+".changes";

606
    if not file.endswith(".changes"):
607 608 609 610 611 612 613 614 615 616
        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:
617
            fubar("%s: %s." % (orig_filename, error));
618
        else:
619
            warn("Skipping %s - %s" % (orig_filename, error));
620 621 622 623 624 625
            return None;
    else:
        return file;

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

626 627 628 629 630
def real_arch(arch):
    return (arch != "source" and arch != "all");

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

631 632 633
def join_with_commas_and(list):
	if len(list) == 0: return "nothing";
	if len(list) == 1: return list[0];
634
	return ", ".join(list[:-1]) + " and " + list[-1];
635 636 637

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

638 639 640 641 642
def get_conf():
	return Cnf;

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

643 644 645 646 647
# Handle -a, -c and -s arguments; returns them as SQL constraints
def parse_args(Options):
    # Process suite
    if Options["Suite"]:
        suite_ids_list = [];
648
        for suite in split_args(Options["Suite"]):
649 650
            suite_id = db_access.get_suite_id(suite);
            if suite_id == -1:
651
                warn("suite '%s' not recognised." % (suite));
652 653 654
            else:
                suite_ids_list.append(suite_id);
        if suite_ids_list:
655
            con_suites = "AND su.id IN (%s)" % ", ".join(map(str, suite_ids_list));
656 657 658 659 660 661 662 663
        else:
            fubar("No valid suite given.");
    else:
        con_suites = "";

    # Process component
    if Options["Component"]:
        component_ids_list = [];
664
        for component in split_args(Options["Component"]):
665 666 667 668 669 670
            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:
671
            con_components = "AND c.id IN (%s)" % ", ".join(map(str, component_ids_list));
672 673 674 675 676 677 678 679 680 681
        else:
            fubar("No valid component given.");
    else:
        con_components = "";

    # Process architecture
    con_architectures = "";
    if Options["Architecture"]:
        arch_ids_list = [];
        check_source = 0;
682
        for architecture in split_args(Options["Architecture"]):
683 684 685 686 687 688 689 690 691
            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:
692
            con_architectures = "AND a.id IN (%s)" % ", ".join(map(str, arch_ids_list));
693 694 695 696 697 698 699 700 701 702
        else:
            if not check_source:
                fubar("No valid architecture given.");
    else:
        check_source = 1;

    return (con_suites, con_architectures, con_components, check_source);

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

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
# 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 已提交
730 731 732 733 734 735 736 737 738 739
def try_with_debug(function):
    try:
        function();
    except SystemExit:
        raise;
    except:
        print_exc();

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

J
James Troup 已提交
740 741 742 743 744 745 746 747 748 749 750 751 752 753 754
# 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);

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

755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770
# 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(",");

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

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
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);

802
    # Parent
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
    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,
846 847
the second is an optional prefix string.  It's possible for reject()
to be called more than once during an invocation of check_signature()."""
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 887 888 889 890

    # 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"):
891
        reject("The key used to sign %s has expired." % (filename));
892 893
        bad = 1;
    if keywords.has_key("KEYREVOKED"):
894
        reject("The key used to sign %s has been revoked." % (filename));
895 896 897 898 899 900 901 902
        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"):
903 904 905 906
        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));
907 908
        bad = 1;
    if keywords.has_key("BADARMOR"):
909
        reject("ASCII armour of signature was corrupt in %s." % (filename));
910 911 912 913 914 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 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961
        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;

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

962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005
# 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;

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

1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028
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:
        old_tempdir = tempfile.tempdir;
        tempfile.tempdir = directory;

    filename = tempfile.mktemp();

    if dotprefix:
        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);

    if directory:
        tempfile.tempdir = old_tempdir;

    return filename;

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

1029
apt_pkg.init();
1030 1031 1032 1033 1034

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

if which_conf_file() != default_config:
1035
	apt_pkg.ReadConfigFileISC(Cnf,which_conf_file());
1036 1037

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