summaryrefslogtreecommitdiff
path: root/rdiff-backup/rdiff_backup/Main.py
diff options
context:
space:
mode:
Diffstat (limited to 'rdiff-backup/rdiff_backup/Main.py')
-rw-r--r--rdiff-backup/rdiff_backup/Main.py327
1 files changed, 171 insertions, 156 deletions
diff --git a/rdiff-backup/rdiff_backup/Main.py b/rdiff-backup/rdiff_backup/Main.py
index 5a03fb5..505025d 100644
--- a/rdiff-backup/rdiff_backup/Main.py
+++ b/rdiff-backup/rdiff_backup/Main.py
@@ -1,4 +1,4 @@
-# Copyright 2002 Ben Escoto
+# Copyright 2002, 2003 Ben Escoto
#
# This file is part of rdiff-backup.
#
@@ -24,7 +24,7 @@ import getopt, sys, re, os
from log import Log, LoggerError, ErrorLog
import Globals, Time, SetConnections, selection, robust, rpath, \
manage, backup, connection, restore, FilenameMapping, \
- Security, Hardlink, regress, C
+ Security, Hardlink, regress, C, fs_abilities
action = None
@@ -32,6 +32,9 @@ remote_cmd, remote_schema = None, None
force = None
select_opts = []
select_files = []
+# These are global because they are set while we are trying to figure
+# whether to restore or to backup
+restore_root, restore_index, restore_root_set = None, None, 0
def parse_cmdlineoptions(arglist):
"""Parse argument list and set global preferences"""
@@ -43,24 +46,24 @@ def parse_cmdlineoptions(arglist):
except IOError: Log.FatalError("Error opening file %s" % filename)
try: optlist, args = getopt.getopt(arglist, "blr:sv:V",
- ["backup-mode", "calculate-average", "chars-to-quote=",
- "check-destination-dir", "current-time=", "exclude=",
- "exclude-device-files", "exclude-filelist=",
- "exclude-filelist-stdin", "exclude-globbing-filelist=",
- "exclude-mirror=", "exclude-other-filesystems",
- "exclude-regexp=", "exclude-special-files", "force",
- "include=", "include-filelist=", "include-filelist-stdin",
+ ["backup-mode", "calculate-average", "check-destination-dir",
+ "current-time=", "exclude=", "exclude-device-files",
+ "exclude-filelist=", "exclude-filelist-stdin",
+ "exclude-globbing-filelist=", "exclude-mirror=",
+ "exclude-other-filesystems", "exclude-regexp=",
+ "exclude-special-files", "force", "include=",
+ "include-filelist=", "include-filelist-stdin",
"include-globbing-filelist=", "include-regexp=",
"list-at-time=", "list-changed-since=", "list-increments",
"no-compare-inode", "no-compression",
"no-compression-regexp=", "no-file-statistics",
- "no-hard-links", "null-separator", "parsable-output",
- "print-statistics", "quoting-char=", "remote-cmd=",
- "remote-schema=", "remove-older-than=", "restore-as-of=",
- "restrict=", "restrict-read-only=", "restrict-update-only=",
- "server", "ssh-no-compression", "terminal-verbosity=",
- "test-server", "verbosity=", "version", "windows-mode",
- "windows-restore"])
+ "no-hard-links", "null-separator",
+ "override-chars-to-quote=", "parsable-output",
+ "print-statistics", "remote-cmd=", "remote-schema=",
+ "remove-older-than=", "restore-as-of=", "restrict=",
+ "restrict-read-only=", "restrict-update-only=", "server",
+ "ssh-no-compression", "terminal-verbosity=", "test-server",
+ "verbosity=", "version"])
except getopt.error, e:
commandline_error("Bad commandline options: %s" % str(e))
@@ -68,9 +71,6 @@ def parse_cmdlineoptions(arglist):
if opt == "-b" or opt == "--backup-mode": action = "backup"
elif opt == "--calculate-average": action = "calculate-average"
elif opt == "--check-destination-dir": action = "check-destination-dir"
- elif opt == "--chars-to-quote":
- Globals.set('chars_to_quote', arg)
- Globals.set('quoting_enabled', 1)
elif opt == "--current-time":
Globals.set_integer('current_time', arg)
elif opt == "--exclude": select_opts.append((opt, arg))
@@ -112,11 +112,10 @@ def parse_cmdlineoptions(arglist):
elif opt == "--no-file-statistics": Globals.set('file_statistics', 0)
elif opt == "--no-hard-links": Globals.set('preserve_hardlinks', 0)
elif opt == "--null-separator": Globals.set("null_separator", 1)
+ elif opt == "--override-chars-to-quote":
+ Globals.set('chars_to_quote', arg)
elif opt == "--parsable-output": Globals.set('parsable_output', 1)
elif opt == "--print-statistics": Globals.set('print_statistics', 1)
- elif opt == "--quoting-char":
- Globals.set('quoting_char', arg)
- Globals.set('quoting_enabled', 1)
elif opt == "-r" or opt == "--restore-as-of":
restore_timestr, action = arg, "restore-as-of"
elif opt == "--remote-cmd": remote_cmd = arg
@@ -142,55 +141,30 @@ def parse_cmdlineoptions(arglist):
print "rdiff-backup " + Globals.version
sys.exit(0)
elif opt == "-v" or opt == "--verbosity": Log.setverbosity(arg)
- elif opt == "--windows-mode":
- Globals.set('chars_to_quote', "^a-z0-9._ -")
- Globals.set('quoting_enabled', 1)
- Globals.set('preserve_hardlinks', 0)
- Globals.set('change_ownership', 0)
- Globals.set('change_permissions', 0)
- Globals.set('fsync_directories', 0)
- elif opt == '--windows-restore':
- Globals.set('chars_to_quote', "^a-z0-9._ -")
- Globals.set('quoting_enabled', 1)
else: Log.FatalError("Unknown option %s" % opt)
-def isincfilename(path):
- """Return true if path is of a (possibly quoted) increment file"""
- rp = rpath.RPath(Globals.local_connection, path)
- if Globals.quoting_enabled:
- if not FilenameMapping.quoting_char:
- FilenameMapping.set_init_quote_vals()
- rp = FilenameMapping.get_quotedrpath(rp, separate_basename = 1)
- result = rp.isincfile()
- return result
-
-def set_action():
- """Check arguments and try to set action"""
+def check_action():
+ """Check to make sure action is compatible with args"""
global action
+ arg_action_dict = {0: ['server'],
+ 1: ['list-increments', 'remove-older-than',
+ 'list-at-time', 'list-changed-since',
+ 'check-destination-dir'],
+ 2: ['backup', 'restore', 'restore-as-of']}
l = len(args)
- if not action:
+ if not action: assert l == 2, args # cannot tell backup or restore yet
+ elif action == 'calculate-average':
if l == 0: commandline_error("No arguments given")
- elif l == 1: action = "restore"
- elif l == 2:
- if isincfilename(args[0]): action = "restore"
- else: action = "backup"
- else: commandline_error("Too many arguments given")
-
- if l == 0 and action != "server":
- commandline_error("No arguments given")
- if l > 0 and action == "server":
- commandline_error("Too many arguments given")
- if l < 2 and (action == "backup" or action == "restore-as-of"):
- commandline_error("Two arguments are required (source, destination).")
- if l == 2 and (action == "list-increments" or
- action == "remove-older-than" or
- action == "list-at-time" or
- action == "list-changed-since" or
- action == "check-destination-dir"):
- commandline_error("Only use one argument, "
- "the root of the backup directory")
- if l > 2 and action != "calculate-average":
- commandline_error("Too many arguments given")
+ elif l > 2 or action not in arg_action_dict[l]:
+ commandline_error("Wrong number of arguments given. See man page.")
+
+def final_set_action(rps):
+ """If no action set, decide between backup and restore at this point"""
+ global action
+ if action: return
+ assert len(rps) == 2, rps
+ if restore_get_root(rps[0]): action = "restore"
+ else: action = "backup"
def commandline_error(message):
sys.stderr.write("Error: %s\n" % message)
@@ -201,7 +175,6 @@ def misc_setup(rps):
"""Set default change ownership flag, umask, relay regexps"""
os.umask(077)
Time.setcurtime(Globals.current_time)
- FilenameMapping.set_init_quote_vals()
SetConnections.UpdateGlobal("client_conn", Globals.local_connection)
Globals.postset_regexp('no_compression_regexp',
Globals.no_compression_regexp_string)
@@ -216,7 +189,7 @@ def take_action(rps):
sys.exit(0)
elif action == "backup": Backup(rps[0], rps[1])
elif action == "restore": Restore(*rps)
- elif action == "restore-as-of": RestoreAsOf(rps[0], rps[1])
+ elif action == "restore-as-of": Restore(rps[0], rps[1], 1)
elif action == "test-server": SetConnections.TestConnections()
elif action == "list-at-time": ListAtTime(rps[0])
elif action == "list-changed-since": ListChangedSince(rps[0])
@@ -236,10 +209,11 @@ def cleanup():
def Main(arglist):
"""Start everything up!"""
parse_cmdlineoptions(arglist)
- set_action()
+ check_action()
cmdpairs = SetConnections.get_cmd_pairs(args, remote_schema, remote_cmd)
- Security.initialize(action, cmdpairs)
+ Security.initialize(action or "mirror", cmdpairs)
rps = map(SetConnections.cmdpair2rp, cmdpairs)
+ final_set_action(rps)
misc_setup(rps)
take_action(rps)
cleanup()
@@ -247,11 +221,15 @@ def Main(arglist):
def Backup(rpin, rpout):
"""Backup, possibly incrementally, src_path to dest_path."""
- if Globals.quoting_enabled:
- rpout = FilenameMapping.get_quotedrpath(rpout)
SetConnections.BackupInitConnections(rpin.conn, rpout.conn)
+ backup_check_dirs(rpin, rpout)
+ backup_set_fs_globals(rpin, rpout)
+ if Globals.chars_to_quote:
+ rpout = FilenameMapping.get_quotedrpath(rpout)
+ SetConnections.UpdateGlobal(
+ 'rbdir', FilenameMapping.get_quotedrpath(Globals.rbdir))
+ backup_set_rbdir(rpin, rpout)
backup_set_select(rpin)
- backup_init_dirs(rpin, rpout)
if prevtime:
rpout.conn.Main.backup_touch_curmirror_local(rpin, rpout)
Time.setprevtime(prevtime)
@@ -266,32 +244,39 @@ def backup_set_select(rpin):
rpin.conn.backup.SourceStruct.set_source_select(rpin, select_opts,
*select_files)
-def backup_init_dirs(rpin, rpout):
- """Make sure rpin and rpout are valid, init data dir and logging"""
- global datadir, incdir, prevtime
+def backup_check_dirs(rpin, rpout):
+ """Make sure in and out dirs exist and are directories"""
if rpout.lstat() and not rpout.isdir():
if not force: Log.FatalError("Destination %s exists and is not a "
"directory" % rpout.path)
else:
Log("Deleting %s" % rpout.path, 3)
rpout.delete()
+ if not rpout.lstat():
+ try: rpout.mkdir()
+ except os.error:
+ Log.FatalError("Unable to create directory %s" % rpout.path)
if not rpin.lstat():
Log.FatalError("Source directory %s does not exist" % rpin.path)
elif not rpin.isdir():
Log.FatalError("Source %s is not a directory" % rpin.path)
+ backup_warn_if_infinite_regress(rpin, rpout)
+ Globals.rbdir = rpout.append_path("rdiff-backup-data")
- datadir = rpout.append_path("rdiff-backup-data")
- SetConnections.UpdateGlobal('rbdir', datadir)
+def backup_set_rbdir(rpin, rpout):
+ """Initialize data dir and logging"""
+ global incdir, prevtime
+ SetConnections.UpdateGlobal('rbdir', Globals.rbdir)
checkdest_if_necessary(rpout)
- incdir = datadir.append_path("increments")
+ incdir = Globals.rbdir.append_path("increments")
prevtime = backup_get_mirrortime()
- if rpout.lstat():
- if rpout.isdir() and not rpout.listdir(): # rpout is empty dir
- if Globals.change_permissions:
- rpout.chmod(0700) # just make sure permissions aren't too lax
- elif not datadir.lstat() and not force: Log.FatalError(
+ assert rpout.lstat(), (rpout.path, rpout.lstat())
+ if rpout.isdir() and not rpout.listdir(): # rpout is empty dir
+ if Globals.change_permissions:
+ rpout.chmod(0700) # just make sure permissions aren't too lax
+ elif not Globals.rbdir.lstat() and not force: Log.FatalError(
"""Destination directory
%s
@@ -301,17 +286,12 @@ rdiff-backup like this could mess up what is currently in it. If you
want to update or overwrite it, run rdiff-backup with the --force
option.""" % rpout.path)
- if not rpout.lstat():
- try: rpout.mkdir()
- except os.error:
- Log.FatalError("Unable to create directory %s" % rpout.path)
- if not datadir.lstat(): datadir.mkdir()
- inc_base = datadir.append_path("increments")
+ if not Globals.rbdir.lstat(): Globals.rbdir.mkdir()
+ inc_base = Globals.rbdir.append_path("increments")
if not inc_base.lstat(): inc_base.mkdir()
if Log.verbosity > 0:
- Log.open_logfile(datadir.append("backup.log"))
+ Log.open_logfile(Globals.rbdir.append("backup.log"))
ErrorLog.open(Time.curtimestr, compress = Globals.compression)
- backup_warn_if_infinite_regress(rpin, rpout)
def backup_warn_if_infinite_regress(rpin, rpout):
"""Warn user if destination area contained in source area"""
@@ -336,6 +316,25 @@ def backup_get_mirrortime():
if mirror_rps: return mirror_rps[0].getinctime()
else: return None
+def backup_set_fs_globals(rpin, rpout):
+ """Use fs_abilities to set the globals that depend on filesystem"""
+ src_fsa = fs_abilities.FSAbilities().init_readonly(rpin)
+ SetConnections.UpdateGlobal('read_acls', src_fsa.acls)
+ if src_fsa.eas: rpin.get_ea()
+ SetConnections.UpdateGlobal('read_eas', src_fsa.eas)
+
+ dest_fsa = fs_abilities.FSAbilities().init_readwrite(
+ Globals.rbdir, override_chars_to_quote = Globals.chars_to_quote)
+ SetConnections.UpdateGlobal('preserve_hardlinks', dest_fsa.hardlinks)
+ SetConnections.UpdateGlobal('fsync_directories', dest_fsa.fsync_dirs)
+ SetConnections.UpdateGlobal('write_acls', dest_fsa.acls)
+ SetConnections.UpdateGlobal('write_eas', Globals.read_eas and dest_fsa.eas)
+ SetConnections.UpdateGlobal('change_ownership', dest_fsa.ownership)
+ SetConnections.UpdateGlobal('chars_to_quote', dest_fsa.chars_to_quote)
+ if Globals.chars_to_quote:
+ for conn in Globals.connections:
+ conn.FilenameMapping.set_init_quote_vals()
+
def backup_touch_curmirror_local(rpin, rpout):
"""Make a file like current_mirror.time.data to record time
@@ -367,40 +366,56 @@ def backup_remove_curmirror_local():
older_inc.delete()
-def Restore(src_rp, dest_rp = None):
+def Restore(src_rp, dest_rp, restore_as_of = None):
"""Main restoring function
- Here src_rp should be an increment file, and if dest_rp is
- missing it defaults to the base of the increment.
+ Here src_rp should be the source file (either an increment or
+ mirror file), dest_rp should be the target rp to be written.
"""
- rpin, rpout = restore_check_paths(src_rp, dest_rp)
- restore_common(rpin, rpout, rpin.getinctime())
-
-def RestoreAsOf(rpin, target):
- """Secondary syntax for restore operation
-
- rpin - RPath of mirror file to restore (not nec. with correct index)
- target - RPath of place to put restored file
-
- """
- rpin, rpout = restore_check_paths(rpin, target, 1)
- try: time = Time.genstrtotime(restore_timestr)
- except Time.TimeException, exc: Log.FatalError(str(exc))
- restore_common(rpin, target, time)
-
-def restore_common(rpin, target, time):
- """Restore operation common to Restore and RestoreAsOf"""
- if target.conn.os.getuid() == 0:
- SetConnections.UpdateGlobal('change_ownership', 1)
- mirror_root, index = restore_get_root(rpin)
- restore_check_backup_dir(mirror_root)
- mirror = mirror_root.new_index(index)
- inc_rpath = datadir.append_path('increments', index)
- restore_set_select(mirror_root, target)
- restore_start_log(rpin, target, time)
- restore.Restore(mirror, inc_rpath, target, time)
- Log("Restore ended", 4)
+ if not restore_root_set: assert restore_get_root(src_rp)
+ restore_check_paths(src_rp, dest_rp, restore_as_of)
+ restore_set_fs_globals(Globals.rbdir)
+ src_rp = restore_init_quoting(src_rp)
+ restore_check_backup_dir(restore_root, src_rp, restore_as_of)
+ if restore_as_of:
+ try: time = Time.genstrtotime(restore_timestr)
+ except Time.TimeException, exc: Log.FatalError(str(exc))
+ else: time = src_rp.getinctime()
+ inc_rpath = Globals.rbdir.append_path('increments', restore_index)
+ restore_set_select(restore_root, dest_rp)
+ restore_start_log(src_rp, dest_rp, time)
+ restore.Restore(restore_root.new_index(restore_index),
+ inc_rpath, dest_rp, time)
+ Log("Restore finished", 4)
+
+def restore_init_quoting(src_rp):
+ """Change rpaths into quoted versions of themselves if necessary"""
+ global restore_root
+ if not Globals.chars_to_quote: return src_rp
+ for conn in Globals.connections: conn.FilenameMapping.set_init_quote_vals()
+ restore_root = FilenameMapping.get_quotedrpath(restore_root)
+ SetConnections.UpdateGlobal(
+ 'rbdir', FilenameMapping.get_quotedrpath(Globals.rbdir))
+ return FilenameMapping.get_quotedrpath(src_rp)
+
+def restore_set_fs_globals(target):
+ """Use fs_abilities to set the globals that depend on filesystem"""
+ target_fsa = fs_abilities.FSAbilities().init_readwrite(target, 0)
+ SetConnections.UpdateGlobal('read_acls', target_fsa.acls)
+ SetConnections.UpdateGlobal('write_acls', target_fsa.acls)
+ SetConnections.UpdateGlobal('read_eas', target_fsa.eas)
+ SetConnections.UpdateGlobal('write_eas', target_fsa.eas)
+ if Globals.read_eas: target.get_ea()
+ SetConnections.UpdateGlobal('preserve_hardlinks', target_fsa.hardlinks)
+ SetConnections.UpdateGlobal('change_ownership', target_fsa.ownership)
+
+ mirror_fsa = fs_abilities.FSAbilities().init_readwrite(Globals.rbdir)
+ if Globals.chars_to_quote is None: # otherwise already overridden
+ if mirror_fsa.chars_to_quote:
+ SetConnections.UpdateGlobal('chars_to_quote',
+ mirror_fsa.chars_to_quote)
+ else: SetConnections.UpdateGlobal('chars_to_quote', "")
def restore_set_select(mirror_rp, target):
"""Set the selection iterator on mirror side from command line args
@@ -416,7 +431,7 @@ def restore_set_select(mirror_rp, target):
def restore_start_log(rpin, target, time):
"""Open restore log file, log initial message"""
- try: Log.open_logfile(datadir.append("restore.log"))
+ try: Log.open_logfile(Globals.rbdir.append("restore.log"))
except LoggerError, e: Log("Warning, " + str(e), 2)
# Log following message at file verbosity 3, but term verbosity 4
@@ -430,34 +445,29 @@ def restore_check_paths(rpin, rpout, restoreasof = None):
if not restoreasof:
if not rpin.lstat():
Log.FatalError("Source file %s does not exist" % rpin.path)
- if Globals.quoting_enabled:
- rpin = FilenameMapping.get_quotedrpath(rpin, 1)
- if not rpin.isincfile():
- Log.FatalError("""File %s does not look like an increment file.
-
-Try restoring from an increment file (the filenames look like
-"foobar.2001-09-01T04:49:04-07:00.diff").""" % rpin.path)
-
- if not rpout: rpout = rpath.RPath(Globals.local_connection,
- rpin.getincbase_str())
- if rpout.lstat() and not force:
+ if not force and rpout.lstat() and (not rpout.isdir() or rpout.listdir()):
Log.FatalError("Restore target %s already exists, "
"specify --force to overwrite." % rpout.path)
- return rpin, rpout
-def restore_check_backup_dir(rpin):
+def restore_check_backup_dir(mirror_root, src_rp, restore_as_of):
"""Make sure backup dir root rpin is in consistent state"""
- result = checkdest_need_check(rpin)
+ if not restore_as_of and not src_rp.isincfile():
+ Log.FatalError("""File %s does not look like an increment file.
+
+Try restoring from an increment file (the filenames look like
+"foobar.2001-09-01T04:49:04-07:00.diff").""" % src_rp.path)
+
+ result = checkdest_need_check(mirror_root)
if result is None:
Log.FatalError("%s does not appear to be an rdiff-backup directory."
- % (rpin.path,))
+ % (Globals.rbdir.path,))
elif result == 1: Log.FatalError(
- "Previous backup to %s seems to have failed."
- "Rerun rdiff-backup with --check-destination-dir option to revert"
- "directory to state before unsuccessful session." % (rpin.path,))
+ "Previous backup to %s seems to have failed.\nRerun rdiff-backup "
+ "rdiff-with --check-destination-dir option to revert directory "
+ "to state before unsuccessful session." % (mirror_root.path,))
def restore_get_root(rpin):
- """Return (mirror root, index) and set the data dir
+ """Set data dir, restore_root and index, or return None if fail
The idea here is to keep backing up on the path until we find
a directory that contains "rdiff-backup-data". That is the
@@ -470,7 +480,7 @@ def restore_get_root(rpin):
funny way, using symlinks or somesuch.
"""
- global datadir
+ global restore_root, restore_index
if rpin.isincfile(): relpath = rpin.getincbase().path
else: relpath = rpin.path
pathcomps = os.path.join(rpin.conn.os.getcwd(), relpath).split("/")
@@ -482,23 +492,25 @@ def restore_get_root(rpin):
if (parent_dir.isdir() and
"rdiff-backup-data" in parent_dir.listdir()): break
i = i-1
- else: Log.FatalError("Unable to find rdiff-backup-data directory")
-
- if not Globals.quoting_enabled: rootrp = parent_dir
- else: rootrp = FilenameMapping.get_quotedrpath(parent_dir)
- Log("Using mirror root directory %s" % rootrp.path, 6)
+ else: return None
- datadir = rootrp.append_path("rdiff-backup-data")
- SetConnections.UpdateGlobal('rbdir', datadir)
- if not datadir.isdir():
+ restore_root = parent_dir
+ Log("Using mirror root directory %s" % restore_root.path, 6)
+ SetConnections.UpdateGlobal('rbdir',
+ restore_root.append_path("rdiff-backup-data"))
+ if not Globals.rbdir.isdir():
Log.FatalError("Unable to read rdiff-backup-data directory %s" %
- datadir.path)
+ Globals.rbdir.path)
from_datadir = tuple(pathcomps[i:])
if not from_datadir or from_datadir[0] != "rdiff-backup-data":
- return (rootrp, from_datadir) # in mirror, not increments
- assert from_datadir[1] == "increments"
- return (rootrp, from_datadir[2:])
+ restore_index = from_datadir # in mirror, not increments
+ else:
+ assert (from_datadir[1] == "increments" or
+ (len(from_datadir) == 2 and
+ from_datadir[1].startswith('increments'))), from_datadir
+ restore_index = from_datadir[2:]
+ return 1
def ListIncrements(rp):
@@ -555,7 +567,7 @@ def rom_check_dir(rootrp):
rootrp.append_path("rdiff-backup-data"))
if not Globals.rbdir.isdir():
Log.FatalError("Unable to open rdiff-backup-data dir %s" %
- (datadir.path,))
+ (Globals.rbdir.path,))
checkdest_if_necessary(rootrp)
@@ -597,6 +609,9 @@ def CheckDest(dest_rp):
def checkdest_need_check(dest_rp):
"""Return None if no dest dir found, 1 if dest dir needs check, 0 o/w"""
if not dest_rp.isdir() or not Globals.rbdir.isdir(): return None
+ if Globals.rbdir.listdir() == ['chars_to_quote']:
+ # This may happen the first backup just after we test for quoting
+ return None
curmirroot = Globals.rbdir.append("current_mirror")
curmir_incs = restore.get_inclist(curmirroot)
if not curmir_incs: