From 5a2d35d3ccb2b818968f069dde63432da330856d Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Thu, 8 Apr 2021 23:09:00 -0600 Subject: [PATCH] Prevent recovering to wrong devices or paths Before starting a recovery run verify the source and destination have not changed. This will prevent issues on some extreme edge cases but the main goal is for disappearing source drives with heavy damage. e.g. A very damaged source drive disappears mid-recovery, drops off and before would need a restart, or unplug/replug, to continue. Now we can attempt to re-detect the drive and resume recovery without leaving the script. If for some reason the drive order were to change then we'll avoid using the wrong source or destination device. --- scripts/wk/hw/ddrescue.py | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/scripts/wk/hw/ddrescue.py b/scripts/wk/hw/ddrescue.py index 966d65a4..0a063a98 100644 --- a/scripts/wk/hw/ddrescue.py +++ b/scripts/wk/hw/ddrescue.py @@ -1719,6 +1719,8 @@ def is_missing_source_or_destination(state): 'Source': state.source, 'Destination': state.destination, } + + # Check items for name, item in items.items(): if not item: continue @@ -1733,9 +1735,38 @@ def is_missing_source_or_destination(state): else: LOG.error('Unknown %s type: %s', name, item) + # Update top panes + state.update_top_panes() + + # Done return missing +def source_or_destination_changed(state): + """Verify the source and destination objects are still valid.""" + changed = False + + # Compare objects + for obj in (state.source, state.destination): + if not obj: + changed = True + elif hasattr(obj, 'exists'): + # Assuming dest path + changed = changed or not obj.exists() + elif isinstance(obj, hw_obj.Disk): + compare_dev = hw_obj.Disk(obj.path) + for key in ('model', 'serial'): + changed = changed or obj.details[key] != compare_dev.details[key] + + # Update top panes + state.update_top_panes() + + # Done + if changed: + std.print_error('Source and/or Destination changed') + return changed + + def main(): # pylint: disable=too-many-branches """Main function for ddrescue TUI.""" @@ -1760,7 +1791,6 @@ def main(): # Show menu while True: - state.update_top_panes() selection = main_menu.advanced_select() # Change settings @@ -1781,6 +1811,8 @@ def main(): std.print_standard('Forcing controllers to rescan for devices...') cmd = 'echo "- - -" | sudo tee /sys/class/scsi_host/host*/scan' exe.run_program(cmd, check=False, shell=True) + if source_or_destination_changed(state): + std.abort() # Start recovery if 'Start' in selection: @@ -2022,10 +2054,12 @@ def run_recovery(state, main_menu, settings_menu, dry_run=True): # Bail early if is_missing_source_or_destination(state): - state.update_top_panes() std.print_standard('') std.pause('Press Enter to return to main menu...') return + if source_or_destination_changed(state): + std.print_standard('') + std.abort() # Get settings for name, details in main_menu.toggles.items():