diff options
author | ben <ben@2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109> | 2002-03-21 07:22:43 +0000 |
---|---|---|
committer | ben <ben@2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109> | 2002-03-21 07:22:43 +0000 |
commit | 8c37a5bdfdd46d5cfad6e9d67925ddef9ca382bf (patch) | |
tree | 8f19be83962ef31d8ad58429d575c6f17d89c0ea /rdiff-backup/src/main.py | |
parent | 8259a0d8a9ad1396a93cd6320943dc33446ac6ed (diff) | |
download | rdiff-backup-8c37a5bdfdd46d5cfad6e9d67925ddef9ca382bf.tar.gz |
First checkin
git-svn-id: http://svn.savannah.nongnu.org/svn/rdiff-backup/trunk@2 2b77aa54-bcbc-44c9-a7ec-4f6cf2b41109
Diffstat (limited to 'rdiff-backup/src/main.py')
-rwxr-xr-x | rdiff-backup/src/main.py | 401 |
1 files changed, 401 insertions, 0 deletions
diff --git a/rdiff-backup/src/main.py b/rdiff-backup/src/main.py new file mode 100755 index 0000000..24455f6 --- /dev/null +++ b/rdiff-backup/src/main.py @@ -0,0 +1,401 @@ +#!/usr/bin/python + +execfile("highlevel.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.exclude_regstrs = ["/proc"] + self.exclude_mirror_regstrs = [] + + def parse_cmdlineoptions(self): + """Parse argument list and set global preferences""" + try: optlist, self.args = getopt.getopt(sys.argv[1:], "blmv:Vs", + ["backup-mode", "version", "verbosity=", "exclude=", + "exclude-mirror=", "server", "test-server", + "remote-cmd=", "mirror-only", "force", + "change-source-perms", "list-increments", + "remove-older-than=", "remote-schema=", + "include-from-stdin", "terminal-verbosity=", + "exclude-device-files", "resume", "no-resume", + "resume-window=", "windows-time-format", + "checkpoint-interval="]) + except getopt.error: + self.commandline_error("Error parsing commandline options") + + for opt, arg in optlist: + if opt == "-b" or opt == "--backup-mode": self.action = "backup" + elif opt == "--change-source-perms": + Globals.set('change_source_perms', 1) + elif opt == "--checkpoint-interval": + Globals.set_integer('checkpoint_interval', arg) + elif opt == "--exclude": self.exclude_regstrs.append(arg) + elif opt == "--exclude-device-files": + Globals.set('exclude_device_files', 1) + elif opt == "--exclude-mirror": + self.exclude_mirror_regstrs.append(arg) + elif opt == "--force": self.force = 1 + elif opt == "--include-from-stdin": Globals.include_from_stdin = 1 + elif opt == "-l" or opt == "--list-increments": + self.action = "list-increments" + elif opt == "-m" or opt == "--mirror-only": self.action = "mirror" + elif opt == '--no-resume': Globals.resume = 0 + 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 == "--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-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"): + 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: 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, excludes""" + 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) + for regex_string in self.exclude_regstrs: + Globals.add_regexp(regex_string, None) + for regex_string in self.exclude_mirror_regstrs: + Globals.add_regexp(regex_string, 1) + + 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": apply(self.Restore, rps) + 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]) + 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): + """Start everything up!""" + self.parse_cmdlineoptions() + 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) + HighLevel.Mirror(src_rp, dest_rp, None) # No checkpointing - no rbdir + + 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_dirs(rpin, rpout) + Time.setcurtime() + RSI = Resume.ResumeCheck() + if self.prevtime: + Time.setprevtime(self.prevtime) + SaveState.init_filenames(1) + HighLevel.Mirror_and_increment(rpin, rpout, self.incdir, RSI) + else: + SaveState.init_filenames(None) + HighLevel.Mirror(rpin, rpout, 1, RSI) + self.backup_touch_curmirror(rpin, rpout) + + 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() and 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() + Globals.add_regexp(self.datadir.path, 1) + Globals.add_regexp(rpin.append("rdiff-backup-data").path, None) + 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 not DestructiveStepping.isexcluded(rpout, 1): + 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 [] + mirrorfiles = filter(lambda f: f.startswith("current_mirror."), + self.datadir.listdir()) + mirrorrps = map(lambda x: self.datadir.append(x), mirrorfiles) + 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 = self.datadir.append(mirrorrps[-1].path).getinctime() + return Time.stringtotime(timestr) + + def backup_touch_curmirror(self, rpin, rpout): + """Make a file like current_mirror.time.snapshot 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, "snapshot")) + 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 - take src_path to dest_path""" + Log("Starting Restore", 5) + rpin, rpout = self.restore_check_paths(src_rp, dest_rp) + inc_tup = self.restore_get_inctup(rpin) + mirror_base = self.restore_get_mirror(rpin) + rtime = Time.stringtotime(rpin.getinctime()) + Log.open_logfile(self.datadir.append("restore.log")) + HighLevel.Restore(rtime, mirror_base, inc_tup, rpout) + + def restore_check_paths(self, rpin, rpout): + """Check paths and return pair of corresponding rps""" + if not rpin.lstat(): + Log.FatalError("Increment file %s does not exist" % src_path) + 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").""") + + if not rpout: rpout = RPath(Globals.local_connection, + rpin.getincbase_str()) + if rpout.lstat(): + Log.FatalError("Restore target %s already exists. " + "Will not overwrite." % rpout.path) + return rpin, rpout + + def restore_get_inctup(self, rpin): + """Return increment tuple (incrp, list of incs)""" + rpin_dir = rpin.dirsplit()[0] + if not rpin_dir: rpin_dir = "/" + rpin_dir_rp = RPath(rpin.conn, rpin_dir) + incbase = rpin.getincbase() + incbasename = incbase.dirsplit()[1] + inclist = filter(lambda rp: rp.isincfile() and + rp.getincbase_str() == incbasename, + map(rpin_dir_rp.append, rpin_dir_rp.listdir())) + return IndexedTuple((), (incbase, inclist)) + + def restore_get_mirror(self, rpin): + """Return mirror file and set the data dir + + The idea here is to keep backing up on the path until we find + something named "rdiff-backup-data". Then use that as a + reference to calculate the oldfile. This could fail if the + increment file is pointed to in a funny way, using symlinks or + somesuch. + + """ + pathcomps = os.path.join(rpin.conn.os.getcwd(), + rpin.getincbase().path).split("/") + for i in range(1, len(pathcomps)): + datadirrp = RPath(rpin.conn, "/".join(pathcomps[:i+1])) + if pathcomps[i] == "rdiff-backup-data" and datadirrp.isdir(): + break + else: Log.FatalError("Unable to find rdiff-backup-data dir") + + self.datadir = datadirrp + Globals.add_regexp(self.datadir.path, 1) + rootrp = RPath(rpin.conn, "/".join(pathcomps[:i])) + if not rootrp.lstat(): + Log.FatalError("Root of mirror area %s does not exist" % + rootrp.path) + else: Log("Using root mirror %s" % rootrp.path, 6) + + from_datadir = pathcomps[i+1:] + if len(from_datadir) == 1: result = rootrp + elif len(from_datadir) > 1: + result = RPath(rootrp.conn, apply(os.path.join, + [rootrp.path] + from_datadir[1:])) + else: raise RestoreError("Problem finding mirror file") + + Log("Using mirror file %s" % result.path, 6) + return result + + + def ListIncrements(self, rootrp): + """Print out a summary of the increments and their times""" + datadir = self.li_getdatadir(rootrp, + """Unable to open rdiff-backup-data dir. + +The argument to rdiff-backup -l or rdiff-backup --list-increments +should be the root of the target backup directory, of which +rdiff-backup-data is a subdirectory. So, if you ran + +rdiff-backup /home/foo /mnt/back/bar + +earlier, try: + +rdiff-backup -l /mnt/back/bar +""") + print Manage.describe_root_incs(datadir) + + def li_getdatadir(self, rootrp, errormsg): + """Return data dir if can find it, otherwise use errormsg""" + datadir = rootrp.append("rdiff-backup-data") + if not datadir.lstat() or not datadir.isdir(): + Log.FatalError(errormsg) + return datadir + + + def RemoveOlderThan(self, rootrp): + """Remove all increment files older than a certain time""" + datadir = self.li_getdatadir(rootrp, + """Unable to open rdiff-backup-data dir. + +Try finding the increments first using --list-increments.""") + time = self.rot_get_earliest_time() + timep = Time.timetopretty(time) + Log("Deleting increment(s) before %s" % timep, 4) + incobjs = filter(lambda x: x.time < time, Manage.get_incobjs(datadir)) + incobjs_time = ", ".join(map(IncObj.pretty_time, incobjs)) + if not incobjs: + Log.FatalError("No increments older than %s found" % timep) + elif len(incobjs) > 1 and not self.force: + Log.FatalError("Found %d relevant increments, dated %s.\n" + "If you want to delete multiple increments in this way, " + "use the --force." % (len(incobjs), incobjs_time)) + + Log("Deleting increment%sat %s" % (len(incobjs) == 1 and " " or "s ", + incobjs_time), 3) + Manage.delete_earlier_than(datadir, time) + + def rot_get_earliest_time(self): + """Return earliest time in seconds that will not be deleted""" + seconds = Time.intstringtoseconds(self.remove_older_than_string) + return time.time() - seconds + + + +if __name__ == "__main__": + Globals.Main = Main() + Globals.Main.Main() |