dwipe 3.0.2__tar.gz → 3.2.0__tar.gz
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-3.0.2 → dwipe-3.2.0}/PKG-INFO +25 -5
- {dwipe-3.0.2 → dwipe-3.2.0}/README.md +8 -1
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/DeviceChangeMonitor.py +2 -3
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/DeviceInfo.py +17 -19
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/DeviceWorker.py +8 -8
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/DiskWipe.py +33 -19
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/DrivePreChecker.py +3 -1
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/FirmwareWipeTask.py +6 -7
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/NvmeTool.py +9 -5
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/PersistentState.py +4 -3
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/Prereqs.py +3 -4
- dwipe-3.2.0/dwipe/README-DEV.md +186 -0
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/SataTool.py +12 -8
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/StructuredLogger.py +22 -22
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/Tunables.py +2 -0
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/Utils.py +7 -6
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/VerifyTask.py +2 -3
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/WipeJob.py +9 -10
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/WriteTask.py +3 -4
- dwipe-3.2.0/dwipe/__init__.py +10 -0
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/main.py +10 -4
- dwipe-3.2.0/pyproject.toml +150 -0
- dwipe-3.0.2/.github/workflows/tests.yml +0 -47
- dwipe-3.0.2/.gitignore +0 -10
- dwipe-3.0.2/dwipe/__init__.py +0 -0
- dwipe-3.0.2/images/dwipe-2025-12-31-09-37.gif +0 -0
- dwipe-3.0.2/images/dwipe-2026-02-05.07-58.gif +0 -0
- dwipe-3.0.2/linter +0 -6
- dwipe-3.0.2/pyproject.toml +0 -49
- dwipe-3.0.2/resources/dwipe-help-screen.png +0 -0
- dwipe-3.0.2/resources/dwipe-main-screen.png +0 -0
- dwipe-3.0.2/runner +0 -19
- dwipe-3.0.2/tests/conftest.py +0 -50
- dwipe-3.0.2/tests/device_double.py +0 -135
- dwipe-3.0.2/tests/test_cli_options.py +0 -25
- dwipe-3.0.2/tests/test_device_change_monitor.py +0 -87
- dwipe-3.0.2/tests/test_device_io.py +0 -165
- dwipe-3.0.2/tests/test_device_worker.py +0 -228
- dwipe-3.0.2/tests/test_deviceinfo_logic.py +0 -314
- dwipe-3.0.2/tests/test_diskwipe_terminal.py +0 -41
- dwipe-3.0.2/tests/test_drive_prechecker.py +0 -157
- dwipe-3.0.2/tests/test_exception_observability.py +0 -92
- dwipe-3.0.2/tests/test_firmware_wipe_task.py +0 -126
- dwipe-3.0.2/tests/test_nvme_tool.py +0 -182
- dwipe-3.0.2/tests/test_persistent_state.py +0 -203
- dwipe-3.0.2/tests/test_safe_write.py +0 -90
- dwipe-3.0.2/tests/test_satatool_parse.py +0 -118
- dwipe-3.0.2/tests/test_structured_logger.py +0 -167
- dwipe-3.0.2/tests/test_utils.py +0 -172
- dwipe-3.0.2/tests/test_verification.py +0 -97
- dwipe-3.0.2/tests/test_verify_analysis.py +0 -80
- dwipe-3.0.2/tests/test_wipejob_logic.py +0 -208
- dwipe-3.0.2/tests/test_wipejob_resume.py +0 -214
- {dwipe-3.0.2 → dwipe-3.2.0}/LICENSE +0 -0
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/WipeTask.py +0 -0
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/device_io.py +0 -0
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/markers.py +1 -1
- {dwipe-3.0.2 → dwipe-3.2.0}/dwipe/verification.py +1 -1
|
@@ -1,21 +1,34 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
2
|
Name: dwipe
|
|
3
|
-
Version: 3.0
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: A tool to wipe disks and partitions for Linux
|
|
5
5
|
Keywords: disk,partition,wipe,clean,scrub
|
|
6
6
|
Author-email: Joe Defen <joedef@google.com>
|
|
7
7
|
Requires-Python: >=3.10
|
|
8
8
|
Description-Content-Type: text/markdown
|
|
9
|
+
License-Expression: MIT
|
|
9
10
|
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier:
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
11
16
|
Classifier: Operating System :: POSIX :: Linux
|
|
12
17
|
License-File: LICENSE
|
|
13
|
-
Requires-Dist: console-window == 1.
|
|
18
|
+
Requires-Dist: console-window == 2.1.3
|
|
19
|
+
Requires-Dist: pytest >= 7.0 ; extra == "dev"
|
|
20
|
+
Requires-Dist: pytest-cov >= 4.0 ; extra == "dev"
|
|
21
|
+
Requires-Dist: ruff >= 0.9 ; extra == "dev"
|
|
22
|
+
Requires-Dist: build >= 1.2 ; extra == "dev"
|
|
23
|
+
Requires-Dist: twine >= 7 ; extra == "dev"
|
|
24
|
+
Requires-Dist: packaging >= 26 ; extra == "dev"
|
|
14
25
|
Requires-Dist: pytest >= 7.0 ; extra == "test"
|
|
15
26
|
Requires-Dist: pytest-cov >= 4.0 ; extra == "test"
|
|
16
27
|
Project-URL: Bug Tracker, https://github.com/joedefen/dwipe/issues
|
|
17
28
|
Project-URL: Homepage, https://github.com/joedefen/dwipe
|
|
29
|
+
Provides-Extra: dev
|
|
18
30
|
Provides-Extra: test
|
|
31
|
+
Import-Name: dwipe
|
|
19
32
|
|
|
20
33
|
# dwipe
|
|
21
34
|
`dwipe` is a tool to wipe disks and partitions for Linux to help secure your data. `dwipe` aims to reduce mistakes by providing ample information about your devices during selection.
|
|
@@ -71,7 +84,7 @@ Provides-Extra: test
|
|
|
71
84
|
## Installation
|
|
72
85
|
|
|
73
86
|
* **Recommended (using pipx):** `pipx install dwipe`
|
|
74
|
-
* **Verify installation:** `dwipe --help`
|
|
87
|
+
* **Verify installation:** `dwipe --version` (prints the release number) and `dwipe --help`
|
|
75
88
|
* **Uninstall:** `pipx uninstall dwipe`
|
|
76
89
|
|
|
77
90
|
## Quick Start (please READ THIS, at least)
|
|
@@ -149,6 +162,7 @@ Simply run `dwipe` from the command line without arguments: `dwipe`
|
|
|
149
162
|
- The block is remembered per device; once you unblock, it stays unblocked
|
|
150
163
|
- Set to `off` when re-provisioning many encrypted disks to avoid unblocking each one
|
|
151
164
|
- `--dump-lsblk` - Dump parsed device information and exit (for debugging)
|
|
165
|
+
- `--version` - Print the version and exit (works without root, like `--help`)
|
|
152
166
|
- `--help` - Show help message with all available options
|
|
153
167
|
|
|
154
168
|
### Color Legend
|
|
@@ -598,6 +612,12 @@ sudo hdparm --user-master u --security-disable NULL /dev/sdX
|
|
|
598
612
|
### Contributing
|
|
599
613
|
Issues and pull requests welcome at [github.com/joedefen/dwipe](https://github.com/joedefen/dwipe)
|
|
600
614
|
|
|
615
|
+
To work on the source tree, see
|
|
616
|
+
[dwipe/README-DEV.md](dwipe/README-DEV.md): it documents the virtualenv + `dev`
|
|
617
|
+
extra, the test gate (`ruff`, `compileall`, `pytest --cov`), and `./bless`, the
|
|
618
|
+
only path to a release -- a single command that runs every gate, rehearses on
|
|
619
|
+
TestPyPI when asked to, and tags, uploads and pushes only when asked to.
|
|
620
|
+
|
|
601
621
|
## License
|
|
602
622
|
|
|
603
623
|
MIT License - see [LICENSE](LICENSE) file for details.
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
## Installation
|
|
53
53
|
|
|
54
54
|
* **Recommended (using pipx):** `pipx install dwipe`
|
|
55
|
-
* **Verify installation:** `dwipe --help`
|
|
55
|
+
* **Verify installation:** `dwipe --version` (prints the release number) and `dwipe --help`
|
|
56
56
|
* **Uninstall:** `pipx uninstall dwipe`
|
|
57
57
|
|
|
58
58
|
## Quick Start (please READ THIS, at least)
|
|
@@ -130,6 +130,7 @@ Simply run `dwipe` from the command line without arguments: `dwipe`
|
|
|
130
130
|
- The block is remembered per device; once you unblock, it stays unblocked
|
|
131
131
|
- Set to `off` when re-provisioning many encrypted disks to avoid unblocking each one
|
|
132
132
|
- `--dump-lsblk` - Dump parsed device information and exit (for debugging)
|
|
133
|
+
- `--version` - Print the version and exit (works without root, like `--help`)
|
|
133
134
|
- `--help` - Show help message with all available options
|
|
134
135
|
|
|
135
136
|
### Color Legend
|
|
@@ -579,6 +580,12 @@ sudo hdparm --user-master u --security-disable NULL /dev/sdX
|
|
|
579
580
|
### Contributing
|
|
580
581
|
Issues and pull requests welcome at [github.com/joedefen/dwipe](https://github.com/joedefen/dwipe)
|
|
581
582
|
|
|
583
|
+
To work on the source tree, see
|
|
584
|
+
[dwipe/README-DEV.md](dwipe/README-DEV.md): it documents the virtualenv + `dev`
|
|
585
|
+
extra, the test gate (`ruff`, `compileall`, `pytest --cov`), and `./bless`, the
|
|
586
|
+
only path to a release -- a single command that runs every gate, rehearses on
|
|
587
|
+
TestPyPI when asked to, and tags, uploads and pushes only when asked to.
|
|
588
|
+
|
|
582
589
|
## License
|
|
583
590
|
|
|
584
591
|
MIT License - see [LICENSE](LICENSE) file for details.
|
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
"""
|
|
2
2
|
DeviceChangeMonitor - Background thread for monitoring block device changes
|
|
3
3
|
"""
|
|
4
|
+
import ctypes
|
|
4
5
|
import os
|
|
5
6
|
import select
|
|
6
7
|
import threading
|
|
7
8
|
import traceback
|
|
8
|
-
import ctypes
|
|
9
9
|
from ctypes.util import find_library
|
|
10
10
|
|
|
11
|
-
|
|
12
11
|
# inotify constants (from Linux headers)
|
|
13
12
|
IN_CREATE = 0x00000100
|
|
14
13
|
IN_DELETE = 0x00000200
|
|
@@ -153,7 +152,7 @@ class DeviceChangeMonitor:
|
|
|
153
152
|
current_devs = os.listdir('/sys/class/block')
|
|
154
153
|
|
|
155
154
|
# 2. Secondary check: Do the partition sizes/counts match?
|
|
156
|
-
with open('/proc/partitions',
|
|
155
|
+
with open('/proc/partitions', encoding='utf-8') as f:
|
|
157
156
|
current_parts = f.read()
|
|
158
157
|
|
|
159
158
|
# Create a combined "Fingerprint"
|
|
@@ -6,20 +6,18 @@ DeviceInfo class for device discovery and information management
|
|
|
6
6
|
# pylint: disable=too-many-return-statements,too-many-nested-blocks
|
|
7
7
|
# pylint: disable=too-many-statements
|
|
8
8
|
|
|
9
|
+
import curses
|
|
9
10
|
import os
|
|
10
11
|
import re
|
|
11
|
-
import json
|
|
12
|
-
import subprocess
|
|
13
|
-
import curses
|
|
14
12
|
import traceback
|
|
15
13
|
from fnmatch import fnmatch
|
|
16
14
|
from types import SimpleNamespace
|
|
15
|
+
|
|
17
16
|
from console_window import Theme
|
|
18
|
-
from dataclasses import asdict
|
|
19
17
|
|
|
20
|
-
from .Utils import Utils
|
|
21
|
-
from .DrivePreChecker import DrivePreChecker
|
|
22
18
|
from .DeviceWorker import DeviceWorkerManager, ProbeState
|
|
19
|
+
from .DrivePreChecker import DrivePreChecker
|
|
20
|
+
from .Utils import Utils
|
|
23
21
|
|
|
24
22
|
|
|
25
23
|
class DeviceInfo:
|
|
@@ -209,7 +207,7 @@ class DeviceInfo:
|
|
|
209
207
|
try:
|
|
210
208
|
rv = ''
|
|
211
209
|
fullpath = f'/sys/class/block/{device_name}/device/{suffix}'
|
|
212
|
-
with open(fullpath,
|
|
210
|
+
with open(fullpath, encoding='utf-8') as f: # Read information
|
|
213
211
|
# Sanitize: some USB bridges return strings with embedded nulls
|
|
214
212
|
rv = f.read().strip().replace('\x00', '')
|
|
215
213
|
except (FileNotFoundError, Exception):
|
|
@@ -240,7 +238,7 @@ class DeviceInfo:
|
|
|
240
238
|
parent = device_name
|
|
241
239
|
|
|
242
240
|
path = f'/sys/block/{parent}/queue/rotational'
|
|
243
|
-
with open(path,
|
|
241
|
+
with open(path, encoding='utf-8') as f:
|
|
244
242
|
return f.read().strip() == '1'
|
|
245
243
|
except (FileNotFoundError, PermissionError, OSError):
|
|
246
244
|
return False
|
|
@@ -261,7 +259,7 @@ class DeviceInfo:
|
|
|
261
259
|
"""
|
|
262
260
|
devices = {}
|
|
263
261
|
try:
|
|
264
|
-
with open('/proc/partitions',
|
|
262
|
+
with open('/proc/partitions', encoding='utf-8') as f:
|
|
265
263
|
for line in f:
|
|
266
264
|
parts = line.split()
|
|
267
265
|
if len(parts) == 4 and parts[0].isdigit():
|
|
@@ -315,7 +313,7 @@ class DeviceInfo:
|
|
|
315
313
|
"""
|
|
316
314
|
mounts = {}
|
|
317
315
|
try:
|
|
318
|
-
with open('/proc/mounts',
|
|
316
|
+
with open('/proc/mounts', encoding='utf-8') as f:
|
|
319
317
|
for line in f:
|
|
320
318
|
parts = line.split()
|
|
321
319
|
if len(parts) >= 3 and parts[0].startswith('/dev/'):
|
|
@@ -336,7 +334,7 @@ class DeviceInfo:
|
|
|
336
334
|
"""Return canonical block names currently used as swap (non-blocking)."""
|
|
337
335
|
names = set()
|
|
338
336
|
try:
|
|
339
|
-
with open('/proc/swaps',
|
|
337
|
+
with open('/proc/swaps', encoding='utf-8') as f:
|
|
340
338
|
next(f, None) # skip header line
|
|
341
339
|
for line in f:
|
|
342
340
|
parts = line.split()
|
|
@@ -410,7 +408,7 @@ class DeviceInfo:
|
|
|
410
408
|
if DeviceInfo._read_sysfs_dir(f'/sys/class/block/{name}/holders'):
|
|
411
409
|
return True
|
|
412
410
|
try:
|
|
413
|
-
with open('/proc/mounts',
|
|
411
|
+
with open('/proc/mounts', encoding='utf-8') as f:
|
|
414
412
|
for line in f:
|
|
415
413
|
parts = line.split()
|
|
416
414
|
if parts and DeviceInfo._block_name_from_devpath(parts[0]) == name:
|
|
@@ -463,7 +461,7 @@ class DeviceInfo:
|
|
|
463
461
|
Reads /sys/class/block/{name}/size which contains sector count.
|
|
464
462
|
"""
|
|
465
463
|
try:
|
|
466
|
-
with open(f'/sys/class/block/{name}/size',
|
|
464
|
+
with open(f'/sys/class/block/{name}/size', encoding='utf-8') as f:
|
|
467
465
|
sectors = int(f.read().strip())
|
|
468
466
|
return sectors * 512 # Convert to bytes
|
|
469
467
|
except (FileNotFoundError, ValueError, Exception):
|
|
@@ -477,7 +475,7 @@ class DeviceInfo:
|
|
|
477
475
|
"""
|
|
478
476
|
# Try NVMe-style path first (plain text)
|
|
479
477
|
try:
|
|
480
|
-
with open(f'/sys/class/block/{name}/device/serial',
|
|
478
|
+
with open(f'/sys/class/block/{name}/device/serial', encoding='utf-8') as f:
|
|
481
479
|
# Sanitize: some USB bridges return strings with embedded nulls
|
|
482
480
|
return f.read().strip().replace('\x00', '')
|
|
483
481
|
except (FileNotFoundError, Exception):
|
|
@@ -531,7 +529,7 @@ class DeviceInfo:
|
|
|
531
529
|
result['uuid'] = line.split('=', 1)[1].strip()
|
|
532
530
|
elif line.startswith('E:ID_PART_ENTRY_UUID=') and not result['uuid']:
|
|
533
531
|
result['uuid'] = line.split('=', 1)[1].strip()
|
|
534
|
-
except (FileNotFoundError,
|
|
532
|
+
except (FileNotFoundError, OSError):
|
|
535
533
|
pass
|
|
536
534
|
return result
|
|
537
535
|
|
|
@@ -733,7 +731,7 @@ class DeviceInfo:
|
|
|
733
731
|
|
|
734
732
|
# Phase 4b: Clear marker display and state for disks with actual partitions
|
|
735
733
|
# If a disk has partitions, the partitions take precedence over the wipe marker
|
|
736
|
-
for
|
|
734
|
+
for entry in entries.values():
|
|
737
735
|
if entry.minors: # Disk has child partitions
|
|
738
736
|
entry.marker = '' # Don't show marker when partitions exist
|
|
739
737
|
# Also clear the state if it was set to a wipe state ('W' or 's')
|
|
@@ -875,7 +873,7 @@ class DeviceInfo:
|
|
|
875
873
|
# Must be writable (excludes CD-ROMs, eMMC boot partitions, etc.)
|
|
876
874
|
ro_path = f'/sys/class/block/{name}/ro'
|
|
877
875
|
try:
|
|
878
|
-
with open(ro_path,
|
|
876
|
+
with open(ro_path, encoding='utf-8') as f:
|
|
879
877
|
if f.read().strip() != '0':
|
|
880
878
|
continue # Skip read-only devices
|
|
881
879
|
except (FileNotFoundError, Exception):
|
|
@@ -1262,7 +1260,7 @@ class DeviceInfo:
|
|
|
1262
1260
|
if part.parent == disk.name]
|
|
1263
1261
|
|
|
1264
1262
|
if disk_parts:
|
|
1265
|
-
for i, (
|
|
1263
|
+
for i, (_, part) in enumerate(sorted(disk_parts)):
|
|
1266
1264
|
is_last = (i == len(disk_parts) - 1)
|
|
1267
1265
|
branch = '└─' if is_last else '├─'
|
|
1268
1266
|
|
|
@@ -1281,7 +1279,7 @@ class DeviceInfo:
|
|
|
1281
1279
|
mounts_str = ', '.join(part.mounts)
|
|
1282
1280
|
print(f'│ {" " if is_last else "│ "} Mounted: {mounts_str}')
|
|
1283
1281
|
else:
|
|
1284
|
-
print(
|
|
1282
|
+
print('│ └─ (no partitions)')
|
|
1285
1283
|
|
|
1286
1284
|
print('│')
|
|
1287
1285
|
|
|
@@ -5,9 +5,9 @@ Each device gets its own worker thread that handles potentially-blocking
|
|
|
5
5
|
operations (sysfs reads, hdparm, nvme commands) without freezing the UI.
|
|
6
6
|
"""
|
|
7
7
|
import os
|
|
8
|
-
import time
|
|
9
|
-
import threading
|
|
10
8
|
import queue
|
|
9
|
+
import threading
|
|
10
|
+
import time
|
|
11
11
|
from enum import Enum
|
|
12
12
|
|
|
13
13
|
from .DrivePreChecker import DrivePreChecker
|
|
@@ -315,8 +315,8 @@ class DeviceWorker(threading.Thread):
|
|
|
315
315
|
"""Probe marker once and format the result as a string."""
|
|
316
316
|
try:
|
|
317
317
|
# Import here to avoid circular dependency
|
|
318
|
+
|
|
318
319
|
from .WipeJob import WipeJob
|
|
319
|
-
import datetime
|
|
320
320
|
|
|
321
321
|
marker = WipeJob.read_marker_buffer(self.device_name)
|
|
322
322
|
|
|
@@ -381,10 +381,10 @@ class DeviceWorker(threading.Thread):
|
|
|
381
381
|
"""
|
|
382
382
|
path = f"/sys/class/block/{self.device_name}/{attr_path}"
|
|
383
383
|
try:
|
|
384
|
-
with open(path
|
|
384
|
+
with open(path) as f:
|
|
385
385
|
# Sanitize: some USB bridges return strings with embedded nulls
|
|
386
386
|
return f.read().strip().replace('\x00', '')
|
|
387
|
-
except (FileNotFoundError,
|
|
387
|
+
except (FileNotFoundError, OSError):
|
|
388
388
|
return ''
|
|
389
389
|
|
|
390
390
|
def _read_vpd_serial(self):
|
|
@@ -399,7 +399,7 @@ class DeviceWorker(threading.Thread):
|
|
|
399
399
|
if len(data) > 4:
|
|
400
400
|
# Sanitize: remove nulls that some USB bridges include
|
|
401
401
|
return data[4:].decode('ascii', errors='ignore').strip().replace('\x00', '')
|
|
402
|
-
except (FileNotFoundError,
|
|
402
|
+
except (FileNotFoundError, OSError):
|
|
403
403
|
pass
|
|
404
404
|
return ''
|
|
405
405
|
|
|
@@ -409,7 +409,7 @@ class DeviceWorker(threading.Thread):
|
|
|
409
409
|
sysfs_path = f'/sys/class/block/{self.device_name}'
|
|
410
410
|
real_path = os.path.realpath(sysfs_path).lower()
|
|
411
411
|
return '/usb' in real_path
|
|
412
|
-
except
|
|
412
|
+
except OSError:
|
|
413
413
|
return False
|
|
414
414
|
|
|
415
415
|
def _is_rotational_device(self):
|
|
@@ -424,7 +424,7 @@ class DeviceWorker(threading.Thread):
|
|
|
424
424
|
"""
|
|
425
425
|
try:
|
|
426
426
|
path = f'/sys/block/{self.device_name}/queue/rotational'
|
|
427
|
-
with open(path,
|
|
427
|
+
with open(path, encoding='utf-8') as f:
|
|
428
428
|
return f.read().strip() == '1'
|
|
429
429
|
except (FileNotFoundError, PermissionError, OSError):
|
|
430
430
|
return False
|
|
@@ -6,28 +6,38 @@ DiskWipe class - Main application controller/singleton
|
|
|
6
6
|
# pylint: disable=too-many-branches,too-many-statements,too-many-locals
|
|
7
7
|
# pylint: disable=protected-access,too-many-return-statements
|
|
8
8
|
# pylint: disable=too-few-public-methods
|
|
9
|
+
import curses as cs
|
|
10
|
+
|
|
11
|
+
# import shutil
|
|
12
|
+
import json
|
|
9
13
|
import os
|
|
10
|
-
import sys
|
|
11
|
-
import subprocess
|
|
12
14
|
import re
|
|
13
|
-
import
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
14
17
|
import threading
|
|
15
|
-
|
|
16
|
-
import json
|
|
17
|
-
import curses as cs
|
|
18
|
-
from types import SimpleNamespace
|
|
18
|
+
import time
|
|
19
19
|
from datetime import datetime
|
|
20
|
-
from
|
|
21
|
-
IncrementalSearchBar, InlineConfirmation, Theme,
|
|
22
|
-
Screen, ScreenStack, Context)
|
|
20
|
+
from types import SimpleNamespace
|
|
23
21
|
|
|
24
|
-
from
|
|
22
|
+
from console_window import (
|
|
23
|
+
ConsoleWindow,
|
|
24
|
+
ConsoleWindowOpts,
|
|
25
|
+
Context,
|
|
26
|
+
IncrementalSearchBar,
|
|
27
|
+
InlineConfirmation,
|
|
28
|
+
OptionSpinner,
|
|
29
|
+
Screen,
|
|
30
|
+
ScreenStack,
|
|
31
|
+
Theme,
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
from .DeviceChangeMonitor import DeviceChangeMonitor
|
|
25
35
|
from .DeviceInfo import DeviceInfo
|
|
26
36
|
from .DeviceWorker import ProbeState
|
|
27
|
-
from .Utils import Utils
|
|
28
37
|
from .PersistentState import PersistentState
|
|
29
38
|
from .StructuredLogger import StructuredLogger
|
|
30
|
-
from .
|
|
39
|
+
from .Utils import Utils
|
|
40
|
+
from .WipeJob import WipeJob
|
|
31
41
|
|
|
32
42
|
|
|
33
43
|
def _restore_terminal():
|
|
@@ -144,9 +154,13 @@ class DiskWipe:
|
|
|
144
154
|
command_args = DrivePreChecker.get_wipe_command_args(wipe_type)
|
|
145
155
|
|
|
146
156
|
# Import firmware task classes
|
|
147
|
-
from .FirmwareWipeTask import (
|
|
148
|
-
|
|
149
|
-
|
|
157
|
+
from .FirmwareWipeTask import (
|
|
158
|
+
FirmwarePostVerifyTask,
|
|
159
|
+
FirmwarePreVerifyTask,
|
|
160
|
+
NvmeWipeTask,
|
|
161
|
+
SataWipeTask,
|
|
162
|
+
StandardPrecheckTask,
|
|
163
|
+
)
|
|
150
164
|
|
|
151
165
|
# Determine task type based on device name
|
|
152
166
|
if part.name.startswith('nvme'):
|
|
@@ -1033,9 +1047,9 @@ class MainScreen(DiskWipeScreen):
|
|
|
1033
1047
|
if app.confirmation.active and app.confirmation.identity == partition.name:
|
|
1034
1048
|
# Build confirmation message
|
|
1035
1049
|
if app.confirmation.action_type == 'wipe':
|
|
1036
|
-
msg =
|
|
1050
|
+
msg = '⚠️ WIPE'
|
|
1037
1051
|
else: # verify
|
|
1038
|
-
msg =
|
|
1052
|
+
msg = '⚠️ VERIFY [writes marker]'
|
|
1039
1053
|
|
|
1040
1054
|
# Add mode-specific prompt (base message without input)
|
|
1041
1055
|
if app.confirmation.mode == 'yes':
|
|
@@ -1296,7 +1310,7 @@ class MainScreen(DiskWipeScreen):
|
|
|
1296
1310
|
# Check if this is an NVMe controller (class 0x010802)
|
|
1297
1311
|
class_file = os.path.join(device_path, 'class')
|
|
1298
1312
|
try:
|
|
1299
|
-
with open(class_file,
|
|
1313
|
+
with open(class_file, encoding='utf-8') as f:
|
|
1300
1314
|
device_class = f.read().strip()
|
|
1301
1315
|
if device_class != '0x010802':
|
|
1302
1316
|
continue
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
|
-
import subprocess
|
|
3
2
|
import json
|
|
3
|
+
import subprocess
|
|
4
4
|
from dataclasses import dataclass
|
|
5
|
+
|
|
5
6
|
from .SataTool import SataTool
|
|
6
7
|
from .Utils import Utils
|
|
7
8
|
|
|
9
|
+
|
|
8
10
|
@dataclass
|
|
9
11
|
class PreCheckResult:
|
|
10
12
|
# Dict of capabilities and issues
|
|
@@ -11,17 +11,16 @@ Includes:
|
|
|
11
11
|
# pylint: disable=too-many-positional-arguments,consider-using-with
|
|
12
12
|
# pylint: disable=too-many-return-statements
|
|
13
13
|
import os
|
|
14
|
-
import time
|
|
15
14
|
import random
|
|
16
15
|
import subprocess
|
|
16
|
+
import time
|
|
17
17
|
import traceback
|
|
18
18
|
|
|
19
|
-
from . import device_io
|
|
20
|
-
from . import markers
|
|
21
|
-
from .WipeTask import WipeTask
|
|
22
|
-
from .Utils import Utils
|
|
23
|
-
from .SataTool import SataTool
|
|
19
|
+
from . import device_io, markers
|
|
24
20
|
from .NvmeTool import NvmeTool
|
|
21
|
+
from .SataTool import SataTool
|
|
22
|
+
from .Utils import Utils
|
|
23
|
+
from .WipeTask import WipeTask
|
|
25
24
|
|
|
26
25
|
|
|
27
26
|
class FirmwareWipeTask(WipeTask):
|
|
@@ -618,7 +617,7 @@ class FirmwarePreVerifyTask(WipeTask):
|
|
|
618
617
|
|
|
619
618
|
# Generate pseudo-random test data
|
|
620
619
|
random.seed(int(time.time()))
|
|
621
|
-
for
|
|
620
|
+
for _ in range(self.NUM_BLOCKS):
|
|
622
621
|
block = bytes([random.randint(0, 255) for _ in range(self.BLOCK_SIZE)])
|
|
623
622
|
self.original_blocks.append(block)
|
|
624
623
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import os
|
|
2
|
-
import json
|
|
3
|
-
import subprocess
|
|
4
1
|
import io
|
|
2
|
+
import json
|
|
5
3
|
import re
|
|
4
|
+
import subprocess
|
|
6
5
|
import time
|
|
7
6
|
from types import SimpleNamespace
|
|
7
|
+
|
|
8
8
|
from .Utils import Utils
|
|
9
9
|
|
|
10
10
|
|
|
@@ -200,12 +200,16 @@ class NvmeTool:
|
|
|
200
200
|
return self.job.process
|
|
201
201
|
|
|
202
202
|
except Exception as e:
|
|
203
|
+
# Bind the message before leaving the handler: Python unbinds `e`
|
|
204
|
+
# at the end of the `except` block, so a lambda closing over it
|
|
205
|
+
# would raise NameError when the task manager called it.
|
|
206
|
+
error_text = str(e)
|
|
203
207
|
self.job.process = SimpleNamespace(
|
|
204
208
|
poll=lambda: 1,
|
|
205
209
|
returncode=1,
|
|
206
210
|
stdout=io.StringIO(""),
|
|
207
|
-
stderr=io.StringIO(
|
|
208
|
-
communicate=lambda: ("",
|
|
211
|
+
stderr=io.StringIO(error_text),
|
|
212
|
+
communicate=lambda: ("", error_text)
|
|
209
213
|
)
|
|
210
214
|
return self.job.process
|
|
211
215
|
|
|
@@ -5,6 +5,7 @@ PersistentState class for saving user preferences and device states
|
|
|
5
5
|
import json
|
|
6
6
|
import time
|
|
7
7
|
from pathlib import Path
|
|
8
|
+
|
|
8
9
|
from .Utils import Utils
|
|
9
10
|
|
|
10
11
|
|
|
@@ -63,7 +64,7 @@ class PersistentState:
|
|
|
63
64
|
# Remember valid keys from default state
|
|
64
65
|
valid_keys = set(self.state.keys())
|
|
65
66
|
|
|
66
|
-
with open(self.config_path,
|
|
67
|
+
with open(self.config_path, encoding='utf-8') as f:
|
|
67
68
|
loaded = json.load(f)
|
|
68
69
|
|
|
69
70
|
# Only load keys that exist in current schema
|
|
@@ -87,7 +88,7 @@ class PersistentState:
|
|
|
87
88
|
self.state['wipe_mode'] = '-V'
|
|
88
89
|
self.dirty = True # Save the migration
|
|
89
90
|
|
|
90
|
-
except (json.JSONDecodeError
|
|
91
|
+
except (OSError, json.JSONDecodeError) as e:
|
|
91
92
|
print(f'Warning: Could not load state from {self.config_path}: {e}')
|
|
92
93
|
|
|
93
94
|
def save(self):
|
|
@@ -104,7 +105,7 @@ class PersistentState:
|
|
|
104
105
|
# Fix ownership if running with sudo
|
|
105
106
|
Utils.fix_file_ownership(self.config_path)
|
|
106
107
|
self.dirty = False
|
|
107
|
-
except
|
|
108
|
+
except OSError as e:
|
|
108
109
|
print(f'Warning: Could not save state to {self.config_path}: {e}')
|
|
109
110
|
|
|
110
111
|
def _cleanup_old_devices(self):
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env python3
|
|
2
2
|
import shutil
|
|
3
3
|
import sys
|
|
4
|
-
|
|
5
|
-
from typing import Optional, Dict, List
|
|
4
|
+
|
|
6
5
|
|
|
7
6
|
class Prereqs:
|
|
8
7
|
"""Manages tool dependencies and provides actionable install suggestions."""
|
|
@@ -29,14 +28,14 @@ class Prereqs:
|
|
|
29
28
|
# Track status of required tools
|
|
30
29
|
self.results = {}
|
|
31
30
|
|
|
32
|
-
def _detect_package_manager(self) ->
|
|
31
|
+
def _detect_package_manager(self) -> str | None:
|
|
33
32
|
managers = ['apt', 'dnf', 'yum', 'pacman', 'zypper', 'apk', 'brew']
|
|
34
33
|
for cmd in managers:
|
|
35
34
|
if shutil.which(cmd):
|
|
36
35
|
return cmd
|
|
37
36
|
return None
|
|
38
37
|
|
|
39
|
-
def check_all(self, tools:
|
|
38
|
+
def check_all(self, tools: list[str]):
|
|
40
39
|
"""Runs the check for a list of tools."""
|
|
41
40
|
for tool in tools:
|
|
42
41
|
path = shutil.which(tool)
|