summaryrefslogtreecommitdiffstats
path: root/Lib/tempfile.py
blob: 74922405a1b2900655a11ff56b6a6ecf2b5e8458 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
"""Temporary files and filenames."""

# 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...

import os

__all__ = ["mktemp", "TemporaryFile", "tempdir", "gettempprefix"]

# 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()
    try:
        return _gettempdir_inner()
    finally:
        _tempdir_lock.release()

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)
    for envname in 'TMPDIR', 'TEMP', 'TMP':
        if os.environ.has_key(envname):
            attempdirs.insert(0, os.environ[envname])
    testfile = gettempprefix() + 'test'
    for dir in attempdirs:
        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')
                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


# 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

def gettempprefix():
    """Function to calculate a prefix of the filename to use.

    This incorporates the current process id on systems that support such a
    notion, so that concurrent processes don't generate the same prefix.
    """

    global template
    if template is None:
        return '@' + `os.getpid()` + '.'
    else:
        return template


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):
            return file


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.
    """

    # 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 __init__(self, file, path):
        self.file = file
        self.path = path
        self.close_called = 0

    def close(self):
        if not self.close_called:
            self.close_called = 1
            self.file.close()
            self.unlink(self.path)

    def __del__(self):
        self.close()

    def __getattr__(self, name):
        file = self.__dict__['file']
        a = getattr(file, name)
        if type(a) != type(0):
            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

try:
    import thread

except ImportError:
    class _DummyMutex:
        def acquire(self):
            pass

        release = acquire

    _counter = _ThreadSafeCounter(_DummyMutex())
    _tempdir_lock = _DummyMutex()
    del _DummyMutex

else:
    _counter = _ThreadSafeCounter(thread.allocate_lock())
    _tempdir_lock = thread.allocate_lock()
    del thread

del _ThreadSafeCounter