4 # Copyright (C) 2000, 2001, 2002, 2003, 2004 James Troup <james@nocrew.org>
5 # $Id: utils.py,v 1.69 2004-06-24 00:41:39 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 # 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 # dsc_whitespace_rules is an optional boolean argument which defaults
145 # to off. If true, it turns on strict format checking to avoid
146 # allowing in source packages which are unextracable by the
147 # inappropriately fragile dpkg-source.
151 # o The PGP header consists of "-----BEGIN PGP SIGNED MESSAGE-----"
152 # followed by any PGP header data and must end with a blank line.
154 # o The data section must end with a blank line and must be followed by
155 # "-----BEGIN PGP SIGNATURE-----".
157 def parse_changes(filename, dsc_whitespace_rules=0):
161 changes_in = open_file(filename);
162 lines = changes_in.readlines();
165 raise changes_parse_error_exc, "[Empty changes file]";
167 # Reindex by line number so we can easily verify the format of
173 indexed_lines[index] = line[:-1];
175 inside_signature = 0;
177 num_of_lines = len(indexed_lines.keys());
180 while index < num_of_lines:
182 line = indexed_lines[index];
184 if dsc_whitespace_rules:
186 if index > num_of_lines:
187 raise invalid_dsc_format_exc, index;
188 line = indexed_lines[index];
189 if not line.startswith("-----BEGIN PGP SIGNATURE"):
190 raise invalid_dsc_format_exc, index;
191 inside_signature = 0;
195 if line.startswith("-----BEGIN PGP SIGNATURE"):
197 if line.startswith("-----BEGIN PGP SIGNED MESSAGE"):
198 inside_signature = 1;
199 if dsc_whitespace_rules:
200 while index < num_of_lines and line != "":
202 line = indexed_lines[index];
204 # If we're not inside the signed data, don't process anything
205 if not inside_signature:
207 slf = re_single_line_field.match(line);
209 field = slf.groups()[0].lower();
210 changes[field] = slf.groups()[1];
214 changes[field] += '\n';
216 mlf = re_multi_line_field.match(line);
219 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';
223 changes[field] += mlf.groups()[0] + '\n';
227 if dsc_whitespace_rules and inside_signature:
228 raise invalid_dsc_format_exc, index;
231 changes["filecontents"] = "".join(lines);
234 raise changes_parse_error_exc, error;
238 ################################################################################
240 # Dropped support for 1.4 and ``buggy dchanges 3.4'' (?!) compared to di.pl
242 def build_file_list(changes, is_a_dsc=0):
245 # Make sure we have a Files: field to parse...
246 if not changes.has_key("files"):
249 # Make sure we recognise the format of the Files: field
250 format = changes.get("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;
256 # Parse each entry/line:
257 for i in changes["files"].split('\n'):
261 section = priority = "";
264 (md5, size, name) = s;
266 (md5, size, section, priority, name) = s;
268 raise changes_parse_error_exc, i;
275 (section, component) = extract_component_from_section(section);
277 files[name] = Dict(md5sum=md5, size=size, section=section,
278 priority=priority, component=component);
282 ################################################################################
284 def force_to_utf8(s):
285 """Forces a string to UTF-8. If the string isn't already UTF-8,
286 it's assumed to be ISO-8859-1."""
291 latin1_s = unicode(s,'iso8859-1');
292 return latin1_s.encode('utf-8');
294 def rfc2047_encode(s):
295 """Encodes a (header) string per RFC2047 if necessary. If the
296 string is neither ASCII nor UTF-8, it's assumed to be ISO-8859-1."""
298 encodings.ascii.Codec().decode(s);
303 encodings.utf_8.Codec().decode(s);
304 h = email.Header.Header(s, 'utf-8', 998);
307 h = email.Header.Header(s, 'iso-8859-1', 998);
310 ################################################################################
312 # <Culus> 'The standard sucks, but my tool is supposed to interoperate
313 # with it. I know - I'll fix the suckage and make things
316 def fix_maintainer (maintainer):
317 """Parses a Maintainer or Changed-By field and returns:
318 (1) an RFC822 compatible version,
319 (2) an RFC2047 compatible version,
323 The name is forced to UTF-8 for both (1) and (3). If the name field
324 contains '.' or ',' (as allowed by Debian policy), (1) and (2) are
325 switched to 'email (name)' format."""
326 maintainer = maintainer.strip()
328 return ('', '', '', '');
330 if maintainer.find("<") == -1:
333 elif (maintainer[0] == "<" and maintainer[-1:] == ">"):
334 email = maintainer[1:-1];
337 m = re_parse_maintainer.match(maintainer);
339 raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
343 # Get an RFC2047 compliant version of the name
344 rfc2047_name = rfc2047_encode(name);
346 # Force the name to be UTF-8
347 name = force_to_utf8(name);
349 if name.find(',') != -1 or name.find('.') != -1:
350 rfc822_maint = "%s (%s)" % (email, name);
351 rfc2047_maint = "%s (%s)" % (email, rfc2047_name);
353 rfc822_maint = "%s <%s>" % (name, email);
354 rfc2047_maint = "%s <%s>" % (rfc2047_name, email);
356 if email.find("@") == -1 and email.find("buildd_") != 0:
357 raise ParseMaintError, "No @ found in email address part."
359 return (rfc822_maint, rfc2047_maint, name, email);
361 ################################################################################
363 # sendmail wrapper, takes _either_ a message string or a file as arguments
364 def send_mail (message, filename=""):
365 # If we've been passed a string dump it into a temporary file
367 filename = tempfile.mktemp();
368 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700);
369 os.write (fd, message);
373 (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename));
375 raise sendmail_failed_exc, output;
377 # Clean up any temporary files
379 os.unlink (filename);
381 ################################################################################
383 def poolify (source, component):
386 # FIXME: this is nasty
387 component = component.lower().replace("non-us/", "non-US/");
388 if source[:3] == "lib":
389 return component + source[:4] + '/' + source + '/'
391 return component + source[:1] + '/' + source + '/'
393 ################################################################################
395 def move (src, dest, overwrite = 0, perms = 0664):
396 if os.path.exists(dest) and os.path.isdir(dest):
399 dest_dir = os.path.dirname(dest);
400 if not os.path.exists(dest_dir):
401 umask = os.umask(00000);
402 os.makedirs(dest_dir, 02775);
404 #print "Moving %s to %s..." % (src, dest);
405 if os.path.exists(dest) and os.path.isdir(dest):
406 dest += '/' + os.path.basename(src);
407 # Don't overwrite unless forced to
408 if os.path.exists(dest):
410 fubar("Can't move %s to %s - file already exists." % (src, dest));
412 if not os.access(dest, os.W_OK):
413 fubar("Can't move %s to %s - can't write to existing file." % (src, dest));
414 shutil.copy2(src, dest);
415 os.chmod(dest, perms);
418 def copy (src, dest, overwrite = 0, perms = 0664):
419 if os.path.exists(dest) and os.path.isdir(dest):
422 dest_dir = os.path.dirname(dest);
423 if not os.path.exists(dest_dir):
424 umask = os.umask(00000);
425 os.makedirs(dest_dir, 02775);
427 #print "Copying %s to %s..." % (src, dest);
428 if os.path.exists(dest) and os.path.isdir(dest):
429 dest += '/' + os.path.basename(src);
430 # Don't overwrite unless forced to
431 if os.path.exists(dest):
433 raise file_exists_exc
435 if not os.access(dest, os.W_OK):
436 raise cant_overwrite_exc
437 shutil.copy2(src, dest);
438 os.chmod(dest, perms);
440 ################################################################################
443 res = socket.gethostbyaddr(socket.gethostname());
444 database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname");
445 if database_hostname:
446 return database_hostname;
450 def which_conf_file ():
451 res = socket.gethostbyaddr(socket.gethostname());
452 if Cnf.get("Config::" + res[0] + "::KatieConfig"):
453 return Cnf["Config::" + res[0] + "::KatieConfig"]
455 return default_config;
457 def which_apt_conf_file ():
458 res = socket.gethostbyaddr(socket.gethostname());
459 if Cnf.get("Config::" + res[0] + "::AptConfig"):
460 return Cnf["Config::" + res[0] + "::AptConfig"]
462 return default_apt_config;
464 ################################################################################
466 # Escape characters which have meaning to SQL's regex comparison operator ('~')
467 # (woefully incomplete)
470 s = s.replace('+', '\\\\+');
471 s = s.replace('.', '\\\\.');
474 ################################################################################
476 # Perform a substition of template
477 def TemplateSubst(map, filename):
478 file = open_file(filename);
479 template = file.read();
481 template = template.replace(x,map[x]);
485 ################################################################################
487 def fubar(msg, exit_code=1):
488 sys.stderr.write("E: %s\n" % (msg));
492 sys.stderr.write("W: %s\n" % (msg));
494 ################################################################################
496 # Returns the user name with a laughable attempt at rfc822 conformancy
497 # (read: removing stray periods).
499 return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '');
501 ################################################################################
511 return ("%d%s" % (c, t))
513 ################################################################################
515 def cc_fix_changes (changes):
516 o = changes.get("architecture", "");
518 del changes["architecture"];
519 changes["architecture"] = {};
521 changes["architecture"][j] = 1;
523 # Sort by source name, source version, 'have source', and then by filename
524 def changes_compare (a, b):
526 a_changes = parse_changes(a);
531 b_changes = parse_changes(b);
535 cc_fix_changes (a_changes);
536 cc_fix_changes (b_changes);
538 # Sort by source name
539 a_source = a_changes.get("source");
540 b_source = b_changes.get("source");
541 q = cmp (a_source, b_source);
545 # Sort by source version
546 a_version = a_changes.get("version");
547 b_version = b_changes.get("version");
548 q = apt_pkg.VersionCompare(a_version, b_version);
552 # Sort by 'have source'
553 a_has_source = a_changes["architecture"].get("source");
554 b_has_source = b_changes["architecture"].get("source");
555 if a_has_source and not b_has_source:
557 elif b_has_source and not a_has_source:
560 # Fall back to sort by filename
563 ################################################################################
565 def find_next_free (dest, too_many=100):
568 while os.path.exists(dest) and extra < too_many:
569 dest = orig_dest + '.' + repr(extra);
571 if extra >= too_many:
572 raise tried_too_hard_exc;
575 ################################################################################
577 def result_join (original, sep = '\t'):
579 for i in xrange(len(original)):
580 if original[i] == None:
583 list.append(original[i]);
584 return sep.join(list);
586 ################################################################################
588 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
590 for line in str.split('\n'):
592 if line or include_blank_lines:
593 out += "%s%s\n" % (prefix, line);
594 # Strip trailing new line
599 ################################################################################
601 def validate_changes_file_arg(file, fatal=1):
605 if file.endswith(".katie"):
606 file = file[:-6]+".changes";
608 if not file.endswith(".changes"):
609 error = "invalid file type; not a changes file";
611 if not os.access(file,os.R_OK):
612 if os.path.exists(file):
613 error = "permission denied";
615 error = "file not found";
619 fubar("%s: %s." % (orig_filename, error));
621 warn("Skipping %s - %s" % (orig_filename, error));
626 ################################################################################
629 return (arch != "source" and arch != "all");
631 ################################################################################
633 def join_with_commas_and(list):
634 if len(list) == 0: return "nothing";
635 if len(list) == 1: return list[0];
636 return ", ".join(list[:-1]) + " and " + list[-1];
638 ################################################################################
643 (pkg, version, constraint) = atom;
645 pp_dep = "%s (%s %s)" % (pkg, constraint, version);
648 pp_deps.append(pp_dep);
649 return " |".join(pp_deps);
651 ################################################################################
656 ################################################################################
658 # Handle -a, -c and -s arguments; returns them as SQL constraints
659 def parse_args(Options):
663 for suite in split_args(Options["Suite"]):
664 suite_id = db_access.get_suite_id(suite);
666 warn("suite '%s' not recognised." % (suite));
668 suite_ids_list.append(suite_id);
670 con_suites = "AND su.id IN (%s)" % ", ".join(map(str, suite_ids_list));
672 fubar("No valid suite given.");
677 if Options["Component"]:
678 component_ids_list = [];
679 for component in split_args(Options["Component"]):
680 component_id = db_access.get_component_id(component);
681 if component_id == -1:
682 warn("component '%s' not recognised." % (component));
684 component_ids_list.append(component_id);
685 if component_ids_list:
686 con_components = "AND c.id IN (%s)" % ", ".join(map(str, component_ids_list));
688 fubar("No valid component given.");
692 # Process architecture
693 con_architectures = "";
694 if Options["Architecture"]:
697 for architecture in split_args(Options["Architecture"]):
698 if architecture == "source":
701 architecture_id = db_access.get_architecture_id(architecture);
702 if architecture_id == -1:
703 warn("architecture '%s' not recognised." % (architecture));
705 arch_ids_list.append(architecture_id);
707 con_architectures = "AND a.id IN (%s)" % ", ".join(map(str, arch_ids_list));
710 fubar("No valid architecture given.");
714 return (con_suites, con_architectures, con_components, check_source);
716 ################################################################################
718 # Inspired(tm) by Bryn Keller's print_exc_plus (See
719 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
722 tb = sys.exc_info()[2];
729 frame = frame.f_back;
731 traceback.print_exc();
733 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
734 frame.f_code.co_filename,
736 for key, value in frame.f_locals.items():
737 print "\t%20s = " % key,;
741 print "<unable to print>";
743 ################################################################################
745 def try_with_debug(function):
753 ################################################################################
755 # Function for use in sorting lists of architectures.
756 # Sorts normally except that 'source' dominates all others.
758 def arch_compare_sw (a, b):
759 if a == "source" and b == "source":
768 ################################################################################
770 # Split command line arguments which can be separated by either commas
771 # or whitespace. If dwim is set, it will complain about string ending
772 # in comma since this usually means someone did 'madison -a i386, m68k
773 # foo' or something and the inevitable confusion resulting from 'm68k'
774 # being treated as an argument is undesirable.
776 def split_args (s, dwim=1):
777 if s.find(",") == -1:
780 if s[-1:] == "," and dwim:
781 fubar("split_args: found trailing comma, spurious space maybe?");
784 ################################################################################
786 def Dict(**dict): return dict
788 ########################################
790 # Our very own version of commands.getouputstatus(), hacked to support
792 def gpgv_get_status_output(cmd, status_read, status_write):
793 cmd = ['/bin/sh', '-c', cmd];
794 p2cread, p2cwrite = os.pipe();
795 c2pread, c2pwrite = os.pipe();
796 errout, errin = os.pipe();
806 for i in range(3, 256):
807 if i != status_write:
813 os.execvp(cmd[0], cmd);
819 os.dup2(c2pread, c2pwrite);
820 os.dup2(errout, errin);
822 output = status = "";
824 i, o, e = select.select([c2pwrite, errin, status_read], [], []);
827 r = os.read(fd, 8196);
829 more_data.append(fd);
830 if fd == c2pwrite or fd == errin:
832 elif fd == status_read:
835 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd));
837 pid, exit_status = os.waitpid(pid, 0)
839 os.close(status_write);
840 os.close(status_read);
850 return output, status, exit_status;
852 ############################################################
855 def check_signature (filename, reject):
856 """Check the signature of a file and return the fingerprint if the
857 signature is valid or 'None' if it's not. The first argument is the
858 filename whose signature should be checked. The second argument is a
859 reject function and is called when an error is found. The reject()
860 function must allow for two arguments: the first is the error message,
861 the second is an optional prefix string. It's possible for reject()
862 to be called more than once during an invocation of check_signature()."""
864 # Ensure the filename contains no shell meta-characters or other badness
865 if not re_taint_free.match(os.path.basename(filename)):
866 reject("!!WARNING!! tainted filename: '%s'." % (filename));
869 # Invoke gpgv on the file
870 status_read, status_write = os.pipe();
871 cmd = "gpgv --status-fd %s --keyring %s --keyring %s %s" \
872 % (status_write, Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"], filename);
873 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write);
875 # Process the status-fd output
877 bad = internal_error = "";
878 for line in status.split('\n'):
882 split = line.split();
884 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line);
886 (gnupg, keyword) = split[:2];
887 if gnupg != "[GNUPG:]":
888 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg);
891 if keywords.has_key(keyword) and (keyword != "NODATA" and keyword != "SIGEXPIRED"):
892 internal_error += "found duplicate status token ('%s').\n" % (keyword);
895 keywords[keyword] = args;
897 # If we failed to parse the status-fd output, let's just whine and bail now
899 reject("internal error while performing signature check on %s." % (filename));
900 reject(internal_error, "");
901 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "");
904 # Now check for obviously bad things in the processed output
905 if keywords.has_key("SIGEXPIRED"):
906 reject("The key used to sign %s has expired." % (filename));
908 if keywords.has_key("KEYREVOKED"):
909 reject("The key used to sign %s has been revoked." % (filename));
911 if keywords.has_key("BADSIG"):
912 reject("bad signature on %s." % (filename));
914 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
915 reject("failed to check signature on %s." % (filename));
917 if keywords.has_key("NO_PUBKEY"):
918 args = keywords["NO_PUBKEY"];
921 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, filename));
923 if keywords.has_key("BADARMOR"):
924 reject("ASCII armour of signature was corrupt in %s." % (filename));
926 if keywords.has_key("NODATA"):
927 reject("no signature found in %s." % (filename));
933 # Next check gpgv exited with a zero return code
935 reject("gpgv failed while checking %s." % (filename));
937 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "");
939 reject(prefix_multi_line_string(output, " [GPG output:] "), "");
942 # Sanity check the good stuff we expect
943 if not keywords.has_key("VALIDSIG"):
944 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (filename));
947 args = keywords["VALIDSIG"];
949 reject("internal error while checking signature on %s." % (filename));
952 fingerprint = args[0];
953 if not keywords.has_key("GOODSIG"):
954 reject("signature on %s does not appear to be valid [No GOODSIG]." % (filename));
956 if not keywords.has_key("SIG_ID"):
957 reject("signature on %s does not appear to be valid [No SIG_ID]." % (filename));
960 # Finally ensure there's not something we don't recognise
961 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
962 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
965 for keyword in keywords.keys():
966 if not known_keywords.has_key(keyword):
967 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], filename));
975 ################################################################################
977 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
979 def wrap(paragraph, max_length, prefix=""):
983 words = paragraph.split();
986 word_size = len(word);
987 if word_size > max_length:
989 s += line + '\n' + prefix;
990 s += word + '\n' + prefix;
993 new_length = len(line) + word_size + 1;
994 if new_length > max_length:
995 s += line + '\n' + prefix;
1008 ################################################################################
1010 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
1011 # Returns fixed 'src'
1012 def clean_symlink (src, dest, root):
1013 src = src.replace(root, '', 1);
1014 dest = dest.replace(root, '', 1);
1015 dest = os.path.dirname(dest);
1016 new_src = '../' * len(dest.split('/'));
1017 return new_src + src;
1019 ################################################################################
1021 def temp_filename(directory=None, dotprefix=None, perms=0700):
1022 """Return a secure and unique filename by pre-creating it.
1023 If 'directory' is non-null, it will be the directory the file is pre-created in.
1024 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1027 old_tempdir = tempfile.tempdir;
1028 tempfile.tempdir = directory;
1030 filename = tempfile.mktemp();
1033 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename));
1034 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms);
1038 tempfile.tempdir = old_tempdir;
1042 ################################################################################
1046 Cnf = apt_pkg.newConfiguration();
1047 apt_pkg.ReadConfigFileISC(Cnf,default_config);
1049 if which_conf_file() != default_config:
1050 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file());
1052 ################################################################################