4 # Copyright (C) 2000, 2001, 2002, 2003, 2004, 2005, 2006 James Troup <james@nocrew.org>
5 # $Id: utils.py,v 1.73 2005-03-18 05:24:38 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 codecs, commands, email.Header, os, pwd, re, select, socket, shutil, \
26 string, sys, tempfile, traceback
30 ################################################################################
32 re_comments = re.compile(r"\#.*")
33 re_no_epoch = re.compile(r"^\d+\:")
34 re_no_revision = re.compile(r"-[^-]+$")
35 re_arch_from_filename = re.compile(r"/binary-[^/]+/")
36 re_extract_src_version = re.compile (r"(\S+)\s*\((.*)\)")
37 re_isadeb = re.compile (r"(.+?)_(.+?)_(.+)\.u?deb$")
38 re_issource = re.compile (r"(.+)_(.+?)\.(orig\.tar\.gz|diff\.gz|tar\.gz|dsc)$")
40 re_single_line_field = re.compile(r"^(\S*)\s*:\s*(.*)")
41 re_multi_line_field = re.compile(r"^\s(.*)")
42 re_taint_free = re.compile(r"^[-+~/\.\w]+$")
44 re_parse_maintainer = re.compile(r"^\s*(\S.*\S)\s*\<([^\>]+)\>")
46 changes_parse_error_exc = "Can't parse line in .changes file"
47 invalid_dsc_format_exc = "Invalid .dsc file"
48 nk_format_exc = "Unknown Format: in .changes file"
49 no_files_exc = "No Files: field in .dsc or .changes file."
50 cant_open_exc = "Can't open file"
51 unknown_hostname_exc = "Unknown hostname"
52 cant_overwrite_exc = "Permission denied; can't overwrite existent file."
53 file_exists_exc = "Destination file exists"
54 sendmail_failed_exc = "Sendmail invocation failed"
55 tried_too_hard_exc = "Tried too hard to find a free filename."
57 default_config = "/etc/dak/dak.conf"
58 default_apt_config = "/etc/dak/apt.conf"
60 ################################################################################
62 class Error(Exception):
63 """Base class for exceptions in this module."""
66 class ParseMaintError(Error):
67 """Exception raised for errors in parsing a maintainer field.
70 message -- explanation of the error
73 def __init__(self, message):
75 self.message = message
77 ################################################################################
79 def open_file(filename, mode='r'):
81 f = open(filename, mode)
83 raise cant_open_exc, filename
86 ################################################################################
88 def our_raw_input(prompt=""):
90 sys.stdout.write(prompt)
96 sys.stderr.write("\nUser interrupt (^D).\n")
99 ################################################################################
103 if c not in string.digits:
107 ################################################################################
109 def extract_component_from_section(section):
112 if section.find('/') != -1:
113 component = section.split('/')[0]
114 if component.lower() == "non-us" and section.find('/') != -1:
115 s = component + '/' + section.split('/')[1]
116 if Cnf.has_key("Component::%s" % s): # Avoid e.g. non-US/libs
119 if section.lower() == "non-us":
120 component = "non-US/main"
122 # non-US prefix is case insensitive
123 if component.lower()[:6] == "non-us":
124 component = "non-US"+component[6:]
126 # Expand default component
128 if Cnf.has_key("Component::%s" % section):
132 elif component == "non-US":
133 component = "non-US/main"
135 return (section, component)
137 ################################################################################
139 def parse_changes(filename, signing_rules=0):
140 """Parses a changes file and returns a dictionary where each field is a
141 key. The mandatory first argument is the filename of the .changes
144 signing_rules is an optional argument:
146 o If signing_rules == -1, no signature is required.
147 o If signing_rules == 0 (the default), a signature is required.
148 o If signing_rules == 1, it turns on the same strict format checking
151 The rules for (signing_rules == 1)-mode are:
153 o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
154 followed by any PGP header data and must end with a blank line.
156 o The data section must end with a blank line and must be followed by
157 "-----BEGIN PGP SIGNATURE-----".
163 changes_in = open_file(filename)
164 lines = changes_in.readlines()
167 raise changes_parse_error_exc, "[Empty changes file]"
169 # Reindex by line number so we can easily verify the format of
175 indexed_lines[index] = line[:-1]
179 num_of_lines = len(indexed_lines.keys())
182 while index < num_of_lines:
184 line = indexed_lines[index]
186 if signing_rules == 1:
188 if index > num_of_lines:
189 raise invalid_dsc_format_exc, index
190 line = indexed_lines[index]
191 if not line.startswith("-----BEGIN PGP SIGNATURE"):
192 raise invalid_dsc_format_exc, index
197 if line.startswith("-----BEGIN PGP SIGNATURE"):
199 if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
201 if signing_rules == 1:
202 while index < num_of_lines and line != "":
204 line = indexed_lines[index]
206 # If we're not inside the signed data, don't process anything
207 if signing_rules >= 0 and not inside_signature:
209 slf = re_single_line_field.match(line)
211 field = slf.groups()[0].lower()
212 changes[field] = slf.groups()[1]
216 changes[field] += '\n'
218 mlf = re_multi_line_field.match(line)
221 raise changes_parse_error_exc, "'%s'\n [Multi-line field continuing on from nothing?]" % (line)
222 if first == 1 and changes[field] != "":
223 changes[field] += '\n'
225 changes[field] += mlf.groups()[0] + '\n'
229 if signing_rules == 1 and inside_signature:
230 raise invalid_dsc_format_exc, index
233 changes["filecontents"] = "".join(lines)
236 raise changes_parse_error_exc, error
240 ################################################################################
242 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
244 def build_file_list(changes, is_a_dsc=0):
247 # Make sure we have a Files: field to parse...
248 if not changes.has_key("files"):
251 # Make sure we recognise the format of the Files: field
252 format = changes.get("format", "")
254 format = float(format)
255 if not is_a_dsc and (format < 1.5 or format > 2.0):
256 raise nk_format_exc, format
258 # Parse each entry/line:
259 for i in changes["files"].split('\n'):
263 section = priority = ""
266 (md5, size, name) = s
268 (md5, size, section, priority, name) = s
270 raise changes_parse_error_exc, i
277 (section, component) = extract_component_from_section(section)
279 files[name] = Dict(md5sum=md5, size=size, section=section,
280 priority=priority, component=component)
284 ################################################################################
286 def force_to_utf8(s):
287 """Forces a string to UTF-8. If the string isn't already UTF-8,
288 it's assumed to be ISO-8859-1."""
293 latin1_s = unicode(s,'iso8859-1')
294 return latin1_s.encode('utf-8')
296 def rfc2047_encode(s):
297 """Encodes a (header) string per RFC2047 if necessary. If the
298 string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
300 codecs.lookup('ascii')[1](s)
305 codecs.lookup('utf-8')[1](s)
306 h = email.Header.Header(s, 'utf-8', 998)
309 h = email.Header.Header(s, 'iso-8859-1', 998)
312 ################################################################################
314 # <Culus> 'The standard sucks, but my tool is supposed to interoperate
315 # with it. I know - I'll fix the suckage and make things
318 def fix_maintainer (maintainer):
319 """Parses a Maintainer or Changed-By field and returns:
320 (1) an RFC822 compatible version,
321 (2) an RFC2047 compatible version,
325 The name is forced to UTF-8 for both (1) and (3). If the name field
326 contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
327 switched to 'email (name)' format."""
328 maintainer = maintainer.strip()
330 return ('', '', '', '')
332 if maintainer.find("<") == -1:
335 elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
336 email = maintainer[1:-1]
339 m = re_parse_maintainer.match(maintainer)
341 raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
345 # Get an RFC2047 compliant version of the name
346 rfc2047_name = rfc2047_encode(name)
348 # Force the name to be UTF-8
349 name = force_to_utf8(name)
351 if name.find(',') != -1 or name.find('.') != -1:
352 rfc822_maint = "%s (%s)" % (email, name)
353 rfc2047_maint = "%s (%s)" % (email, rfc2047_name)
355 rfc822_maint = "%s <%s>" % (name, email)
356 rfc2047_maint = "%s <%s>" % (rfc2047_name, email)
358 if email.find("@") == -1 and email.find("buildd_") != 0:
359 raise ParseMaintError, "No @ found in email address part."
361 return (rfc822_maint, rfc2047_maint, name, email)
363 ################################################################################
365 # sendmail wrapper, takes _either_ a message string or a file as arguments
366 def send_mail (message, filename=""):
367 # If we've been passed a string dump it into a temporary file
369 filename = tempfile.mktemp()
370 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
371 os.write (fd, message)
375 (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename))
377 raise sendmail_failed_exc, output
379 # Clean up any temporary files
383 ################################################################################
385 def poolify (source, component):
388 # FIXME: this is nasty
389 component = component.lower().replace("non-us/", "non-US/")
390 if source[:3] == "lib":
391 return component + source[:4] + '/' + source + '/'
393 return component + source[:1] + '/' + source + '/'
395 ################################################################################
397 def move (src, dest, overwrite = 0, perms = 0664):
398 if os.path.exists(dest) and os.path.isdir(dest):
401 dest_dir = os.path.dirname(dest)
402 if not os.path.exists(dest_dir):
403 umask = os.umask(00000)
404 os.makedirs(dest_dir, 02775)
406 #print "Moving %s to %s..." % (src, dest)
407 if os.path.exists(dest) and os.path.isdir(dest):
408 dest += '/' + os.path.basename(src)
409 # Don't overwrite unless forced to
410 if os.path.exists(dest):
412 fubar("Can't move %s to %s - file already exists." % (src, dest))
414 if not os.access(dest, os.W_OK):
415 fubar("Can't move %s to %s - can't write to existing file." % (src, dest))
416 shutil.copy2(src, dest)
417 os.chmod(dest, perms)
420 def copy (src, dest, overwrite = 0, perms = 0664):
421 if os.path.exists(dest) and os.path.isdir(dest):
424 dest_dir = os.path.dirname(dest)
425 if not os.path.exists(dest_dir):
426 umask = os.umask(00000)
427 os.makedirs(dest_dir, 02775)
429 #print "Copying %s to %s..." % (src, dest)
430 if os.path.exists(dest) and os.path.isdir(dest):
431 dest += '/' + os.path.basename(src)
432 # Don't overwrite unless forced to
433 if os.path.exists(dest):
435 raise file_exists_exc
437 if not os.access(dest, os.W_OK):
438 raise cant_overwrite_exc
439 shutil.copy2(src, dest)
440 os.chmod(dest, perms)
442 ################################################################################
445 res = socket.gethostbyaddr(socket.gethostname())
446 database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname")
447 if database_hostname:
448 return database_hostname
452 def which_conf_file ():
453 res = socket.gethostbyaddr(socket.gethostname())
454 if Cnf.get("Config::" + res[0] + "::DakConfig"):
455 return Cnf["Config::" + res[0] + "::DakConfig"]
457 return default_config
459 def which_apt_conf_file ():
460 res = socket.gethostbyaddr(socket.gethostname())
461 if Cnf.get("Config::" + res[0] + "::AptConfig"):
462 return Cnf["Config::" + res[0] + "::AptConfig"]
464 return default_apt_config
466 ################################################################################
468 # Escape characters which have meaning to SQL's regex comparison operator ('~')
469 # (woefully incomplete)
472 s = s.replace('+', '\\\\+')
473 s = s.replace('.', '\\\\.')
476 ################################################################################
478 # Perform a substition of template
479 def TemplateSubst(map, filename):
480 file = open_file(filename)
481 template = file.read()
483 template = template.replace(x,map[x])
487 ################################################################################
489 def fubar(msg, exit_code=1):
490 sys.stderr.write("E: %s\n" % (msg))
494 sys.stderr.write("W: %s\n" % (msg))
496 ################################################################################
498 # Returns the user name with a laughable attempt at rfc822 conformancy
499 # (read: removing stray periods).
501 return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '')
503 ################################################################################
513 return ("%d%s" % (c, t))
515 ################################################################################
517 def cc_fix_changes (changes):
518 o = changes.get("architecture", "")
520 del changes["architecture"]
521 changes["architecture"] = {}
523 changes["architecture"][j] = 1
525 # Sort by source name, source version, 'have source', and then by filename
526 def changes_compare (a, b):
528 a_changes = parse_changes(a)
533 b_changes = parse_changes(b)
537 cc_fix_changes (a_changes)
538 cc_fix_changes (b_changes)
540 # Sort by source name
541 a_source = a_changes.get("source")
542 b_source = b_changes.get("source")
543 q = cmp (a_source, b_source)
547 # Sort by source version
548 a_version = a_changes.get("version", "0")
549 b_version = b_changes.get("version", "0")
550 q = apt_pkg.VersionCompare(a_version, b_version)
554 # Sort by 'have source'
555 a_has_source = a_changes["architecture"].get("source")
556 b_has_source = b_changes["architecture"].get("source")
557 if a_has_source and not b_has_source:
559 elif b_has_source and not a_has_source:
562 # Fall back to sort by filename
565 ################################################################################
567 def find_next_free (dest, too_many=100):
570 while os.path.exists(dest) and extra < too_many:
571 dest = orig_dest + '.' + repr(extra)
573 if extra >= too_many:
574 raise tried_too_hard_exc
577 ################################################################################
579 def result_join (original, sep = '\t'):
581 for i in xrange(len(original)):
582 if original[i] == None:
585 list.append(original[i])
586 return sep.join(list)
588 ################################################################################
590 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
592 for line in str.split('\n'):
594 if line or include_blank_lines:
595 out += "%s%s\n" % (prefix, line)
596 # Strip trailing new line
601 ################################################################################
603 def validate_changes_file_arg(filename, require_changes=1):
604 """'filename' is either a .changes or .dak file. If 'filename' is a
605 .dak file, it's changed to be the corresponding .changes file. The
606 function then checks if the .changes file a) exists and b) is
607 readable and returns the .changes filename if so. If there's a
608 problem, the next action depends on the option 'require_changes'
611 o If 'require_changes' == -1, errors are ignored and the .changes
612 filename is returned.
613 o If 'require_changes' == 0, a warning is given and 'None' is returned.
614 o If 'require_changes' == 1, a fatal error is raised.
618 orig_filename = filename
619 if filename.endswith(".dak"):
620 filename = filename[:-6]+".changes"
622 if not filename.endswith(".changes"):
623 error = "invalid file type; not a changes file"
625 if not os.access(filename,os.R_OK):
626 if os.path.exists(filename):
627 error = "permission denied"
629 error = "file not found"
632 if require_changes == 1:
633 fubar("%s: %s." % (orig_filename, error))
634 elif require_changes == 0:
635 warn("Skipping %s - %s" % (orig_filename, error))
637 else: # We only care about the .dak file
642 ################################################################################
645 return (arch != "source" and arch != "all")
647 ################################################################################
649 def join_with_commas_and(list):
650 if len(list) == 0: return "nothing"
651 if len(list) == 1: return list[0]
652 return ", ".join(list[:-1]) + " and " + list[-1]
654 ################################################################################
659 (pkg, version, constraint) = atom
661 pp_dep = "%s (%s %s)" % (pkg, constraint, version)
664 pp_deps.append(pp_dep)
665 return " |".join(pp_deps)
667 ################################################################################
672 ################################################################################
674 # Handle -a, -c and -s arguments; returns them as SQL constraints
675 def parse_args(Options):
679 for suite in split_args(Options["Suite"]):
680 suite_id = dak.lib.database.get_suite_id(suite)
682 warn("suite '%s' not recognised." % (suite))
684 suite_ids_list.append(suite_id)
686 con_suites = "AND su.id IN (%s)" % ", ".join(map(str, suite_ids_list))
688 fubar("No valid suite given.")
693 if Options["Component"]:
694 component_ids_list = []
695 for component in split_args(Options["Component"]):
696 component_id = dak.lib.database.get_component_id(component)
697 if component_id == -1:
698 warn("component '%s' not recognised." % (component))
700 component_ids_list.append(component_id)
701 if component_ids_list:
702 con_components = "AND c.id IN (%s)" % ", ".join(map(str, component_ids_list))
704 fubar("No valid component given.")
708 # Process architecture
709 con_architectures = ""
710 if Options["Architecture"]:
713 for architecture in split_args(Options["Architecture"]):
714 if architecture == "source":
717 architecture_id = dak.lib.database.get_architecture_id(architecture)
718 if architecture_id == -1:
719 warn("architecture '%s' not recognised." % (architecture))
721 arch_ids_list.append(architecture_id)
723 con_architectures = "AND a.id IN (%s)" % ", ".join(map(str, arch_ids_list))
726 fubar("No valid architecture given.")
730 return (con_suites, con_architectures, con_components, check_source)
732 ################################################################################
734 # Inspired(tm) by Bryn Keller's print_exc_plus (See
735 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
738 tb = sys.exc_info()[2]
747 traceback.print_exc()
749 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
750 frame.f_code.co_filename,
752 for key, value in frame.f_locals.items():
753 print "\t%20s = " % key,
757 print "<unable to print>"
759 ################################################################################
761 def try_with_debug(function):
769 ################################################################################
771 # Function for use in sorting lists of architectures.
772 # Sorts normally except that 'source' dominates all others.
774 def arch_compare_sw (a, b):
775 if a == "source" and b == "source":
784 ################################################################################
786 # Split command line arguments which can be separated by either commas
787 # or whitespace. If dwim is set, it will complain about string ending
788 # in comma since this usually means someone did 'dak ls -a i386, m68k
789 # foo' or something and the inevitable confusion resulting from 'm68k'
790 # being treated as an argument is undesirable.
792 def split_args (s, dwim=1):
793 if s.find(",") == -1:
796 if s[-1:] == "," and dwim:
797 fubar("split_args: found trailing comma, spurious space maybe?")
800 ################################################################################
802 def Dict(**dict): return dict
804 ########################################
806 # Our very own version of commands.getouputstatus(), hacked to support
808 def gpgv_get_status_output(cmd, status_read, status_write):
809 cmd = ['/bin/sh', '-c', cmd]
810 p2cread, p2cwrite = os.pipe()
811 c2pread, c2pwrite = os.pipe()
812 errout, errin = os.pipe()
822 for i in range(3, 256):
823 if i != status_write:
829 os.execvp(cmd[0], cmd)
835 os.dup2(c2pread, c2pwrite)
836 os.dup2(errout, errin)
840 i, o, e = select.select([c2pwrite, errin, status_read], [], [])
843 r = os.read(fd, 8196)
846 if fd == c2pwrite or fd == errin:
848 elif fd == status_read:
851 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd))
853 pid, exit_status = os.waitpid(pid, 0)
855 os.close(status_write)
856 os.close(status_read)
866 return output, status, exit_status
868 ############################################################
871 def check_signature (sig_filename, reject, data_filename="", keyrings=None):
872 """Check the signature of a file and return the fingerprint if the
873 signature is valid or 'None' if it's not. The first argument is the
874 filename whose signature should be checked. The second argument is a
875 reject function and is called when an error is found. The reject()
876 function must allow for two arguments: the first is the error message,
877 the second is an optional prefix string. It's possible for reject()
878 to be called more than once during an invocation of check_signature().
879 The third argument is optional and is the name of the files the
880 detached signature applies to. The fourth argument is optional and is
881 a *list* of keyrings to use.
884 # Ensure the filename contains no shell meta-characters or other badness
885 if not re_taint_free.match(sig_filename):
886 reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename))
889 if data_filename and not re_taint_free.match(data_filename):
890 reject("!!WARNING!! tainted data filename: '%s'." % (data_filename))
894 keyrings = (Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"])
896 # Build the command line
897 status_read, status_write = os.pipe();
898 cmd = "gpgv --status-fd %s" % (status_write)
899 for keyring in keyrings:
900 cmd += " --keyring %s" % (keyring)
901 cmd += " %s %s" % (sig_filename, data_filename)
902 # Invoke gpgv on the file
903 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write)
905 # Process the status-fd output
907 bad = internal_error = ""
908 for line in status.split('\n'):
914 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line)
916 (gnupg, keyword) = split[:2]
917 if gnupg != "[GNUPG:]":
918 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg)
921 if keywords.has_key(keyword) and (keyword != "NODATA" and keyword != "SIGEXPIRED"):
922 internal_error += "found duplicate status token ('%s').\n" % (keyword)
925 keywords[keyword] = args
927 # If we failed to parse the status-fd output, let's just whine and bail now
929 reject("internal error while performing signature check on %s." % (sig_filename))
930 reject(internal_error, "")
931 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "")
934 # Now check for obviously bad things in the processed output
935 if keywords.has_key("SIGEXPIRED"):
936 reject("The key used to sign %s has expired." % (sig_filename))
938 if keywords.has_key("KEYREVOKED"):
939 reject("The key used to sign %s has been revoked." % (sig_filename))
941 if keywords.has_key("BADSIG"):
942 reject("bad signature on %s." % (sig_filename))
944 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
945 reject("failed to check signature on %s." % (sig_filename))
947 if keywords.has_key("NO_PUBKEY"):
948 args = keywords["NO_PUBKEY"]
951 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename))
953 if keywords.has_key("BADARMOR"):
954 reject("ASCII armour of signature was corrupt in %s." % (sig_filename))
956 if keywords.has_key("NODATA"):
957 reject("no signature found in %s." % (sig_filename))
963 # Next check gpgv exited with a zero return code
965 reject("gpgv failed while checking %s." % (sig_filename))
967 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "")
969 reject(prefix_multi_line_string(output, " [GPG output:] "), "")
972 # Sanity check the good stuff we expect
973 if not keywords.has_key("VALIDSIG"):
974 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename))
977 args = keywords["VALIDSIG"]
979 reject("internal error while checking signature on %s." % (sig_filename))
982 fingerprint = args[0]
983 if not keywords.has_key("GOODSIG"):
984 reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename))
986 if not keywords.has_key("SIG_ID"):
987 reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename))
990 # Finally ensure there's not something we don't recognise
991 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
992 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
995 for keyword in keywords.keys():
996 if not known_keywords.has_key(keyword):
997 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename))
1005 ################################################################################
1007 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
1009 def wrap(paragraph, max_length, prefix=""):
1013 words = paragraph.split()
1016 word_size = len(word)
1017 if word_size > max_length:
1019 s += line + '\n' + prefix
1020 s += word + '\n' + prefix
1023 new_length = len(line) + word_size + 1
1024 if new_length > max_length:
1025 s += line + '\n' + prefix
1038 ################################################################################
1040 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1041 # Returns fixed 'src'
1042 def clean_symlink (src, dest, root):
1043 src = src.replace(root, '', 1)
1044 dest = dest.replace(root, '', 1)
1045 dest = os.path.dirname(dest)
1046 new_src = '../' * len(dest.split('/'))
1047 return new_src + src
1049 ################################################################################
1051 def temp_filename(directory=None, dotprefix=None, perms=0700):
1052 """Return a secure and unique filename by pre-creating it.
1053 If 'directory' is non-null, it will be the directory the file is pre-created in.
1054 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1057 old_tempdir = tempfile.tempdir
1058 tempfile.tempdir = directory
1060 filename = tempfile.mktemp()
1063 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename))
1064 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms)
1068 tempfile.tempdir = old_tempdir
1072 ################################################################################
1076 Cnf = apt_pkg.newConfiguration()
1077 apt_pkg.ReadConfigFileISC(Cnf,default_config)
1079 if which_conf_file() != default_config:
1080 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file())
1082 ################################################################################