diff options
Diffstat (limited to 'rdiff-backup')
-rw-r--r-- | rdiff-backup/CHANGELOG | 3 | ||||
-rw-r--r-- | rdiff-backup/rdiff_backup/FilenameMapping.py | 86 | ||||
-rw-r--r-- | rdiff-backup/rdiff_backup/Main.py | 2 | ||||
-rw-r--r-- | rdiff-backup/rdiff_backup/fs_abilities.py | 38 |
4 files changed, 116 insertions, 13 deletions
diff --git a/rdiff-backup/CHANGELOG b/rdiff-backup/CHANGELOG index 05ab0f4..45ab0ba 100644 --- a/rdiff-backup/CHANGELOG +++ b/rdiff-backup/CHANGELOG @@ -1,6 +1,9 @@ New in v1.1.15 (????/??/??) --------------------------- +New feature: If quoting requirements change, rdiff-backup can requote the +entire repository if user specifies the --force option. (Andrew Ferguson) + Don't print the warning message about unsupported hard links if the user has specified the --no-hard-links option. (Suggested by Andreas Olsson) diff --git a/rdiff-backup/rdiff_backup/FilenameMapping.py b/rdiff-backup/rdiff_backup/FilenameMapping.py index f213fbc..c63df53 100644 --- a/rdiff-backup/rdiff_backup/FilenameMapping.py +++ b/rdiff-backup/rdiff_backup/FilenameMapping.py @@ -29,7 +29,8 @@ handle that error.) """ -import re, types +from __future__ import generators +import os, re, types import Globals, log, rpath # If true, enable character quoting, and set characters making @@ -71,7 +72,7 @@ def init_quoting_regexps(): re.compile("[%s]|%s" % (chars_to_quote, quoting_char), re.S) unquoting_regexp = re.compile("%s[0-9]{3}" % quoting_char, re.S) except re.error: - log.Log.FatalError("Error '%s' when processing char quote list '%s'" % + log.Log.FatalError("Error '%s' when processing char quote list %r" % (re.error, chars_to_quote)) def quote(path): @@ -171,3 +172,84 @@ def get_quotedrpath(rp, separate_basename = 0): def get_quoted_sep_base(filename): """Get QuotedRPath from filename assuming last bit is quoted""" return get_quotedrpath(rpath.RPath(Globals.local_connection, filename), 1) + +def update_quoting(rbdir): + """Update the quoting of a repository by renaming any + files that should be quoted differently. + """ + + def requote(name): + unquoted_name = unquote(name) + quoted_name = quote(unquoted_name) + if name != quoted_name: + return quoted_name + else: + return None + + def process(dirpath_rp, name, list): + new_name = requote(name) + if new_name: + if list: + list.remove(name) + list.append(new_name) + name_rp = dirpath_rp.append(name) + new_rp = dirpath_rp.append(new_name) + log.Log("Re-quoting %s to %s" % (name_rp.path, new_rp.path), 5) + rpath.move(name_rp, new_rp) + + assert rbdir.conn is Globals.local_connection + mirror_rp = rbdir.get_parent_rp() + mirror = mirror_rp.path + + log.Log("Re-quoting repository %s" % mirror_rp.path, 3) + + try: + os_walk = os.walk + except AttributeError: + os_walk = walk + + for dirpath, dirs, files in os_walk(mirror): + dirpath_rp = mirror_rp.newpath(dirpath) + + for name in dirs: process(dirpath_rp, name, dirs) + for name in files: process(dirpath_rp, name, None) + +""" +os.walk() copied directly from Python 2.5.1's os.py + +Backported here for Python 2.2 support. os.walk() was first added +in Python 2.3. +""" +def walk(top, topdown=True, onerror=None): + from os import error, listdir + from os.path import join, isdir, islink + # We may not have read permission for top, in which case we can't + # get a list of the files the directory contains. os.path.walk + # always suppressed the exception then, rather than blow up for a + # minor reason when (say) a thousand readable directories are still + # left to visit. That logic is copied here. + try: + # Note that listdir and error are globals in this module due + # to earlier import-*. + names = listdir(top) + except error, err: + if onerror is not None: + onerror(err) + return + + dirs, nondirs = [], [] + for name in names: + if isdir(join(top, name)): + dirs.append(name) + else: + nondirs.append(name) + + if topdown: + yield top, dirs, nondirs + for name in dirs: + path = join(top, name) + if not islink(path): + for x in walk(path, topdown, onerror): + yield x + if not topdown: + yield top, dirs, nondirs diff --git a/rdiff-backup/rdiff_backup/Main.py b/rdiff-backup/rdiff_backup/Main.py index a7fe220..b976424 100644 --- a/rdiff-backup/rdiff_backup/Main.py +++ b/rdiff-backup/rdiff_backup/Main.py @@ -329,7 +329,7 @@ def Backup(rpin, rpout): SetConnections.BackupInitConnections(rpin.conn, rpout.conn) backup_check_dirs(rpin, rpout) backup_set_rbdir(rpin, rpout) - rpout.conn.fs_abilities.backup_set_globals(rpin) + rpout.conn.fs_abilities.backup_set_globals(rpin, force) if Globals.chars_to_quote: rpout = backup_quoted_rpaths(rpout) init_user_group_mapping(rpout.conn) backup_final_init(rpout) diff --git a/rdiff-backup/rdiff_backup/fs_abilities.py b/rdiff-backup/rdiff_backup/fs_abilities.py index 2274104..b028369 100644 --- a/rdiff-backup/rdiff_backup/fs_abilities.py +++ b/rdiff-backup/rdiff_backup/fs_abilities.py @@ -577,7 +577,7 @@ class BackupSetGlobals(SetGlobals): log.Log("Backup: must_escape_dos_devices = %d" % \ (self.src_fsa.escape_dos_devices or local_edd), 4) - def set_chars_to_quote(self, rbdir): + def set_chars_to_quote(self, rbdir, force): """Set chars_to_quote setting for backup session Unlike the other options, the chars_to_quote setting also @@ -585,10 +585,12 @@ class BackupSetGlobals(SetGlobals): directory, not just the current fs features. """ - ctq = self.compare_ctq_file(rbdir, self.get_ctq_from_fsas()) + (ctq, update) = self.compare_ctq_file(rbdir, + self.get_ctq_from_fsas(), force) SetConnections.UpdateGlobal('chars_to_quote', ctq) if Globals.chars_to_quote: FilenameMapping.set_init_quote_vals() + return update def get_ctq_from_fsas(self): """Determine chars_to_quote just from filesystems, no ctq file""" @@ -608,25 +610,33 @@ class BackupSetGlobals(SetGlobals): if ctq: ctq.append(';') # Quote quoting char if quoting anything return "".join(ctq) - def compare_ctq_file(self, rbdir, suggested_ctq): + def compare_ctq_file(self, rbdir, suggested_ctq, force): """Compare ctq file with suggested result, return actual ctq""" ctq_rp = rbdir.append("chars_to_quote") if not ctq_rp.lstat(): if Globals.chars_to_quote is None: actual_ctq = suggested_ctq else: actual_ctq = Globals.chars_to_quote ctq_rp.write_string(actual_ctq) - return actual_ctq + return (actual_ctq, None) if Globals.chars_to_quote is None: actual_ctq = ctq_rp.get_data() else: actual_ctq = Globals.chars_to_quote # Globals override - if actual_ctq == suggested_ctq: return actual_ctq + if actual_ctq == suggested_ctq: return (actual_ctq, None) if suggested_ctq == "": log.Log("Warning: File system no longer needs quoting, " "but we will retain for backwards compatibility.", 2) - return actual_ctq + return (actual_ctq, None) if Globals.chars_to_quote is None: - log.Log.FatalError("""New quoting requirements! + if force: + log.Log("Warning: migrating rdiff-backup repository from" + "old quoting chars %r to new quoting chars %r" % + (actual_ctq, suggested_ctq), 2) + ctq_rp.delete() + ctq_rp.write_string(suggested_ctq) + return (suggested_ctq, 1) + else: + log.Log.FatalError("""New quoting requirements! The quoting chars this session needs %r do not match the repository settings %r listed in @@ -637,7 +647,11 @@ This may be caused when you copy an rdiff-backup repository from a normal file system onto a windows one that cannot support the same characters, or if you backup a case-sensitive file system onto a case-insensitive one that previously only had case-insensitive ones -backed up onto it.""" % (suggested_ctq, actual_ctq, ctq_rp.path)) +backed up onto it. + +By specificying the --force option, rdiff-backup will migrate the +repository from the old quoting chars to the new ones.""" % + (suggested_ctq, actual_ctq, ctq_rp.path)) class RestoreSetGlobals(SetGlobals): @@ -719,7 +733,7 @@ class SingleSetGlobals(RestoreSetGlobals): ('carbonfile_active', 'carbonfile_write', 'carbonfile_conn')) -def backup_set_globals(rpin): +def backup_set_globals(rpin, force): """Given rps for source filesystem and repository, set fsa globals This should be run on the destination connection, because we may @@ -742,10 +756,14 @@ def backup_set_globals(rpin): bsg.set_change_ownership() bsg.set_high_perms() bsg.set_symlink_perms() - bsg.set_chars_to_quote(Globals.rbdir) + update_quoting = bsg.set_chars_to_quote(Globals.rbdir, force) bsg.set_escape_dos_devices() bsg.set_must_escape_dos_devices(Globals.rbdir) + if update_quoting and force: + FilenameMapping.update_quoting(Globals.rbdir) + + def restore_set_globals(rpout): """Set fsa related globals for restore session, given in/out rps""" assert rpout.conn is Globals.local_connection |