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