def set_special_escapes(self, rbdir): """Set escape_dos_devices and escape_trailing_spaces from rdiff-backup-data dir, just like chars_to_quote""" se_rp = rbdir.append("special_escapes") if se_rp.lstat(): se = se_rp.get_data().split("\n") actual_edd = ("escape_dos_devices" in se) actual_ets = ("escape_trailing_spaces" in se) else: log.Log( "Warning: special_escapes file not found,\n" "will assume need to escape DOS devices and trailing " "spaces based on file systems.", 2) if getattr(self, "src_fsa", None) is not None: actual_edd = (self.src_fsa.escape_dos_devices and not self.dest_fsa.escape_dos_devices) actual_ets = (self.src_fsa.escape_trailing_spaces and not self.dest_fsa.escape_trailing_spaces) else: # Single filesystem operation actual_edd = self.dest_fsa.escape_dos_devices actual_ets = self.dest_fsa.escape_trailing_spaces SetConnections.UpdateGlobal('escape_dos_devices', actual_edd) log.Log("Backup: escape_dos_devices = %d" % actual_edd, 4) SetConnections.UpdateGlobal('escape_trailing_spaces', actual_ets) log.Log("Backup: escape_trailing_spaces = %d" % actual_ets, 4)
def update_triple(self, fsa_support, attr_triple): """Update global vars from single fsa test""" active_attr, write_attr, conn_attr = attr_triple if Globals.get(active_attr) == 0: return # don't override 0 for attr in attr_triple: SetConnections.UpdateGlobal(attr, None) if not fsa_support: return SetConnections.UpdateGlobal(active_attr, 1) SetConnections.UpdateGlobal(write_attr, 1) self.conn.Globals.set_local(conn_attr, 1)
def set_chars_to_quote(self, rbdir): """Set chars_to_quote from rdiff-backup-data dir""" if Globals.chars_to_quote is not None: return # already overridden ctq_rp = rbdir.append("chars_to_quote") if ctq_rp.lstat(): SetConnections.UpdateGlobal("chars_to_quote", ctq_rp.get_data()) else: log.Log("Warning: chars_to_quote file not found,\n" "assuming no quoting in backup repository.", 2) SetConnections.UpdateGlobal("chars_to_quote", "")
def update_triple(self, src_support, dest_support, attr_triple): """Many of the settings have a common form we can handle here""" active_attr, write_attr, conn_attr = attr_triple if Globals.get(active_attr) == 0: return # don't override 0 for attr in attr_triple: SetConnections.UpdateGlobal(attr, None) if not src_support: return # if source doesn't support, nothing SetConnections.UpdateGlobal(active_attr, 1) self.in_conn.Globals.set_local(conn_attr, 1) if dest_support: SetConnections.UpdateGlobal(write_attr, 1) self.out_conn.Globals.set_local(conn_attr, 1)
def take_action(rps): """Do whatever action says""" if action == "server": connection.PipeConnection(sys.stdin, sys.stdout).Server() sys.exit(0) elif action == "backup": Backup(rps[0], rps[1]) elif action == "calculate-average": CalculateAverage(rps) elif action == "check-destination-dir": CheckDest(rps[0]) elif action.startswith("compare"): Compare(action, rps[0], rps[1]) elif action == "list-at-time": ListAtTime(rps[0]) elif action == "list-changed-since": ListChangedSince(rps[0]) elif action == "list-increments": ListIncrements(rps[0]) elif action == 'list-increment-sizes': ListIncrementSizes(rps[0]) elif action == "remove-older-than": RemoveOlderThan(rps[0]) elif action == "restore": Restore(*rps) elif action == "restore-as-of": Restore(rps[0], rps[1], 1) elif action == "test-server": SetConnections.TestConnections(rps) elif action == "verify": Verify(rps[0]) else: raise AssertionError("Unknown action " + action)
def backup_quoted_rpaths(rpout): """Get QuotedRPath versions of important RPaths. Return rpout""" global incdir SetConnections.UpdateGlobal( 'rbdir', FilenameMapping.get_quotedrpath(Globals.rbdir)) incdir = FilenameMapping.get_quotedrpath(incdir) return FilenameMapping.get_quotedrpath(rpout)
def update_triple(self, src_support, dest_support, attr_triple): """Update global settings for feature based on fsa results This is slightly different from BackupSetGlobals.update_triple because (using the mirror_metadata file) rpaths from the source may have more information than the file system supports. """ active_attr, write_attr, conn_attr = attr_triple if Globals.get(active_attr) == 0: return # don't override 0 for attr in attr_triple: SetConnections.UpdateGlobal(attr, None) if not dest_support: return # if dest doesn't support, do nothing SetConnections.UpdateGlobal(active_attr, 1) self.out_conn.Globals.set_local(conn_attr, 1) self.out_conn.Globals.set_local(write_attr, 1) if src_support: self.in_conn.Globals.set_local(conn_attr, 1)
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_root(rpin): """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 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. """ global restore_root, restore_index, restore_root_set if rpin.isincfile(): relpath = rpin.getincbase().path else: relpath = rpin.path if rpin.conn is not Globals.local_connection: # For security checking consistency, don't get absolute path pathcomps = relpath.split('/') else: pathcomps = os.path.join(os.getcwd(), relpath).split("/") if not pathcomps[0]: min_len_pathcomps = 2 # treat abs paths differently else: min_len_pathcomps = 1 i = len(pathcomps) while i >= min_len_pathcomps: parent_dir = rpath.RPath(rpin.conn, "/".join(pathcomps[:i])) if (parent_dir.isdir() and parent_dir.readable() and "rdiff-backup-data" in parent_dir.listdir()): break if parent_dir.path == rpin.conn.Globals.get('restrict_path'): return None i = i - 1 else: return None restore_root = parent_dir Log("Using mirror root directory %s" % restore_root.path, 6) if restore_root.conn is Globals.local_connection: Security.reset_restrict_path(restore_root) 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" % Globals.rbdir.path) from_datadir = tuple(pathcomps[i:]) if not from_datadir or from_datadir[0] != "rdiff-backup-data": 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:] restore_root_set = 1 return 1
def misc_setup(rps): """Set default change ownership flag, umask, relay regexps""" os.umask(077) Time.setcurtime(Globals.current_time) SetConnections.UpdateGlobal("client_conn", Globals.local_connection) Globals.postset_regexp('no_compression_regexp', Globals.no_compression_regexp_string) for conn in Globals.connections: conn.robust.install_signal_handlers() conn.Hardlink.initialize_dictionaries()
def set_must_escape_trailing_spaces(self, rbdir): """If local ets or src ets, then must escape """ # Disable this for 1.2.4 SetConnections.UpdateGlobal('must_escape_trailing_spaces', 0) return try: space_rp = rbdir.append("test ") space_rp.touch() if space_rp.lstat(): local_ets = 0 space_rp.delete() else: local_ets = 1 except (OSError, IOError): local_ets = 1 SetConnections.UpdateGlobal('must_escape_trailing_spaces', \ self.src_fsa.escape_trailing_spaces or local_ets) log.Log("Backup: must_escape_trailing_spaces = %d" % \ (self.src_fsa.escape_trailing_spaces or local_ets), 4)
def set_must_escape_dos_devices(self, rbdir): """If local edd or src edd, then must escape """ try: device_rp = rbdir.append("con") if device_rp.lstat(): local_edd = 1 else: local_edd = 0 except (OSError): local_edd = 1 SetConnections.UpdateGlobal('must_escape_dos_devices', \ self.src_fsa.escape_dos_devices or local_edd) log.Log("Backup: must_escape_dos_devices = %d" % \ (self.src_fsa.escape_dos_devices or local_edd), 4)
def set_special_escapes(self, rbdir): """Escaping DOS devices and trailing periods/spaces works like regular filename escaping. If only the destination requires it, then we do it. Otherwise, it is not necessary, since the files couldn't have been created in the first place. We also record whether we have done it in order to handle the case where a volume which was escaped is later restored by an OS that does not require it.""" suggested_edd = (self.dest_fsa.escape_dos_devices and not \ self.src_fsa.escape_dos_devices) suggested_ets = (self.dest_fsa.escape_trailing_spaces and not \ self.src_fsa.escape_trailing_spaces) se_rp = rbdir.append("special_escapes") if not se_rp.lstat(): actual_edd, actual_ets = suggested_edd, suggested_ets se = "" if actual_edd: se = se + "escape_dos_devices\n" if actual_ets: se = se + "escape_trailing_spaces\n" se_rp.write_string(se) else: se = se_rp.get_data().split("\n") actual_edd = ("escape_dos_devices" in se) actual_ets = ("escape_trailing_spaces" in se) if actual_edd != suggested_edd and not suggested_edd: log.Log( "Warning: System no longer needs DOS devices escaped, " "but we will retain for backwards compatibility.", 2) if actual_ets != suggested_ets and not suggested_ets: log.Log( "Warning: System no longer needs trailing spaces or " "periods escaped, but we will retain for backwards " "compatibility.", 2) SetConnections.UpdateGlobal('escape_dos_devices', actual_edd) log.Log("Backup: escape_dos_devices = %d" % actual_edd, 4) SetConnections.UpdateGlobal('escape_trailing_spaces', actual_ets) log.Log("Backup: escape_trailing_spaces = %d" % actual_ets, 4)
def get_readonly_fsa(desc_string, rp): """Return an fsa with given description_string Will be initialized read_only with given RPath rp. We separate this out into a separate function so the request can be vetted by the security module. """ if os.name == 'nt': log.Log("Hardlinks disabled by default on Windows", 4) SetConnections.UpdateGlobal('preserve_hardlinks', 0) return FSAbilities(desc_string).init_readonly(rp)
def Main(arglist): """Start everything up!""" parse_cmdlineoptions(arglist) check_action() cmdpairs = SetConnections.get_cmd_pairs(args, remote_schema, remote_cmd) Security.initialize(action or "mirror", cmdpairs) rps = map(SetConnections.cmdpair2rp, cmdpairs) final_set_action(rps) misc_setup(rps) take_action(rps) cleanup() if return_val is not None: sys.exit(return_val)
def set_must_escape_dos_devices(self, rbdir): """If local edd or src edd, then must escape """ if getattr(self, "src_fsa", None) is not None: src_edd = self.src_fsa.escape_dos_devices else: src_edd = 0 try: device_rp = rbdir.append("con") if device_rp.lstat(): local_edd = 1 else: local_edd = 0 except (OSError): local_edd = 1 SetConnections.UpdateGlobal('must_escape_dos_devices', \ src_edd or local_edd) log.Log("Restore: must_escape_dos_devices = %d" % \ (src_edd or local_edd), 4)
def set_chars_to_quote(self, rbdir, force): """Set chars_to_quote setting for backup session Unlike most other options, the chars_to_quote setting also depends on the current settings in the rdiff-backup-data directory, not just the current fs features. """ (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 Backup(rpin, rpout): """Backup, possibly incrementally, src_path to dest_path.""" global incdir SetConnections.BackupInitConnections(rpin.conn, rpout.conn) backup_check_dirs(rpin, rpout) backup_set_rbdir(rpin, rpout) 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) backup_set_select(rpin) backup_warn_if_infinite_regress(rpin, rpout) if prevtime: Time.setprevtime(prevtime) rpout.conn.Main.backup_touch_curmirror_local(rpin, rpout) backup.Mirror_and_increment(rpin, rpout, incdir) rpout.conn.Main.backup_remove_curmirror_local() else: backup.Mirror(rpin, rpout) rpout.conn.Main.backup_touch_curmirror_local(rpin, rpout) rpout.conn.Main.backup_close_statistics(time.time())
def set_hardlinks(self): if Globals.preserve_hardlinks != 0: SetConnections.UpdateGlobal('preserve_hardlinks', self.dest_fsa.hardlinks)
def cleanup(): """Do any last minute cleaning before exiting""" Log("Cleaning up", 6) if ErrorLog.isopen(): ErrorLog.close() Log.close_logfile() if not Globals.server: SetConnections.CloseConnections()
if not Globals.rbdir.lstat(): try: Globals.rbdir.mkdir() except (OSError, IOError), exc: Log.FatalError( """Could not create rdiff-backup directory %s due to %s Please check that the rdiff-backup user can create files and directories in the destination directory: %s""" % (Globals.rbdir.path, exc, rpout.path)) SetConnections.UpdateGlobal('rbdir', Globals.rbdir) def backup_warn_if_infinite_regress(rpin, rpout): """Warn user if destination area contained in source area""" # Just a few heuristics, we don't have to get every case if rpout.conn is not rpin.conn: return if len(rpout.path) <= len(rpin.path)+1: return if rpout.path[:len(rpin.path)+1] != rpin.path + '/': return relative_rpout_comps = tuple(rpout.path[len(rpin.path)+1:].split('/')) relative_rpout = rpin.new_index(relative_rpout_comps) if not Globals.select_mirror.Select(relative_rpout): return Log( """Warning: The destination directory '%s' may be contained in the source directory '%s'. This could cause an infinite regress. You
def set_unicode_filenames(self): SetConnections.UpdateGlobal('use_unicode_paths', self.dest_fsa.unicode_filenames)
def set_escape_trailing_spaces(self): SetConnections.UpdateGlobal('escape_trailing_spaces', \ self.dest_fsa.escape_trailing_spaces)
def set_compatible_timestamps(self): if Globals.chars_to_quote.find(":") > -1: SetConnections.UpdateGlobal('use_compatible_timestamps', 1) Time.setcurtime( Time.curtime) # update Time.curtimestr on all conns log.Log("Enabled use_compatible_timestamps", 4)
def set_symlink_perms(self): SetConnections.UpdateGlobal('symlink_perms', self.dest_fsa.symlink_perms)
def set_change_ownership(self): SetConnections.UpdateGlobal('change_ownership', self.dest_fsa.ownership)
def set_fsync_directories(self): SetConnections.UpdateGlobal('fsync_directories', self.dest_fsa.fsync_dirs)
def set_escape_dos_devices(self): SetConnections.UpdateGlobal('escape_dos_devices', \ self.dest_fsa.escape_dos_devices)