summaryrefslogtreecommitdiff
path: root/rdiff-backup/rdiff_backup/longname.py
diff options
context:
space:
mode:
authorbescoto <bescoto@2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109>2005-11-23 23:16:32 +0000
committerbescoto <bescoto@2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109>2005-11-23 23:16:32 +0000
commit81083cb48d648e0fd96ce3d39df64c71ec15f17b (patch)
treee091bc02d4bfd0219d63095d53c5686d7446c11a /rdiff-backup/rdiff_backup/longname.py
parentb013f40c0c2d1bbbbe1d58208eef9477a791fa0b (diff)
downloadrdiff-backup-81083cb48d648e0fd96ce3d39df64c71ec15f17b.tar.gz
Final (?) too-long-filename bug fix
git-svn-id: http://svn.savannah.nongnu.org/svn/rdiff-backup/trunk@687 2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109
Diffstat (limited to 'rdiff-backup/rdiff_backup/longname.py')
-rw-r--r--rdiff-backup/rdiff_backup/longname.py275
1 files changed, 275 insertions, 0 deletions
diff --git a/rdiff-backup/rdiff_backup/longname.py b/rdiff-backup/rdiff_backup/longname.py
new file mode 100644
index 0000000..9d3697a
--- /dev/null
+++ b/rdiff-backup/rdiff_backup/longname.py
@@ -0,0 +1,275 @@
+# 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
+ 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(mirror_name)
+ elif rorp.has_alt_inc_name():
+ inc_name = rorp.get_alt_inc_name()
+ mirror_rp = mirror_root.new_index(rorp.index)
+ else: assert 0, "Making new rf when rorp has no alternate name info"
+
+ 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)