dwipe 2.0.1__py3-none-any.whl → 3.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- dwipe/DeviceChangeMonitor.py +244 -0
- dwipe/DeviceInfo.py +703 -177
- dwipe/DeviceWorker.py +566 -0
- dwipe/DiskWipe.py +953 -214
- dwipe/DrivePreChecker.py +203 -0
- dwipe/FirmwareWipeTask.py +865 -0
- dwipe/NvmeTool.py +225 -0
- dwipe/PersistentState.py +45 -16
- dwipe/Prereqs.py +84 -0
- dwipe/SataTool.py +499 -0
- dwipe/StructuredLogger.py +644 -0
- dwipe/Tunables.py +62 -0
- dwipe/Utils.py +298 -3
- dwipe/VerifyTask.py +412 -0
- dwipe/WipeJob.py +631 -171
- dwipe/WipeTask.py +150 -0
- dwipe/WriteTask.py +402 -0
- dwipe/main.py +34 -9
- dwipe-3.0.0.dist-info/METADATA +566 -0
- dwipe-3.0.0.dist-info/RECORD +24 -0
- dwipe/ToolManager.py +0 -637
- dwipe/WipeJobFuture.py +0 -245
- dwipe-2.0.1.dist-info/METADATA +0 -410
- dwipe-2.0.1.dist-info/RECORD +0 -14
- {dwipe-2.0.1.dist-info → dwipe-3.0.0.dist-info}/WHEEL +0 -0
- {dwipe-2.0.1.dist-info → dwipe-3.0.0.dist-info}/entry_points.txt +0 -0
- {dwipe-2.0.1.dist-info → dwipe-3.0.0.dist-info}/licenses/LICENSE +0 -0
dwipe/DrivePreChecker.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import subprocess
|
|
3
|
+
import json
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
from .SataTool import SataTool
|
|
6
|
+
from .Utils import Utils
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class PreCheckResult:
|
|
10
|
+
# Dict of capabilities and issues
|
|
11
|
+
issues: dict = None # e.g. {"Frozen": reason, "Locked": reason}
|
|
12
|
+
modes: dict = None # e.g. {"Crypto": ..., "Block": ..., "Ovwr": ...}
|
|
13
|
+
|
|
14
|
+
def __post_init__(self):
|
|
15
|
+
if self.issues is None:
|
|
16
|
+
self.issues = {}
|
|
17
|
+
if self.modes is None:
|
|
18
|
+
self.modes = {}
|
|
19
|
+
|
|
20
|
+
class DrivePreChecker:
|
|
21
|
+
# Firmware wipe rankings for SSDs: lower number = better (1 = most desirable)
|
|
22
|
+
# SSDs benefit from firmware wipes due to wear leveling and hidden blocks
|
|
23
|
+
WIPE_RANKS_SSD = {
|
|
24
|
+
# NVMe modes (rank 1-5)
|
|
25
|
+
'Crypto': 1, # Sanitize Cryptographic Erase - fastest, instant key invalidation
|
|
26
|
+
'Block': 2, # Sanitize Block Erase - fast, deallocates blocks
|
|
27
|
+
'FCrypto': 3, # Format with Crypto Erase - fast, namespace reformat
|
|
28
|
+
'FErase': 4, # Format with User Data Erase - less thorough than crypto
|
|
29
|
+
'Ovwr': 5, # Sanitize Overwrite - slow, pattern write
|
|
30
|
+
# SATA SSD modes (rank 1-5)
|
|
31
|
+
'SCrypto': 1, # Sanitize Cryptographic Erase - fastest
|
|
32
|
+
'Enhanced': 2, # ATA Security Erase Enhanced - fast with crypto key destruction
|
|
33
|
+
'SBlock': 3, # Sanitize Block Erase - fast
|
|
34
|
+
'Erase': 4, # ATA Security Erase Normal - slow but widely supported
|
|
35
|
+
'SOverwrite': 5, # Sanitize Overwrite - slow
|
|
36
|
+
# Software wipes (fallback - can't reach wear-leveled blocks on SSDs)
|
|
37
|
+
'Rand': 6, # Software Random Write - fallback
|
|
38
|
+
'Zero': 7, # Software Zero Write - fallback
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
# Rankings for HDDs: software wipes preferred (interruptible, resumable, progress)
|
|
42
|
+
# HDDs have no wear leveling or hidden blocks, so software wipes are equally thorough
|
|
43
|
+
WIPE_RANKS_HDD = {
|
|
44
|
+
# Software wipes preferred for HDDs
|
|
45
|
+
'Rand': 1, # Software Random Write - preferred, interruptible, resumable
|
|
46
|
+
'Zero': 2, # Software Zero Write - preferred, fast verification
|
|
47
|
+
# Firmware wipes available but not recommended (slow, no progress, not interruptible)
|
|
48
|
+
'Enhanced': 3, # ATA Security Erase Enhanced - slow, no advantage over software
|
|
49
|
+
'Erase': 4, # ATA Security Erase Normal - slow, no advantage over software
|
|
50
|
+
'SCrypto': 5, # Sanitize Cryptographic Erase - rare on HDDs
|
|
51
|
+
'SBlock': 6, # Sanitize Block Erase - rare on HDDs
|
|
52
|
+
'SOverwrite': 7, # Sanitize Overwrite - very slow
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
# Default to SSD rankings (used for display, etc.)
|
|
56
|
+
WIPE_RANKS = WIPE_RANKS_SSD
|
|
57
|
+
|
|
58
|
+
@staticmethod
|
|
59
|
+
def sort_modes_by_rank(modes, add_star=True, is_rotational=False):
|
|
60
|
+
"""Sort wipe modes from worst to best rank (highest rank number first).
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
modes: iterable of mode names (e.g., ['Crypto', 'Block', 'Ovwr'])
|
|
64
|
+
add_star: if True, append '*' to the last (best) mode
|
|
65
|
+
is_rotational: if True, use HDD rankings (prefer software wipes)
|
|
66
|
+
|
|
67
|
+
Returns:
|
|
68
|
+
list of mode names sorted worst to best, optionally with '*' on last
|
|
69
|
+
"""
|
|
70
|
+
ranks = DrivePreChecker.WIPE_RANKS_HDD if is_rotational else DrivePreChecker.WIPE_RANKS_SSD
|
|
71
|
+
# Sort by rank descending (worst first), unknown modes get rank 99
|
|
72
|
+
sorted_modes = sorted(modes, key=lambda m: ranks.get(m, 99), reverse=True)
|
|
73
|
+
if add_star and sorted_modes:
|
|
74
|
+
sorted_modes[-1] = sorted_modes[-1] + '*'
|
|
75
|
+
return sorted_modes
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def get_fw_caps_summary(modes):
|
|
79
|
+
"""Get a compact summary of firmware wipe capabilities.
|
|
80
|
+
|
|
81
|
+
Args:
|
|
82
|
+
modes: iterable of mode names (e.g., ['Crypto', 'Block', 'Ovwr'])
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
str: Summary like 'ϟCrypto', 'ϟErase', or 'ϟOverwrite' based on best available,
|
|
86
|
+
or empty string if no firmware modes available
|
|
87
|
+
"""
|
|
88
|
+
if not modes:
|
|
89
|
+
return ''
|
|
90
|
+
|
|
91
|
+
ranks = DrivePreChecker.WIPE_RANKS
|
|
92
|
+
# Find the best (lowest rank) mode
|
|
93
|
+
best_mode = min(modes, key=lambda m: ranks.get(m, 99))
|
|
94
|
+
best_rank = ranks.get(best_mode, 99)
|
|
95
|
+
|
|
96
|
+
# Categorize: Crypto (rank 1 or name contains Crypto), Erase (2-4), Overwrite (5)
|
|
97
|
+
if best_rank == 1 or 'Crypto' in best_mode:
|
|
98
|
+
return 'ϟCrypto'
|
|
99
|
+
elif best_rank <= 4:
|
|
100
|
+
return 'ϟErase'
|
|
101
|
+
elif best_rank == 5:
|
|
102
|
+
return 'ϟOverwrite'
|
|
103
|
+
return ''
|
|
104
|
+
|
|
105
|
+
def __init__(self, timeout: int = 10):
|
|
106
|
+
self.timeout = timeout
|
|
107
|
+
|
|
108
|
+
@staticmethod
|
|
109
|
+
def get_wipe_command_args(wipe_type: str) -> str:
|
|
110
|
+
"""Get command arguments for a given wipe type name.
|
|
111
|
+
|
|
112
|
+
Maps wipe mode names to their corresponding firmware command arguments.
|
|
113
|
+
This is a static mapping independent of device capabilities.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
wipe_type: Wipe mode name (e.g., 'Crypto', 'Block', 'Enhanced', 'Erase')
|
|
117
|
+
|
|
118
|
+
Returns:
|
|
119
|
+
str: Command argument string (e.g., 'sanitize_crypto', 'enhanced')
|
|
120
|
+
Empty string if wipe_type is unknown
|
|
121
|
+
"""
|
|
122
|
+
wipe_args_map = {
|
|
123
|
+
# NVMe sanitize operations
|
|
124
|
+
'Crypto': 'sanitize_crypto',
|
|
125
|
+
'Block': 'sanitize_block',
|
|
126
|
+
'Ovwr': 'sanitize_overwrite',
|
|
127
|
+
# NVMe format operations
|
|
128
|
+
'FCrypto': 'format_crypto',
|
|
129
|
+
'FErase': 'format_erase',
|
|
130
|
+
# SATA sanitize operations
|
|
131
|
+
'SCrypto': 'sanitize_crypto',
|
|
132
|
+
'SBlock': 'sanitize_block',
|
|
133
|
+
'SOverwrite': 'sanitize_overwrite',
|
|
134
|
+
# SATA/ATA security erase operations
|
|
135
|
+
'Enhanced': 'enhanced',
|
|
136
|
+
'Erase': 'normal',
|
|
137
|
+
}
|
|
138
|
+
return wipe_args_map.get(wipe_type, '')
|
|
139
|
+
|
|
140
|
+
def check_nvme_drive(self, device: str) -> PreCheckResult:
|
|
141
|
+
result = PreCheckResult()
|
|
142
|
+
try:
|
|
143
|
+
id_ctrl = subprocess.run(
|
|
144
|
+
['nvme', 'id-ctrl', device, '-o', 'json'],
|
|
145
|
+
check=False, capture_output=True, text=True, timeout=self.timeout
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
if id_ctrl.returncode != 0:
|
|
149
|
+
result.issues['Unresponsive'] = "NVMe controller did not respond"
|
|
150
|
+
return result
|
|
151
|
+
|
|
152
|
+
data = json.loads(id_ctrl.stdout)
|
|
153
|
+
|
|
154
|
+
# 1. Sanitize Support
|
|
155
|
+
result.modes.update(Utils.parse_nvme_sanitize_capabilities(data))
|
|
156
|
+
|
|
157
|
+
# 2. Format Support
|
|
158
|
+
oncs = data.get('oncs', 0)
|
|
159
|
+
if oncs & 0x04: # Format NVM command supported
|
|
160
|
+
fna = data.get('fna', 0)
|
|
161
|
+
if fna & 0x04:
|
|
162
|
+
result.modes['FCrypto'] = 'format_crypto'
|
|
163
|
+
result.modes['FErase'] = 'format_erase'
|
|
164
|
+
|
|
165
|
+
if not result.modes:
|
|
166
|
+
result.issues['Unsupported'] = "Drive lacks Sanitize or Format NVM capabilities"
|
|
167
|
+
|
|
168
|
+
except Exception as e:
|
|
169
|
+
result.issues['Error'] = f"NVMe Probe Exception: {str(e)}"
|
|
170
|
+
|
|
171
|
+
return result
|
|
172
|
+
|
|
173
|
+
def check_ata_drive(self, device: str) -> PreCheckResult:
|
|
174
|
+
result = PreCheckResult()
|
|
175
|
+
try:
|
|
176
|
+
tool = SataTool(device)
|
|
177
|
+
verdict = tool.get_wipe_verdict()
|
|
178
|
+
if verdict == 'OK':
|
|
179
|
+
# Populate Modes only if no fatal issues
|
|
180
|
+
secures = tool.secures
|
|
181
|
+
|
|
182
|
+
# ATA Security Erase modes
|
|
183
|
+
if secures.enhanced_erase_supported:
|
|
184
|
+
result.modes['Enhanced'] = 'enhanced'
|
|
185
|
+
result.modes['Erase'] = 'normal'
|
|
186
|
+
|
|
187
|
+
# SATA Sanitize modes (if supported)
|
|
188
|
+
if secures.sanitize_supported:
|
|
189
|
+
if secures.sanitize_crypto_supported:
|
|
190
|
+
result.modes['SCrypto'] = 'sanitize_crypto'
|
|
191
|
+
if secures.sanitize_block_supported:
|
|
192
|
+
result.modes['SBlock'] = 'sanitize_block'
|
|
193
|
+
if secures.sanitize_overwrite_supported:
|
|
194
|
+
result.modes['SOverwrite'] = 'sanitize_overwrite'
|
|
195
|
+
elif verdict == 'DumbDevice':
|
|
196
|
+
pass # No security feature - don't report as error (e.g., USB thumb drive)
|
|
197
|
+
else:
|
|
198
|
+
result.issues[verdict] = verdict
|
|
199
|
+
|
|
200
|
+
except Exception as e:
|
|
201
|
+
result.issues['Error'] = f"ATA Probe Exception: {str(e)}"
|
|
202
|
+
|
|
203
|
+
return result
|