~ubuntu-branches/ubuntu/natty/duplicity/natty-proposed

« back to all changes in this revision

Viewing changes to src/tempdir.py

Tags: upstream-0.4.8
ImportĀ upstreamĀ versionĀ 0.4.8

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright 2002 Ben Escoto
 
2
#
 
3
# This file is part of duplicity.
 
4
#
 
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.
 
9
#
 
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.
 
14
#
 
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
 
18
 
 
19
"""
 
20
Provides temporary file handling cenetered around a single top-level
 
21
securely created temporary directory.
 
22
 
 
23
The public interface of this module is thread-safe.
 
24
"""
 
25
 
 
26
import os
 
27
import threading
 
28
import tempfile
 
29
 
 
30
import duplicity.log as log
 
31
 
 
32
# Set up state related to managing the default temporary directory
 
33
# instance
 
34
_defaultLock = threading.Lock()
 
35
_defaultInstance = None
 
36
 
 
37
def default():
 
38
    """
 
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.
 
44
 
 
45
    This function is thread-safe.
 
46
    """
 
47
    global _defaultLock
 
48
    global _defaultInstance
 
49
 
 
50
    _defaultLock.acquire()
 
51
    try:
 
52
        if _defaultInstance is None:
 
53
            _defaultInstance = TemporaryDirectory()
 
54
        return _defaultInstance
 
55
    finally:
 
56
        _defaultLock.release()
 
57
 
 
58
class TemporaryDirectory:
 
59
    """
 
60
    A temporary directory.
 
61
 
 
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.
 
67
 
 
68
    See further below for the security implications of using it.
 
69
 
 
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.
 
77
 
 
78
    This class serves two primary purposes:
 
79
 
 
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.
 
83
 
 
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
 
93
    offers.
 
94
 
 
95
    For this reason, use of mkstemp() is greatly preferred above use
 
96
    of mktemp().
 
97
 
 
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
 
102
    'attackers' file.
 
103
    """
 
104
    def __init__(self, temproot = None):
 
105
        """
 
106
        Create a new TemporaryDirectory backed by a unique and
 
107
        securely created file system directory.
 
108
 
 
109
        tempbase - The temp root directory, or None to use system
 
110
        default (recommended).
 
111
        """
 
112
        self.__dir = tempfile.mkdtemp("-tempdir", "duplicity-", temproot)
 
113
 
 
114
        log.Log("Using temporary directory %s" % (self.__dir,), 5)
 
115
 
 
116
        # number of mktemp()/mkstemp() calls served so far
 
117
        self.__tempcount = 0
 
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.
 
121
        self.__pending = {}
 
122
 
 
123
        self.__lock = threading.Lock()  # protect private resources *AND* mktemp/mkstemp calls
 
124
 
 
125
    def __del__(self):
 
126
        """
 
127
        Perform cleanup.
 
128
        """
 
129
        self.cleanup()
 
130
    
 
131
    def mktemp(self):
 
132
        """
 
133
        Return a unique filename suitable for use for a temporary
 
134
        file. The file is not created.
 
135
 
 
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.
 
139
 
 
140
        NOTE: mkstemp() is greatly preferred.
 
141
        """
 
142
        filename = None
 
143
 
 
144
        self.__lock.acquire()
 
145
        try:
 
146
            self.__tempcount = self.__tempcount + 1
 
147
            suffix = "-%d" % (self.__tempcount,)
 
148
            filename = tempfile.mktemp(suffix, "mktemp-", self.__dir)
 
149
 
 
150
            log.Log("Registering (mktemp) temporary file %s" % (filename,), 9)
 
151
            self.__pending[filename] = None
 
152
        finally:
 
153
            self.__lock.release()
 
154
 
 
155
        return filename
 
156
 
 
157
    def mkstemp(self):
 
158
        """
 
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.
 
162
        """
 
163
        fd = None
 
164
        filename = None
 
165
 
 
166
        self.__lock.acquire()
 
167
        try:
 
168
            self.__tempcount = self.__tempcount + 1
 
169
            suffix = "-%d" % (self.__tempcount,)
 
170
            fd, filename = tempfile.mkstemp(suffix, "mkstemp-", self.__dir)
 
171
 
 
172
            log.Log("Registering (mkstemp) temporary file %s" % (filename,), 9)
 
173
            self.__pending[filename] = None
 
174
        finally:
 
175
            self.__lock.release()
 
176
 
 
177
        return fd, filename
 
178
 
 
179
    def mkstemp_file(self):
 
180
        """
 
181
        Convenience wrapper around mkstemp(), with the file descriptor
 
182
        converted into a file object.
 
183
        """
 
184
        fd, filename = self.mkstemp()
 
185
 
 
186
        return os.fdopen(fd, "r+"), filename
 
187
 
 
188
    def forget(self, fname):
 
189
        """
 
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
 
193
        delete it.
 
194
 
 
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.
 
200
        """
 
201
        self.__lock.acquire()
 
202
        try:
 
203
            if self.__pending.has_key(fname):
 
204
                log.Log("Forgetting temporary file %s" % (fname, ), 9)
 
205
                del(self.__pending[fname])
 
206
            else:
 
207
                log.Log("Attempt to forget unknown tempfile %s - this is probably a bug." % (fname,), 1)
 
208
                pass
 
209
        finally:
 
210
            self.__lock.release()
 
211
 
 
212
    def cleanup(self):
 
213
        """
 
214
        Cleanup any files created in the temporary directory (that
 
215
        have not been forgotten), and clean up the temporary directory
 
216
        itself.
 
217
 
 
218
        On failure they are logged, but this method will not raise an
 
219
        exception.
 
220
        """
 
221
        self.__lock.acquire()
 
222
        try:
 
223
            if not self.__dir is None:
 
224
                for file in self.__pending.keys():
 
225
                    try:
 
226
                        os.unlink(file)
 
227
                    except:
 
228
                        log.Log("Cleanup of temporary file %s failed" % (file,), 7)
 
229
                        pass
 
230
                try:
 
231
                    os.rmdir(self.__dir)
 
232
                except:
 
233
                    log.Log("Cleanup of temporary directory %s failed - this is probably a bug." % (self.__dir,), 1)
 
234
                    pass
 
235
                self.__pending = None
 
236
                self.__dir = None
 
237
        finally:
 
238
            self.__lock.release()