4 # Copyright (C) 2000, 2001, 2002, 2003, 2004 James Troup <james@nocrew.org>
5 # $Id: utils.py,v 1.67 2004-04-07 14:23:30 dsilvers 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 or (maintainer[0] == "<" and \
331 maintainer[-1:] == ">"):
335 m = re_parse_maintainer.match(maintainer);
337 raise ParseMaintError, "Doesn't parse as a valid Maintainer field."
341 # Get an RFC2047 compliant version of the name
342 rfc2047_name = rfc2047_encode(name);
344 # Force the name to be UTF-8
345 name = force_to_utf8(name);
347 if name.find(',') != -1 or name.find('.') != -1:
348 rfc822_maint = "%s (%s)" % (email, name);
349 rfc2047_maint = "%s (%s)" % (email, rfc2047_name);
351 rfc822_maint = "%s <%s>" % (name, email);
352 rfc2047_maint = "%s <%s>" % (rfc2047_name, email);
354 if email.find("@") == -1 and email.find("buildd_") != 0:
355 raise ParseMaintError, "No @ found in email address part."
357 return (rfc822_maint, rfc2047_maint, name, email);
359 ################################################################################
361 # sendmail wrapper, takes _either_ a message string or a file as arguments
362 def send_mail (message, filename=""):
363 # If we've been passed a string dump it into a temporary file
365 filename = tempfile.mktemp();
366 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700);
367 os.write (fd, message);
371 (result, output) = commands.getstatusoutput("%s < %s" % (Cnf["Dinstall::SendmailCommand"], filename));
373 raise sendmail_failed_exc, output;
375 # Clean up any temporary files
377 os.unlink (filename);
379 ################################################################################
381 def poolify (source, component):
384 # FIXME: this is nasty
385 component = component.lower().replace("non-us/", "non-US/");
386 if source[:3] == "lib":
387 return component + source[:4] + '/' + source + '/'
389 return component + source[:1] + '/' + source + '/'
391 ################################################################################
393 def move (src, dest, overwrite = 0, perms = 0664):
394 if os.path.exists(dest) and os.path.isdir(dest):
397 dest_dir = os.path.dirname(dest);
398 if not os.path.exists(dest_dir):
399 umask = os.umask(00000);
400 os.makedirs(dest_dir, 02775);
402 #print "Moving %s to %s..." % (src, dest);
403 if os.path.exists(dest) and os.path.isdir(dest):
404 dest += '/' + os.path.basename(src);
405 # Don't overwrite unless forced to
406 if os.path.exists(dest):
408 fubar("Can't move %s to %s - file already exists." % (src, dest));
410 if not os.access(dest, os.W_OK):
411 fubar("Can't move %s to %s - can't write to existing file." % (src, dest));
412 shutil.copy2(src, dest);
413 os.chmod(dest, perms);
416 def copy (src, dest, overwrite = 0, perms = 0664):
417 if os.path.exists(dest) and os.path.isdir(dest):
420 dest_dir = os.path.dirname(dest);
421 if not os.path.exists(dest_dir):
422 umask = os.umask(00000);
423 os.makedirs(dest_dir, 02775);
425 #print "Copying %s to %s..." % (src, dest);
426 if os.path.exists(dest) and os.path.isdir(dest):
427 dest += '/' + os.path.basename(src);
428 # Don't overwrite unless forced to
429 if os.path.exists(dest):
431 raise file_exists_exc
433 if not os.access(dest, os.W_OK):
434 raise cant_overwrite_exc
435 shutil.copy2(src, dest);
436 os.chmod(dest, perms);
438 ################################################################################
441 res = socket.gethostbyaddr(socket.gethostname());
442 database_hostname = Cnf.get("Config::" + res[0] + "::DatabaseHostname");
443 if database_hostname:
444 return database_hostname;
448 def which_conf_file ():
449 res = socket.gethostbyaddr(socket.gethostname());
450 if Cnf.get("Config::" + res[0] + "::KatieConfig"):
451 return Cnf["Config::" + res[0] + "::KatieConfig"]
453 return default_config;
455 def which_apt_conf_file ():
456 res = socket.gethostbyaddr(socket.gethostname());
457 if Cnf.get("Config::" + res[0] + "::AptConfig"):
458 return Cnf["Config::" + res[0] + "::AptConfig"]
460 return default_apt_config;
462 ################################################################################
464 # Escape characters which have meaning to SQL's regex comparison operator ('~')
465 # (woefully incomplete)
468 s = s.replace('+', '\\\\+');
469 s = s.replace('.', '\\\\.');
472 ################################################################################
474 # Perform a substition of template
475 def TemplateSubst(map, filename):
476 file = open_file(filename);
477 template = file.read();
479 template = template.replace(x,map[x]);
483 ################################################################################
485 def fubar(msg, exit_code=1):
486 sys.stderr.write("E: %s\n" % (msg));
490 sys.stderr.write("W: %s\n" % (msg));
492 ################################################################################
494 # Returns the user name with a laughable attempt at rfc822 conformancy
495 # (read: removing stray periods).
497 return pwd.getpwuid(os.getuid())[4].split(',')[0].replace('.', '');
499 ################################################################################
509 return ("%d%s" % (c, t))
511 ################################################################################
513 def cc_fix_changes (changes):
514 o = changes.get("architecture", "");
516 del changes["architecture"];
517 changes["architecture"] = {};
519 changes["architecture"][j] = 1;
521 # Sort by source name, source version, 'have source', and then by filename
522 def changes_compare (a, b):
524 a_changes = parse_changes(a);
529 b_changes = parse_changes(b);
533 cc_fix_changes (a_changes);
534 cc_fix_changes (b_changes);
536 # Sort by source name
537 a_source = a_changes.get("source");
538 b_source = b_changes.get("source");
539 q = cmp (a_source, b_source);
543 # Sort by source version
544 a_version = a_changes.get("version");
545 b_version = b_changes.get("version");
546 q = apt_pkg.VersionCompare(a_version, b_version);
550 # Sort by 'have source'
551 a_has_source = a_changes["architecture"].get("source");
552 b_has_source = b_changes["architecture"].get("source");
553 if a_has_source and not b_has_source:
555 elif b_has_source and not a_has_source:
558 # Fall back to sort by filename
561 ################################################################################
563 def find_next_free (dest, too_many=100):
566 while os.path.exists(dest) and extra < too_many:
567 dest = orig_dest + '.' + repr(extra);
569 if extra >= too_many:
570 raise tried_too_hard_exc;
573 ################################################################################
575 def result_join (original, sep = '\t'):
577 for i in xrange(len(original)):
578 if original[i] == None:
581 list.append(original[i]);
582 return sep.join(list);
584 ################################################################################
586 def prefix_multi_line_string(str, prefix, include_blank_lines=0):
588 for line in str.split('\n'):
590 if line or include_blank_lines:
591 out += "%s%s\n" % (prefix, line);
592 # Strip trailing new line
597 ################################################################################
599 def validate_changes_file_arg(file, fatal=1):
603 if file.endswith(".katie"):
604 file = file[:-6]+".changes";
606 if not file.endswith(".changes"):
607 error = "invalid file type; not a changes file";
609 if not os.access(file,os.R_OK):
610 if os.path.exists(file):
611 error = "permission denied";
613 error = "file not found";
617 fubar("%s: %s." % (orig_filename, error));
619 warn("Skipping %s - %s" % (orig_filename, error));
624 ################################################################################
627 return (arch != "source" and arch != "all");
629 ################################################################################
631 def join_with_commas_and(list):
632 if len(list) == 0: return "nothing";
633 if len(list) == 1: return list[0];
634 return ", ".join(list[:-1]) + " and " + list[-1];
636 ################################################################################
641 ################################################################################
643 # Handle -a, -c and -s arguments; returns them as SQL constraints
644 def parse_args(Options):
648 for suite in split_args(Options["Suite"]):
649 suite_id = db_access.get_suite_id(suite);
651 warn("suite '%s' not recognised." % (suite));
653 suite_ids_list.append(suite_id);
655 con_suites = "AND su.id IN (%s)" % ", ".join(map(str, suite_ids_list));
657 fubar("No valid suite given.");
662 if Options["Component"]:
663 component_ids_list = [];
664 for component in split_args(Options["Component"]):
665 component_id = db_access.get_component_id(component);
666 if component_id == -1:
667 warn("component '%s' not recognised." % (component));
669 component_ids_list.append(component_id);
670 if component_ids_list:
671 con_components = "AND c.id IN (%s)" % ", ".join(map(str, component_ids_list));
673 fubar("No valid component given.");
677 # Process architecture
678 con_architectures = "";
679 if Options["Architecture"]:
682 for architecture in split_args(Options["Architecture"]):
683 if architecture == "source":
686 architecture_id = db_access.get_architecture_id(architecture);
687 if architecture_id == -1:
688 warn("architecture '%s' not recognised." % (architecture));
690 arch_ids_list.append(architecture_id);
692 con_architectures = "AND a.id IN (%s)" % ", ".join(map(str, arch_ids_list));
695 fubar("No valid architecture given.");
699 return (con_suites, con_architectures, con_components, check_source);
701 ################################################################################
703 # Inspired(tm) by Bryn Keller's print_exc_plus (See
704 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
707 tb = sys.exc_info()[2];
714 frame = frame.f_back;
716 traceback.print_exc();
718 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
719 frame.f_code.co_filename,
721 for key, value in frame.f_locals.items():
722 print "\t%20s = " % key,;
726 print "<unable to print>";
728 ################################################################################
730 def try_with_debug(function):
738 ################################################################################
740 # Function for use in sorting lists of architectures.
741 # Sorts normally except that 'source' dominates all others.
743 def arch_compare_sw (a, b):
744 if a == "source" and b == "source":
753 ################################################################################
755 # Split command line arguments which can be separated by either commas
756 # or whitespace. If dwim is set, it will complain about string ending
757 # in comma since this usually means someone did 'madison -a i386, m68k
758 # foo' or something and the inevitable confusion resulting from 'm68k'
759 # being treated as an argument is undesirable.
761 def split_args (s, dwim=1):
762 if s.find(",") == -1:
765 if s[-1:] == "," and dwim:
766 fubar("split_args: found trailing comma, spurious space maybe?");
769 ################################################################################
771 def Dict(**dict): return dict
773 ########################################
775 # Our very own version of commands.getouputstatus(), hacked to support
777 def gpgv_get_status_output(cmd, status_read, status_write):
778 cmd = ['/bin/sh', '-c', cmd];
779 p2cread, p2cwrite = os.pipe();
780 c2pread, c2pwrite = os.pipe();
781 errout, errin = os.pipe();
791 for i in range(3, 256):
792 if i != status_write:
798 os.execvp(cmd[0], cmd);
804 os.dup2(c2pread, c2pwrite);
805 os.dup2(errout, errin);
807 output = status = "";
809 i, o, e = select.select([c2pwrite, errin, status_read], [], []);
812 r = os.read(fd, 8196);
814 more_data.append(fd);
815 if fd == c2pwrite or fd == errin:
817 elif fd == status_read:
820 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd));
822 pid, exit_status = os.waitpid(pid, 0)
824 os.close(status_write);
825 os.close(status_read);
835 return output, status, exit_status;
837 ############################################################
840 def check_signature (filename, reject):
841 """Check the signature of a file and return the fingerprint if the
842 signature is valid or 'None' if it's not. The first argument is the
843 filename whose signature should be checked. The second argument is a
844 reject function and is called when an error is found. The reject()
845 function must allow for two arguments: the first is the error message,
846 the second is an optional prefix string. It's possible for reject()
847 to be called more than once during an invocation of check_signature()."""
849 # Ensure the filename contains no shell meta-characters or other badness
850 if not re_taint_free.match(os.path.basename(filename)):
851 reject("!!WARNING!! tainted filename: '%s'." % (filename));
854 # Invoke gpgv on the file
855 status_read, status_write = os.pipe();
856 cmd = "gpgv --status-fd %s --keyring %s --keyring %s %s" \
857 % (status_write, Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"], filename);
858 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write);
860 # Process the status-fd output
862 bad = internal_error = "";
863 for line in status.split('\n'):
867 split = line.split();
869 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line);
871 (gnupg, keyword) = split[:2];
872 if gnupg != "[GNUPG:]":
873 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg);
876 if keywords.has_key(keyword) and (keyword != "NODATA" and keyword != "SIGEXPIRED"):
877 internal_error += "found duplicate status token ('%s').\n" % (keyword);
880 keywords[keyword] = args;
882 # If we failed to parse the status-fd output, let's just whine and bail now
884 reject("internal error while performing signature check on %s." % (filename));
885 reject(internal_error, "");
886 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "");
889 # Now check for obviously bad things in the processed output
890 if keywords.has_key("SIGEXPIRED"):
891 reject("The key used to sign %s has expired." % (filename));
893 if keywords.has_key("KEYREVOKED"):
894 reject("The key used to sign %s has been revoked." % (filename));
896 if keywords.has_key("BADSIG"):
897 reject("bad signature on %s." % (filename));
899 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
900 reject("failed to check signature on %s." % (filename));
902 if keywords.has_key("NO_PUBKEY"):
903 args = keywords["NO_PUBKEY"];
906 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, filename));
908 if keywords.has_key("BADARMOR"):
909 reject("ASCII armour of signature was corrupt in %s." % (filename));
911 if keywords.has_key("NODATA"):
912 reject("no signature found in %s." % (filename));
918 # Next check gpgv exited with a zero return code
920 reject("gpgv failed while checking %s." % (filename));
922 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "");
924 reject(prefix_multi_line_string(output, " [GPG output:] "), "");
927 # Sanity check the good stuff we expect
928 if not keywords.has_key("VALIDSIG"):
929 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (filename));
932 args = keywords["VALIDSIG"];
934 reject("internal error while checking signature on %s." % (filename));
937 fingerprint = args[0];
938 if not keywords.has_key("GOODSIG"):
939 reject("signature on %s does not appear to be valid [No GOODSIG]." % (filename));
941 if not keywords.has_key("SIG_ID"):
942 reject("signature on %s does not appear to be valid [No SIG_ID]." % (filename));
945 # Finally ensure there's not something we don't recognise
946 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
947 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
950 for keyword in keywords.keys():
951 if not known_keywords.has_key(keyword):
952 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], filename));
960 ################################################################################
962 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
964 def wrap(paragraph, max_length, prefix=""):
968 words = paragraph.split();
971 word_size = len(word);
972 if word_size > max_length:
974 s += line + '\n' + prefix;
975 s += word + '\n' + prefix;
978 new_length = len(line) + word_size + 1;
979 if new_length > max_length:
980 s += line + '\n' + prefix;
993 ################################################################################
995 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
996 # Returns fixed 'src'
997 def clean_symlink (src, dest, root):
998 src = src.replace(root, '', 1);
999 dest = dest.replace(root, '', 1);
1000 dest = os.path.dirname(dest);
1001 new_src = '../' * len(dest.split('/'));
1002 return new_src + src;
1004 ################################################################################
1006 def temp_filename(directory=None, dotprefix=None, perms=0700):
1007 """Return a secure and unique filename by pre-creating it.
1008 If 'directory' is non-null, it will be the directory the file is pre-created in.
1009 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1012 old_tempdir = tempfile.tempdir;
1013 tempfile.tempdir = directory;
1015 filename = tempfile.mktemp();
1018 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename));
1019 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms);
1023 tempfile.tempdir = old_tempdir;
1027 ################################################################################
1031 Cnf = apt_pkg.newConfiguration();
1032 apt_pkg.ReadConfigFileISC(Cnf,default_config);
1034 if which_conf_file() != default_config:
1035 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file());
1037 ################################################################################