Don't crash when the remote doesn't support the new config-get
command, just provide suitable substitute values (None should work for
all currently valid options) from a RemoteRepo via a
_fallback_config_get().
Move the valid options list to protocol.valid_config_opts so we can
share it between Server.config_get() and _fallback_config_get().
Add some initial cross-version tests by adding an "internal only"
BUP_TEST_SSH_BUP_PATH that can specify a path to the remote
bup (instead of just 'bup') and set that to BUP_TEST_OTHER_BUP when
BUP_TEST_OTHER_BUP has a value, in order to provide some simple ls,
save, and restore tests with ./bup as the client and
BUP_TEST_OTHER_BUP as the server.
Thanks to Mark Hewitt for reporting the issue, and Johannes Berg for
help evaluating the solution.
Signed-off-by: Rob Browning <
r...@defaultvalue.org>
Tested-by: Rob Browning <
r...@defaultvalue.org>
---
Pushed to main.
lib/bup/client.py | 8 ++++----
lib/bup/protocol.py | 20 ++++++++++++++------
lib/bup/repo/remote.py | 18 +++++++++++++++++-
lib/bup/ssh.py | 14 ++++++++++----
test/ext/test-ls-from-other-bup | 15 +++++++++++++++
test/ext/test-save-restore | 24 +++++++++++++++++++++---
6 files changed, 81 insertions(+), 18 deletions(-)
create mode 100755 test/ext/test-ls-from-other-bup
diff --git a/lib/bup/client.py b/lib/bup/client.py
index 1485d9c1..1f1ea3dc 100644
--- a/lib/bup/client.py
+++ b/lib/bup/client.py
@@ -276,10 +276,7 @@ class Client:
# dirs when the remote repo has one (that can be accessed).
repo_id = None
if b'config-get' in self._available_commands:
- try:
- repo_id = self.config_get(b'
bup.repo.id')
- except PermissionError:
- pass
+ repo_id = self.config_get(b'
bup.repo.id')
legacy = index_cache(legacy_id)
if repo_id is None:
return legacy
@@ -394,6 +391,9 @@ class Client:
raise ClientError('server does not appear to provide %s command'
% name.decode('ascii'))
+ def supports(self, command):
+ return command in self._available_commands
+
def _list_indexes(self):
with self._line_based_call('list-indexes') as call:
for line in call.lines():
diff --git a/lib/bup/protocol.py b/lib/bup/protocol.py
index f812da46..1aadb984 100644
--- a/lib/bup/protocol.py
+++ b/lib/bup/protocol.py
@@ -11,6 +11,19 @@ from bup.vint import \
from bup.vfs import Item, Chunky, RevList, Root, Tags, Commit, FakeLink
+# See also RemoteRepo._config_get_fallback(). The current assumption
+# is that None is the appropriate value for an option whenever the
+# server does not support config-get.
+
+valid_config_opts = \
+ frozenset((b'
bup.repo.id',
+ b'bup.split.trees',
+ b'bup.split.files',
+ b'pack.packsizelimit',
+ b'core.compression',
+ b'pack.compression'))
+
+
def read_item(port):
"""Read an encoded VFS item from port. Throw EOFError for EOF."""
def read_oid(port, kind):
@@ -450,12 +463,7 @@ class Server:
# pylint: disable-next=unbalanced-tuple-unpacking
key, opttype = vint.recv(self.conn, 'ss')
# git is case-insensitve, and the client sends lower-case
- if key in (b'
bup.repo.id',
- b'bup.split.trees',
- b'bup.split.files',
- b'pack.packsizelimit',
- b'core.compression',
- b'pack.compression'):
+ if key in valid_config_opts:
opttype = None if not opttype else opttype.decode('ascii')
val = self.repo.config_get(key, opttype=opttype)
if val is None:
diff --git a/lib/bup/repo/remote.py b/lib/bup/repo/remote.py
index a399a3a1..46ce34bb 100644
--- a/lib/bup/repo/remote.py
+++ b/lib/bup/repo/remote.py
@@ -3,6 +3,7 @@ from binascii import hexlify
import re
from bup import client, git
+from bup.protocol import valid_config_opts
from bup.repo.base import _make_base, RepoProtocol
@@ -17,7 +18,10 @@ class RemoteRepo(RepoProtocol):
self.closed = True # in case Client instantiation fails
self.client = client.Client(location, create=create)
self.closed = False
- self.config_get = self.client.config_get
+ if self.client.supports(b'config-get'):
+ self.config_get = self.client.config_get
+ else:
+ self.config_get = self._config_get_fallback
self._base = _make_base(self.config_get, compression_level,
max_pack_size, max_pack_objects)
self.write_symlink = self.write_data
@@ -48,6 +52,18 @@ class RemoteRepo(RepoProtocol):
def __enter__(self): return self
def __exit__(self, type, value, traceback): self.close()
+ def _config_get_fallback(self, name, opttype=None):
+ """Return an appropriate value when (older) remote does not
+ support config-get.
+
+ """
+ assert isinstance(name, bytes)
+ name = name.lower() # git is case insensitive here
+ assert opttype in ('int', 'bool', None)
+ if name not in valid_config_opts:
+ raise PermissionError(f'remote access to {name} is not allowed')
+ return None
+
def update_ref(self, refname, newval, oldval):
self.finish_writing()
return self.client.update_ref(refname, newval, oldval)
diff --git a/lib/bup/ssh.py b/lib/bup/ssh.py
index 9a721e6d..c231a10f 100644
--- a/lib/bup/ssh.py
+++ b/lib/bup/ssh.py
@@ -8,6 +8,7 @@ import re
from bup import path
from bup.compat import environ
+from bup.helpers import quote
from
bup.io import buglvl, debug1
@@ -21,22 +22,27 @@ def connect(destination, port, subcmd, stderr=None):
"""
assert re.fullmatch(br'[-_a-zA-Z0-9]+', subcmd), subcmd
+
if not destination:
if b'BUP_TEST_LEVEL' not in environ:
raise Exception('no ssh destination')
- argv = [path.exe(), subcmd]
+ argv = [environ.get(b'BUP_TEST_SSH_BUP_PATH', path.exe()), subcmd]
elif destination == b'-':
if b'BUP_TEST_LEVEL' not in environ:
raise Exception('invalid ssh destination "-"')
- argv = [path.exe(), subcmd]
+ argv = [environ.get(b'BUP_TEST_SSH_BUP_PATH', path.exe()), subcmd]
else:
+ bup = environ.get(b'BUP_TEST_SSH_BUP_PATH')
+ if bup and b'BUP_TEST_LEVEL' not in environ:
+ raise Exception('BUP_TEST_SSH_BUP_PATH only allowed when testing')
+ bup = bup or b'bup'
force_tty = int(environ.get(b'BUP_FORCE_TTY', 0))
argv = [b'ssh']
if port:
argv.extend((b'-p', port))
argv.extend((destination, b'--',
- b"sh -c 'BUP_DEBUG=%d BUP_FORCE_TTY=%d bup %s'"
- % (buglvl, force_tty, subcmd)))
+ b"sh -c \"BUP_DEBUG=%d BUP_FORCE_TTY=%d %s %s\""
+ % (buglvl, force_tty, quote(bup), subcmd)))
debug1(f'ssh: {argv!r}\n')
return Popen(argv, stdin=PIPE, stdout=PIPE, stderr=stderr,
start_new_session=True)
diff --git a/test/ext/test-ls-from-other-bup b/test/ext/test-ls-from-other-bup
new file mode 100755
index 00000000..561e0afb
--- /dev/null
+++ b/test/ext/test-ls-from-other-bup
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+. wvtest.sh || exit $?
+. wvtest-bup.sh || exit $?
+
+set -o pipefail
+
+if test -z "$BUP_TEST_OTHER_BUP"; then
+ WVSKIP 'no BUP_TEST_OTHER_BUP'
+ exit 0
+fi
+
+export BUP_TEST_REMOTE_REPO=t # as per test-ls-remote
+export BUP_TEST_SSH_BUP_PATH="$BUP_TEST_OTHER_BUP"
+
+test/ext/test-ls
diff --git a/test/ext/test-save-restore b/test/ext/test-save-restore
index 24fe7941..f1d6ffcd 100755
--- a/test/ext/test-save-restore
+++ b/test/ext/test-save-restore
@@ -22,9 +22,9 @@ validate-local-and-remote-restore()
WVPASS bup restore -r "-:$BUP_DIR" -C "$dest" "$src"
WVPASS "$top/dev/compare-trees" "$cmp_src" "$cmp_dest"
if test "${BUP_TEST_LOCAL_SSH:-}"; then
- force-delete "$dest"
- WVPASS bup restore -r "$BUP_TEST_LOCAL_SSH:$BUP_DIR" -C "$dest" "$src"
- WVPASS "$top/dev/compare-trees" "$cmp_src" "$cmp_dest"
+ force-delete "$dest"
+ WVPASS bup restore -r "$BUP_TEST_LOCAL_SSH:$BUP_DIR" -C "$dest" "$src"
+ WVPASS "$top/dev/compare-trees" "$cmp_src" "$cmp_dest"
fi
}
@@ -196,5 +196,23 @@ WVSTART "save disjoint top-level directories"
) || exit $?
+if ! test "$BUP_TEST_OTHER_BUP"; then
+ WVSKIP "BUP_TEST_OTHER_BUP not set, skipping related tests"
+else
+ WVSTART "save/restore compatibility between ./bup and $BUP_TEST_OTHER_BUP"
+ WVPASSEQ '' "$BUP_TEST_SSH_BUP_PATH"
+ force-delete src repo restore
+ export BUP_DIR="$(pwd)/repo"
+ WVPASS bup init
+ WVPASS bup index "$top/test/sampledata"
+ export BUP_TEST_SSH_BUP_PATH="$BUP_TEST_OTHER_BUP"
+ WVPASS bup save -n save --strip "$top/test/sampledata"
+ WVPASS bup restore -r "-:$BUP_DIR" -C restore save/latest
+ unset BUP_TEST_SSH_BUP_PATH
+ WVPASS "$top/dev/compare-trees" "$top/test/sampledata/" restore/latest
+ force-delete src repo restore
+fi
+
+
WVPASS cd "$top"
WVPASS rm -rf "$tmpdir"
--
2.47.3