4 # Copyright (C) 2000, 2001, 2002, 2003, 2004 James Troup <james@nocrew.org>
5 # $Id: utils.py,v 1.72 2004-11-27 18:12:57 troup Exp $
7 ################################################################################
9 # This program is free software; you can redistribute it and/or modify
10 # it under the terms of the GNU General Public License as published by
11 # the Free Software Foundation; either version 2 of the License, or
12 # (at your option) any later version.
14 # This program is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 # GNU General Public License for more details.
19 # You should have received a copy of the GNU General Public License
20 # along with this program; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
23 ################################################################################
25 import commands, encodings.ascii, encodings.utf_8, encodings.latin_1, \
26 email.Header, os, pwd, re, select, socket, shutil, string, sys, \
31 ################################################################################
33 re_comments = re.compile(r"\#.*")
34 re_no_epoch = re.compile(r"^\d+\:")
35 re_no_revision = re.compile(r"-[^-]+$")
36 re_arch_from_filename = re.compile(r"/binary-[^/]+/")
37 re_extract_src_version = re.compile (r"(\S+)\s*\((.*)\)")
38 re_isadeb = re.compile (r"(.+?)_(.+?)_(.+)\.u?deb$");
39 re_issource = re.compile (r"(.+)_(.+?)\.(orig\.tar\.gz|diff\.gz|tar\.gz|dsc)$");
41 re_single_line_field = re.compile(r"^(\S*)\s*:\s*(.*)");
42 re_multi_line_field = re.compile(r"^\s(.*)");
43 re_taint_free = re.compile(r"^[-+~/\.\w]+$");
45 re_parse_maintainer = re.compile(r"^\s*(\S.*\S)\s*\<([^\>]+)\>");
47 changes_parse_error_exc = "Can't parse line in .changes file";
48 invalid_dsc_format_exc = "Invalid .dsc file";
49 nk_format_exc = "Unknown Format: in .changes file";
50 no_files_exc = "No Files: field in .dsc or .changes file.";
51 cant_open_exc = "Can't open file";
52 unknown_hostname_exc = "Unknown hostname";
53 cant_overwrite_exc = "Permission denied; can't overwrite existent file."
54 file_exists_exc = "Destination file exists";
55 sendmail_failed_exc = "Sendmail invocation failed";
56 tried_too_hard_exc = "Tried too hard to find a free filename.";
58 default_config = "/etc/katie/katie.conf";
59 default_apt_config = "/etc/katie/apt.conf";
61 ################################################################################
63 class Error(Exception):
64 """Base class for exceptions in this module."""
67 class ParseMaintError(Error):
68 """Exception raised for errors in parsing a maintainer field.
71 message -- explanation of the error
74 def __init__(self, message):
76 self.message = message;
78 ################################################################################
80 def open_file(filename, mode='r'):
82 f = open(filename, mode);
84 raise cant_open_exc, filename;
87 ################################################################################
89 def our_raw_input(prompt=""):
91 sys.stdout.write(prompt);
97 sys.stderr.write("\nUser interrupt (^D).\n");
100 ################################################################################
104 if c not in string.digits:
108 ################################################################################
110 def extract_component_from_section(section):
113 if section.find('/') != -1:
114 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
120 if section.lower() == "non-us":
121 component = "non-US/main";
123 # non-US prefix is case insensitive
124 if component.lower()[:6] == "non-us":
125 component = "non-US"+component[6:];
127 # Expand default component
129 if Cnf.has_key("Component::%s" % section):
133 elif component == "non-US":
134 component = "non-US/main";
136 return (section, component);
138 ################################################################################
140 def parse_changes(filename, signing_rules=0):
141 """Parses a changes file and returns a dictionary where each field is a
142 key. The mandatory first argument is the filename of the .changes
145 signing_rules is an optional argument:
147 o If signing_rules == -1, no signature is required.
148 o If signing_rules == 0 (the default), a signature is required.
149 o If signing_rules == 1, it turns on the same strict format checking
152 The rules for (signing_rules == 1)-mode are:
154 o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
155 followed by any PGP header data and must end with a blank line.
157 o The data section must end with a blank line and must be followed by
158 "-----BEGIN PGP SIGNATURE-----".
164 changes_in = open_file(filename);
165 lines = changes_in.readlines();
168 raise changes_parse_error_exc, "[Empty changes file]";
170 # Reindex by line number so we can easily verify the format of
176 indexed_lines[index] = line[:-1];
178 inside_signature = 0;
180 num_of_lines = len(indexed_lines.keys());
183 while index < num_of_lines:
185 line = indexed_lines[index];
187 if signing_rules == 1:
189 if index > num_of_lines:
190 raise invalid_dsc_format_exc, index;
191 line = indexed_lines[index];
192 if not line.startswith("-----BEGIN PGP SIGNATURE"):
193 raise invalid_dsc_format_exc, index;
194 inside_signature = 0;
198 if line.startswith("-----BEGIN PGP SIGNATURE"):
200 if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
201 inside_signature = 1;
202 if signing_rules == 1:
203 while index < num_of_lines and line != "":
205 line = indexed_lines[index];
207 # If we're not inside the signed data, don't process anything
208 if signing_rules >= 0 and not inside_signature:
210 slf = re_single_line_field.match(line);
212 field = slf.groups()[0].lower();
213 changes[field] = slf.groups()[1];
217 changes[field] += '\n';
219 mlf = re_multi_line_field.match(line);
222 raise changes_parse_error_exc, "'%s'\n [Multi-line field continuing on from nothing?]" % (line);
223 if first == 1 and changes[field] != "":
224 changes[field] += '\n';
226 changes[field] += mlf.groups()[0] + '\n';
230 if signing_rules == 1 and inside_signature:
231 raise invalid_dsc_format_exc, index;
234 changes["filecontents"] = "".join(lines);
237 raise changes_parse_error_exc, error;
241 ################################################################################
243 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
245 def build_file_list(changes, is_a_dsc=0):
248 # Make sure we have a Files: field to parse...
249 if not changes.has_key("files"):
252 # Make sure we recognise the format of the Files: field
253 format = changes.get("format", "");
255 format = float(format);
256 if not is_a_dsc and (format < 1.5 or format > 2.0):
257 raise nk_format_exc, format;
259 # Parse each entry/line:
260 for i in changes["files"].split('\n'):
264 section = priority = "";
267 (md5, size, name) = s;
269 (md5, size, section, priority, name) = s;
271 raise changes_parse_error_exc, i;
278 (section, component) = extract_component_from_section(section);
280 files[name] = Dict(md5sum=md5, size=size, section=section,
281 priority=priority, component=component);
285 ################################################################################
287 def force_to_utf8(s):
288 """Forces a string to UTF-8. If the string isn't already UTF-8,
289 it's assumed to be ISO-8859-1."""
294 latin1_s = unicode(s,'iso8859-1');
295 return latin1_s.encode('utf-8');
297 def rfc2047_encode(s):
298 """Encodes a (header) string per RFC2047 if necessary. If the
299 string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
301 encodings.ascii.Codec().decode(s);
306 encodings.utf_8.Codec().decode(s);
307 h = email.Header.Header(s, 'utf-8', 998);
310 h = email.Header.Header(s, 'iso-8859-1', 998);
313 ################################################################################
315 # <Culus> 'The standard sucks, but my tool is supposed to interoperate
316 # with it. I know - I'll fix the suckage and make things
319 def fix_maintainer (maintainer):
320 """Parses a Maintainer or Changed-By field and returns:
321 (1) an RFC822 compatible version,
322 (2) an RFC2047 compatible version,
326 The name is forced to UTF-8 for both (1) and (3). If the name field
327 contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
328 switched to 'email (name)' format."""
329 maintainer = maintainer.strip()
331 return ('', '', '', '');
333 if maintainer.find("<") == -1:
336 elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
337 email = maintainer[1:-1];
340 m = re_parse_maintainer.match(maintainer);
342 raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
346 # Get an RFC2047 compliant version of the name
347 rfc2047_name = rfc2047_encode(name);
349 # Force the name to be UTF-8
350 name = force_to_utf8(name);
352 if name.find(',') != -1 or name.find('.') != -1:
353 rfc822_maint = "%s (%s)" % (email, name);
354 rfc2047_maint = "%s (%s)" % (email, rfc2047_name);
356 rfc822_maint = "%s <%s>" % (name, email);
357 rfc2047_maint = "%s <%s>" % (rfc2047_name, email);
359 if email.find("@") == -1 and email.find("buildd_") != 0:
360 raise ParseMaintError, "No @ found in email address part."
362 return (rfc822_maint, rfc2047_maint, name, email);
364 ################################################################################
366 # sendmail wrapper, takes _either_ a message string or a file as arguments
367 def send_mail (message, filename=""):
368 # If we've been passed a string dump it into a temporary file
370 filename = tempfile.mktemp();
371 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700);
372 os.write (fd, message);
376 (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename));
378 raise sendmail_failed_exc, output;
380 # Clean up any temporary files
382 os.unlink (filename);
384 ################################################################################
386 def poolify (source, component):
389 # FIXME: this is nasty
390 component = component.lower().replace("non-us/", "non-US/");
391 if source[:3] == "lib":
392 return component + source[:4] + '/' + source + '/'
394 return component + source[:1] + '/' + source + '/'
396 ################################################################################
398 def move (src, dest, overwrite = 0, perms = 0664):
399 if os.path.exists(dest) and os.path.isdir(dest):
402 dest_dir = os.path.dirname(dest);
403 if not os.path.exists(dest_dir):
404 umask = os.umask(00000);
405 os.makedirs(dest_dir, 02775);
407 #print "Moving %s to %s..." % (src, dest);
408 if os.path.exists(dest) and os.path.isdir(dest):
409 dest += '/' + os.path.basename(src);
410 # Don't overwrite unless forced to
411 if os.path.exists(dest):
413 fubar("Can't move %s to %s - file already exists." % (src, dest));
415 if not os.access(dest, os.W_OK):
416 fubar("Can't move %s to %s - can't write to existing file." % (src, dest));
417 shutil.copy2(src, dest);
418 os.chmod(dest, perms);
421 def copy (src, dest, overwrite = 0, perms = 0664):
422 if os.path.exists(dest) and os.path.isdir(dest):
425 dest_dir = os.path.dirname(dest);
426 if not os.path.exists(dest_dir):
427 umask = os.umask(00000);
428 os.makedirs(dest_dir, 02775);
430 #print "Copying %s to %s..." % (src, dest);
431 if os.path.exists(dest) and os.path.isdir(dest):
432 dest += '/' + os.path.basename(src);
433 # Don't overwrite unless forced to
434 if os.path.exists(dest):
436 raise file_exists_exc
438 if not os.access(dest, os.W_OK):
439 raise cant_overwrite_exc
440 shutil.copy2(src, dest);
441 os.chmod(dest, perms);
443 ################################################################################
446 res = socket.gethostbyaddr(socket.gethostname());
447 database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname");
448 if database_hostname:
449 return database_hostname;
453 def which_conf_file ():
454 res = socket.gethostbyaddr(socket.gethostname());
455 if Cnf.get("Config::" + res[0] + "::KatieConfig"):
456 return Cnf["Config::" + res[0] + "::KatieConfig"]
458 return default_config;
460 def which_apt_conf_file ():
461 res = socket.gethostbyaddr(socket.gethostname());
462 if Cnf.get("Config::" + res[0] + "::AptConfig"):
463 return Cnf["Config::" + res[0] + "::AptConfig"]
465 return default_apt_config;
467 ################################################################################
469 # Escape characters which have meaning to SQL's regex comparison operator ('~')
470 # (woefully incomplete)
473 s = s.replace('+', '\\\\+');
474 s = s.replace('.', '\\\\.');
477 ################################################################################
479 # Perform a substition of template
480 def TemplateSubst(map, filename):
481 file = open_file(filename);
482 template = file.read();
484 template = template.replace(x,map[x]);
488 ################################################################################
490 def fubar(msg, exit_code=1):
491 sys.stderr.write("E: %s\n" % (msg));
495 sys.stderr.write("W: %s\n" % (msg));
497 ################################################################################
499 # Returns the user name with a laughable attempt at rfc822 conformancy
500 # (read: removing stray periods).
502 return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '');
504 ################################################################################
514 return ("%d%s" % (c, t))
516 ################################################################################
518 def cc_fix_changes (changes):
519 o = changes.get("architecture", "");
521 del changes["architecture"];
522 changes["architecture"] = {};
524 changes["architecture"][j] = 1;
526 # Sort by source name, source version, 'have source', and then by filename
527 def changes_compare (a, b):
529 a_changes = parse_changes(a);
534 b_changes = parse_changes(b);
538 cc_fix_changes (a_changes);
539 cc_fix_changes (b_changes);
541 # Sort by source name
542 a_source = a_changes.get("source");
543 b_source = b_changes.get("source");
544 q = cmp (a_source, b_source);
548 # Sort by source version
549 a_version = a_changes.get("version", "0");
550 b_version = b_changes.get("version", "0");
551 q = apt_pkg.VersionCompare(a_version, b_version);
555 # Sort by 'have source'
556 a_has_source = a_changes["architecture"].get("source");
557 b_has_source = b_changes["architecture"].get("source");
558 if a_has_source and not b_has_source:
560 elif b_has_source and not a_has_source:
563 # Fall back to sort by filename
566 ################################################################################
568 def find_next_free (dest, too_many=100):
571 while os.path.exists(dest) and extra < too_many:
572 dest = orig_dest + '.' + repr(extra);
574 if extra >= too_many:
575 raise tried_too_hard_exc;
578 ################################################################################
580 def result_join (original, sep = '\t'):
582 for i in xrange(len(original)):
583 if original[i] == None:
586 list.append(original[i]);
587 return sep.join(list);
589 ################################################################################
591 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
593 for line in str.split('\n'):
595 if line or include_blank_lines:
596 out += "%s%s\n" % (prefix, line);
597 # Strip trailing new line
602 ################################################################################
604 def validate_changes_file_arg(filename, require_changes=1):
605 """'filename' is either a .changes or .katie file. If 'filename' is a
606 .katie file, it's changed to be the corresponding .changes file. The
607 function then checks if the .changes file a) exists and b) is
608 readable and returns the .changes filename if so. If there's a
609 problem, the next action depends on the option 'require_changes'
612 o If 'require_changes' == -1, errors are ignored and the .changes
613 filename is returned.
614 o If 'require_changes' == 0, a warning is given and 'None' is returned.
615 o If 'require_changes' == 1, a fatal error is raised.
619 orig_filename = filename
620 if filename.endswith(".katie"):
621 filename = filename[:-6]+".changes";
623 if not filename.endswith(".changes"):
624 error = "invalid file type; not a changes file";
626 if not os.access(filename,os.R_OK):
627 if os.path.exists(filename):
628 error = "permission denied";
630 error = "file not found";
633 if require_changes == 1:
634 fubar("%s: %s." % (orig_filename, error));
635 elif require_changes == 0:
636 warn("Skipping %s - %s" % (orig_filename, error));
638 else: # We only care about the .katie file
643 ################################################################################
646 return (arch != "source" and arch != "all");
648 ################################################################################
650 def join_with_commas_and(list):
651 if len(list) == 0: return "nothing";
652 if len(list) == 1: return list[0];
653 return ", ".join(list[:-1]) + " and " + list[-1];
655 ################################################################################
660 (pkg, version, constraint) = atom;
662 pp_dep = "%s (%s %s)" % (pkg, constraint, version);
665 pp_deps.append(pp_dep);
666 return " |".join(pp_deps);
668 ################################################################################
673 ################################################################################
675 # Handle -a, -c and -s arguments; returns them as SQL constraints
676 def parse_args(Options):
680 for suite in split_args(Options["Suite"]):
681 suite_id = db_access.get_suite_id(suite);
683 warn("suite '%s' not recognised." % (suite));
685 suite_ids_list.append(suite_id);
687 con_suites = "AND su.id IN (%s)" % ", ".join(map(str, suite_ids_list));
689 fubar("No valid suite given.");
694 if Options["Component"]:
695 component_ids_list = [];
696 for component in split_args(Options["Component"]):
697 component_id = db_access.get_component_id(component);
698 if component_id == -1:
699 warn("component '%s' not recognised." % (component));
701 component_ids_list.append(component_id);
702 if component_ids_list:
703 con_components = "AND c.id IN (%s)" % ", ".join(map(str, component_ids_list));
705 fubar("No valid component given.");
709 # Process architecture
710 con_architectures = "";
711 if Options["Architecture"]:
714 for architecture in split_args(Options["Architecture"]):
715 if architecture == "source":
718 architecture_id = db_access.get_architecture_id(architecture);
719 if architecture_id == -1:
720 warn("architecture '%s' not recognised." % (architecture));
722 arch_ids_list.append(architecture_id);
724 con_architectures = "AND a.id IN (%s)" % ", ".join(map(str, arch_ids_list));
727 fubar("No valid architecture given.");
731 return (con_suites, con_architectures, con_components, check_source);
733 ################################################################################
735 # Inspired(tm) by Bryn Keller's print_exc_plus (See
736 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
739 tb = sys.exc_info()[2];
746 frame = frame.f_back;
748 traceback.print_exc();
750 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
751 frame.f_code.co_filename,
753 for key, value in frame.f_locals.items():
754 print "\t%20s = " % key,;
758 print "<unable to print>";
760 ################################################################################
762 def try_with_debug(function):
770 ################################################################################
772 # Function for use in sorting lists of architectures.
773 # Sorts normally except that 'source' dominates all others.
775 def arch_compare_sw (a, b):
776 if a == "source" and b == "source":
785 ################################################################################
787 # Split command line arguments which can be separated by either commas
788 # or whitespace. If dwim is set, it will complain about string ending
789 # in comma since this usually means someone did 'madison -a i386, m68k
790 # foo' or something and the inevitable confusion resulting from 'm68k'
791 # being treated as an argument is undesirable.
793 def split_args (s, dwim=1):
794 if s.find(",") == -1:
797 if s[-1:] == "," and dwim:
798 fubar("split_args: found trailing comma, spurious space maybe?");
801 ################################################################################
803 def Dict(**dict): return dict
805 ########################################
807 # Our very own version of commands.getouputstatus(), hacked to support
809 def gpgv_get_status_output(cmd, status_read, status_write):
810 cmd = ['/bin/sh', '-c', cmd];
811 p2cread, p2cwrite = os.pipe();
812 c2pread, c2pwrite = os.pipe();
813 errout, errin = os.pipe();
823 for i in range(3, 256):
824 if i != status_write:
830 os.execvp(cmd[0], cmd);
836 os.dup2(c2pread, c2pwrite);
837 os.dup2(errout, errin);
839 output = status = "";
841 i, o, e = select.select([c2pwrite, errin, status_read], [], []);
844 r = os.read(fd, 8196);
846 more_data.append(fd);
847 if fd == c2pwrite or fd == errin:
849 elif fd == status_read:
852 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd));
854 pid, exit_status = os.waitpid(pid, 0)
856 os.close(status_write);
857 os.close(status_read);
867 return output, status, exit_status;
869 ############################################################
872 def check_signature (sig_filename, reject, data_filename="", keyrings=None):
873 """Check the signature of a file and return the fingerprint if the
874 signature is valid or 'None' if it's not. The first argument is the
875 filename whose signature should be checked. The second argument is a
876 reject function and is called when an error is found. The reject()
877 function must allow for two arguments: the first is the error message,
878 the second is an optional prefix string. It's possible for reject()
879 to be called more than once during an invocation of check_signature().
880 The third argument is optional and is the name of the files the
881 detached signature applies to. The fourth argument is optional and is
882 a *list* of keyrings to use.
885 # Ensure the filename contains no shell meta-characters or other badness
886 if not re_taint_free.match(sig_filename):
887 reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename));
890 if data_filename and not re_taint_free.match(data_filename):
891 reject("!!WARNING!! tainted data filename: '%s'." % (data_filename));
895 keyrings = (Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"])
897 # Build the command line
898 status_read, status_write = os.pipe();
899 cmd = "gpgv --status-fd %s" % (status_write);
900 for keyring in keyrings:
901 cmd += " --keyring %s" % (keyring);
902 cmd += " %s %s" % (sig_filename, data_filename);
903 # Invoke gpgv on the file
904 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write);
906 # Process the status-fd output
908 bad = internal_error = "";
909 for line in status.split('\n'):
913 split = line.split();
915 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line);
917 (gnupg, keyword) = split[:2];
918 if gnupg != "[GNUPG:]":
919 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg);
922 if keywords.has_key(keyword) and (keyword != "NODATA" and keyword != "SIGEXPIRED"):
923 internal_error += "found duplicate status token ('%s').\n" % (keyword);
926 keywords[keyword] = args;
928 # If we failed to parse the status-fd output, let's just whine and bail now
930 reject("internal error while performing signature check on %s." % (sig_filename));
931 reject(internal_error, "");
932 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "");
935 # Now check for obviously bad things in the processed output
936 if keywords.has_key("SIGEXPIRED"):
937 reject("The key used to sign %s has expired." % (sig_filename));
939 if keywords.has_key("KEYREVOKED"):
940 reject("The key used to sign %s has been revoked." % (sig_filename));
942 if keywords.has_key("BADSIG"):
943 reject("bad signature on %s." % (sig_filename));
945 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
946 reject("failed to check signature on %s." % (sig_filename));
948 if keywords.has_key("NO_PUBKEY"):
949 args = keywords["NO_PUBKEY"];
952 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename));
954 if keywords.has_key("BADARMOR"):
955 reject("ASCII armour of signature was corrupt in %s." % (sig_filename));
957 if keywords.has_key("NODATA"):
958 reject("no signature found in %s." % (sig_filename));
964 # Next check gpgv exited with a zero return code
966 reject("gpgv failed while checking %s." % (sig_filename));
968 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "");
970 reject(prefix_multi_line_string(output, " [GPG output:] "), "");
973 # Sanity check the good stuff we expect
974 if not keywords.has_key("VALIDSIG"):
975 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename));
978 args = keywords["VALIDSIG"];
980 reject("internal error while checking signature on %s." % (sig_filename));
983 fingerprint = args[0];
984 if not keywords.has_key("GOODSIG"):
985 reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename));
987 if not keywords.has_key("SIG_ID"):
988 reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename));
991 # Finally ensure there's not something we don't recognise
992 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
993 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
996 for keyword in keywords.keys():
997 if not known_keywords.has_key(keyword):
998 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename));
1006 ################################################################################
1008 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1010 def wrap(paragraph, max_length, prefix=""):
1014 words = paragraph.split();
1017 word_size = len(word);
1018 if word_size > max_length:
1020 s += line + '\n' + prefix;
1021 s += word + '\n' + prefix;
1024 new_length = len(line) + word_size + 1;
1025 if new_length > max_length:
1026 s += line + '\n' + prefix;
1039 ################################################################################
1041 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1042 # Returns fixed 'src'
1043 def clean_symlink (src, dest, root):
1044 src = src.replace(root, '', 1);
1045 dest = dest.replace(root, '', 1);
1046 dest = os.path.dirname(dest);
1047 new_src = '../' * len(dest.split('/'));
1048 return new_src + src;
1050 ################################################################################
1052 def temp_filename(directory=None, dotprefix=None, perms=0700):
1053 """Return a secure and unique filename by pre-creating it.
1054 If 'directory' is non-null, it will be the directory the file is pre-created in.
1055 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1058 old_tempdir = tempfile.tempdir;
1059 tempfile.tempdir = directory;
1061 filename = tempfile.mktemp();
1064 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename));
1065 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms);
1069 tempfile.tempdir = old_tempdir;
1073 ################################################################################
1077 Cnf = apt_pkg.newConfiguration();
1078 apt_pkg.ReadConfigFileISC(Cnf,default_config);
1080 if which_conf_file() != default_config:
1081 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file());
1083 ################################################################################