From 38370e7c144620fe0e2b9d435d0d8b8bbb9dbae6 Mon Sep 17 00:00:00 2001 From: Anton Khirnov Date: Tue, 20 Oct 2020 12:14:36 +0200 Subject: TargetSSHLVM: resolve remote dirs Should handle remote symlinks properly. --- lbup/targets.py | 40 +++++++++++++++++++--------------------- 1 file changed, 19 insertions(+), 21 deletions(-) diff --git a/lbup/targets.py b/lbup/targets.py index f6f7e82..64dd80f 100644 --- a/lbup/targets.py +++ b/lbup/targets.py @@ -50,8 +50,8 @@ class Target(ABC): raise ValueError('One or more dirs to backup required') self.name = name - self.dirs = list(map(AbsPath, dirs)) - self.excludes = list(map(AbsPath, excludes)) + self.dirs = dirs + self.excludes = excludes if logger is None: self._logger = logging.getLogger('%s.%s' % (self.__class__.__name__, self.name)) @@ -182,16 +182,15 @@ class TargetSSH(Target): return out - def _resolve_remote_bupdir(self, ssh): - bupdir = self._paramiko_exec_cmd(ssh, 'realpath -e ' + self._remote_bupdir).splitlines() - if (len(bupdir) != 1 or len(bupdir[0]) <= 1 or bupdir[0][0] != '/' or - re.search(r'\s', bupdir[0])): - raise BackupException('Invalid BUP_DIR on the remote target: %s' % str(bupdir)) - return bupdir[0] + def _resolve_remote_path(self, ssh, path): + path = self._paramiko_exec_cmd(ssh, 'realpath -e %s' % path).splitlines() + if len(path) != 1: + raise BackupException('Expected exactly one path from realpath', path) + return AbsPath(path[0]) def save(self, dry_run = False): with _ssh_client.SSHConnection(self._remote) as ssh: - remote_bupdir = self._resolve_remote_bupdir(ssh) + remote_bupdir = self._resolve_remote_path(ssh, self._remote_bupdir) bup_exec = ['bup', 'on', '%s@%s' % (self._remote.username, self._remote.host), '-d', remote_bupdir] @@ -236,12 +235,12 @@ class TargetSSHLVM(TargetSSH): """ return 1 - def _resolve_mntdev(self, mntinfo): + def _resolve_mntdev(self, mntinfo, dirs): """ Find out which LV to snapshot. This also checks that all the dirs are on the same LV and no non-trivial - topologies (such as symlinks or bind mounts) are involved, + topologies (like bind mounts) are involved, otherwise a BackupException is raised. Return a tuple of (devnum, mountpoint) @@ -249,7 +248,7 @@ class TargetSSHLVM(TargetSSH): devnum = None mountpoint = None fstype = None - for d in self.dirs: + for d in dirs: mp = mntinfo.mountpoint_for_path(d) e = list(mntinfo.entries_for_mountpoint(mp)) @@ -269,9 +268,6 @@ class TargetSSHLVM(TargetSSH): raise BackupException('Mismatching device numbers/mountpoints', dn, devnum, mp, mountpoint) - # TODO? check that there are no symlinks? - # by running stat maybe? - return (devnum, mountpoint, fstype) def _resolve_lv(self, ssh, devnum): @@ -349,8 +345,10 @@ class TargetSSHLVM(TargetSSH): conn_tgt = stack.enter_context(_ssh_client.SSHConnection(self._remote)) conn_root = stack.enter_context(_ssh_client.SSHConnection(self._root_remote())) - # resolve the path to BUP_DIR on the remote - bupdir = self._resolve_remote_bupdir(conn_tgt) + # resolve the remote paths + bupdir = self._resolve_remote_path(conn_tgt, self._remote_bupdir) + dirs = [self._resolve_remote_path(conn_tgt, d) for d in self.dirs] + excludes = [self._resolve_remote_path(conn_tgt, d) for d in self.excludes] # make sure the mount directory exists # due to how bup index works, the mount directory has to stay the @@ -366,7 +364,7 @@ class TargetSSHLVM(TargetSSH): self._paramiko_exec_cmd(conn_root, 'cat /proc/%d/mountinfo' % self._mntns_pid, decode = False)) - devnum, mountpoint, fstype = self._resolve_mntdev(mntinfo) + devnum, mountpoint, fstype = self._resolve_mntdev(mntinfo, dirs) # make sure we have a valid fstype fstype = fstype.decode('ascii') @@ -377,12 +375,12 @@ class TargetSSHLVM(TargetSSH): "%d:%d" % (devnum >> 8, devnum & 255), fstype, mountpoint) bup_exec = ['bup', 'on', '%s@%s' % (self._remote.username, self._remote.host), - '-d', bupdir] + '-d', str(bupdir)] save_opts = ['--graft=%s=%s' % (snapshot_mount, mountpoint)] index_opts = ['--no-check-device'] reparent = (mountpoint, AbsPath(snapshot_mount)) - dirs = [d.reparent(*reparent) for d in self.dirs] - excludes = [d.reparent(*reparent) for d in self.excludes] + dirs = [d.reparent(*reparent) for d in dirs] + excludes = [d.reparent(*reparent) for d in excludes] stack.enter_context(self._mount_snapshot(conn_root, devnum, snapshot_mount, fstype)) -- cgit v1.2.3