Added real ddrescue command logic
* Still needs testing!! * Set all dry_run keywords to default to True
This commit is contained in:
parent
c22c3da493
commit
2b18da7244
1 changed files with 105 additions and 27 deletions
|
|
@ -12,6 +12,7 @@ import pathlib
|
||||||
import plistlib
|
import plistlib
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
|
import subprocess
|
||||||
import time
|
import time
|
||||||
|
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
@ -640,7 +641,7 @@ class State():
|
||||||
"""Check if all block_pairs completed pass_name, returns bool."""
|
"""Check if all block_pairs completed pass_name, returns bool."""
|
||||||
return all([p.pass_complete(pass_name) for p in self.block_pairs])
|
return all([p.pass_complete(pass_name) for p in self.block_pairs])
|
||||||
|
|
||||||
def prep_destination(self, source_parts, working_dir, dry_run=False):
|
def prep_destination(self, source_parts, working_dir, dry_run=True):
|
||||||
"""Prep destination as necessary."""
|
"""Prep destination as necessary."""
|
||||||
dest_prefix = str(self.destination.path)
|
dest_prefix = str(self.destination.path)
|
||||||
dest_prefix += get_partition_separator(self.destination.path.name)
|
dest_prefix += get_partition_separator(self.destination.path.name)
|
||||||
|
|
@ -1005,6 +1006,30 @@ def build_block_pair_report(block_pairs, settings):
|
||||||
return report
|
return report
|
||||||
|
|
||||||
|
|
||||||
|
def build_ddrescue_cmd(block_pair, pass_name, settings):
|
||||||
|
"""Build ddrescue cmd using passed details, returns list."""
|
||||||
|
cmd = ['sudo', 'ddrescue']
|
||||||
|
if (block_pair.destination.is_block_device()
|
||||||
|
or block_pair.destination.is_char_device()):
|
||||||
|
cmd.append('--force')
|
||||||
|
if pass_name == 'read':
|
||||||
|
cmd.extend(['--no-trim', '--no-scrape'])
|
||||||
|
elif pass_name == 'trim':
|
||||||
|
# Allow trimming
|
||||||
|
cmd.append('--no-scrape')
|
||||||
|
elif pass_name == 'scrape':
|
||||||
|
# Allow trimming and scraping
|
||||||
|
pass
|
||||||
|
cmd.extend(settings)
|
||||||
|
cmd.append(block_pair.source)
|
||||||
|
cmd.append(block_pair.destination)
|
||||||
|
cmd.append(block_pair.map_path)
|
||||||
|
|
||||||
|
# Done
|
||||||
|
LOG.debug('ddrescue cmd: %s', cmd)
|
||||||
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
def build_directory_report(path):
|
def build_directory_report(path):
|
||||||
"""Build directory report, returns list."""
|
"""Build directory report, returns list."""
|
||||||
path = f'{path}/'
|
path = f'{path}/'
|
||||||
|
|
@ -1519,7 +1544,7 @@ def main():
|
||||||
|
|
||||||
# Start recovery
|
# Start recovery
|
||||||
if 'Start' in selection:
|
if 'Start' in selection:
|
||||||
run_recovery(state, main_menu, settings_menu)
|
run_recovery(state, main_menu, settings_menu, dry_run=args['--dry-run'])
|
||||||
|
|
||||||
# Quit
|
# Quit
|
||||||
if 'Quit' in selection:
|
if 'Quit' in selection:
|
||||||
|
|
@ -1599,41 +1624,94 @@ def mount_raw_image_macos(path):
|
||||||
return loopback_path
|
return loopback_path
|
||||||
|
|
||||||
|
|
||||||
def run_ddrescue(state, block_pair, pass_name, settings):
|
def run_ddrescue(state, block_pair, pass_name, settings, dry_run=True):
|
||||||
"""Run ddrescue using passed settings."""
|
"""Run ddrescue using passed settings."""
|
||||||
|
cmd = build_ddrescue_cmd(block_pair, pass_name, settings)
|
||||||
|
proc = None
|
||||||
state.update_progress_pane('Active')
|
state.update_progress_pane('Active')
|
||||||
i = 0
|
std.clear_screen()
|
||||||
|
warning_message = ''
|
||||||
|
|
||||||
|
def _update_smart_pane(iteration):
|
||||||
|
"""Update SMART pane every 30 seconds."""
|
||||||
|
if iteration % 30 != 0:
|
||||||
|
return
|
||||||
|
state.source.update_smart_details()
|
||||||
|
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M %Z')
|
||||||
|
with open(f'{state.log_dir}/smart.out', 'w') as _f:
|
||||||
|
_f.write(
|
||||||
|
std.color_string(
|
||||||
|
['SMART Attributes', f'Updated: {now}\n'],
|
||||||
|
['BLUE', 'YELLOW'],
|
||||||
|
sep='\t\t',
|
||||||
|
),
|
||||||
|
)
|
||||||
|
_f.write('\n'.join(state.source.generate_report(header=False)))
|
||||||
|
|
||||||
|
# Dry run
|
||||||
|
if dry_run:
|
||||||
|
std.print_info('ddrescue cmd:')
|
||||||
|
for _c in cmd:
|
||||||
|
std.print_standard(f' {_c}')
|
||||||
|
std.pause()
|
||||||
|
return
|
||||||
|
|
||||||
|
# Start ddrescue
|
||||||
|
proc = exe.popen_program(cmd)
|
||||||
|
|
||||||
|
# ddrescue loop
|
||||||
|
_i = 0
|
||||||
while True:
|
while True:
|
||||||
# Update SMART pane (every 30 seconds)
|
_update_smart_pane(_i)
|
||||||
if i % 30 == 0:
|
_i += 1
|
||||||
now = datetime.datetime.now().strftime('%Y-%m-%d %H:%M %Z')
|
|
||||||
with open(f'{state.log_dir}/smart.out', 'w') as _f:
|
|
||||||
_f.write(
|
|
||||||
std.color_string(
|
|
||||||
['SMART Attributes', f'Updated: {now}\n'],
|
|
||||||
['BLUE', 'YELLOW'],
|
|
||||||
sep='\t\t',
|
|
||||||
),
|
|
||||||
)
|
|
||||||
_f.write('\n'.join(state.source.generate_report(header=False)))
|
|
||||||
|
|
||||||
# Update progress
|
# Update progress
|
||||||
block_pair.update_progress(pass_name)
|
block_pair.update_progress(pass_name)
|
||||||
state.update_progress_pane('Active')
|
state.update_progress_pane('Active')
|
||||||
|
|
||||||
# Run
|
# Check if complete
|
||||||
# TODO: Make ddrescue calls real
|
try:
|
||||||
print('Running ddrescue')
|
proc.wait(timeout=1)
|
||||||
print(f' {block_pair.source} --> {block_pair.destination}')
|
except KeyboardInterrupt:
|
||||||
print('Using these settings:')
|
# Wait a bit to let ddrescue exit safely
|
||||||
for _s in settings:
|
warning_message = 'Aborted'
|
||||||
print(f' {_s}')
|
proc.wait(timeout=10)
|
||||||
break
|
proc.terminate()
|
||||||
std.pause()
|
break
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
# Continue to next loop to update panes
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# Done
|
||||||
|
break
|
||||||
|
|
||||||
|
# Update progress
|
||||||
|
# NOTE: Using 'Active' here to avoid flickering between block pairs
|
||||||
|
block_pair.update_progress(pass_name)
|
||||||
|
state.update_progress_pane('Active')
|
||||||
|
|
||||||
|
# Check result
|
||||||
|
if proc.poll():
|
||||||
|
# True if return code is non-zero (poll() returns None if still running)
|
||||||
|
warning_message = 'Error(s) encountered, see message above'
|
||||||
|
if warning_message:
|
||||||
|
print(' ')
|
||||||
|
print(' ')
|
||||||
|
std.print_error('DDRESCUE PROCESS HALTED')
|
||||||
|
print(' ')
|
||||||
|
std.print_warning(warning_message)
|
||||||
|
|
||||||
|
# Needs attention?
|
||||||
|
if str(proc.poll()) != '0':
|
||||||
|
state.update_progress_pane('NEEDS ATTENTION')
|
||||||
|
std.pause('Press Enter to return to main menu...')
|
||||||
|
|
||||||
|
# Aborted?
|
||||||
|
if 'Aborted' in warning_message:
|
||||||
|
raise std.GenericAbort()
|
||||||
|
|
||||||
|
|
||||||
def run_recovery(state, main_menu, settings_menu):
|
def run_recovery(state, main_menu, settings_menu, dry_run=True):
|
||||||
"""Run recovery passes."""
|
"""Run recovery passes."""
|
||||||
atexit.register(state.save_debug_reports)
|
atexit.register(state.save_debug_reports)
|
||||||
attempted_recovery = False
|
attempted_recovery = False
|
||||||
|
|
@ -1671,7 +1749,7 @@ def run_recovery(state, main_menu, settings_menu):
|
||||||
if not pair.pass_complete(pass_name):
|
if not pair.pass_complete(pass_name):
|
||||||
attempted_recovery = True
|
attempted_recovery = True
|
||||||
try:
|
try:
|
||||||
run_ddrescue(state, pair, pass_name, settings)
|
run_ddrescue(state, pair, pass_name, settings, dry_run=dry_run)
|
||||||
except KeyboardInterrupt:
|
except KeyboardInterrupt:
|
||||||
abort = True
|
abort = True
|
||||||
break
|
break
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue