From 8f14fd2442093e4ab2400dda19d872726f0dd3c8 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sat, 8 Jul 2023 18:07:55 -0700 Subject: [PATCH 1/6] Fix SMART attribute tracking Since we've moved to delayed SMART attribute updates we need to set initial_attributes after we first check the SMART data instead of at object creation time. --- scripts/wk/hw/disk.py | 4 +--- scripts/wk/hw/smart.py | 4 ++++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/scripts/wk/hw/disk.py b/scripts/wk/hw/disk.py index 161ff9f1..1e81ba24 100644 --- a/scripts/wk/hw/disk.py +++ b/scripts/wk/hw/disk.py @@ -1,7 +1,6 @@ """WizardKit: Disk object and functions""" # vim: sts=2 sw=2 ts=2 -import copy import logging import pathlib import platform @@ -39,7 +38,7 @@ class Disk: children: list[dict] = field(init=False, default_factory=list) description: str = field(init=False) filesystem: str = field(init=False) - initial_attributes: dict[Any, dict] = field(init=False) + initial_attributes: dict[Any, dict] = field(init=False, default_factory=dict) known_attributes: dict[Any, dict] = field(init=False, default_factory=dict) log_sec: int = field(init=False) model: str = field(init=False) @@ -62,7 +61,6 @@ class Disk: self.update_details() self.set_description() self.known_attributes = get_known_disk_attributes(self.model) - self.initial_attributes = copy.deepcopy(self.attributes) if not self.is_4k_aligned(): self.add_note('One or more partitions are not 4K aligned', 'YELLOW') diff --git a/scripts/wk/hw/smart.py b/scripts/wk/hw/smart.py index 915225bd..6c5c66e5 100644 --- a/scripts/wk/hw/smart.py +++ b/scripts/wk/hw/smart.py @@ -506,6 +506,10 @@ def update_smart_details(dev) -> None: if not updated_attributes: dev.add_note('No NVMe or SMART data available', 'YELLOW') + # Update iniital_attributes if needed + if not dev.initial_attributes: + dev.initial_attributes = copy.deepcopy(updated_attributes) + # Done dev.attributes.update(updated_attributes) From 6a1cf98d0bc1b58c3288e50f82b86d28d1acbbc4 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sat, 8 Jul 2023 18:10:14 -0700 Subject: [PATCH 2/6] Terminate ddrescue directly instead --- scripts/wk/clone/ddrescue.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scripts/wk/clone/ddrescue.py b/scripts/wk/clone/ddrescue.py index 6e6f9568..abfd0d6b 100644 --- a/scripts/wk/clone/ddrescue.py +++ b/scripts/wk/clone/ddrescue.py @@ -1976,7 +1976,7 @@ def run_ddrescue(state, block_pair, pass_name, settings, dry_run=True) -> None: warning_message = check_destination_health(state.destination) if warning_message: # Error detected on destination, stop recovery - exe.stop_process(proc) + proc.terminate() cli.print_error(warning_message) break _i += 1 @@ -1994,7 +1994,7 @@ def run_ddrescue(state, block_pair, pass_name, settings, dry_run=True) -> None: LOG.warning('ddrescue stopped by user') warning_message = 'Aborted' std.sleep(2) - exe.stop_process(proc, graceful=False) + proc.terminate() break except subprocess.TimeoutExpired: # Continue to next loop to update panes From 0ace9513805f3886216fd844ee55a823c8e58b2e Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sat, 8 Jul 2023 18:10:59 -0700 Subject: [PATCH 3/6] Update run_program to avoid linting warnings --- scripts/wk/exe.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/scripts/wk/exe.py b/scripts/wk/exe.py index 92d48cdd..7b2ef344 100644 --- a/scripts/wk/exe.py +++ b/scripts/wk/exe.py @@ -256,8 +256,9 @@ def run_program( pipe=pipe, shell=shell, **kwargs) + check = cmd_kwargs.pop('check', True) # Avoids linting warning try: - proc = subprocess.run(**cmd_kwargs) + proc = subprocess.run(check=check, **cmd_kwargs) except FileNotFoundError: LOG.error('Command not found: %s', cmd) raise From 4a34f5477d296319e267480ab77d08de644e7a32 Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sat, 8 Jul 2023 18:32:31 -0700 Subject: [PATCH 4/6] Add delay to TUI() initialization Avoids issue where the main menu is printed before the layout is fully set causing the first few lines to be hidden by the title pane. --- scripts/wk/ui/tui.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/wk/ui/tui.py b/scripts/wk/ui/tui.py index a32dde76..e4389f50 100644 --- a/scripts/wk/ui/tui.py +++ b/scripts/wk/ui/tui.py @@ -211,6 +211,9 @@ class TUI(): ), )) + # Done + sleep(0.2) + def remove_all_info_panes(self) -> None: """Remove all info panes and update layout.""" self.layout['Info'].pop('height', None) From df1d2b713f153081ec99131a4ff9d787e2d68ced Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sat, 8 Jul 2023 18:37:42 -0700 Subject: [PATCH 5/6] Simplify _poweroff_source_drive() --- scripts/wk/clone/ddrescue.py | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/scripts/wk/clone/ddrescue.py b/scripts/wk/clone/ddrescue.py index abfd0d6b..aa71b6d5 100644 --- a/scripts/wk/clone/ddrescue.py +++ b/scripts/wk/clone/ddrescue.py @@ -1907,19 +1907,15 @@ def run_ddrescue(state, block_pair, pass_name, settings, dry_run=True) -> None: return # Sleep - i = 0 - while i < idle_minutes*60: + for i in range(1, idle_minutes*60, 1): if not poweroff_source_after_idle: # Countdown canceled, exit without powering-down drives return - if i % 600 == 0 and i > 0: - if i == 600: - cli.print_standard(' ', flush=True) + if i % 60 == 0: cli.print_warning( f'Powering off source in {int((idle_minutes*60-i)/60)} minutes...', ) - std.sleep(5) - i += 5 + std.sleep(1) # Power off drive cmd = ['sudo', 'hdparm', '-Y', source_dev] @@ -2034,7 +2030,7 @@ def run_ddrescue(state, block_pair, pass_name, settings, dry_run=True) -> None: # Stop source poweroff countdown cli.print_standard('Stopping device poweroff countdown...', flush=True) poweroff_source_after_idle = False - poweroff_thread.join() + poweroff_thread.join() # type: ignore[reportUnboundVariable] # Done raise std.GenericAbort() From a20fdf7bd352b55ddb05e05639d4b7bdb0004adf Mon Sep 17 00:00:00 2001 From: 2Shirt <2xShirt@gmail.com> Date: Sat, 8 Jul 2023 18:58:05 -0700 Subject: [PATCH 6/6] Only show destination SMART data if present. Also avoids crash when imaging --- scripts/wk/clone/ddrescue.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/scripts/wk/clone/ddrescue.py b/scripts/wk/clone/ddrescue.py index aa71b6d5..756d587e 100644 --- a/scripts/wk/clone/ddrescue.py +++ b/scripts/wk/clone/ddrescue.py @@ -1933,6 +1933,12 @@ def run_ddrescue(state, block_pair, pass_name, settings, dry_run=True) -> None: now = datetime.datetime.now(tz=TIMEZONE).strftime('%Y-%m-%d %H:%M %Z') for dev_str in ('source', 'destination'): dev = getattr(state, dev_str) + + # Safety check + if not hasattr(dev, 'attributes'): + continue + + # Update SMART data out_path = f'{state.log_dir}/smart_{dev_str}.out' update_smart_details(dev) with open(out_path, 'w', encoding='utf-8') as _f: @@ -2065,11 +2071,12 @@ def run_recovery(state: State, main_menu, settings_menu, dry_run=True) -> None: update_layout=False, watch_file=f'{state.log_dir}/smart_source.out', ) - state.ui.add_info_pane( - percent=50, - update_layout=False, - watch_file=f'{state.log_dir}/smart_destination.out', - ) + if hasattr(state.destination, 'attributes'): + state.ui.add_info_pane( + percent=50, + update_layout=False, + watch_file=f'{state.log_dir}/smart_destination.out', + ) if PLATFORM == 'Linux': state.ui.add_worker_pane(lines=4, cmd='journal-datarec-monitor') state.ui.set_current_pane_height(DDRESCUE_OUTPUT_HEIGHT)