1# Copyright 2005 Ben Escoto 2# 3# This file is part of rdiff-backup. 4# 5# rdiff-backup is free software; you can redistribute it and/or modify 6# under the terms of the GNU General Public License as published by the 7# Free Software Foundation; either version 2 of the License, or (at your 8# option) any later version. 9# 10# rdiff-backup 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 rdiff-backup; if not, write to the Free Software 17# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 18# 02110-1301, USA 19"""Handle long filenames 20 21rdiff-backup sometimes wants to write filenames longer than allowed by 22the destination directory. This can happen in 3 ways: 23 241) Because the destination directory has a low maximum length limit. 252) When the source directory has a filename close to the limit, so 26 that its increments would be above the limit. 273) When quoting is enabled, so that even the mirror filenames are too 28 long. 29 30When rdiff-backup would otherwise write a file whose name is too long, 31instead it either skips the operation altogether (for non-regular 32files), or writes the data to a unique file in the 33rdiff-backup-data/long-filename directory. This file will have an 34arbitrary basename, but if it's an increment the suffix will be the 35same. The name will be recorded in the mirror_metadata so we can find 36it later. 37 38""" 39 40import errno 41from . import log, Globals, restore, regress 42 43long_name_dir = b"long_filename_data" 44rootrp = None 45 46 47def get_long_rp(base=None): 48 """Return an rpath in long name directory with given base""" 49 global rootrp 50 if not rootrp: 51 rootrp = Globals.rbdir.append(long_name_dir) 52 if not rootrp.lstat(): 53 rootrp.mkdir() 54 if base: 55 return rootrp.append(base) 56 else: 57 return rootrp 58 59 60# ------------------------------------------------------------------ 61# These functions used mainly for backing up 62 63# integer number of next free prefix. Names will be created from 64# integers consecutively like '1', '2', and so on. 65free_name_counter = None 66 67# Filename which holds the next available free name in it 68counter_filename = b"next_free" 69 70 71def get_next_free(): 72 """Return next free filename available in the long filename directory""" 73 global free_name_counter 74 75 def scan_next_free(): 76 """Return value of free_name_counter by listing long filename dir""" 77 log.Log("Setting next free from long filenames dir", 5) 78 cur_high = 0 79 for filename in get_long_rp().listdir(): 80 try: 81 i = int(filename.split(b'.')[0]) 82 except ValueError: 83 continue 84 if i > cur_high: 85 cur_high = i 86 return cur_high + 1 87 88 def read_next_free(): 89 """Return next int free by reading the next_free file, or None""" 90 rp = get_long_rp(counter_filename) 91 if not rp.lstat(): 92 return None 93 return int(rp.get_string()) 94 95 def write_next_free(i): 96 """Write value i into the counter file""" 97 rp = get_long_rp(counter_filename) 98 if rp.lstat(): 99 rp.delete() 100 rp.write_string(str(free_name_counter)) 101 rp.fsync_with_dir() 102 103 if not free_name_counter: 104 free_name_counter = read_next_free() 105 if not free_name_counter: 106 free_name_counter = scan_next_free() 107 filename = b'%i' % free_name_counter 108 rp = get_long_rp(filename) 109 assert not rp.lstat(), "Unexpected file at '%s' found" % rp.get_safepath() 110 free_name_counter += 1 111 write_next_free(free_name_counter) 112 return filename 113 114 115def check_new_index(base, index, make_dirs=0): 116 """Return new rpath with given index, or None if that is too long 117 118 If make_dir is True, make any parent directories to assure that 119 file is really too long, and not just in directories that don't exist. 120 121 """ 122 123 def wrap_call(func, *args): 124 try: 125 result = func(*args) 126 except EnvironmentError as exc: 127 if (exc.errno == errno.ENAMETOOLONG): 128 return None 129 raise 130 return result 131 132 def make_parent(rp): 133 parent = rp.get_parent_rp() 134 if parent.lstat(): 135 return 1 136 parent.makedirs() 137 return 2 138 139 rp = wrap_call(base.new_index, index) 140 if not make_dirs or not rp or rp.lstat(): 141 return rp 142 143 parent_result = wrap_call(make_parent, rp) 144 if not parent_result: 145 return None 146 elif parent_result == 1: 147 return rp 148 else: 149 return wrap_call(base.new_index, index) 150 151 152def get_mirror_rp(mirror_base, mirror_rorp): 153 """Get the mirror_rp for reading a regular file 154 155 This will just be in the mirror_base, unless rorp has an alt 156 mirror name specified. Use new_rorp, unless it is None or empty, 157 and mirror_rorp exists. 158 159 """ 160 if mirror_rorp.has_alt_mirror_name(): 161 return get_long_rp(mirror_rorp.get_alt_mirror_name()) 162 else: 163 rp = check_new_index(mirror_base, mirror_rorp.index) 164 if rp: 165 return rp 166 else: 167 raise Exception("the following line doesn't make any sense but does it matter?") 168 # FIXME index isn't defined anywhere, is mirror_rorp.index meant? 169 # return mirror_base.new_index_empty(index) 170 171 172def get_mirror_inc_rps(rorp_pair, mirror_root, inc_root=None): 173 """Get (mirror_rp, inc_rp) pair, possibly making new longname base 174 175 To test inc_rp, pad incbase with 50 random (non-quoted) characters 176 and see if that raises an error. 177 178 """ 179 if not inc_root: # make fake inc_root if not available 180 inc_root = mirror_root.append_path(b'rdiff-backup-data/increments') 181 182 def mir_triple_old(old_rorp): 183 """Return (mirror_rp, alt_mirror, alt_inc) from old_rorp""" 184 if old_rorp.has_alt_mirror_name(): 185 alt_mirror = old_rorp.get_alt_mirror_name() 186 return (get_long_rp(alt_mirror), alt_mirror, None) 187 else: 188 mirror_rp = mirror_root.new_index(old_rorp.index) 189 if old_rorp.has_alt_inc_name(): 190 return (mirror_rp, None, old_rorp.get_alt_inc_name()) 191 else: 192 return (mirror_rp, None, None) 193 194 def mir_triple_new(new_rorp): 195 """Return (mirror_rp, alt_mirror, None) from new_rorp""" 196 mirror_rp = check_new_index(mirror_root, new_rorp.index) 197 if mirror_rp: 198 return (mirror_rp, None, None) 199 alt_mirror = get_next_free() 200 return (get_long_rp(alt_mirror), alt_mirror, None) 201 202 def update_rorp(new_rorp, alt_mirror, alt_inc): 203 """Update new_rorp with alternate mirror/inc information""" 204 if not new_rorp or not new_rorp.lstat(): 205 return 206 if alt_mirror: 207 new_rorp.set_alt_mirror_name(alt_mirror) 208 elif alt_inc: 209 new_rorp.set_alt_inc_name(alt_inc) 210 211 def find_inc_pair(index, mirror_rp, alt_mirror, alt_inc): 212 """Return (alt_inc, inc_rp) pair""" 213 if alt_mirror: 214 return (None, mirror_rp) 215 elif alt_inc: 216 return (alt_inc, get_long_rp(alt_inc)) 217 elif not index: 218 return (None, inc_root) 219 220 trial_inc_index = index[:-1] + (index[-1] + (b'a' * 50), ) 221 if check_new_index(inc_root, trial_inc_index, make_dirs=1): 222 return (None, inc_root.new_index(index)) 223 alt_inc = get_next_free() 224 return (alt_inc, get_long_rp(alt_inc)) 225 226 (new_rorp, old_rorp) = rorp_pair 227 if old_rorp and old_rorp.lstat(): 228 mirror_rp, alt_mirror, alt_inc = mir_triple_old(old_rorp) 229 index = old_rorp.index 230 else: 231 assert new_rorp and new_rorp.lstat(), (old_rorp, new_rorp) 232 mirror_rp, alt_mirror, alt_inc = mir_triple_new(new_rorp) 233 index = new_rorp.index 234 235 alt_inc, inc_rp = find_inc_pair(index, mirror_rp, alt_mirror, alt_inc) 236 update_rorp(new_rorp, alt_mirror, alt_inc) 237 return mirror_rp, inc_rp 238 239 240# ------------------------------------------------------------------ 241# The following section is for restoring 242 243# This holds a dictionary {incbase: inclist}. The keys are increment 244# bases like '1' or '23', and the values are lists containing the 245# associated increments. 246restore_inc_cache = None 247 248 249def set_restore_cache(): 250 """Initialize restore_inc_cache based on long filename dir""" 251 global restore_inc_cache 252 restore_inc_cache = {} 253 root_rf = restore.RestoreFile(get_long_rp(), get_long_rp(), []) 254 for incbase_rp, inclist in root_rf.yield_inc_complexes(get_long_rp()): 255 restore_inc_cache[incbase_rp.index[-1]] = inclist 256 257 258def get_inclist(inc_base_name): 259 if not restore_inc_cache: 260 set_restore_cache() 261 try: 262 return restore_inc_cache[inc_base_name] 263 except KeyError: 264 return [] 265 266 267def update_rf(rf, rorp, mirror_root): 268 """Return new or updated restorefile based on alt name info in rorp""" 269 270 def _safe_str(cmd): 271 """Transform bytes into string without risk of conversion error""" 272 if isinstance(cmd, str): 273 return cmd 274 else: 275 return str(cmd, errors='replace') 276 277 def update_incs(rf, inc_base): 278 """Swap inclist in rf with those with base inc_base and return""" 279 log.Log( 280 "Restoring with increment base %s for file %s" % 281 (_safe_str(inc_base), rorp.get_safeindexpath()), 6) 282 rf.inc_rp = get_long_rp(inc_base) 283 rf.inc_list = get_inclist(inc_base) 284 rf.set_relevant_incs() 285 286 def update_existing_rf(rf, rorp): 287 """Update rf based on rorp, don't make new one""" 288 if rorp.has_alt_mirror_name(): 289 inc_name = rorp.get_alt_mirror_name() 290 raise Exception("the following line doesn't make any sense but does it matter?") 291 # FIXME mirror_name isn't defined anywhere, is inc_name meant? 292 # rf.mirror_rp = get_long_rp(mirror_name) 293 elif rorp.has_alt_inc_name(): 294 inc_name = rorp.get_alt_inc_name() 295 else: 296 inc_name = None 297 298 if inc_name: 299 update_incs(rf, inc_name) 300 301 def make_new_rf(rorp, mirror_root): 302 """Make a new rf when long name info is available""" 303 if rorp.has_alt_mirror_name(): 304 inc_name = rorp.get_alt_mirror_name() 305 mirror_rp = get_long_rp(inc_name) 306 else: 307 mirror_rp = mirror_root.new_index(rorp.index) 308 if rorp.has_alt_inc_name(): 309 inc_name = rorp.get_alt_inc_name() 310 else: 311 return restore.RestoreFile(mirror_rp, None, []) 312 313 rf = restore.RestoreFile(mirror_rp, None, []) 314 update_incs(rf, inc_name) 315 return rf 316 317 if not rorp: 318 return rf 319 if rf and not rorp.has_alt_mirror_name() and not rorp.has_alt_inc_name(): 320 return rf # Most common case 321 if rf: 322 update_existing_rf(rf, rorp) 323 return rf 324 else: 325 return make_new_rf(rorp, mirror_root) 326 327 328def update_regressfile(rf, rorp, mirror_root): 329 """Like update_rf except return a regress file object""" 330 rf = update_rf(rf, rorp, mirror_root) 331 if isinstance(rf, regress.RegressFile): 332 return rf 333 return regress.RegressFile(rf.mirror_rp, rf.inc_rp, rf.inc_list) 334