Reworked retry sections
* Edit the map file directly instead of using --retrim and --try-again * Allows for more accurate pass status reporting * Allows for simpler pass break/continue logic * Create the map file before running ddrescue * Allows file to be edited by the current user instead of just root/ddrescue * Added check for empty map files * Avoids incorrectly marking a pass as complete
This commit is contained in:
parent
470524dfff
commit
5926c3170d
1 changed files with 53 additions and 36 deletions
|
|
@ -140,20 +140,10 @@ class BlockPair():
|
||||||
else:
|
else:
|
||||||
# Cloning
|
# Cloning
|
||||||
self.map_path = pathlib.Path(f'{working_dir}/Clone_{map_name}.map')
|
self.map_path = pathlib.Path(f'{working_dir}/Clone_{map_name}.map')
|
||||||
|
self.map_path.touch()
|
||||||
# Read map file
|
|
||||||
self.load_map_data()
|
|
||||||
|
|
||||||
# Set initial status
|
# Set initial status
|
||||||
percent = self.get_percent_recovered()
|
self.set_initial_status()
|
||||||
for name in self.status.keys():
|
|
||||||
if self.pass_complete(name):
|
|
||||||
self.status[name] = percent
|
|
||||||
else:
|
|
||||||
# Stop checking
|
|
||||||
if percent > 0:
|
|
||||||
self.status[name] = percent
|
|
||||||
break
|
|
||||||
|
|
||||||
def get_percent_recovered(self):
|
def get_percent_recovered(self):
|
||||||
"""Get percent rescued from map_data, returns float."""
|
"""Get percent rescued from map_data, returns float."""
|
||||||
|
|
@ -197,14 +187,16 @@ class BlockPair():
|
||||||
)
|
)
|
||||||
data['pass completed'] = 'current status: finished' in line.lower()
|
data['pass completed'] = 'current status: finished' in line.lower()
|
||||||
|
|
||||||
# Check if 100% done
|
# Check if 100% done (only if map is present and non-zero size
|
||||||
cmd = [
|
# NOTE: ddrescuelog returns 0 (i.e. 100% done) for empty files
|
||||||
'ddrescuelog',
|
if self.map_path.exists() and self.map_path.stat().st_size != 0:
|
||||||
'--done-status',
|
cmd = [
|
||||||
self.map_path,
|
'ddrescuelog',
|
||||||
]
|
'--done-status',
|
||||||
proc = exe.run_program(cmd, check=False)
|
self.map_path,
|
||||||
data['full recovery'] = proc.returncode == 0
|
]
|
||||||
|
proc = exe.run_program(cmd, check=False)
|
||||||
|
data['full recovery'] = proc.returncode == 0
|
||||||
|
|
||||||
# Done
|
# Done
|
||||||
self.map_data.update(data)
|
self.map_data.update(data)
|
||||||
|
|
@ -246,6 +238,19 @@ class BlockPair():
|
||||||
std.print_error(f'Invalid destination: {self.destination}')
|
std.print_error(f'Invalid destination: {self.destination}')
|
||||||
raise std.GenericAbort()
|
raise std.GenericAbort()
|
||||||
|
|
||||||
|
def set_initial_status(self):
|
||||||
|
"""Read map data and set initial statuses."""
|
||||||
|
self.load_map_data()
|
||||||
|
percent = self.get_percent_recovered()
|
||||||
|
for name in self.status.keys():
|
||||||
|
if self.pass_complete(name):
|
||||||
|
self.status[name] = percent
|
||||||
|
else:
|
||||||
|
# Stop checking
|
||||||
|
if percent > 0:
|
||||||
|
self.status[name] = percent
|
||||||
|
break
|
||||||
|
|
||||||
def skip_pass(self, pass_name):
|
def skip_pass(self, pass_name):
|
||||||
"""Mark pass as skipped if applicable."""
|
"""Mark pass as skipped if applicable."""
|
||||||
if self.status[pass_name] == 'Pending':
|
if self.status[pass_name] == 'Pending':
|
||||||
|
|
@ -783,11 +788,30 @@ class State():
|
||||||
self.save_settings(settings)
|
self.save_settings(settings)
|
||||||
|
|
||||||
def retry_all_passes(self):
|
def retry_all_passes(self):
|
||||||
"""Set all statuses to Pending."""
|
"""Prep block_pairs for a retry recovery attempt."""
|
||||||
|
bad_statuses = ('*', '/', '-')
|
||||||
for pair in self.block_pairs:
|
for pair in self.block_pairs:
|
||||||
|
map_data = []
|
||||||
|
|
||||||
|
# Reset status strings
|
||||||
for name in pair.status.keys():
|
for name in pair.status.keys():
|
||||||
pair.status[name] = 'Pending'
|
pair.status[name] = 'Pending'
|
||||||
|
|
||||||
|
# Mark all non-trimmed, non-scraped, and bad areas as non-tried
|
||||||
|
with open(pair.map_path, 'r') as _f:
|
||||||
|
for line in _f.readlines():
|
||||||
|
line = line.strip()
|
||||||
|
if line.startswith('0x') and line.endswith(bad_statuses):
|
||||||
|
line = f'{line[:-1]}?'
|
||||||
|
map_data.append(line)
|
||||||
|
|
||||||
|
# Save updated map
|
||||||
|
with open(pair.map_path, 'w') as _f:
|
||||||
|
_f.write('\n'.join(map_data))
|
||||||
|
|
||||||
|
# Reinitialize status
|
||||||
|
pair.set_initial_status()
|
||||||
|
|
||||||
def safety_check_destination(self):
|
def safety_check_destination(self):
|
||||||
"""Run safety checks for destination and abort if necessary."""
|
"""Run safety checks for destination and abort if necessary."""
|
||||||
try:
|
try:
|
||||||
|
|
@ -1382,15 +1406,11 @@ def fstype_is_ok(path, map_dir=False):
|
||||||
return is_ok
|
return is_ok
|
||||||
|
|
||||||
|
|
||||||
def get_ddrescue_settings(main_menu, settings_menu):
|
def get_ddrescue_settings(settings_menu):
|
||||||
"""Get ddrescue settings from menu selections, returns list."""
|
"""Get ddrescue settings from menu selections, returns list."""
|
||||||
settings = []
|
settings = []
|
||||||
|
|
||||||
# Check menu selections
|
# Check menu selections
|
||||||
for name, details in main_menu.toggles.items():
|
|
||||||
if 'Retry' in name and details['Selected']:
|
|
||||||
settings.append('--retrim')
|
|
||||||
settings.append('--try-again')
|
|
||||||
for name, details in settings_menu.options.items():
|
for name, details in settings_menu.options.items():
|
||||||
if details['Selected']:
|
if details['Selected']:
|
||||||
if 'Value' in details:
|
if 'Value' in details:
|
||||||
|
|
@ -1783,7 +1803,10 @@ def run_recovery(state, main_menu, settings_menu, dry_run=True):
|
||||||
for name, details in main_menu.toggles.items():
|
for name, details in main_menu.toggles.items():
|
||||||
if 'Auto continue' in name and details['Selected']:
|
if 'Auto continue' in name and details['Selected']:
|
||||||
auto_continue = True
|
auto_continue = True
|
||||||
settings = get_ddrescue_settings(main_menu, settings_menu)
|
if 'Retry' in name and details['Selected']:
|
||||||
|
details['Selected'] = False
|
||||||
|
state.retry_all_passes()
|
||||||
|
settings = get_ddrescue_settings(settings_menu)
|
||||||
|
|
||||||
# Start SMART/Journal
|
# Start SMART/Journal
|
||||||
state.panes['SMART'] = tmux.split_window(
|
state.panes['SMART'] = tmux.split_window(
|
||||||
|
|
@ -1794,25 +1817,19 @@ def run_recovery(state, main_menu, settings_menu, dry_run=True):
|
||||||
lines=4, vertical=True, cmd='journalctl --dmesg --follow',
|
lines=4, vertical=True, cmd='journalctl --dmesg --follow',
|
||||||
)
|
)
|
||||||
|
|
||||||
# Check if retrying
|
|
||||||
if '--retrim' in settings:
|
|
||||||
state.retry_all_passes()
|
|
||||||
|
|
||||||
# Run pass(es)
|
# Run pass(es)
|
||||||
for pass_name in ('read', 'trim', 'scrape'):
|
for pass_name in ('read', 'trim', 'scrape'):
|
||||||
abort = False
|
abort = False
|
||||||
|
|
||||||
# Skip to next pass (unless retry selected)
|
# Skip to next pass
|
||||||
if '--retrim' not in settings and state.pass_complete(pass_name):
|
if state.pass_complete(pass_name):
|
||||||
# NOTE: This bypasses auto_continue
|
# NOTE: This bypasses auto_continue
|
||||||
state.skip_pass(pass_name)
|
state.skip_pass(pass_name)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Run ddrescue
|
# Run ddrescue
|
||||||
for pair in state.block_pairs:
|
for pair in state.block_pairs:
|
||||||
if '--retrim' not in settings and pair.pass_complete(pass_name):
|
if not pair.pass_complete(pass_name):
|
||||||
pair.skip_pass(pass_name)
|
|
||||||
else:
|
|
||||||
attempted_recovery = True
|
attempted_recovery = True
|
||||||
state.mark_started()
|
state.mark_started()
|
||||||
try:
|
try:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue