Added size safety check to ddrescue TUI

This commit is contained in:
2Shirt 2019-12-26 19:27:08 -07:00
parent bd3601e0c8
commit fc0a37999b
Signed by: 2Shirt
GPG key ID: 152FAC923B0E132C

View file

@ -16,6 +16,8 @@ import time
from collections import OrderedDict from collections import OrderedDict
from docopt import docopt from docopt import docopt
import psutil
from wk import cfg, debug, exe, io, log, net, std, tmux from wk import cfg, debug, exe, io, log, net, std, tmux
from wk.hw import obj as hw_obj from wk.hw import obj as hw_obj
from wk.hw import sensors as hw_sensors from wk.hw import sensors as hw_sensors
@ -36,6 +38,7 @@ Options:
CLONE_SETTINGS = { CLONE_SETTINGS = {
'Source': None, 'Source': None,
'Destination': None, 'Destination': None,
'Create Boot Partition': False,
'First Run': True, 'First Run': True,
'Needs Format': False, 'Needs Format': False,
'Table Type': None, 'Table Type': None,
@ -175,7 +178,7 @@ class State():
settings = {} settings = {}
# Clone settings # Clone settings
settings = self.load_settings(working_dir) settings = self.load_settings(working_dir, discard_unused_settings=True)
# Add pairs # Add pairs
if settings['Partition Mapping']: if settings['Partition Mapping']:
@ -196,10 +199,11 @@ class State():
self.add_block_pair(self.source, bp_dest, working_dir) self.add_block_pair(self.source, bp_dest, working_dir)
else: else:
# New run, use new settings file # New run, use new settings file
settings['Needs Format'] = True
offset = 0 offset = 0
if std.ask('Create an empty Windows boot partition on the clone?'): if std.ask('Create an empty Windows boot partition on the clone?'):
offset = 2 offset = 2
settings['Needs Format'] = True settings['Create Boot Partition'] = True
settings['Table Type'] = 'GPT' settings['Table Type'] = 'GPT'
if std.choice(['G', 'M'], 'GPT or MBR partition table?') == 'M': if std.choice(['G', 'M'], 'GPT or MBR partition table?') == 'M':
offset = 1 offset = 1
@ -281,7 +285,7 @@ class State():
report.extend( report.extend(
build_block_pair_report( build_block_pair_report(
self.block_pairs, self.block_pairs,
self.load_settings(working_dir, False) if mode == 'Clone' else {}, self.load_settings(working_dir) if mode == 'Clone' else {},
), ),
) )
report.append(' ') report.append(' ')
@ -427,6 +431,9 @@ class State():
source_parts = select_disk_parts(mode, self.source) source_parts = select_disk_parts(mode, self.source)
self.add_image_block_pairs(source_parts, working_dir) self.add_image_block_pairs(source_parts, working_dir)
# Safety Check
self.safety_check(mode, working_dir)
# Confirmation #2 # Confirmation #2
self.confirm_selections(mode, 'Start recovery?', working_dir=working_dir) self.confirm_selections(mode, 'Start recovery?', working_dir=working_dir)
@ -513,6 +520,52 @@ class State():
# Done # Done
return settings return settings
def safety_check(self, mode, working_dir):
"""Run safety check and abort if necessary."""
required_size = sum([pair.size for pair in self.block_pairs])
# Increase required_size if necessary
if mode == 'Clone' and settings['Needs Format']:
settings = self.load_settings(working_dir)
if settings['Table Type'] == 'GPT':
# Below is the size calculation for the GPT
# 1 LBA for the protective MBR
# 33 LBAs each for the primary and backup GPT tables
# Source: https://en.wikipedia.org/wiki/GUID_Partition_Table
required_size += (1 + 33 + 33) * self.destination.details['phy-sec']
if settings['Create Boot Partition']:
# 384MiB EFI System Partition and a 16MiB MS Reserved partition
required_size += (384 + 16) * 1024**2
else:
# MBR only requires one LBA but adding a full 4096 bytes anyway
required_size += 4096
if settings['Create Boot Partition']:
# 100MiB System Reserved partition
required_size += 100 * 1024**2
# Reduce required_size if necessary
if mode == 'Image':
for pair in self.block_pairs:
if pair.destination.exists():
# NOTE: This uses the "max space" of the destination
# i.e. not the apparent size which is smaller for sparse files
# While this can result in an out-of-space error it's better
# than nothing.
required_size -= pair.destination.stat().st_size
# Check destination size
if mode == 'Clone':
destination_size = self.destination.details['size']
error_msg = 'A larger destination disk is required'
else:
# NOTE: Adding an extra 5% here to better ensure it will fit
destination_size = psutil.disk_usage(self.destination).free
destination_size *= 1.05
error_msg = 'Not enough free space on the destination'
if required_size > destination_size:
std.print_error(error_msg)
raise std.GenericAbort()
def save_debug_reports(self): def save_debug_reports(self):
"""Save debug reports to disk.""" """Save debug reports to disk."""
LOG.info('Saving debug reports') LOG.info('Saving debug reports')
@ -631,7 +684,7 @@ def build_block_pair_report(block_pairs, settings):
return report return report
# Show block pair mapping # Show block pair mapping
if settings and settings['Needs Format']: if settings and settings['Create Boot Partition']:
if settings['Table Type'] == 'GPT': if settings['Table Type'] == 'GPT':
report.append(f'{" —— ":<9} --> EFI System Partition') report.append(f'{" —— ":<9} --> EFI System Partition')
report.append(f'{" —— ":<9} --> Microsoft Reserved Partition') report.append(f'{" —— ":<9} --> Microsoft Reserved Partition')