fix(recovery): use modern no-log-replay mount option and report stderr
This commit is contained in:
@@ -150,8 +150,12 @@ def sync_tree(root):
|
|||||||
|
|
||||||
class Btrfs:
|
class Btrfs:
|
||||||
def run(self, argv):
|
def run(self, argv):
|
||||||
|
try:
|
||||||
return subprocess.run(argv, check=True, text=True, stdout=subprocess.PIPE,
|
return subprocess.run(argv, check=True, text=True, stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE, env=dict(os.environ, LC_ALL='C')).stdout
|
stderr=subprocess.PIPE, env=dict(os.environ, LC_ALL='C')).stdout
|
||||||
|
except subprocess.CalledProcessError as exc:
|
||||||
|
detail = (exc.stderr or '').strip()[:2000]
|
||||||
|
raise RecoveryError(f'{argv[0]} failed ({exc.returncode}): {detail}') from exc
|
||||||
def info(self, path):
|
def info(self, path):
|
||||||
output = self.run(['btrfs', 'subvolume', 'show', str(path)])
|
output = self.run(['btrfs', 'subvolume', 'show', str(path)])
|
||||||
fields = dict(line.strip().split(':', 1) for line in output.splitlines() if ':' in line)
|
fields = dict(line.strip().split(':', 1) for line in output.splitlines() if ':' in line)
|
||||||
@@ -476,7 +480,7 @@ def mounted(backend, device, filesystem, writable=False):
|
|||||||
options = ['rw' if writable else 'ro', 'nosuid', 'nodev', 'noexec']
|
options = ['rw' if writable else 'ro', 'nosuid', 'nodev', 'noexec']
|
||||||
if filesystem == 'btrfs':
|
if filesystem == 'btrfs':
|
||||||
options.append('subvolid=5')
|
options.append('subvolid=5')
|
||||||
if not writable: options.append('nologreplay')
|
if not writable: options.append('rescue=nologreplay')
|
||||||
backend.run(['mount', '-t', filesystem, '-o', ','.join(options), device, str(directory)])
|
backend.run(['mount', '-t', filesystem, '-o', ','.join(options), device, str(directory)])
|
||||||
did_mount = True
|
did_mount = True
|
||||||
if filesystem == 'btrfs':
|
if filesystem == 'btrfs':
|
||||||
|
|||||||
@@ -252,6 +252,14 @@ s.restore(sys.argv[3])
|
|||||||
self.assertEqual((self.top / '@root/kernel').read_text(), 'old kernel')
|
self.assertEqual((self.top / '@root/kernel').read_text(), 'old kernel')
|
||||||
|
|
||||||
class NativeBoundaryTests(unittest.TestCase):
|
class NativeBoundaryTests(unittest.TestCase):
|
||||||
|
def test_failed_native_command_includes_bounded_stderr(self):
|
||||||
|
backend = r.Btrfs()
|
||||||
|
error = subprocess.CalledProcessError(32, ['mount'], stderr='specific mount failure\n' + 'x' * 5000)
|
||||||
|
with patch.object(r.subprocess, 'run', side_effect=error):
|
||||||
|
with self.assertRaisesRegex(r.RecoveryError, 'specific mount failure') as caught:
|
||||||
|
backend.run(['mount', '-t', 'btrfs', '/dev/example', '/run/example'])
|
||||||
|
self.assertLess(len(str(caught.exception)), 2300)
|
||||||
|
|
||||||
def test_btrfs_show_parser_and_readonly_property(self):
|
def test_btrfs_show_parser_and_readonly_property(self):
|
||||||
backend = r.Btrfs()
|
backend = r.Btrfs()
|
||||||
sample = '''@snapshots/example/root
|
sample = '''@snapshots/example/root
|
||||||
@@ -292,7 +300,7 @@ class NativeBoundaryTests(unittest.TestCase):
|
|||||||
with self.assertRaises(r.RecoveryError):
|
with self.assertRaises(r.RecoveryError):
|
||||||
with r.mounted(backend, '/dev/fake', 'btrfs'): pass
|
with r.mounted(backend, '/dev/fake', 'btrfs'): pass
|
||||||
self.assertEqual([argv[0] for argv in calls], ['mount', 'btrfs', 'umount'])
|
self.assertEqual([argv[0] for argv in calls], ['mount', 'btrfs', 'umount'])
|
||||||
self.assertIn('ro,nosuid,nodev,noexec,subvolid=5,nologreplay', calls[0])
|
self.assertIn('ro,nosuid,nodev,noexec,subvolid=5,rescue=nologreplay', calls[0])
|
||||||
def test_cli_requires_matching_confirmation_before_probe(self):
|
def test_cli_requires_matching_confirmation_before_probe(self):
|
||||||
with patch.object(r, 'DeviceProbe') as probe, patch.object(sys, 'stderr', new_callable=io.StringIO):
|
with patch.object(r, 'DeviceProbe') as probe, patch.object(sys, 'stderr', new_callable=io.StringIO):
|
||||||
self.assertEqual(r.main(['restore', '--root-uuid', ROOT_UUID, '--checkpoint', ROOT_PART, '--execute']), 1)
|
self.assertEqual(r.main(['restore', '--root-uuid', ROOT_UUID, '--checkpoint', ROOT_PART, '--execute']), 1)
|
||||||
|
|||||||
Reference in New Issue
Block a user