summaryrefslogtreecommitdiffstats
path: root/Lib/tempfile.py
diff options
context:
space:
mode:
Diffstat (limited to 'Lib/tempfile.py')
-rw-r--r--Lib/tempfile.py621
1 files changed, 396 insertions, 225 deletions
diff --git a/Lib/tempfile.py b/Lib/tempfile.py
index eb8253c..f3cc481 100644
--- a/Lib/tempfile.py
+++ b/Lib/tempfile.py
@@ -1,177 +1,360 @@
-"""Temporary files and filenames."""
+"""Temporary files.
-# XXX This tries to be not UNIX specific, but I don't know beans about
-# how to choose a temp directory or filename on MS-DOS or other
-# systems so it may have to be changed...
+This module provides generic, low- and high-level interfaces for
+creating temporary files and directories. The interfaces listed
+as "safe" just below can be used without fear of race conditions.
+Those listed as "unsafe" cannot, and are provided for backward
+compatibility only.
-import os
+This module also provides some data items to the user:
-__all__ = ["mktemp", "TemporaryFile", "tempdir", "gettempprefix"]
+ TMP_MAX - maximum number of names that will be tried before
+ giving up.
+ template - the default prefix for all temporary names.
+ You may change this to control the default prefix.
+ tempdir - If this is set to a string before the first use of
+ any routine from this module, it will be considered as
+ another candidate location to store temporary files.
+"""
+
+__all__ = [
+ "NamedTemporaryFile", "TemporaryFile", # high level safe interfaces
+ "mkstemp", "mkdtemp", # low level safe interfaces
+ "mktemp", # deprecated unsafe interface
+ "TMP_MAX", "gettempprefix", # constants
+ "tempdir", "gettempdir"
+ ]
+
+
+# Imports.
+
+import os as _os
+import errno as _errno
+from random import Random as _Random
+
+if _os.name == 'mac':
+ import macfs as _macfs
+ import MACFS as _MACFS
+
+try:
+ import fcntl as _fcntl
+ def _set_cloexec(fd):
+ flags = _fcntl.fcntl(fd, _fcntl.F_GETFD, 0)
+ if flags >= 0:
+ # flags read successfully, modify
+ flags |= _fcntl.FD_CLOEXEC
+ _fcntl.fcntl(fd, _fcntl.F_SETFD, flags)
+except (ImportError, AttributeError):
+ def _set_cloexec(fd):
+ pass
+
+try:
+ import thread as _thread
+ _allocate_lock = _thread.allocate_lock
+except (ImportError, AttributeError):
+ class _allocate_lock:
+ def acquire(self):
+ pass
+ release = acquire
+
+_text_openflags = _os.O_RDWR | _os.O_CREAT | _os.O_EXCL
+if hasattr(_os, 'O_NOINHERIT'): _text_openflags |= _os.O_NOINHERIT
+if hasattr(_os, 'O_NOFOLLOW'): _text_openflags |= _os.O_NOFOLLOW
+
+_bin_openflags = _text_openflags
+if hasattr(_os, 'O_BINARY'): _bin_openflags |= _os.O_BINARY
+
+if hasattr(_os, 'TMP_MAX'):
+ TMP_MAX = _os.TMP_MAX
+else:
+ TMP_MAX = 10000
+
+if _os.name == 'nt':
+ template = '~t' # cater to eight-letter limit
+else:
+ template = "tmp"
-# Parameters that the caller may set to override the defaults
tempdir = None
-template = None
-def gettempdir():
- """Function to calculate the directory to use."""
- global tempdir
- if tempdir is not None:
- return tempdir
-
- # _gettempdir_inner deduces whether a candidate temp dir is usable by
- # trying to create a file in it, and write to it. If that succeeds,
- # great, it closes the file and unlinks it. There's a race, though:
- # the *name* of the test file it tries is the same across all threads
- # under most OSes (Linux is an exception), and letting multiple threads
- # all try to open, write to, close, and unlink a single file can cause
- # a variety of bogus errors (e.g., you cannot unlink a file under
- # Windows if anyone has it open, and two threads cannot create the
- # same file in O_EXCL mode under Unix). The simplest cure is to serialize
- # calls to _gettempdir_inner. This isn't a real expense, because the
- # first thread to succeed sets the global tempdir, and all subsequent
- # calls to gettempdir() reuse that without trying _gettempdir_inner.
- _tempdir_lock.acquire()
+# Internal routines.
+
+_once_lock = _allocate_lock()
+
+def _once(var, initializer):
+ """Wrapper to execute an initialization operation just once,
+ even if multiple threads reach the same point at the same time.
+
+ var is the name (as a string) of the variable to be entered into
+ the current global namespace.
+
+ initializer is a callable which will return the appropriate initial
+ value for variable. It will be called only if variable is not
+ present in the global namespace, or its current value is None.
+
+ Do not call _once from inside an initializer routine, it will deadlock.
+ """
+
+ vars = globals()
+ lock = _once_lock
+
+ # Check first outside the lock.
+ if var in vars and vars[var] is not None:
+ return
try:
- return _gettempdir_inner()
+ lock.acquire()
+ # Check again inside the lock.
+ if var in vars and vars[var] is not None:
+ return
+ vars[var] = initializer()
finally:
- _tempdir_lock.release()
+ lock.release()
+
+class _RandomNameSequence:
+ """An instance of _RandomNameSequence generates an endless
+ sequence of unpredictable strings which can safely be incorporated
+ into file names. Each string is six characters long. Multiple
+ threads can safely use the same instance at the same time.
+
+ _RandomNameSequence is an iterator."""
+
+ characters = ( "abcdefghijklmnopqrstuvwxyz"
+ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+ + "0123456789-_")
+
+ def __init__(self):
+ self.mutex = _allocate_lock()
+ self.rng = _Random()
+ self.normcase = _os.path.normcase
+ def __iter__(self):
+ return self
+
+ def next(self):
+ m = self.mutex
+ c = self.characters
+ r = self.rng
-def _gettempdir_inner():
- """Function to calculate the directory to use."""
- global tempdir
- if tempdir is not None:
- return tempdir
- try:
- pwd = os.getcwd()
- except (AttributeError, os.error):
- pwd = os.curdir
- attempdirs = ['/tmp', '/var/tmp', '/usr/tmp', pwd]
- if os.name == 'nt':
- attempdirs.insert(0, 'C:\\TEMP')
- attempdirs.insert(0, '\\TEMP')
- elif os.name == 'mac':
- import macfs, MACFS
try:
- refnum, dirid = macfs.FindFolder(MACFS.kOnSystemDisk,
- MACFS.kTemporaryFolderType, 1)
- dirname = macfs.FSSpec((refnum, dirid, '')).as_pathname()
- attempdirs.insert(0, dirname)
- except macfs.error:
- pass
- elif os.name == 'riscos':
- scrapdir = os.getenv('Wimp$ScrapDir')
- if scrapdir:
- attempdirs.insert(0, scrapdir)
+ m.acquire()
+ letters = ''.join([r.choice(c), r.choice(c), r.choice(c),
+ r.choice(c), r.choice(c), r.choice(c)])
+ finally:
+ m.release()
+
+ return self.normcase(letters)
+
+def _candidate_tempdir_list():
+ """Generate a list of candidate temporary directories which
+ _get_default_tempdir will try."""
+
+ dirlist = []
+
+ # First, try the environment.
for envname in 'TMPDIR', 'TEMP', 'TMP':
- if envname in os.environ:
- attempdirs.insert(0, os.environ[envname])
- testfile = gettempprefix() + 'test'
- for dir in attempdirs:
+ dirname = _os.getenv(envname)
+ if dirname: dirlist.append(dirname)
+
+ # Failing that, try OS-specific locations.
+ if _os.name == 'mac':
try:
- filename = os.path.join(dir, testfile)
- if os.name == 'posix':
- try:
- fd = os.open(filename,
- os.O_RDWR | os.O_CREAT | os.O_EXCL, 0700)
- except OSError:
- pass
- else:
- fp = os.fdopen(fd, 'w')
- fp.write('blat')
- fp.close()
- os.unlink(filename)
- del fp, fd
- tempdir = dir
- break
- else:
- fp = open(filename, 'w')
+ refnum, dirid = _macfs.FindFolder(_MACFS.kOnSystemDisk,
+ _MACFS.kTemporaryFolderType, 1)
+ dirname = _macfs.FSSpec((refnum, dirid, '')).as_pathname()
+ dirlist.append(dirname)
+ except _macfs.error:
+ pass
+ elif _os.name == 'riscos':
+ dirname = _os.getenv('Wimp$ScrapDir')
+ if dirname: dirlist.append(dirname)
+ elif _os.name == 'nt':
+ dirlist.extend([ r'c:\temp', r'c:\tmp', r'\temp', r'\tmp' ])
+ else:
+ dirlist.extend([ '/tmp', '/var/tmp', '/usr/tmp' ])
+
+ # As a last resort, the current directory.
+ try:
+ dirlist.append(_os.getcwd())
+ except (AttributeError, _os.error):
+ dirlist.append(_os.curdir)
+
+ return dirlist
+
+def _get_default_tempdir():
+ """Calculate the default directory to use for temporary files.
+ This routine should be called through '_once' (see above) as we
+ do not want multiple threads attempting this calculation simultaneously.
+
+ We determine whether or not a candidate temp dir is usable by
+ trying to create and write to a file in that directory. If this
+ is successful, the test file is deleted. To prevent denial of
+ service, the name of the test file must be randomized."""
+
+ namer = _RandomNameSequence()
+ dirlist = _candidate_tempdir_list()
+ flags = _text_openflags
+
+ for dir in dirlist:
+ if dir != _os.curdir:
+ dir = _os.path.normcase(_os.path.abspath(dir))
+ # Try only a few names per directory.
+ for seq in xrange(100):
+ name = namer.next()
+ filename = _os.path.join(dir, name)
+ try:
+ fd = _os.open(filename, flags, 0600)
+ fp = _os.fdopen(fd, 'w')
fp.write('blat')
fp.close()
- os.unlink(filename)
- tempdir = dir
- break
- except IOError:
- pass
- if tempdir is None:
- msg = "Can't find a usable temporary directory amongst " + `attempdirs`
- raise IOError, msg
- return tempdir
+ _os.unlink(filename)
+ del fp, fd
+ return dir
+ except (OSError, IOError), e:
+ if e[0] != _errno.EEXIST:
+ break # no point trying more names in this directory
+ pass
+ raise IOError, (_errno.ENOENT,
+ ("No usable temporary directory found in %s" % dirlist))
+def _get_candidate_names():
+ """Common setup sequence for all user-callable interfaces."""
-# template caches the result of gettempprefix, for speed, when possible.
-# XXX unclear why this isn't "_template"; left it "template" for backward
-# compatibility.
-if os.name == "posix":
- # We don't try to cache the template on posix: the pid may change on us
- # between calls due to a fork, and on Linux the pid changes even for
- # another thread in the same process. Since any attempt to keep the
- # cache in synch would have to call os.getpid() anyway in order to make
- # sure the pid hasn't changed between calls, a cache wouldn't save any
- # time. In addition, a cache is difficult to keep correct with the pid
- # changing willy-nilly, and earlier attempts proved buggy (races).
- template = None
-
-# Else the pid never changes, so gettempprefix always returns the same
-# string.
-elif os.name == "nt":
- template = '~' + `os.getpid()` + '-'
-elif os.name in ('mac', 'riscos'):
- template = 'Python-Tmp-'
-else:
- template = 'tmp' # XXX might choose a better one
+ _once('_name_sequence', _RandomNameSequence)
+ return _name_sequence
+
+
+def _mkstemp_inner(dir, pre, suf, flags):
+ """Code common to mkstemp, TemporaryFile, and NamedTemporaryFile."""
+
+ names = _get_candidate_names()
+
+ for seq in xrange(TMP_MAX):
+ name = names.next()
+ file = _os.path.join(dir, pre + name + suf)
+ try:
+ fd = _os.open(file, flags, 0600)
+ _set_cloexec(fd)
+ return (fd, file)
+ except OSError, e:
+ if e.errno == _errno.EEXIST:
+ continue # try again
+ raise
+
+ raise IOError, (_errno.EEXIST, "No usable temporary file name found")
+
+
+# User visible interfaces.
def gettempprefix():
- """Function to calculate a prefix of the filename to use.
+ """Accessor for tempdir.template."""
+ return template
+
+def gettempdir():
+ """Accessor for tempdir.tempdir."""
+ _once('tempdir', _get_default_tempdir)
+ return tempdir
+
+def mkstemp(suffix="", prefix=template, dir=gettempdir(), binary=1):
+ """mkstemp([suffix, [prefix, [dir, [binary]]]])
+ User-callable function to create and return a unique temporary
+ file. The return value is a pair (fd, name) where fd is the
+ file descriptor returned by os.open, and name is the filename.
+
+ If 'suffix' is specified, the file name will end with that suffix,
+ otherwise there will be no suffix.
+
+ If 'prefix' is specified, the file name will begin with that prefix,
+ otherwise a default prefix is used.
+
+ If 'dir' is specified, the file will be created in that directory,
+ otherwise a default directory is used.
+
+ If 'binary' is specified and false, the file is opened in binary
+ mode. Otherwise, the file is opened in text mode. On some
+ operating systems, this makes no difference.
+
+ The file is readable and writable only by the creating user ID.
+ If the operating system uses permission bits to indicate whether a
+ file is executable, the file is executable by no one. The file
+ descriptor is not inherited by children of this process.
- This incorporates the current process id on systems that support such a
- notion, so that concurrent processes don't generate the same prefix.
+ Caller is responsible for deleting the file when done with it.
"""
- global template
- if template is None:
- return '@' + `os.getpid()` + '.'
+ if binary:
+ flags = _bin_openflags
else:
- return template
+ flags = _text_openflags
+ return _mkstemp_inner(dir, prefix, suffix, flags)
-def mktemp(suffix=""):
- """User-callable function to return a unique temporary file name."""
- dir = gettempdir()
- pre = gettempprefix()
- while 1:
- i = _counter.get_next()
- file = os.path.join(dir, pre + str(i) + suffix)
- if not os.path.exists(file):
+
+def mkdtemp(suffix="", prefix=template, dir=gettempdir()):
+ """mkdtemp([suffix, [prefix, [dir]]])
+ User-callable function to create and return a unique temporary
+ directory. The return value is the pathname of the directory.
+
+ Arguments are as for mkstemp, except that the 'binary' argument is
+ not accepted.
+
+ The directory is readable, writable, and searchable only by the
+ creating user.
+
+ Caller is responsible for deleting the directory when done with it.
+ """
+
+ names = _get_candidate_names()
+
+ for seq in xrange(TMP_MAX):
+ name = names.next()
+ file = _os.path.join(dir, prefix + name + suffix)
+ try:
+ _os.mkdir(file, 0700)
return file
+ except OSError, e:
+ if e.errno == _errno.EEXIST:
+ continue # try again
+ raise
+ raise IOError, (_errno.EEXIST, "No usable temporary directory name found")
-class TemporaryFileWrapper:
- """Temporary file wrapper
+def mktemp(suffix="", prefix=template, dir=gettempdir()):
+ """mktemp([suffix, [prefix, [dir]]])
+ User-callable function to return a unique temporary file name. The
+ file is not created.
- This class provides a wrapper around files opened for temporary use.
- In particular, it seeks to automatically remove the file when it is
- no longer needed.
+ Arguments are as for mkstemp, except that the 'binary' argument is
+ not accepted.
+
+ This function is unsafe and should not be used. The file name
+ refers to a file that did not exist at some point, but by the time
+ you get around to creating it, someone else may have beaten you to
+ the punch.
"""
- # Cache the unlinker so we don't get spurious errors at shutdown
- # when the module-level "os" is None'd out. Note that this must
- # be referenced as self.unlink, because the name TemporaryFileWrapper
- # may also get None'd out before __del__ is called.
- unlink = os.unlink
+ from warnings import warn as _warn
+ _warn("mktemp is a potential security risk to your program",
+ RuntimeWarning, stacklevel=2)
- def __init__(self, file, path):
- self.file = file
- self.path = path
- self.close_called = 0
+ names = _get_candidate_names()
+ for seq in xrange(TMP_MAX):
+ name = names.next()
+ file = _os.path.join(dir, prefix + name + suffix)
+ if not _os.path.exists(file):
+ return file
+
+ raise IOError, (_errno.EEXIST, "No usable temporary filename found")
- def close(self):
- if not self.close_called:
- self.close_called = 1
- self.file.close()
- self.unlink(self.path)
+class _TemporaryFileWrapper:
+ """Temporary file wrapper
+
+ This class provides a wrapper around files opened for
+ temporary use. In particular, it seeks to automatically
+ remove the file when it is no longer needed.
+ """
- def __del__(self):
- self.close()
+ def __init__(self, file, name):
+ self.file = file
+ self.name = name
+ self.close_called = 0
def __getattr__(self, name):
file = self.__dict__['file']
@@ -180,93 +363,81 @@ class TemporaryFileWrapper:
setattr(self, name, a)
return a
-try:
- import fcntl as _fcntl
- def _set_cloexec(fd, flag=_fcntl.FD_CLOEXEC):
- flags = _fcntl.fcntl(fd, _fcntl.F_GETFD, 0)
- if flags >= 0:
- # flags read successfully, modify
- flags |= flag
- _fcntl.fcntl(fd, _fcntl.F_SETFD, flags)
-except (ImportError, AttributeError):
- def _set_cloexec(fd):
- pass
-
-def TemporaryFile(mode='w+b', bufsize=-1, suffix=""):
- """Create and return a temporary file (opened read-write by default)."""
- name = mktemp(suffix)
- if os.name == 'posix':
- # Unix -- be very careful
- fd = os.open(name, os.O_RDWR|os.O_CREAT|os.O_EXCL, 0700)
- _set_cloexec(fd)
- try:
- os.unlink(name)
- return os.fdopen(fd, mode, bufsize)
- except:
- os.close(fd)
- raise
- elif os.name == 'nt':
- # Windows -- can't unlink an open file, but O_TEMPORARY creates a
- # file that "deletes itself" when the last handle is closed.
- # O_NOINHERIT ensures processes created via spawn() don't get a
- # handle to this too. That would be a security hole, and, on my
- # Win98SE box, when an O_TEMPORARY file is inherited by a spawned
- # process, the fd in the spawned process seems to lack the
- # O_TEMPORARY flag, so the file doesn't go away by magic then if the
- # spawning process closes it first.
- flags = (os.O_RDWR | os.O_CREAT | os.O_EXCL |
- os.O_TEMPORARY | os.O_NOINHERIT)
- if 'b' in mode:
- flags |= os.O_BINARY
- fd = os.open(name, flags, 0700)
- return os.fdopen(fd, mode, bufsize)
- else:
- # Assume we can't unlink a file that's still open, or arrange for
- # an automagically self-deleting file -- use wrapper.
- file = open(name, mode, bufsize)
- return TemporaryFileWrapper(file, name)
-
-# In order to generate unique names, mktemp() uses _counter.get_next().
-# This returns a unique integer on each call, in a threadsafe way (i.e.,
-# multiple threads will never see the same integer). The integer will
-# usually be a Python int, but if _counter.get_next() is called often
-# enough, it will become a Python long.
-# Note that the only names that survive this next block of code
-# are "_counter" and "_tempdir_lock".
-
-class _ThreadSafeCounter:
- def __init__(self, mutex, initialvalue=0):
- self.mutex = mutex
- self.i = initialvalue
-
- def get_next(self):
- self.mutex.acquire()
- result = self.i
- try:
- newi = result + 1
- except OverflowError:
- newi = long(result) + 1
- self.i = newi
- self.mutex.release()
- return result
+ # NT provides delete-on-close as a primitive, so we don't need
+ # the wrapper to do anything special. We still use it so that
+ # file.name is useful (i.e. not "(fdopen)") with NamedTemporaryFile.
+ if _os.name != 'nt':
+
+ # Cache the unlinker so we don't get spurious errors at
+ # shutdown when the module-level "os" is None'd out. Note
+ # that this must be referenced as self.unlink, because the
+ # name TemporaryFileWrapper may also get None'd out before
+ # __del__ is called.
+ unlink = _os.unlink
+
+ def close(self):
+ if not self.close_called:
+ self.close_called = 1
+ self.file.close()
+ self.unlink(self.name)
+
+ def __del__(self):
+ self.close()
+
+def NamedTemporaryFile(mode='w+b', bufsize=-1, suffix="",
+ prefix=template, dir=gettempdir()):
+ """Create and return a temporary file.
+ Arguments:
+ 'prefix', 'suffix', 'dir' -- as for mkstemp.
+ 'mode' -- the mode argument to os.fdopen (default "w+b").
+ 'bufsize' -- the buffer size argument to os.fdopen (default -1).
+ The file is created as mkstemp() would do it.
+
+ Returns a file object; the name of the file is accessible as
+ file.name. The file will be automatically deleted when it is
+ closed.
+ """
-try:
- import thread
+ bin = 'b' in mode
+ if bin: flags = _bin_openflags
+ else: flags = _text_openflags
-except ImportError:
- class _DummyMutex:
- def acquire(self):
- pass
+ # Setting O_TEMPORARY in the flags causes the OS to delete
+ # the file when it is closed. This is only supported by Windows.
+ if _os.name == 'nt':
+ flags |= _os.O_TEMPORARY
- release = acquire
+ (fd, name) = _mkstemp_inner(dir, prefix, suffix, flags)
+ file = _os.fdopen(fd, mode, bufsize)
+ return _TemporaryFileWrapper(file, name)
- _counter = _ThreadSafeCounter(_DummyMutex())
- _tempdir_lock = _DummyMutex()
- del _DummyMutex
+if _os.name != 'posix':
+ # On non-POSIX systems, assume that we cannot unlink a file while
+ # it is open.
+ TemporaryFile = NamedTemporaryFile
else:
- _counter = _ThreadSafeCounter(thread.allocate_lock())
- _tempdir_lock = thread.allocate_lock()
- del thread
-
-del _ThreadSafeCounter
+ def TemporaryFile(mode='w+b', bufsize=-1, suffix="",
+ prefix=template, dir=gettempdir()):
+ """Create and return a temporary file.
+ Arguments:
+ 'prefix', 'suffix', 'directory' -- as for mkstemp.
+ 'mode' -- the mode argument to os.fdopen (default "w+b").
+ 'bufsize' -- the buffer size argument to os.fdopen (default -1).
+ The file is created as mkstemp() would do it.
+
+ Returns a file object. The file has no name, and will cease to
+ exist when it is closed.
+ """
+
+ bin = 'b' in mode
+ if bin: flags = _bin_openflags
+ else: flags = _text_openflags
+
+ (fd, name) = _mkstemp_inner(dir, prefix, suffix, flags)
+ try:
+ _os.unlink(name)
+ return _os.fdopen(fd, mode, bufsize)
+ except:
+ _os.close(fd)
+ raise