summaryrefslogtreecommitdiff
path: root/rdiff-backup
diff options
context:
space:
mode:
Diffstat (limited to 'rdiff-backup')
-rw-r--r--rdiff-backup/CHANGELOG3
-rw-r--r--rdiff-backup/rdiff_backup/FilenameMapping.py86
-rw-r--r--rdiff-backup/rdiff_backup/Main.py2
-rw-r--r--rdiff-backup/rdiff_backup/fs_abilities.py38
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