1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 """
23 Provides temporary file handling cenetered around a single top-level
24 securely created temporary directory.
25
26 The public interface of this module is thread-safe.
27 """
28
29 import os
30 import threading
31 import tempfile
32
33 from duplicity import log
34 from duplicity import util
35 from duplicity import globals
36
37
38
39 _defaultLock = threading.Lock()
40 _defaultInstance = None
41
62
64 """
65 A temporary directory.
66
67 An instance of this class is backed by a directory in the file
68 system created securely by the use of tempfile.mkdtemp(). Said
69 instance can be used to obtain unique filenames inside of this
70 directory for cases where mktemp()-like semantics is desired, or
71 (recommended) an fd,filename pair for mkstemp()-like semantics.
72
73 See further below for the security implications of using it.
74
75 Each instance will keep a list of all files ever created by it, to
76 faciliate deletion of such files and rmdir() of the directory
77 itself. It does this in order to be able to clean out the
78 directory without resorting to a recursive delete (ala rm -rf),
79 which would be risky. Calling code can optionally (recommended)
80 notify an instance of the fact that a tempfile was deleted, and
81 thus need not be kept track of anymore.
82
83 This class serves two primary purposes:
84
85 Firstly, it provides a convenient single top-level directory in
86 which all the clutter ends up, rather than cluttering up the root
87 of the system temp directory itself with many files.
88
89 Secondly, it provides a way to get mktemp() style semantics for
90 temporary file creation, with most of the risks
91 gone. Specifically, since the directory itself is created
92 securely, files in this directory can be (mostly) safely created
93 non-atomically without the usual mktemp() security
94 implications. However, in the presence of tmpwatch, tmpreaper, or
95 similar mechanisms that will cause files in the system tempdir to
96 expire, a security risk is still present because the removal of
97 the TemporaryDirectory managed directory removes all protection it
98 offers.
99
100 For this reason, use of mkstemp() is greatly preferred above use
101 of mktemp().
102
103 In addition, since cleanup is in the form of deletion based on a
104 list of filenames, completely independently of whether someone
105 else already deleted the file, there exists a race here as
106 well. The impact should however be limited to the removal of an
107 'attackers' file.
108 """
110 """
111 Create a new TemporaryDirectory backed by a unique and
112 securely created file system directory.
113
114 tempbase - The temp root directory, or None to use system
115 default (recommended).
116 """
117 self.__dir = tempfile.mkdtemp("-tempdir", "duplicity-", temproot)
118
119 log.Info(_("Using temporary directory %s") % (self.__dir,))
120
121
122 self.__tempcount = 0
123
124
125
126 self.__pending = {}
127
128 self.__lock = threading.Lock()
129
137
139 """
140 Return a unique filename suitable for use for a temporary
141 file. The file is not created.
142
143 Subsequent calls to this method are guaranteed to never return
144 the same filename again. As a result, it is safe to use under
145 concurrent conditions.
146
147 NOTE: mkstemp() is greatly preferred.
148 """
149 filename = None
150
151 self.__lock.acquire()
152 try:
153 self.__tempcount = self.__tempcount + 1
154 suffix = "-%d" % (self.__tempcount,)
155 filename = tempfile.mktemp(suffix, "mktemp-", self.__dir)
156
157 log.Debug(_("Registering (mktemp) temporary file %s") % (filename,))
158 self.__pending[filename] = None
159 finally:
160 self.__lock.release()
161
162 return filename
163
165 """
166 Returns a filedescriptor and a filename, as per os.mkstemp(),
167 but located in the temporary directory and subject to tracking
168 and automatic cleanup.
169 """
170 fd = None
171 filename = None
172
173 self.__lock.acquire()
174 try:
175 self.__tempcount = self.__tempcount + 1
176 suffix = "-%d" % (self.__tempcount,)
177 fd, filename = tempfile.mkstemp(suffix, "mkstemp-", self.__dir)
178
179 log.Debug(_("Registering (mkstemp) temporary file %s") % (filename,))
180 self.__pending[filename] = None
181 finally:
182 self.__lock.release()
183
184 return fd, filename
185
187 """
188 Convenience wrapper around mkstemp(), with the file descriptor
189 converted into a file object.
190 """
191 fd, filename = self.mkstemp()
192
193 return os.fdopen(fd, "r+"), filename
194
196 """
197 Forget about the given filename previously obtained through
198 mktemp() or mkstemp(). This should be called *after* the file
199 has been deleted, to stop a future cleanup() from trying to
200 delete it.
201
202 Forgetting is only needed for scaling purposes; that is, to
203 avoid n timefile creations from implying that n filenames are
204 kept in memory. Typically this whould never matter in
205 duplicity, but for niceness sake callers are recommended to
206 use this method whenever possible.
207 """
208 self.__lock.acquire()
209 try:
210 if self.__pending.has_key(fname):
211 log.Debug(_("Forgetting temporary file %s") % (fname, ))
212 del(self.__pending[fname])
213 else:
214 log.Warn(_("Attempt to forget unknown tempfile %s - this is probably a bug.") % (fname,))
215 pass
216 finally:
217 self.__lock.release()
218
220 """
221 Cleanup any files created in the temporary directory (that
222 have not been forgotten), and clean up the temporary directory
223 itself.
224
225 On failure they are logged, but this method will not raise an
226 exception.
227 """
228 self.__lock.acquire()
229 try:
230 if not self.__dir is None:
231 for file in self.__pending.keys():
232 try:
233 log.Debug(_("Removing still remembered temporary file %s") % (file,))
234 util.ignore_missing(os.unlink, file)
235 except Exception:
236 log.Info(_("Cleanup of temporary file %s failed") % (file,))
237 pass
238 try:
239 os.rmdir(self.__dir)
240 except Exception:
241 log.Warn(_("Cleanup of temporary directory %s failed - this is probably a bug.") % (self.__dir,))
242 pass
243 self.__pending = None
244 self.__dir = None
245 finally:
246 self.__lock.release()
247