1
# Copyright 2002 Ben Escoto
3
# This file is part of duplicity.
5
# Duplicity is free software; you can redistribute it and/or modify it
6
# under the terms of the GNU General Public License as published by the
7
# Free Software Foundation; either version 3 of the License, or (at your
8
# option) any later version.
10
# Duplicity is distributed in the hope that it will be useful, but
11
# WITHOUT ANY WARRANTY; without even the implied warranty of
12
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13
# General Public License for more details.
15
# You should have received a copy of the GNU General Public License
16
# along with duplicity; if not, write to the Free Software Foundation,
17
# Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
20
Provides temporary file handling cenetered around a single top-level
21
securely created temporary directory.
23
The public interface of this module is thread-safe.
30
import duplicity.log as log
32
# Set up state related to managing the default temporary directory
34
_defaultLock = threading.Lock()
35
_defaultInstance = None
39
Obtain the global default instance of TemporaryDirectory, creating
40
it first if necessary. Failures are propagated to caller. Most
41
callers are expected to use this function rather than
42
instantiating TemporaryDirectory directly, unless they explicitly
43
desdire to have their "own" directory for some reason.
45
This function is thread-safe.
48
global _defaultInstance
50
_defaultLock.acquire()
52
if _defaultInstance is None:
53
_defaultInstance = TemporaryDirectory()
54
return _defaultInstance
56
_defaultLock.release()
58
class TemporaryDirectory:
60
A temporary directory.
62
An instance of this class is backed by a directory in the file
63
system created securely by the use of tempfile.mkdtemp(). Said
64
instance can be used to obtain unique filenames inside of this
65
directory for cases where mktemp()-like semantics is desired, or
66
(recommended) an fd,filename pair for mkstemp()-like semantics.
68
See further below for the security implications of using it.
70
Each instance will keep a list of all files ever created by it, to
71
faciliate deletion of such files and rmdir() of the directory
72
itself. It does this in order to be able to clean out the
73
directory without resorting to a recursive delete (ala rm -rf),
74
which would be risky. Calling code can optionally (recommended)
75
notify an instance of the fact that a tempfile was deleted, and
76
thus need not be kept track of anymore.
78
This class serves two primary purposes:
80
Firstly, it provides a convenient single top-level directory in
81
which all the clutter ends up, rather than cluttering up the root
82
of the system temp directory itself with many files.
84
Secondly, it provides a way to get mktemp() style semantics for
85
temporary file creation, with most of the risks
86
gone. Specifically, since the directory itself is created
87
securely, files in this directory can be (mostly) safely created
88
non-atomically without the usual mktemp() security
89
implications. However, in the presence of tmpwatch, tmpreaper, or
90
similar mechanisms that will cause files in the system tempdir to
91
expire, a security risk is still present because the removal of
92
the TemporaryDirectory managed directory removes all protection it
95
For this reason, use of mkstemp() is greatly preferred above use
98
In addition, since cleanup is in the form of deletion based on a
99
list of filenames, completely independently of whether someone
100
else already deleted the file, there exists a race here as
101
well. The impact should however be limited to the removal of an
104
def __init__(self, temproot = None):
106
Create a new TemporaryDirectory backed by a unique and
107
securely created file system directory.
109
tempbase - The temp root directory, or None to use system
110
default (recommended).
112
self.__dir = tempfile.mkdtemp("-tempdir", "duplicity-", temproot)
114
log.Log("Using temporary directory %s" % (self.__dir,), 5)
116
# number of mktemp()/mkstemp() calls served so far
118
# dict of paths pending deletion; use dict even though we are
119
# not concearned with association, because it is unclear whether
120
# sets are O(1), while dictionaries are.
123
self.__lock = threading.Lock() # protect private resources *AND* mktemp/mkstemp calls
133
Return a unique filename suitable for use for a temporary
134
file. The file is not created.
136
Subsequent calls to this method are guaranteed to never return
137
the same filename again. As a result, it is safe to use under
138
concurrent conditions.
140
NOTE: mkstemp() is greatly preferred.
144
self.__lock.acquire()
146
self.__tempcount = self.__tempcount + 1
147
suffix = "-%d" % (self.__tempcount,)
148
filename = tempfile.mktemp(suffix, "mktemp-", self.__dir)
150
log.Log("Registering (mktemp) temporary file %s" % (filename,), 9)
151
self.__pending[filename] = None
153
self.__lock.release()
159
Returns a filedescriptor and a filename, as per os.mkstemp(),
160
but located in the temporary directory and subject to tracking
161
and automatic cleanup.
166
self.__lock.acquire()
168
self.__tempcount = self.__tempcount + 1
169
suffix = "-%d" % (self.__tempcount,)
170
fd, filename = tempfile.mkstemp(suffix, "mkstemp-", self.__dir)
172
log.Log("Registering (mkstemp) temporary file %s" % (filename,), 9)
173
self.__pending[filename] = None
175
self.__lock.release()
179
def mkstemp_file(self):
181
Convenience wrapper around mkstemp(), with the file descriptor
182
converted into a file object.
184
fd, filename = self.mkstemp()
186
return os.fdopen(fd, "r+"), filename
188
def forget(self, fname):
190
Forget about the given filename previously obtained through
191
mktemp() or mkstemp(). This should be called *after* the file
192
has been deleted, to stop a future cleanup() from trying to
195
Forgetting is only needed for scaling purposes; that is, to
196
avoid n timefile creations from implying that n filenames are
197
kept in memory. Typically this whould never matter in
198
duplicity, but for niceness sake callers are recommended to
199
use this method whenever possible.
201
self.__lock.acquire()
203
if self.__pending.has_key(fname):
204
log.Log("Forgetting temporary file %s" % (fname, ), 9)
205
del(self.__pending[fname])
207
log.Log("Attempt to forget unknown tempfile %s - this is probably a bug." % (fname,), 1)
210
self.__lock.release()
214
Cleanup any files created in the temporary directory (that
215
have not been forgotten), and clean up the temporary directory
218
On failure they are logged, but this method will not raise an
221
self.__lock.acquire()
223
if not self.__dir is None:
224
for file in self.__pending.keys():
228
log.Log("Cleanup of temporary file %s failed" % (file,), 7)
233
log.Log("Cleanup of temporary directory %s failed - this is probably a bug." % (self.__dir,), 1)
235
self.__pending = None
238
self.__lock.release()