4 # Copyright (C) 2000, 2001, 2002, 2003, 2004 James Troup <james@nocrew.org>
5 # $Id: utils.py,v 1.68 2004-06-23 23:11:47 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 ################################################################################
645 # Handle -a, -c and -s arguments; returns them as SQL constraints
646 def parse_args(Options):
650 for suite in split_args(Options["Suite"]):
651 suite_id = db_access.get_suite_id(suite);
653 warn("suite '%s' not recognised." % (suite));
655 suite_ids_list.append(suite_id);
657 con_suites = "AND su.id IN (%s)" % ", ".join(map(str, suite_ids_list));
659 fubar("No valid suite given.");
664 if Options["Component"]:
665 component_ids_list = [];
666 for component in split_args(Options["Component"]):
667 component_id = db_access.get_component_id(component);
668 if component_id == -1:
669 warn("component '%s' not recognised." % (component));
671 component_ids_list.append(component_id);
672 if component_ids_list:
673 con_components = "AND c.id IN (%s)" % ", ".join(map(str, component_ids_list));
675 fubar("No valid component given.");
679 # Process architecture
680 con_architectures = "";
681 if Options["Architecture"]:
684 for architecture in split_args(Options["Architecture"]):
685 if architecture == "source":
688 architecture_id = db_access.get_architecture_id(architecture);
689 if architecture_id == -1:
690 warn("architecture '%s' not recognised." % (architecture));
692 arch_ids_list.append(architecture_id);
694 con_architectures = "AND a.id IN (%s)" % ", ".join(map(str, arch_ids_list));
697 fubar("No valid architecture given.");
701 return (con_suites, con_architectures, con_components, check_source);
703 ################################################################################
705 # Inspired(tm) by Bryn Keller's print_exc_plus (See
706 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52215)
709 tb = sys.exc_info()[2];
716 frame = frame.f_back;
718 traceback.print_exc();
720 print "\nFrame %s in %s at line %s" % (frame.f_code.co_name,
721 frame.f_code.co_filename,
723 for key, value in frame.f_locals.items():
724 print "\t%20s = " % key,;
728 print "<unable to print>";
730 ################################################################################
732 def try_with_debug(function):
740 ################################################################################
742 # Function for use in sorting lists of architectures.
743 # Sorts normally except that 'source' dominates all others.
745 def arch_compare_sw (a, b):
746 if a == "source" and b == "source":
755 ################################################################################
757 # Split command line arguments which can be separated by either commas
758 # or whitespace. If dwim is set, it will complain about string ending
759 # in comma since this usually means someone did 'madison -a i386, m68k
760 # foo' or something and the inevitable confusion resulting from 'm68k'
761 # being treated as an argument is undesirable.
763 def split_args (s, dwim=1):
764 if s.find(",") == -1:
767 if s[-1:] == "," and dwim:
768 fubar("split_args: found trailing comma, spurious space maybe?");
771 ################################################################################
773 def Dict(**dict): return dict
775 ########################################
777 # Our very own version of commands.getouputstatus(), hacked to support
779 def gpgv_get_status_output(cmd, status_read, status_write):
780 cmd = ['/bin/sh', '-c', cmd];
781 p2cread, p2cwrite = os.pipe();
782 c2pread, c2pwrite = os.pipe();
783 errout, errin = os.pipe();
793 for i in range(3, 256):
794 if i != status_write:
800 os.execvp(cmd[0], cmd);
806 os.dup2(c2pread, c2pwrite);
807 os.dup2(errout, errin);
809 output = status = "";
811 i, o, e = select.select([c2pwrite, errin, status_read], [], []);
814 r = os.read(fd, 8196);
816 more_data.append(fd);
817 if fd == c2pwrite or fd == errin:
819 elif fd == status_read:
822 fubar("Unexpected file descriptor [%s] returned from select\n" % (fd));
824 pid, exit_status = os.waitpid(pid, 0)
826 os.close(status_write);
827 os.close(status_read);
837 return output, status, exit_status;
839 ############################################################
842 def check_signature (filename, reject):
843 """Check the signature of a file and return the fingerprint if the
844 signature is valid or 'None' if it's not. The first argument is the
845 filename whose signature should be checked. The second argument is a
846 reject function and is called when an error is found. The reject()
847 function must allow for two arguments: the first is the error message,
848 the second is an optional prefix string. It's possible for reject()
849 to be called more than once during an invocation of check_signature()."""
851 # Ensure the filename contains no shell meta-characters or other badness
852 if not re_taint_free.match(os.path.basename(filename)):
853 reject("!!WARNING!! tainted filename: '%s'." % (filename));
856 # Invoke gpgv on the file
857 status_read, status_write = os.pipe();
858 cmd = "gpgv --status-fd %s --keyring %s --keyring %s %s" \
859 % (status_write, Cnf["Dinstall::PGPKeyring"], Cnf["Dinstall::GPGKeyring"], filename);
860 (output, status, exit_status) = gpgv_get_status_output(cmd, status_read, status_write);
862 # Process the status-fd output
864 bad = internal_error = "";
865 for line in status.split('\n'):
869 split = line.split();
871 internal_error += "gpgv status line is malformed (< 2 atoms) ['%s'].\n" % (line);
873 (gnupg, keyword) = split[:2];
874 if gnupg != "[GNUPG:]":
875 internal_error += "gpgv status line is malformed (incorrect prefix '%s').\n" % (gnupg);
878 if keywords.has_key(keyword) and (keyword != "NODATA" and keyword != "SIGEXPIRED"):
879 internal_error += "found duplicate status token ('%s').\n" % (keyword);
882 keywords[keyword] = args;
884 # If we failed to parse the status-fd output, let's just whine and bail now
886 reject("internal error while performing signature check on %s." % (filename));
887 reject(internal_error, "");
888 reject("Please report the above errors to the Archive maintainers by replying to this mail.", "");
891 # Now check for obviously bad things in the processed output
892 if keywords.has_key("SIGEXPIRED"):
893 reject("The key used to sign %s has expired." % (filename));
895 if keywords.has_key("KEYREVOKED"):
896 reject("The key used to sign %s has been revoked." % (filename));
898 if keywords.has_key("BADSIG"):
899 reject("bad signature on %s." % (filename));
901 if keywords.has_key("ERRSIG") and not keywords.has_key("NO_PUBKEY"):
902 reject("failed to check signature on %s." % (filename));
904 if keywords.has_key("NO_PUBKEY"):
905 args = keywords["NO_PUBKEY"];
908 reject("The key (0x%s) used to sign %s wasn't found in the keyring(s)." % (key, filename));
910 if keywords.has_key("BADARMOR"):
911 reject("ASCII armour of signature was corrupt in %s." % (filename));
913 if keywords.has_key("NODATA"):
914 reject("no signature found in %s." % (filename));
920 # Next check gpgv exited with a zero return code
922 reject("gpgv failed while checking %s." % (filename));
924 reject(prefix_multi_line_string(status, " [GPG status-fd output:] "), "");
926 reject(prefix_multi_line_string(output, " [GPG output:] "), "");
929 # Sanity check the good stuff we expect
930 if not keywords.has_key("VALIDSIG"):
931 reject("signature on %s does not appear to be valid [No VALIDSIG]." % (filename));
934 args = keywords["VALIDSIG"];
936 reject("internal error while checking signature on %s." % (filename));
939 fingerprint = args[0];
940 if not keywords.has_key("GOODSIG"):
941 reject("signature on %s does not appear to be valid [No GOODSIG]." % (filename));
943 if not keywords.has_key("SIG_ID"):
944 reject("signature on %s does not appear to be valid [No SIG_ID]." % (filename));
947 # Finally ensure there's not something we don't recognise
948 known_keywords = Dict(VALIDSIG="",SIG_ID="",GOODSIG="",BADSIG="",ERRSIG="",
949 SIGEXPIRED="",KEYREVOKED="",NO_PUBKEY="",BADARMOR="",
952 for keyword in keywords.keys():
953 if not known_keywords.has_key(keyword):
954 reject("found unknown status token '%s' from gpgv with args '%r' in %s." % (keyword, keywords[keyword], filename));
962 ################################################################################
964 # Inspired(tm) by http://www.zopelabs.com/cookbook/1022242603
966 def wrap(paragraph, max_length, prefix=""):
970 words = paragraph.split();
973 word_size = len(word);
974 if word_size > max_length:
976 s += line + '\n' + prefix;
977 s += word + '\n' + prefix;
980 new_length = len(line) + word_size + 1;
981 if new_length > max_length:
982 s += line + '\n' + prefix;
995 ################################################################################
997 # Relativize an absolute symlink from 'src' -> 'dest' relative to 'root'.
998 # Returns fixed 'src'
999 def clean_symlink (src, dest, root):
1000 src = src.replace(root, '', 1);
1001 dest = dest.replace(root, '', 1);
1002 dest = os.path.dirname(dest);
1003 new_src = '../' * len(dest.split('/'));
1004 return new_src + src;
1006 ################################################################################
1008 def temp_filename(directory=None, dotprefix=None, perms=0700):
1009 """Return a secure and unique filename by pre-creating it.
1010 If 'directory' is non-null, it will be the directory the file is pre-created in.
1011 If 'dotprefix' is non-null, the filename will be prefixed with a '.'."""
1014 old_tempdir = tempfile.tempdir;
1015 tempfile.tempdir = directory;
1017 filename = tempfile.mktemp();
1020 filename = "%s/.%s" % (os.path.dirname(filename), os.path.basename(filename));
1021 fd = os.open(filename, os.O_RDWR|os.O_CREAT|os.O_EXCL, perms);
1025 tempfile.tempdir = old_tempdir;
1029 ################################################################################
1033 Cnf = apt_pkg.newConfiguration();
1034 apt_pkg.ReadConfigFileISC(Cnf,default_config);
1036 if which_conf_file() != default_config:
1037 apt_pkg.ReadConfigFileISC(Cnf,which_conf_file());
1039 ################################################################################