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"""Manage temporary files""" 23 24from __future__ import print_function 25from future import standard_library 26standard_library.install_aliases() 27from builtins import object 28 29import os 30import sys 31import shutil 32 33from duplicity import log 34from duplicity import path 35from duplicity import file_naming 36from duplicity import tempdir 37from duplicity import config 38from duplicity import gpg 39 40 41def new_temppath(): 42 u""" 43 Return a new TempPath 44 """ 45 filename = tempdir.default().mktemp() 46 return TempPath(filename) 47 48 49class TempPath(path.Path): 50 u""" 51 Path object used as a temporary file 52 """ 53 def delete(self): 54 u""" 55 Forget and delete 56 """ 57 path.Path.delete(self) 58 tempdir.default().forget(self.name) 59 60 def open_with_delete(self, mode): 61 u""" 62 Returns a fileobj. When that is closed, delete file 63 """ 64 fh = FileobjHooked(path.Path.open(self, mode)) 65 fh.addhook(self.delete) 66 return fh 67 68 69def get_fileobj_duppath(dirpath, partname, permname, remname, overwrite=False): 70 u""" 71 Return a file object open for writing, will write to filename 72 73 Data will be processed and written to a temporary file. When the 74 return fileobject is closed, rename to final position. filename 75 must be a recognizable duplicity data file. 76 """ 77 if not config.restart: 78 td = tempdir.TemporaryDirectory(dirpath.name) 79 tdpname = td.mktemp() 80 tdp = TempDupPath(tdpname, parseresults=file_naming.parse(partname)) 81 fh = FileobjHooked(tdp.filtered_open(u"wb"), tdp=tdp, dirpath=dirpath, 82 partname=partname, permname=permname, remname=remname) 83 else: 84 dp = path.DupPath(dirpath.name, index=(partname,)) 85 mode = u"ab" 86 if overwrite: 87 mode = u"wb" 88 fh = FileobjHooked(dp.filtered_open(mode), tdp=None, dirpath=dirpath, 89 partname=partname, permname=permname, remname=remname) 90 91 def rename_and_forget(): 92 tdp.rename(dirpath.append(partname)) 93 td.forget(tdpname) 94 95 if not config.restart: 96 fh.addhook(rename_and_forget) 97 98 return fh 99 100 101def new_tempduppath(parseresults): 102 u""" 103 Return a new TempDupPath, using settings from parseresults 104 """ 105 filename = tempdir.default().mktemp() 106 return TempDupPath(filename, parseresults=parseresults) 107 108 109class TempDupPath(path.DupPath): 110 u""" 111 Like TempPath, but build around DupPath 112 """ 113 def delete(self): 114 u""" 115 Forget and delete 116 """ 117 path.DupPath.delete(self) 118 tempdir.default().forget(self.name) 119 120 def filtered_open_with_delete(self, mode): 121 u""" 122 Returns a filtered fileobj. When that is closed, delete file 123 """ 124 fh = FileobjHooked(path.DupPath.filtered_open(self, mode)) 125 fh.addhook(self.delete) 126 return fh 127 128 def open_with_delete(self, mode=u"rb"): 129 u""" 130 Returns a fileobj. When that is closed, delete file 131 """ 132 assert mode == u"rb" # Why write a file and then close it immediately? 133 fh = FileobjHooked(path.DupPath.open(self, mode)) 134 fh.addhook(self.delete) 135 return fh 136 137 138class FileobjHooked(object): 139 u""" 140 Simulate a file, but add hook on close 141 """ 142 def __init__(self, fileobj, tdp=None, dirpath=None, 143 partname=None, permname=None, remname=None): 144 u""" 145 Initializer. fileobj is the file object to simulate 146 """ 147 self.fileobj = fileobj # the actual file object 148 self.closed = False # True if closed 149 self.hooklist = [] # filled later with thunks to run on close 150 self.tdp = tdp # TempDupPath object 151 self.dirpath = dirpath # path to directory 152 self.partname = partname # partial filename 153 self.permname = permname # permanent filename 154 self.remname = remname # remote filename 155 156 def write(self, buf): 157 u""" 158 Write fileobj, return result of write() 159 """ 160 return self.fileobj.write(buf) 161 162 def flush(self): 163 u""" 164 Flush fileobj and force sync. 165 """ 166 self.fileobj.flush() 167 os.fsync(self.fileobj.fileno()) 168 169 def to_partial(self): 170 u""" 171 We have achieved the first checkpoint, make file visible and permanent. 172 """ 173 assert not config.restart 174 self.tdp.rename(self.dirpath.append(self.partname)) 175 self.fileobj.flush() 176 del self.hooklist[0] 177 178 def to_remote(self): 179 u""" 180 We have written the last checkpoint, now encrypt or compress 181 and send a copy of it to the remote for final storage. 182 """ 183 pr = file_naming.parse(self.remname) 184 src = self.dirpath.append(self.partname) 185 tgt = self.dirpath.append(self.remname) 186 src_iter = SrcIter(src) 187 if pr.compressed: 188 gpg.GzipWriteFile(src_iter, tgt.name, size=sys.maxsize) 189 elif pr.encrypted: 190 gpg.GPGWriteFile(src_iter, tgt.name, config.gpg_profile, size=sys.maxsize) 191 else: 192 shutil.copyfile(src.name, tgt.name) 193 config.backend.move(tgt) 194 195 def to_final(self): 196 u""" 197 We are finished, rename to final, gzip if needed. 198 """ 199 src = self.dirpath.append(self.partname) 200 tgt = self.dirpath.append(self.permname) 201 src_iter = SrcIter(src) 202 pr = file_naming.parse(self.permname) 203 if pr.compressed: 204 gpg.GzipWriteFile(src_iter, tgt.name, size=sys.maxsize) 205 os.unlink(src.name) 206 else: 207 os.rename(src.name, tgt.name) 208 209 def read(self, length=-1): 210 u""" 211 Read fileobj, return result of read() 212 """ 213 return self.fileobj.read(length) 214 215 def tell(self): 216 u""" 217 Returns current location of fileobj 218 """ 219 return self.fileobj.tell() 220 221 def seek(self, offset): 222 u""" 223 Seeks to a location of fileobj 224 """ 225 return self.fileobj.seek(offset) 226 227 def close(self): 228 u""" 229 Close fileobj, running hooks right afterwards 230 """ 231 assert not self.fileobj.close() 232 for hook in self.hooklist: 233 hook() 234 235 def addhook(self, hook): 236 u""" 237 Add hook (function taking no arguments) to run upon closing 238 """ 239 self.hooklist.append(hook) 240 241 def get_name(self): 242 u""" 243 Return the name of the file 244 """ 245 return self.fileobj.name 246 247 name = property(get_name) 248 249 250class Block(object): 251 u""" 252 Data block to return from SrcIter 253 """ 254 def __init__(self, data): 255 self.data = data 256 257 258class SrcIter(object): 259 u""" 260 Iterate over source and return Block of data. 261 """ 262 def __init__(self, src): 263 self.src = src 264 self.fp = src.open(u"rb") 265 266 def __next__(self): 267 try: 268 res = Block(self.fp.read(self.get_read_size())) 269 except Exception: 270 log.FatalError(_(u"Failed to read %s: %s") % 271 (self.src.uc_name, sys.exc_info()), 272 log.ErrorCode.generic) 273 if not res.data: 274 self.fp.close() 275 raise StopIteration 276 return res 277 278 def get_read_size(self): 279 return 128 * 1024 280 281 def get_footer(self): 282 return b"" 283