diff options
Diffstat (limited to 'rdiff-backup/src/main.py')
-rwxr-xr-x | rdiff-backup/src/main.py | 498 |
1 files changed, 0 insertions, 498 deletions
diff --git a/rdiff-backup/src/main.py b/rdiff-backup/src/main.py deleted file mode 100755 index e8a15af..0000000 --- a/rdiff-backup/src/main.py +++ /dev/null @@ -1,498 +0,0 @@ -#!/usr/bin/python - -execfile("setconnections.py") -import getopt, sys, re - -####################################################################### -# -# main - Start here: Read arguments, set global settings, etc. -# - -class Main: - def __init__(self): - self.action = None - self.remote_cmd, self.remote_schema = None, None - self.force = None - self.select_opts, self.select_mirror_opts = [], [] - self.select_files = [] - - def parse_cmdlineoptions(self, arglist): - """Parse argument list and set global preferences""" - def sel_fl(filename): - """Helper function for including/excluding filelists below""" - try: return open(filename, "r") - except IOError: Log.FatalError("Error opening file %s" % filename) - - try: optlist, self.args = getopt.getopt(arglist, "blmr:sv:V", - ["backup-mode", "calculate-average", - "change-source-perms", "chars-to-quote=", - "checkpoint-interval=", "current-time=", "exclude=", - "exclude-device-files", "exclude-filelist=", - "exclude-filelist-stdin", "exclude-mirror=", - "exclude-regexp=", "force", "include=", - "include-filelist=", "include-filelist-stdin", - "include-regexp=", "list-increments", "mirror-only", - "no-compression", "no-compression-regexp=", - "no-hard-links", "no-resume", "null-separator", - "parsable-output", "print-statistics", "quoting-char=", - "remote-cmd=", "remote-schema=", "remove-older-than=", - "restore-as-of=", "resume", "resume-window=", "server", - "ssh-no-compression", "terminal-verbosity=", - "test-server", "verbosity", "version", "windows-mode", - "windows-time-format"]) - except getopt.error, e: - self.commandline_error("Bad commandline options: %s" % str(e)) - - for opt, arg in optlist: - if opt == "-b" or opt == "--backup-mode": self.action = "backup" - elif opt == "--calculate-average": - self.action = "calculate-average" - elif opt == "--change-source-perms": - Globals.set('change_source_perms', 1) - elif opt == "--chars-to-quote": - Globals.set('chars_to_quote', arg) - Globals.set('quoting_enabled', 1) - elif opt == "--checkpoint-interval": - Globals.set_integer('checkpoint_interval', arg) - elif opt == "--current-time": - Globals.set_integer('current_time', arg) - elif opt == "--exclude": self.select_opts.append((opt, arg)) - elif opt == "--exclude-device-files": - self.select_opts.append((opt, arg)) - elif opt == "--exclude-filelist": - self.select_opts.append((opt, arg)) - self.select_files.append(sel_fl(arg)) - elif opt == "--exclude-filelist-stdin": - self.select_opts.append(("--exclude-filelist", - "standard input")) - self.select_files.append(sys.stdin) - elif opt == "--exclude-mirror": - self.select_mirror_opts.append(("--exclude", arg)) - elif opt == "--exclude-regexp": self.select_opts.append((opt, arg)) - elif opt == "--force": self.force = 1 - elif opt == "--include": self.select_opts.append((opt, arg)) - elif opt == "--include-filelist": - self.select_opts.append((opt, arg)) - self.select_files.append(sel_fl(arg)) - elif opt == "--include-filelist-stdin": - self.select_opts.append(("--include-filelist", - "standard input")) - self.select_files.append(sys.stdin) - elif opt == "--include-regexp": - self.select_opts.append((opt, arg)) - elif opt == "-l" or opt == "--list-increments": - self.action = "list-increments" - elif opt == "-m" or opt == "--mirror-only": self.action = "mirror" - elif opt == "--no-compression": Globals.set("compression", None) - elif opt == "--no-compression-regexp": - Globals.set("no_compression_regexp_string", arg) - elif opt == "--no-hard-links": Globals.set('preserve_hardlinks', 0) - elif opt == '--no-resume': Globals.resume = 0 - elif opt == "--null-separator": Globals.set("null_separator", 1) - elif opt == "-r" or opt == "--restore-as-of": - self.restore_timestr = arg - self.action = "restore-as-of" - 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 == "--remote-cmd": self.remote_cmd = arg - elif opt == "--remote-schema": self.remote_schema = arg - elif opt == "--remove-older-than": - self.remove_older_than_string = arg - self.action = "remove-older-than" - elif opt == '--resume': Globals.resume = 1 - elif opt == '--resume-window': - Globals.set_integer('resume_window', arg) - elif opt == "-s" or opt == "--server": self.action = "server" - elif opt == "--ssh-no-compression": - Globals.set('ssh_compression', None) - elif opt == "--terminal-verbosity": Log.setterm_verbosity(arg) - elif opt == "--test-server": self.action = "test-server" - elif opt == "-V" or opt == "--version": - print "rdiff-backup " + Globals.version - sys.exit(0) - elif opt == "-v" or opt == "--verbosity": Log.setverbosity(arg) - elif opt == "--windows-mode": - Globals.set('time_separator', "_") - Globals.set('chars_to_quote', ":") - Globals.set('quoting_enabled', 1) - elif opt == '--windows-time-format': - Globals.set('time_separator', "_") - else: Log.FatalError("Unknown option %s" % opt) - - def set_action(self): - """Check arguments and try to set self.action""" - l = len(self.args) - if not self.action: - if l == 0: self.commandline_error("No arguments given") - elif l == 1: self.action = "restore" - elif l == 2: - if RPath(Globals.local_connection, self.args[0]).isincfile(): - self.action = "restore" - else: self.action = "backup" - else: self.commandline_error("Too many arguments given") - - if l == 0 and self.action != "server" and self.action != "test-server": - self.commandline_error("No arguments given") - if l > 0 and self.action == "server": - self.commandline_error("Too many arguments given") - if l < 2 and (self.action == "backup" or self.action == "mirror" or - self.action == "restore-as-of"): - self.commandline_error("Two arguments are required " - "(source, destination).") - if l == 2 and (self.action == "list-increments" or - self.action == "remove-older-than"): - self.commandline_error("Only use one argument, " - "the root of the backup directory") - if l > 2 and self.action != "calculate-average": - self.commandline_error("Too many arguments given") - - def commandline_error(self, message): - sys.stderr.write("Error: %s\n" % message) - sys.stderr.write("See the rdiff-backup manual page for instructions\n") - sys.exit(1) - - def misc_setup(self, rps): - """Set default change ownership flag, umask, relay regexps""" - if ((len(rps) == 2 and rps[1].conn.os.getuid() == 0) or - (len(rps) < 2 and os.getuid() == 0)): - # Allow change_ownership if destination connection is root - for conn in Globals.connections: - conn.Globals.set('change_ownership', 1) - for rp in rps: rp.setdata() # Update with userinfo - - os.umask(077) - Time.setcurtime(Globals.current_time) - FilenameMapping.set_init_quote_vals() - Globals.set("isclient", 1) - SetConnections.UpdateGlobal("client_conn", Globals.local_connection) - - # This is because I originally didn't think compiled regexps - # could be pickled, and so must be compiled on remote side. - Globals.postset_regexp('no_compression_regexp', - Globals.no_compression_regexp_string) - - for conn in Globals.connections: Robust.install_signal_handlers() - - def take_action(self, rps): - """Do whatever self.action says""" - if self.action == "server": - PipeConnection(sys.stdin, sys.stdout).Server() - elif self.action == "backup": self.Backup(rps[0], rps[1]) - elif self.action == "restore": self.Restore(*rps) - elif self.action == "restore-as-of": self.RestoreAsOf(rps[0], rps[1]) - elif self.action == "mirror": self.Mirror(rps[0], rps[1]) - elif self.action == "test-server": SetConnections.TestConnections() - elif self.action == "list-increments": self.ListIncrements(rps[0]) - elif self.action == "remove-older-than": self.RemoveOlderThan(rps[0]) - elif self.action == "calculate-average": self.CalculateAverage(rps) - else: raise AssertionError("Unknown action " + self.action) - - def cleanup(self): - """Do any last minute cleaning before exiting""" - Log("Cleaning up", 6) - Log.close_logfile() - if not Globals.server: SetConnections.CloseConnections() - - def Main(self, arglist): - """Start everything up!""" - self.parse_cmdlineoptions(arglist) - self.set_action() - rps = SetConnections.InitRPs(self.args, - self.remote_schema, self.remote_cmd) - self.misc_setup(rps) - self.take_action(rps) - self.cleanup() - - - def Mirror(self, src_rp, dest_rp): - """Turn dest_path into a copy of src_path""" - Log("Mirroring %s to %s" % (src_rp.path, dest_rp.path), 5) - self.mirror_check_paths(src_rp, dest_rp) - # Since no "rdiff-backup-data" dir, use root of destination. - SetConnections.UpdateGlobal('rbdir', dest_rp) - SetConnections.BackupInitConnections(src_rp.conn, dest_rp.conn) - HighLevel.Mirror(src_rp, dest_rp) - - def mirror_check_paths(self, rpin, rpout): - """Check paths and return rpin, rpout""" - if not rpin.lstat(): - Log.FatalError("Source directory %s does not exist" % rpin.path) - if rpout.lstat() and not self.force: - Log.FatalError( -"""Destination %s exists so continuing could mess it up. Run -rdiff-backup with the --force option if you want to mirror anyway.""" % - rpout.path) - - - def Backup(self, rpin, rpout): - """Backup, possibly incrementally, src_path to dest_path.""" - SetConnections.BackupInitConnections(rpin.conn, rpout.conn) - self.backup_init_select(rpin, rpout) - self.backup_init_dirs(rpin, rpout) - RSI = Globals.backup_writer.Resume.ResumeCheck() - SaveState.init_filenames() - if self.prevtime: - Time.setprevtime(self.prevtime) - HighLevel.Mirror_and_increment(rpin, rpout, self.incdir, RSI) - else: HighLevel.Mirror(rpin, rpout, self.incdir, RSI) - self.backup_touch_curmirror(rpin, rpout) - - def backup_init_select(self, rpin, rpout): - """Create Select objects on source and dest connections""" - rpin.conn.Globals.set_select(DSRPath(1, rpin), self.select_opts, - None, *self.select_files) - rpout.conn.Globals.set_select(DSRPath(None, rpout), - self.select_mirror_opts, 1) - - def backup_init_dirs(self, rpin, rpout): - """Make sure rpin and rpout are valid, init data dir and logging""" - if rpout.lstat() and not rpout.isdir(): - if not self.force: - Log.FatalError("Destination %s exists and is not a " - "directory" % rpout.path) - else: - Log("Deleting %s" % rpout.path, 3) - rpout.delete() - - 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) - - self.datadir = rpout.append("rdiff-backup-data") - SetConnections.UpdateGlobal('rbdir', self.datadir) - self.incdir = RPath(rpout.conn, os.path.join(self.datadir.path, - "increments")) - self.prevtime = self.backup_get_mirrortime() - - if rpout.lstat(): - if rpout.isdir() and not rpout.listdir(): # rpout is empty dir - rpout.chmod(0700) # just make sure permissions aren't too lax - elif not self.datadir.lstat() and not self.force: - Log.FatalError( -"""Destination directory %s exists, but does not look like a -rdiff-backup directory. Running rdiff-backup like this could mess up -what is currently in it. If you want to 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 self.datadir.lstat(): self.datadir.mkdir() - if Log.verbosity > 0: - Log.open_logfile(self.datadir.append("backup.log")) - self.backup_warn_if_infinite_regress(rpin, rpout) - - def backup_warn_if_infinite_regress(self, rpin, rpout): - """Warn user if destination area contained in source area""" - if rpout.conn is rpin.conn: # it's meaningful to compare paths - if ((len(rpout.path) > len(rpin.path)+1 and - rpout.path[:len(rpin.path)] == rpin.path and - rpout.path[len(rpin.path)] == '/') or - (rpin.path == "." and rpout.path[0] != '/' and - rpout.path[:2] != '..')): - # Just a few heuristics, we don't have to get every case - if Globals.backup_reader.Globals.select_source \ - .Select(rpout): Log( -"""Warning: The destination directory '%s' may be contained in the -source directory '%s'. This could cause an infinite regress. You -may need to use the --exclude option.""" % (rpout.path, rpin.path), 2) - - def backup_get_mirrorrps(self): - """Return list of current_mirror rps""" - if not self.datadir.isdir(): return [] - mirrorrps = [self.datadir.append(fn) for fn in self.datadir.listdir() - if fn.startswith("current_mirror.")] - return filter(lambda rp: rp.isincfile(), mirrorrps) - - def backup_get_mirrortime(self): - """Return time in seconds of previous mirror, or None if cannot""" - mirrorrps = self.backup_get_mirrorrps() - if not mirrorrps: return None - if len(mirrorrps) > 1: - Log( -"""Warning: duplicate current_mirror files found. Perhaps something -went wrong during your last backup? Using """ + mirrorrps[-1].path, 2) - - timestr = mirrorrps[-1].getinctime() - return Time.stringtotime(timestr) - - def backup_touch_curmirror(self, rpin, rpout): - """Make a file like current_mirror.time.data to record time - - Also updates rpout so mod times don't get messed up. - - """ - map(RPath.delete, self.backup_get_mirrorrps()) - mirrorrp = self.datadir.append("current_mirror.%s.%s" % - (Time.curtimestr, "data")) - Log("Touching mirror marker %s" % mirrorrp.path, 6) - mirrorrp.touch() - RPath.copy_attribs(rpin, rpout) - - - def Restore(self, src_rp, dest_rp = 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. - - """ - rpin, rpout = self.restore_check_paths(src_rp, dest_rp) - time = Time.stringtotime(rpin.getinctime()) - self.restore_common(rpin, rpout, time) - - def RestoreAsOf(self, 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 - - """ - self.restore_check_paths(rpin, target, 1) - try: time = Time.genstrtotime(self.restore_timestr) - except TimeException, exc: Log.FatalError(str(exc)) - self.restore_common(rpin, target, time) - - def restore_common(self, rpin, target, time): - """Restore operation common to Restore and RestoreAsOf""" - Log("Starting Restore", 5) - mirror_root, index = self.restore_get_root(rpin) - mirror = mirror_root.new_index(index) - inc_rpath = self.datadir.append_path('increments', index) - self.restore_init_select(mirror_root, target) - Log.open_logfile(self.datadir.append("restore.log")) - Restore.Restore(inc_rpath, mirror, target, time) - - def restore_check_paths(self, rpin, rpout, restoreasof = None): - """Check paths and return pair of corresponding rps""" - if not restoreasof: - if not rpin.lstat(): - Log.FatalError("Source file %s does not exist" % rpin.path) - elif 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(Globals.local_connection, - rpin.getincbase_str()) - if rpout.lstat(): - Log.FatalError("Restore target %s already exists, " - "and will not be overwritten." % rpout.path) - return rpin, rpout - - def restore_init_select(self, rpin, rpout): - """Initialize Select - - Unlike the backup selections, here they are on the local - connection, because the backup operation is pipelined in a way - the restore operation isn't. - - """ - Globals.set_select(DSRPath(1, rpin), self.select_mirror_opts, None) - Globals.set_select(DSRPath(None, rpout), self.select_opts, None, - *self.select_files) - - def restore_get_root(self, rpin): - """Return (mirror root, index) and set the data dir - - The idea here is to keep backing up on the path until we find - a directory that contains "rdiff-backup-data". That is the - mirror root. If the path from there starts - "rdiff-backup-data/increments*", then the index is the - remainder minus that. Otherwise the index is just the path - minus the root. - - All this could fail if the increment file is pointed to in a - funny way, using symlinks or somesuch. - - """ - if rpin.isincfile(): relpath = rpin.getincbase().path - else: relpath = rpin.path - pathcomps = os.path.join(rpin.conn.os.getcwd(), relpath).split("/") - assert len(pathcomps) >= 2 # path should be relative to / - - i = len(pathcomps) - while i >= 2: - parent_dir = RPath(rpin.conn, "/".join(pathcomps[:i])) - 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") - - self.rootrp = rootrp = parent_dir - Log("Using mirror root directory %s" % rootrp.path, 6) - - self.datadir = rootrp.append_path("rdiff-backup-data") - SetConnections.UpdateGlobal('rbdir', self.datadir) - if not self.datadir.isdir(): - Log.FatalError("Unable to read rdiff-backup-data directory %s" % - self.datadir.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:]) - - - def ListIncrements(self, rp): - """Print out a summary of the increments and their times""" - mirror_root, index = self.restore_get_root(rp) - Globals.rbdir = datadir = \ - mirror_root.append_path("rdiff-backup-data") - mirrorrp = mirror_root.new_index(index) - inc_rpath = datadir.append_path('increments', index) - incs = Restore.get_inclist(inc_rpath) - mirror_time = Restore.get_mirror_time() - if Globals.parsable_output: - print Manage.describe_incs_parsable(incs, mirror_time, mirrorrp) - else: print Manage.describe_incs_human(incs, mirror_time, mirrorrp) - - - def CalculateAverage(self, rps): - """Print out the average of the given statistics files""" - statobjs = map(lambda rp: StatsObj().read_stats_from_rp(rp), rps) - average_stats = StatsObj().set_to_average(statobjs) - print average_stats.get_stats_logstring( - "Average of %d stat files" % len(rps)) - - - def RemoveOlderThan(self, rootrp): - """Remove all increment files older than a certain time""" - datadir = rootrp.append("rdiff-backup-data") - if not datadir.lstat() or not datadir.isdir(): - Log.FatalError("Unable to open rdiff-backup-data dir %s" % - (datadir.path,)) - - try: time = Time.genstrtotime(self.remove_older_than_string) - except TimeError, exc: Log.FatalError(str(exc)) - timep = Time.timetopretty(time) - Log("Deleting increment(s) before %s" % timep, 4) - - itimes = [Time.stringtopretty(inc.getinctime()) - for inc in Restore.get_inclist(datadir.append("increments")) - if Time.stringtotime(inc.getinctime()) < time] - - if not itimes: - Log.FatalError("No increments older than %s found" % timep) - inc_pretty_time = "\n".join(itimes) - if len(itimes) > 1 and not self.force: - Log.FatalError("Found %d relevant increments, dated:\n%s" - "\nIf you want to delete multiple increments in this way, " - "use the --force." % (len(itimes), inc_pretty_time)) - - Log("Deleting increment%sat times:\n%s" % - (len(itimes) == 1 and " " or "s ", inc_pretty_time), 3) - Manage.delete_earlier_than(datadir, time) - - -Globals.Main = Main() -if __name__ == "__main__" and not globals().has_key('__no_execute__'): - Globals.Main.Main(sys.argv[1:]) |