4 # Copyright (C) 2000, 2001, 2002, 2003, 2004 James Troup <james@nocrew.org>
5 # $Id: utils.py,v 1.70 2004-11-27 13:32:16 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 read 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");
550 b_version = b_changes.get("version");
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(file, fatal=1):
608 if file.endswith(".katie"):
609 file = file[:-6]+".changes";
611 if not file.endswith(".changes"):
612 error = "invalid file type; not a changes file";
614 if not os.access(file,os.R_OK):
615 if os.path.exists(file):
616 error = "permission denied";
618 error = "file not found";
622 fubar("%s: %s." % (orig_filename, error));
624 warn("Skipping %s - %s" % (orig_filename, error));
629 ################################################################################
632 return (arch != "source" and arch != "all");
634 ################################################################################
636 def join_with_commas_and(list):
637 if len(list) == 0: return "nothing";
638 if len(list) == 1: return list[0];
639 return ", ".join(list[:-1]) + " and " + list[-1];
641 ################################################################################
646 (pkg, version, constraint) = atom;
648 pp_dep = "%s (%s %s)" % (pkg, constraint, version);
651 pp_deps.append(pp_dep);
652 return " |".join(pp_deps);
654 ################################################################################
659 ################################################################################
661 # Handle -a, -c and -s arguments; returns them as SQL constraints
662 def parse_args(Options):
666 for suite in split_args(Options["Suite"]):
667 suite_id = db_access.get_suite_id(suite);
669 warn("suite '%s' not recognised." % (suite));
671 suite_ids_list.append(suite_id);
673 con_suites = "AND su.id IN (%s)" % ", ".join(map(str, suite_ids_list));
675 fubar("No valid suite given.");
680 if Options["Component"]:
681 component_ids_list = [];
682 for component in split_args(Options["Component"]):
683 component_id = db_access.get_component_id(component);
684 if component_id == -1:
685 warn("component '%s' not recognised." % (component));
687 component_ids_list.append(component_id);
688 if component_ids_list:
689 con_components = "AND c.id IN (%s)" % ", ".join(map(str, component_ids_list));
691 fubar("No valid component given.");
695 # Process architecture
696 con_architectures = "";
697 if Options["Architecture"]:
700 for architecture in split_args(Options["Architecture"]):
701 if architecture == "source":
704 architecture_id = db_access.get_architecture_id(architecture);
705 if architecture_id == -1:
706 warn("architecture '%s' not recognised." % (architecture));
708 arch_ids_list.append(architecture_id);
710 con_architectures = "AND a.id IN (%s)" % ", ".join(map(str, arch_ids_list));
713 fubar("No valid architecture given.");
717 return (con_suites, con_architectures, con_components, check_source);
719 ################################################################################
721 # Inspired(tm) by Bryn Keller's print_exc_plus (See
722 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
725 tb = sys.exc_info()[2];
732 frame = frame.f_back;
734 traceback.print_exc();
736 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
737 frame.f_code.co_filename,
739 for key, value in frame.f_locals.items():
740 print "\t%20s = " % key,;
744 print "<unable to print>";
746 ################################################################################
748 def try_with_debug(function):
756 ################################################################################
758 # Function for use in sorting lists of architectures.
759 # Sorts normally except that 'source' dominates all others.
761 def arch_compare_sw (a, b):
762 if a == "source" and b == "source":
771 ################################################################################
773 # Split command line arguments which can be separated by either commas
774 # or whitespace. If dwim is set, it will complain about string ending
775 # in comma since this usually means someone did 'madison -a i386, m68k
776 # foo' or something and the inevitable confusion resulting from 'm68k'
777 # being treated as an argument is undesirable.
779 def split_args (s, dwim=1):
780 if s.find(",") == -1:
783 if s[-1:] == "," and dwim:
784 fubar("split_args: found trailing comma, spurious space maybe?");
787 ################################################################################
789 def Dict(**dict): return dict
791 ########################################
793 # Our very own version of commands.getouputstatus(), hacked to support
795 def gpgv_get_status_output(cmd, status_read, status_write):
796 cmd = ['/bin/sh', '-c', cmd];
797 p2cread, p2cwrite = os.pipe();
798 c2pread, c2pwrite = os.pipe();
799 errout, errin = os.pipe();
809 for i in range(3, 256):
810 if i != status_write:
816 os.execvp(cmd[0], cmd);
822 os.dup2(c2pread, c2pwrite);
823 os.dup2(errout, errin);
825 output = status = "";
827 i, o, e = select.select([c2pwrite, errin, status_read], [], []);
830 r = os.read(fd, 8196);
832 more_data.append(fd);
833 if fd == c2pwrite or fd == errin:
835 elif fd == status_read:
838 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd));
840 pid, exit_status = os.waitpid(pid, 0)
842 os.close(status_write);
843 os.close(status_read);
853 return output, status, exit_status;
855 ############################################################
858 def check_signature (sig_filename, reject, data_filename="", keyrings=None):
859 """Check the signature of a file and return the fingerprint if the
860 signature is valid or 'None' if it's not. The first argument is the
861 filename whose signature should be checked. The second argument is a
862 reject function and is called when an error is found. The reject()
863 function must allow for two arguments: the first is the error message,
864 the second is an optional prefix string. It's possible for reject()
865 to be called more than once during an invocation of check_signature().
866 The third argument is optional and is the name of the files the
867 detached signature applies to. The fourth argument is optional and is
868 a *list* of keyrings to use.
871 # Ensure the filename contains no shell meta-characters or other badness
872 if not re_taint_free.match(sig_filename):
873 reject("!!WARNING!! tainted signature filename: '%s'." % (sig_filename));
876 if data_filename and not re_taint_free.match(data_filename):
877 reject("!!WARNING!! tainted data filename: '%s'." % (data_filename));
881 keyrings = (Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"])
883 # Build the command line
884 status_read, status_write = os.pipe();
885 cmd = "gpgv --status-fd %s" % (status_write);
886 for keyring in keyrings:
887 cmd += " --keyring %s" % (keyring);
888 cmd += " %s %s" % (sig_filename, data_filename);
889 # Invoke gpgv on the file
890 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write);
892 # Process the status-fd output
894 bad = internal_error = "";
895 for line in status.split('\n'):
899 split = line.split();
901 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line);
903 (gnupg, keyword) = split[:2];
904 if gnupg != "[GNUPG:]":
905 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg);
908 if keywords.has_key(keyword) and (keyword != "NODATA" and keyword != "SIGEXPIRED"):
909 internal_error += "found duplicate status token ('%s').\n" % (keyword);
912 keywords[keyword] = args;
914 # If we failed to parse the status-fd output, let's just whine and bail now
916 reject("internal error while performing signature check on %s." % (sig_filename));
917 reject(internal_error, "");
918 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "");
921 # Now check for obviously bad things in the processed output
922 if keywords.has_key("SIGEXPIRED"):
923 reject("The key used to sign %s has expired." % (sig_filename));
925 if keywords.has_key("KEYREVOKED"):
926 reject("The key used to sign %s has been revoked." % (sig_filename));
928 if keywords.has_key("BADSIG"):
929 reject("bad signature on %s." % (sig_filename));
931 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
932 reject("failed to check signature on %s." % (sig_filename));
934 if keywords.has_key("NO_PUBKEY"):
935 args = keywords["NO_PUBKEY"];
938 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, sig_filename));
940 if keywords.has_key("BADARMOR"):
941 reject("ASCII armour of signature was corrupt in %s." % (sig_filename));
943 if keywords.has_key("NODATA"):
944 reject("no signature found in %s." % (sig_filename));
950 # Next check gpgv exited with a zero return code
952 reject("gpgv failed while checking %s." % (sig_filename));
954 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "");
956 reject(prefix_multi_line_string(output, " [GPG output:] "), "");
959 # Sanity check the good stuff we expect
960 if not keywords.has_key("VALIDSIG"):
961 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (sig_filename));
964 args = keywords["VALIDSIG"];
966 reject("internal error while checking signature on %s." % (sig_filename));
969 fingerprint = args[0];
970 if not keywords.has_key("GOODSIG"):
971 reject("signature on %s does not appear to be valid [No GOODSIG]." % (sig_filename));
973 if not keywords.has_key("SIG_ID"):
974 reject("signature on %s does not appear to be valid [No SIG_ID]." % (sig_filename));
977 # Finally ensure there's not something we don't recognise
978 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
979 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
982 for keyword in keywords.keys():
983 if not known_keywords.has_key(keyword):
984 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], sig_filename));
992 ################################################################################
994 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
996 def wrap(paragraph, max_length, prefix=""):
1000 words = paragraph.split();
1003 word_size = len(word);
1004 if word_size > max_length:
1006 s += line + '\n' + prefix;
1007 s += word + '\n' + prefix;
1010 new_length = len(line) + word_size + 1;
1011 if new_length > max_length:
1012 s += line + '\n' + prefix;
1025 ################################################################################
1027 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1028 # Returns fixed 'src'
1029 def clean_symlink (src, dest, root):
1030 src = src.replace(root, '', 1);
1031 dest = dest.replace(root, '', 1);
1032 dest = os.path.dirname(dest);
1033 new_src = '../' * len(dest.split('/'));
1034 return new_src + src;
1036 ################################################################################
1038 def temp_filename(directory=None, dotprefix=None, perms=0700):
1039 """Return a secure and unique filename by pre-creating it.
1040 If 'directory' is non-null, it will be the directory the file is pre-created in.
1041 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1044 old_tempdir = tempfile.tempdir;
1045 tempfile.tempdir = directory;
1047 filename = tempfile.mktemp();
1050 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename));
1051 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms);
1055 tempfile.tempdir = old_tempdir;
1059 ################################################################################
1063 Cnf = apt_pkg.newConfiguration();
1064 apt_pkg.ReadConfigFileISC(Cnf,default_config);
1066 if which_conf_file() != default_config:
1067 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file());
1069 ################################################################################