1# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4; encoding:utf8 -*- 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 22u""" 23Provides temporary file handling cenetered around a single top-level 24securely created temporary directory. 25 26The public interface of this module is thread-safe. 27""" 28 29from __future__ import print_function 30from future import standard_library 31standard_library.install_aliases() 32from builtins import object 33 34import os 35import platform 36import subprocess 37import tempfile 38import threading 39 40from duplicity import config 41from duplicity import log 42from duplicity import util 43 44# Set up state related to managing the default temporary directory 45# instance 46_defaultLock = threading.Lock() 47_defaultInstance = None 48# backup the initial tmp dir path because we will force tempfile 49# later to use our generated _defaultInstance.dir() as temproot 50_initialSystemTempRoot = tempfile.gettempdir() 51 52 53def default(): 54 u""" 55 Obtain the global default instance of TemporaryDirectory, creating 56 it first if necessary. Failures are propagated to caller. Most 57 callers are expected to use this function rather than 58 instantiating TemporaryDirectory directly, unless they explicitly 59 desdire to have their "own" directory for some reason. 60 61 This function is thread-safe. 62 """ 63 global _defaultLock 64 global _defaultInstance 65 66 _defaultLock.acquire() 67 try: 68 if _defaultInstance is None or _defaultInstance.dir() is None: 69 _defaultInstance = TemporaryDirectory(temproot=config.temproot) 70 # set the temp dir to be the default in tempfile module from now on 71 tempfile.tempdir = _defaultInstance.dir() 72 return _defaultInstance 73 finally: 74 _defaultLock.release() 75 76 77class TemporaryDirectory(object): 78 u""" 79 A temporary directory. 80 81 An instance of this class is backed by a directory in the file 82 system created securely by the use of tempfile.mkdtemp(). Said 83 instance can be used to obtain unique filenames inside of this 84 directory for cases where mktemp()-like semantics is desired, or 85 (recommended) an fd,filename pair for mkstemp()-like semantics. 86 87 See further below for the security implications of using it. 88 89 Each instance will keep a list of all files ever created by it, to 90 faciliate deletion of such files and rmdir() of the directory 91 itself. It does this in order to be able to clean out the 92 directory without resorting to a recursive delete (ala rm -rf), 93 which would be risky. Calling code can optionally (recommended) 94 notify an instance of the fact that a tempfile was deleted, and 95 thus need not be kept track of anymore. 96 97 This class serves two primary purposes: 98 99 Firstly, it provides a convenient single top-level directory in 100 which all the clutter ends up, rather than cluttering up the root 101 of the system temp directory itself with many files. 102 103 Secondly, it provides a way to get mktemp() style semantics for 104 temporary file creation, with most of the risks 105 gone. Specifically, since the directory itself is created 106 securely, files in this directory can be (mostly) safely created 107 non-atomically without the usual mktemp() security 108 implications. However, in the presence of tmpwatch, tmpreaper, or 109 similar mechanisms that will cause files in the system tempdir to 110 expire, a security risk is still present because the removal of 111 the TemporaryDirectory managed directory removes all protection it 112 offers. 113 114 For this reason, use of mkstemp() is greatly preferred above use 115 of mktemp(). 116 117 In addition, since cleanup is in the form of deletion based on a 118 list of filenames, completely independently of whether someone 119 else already deleted the file, there exists a race here as 120 well. The impact should however be limited to the removal of an 121 'attackers' file. 122 """ 123 def __init__(self, temproot=None): 124 u""" 125 Create a new TemporaryDirectory backed by a unique and 126 securely created file system directory. 127 128 tempbase - The temp root directory, or None to use system 129 default (recommended). 130 """ 131 def defaults_to_tmp(path): 132 u'''Determine if path point to a MAcOS system tmp''' 133 sys_temps = [ 134 os.path.realpath(u"/tmp"), 135 os.path.realpath(u"/var/tmp"), 136 ] 137 138 user_temp = os.path.realpath(path) 139 for sys_temp in sys_temps: 140 if user_temp.startswith(sys_temp): 141 return True 142 return False 143 144 if temproot is None: 145 if config.temproot: 146 temproot = config.temproot 147 else: 148 global _initialSystemTempRoot 149 temproot = _initialSystemTempRoot 150 if isinstance(temproot, b"".__class__): 151 temproot = util.fsdecode(temproot) 152 153 if (platform.system().startswith(u'Darwin') and defaults_to_tmp(temproot)): 154 # Use temp space from getconf, never /tmp 155 temproot = subprocess.check_output([u'getconf', u'DARWIN_USER_TEMP_DIR']) 156 temproot = util.fsdecode(temproot).rstrip() 157 158 self.__dir = tempfile.mkdtemp(u"-tempdir", u"duplicity-", temproot) 159 160 log.Info(_(u"Using temporary directory %s") % self.__dir) 161 162 # number of mktemp()/mkstemp() calls served so far 163 self.__tempcount = 0 164 # dict of paths pending deletion; use dict even though we are 165 # not concearned with association, because it is unclear whether 166 # sets are O(1), while dictionaries are. 167 self.__pending = {} 168 169 self.__lock = threading.Lock() # protect private resources *AND* mktemp/mkstemp calls 170 171 def dir(self): 172 u""" 173 Returns the absolute pathname of the temp folder. 174 """ 175 return self.__dir 176 177 def __del__(self): 178 u""" 179 Perform cleanup. 180 """ 181 global _defaultInstance 182 if _defaultInstance is not None: 183 self.cleanup() 184 185 def mktemp(self): 186 u""" 187 Return a unique filename suitable for use for a temporary 188 file. The file is not created. 189 190 Subsequent calls to this method are guaranteed to never return 191 the same filename again. As a result, it is safe to use under 192 concurrent conditions. 193 194 NOTE: mkstemp() is greatly preferred. 195 """ 196 filename = None 197 198 self.__lock.acquire() 199 try: 200 self.__tempcount = self.__tempcount + 1 201 suffix = u"-%d" % (self.__tempcount,) 202 filename = util.fsencode(tempfile.mktemp(suffix, u"mktemp-", self.__dir)) 203 204 log.Debug(_(u"Registering (mktemp) temporary file %s") % util.fsdecode(filename)) 205 self.__pending[filename] = None 206 finally: 207 self.__lock.release() 208 209 return filename 210 211 def mkstemp(self): 212 u""" 213 Returns a filedescriptor and a filename, as per os.mkstemp(), 214 but located in the temporary directory and subject to tracking 215 and automatic cleanup. 216 """ 217 fd = None 218 filename = None 219 220 self.__lock.acquire() 221 try: 222 self.__tempcount = self.__tempcount + 1 223 suffix = u"-%d" % (self.__tempcount,) 224 fd, filename = tempfile.mkstemp(suffix, u"mkstemp-", self.__dir,) 225 226 log.Debug(_(u"Registering (mkstemp) temporary file %s") % filename) 227 self.__pending[filename] = None 228 finally: 229 self.__lock.release() 230 231 return fd, filename 232 233 def mkstemp_file(self): 234 u""" 235 Convenience wrapper around mkstemp(), with the file descriptor 236 converted into a file object. 237 """ 238 fd, filename = self.mkstemp() 239 240 return os.fdopen(fd, u"r+"), filename 241 242 def forget(self, fname): 243 u""" 244 Forget about the given filename previously obtained through 245 mktemp() or mkstemp(). This should be called *after* the file 246 has been deleted, to stop a future cleanup() from trying to 247 delete it. 248 249 Forgetting is only needed for scaling purposes; that is, to 250 avoid n timefile creations from implying that n filenames are 251 kept in memory. Typically this whould never matter in 252 duplicity, but for niceness sake callers are recommended to 253 use this method whenever possible. 254 """ 255 self.__lock.acquire() 256 try: 257 if fname in self.__pending: 258 log.Debug(_(u"Forgetting temporary file %s") % util.fsdecode(fname)) 259 del(self.__pending[fname]) 260 else: 261 log.Warn(_(u"Attempt to forget unknown tempfile %s - this is probably a bug.") % util.fsdecode(fname)) 262 pass 263 finally: 264 self.__lock.release() 265 266 def cleanup(self): 267 u""" 268 Cleanup any files created in the temporary directory (that 269 have not been forgotten), and clean up the temporary directory 270 itself. 271 272 On failure they are logged, but this method will not raise an 273 exception. 274 """ 275 self.__lock.acquire() 276 try: 277 if self.__dir is not None: 278 for file in list(self.__pending.keys()): 279 try: 280 log.Debug(_(u"Removing still remembered temporary file %s") % util.fsdecode(file)) 281 util.ignore_missing(os.unlink, file) 282 except Exception: 283 log.Info(_(u"Cleanup of temporary file %s failed") % util.fsdecode(file)) 284 pass 285 try: 286 os.rmdir(self.__dir) 287 except Exception: 288 log.Warn(_(u"Cleanup of temporary directory %s failed - " 289 u"this is probably a bug.") % util.fsdecode(self.__dir)) 290 pass 291 self.__pending = None 292 self.__dir = None 293 finally: 294 self.__lock.release() 295