Package duplicity :: Module tempdir
[hide private]
[frames] | no frames]

Source Code for Module duplicity.tempdir

  1  # -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- 
  2  # 
  3  # Copyright 2002 Ben Escoto <ben@emerose.org> 
  4  # Copyright 2007 Kenneth Loafman <kenneth@loafman.com> 
  5  # 
  6  # This file is part of duplicity. 
  7  # 
  8  # Duplicity is free software; you can redistribute it and/or modify it 
  9  # under the terms of the GNU General Public License as published by the 
 10  # Free Software Foundation; either version 2 of the License, or (at your 
 11  # option) any later version. 
 12  # 
 13  # Duplicity is distributed in the hope that it will be useful, but 
 14  # WITHOUT ANY WARRANTY; without even the implied warranty of 
 15  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU 
 16  # General Public License for more details. 
 17  # 
 18  # You should have received a copy of the GNU General Public License 
 19  # along with duplicity; if not, write to the Free Software Foundation, 
 20  # Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 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  # Set up state related to managing the default temporary directory 
 38  # instance 
 39  _defaultLock = threading.Lock() 
 40  _defaultInstance = None 
 41   
42 -def default():
43 """ 44 Obtain the global default instance of TemporaryDirectory, creating 45 it first if necessary. Failures are propagated to caller. Most 46 callers are expected to use this function rather than 47 instantiating TemporaryDirectory directly, unless they explicitly 48 desdire to have their "own" directory for some reason. 49 50 This function is thread-safe. 51 """ 52 global _defaultLock 53 global _defaultInstance 54 55 _defaultLock.acquire() 56 try: 57 if _defaultInstance is None: 58 _defaultInstance = TemporaryDirectory(temproot = globals.temproot) 59 return _defaultInstance 60 finally: 61 _defaultLock.release()
62
63 -class TemporaryDirectory:
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 """
109 - def __init__(self, temproot = None):
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 # number of mktemp()/mkstemp() calls served so far 122 self.__tempcount = 0 123 # dict of paths pending deletion; use dict even though we are 124 # not concearned with association, because it is unclear whether 125 # sets are O(1), while dictionaries are. 126 self.__pending = {} 127 128 self.__lock = threading.Lock() # protect private resources *AND* mktemp/mkstemp calls
129
130 - def __del__(self):
131 """ 132 Perform cleanup. 133 """ 134 global _defaultInstance 135 if _defaultInstance is not None: 136 self.cleanup()
137
138 - def mktemp(self):
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
164 - def mkstemp(self):
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
186 - def mkstemp_file(self):
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
195 - def forget(self, fname):
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
219 - def cleanup(self):
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