dwipe 3.0.1__tar.gz → 3.0.2__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/.github/workflows/tests.yml +47 -0
- {dwipe-3.0.1 → dwipe-3.0.2}/.gitignore +2 -1
- dwipe-3.0.1/README.md → dwipe-3.0.2/PKG-INFO +64 -10
- dwipe-3.0.1/PKG-INFO → dwipe-3.0.2/README.md +45 -26
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/DeviceInfo.py +183 -18
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/DeviceWorker.py +1 -1
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/DiskWipe.py +37 -10
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/FirmwareWipeTask.py +24 -51
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/NvmeTool.py +4 -4
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/PersistentState.py +25 -2
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/SataTool.py +7 -5
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/Tunables.py +5 -5
- dwipe-3.0.2/dwipe/VerifyTask.py +194 -0
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/WipeJob.py +92 -686
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/WriteTask.py +21 -26
- dwipe-3.0.2/dwipe/device_io.py +105 -0
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/main.py +4 -0
- dwipe-3.0.2/dwipe/markers.py +155 -0
- dwipe-3.0.2/dwipe/verification.py +283 -0
- dwipe-3.0.2/images/dwipe-2026-02-05.07-58.gif +0 -0
- {dwipe-3.0.1 → dwipe-3.0.2}/pyproject.toml +21 -2
- dwipe-3.0.2/tests/conftest.py +50 -0
- dwipe-3.0.2/tests/device_double.py +135 -0
- dwipe-3.0.2/tests/test_cli_options.py +25 -0
- dwipe-3.0.2/tests/test_device_change_monitor.py +87 -0
- dwipe-3.0.2/tests/test_device_io.py +165 -0
- dwipe-3.0.2/tests/test_device_worker.py +228 -0
- dwipe-3.0.2/tests/test_deviceinfo_logic.py +314 -0
- dwipe-3.0.2/tests/test_diskwipe_terminal.py +41 -0
- dwipe-3.0.2/tests/test_drive_prechecker.py +157 -0
- dwipe-3.0.2/tests/test_exception_observability.py +92 -0
- dwipe-3.0.2/tests/test_firmware_wipe_task.py +126 -0
- dwipe-3.0.2/tests/test_nvme_tool.py +182 -0
- dwipe-3.0.2/tests/test_persistent_state.py +203 -0
- dwipe-3.0.2/tests/test_safe_write.py +90 -0
- dwipe-3.0.2/tests/test_satatool_parse.py +118 -0
- dwipe-3.0.2/tests/test_structured_logger.py +167 -0
- dwipe-3.0.2/tests/test_utils.py +172 -0
- dwipe-3.0.2/tests/test_verification.py +97 -0
- dwipe-3.0.2/tests/test_verify_analysis.py +80 -0
- dwipe-3.0.2/tests/test_wipejob_logic.py +208 -0
- dwipe-3.0.2/tests/test_wipejob_resume.py +214 -0
- dwipe-3.0.1/dwipe/VerifyTask.py +0 -412
- {dwipe-3.0.1 → dwipe-3.0.2}/LICENSE +0 -0
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/DeviceChangeMonitor.py +0 -0
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/DrivePreChecker.py +0 -0
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/Prereqs.py +0 -0
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/StructuredLogger.py +0 -0
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/Utils.py +0 -0
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/WipeTask.py +0 -0
- {dwipe-3.0.1 → dwipe-3.0.2}/dwipe/__init__.py +0 -0
- {dwipe-3.0.1 → dwipe-3.0.2}/images/dwipe-2025-12-31-09-37.gif +0 -0
- {dwipe-3.0.1 → dwipe-3.0.2}/linter +0 -0
- {dwipe-3.0.1 → dwipe-3.0.2}/resources/dwipe-help-screen.png +0 -0
- {dwipe-3.0.1 → dwipe-3.0.2}/resources/dwipe-main-screen.png +0 -0
- {dwipe-3.0.1 → dwipe-3.0.2}/runner +0 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
name: tests
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
test:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
strategy:
|
|
11
|
+
fail-fast: false
|
|
12
|
+
matrix:
|
|
13
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
|
|
17
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
18
|
+
uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: ${{ matrix.python-version }}
|
|
21
|
+
|
|
22
|
+
- name: Install package with test extras
|
|
23
|
+
run: |
|
|
24
|
+
python -m pip install --upgrade pip
|
|
25
|
+
pip install -e '.[test]'
|
|
26
|
+
|
|
27
|
+
- name: Run unit tests with coverage
|
|
28
|
+
run: pytest --cov=dwipe --cov-report=term-missing --cov-fail-under=40
|
|
29
|
+
|
|
30
|
+
lint:
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
|
|
35
|
+
- name: Set up Python
|
|
36
|
+
uses: actions/setup-python@v5
|
|
37
|
+
with:
|
|
38
|
+
python-version: "3.13"
|
|
39
|
+
|
|
40
|
+
- name: Install pylint
|
|
41
|
+
run: python -m pip install --upgrade pip pylint
|
|
42
|
+
|
|
43
|
+
- name: Static checks (undefined names / bad calls)
|
|
44
|
+
# Full advisory lint is available locally via ./linter. This job blocks
|
|
45
|
+
# only on the error classes that indicate real bugs (undefined name,
|
|
46
|
+
# bad call signature) and currently passes at 10.00/10.
|
|
47
|
+
run: pylint --disable=all --enable=E0601,E0602,E0603,E1120,E1121 dwipe
|
|
@@ -1,7 +1,26 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: dwipe
|
|
3
|
+
Version: 3.0.2
|
|
4
|
+
Summary: A tool to wipe disks and partitions for Linux
|
|
5
|
+
Keywords: disk,partition,wipe,clean,scrub
|
|
6
|
+
Author-email: Joe Defen <joedef@google.com>
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: console-window == 1.4.2
|
|
14
|
+
Requires-Dist: pytest >= 7.0 ; extra == "test"
|
|
15
|
+
Requires-Dist: pytest-cov >= 4.0 ; extra == "test"
|
|
16
|
+
Project-URL: Bug Tracker, https://github.com/joedefen/dwipe/issues
|
|
17
|
+
Project-URL: Homepage, https://github.com/joedefen/dwipe
|
|
18
|
+
Provides-Extra: test
|
|
19
|
+
|
|
1
20
|
# dwipe
|
|
2
21
|
`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.
|
|
3
22
|
|
|
4
|
-

|
|
5
24
|
### Quick Comparison
|
|
6
25
|
|
|
7
26
|
| Feature | dwipe | nwipe | shred | dd |
|
|
@@ -15,7 +34,7 @@
|
|
|
15
34
|
| Device/partition blocking | ✓ | ✗ | ✗ | ✗ |
|
|
16
35
|
| Resume interrupted software wipes | ✓ | ✗ | ✗ | ✗ |
|
|
17
36
|
| Wipe operation logging | ✓ | ✗ | ✗ | ✗ |
|
|
18
|
-
| Mount
|
|
37
|
+
| Mount & LUKS/LVM/RAID/swap detection | ✓ | ✓ | ✗ | ✗ |
|
|
19
38
|
| Fast Statistical sampling verification | ✓ | ✗ | ✗ | ✗ |
|
|
20
39
|
| Certified destruction (DoD/Gutmann standards, full verification, certificates) | ✗ | ✓ | ✗ | ✗ |
|
|
21
40
|
|
|
@@ -30,7 +49,7 @@
|
|
|
30
49
|
* **Multiple simultaneous wipes** - Start wipes on multiple devices at once with individual progress tracking and completion states
|
|
31
50
|
* **Persistent state tracking** - Wipe status survives reboots; partially wiped and completed states persist on the device (can resume wipes across power cycles)
|
|
32
51
|
* **Smart device identification** - Uses UUID/PARTUUID/serial numbers for stable device tracking across reconnections
|
|
33
|
-
* **Safety protections** - Prevents wiping mounted devices, detects overlapping wipes, supports device blocking, inline confirmation prompts
|
|
52
|
+
* **Safety protections** - Prevents wiping mounted or in-use devices (including LUKS/LVM/RAID containers and swap), detects overlapping wipes, supports device blocking, inline confirmation prompts
|
|
34
53
|
* **Intelligent verification with statistical sampling** - Fast verification that covers entire disk even at low percentages (1% verification samples from all 100 disk sections)
|
|
35
54
|
* **Direct I/O to disk** - Fast and efficient wiping that avoids page cache pollution with automatic slowdown/stall detection and graceful abort
|
|
36
55
|
* **Multiple color themes** - Terminal color themes designed for readability with context-sensitive visual feedback (mounted devices dimmed, active wipes bright)
|
|
@@ -75,7 +94,7 @@
|
|
|
75
94
|
`dwipe` provides comprehensive disk wiping capabilities with safety features:
|
|
76
95
|
|
|
77
96
|
* **Smart device display** - Shows disks and partitions with labels, sizes, types, and vendor/model information to help identify devices correctly
|
|
78
|
-
* **Safety protections** - Prevents wiping mounted devices, detects overlapping wipes, supports manual disk blocking
|
|
97
|
+
* **Safety protections** - Prevents wiping mounted or in-use devices (including LUKS/LVM/RAID containers and swap), detects overlapping wipes, supports manual disk blocking
|
|
79
98
|
* **Hot-swap detection** - Updates the device list when storage changes; newly added devices are marked with **^** to make them easy to spot
|
|
80
99
|
* **Multiple simultaneous wipes** - Start wipes on multiple devices at once, with individual progress tracking and completion states
|
|
81
100
|
* **Flexible wipe modes** - Choose between Rand, Zero, Rand+V (with auto-verify), or Zero+V (with auto-verify)
|
|
@@ -125,6 +144,10 @@ Simply run `dwipe` from the command line without arguments: `dwipe`
|
|
|
125
144
|
- `--firmware-wipes` or `-F` - Enable firmware wipes
|
|
126
145
|
- Enables hardware-based secure erase operations (NVMe Sanitize/Format, SATA ATA Security Erase)
|
|
127
146
|
- Without this flag, only software wipes (Zero/Rand) are available
|
|
147
|
+
- `--auto-block-containers {on,off}` - Auto-block encrypted/stacked containers on first sight (default: on)
|
|
148
|
+
- Blocks devices whose filesystem is a container (LUKS, LVM, RAID member, swap) the first time they are seen, so wiping requires an explicit unblock (**b**)
|
|
149
|
+
- The block is remembered per device; once you unblock, it stays unblocked
|
|
150
|
+
- Set to `off` when re-provisioning many encrypted disks to avoid unblocking each one
|
|
128
151
|
- `--dump-lsblk` - Dump parsed device information and exit (for debugging)
|
|
129
152
|
- `--help` - Show help message with all available options
|
|
130
153
|
|
|
@@ -132,7 +155,7 @@ Simply run `dwipe` from the command line without arguments: `dwipe`
|
|
|
132
155
|
|
|
133
156
|
`dwipe` uses color coding to provide instant visual feedback about device and operation status:
|
|
134
157
|
|
|
135
|
-
- **Dimmed (gray)** - Mounted or blocked devices (cannot be wiped)
|
|
158
|
+
- **Dimmed (gray)** - Mounted, in-use (LUKS/LVM/RAID/swap), or blocked devices (cannot be wiped)
|
|
136
159
|
- **Default (white)** - Ready to wipe, idle state, or previously wiped (before this session)
|
|
137
160
|
- **Bright cyan/blue + bold** - Active wipe or verification in progress (0-100% write, v0-v100% verify)
|
|
138
161
|
- **Bold yellow** - Stopped or partially completed wipe
|
|
@@ -174,15 +197,41 @@ The **STATE** column shows the current status of each device:
|
|
|
174
197
|
|-------|---------|
|
|
175
198
|
| **-** | Device is ready for wiping |
|
|
176
199
|
| **^** | Device is ready for wiping AND was added after `dwipe` started (hot-swapped) |
|
|
177
|
-
| **Mnt** | Partition is mounted or
|
|
200
|
+
| **Mnt** | Partition/disk is mounted, or in use via a swap/LUKS/LVM/RAID mapping - cannot be wiped |
|
|
178
201
|
| **N%** | Wipe is in progress (shows percentage complete, 0-100%) |
|
|
179
202
|
| **vN%** | Verification is in progress (shows percentage complete, v0-v100%) |
|
|
180
203
|
| **STOP** | Wipe or verification is being stopped |
|
|
181
204
|
| **s** | Wipe was stopped - device is partially wiped (can restart or verify) |
|
|
182
205
|
| **W** | Wipe was completed successfully (can wipe again or verify) |
|
|
183
|
-
| **Blk** |
|
|
206
|
+
| **Blk** | Device/disk is blocked (with **b**, or auto-blocked when it is a LUKS/LVM/RAID/swap container) - cannot be wiped |
|
|
184
207
|
| **Unbl** | Disk was just unblocked (transitory state) |
|
|
185
208
|
|
|
209
|
+
### Mounts / Status Column
|
|
210
|
+
|
|
211
|
+
The **MOUNTS/STATUS** column shows how a device is currently in use:
|
|
212
|
+
|
|
213
|
+
| Display | Meaning |
|
|
214
|
+
|---------|---------|
|
|
215
|
+
| **/**, **/data** | A real mount point (mounted directly, or through a LUKS/LVM/RAID mapping) |
|
|
216
|
+
| **\busy** | In use but not mounted — e.g. an *open* LUKS container, an active LVM volume group, a running RAID array, or active swap |
|
|
217
|
+
|
|
218
|
+
Both are treated as mounted: the device is dimmed and **w** (wipe) is not offered. `\busy` uses a backslash (not `/`) to make clear it is not a real mount point, yet it is treated like one for safety. To wipe such a device, first release the layer holding it (see [Wipe issues](#wipe-issues)).
|
|
219
|
+
|
|
220
|
+
> **Note:** A device held by a lower layer (dm-crypt/LUKS, LVM, md/RAID, swap) is protected even when it is not itself mounted. Wiping the backing device while a mapping is active would corrupt the container, so `dwipe` treats it as mounted. Mount points reached through a mapping are attributed back to the underlying device, so a LUKS partition shows the mount of its unlocked mapping.
|
|
221
|
+
|
|
222
|
+
### Auto-Blocking Containers
|
|
223
|
+
|
|
224
|
+
`dwipe` auto-blocks devices that hold encrypted or stacked containers **the first time they are seen**, whether or not they are currently in use. This guards data that is otherwise easy to wipe by accident, since a *closed* container shows almost no hint beyond its type:
|
|
225
|
+
|
|
226
|
+
- `crypto_LUKS` (dm-crypt/LUKS)
|
|
227
|
+
- `LVM2_member` (LVM physical volume)
|
|
228
|
+
- `linux_raid_member` / `isw_raid_member` / `ddf_raid_member` (RAID members)
|
|
229
|
+
- `swap`
|
|
230
|
+
|
|
231
|
+
Such a device shows state **Blk** with an `auto-blocked` status, and its whole disk is protected too (inherited block). To wipe it, press **b** to unblock — the unblock is remembered, so it is not re-blocked on later scans.
|
|
232
|
+
|
|
233
|
+
Auto-blocking is on by default. Disable it for bulk re-provisioning with `--auto-block-containers off` (saved as a preference).
|
|
234
|
+
|
|
186
235
|
### Available Actions
|
|
187
236
|
|
|
188
237
|
The top line shows available actions. Some are context-sensitive (only available for certain devices):
|
|
@@ -448,9 +497,14 @@ Press **ESC** from the main screen to clear the filter and return to showing all
|
|
|
448
497
|
- **Colors don't work** - Ensure your terminal supports colors (most modern terminals do)
|
|
449
498
|
|
|
450
499
|
### Wipe issues
|
|
451
|
-
- **Can't wipe a device** - Check the STATE
|
|
500
|
+
- **Can't wipe a device** - Check the STATE and MOUNTS/STATUS columns:
|
|
452
501
|
- **Mnt** - Unmount the partition first: `sudo umount /dev/sdXN`
|
|
453
|
-
- **
|
|
502
|
+
- **`\busy`** status, or **Mnt** caused by a lower layer - release the mapping/volume first, then rescan (**r**):
|
|
503
|
+
- LUKS: unmount, then `sudo cryptsetup close <name>`
|
|
504
|
+
- LVM: `sudo vgchange -an <vg>` (deactivate the volume group)
|
|
505
|
+
- RAID: `sudo mdadm --stop /dev/mdX`
|
|
506
|
+
- Swap: `sudo swapoff /dev/...`
|
|
507
|
+
- **Blk** - Press **b** to unblock (containers such as LUKS/LVM/RAID/swap are auto-blocked until you do)
|
|
454
508
|
- **Busy** - Another partition on the disk is being wiped
|
|
455
509
|
- **Wipe is very slow** - Normal for large drives; check write rate to verify progress
|
|
456
510
|
- **Wipe seems stuck** - Most likely due to bad disks; Direct I/O makes progress almost constant on good disks.
|
|
@@ -546,4 +600,4 @@ Issues and pull requests welcome at [github.com/joedefen/dwipe](https://github.c
|
|
|
546
600
|
|
|
547
601
|
## License
|
|
548
602
|
|
|
549
|
-
MIT License - see [LICENSE](LICENSE) file for details.
|
|
603
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
@@ -1,23 +1,7 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: dwipe
|
|
3
|
-
Version: 3.0.1
|
|
4
|
-
Summary: A tool to wipe disks and partitions for Linux
|
|
5
|
-
Keywords: disk,partition,wipe,clean,scrub
|
|
6
|
-
Author-email: Joe Defen <joedef@google.com>
|
|
7
|
-
Requires-Python: >=3.10
|
|
8
|
-
Description-Content-Type: text/markdown
|
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
-
Classifier: Operating System :: POSIX :: Linux
|
|
12
|
-
License-File: LICENSE
|
|
13
|
-
Requires-Dist: console-window == 1.4.2
|
|
14
|
-
Project-URL: Bug Tracker, https://github.com/joedefen/dwipe/issues
|
|
15
|
-
Project-URL: Homepage, https://github.com/joedefen/dwipe
|
|
16
|
-
|
|
17
1
|
# dwipe
|
|
18
2
|
`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.
|
|
19
3
|
|
|
20
|
-

|
|
21
5
|
### Quick Comparison
|
|
22
6
|
|
|
23
7
|
| Feature | dwipe | nwipe | shred | dd |
|
|
@@ -31,7 +15,7 @@ Project-URL: Homepage, https://github.com/joedefen/dwipe
|
|
|
31
15
|
| Device/partition blocking | ✓ | ✗ | ✗ | ✗ |
|
|
32
16
|
| Resume interrupted software wipes | ✓ | ✗ | ✗ | ✗ |
|
|
33
17
|
| Wipe operation logging | ✓ | ✗ | ✗ | ✗ |
|
|
34
|
-
| Mount
|
|
18
|
+
| Mount & LUKS/LVM/RAID/swap detection | ✓ | ✓ | ✗ | ✗ |
|
|
35
19
|
| Fast Statistical sampling verification | ✓ | ✗ | ✗ | ✗ |
|
|
36
20
|
| Certified destruction (DoD/Gutmann standards, full verification, certificates) | ✗ | ✓ | ✗ | ✗ |
|
|
37
21
|
|
|
@@ -46,7 +30,7 @@ Project-URL: Homepage, https://github.com/joedefen/dwipe
|
|
|
46
30
|
* **Multiple simultaneous wipes** - Start wipes on multiple devices at once with individual progress tracking and completion states
|
|
47
31
|
* **Persistent state tracking** - Wipe status survives reboots; partially wiped and completed states persist on the device (can resume wipes across power cycles)
|
|
48
32
|
* **Smart device identification** - Uses UUID/PARTUUID/serial numbers for stable device tracking across reconnections
|
|
49
|
-
* **Safety protections** - Prevents wiping mounted devices, detects overlapping wipes, supports device blocking, inline confirmation prompts
|
|
33
|
+
* **Safety protections** - Prevents wiping mounted or in-use devices (including LUKS/LVM/RAID containers and swap), detects overlapping wipes, supports device blocking, inline confirmation prompts
|
|
50
34
|
* **Intelligent verification with statistical sampling** - Fast verification that covers entire disk even at low percentages (1% verification samples from all 100 disk sections)
|
|
51
35
|
* **Direct I/O to disk** - Fast and efficient wiping that avoids page cache pollution with automatic slowdown/stall detection and graceful abort
|
|
52
36
|
* **Multiple color themes** - Terminal color themes designed for readability with context-sensitive visual feedback (mounted devices dimmed, active wipes bright)
|
|
@@ -91,7 +75,7 @@ Project-URL: Homepage, https://github.com/joedefen/dwipe
|
|
|
91
75
|
`dwipe` provides comprehensive disk wiping capabilities with safety features:
|
|
92
76
|
|
|
93
77
|
* **Smart device display** - Shows disks and partitions with labels, sizes, types, and vendor/model information to help identify devices correctly
|
|
94
|
-
* **Safety protections** - Prevents wiping mounted devices, detects overlapping wipes, supports manual disk blocking
|
|
78
|
+
* **Safety protections** - Prevents wiping mounted or in-use devices (including LUKS/LVM/RAID containers and swap), detects overlapping wipes, supports manual disk blocking
|
|
95
79
|
* **Hot-swap detection** - Updates the device list when storage changes; newly added devices are marked with **^** to make them easy to spot
|
|
96
80
|
* **Multiple simultaneous wipes** - Start wipes on multiple devices at once, with individual progress tracking and completion states
|
|
97
81
|
* **Flexible wipe modes** - Choose between Rand, Zero, Rand+V (with auto-verify), or Zero+V (with auto-verify)
|
|
@@ -141,6 +125,10 @@ Simply run `dwipe` from the command line without arguments: `dwipe`
|
|
|
141
125
|
- `--firmware-wipes` or `-F` - Enable firmware wipes
|
|
142
126
|
- Enables hardware-based secure erase operations (NVMe Sanitize/Format, SATA ATA Security Erase)
|
|
143
127
|
- Without this flag, only software wipes (Zero/Rand) are available
|
|
128
|
+
- `--auto-block-containers {on,off}` - Auto-block encrypted/stacked containers on first sight (default: on)
|
|
129
|
+
- Blocks devices whose filesystem is a container (LUKS, LVM, RAID member, swap) the first time they are seen, so wiping requires an explicit unblock (**b**)
|
|
130
|
+
- The block is remembered per device; once you unblock, it stays unblocked
|
|
131
|
+
- Set to `off` when re-provisioning many encrypted disks to avoid unblocking each one
|
|
144
132
|
- `--dump-lsblk` - Dump parsed device information and exit (for debugging)
|
|
145
133
|
- `--help` - Show help message with all available options
|
|
146
134
|
|
|
@@ -148,7 +136,7 @@ Simply run `dwipe` from the command line without arguments: `dwipe`
|
|
|
148
136
|
|
|
149
137
|
`dwipe` uses color coding to provide instant visual feedback about device and operation status:
|
|
150
138
|
|
|
151
|
-
- **Dimmed (gray)** - Mounted or blocked devices (cannot be wiped)
|
|
139
|
+
- **Dimmed (gray)** - Mounted, in-use (LUKS/LVM/RAID/swap), or blocked devices (cannot be wiped)
|
|
152
140
|
- **Default (white)** - Ready to wipe, idle state, or previously wiped (before this session)
|
|
153
141
|
- **Bright cyan/blue + bold** - Active wipe or verification in progress (0-100% write, v0-v100% verify)
|
|
154
142
|
- **Bold yellow** - Stopped or partially completed wipe
|
|
@@ -190,15 +178,41 @@ The **STATE** column shows the current status of each device:
|
|
|
190
178
|
|-------|---------|
|
|
191
179
|
| **-** | Device is ready for wiping |
|
|
192
180
|
| **^** | Device is ready for wiping AND was added after `dwipe` started (hot-swapped) |
|
|
193
|
-
| **Mnt** | Partition is mounted or
|
|
181
|
+
| **Mnt** | Partition/disk is mounted, or in use via a swap/LUKS/LVM/RAID mapping - cannot be wiped |
|
|
194
182
|
| **N%** | Wipe is in progress (shows percentage complete, 0-100%) |
|
|
195
183
|
| **vN%** | Verification is in progress (shows percentage complete, v0-v100%) |
|
|
196
184
|
| **STOP** | Wipe or verification is being stopped |
|
|
197
185
|
| **s** | Wipe was stopped - device is partially wiped (can restart or verify) |
|
|
198
186
|
| **W** | Wipe was completed successfully (can wipe again or verify) |
|
|
199
|
-
| **Blk** |
|
|
187
|
+
| **Blk** | Device/disk is blocked (with **b**, or auto-blocked when it is a LUKS/LVM/RAID/swap container) - cannot be wiped |
|
|
200
188
|
| **Unbl** | Disk was just unblocked (transitory state) |
|
|
201
189
|
|
|
190
|
+
### Mounts / Status Column
|
|
191
|
+
|
|
192
|
+
The **MOUNTS/STATUS** column shows how a device is currently in use:
|
|
193
|
+
|
|
194
|
+
| Display | Meaning |
|
|
195
|
+
|---------|---------|
|
|
196
|
+
| **/**, **/data** | A real mount point (mounted directly, or through a LUKS/LVM/RAID mapping) |
|
|
197
|
+
| **\busy** | In use but not mounted — e.g. an *open* LUKS container, an active LVM volume group, a running RAID array, or active swap |
|
|
198
|
+
|
|
199
|
+
Both are treated as mounted: the device is dimmed and **w** (wipe) is not offered. `\busy` uses a backslash (not `/`) to make clear it is not a real mount point, yet it is treated like one for safety. To wipe such a device, first release the layer holding it (see [Wipe issues](#wipe-issues)).
|
|
200
|
+
|
|
201
|
+
> **Note:** A device held by a lower layer (dm-crypt/LUKS, LVM, md/RAID, swap) is protected even when it is not itself mounted. Wiping the backing device while a mapping is active would corrupt the container, so `dwipe` treats it as mounted. Mount points reached through a mapping are attributed back to the underlying device, so a LUKS partition shows the mount of its unlocked mapping.
|
|
202
|
+
|
|
203
|
+
### Auto-Blocking Containers
|
|
204
|
+
|
|
205
|
+
`dwipe` auto-blocks devices that hold encrypted or stacked containers **the first time they are seen**, whether or not they are currently in use. This guards data that is otherwise easy to wipe by accident, since a *closed* container shows almost no hint beyond its type:
|
|
206
|
+
|
|
207
|
+
- `crypto_LUKS` (dm-crypt/LUKS)
|
|
208
|
+
- `LVM2_member` (LVM physical volume)
|
|
209
|
+
- `linux_raid_member` / `isw_raid_member` / `ddf_raid_member` (RAID members)
|
|
210
|
+
- `swap`
|
|
211
|
+
|
|
212
|
+
Such a device shows state **Blk** with an `auto-blocked` status, and its whole disk is protected too (inherited block). To wipe it, press **b** to unblock — the unblock is remembered, so it is not re-blocked on later scans.
|
|
213
|
+
|
|
214
|
+
Auto-blocking is on by default. Disable it for bulk re-provisioning with `--auto-block-containers off` (saved as a preference).
|
|
215
|
+
|
|
202
216
|
### Available Actions
|
|
203
217
|
|
|
204
218
|
The top line shows available actions. Some are context-sensitive (only available for certain devices):
|
|
@@ -464,9 +478,14 @@ Press **ESC** from the main screen to clear the filter and return to showing all
|
|
|
464
478
|
- **Colors don't work** - Ensure your terminal supports colors (most modern terminals do)
|
|
465
479
|
|
|
466
480
|
### Wipe issues
|
|
467
|
-
- **Can't wipe a device** - Check the STATE
|
|
481
|
+
- **Can't wipe a device** - Check the STATE and MOUNTS/STATUS columns:
|
|
468
482
|
- **Mnt** - Unmount the partition first: `sudo umount /dev/sdXN`
|
|
469
|
-
- **
|
|
483
|
+
- **`\busy`** status, or **Mnt** caused by a lower layer - release the mapping/volume first, then rescan (**r**):
|
|
484
|
+
- LUKS: unmount, then `sudo cryptsetup close <name>`
|
|
485
|
+
- LVM: `sudo vgchange -an <vg>` (deactivate the volume group)
|
|
486
|
+
- RAID: `sudo mdadm --stop /dev/mdX`
|
|
487
|
+
- Swap: `sudo swapoff /dev/...`
|
|
488
|
+
- **Blk** - Press **b** to unblock (containers such as LUKS/LVM/RAID/swap are auto-blocked until you do)
|
|
470
489
|
- **Busy** - Another partition on the disk is being wiped
|
|
471
490
|
- **Wipe is very slow** - Normal for large drives; check write rate to verify progress
|
|
472
491
|
- **Wipe seems stuck** - Most likely due to bad disks; Direct I/O makes progress almost constant on good disks.
|
|
@@ -562,4 +581,4 @@ Issues and pull requests welcome at [github.com/joedefen/dwipe](https://github.c
|
|
|
562
581
|
|
|
563
582
|
## License
|
|
564
583
|
|
|
565
|
-
MIT License - see [LICENSE](LICENSE) file for details.
|
|
584
|
+
MIT License - see [LICENSE](LICENSE) file for details.
|
|
@@ -27,6 +27,23 @@ class DeviceInfo:
|
|
|
27
27
|
disk_majors = set() # major devices that are disks
|
|
28
28
|
_discovery_cycle = 0 # Counter to prove discovery is running
|
|
29
29
|
|
|
30
|
+
# Filesystem types marking an encrypted/stacked container whose contents
|
|
31
|
+
# are hidden behind another layer. These are auto-blocked on first sight.
|
|
32
|
+
CONTAINER_FSTYPES = (
|
|
33
|
+
'crypto_LUKS', 'LVM2_member', 'linux_raid_member',
|
|
34
|
+
'isw_raid_member', 'ddf_raid_member', 'swap',
|
|
35
|
+
)
|
|
36
|
+
CONTAINER_FSTYPE_PREFIXES = ('crypto_', 'DM_')
|
|
37
|
+
|
|
38
|
+
@staticmethod
|
|
39
|
+
def is_container_fstype(fstype):
|
|
40
|
+
"""True if fstype marks a container (LUKS/LVM/RAID/swap)."""
|
|
41
|
+
if not fstype:
|
|
42
|
+
return False
|
|
43
|
+
if fstype in DeviceInfo.CONTAINER_FSTYPES:
|
|
44
|
+
return True
|
|
45
|
+
return any(fstype.startswith(p) for p in DeviceInfo.CONTAINER_FSTYPE_PREFIXES)
|
|
46
|
+
|
|
30
47
|
@staticmethod
|
|
31
48
|
def clean_partition_label(label):
|
|
32
49
|
"""Clean up partition labels by decoding escape sequences and simplifying common labels.
|
|
@@ -99,6 +116,9 @@ class DeviceInfo:
|
|
|
99
116
|
hw_caps_state=ProbeState.PENDING, # probe state for hw_caps
|
|
100
117
|
is_usb=False, # True if device is on USB bus
|
|
101
118
|
is_rotational=False, # True if HDD (spinning disk)
|
|
119
|
+
in_use=False, # mounted/swap or backing an active holder
|
|
120
|
+
is_container=False, # LUKS/LVM/RAID/swap container
|
|
121
|
+
auto_blocked=False, # blocked automatically (container)
|
|
102
122
|
)
|
|
103
123
|
|
|
104
124
|
def get_hw_capabilities(self, ns):
|
|
@@ -112,11 +132,7 @@ class DeviceInfo:
|
|
|
112
132
|
if ns.hw_caps_state == ProbeState.READY:
|
|
113
133
|
return ns.hw_caps, ns.hw_nopes
|
|
114
134
|
|
|
115
|
-
# 2. Skip probing
|
|
116
|
-
if ns.state in ('Mnt', 'iMnt', 'Blk', 'iBlk', 'Busy'):
|
|
117
|
-
return ns.hw_caps, ns.hw_nopes
|
|
118
|
-
|
|
119
|
-
# 3. Skip probing if device has active job (would block on SATA wipe)
|
|
135
|
+
# 2. Skip probing if device has active job (would block on SATA wipe)
|
|
120
136
|
if ns.job:
|
|
121
137
|
return ns.hw_caps, ns.hw_nopes
|
|
122
138
|
|
|
@@ -260,12 +276,40 @@ class DeviceInfo:
|
|
|
260
276
|
pass
|
|
261
277
|
return devices
|
|
262
278
|
|
|
279
|
+
@staticmethod
|
|
280
|
+
def _block_name_from_devpath(devpath):
|
|
281
|
+
"""Resolve a /dev path to its canonical kernel block name.
|
|
282
|
+
|
|
283
|
+
Handles /dev/sda1, /dev/md0, /dev/dm-0 and indirection through
|
|
284
|
+
/dev/mapper/<name> or /dev/disk/by-* (resolved via realpath).
|
|
285
|
+
Returns '' for anything that is not a block device path.
|
|
286
|
+
"""
|
|
287
|
+
if not devpath.startswith('/dev/'):
|
|
288
|
+
return ''
|
|
289
|
+
try:
|
|
290
|
+
real = os.path.realpath(devpath)
|
|
291
|
+
except OSError:
|
|
292
|
+
real = devpath
|
|
293
|
+
return os.path.basename(real)
|
|
294
|
+
|
|
295
|
+
@staticmethod
|
|
296
|
+
def _read_sysfs_dir(path):
|
|
297
|
+
"""List a sysfs directory, returning [] if it is missing or unreadable."""
|
|
298
|
+
try:
|
|
299
|
+
return os.listdir(path)
|
|
300
|
+
except OSError:
|
|
301
|
+
return []
|
|
302
|
+
|
|
263
303
|
def _parse_proc_mounts(self):
|
|
264
304
|
"""Parse /proc/mounts for mount points (non-blocking).
|
|
265
305
|
|
|
266
306
|
Format: device mountpoint fstype options dump pass
|
|
267
307
|
/dev/sda1 /boot ext4 rw,relatime 0 0
|
|
268
308
|
|
|
309
|
+
Device paths are resolved to canonical block names so a mount on a
|
|
310
|
+
dm-crypt/LVM/RAID mapping (e.g. /dev/mapper/crypt -> dm-0) is
|
|
311
|
+
attributed to the underlying block device.
|
|
312
|
+
|
|
269
313
|
Returns:
|
|
270
314
|
dict: {device_name: {'mounts': [mountpoint1, ...], 'fstype': str}}
|
|
271
315
|
"""
|
|
@@ -275,7 +319,9 @@ class DeviceInfo:
|
|
|
275
319
|
for line in f:
|
|
276
320
|
parts = line.split()
|
|
277
321
|
if len(parts) >= 3 and parts[0].startswith('/dev/'):
|
|
278
|
-
device = parts[0]
|
|
322
|
+
device = self._block_name_from_devpath(parts[0])
|
|
323
|
+
if not device:
|
|
324
|
+
continue
|
|
279
325
|
mountpoint = parts[1]
|
|
280
326
|
fstype = parts[2]
|
|
281
327
|
if device not in mounts:
|
|
@@ -285,6 +331,94 @@ class DeviceInfo:
|
|
|
285
331
|
pass
|
|
286
332
|
return mounts
|
|
287
333
|
|
|
334
|
+
@staticmethod
|
|
335
|
+
def _parse_proc_swaps():
|
|
336
|
+
"""Return canonical block names currently used as swap (non-blocking)."""
|
|
337
|
+
names = set()
|
|
338
|
+
try:
|
|
339
|
+
with open('/proc/swaps', 'r', encoding='utf-8') as f:
|
|
340
|
+
next(f, None) # skip header line
|
|
341
|
+
for line in f:
|
|
342
|
+
parts = line.split()
|
|
343
|
+
if parts:
|
|
344
|
+
name = DeviceInfo._block_name_from_devpath(parts[0])
|
|
345
|
+
if name:
|
|
346
|
+
names.add(name)
|
|
347
|
+
except (FileNotFoundError, PermissionError, Exception):
|
|
348
|
+
pass
|
|
349
|
+
return names
|
|
350
|
+
|
|
351
|
+
def _mark_block_usage(self, entries, swap_names):
|
|
352
|
+
"""Flag devices consumed by a holder (LUKS, LVM, RAID, ...).
|
|
353
|
+
|
|
354
|
+
A device is *in use* when it is mounted, used as swap, or backs any
|
|
355
|
+
holder (an active dm/md mapping). Mountpoints of a mounted holder are
|
|
356
|
+
propagated down its slave chain so a LUKS container shows the mount of
|
|
357
|
+
its unlocked mapping; a holder that is open but unmounted marks the
|
|
358
|
+
backing device in-use (rendered as '\\busy'). All reads are sysfs-only
|
|
359
|
+
and non-blocking.
|
|
360
|
+
"""
|
|
361
|
+
holders, slaves = {}, {}
|
|
362
|
+
for name in entries:
|
|
363
|
+
holders[name] = self._read_sysfs_dir(f'/sys/class/block/{name}/holders')
|
|
364
|
+
slaves[name] = self._read_sysfs_dir(f'/sys/class/block/{name}/slaves')
|
|
365
|
+
|
|
366
|
+
# Seed the walk with devices that are mounted or used as swap
|
|
367
|
+
queue = [n for n, ns in entries.items() if ns.mounts]
|
|
368
|
+
for name in swap_names:
|
|
369
|
+
if name in entries:
|
|
370
|
+
entries[name].in_use = True
|
|
371
|
+
if name not in queue:
|
|
372
|
+
queue.append(name)
|
|
373
|
+
visited = set(queue)
|
|
374
|
+
|
|
375
|
+
# Propagate mount visibility down to backing devices via slaves
|
|
376
|
+
while queue:
|
|
377
|
+
cur = queue.pop()
|
|
378
|
+
cur_mounts = entries[cur].mounts if cur in entries else []
|
|
379
|
+
for backing in slaves.get(cur, []):
|
|
380
|
+
if backing not in entries:
|
|
381
|
+
continue
|
|
382
|
+
bns = entries[backing]
|
|
383
|
+
for mount in cur_mounts:
|
|
384
|
+
if mount not in bns.mounts:
|
|
385
|
+
bns.mounts.append(mount)
|
|
386
|
+
bns.in_use = True
|
|
387
|
+
if backing not in visited:
|
|
388
|
+
visited.add(backing)
|
|
389
|
+
queue.append(backing)
|
|
390
|
+
|
|
391
|
+
# Any device with a holder is in use (mapping active) even if unmounted.
|
|
392
|
+
# Also classify containers (by fstype or by an existing holder).
|
|
393
|
+
for name, holder_list in holders.items():
|
|
394
|
+
if name not in entries:
|
|
395
|
+
continue
|
|
396
|
+
ns = entries[name]
|
|
397
|
+
if holder_list:
|
|
398
|
+
ns.in_use = True
|
|
399
|
+
if holder_list or self.is_container_fstype(ns.fstype):
|
|
400
|
+
ns.is_container = True
|
|
401
|
+
|
|
402
|
+
@staticmethod
|
|
403
|
+
def device_in_use(name):
|
|
404
|
+
"""Best-effort, non-blocking check that a device is in use right now.
|
|
405
|
+
|
|
406
|
+
Guards a wipe at the moment it starts, closing the window between a
|
|
407
|
+
discovery refresh and the actual write. True if the device has any
|
|
408
|
+
holder (active dm/md mapping) or is currently mounted.
|
|
409
|
+
"""
|
|
410
|
+
if DeviceInfo._read_sysfs_dir(f'/sys/class/block/{name}/holders'):
|
|
411
|
+
return True
|
|
412
|
+
try:
|
|
413
|
+
with open('/proc/mounts', 'r', encoding='utf-8') as f:
|
|
414
|
+
for line in f:
|
|
415
|
+
parts = line.split()
|
|
416
|
+
if parts and DeviceInfo._block_name_from_devpath(parts[0]) == name:
|
|
417
|
+
return True
|
|
418
|
+
except (FileNotFoundError, PermissionError, Exception):
|
|
419
|
+
pass
|
|
420
|
+
return False
|
|
421
|
+
|
|
288
422
|
def _is_partition(self, name):
|
|
289
423
|
"""Check if device is a partition (non-blocking).
|
|
290
424
|
|
|
@@ -508,6 +642,8 @@ class DeviceInfo:
|
|
|
508
642
|
entry.model = getattr(prev, 'model', '')
|
|
509
643
|
entry.port = getattr(prev, 'port', '')
|
|
510
644
|
entry.is_rotational = getattr(prev, 'is_rotational', False)
|
|
645
|
+
entry.in_use = getattr(prev, 'in_use', False)
|
|
646
|
+
entry.is_container = getattr(prev, 'is_container', False)
|
|
511
647
|
else:
|
|
512
648
|
# Non-dark device: probe for metadata
|
|
513
649
|
if entry.type == 'disk':
|
|
@@ -582,13 +718,17 @@ class DeviceInfo:
|
|
|
582
718
|
|
|
583
719
|
entries[name] = entry
|
|
584
720
|
|
|
721
|
+
# Phase 3c: Mark devices consumed by a holder (LUKS/LVM/RAID/swap)
|
|
722
|
+
swap_names = self._parse_proc_swaps()
|
|
723
|
+
self._mark_block_usage(entries, swap_names)
|
|
724
|
+
|
|
585
725
|
# Phase 4: Build parent-child relationships (minors lists)
|
|
586
726
|
for name, parent_name in parent_map.items():
|
|
587
727
|
if parent_name in entries:
|
|
588
728
|
entries[parent_name].minors.append(name)
|
|
589
729
|
self.disk_majors.add(entries[name].major)
|
|
590
|
-
# Propagate mount state to parent
|
|
591
|
-
if entries[name].mounts:
|
|
730
|
+
# Propagate mount/in-use state to parent (holder-backed too)
|
|
731
|
+
if entries[name].mounts or getattr(entries[name], 'in_use', False):
|
|
592
732
|
entries[parent_name].state = 'iMnt'
|
|
593
733
|
|
|
594
734
|
# Phase 4b: Clear marker display and state for disks with actual partitions
|
|
@@ -621,6 +761,8 @@ class DeviceInfo:
|
|
|
621
761
|
v_child.mounts = entry.mounts
|
|
622
762
|
v_child.parent = name
|
|
623
763
|
v_child.uuid = entry.uuid
|
|
764
|
+
v_child.in_use = getattr(entry, 'in_use', False)
|
|
765
|
+
v_child.is_container = getattr(entry, 'is_container', False)
|
|
624
766
|
|
|
625
767
|
final_entries[v_key] = v_child
|
|
626
768
|
entry.minors.append(v_key)
|
|
@@ -742,7 +884,7 @@ class DeviceInfo:
|
|
|
742
884
|
|
|
743
885
|
# Exclude common virtual and optical device prefixes as a safety net
|
|
744
886
|
# (most should already be filtered by ro check or missing sysfs)
|
|
745
|
-
excluded_prefixes = ('zram', 'loop', 'dm-', 'ram', 'sr', 'scd')
|
|
887
|
+
excluded_prefixes = ('zram', 'loop', 'dm-', 'md', 'ram', 'sr', 'scd')
|
|
746
888
|
if any(name.startswith(prefix) for prefix in excluded_prefixes):
|
|
747
889
|
continue
|
|
748
890
|
|
|
@@ -757,10 +899,8 @@ class DeviceInfo:
|
|
|
757
899
|
for ns in nss.values():
|
|
758
900
|
wids.state = max(wids.state, len(ns.state))
|
|
759
901
|
wids.name = max(wids.name, len(ns.name) + 2)
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
wids.label = max(wids.label, len(ns.label))
|
|
763
|
-
wids.fstype = max(wids.fstype, len(ns.fstype))
|
|
902
|
+
wids.label = max(wids.label, len(ns.label or ''))
|
|
903
|
+
wids.fstype = max(wids.fstype, len(ns.fstype or ''))
|
|
764
904
|
self.head_str = self.get_head_str()
|
|
765
905
|
|
|
766
906
|
def get_head_str(self):
|
|
@@ -829,15 +969,17 @@ class DeviceInfo:
|
|
|
829
969
|
hw_state = getattr(ns, 'hw_caps_state', ProbeState.PENDING)
|
|
830
970
|
is_usb = getattr(ns, 'is_usb', False)
|
|
831
971
|
|
|
972
|
+
# Non-wipeable devices always show '---' regardless of capabilities
|
|
832
973
|
fw_label = ''
|
|
833
|
-
if
|
|
974
|
+
if ns.state in ('Mnt', 'iMnt', 'Blk', 'iBlk', 'Busy'):
|
|
975
|
+
fw_label = '---'
|
|
976
|
+
elif hw_caps:
|
|
834
977
|
fw_label = hw_summary
|
|
835
978
|
elif hw_nopes and not is_usb:
|
|
836
979
|
first_issue = hw_nopes.split(',')[0].strip()
|
|
837
980
|
fw_label = f'✗{first_issue}'
|
|
838
|
-
elif hw_state in (ProbeState.PENDING, ProbeState.PROBING):
|
|
839
|
-
|
|
840
|
-
fw_label = '...'
|
|
981
|
+
elif hw_state in (ProbeState.PENDING, ProbeState.PROBING) and not is_usb:
|
|
982
|
+
fw_label = '...'
|
|
841
983
|
|
|
842
984
|
if not fw_label:
|
|
843
985
|
fw_label = '---'
|
|
@@ -878,6 +1020,12 @@ class DeviceInfo:
|
|
|
878
1020
|
elif ns.mounts:
|
|
879
1021
|
# Disk has mounts - show them
|
|
880
1022
|
emit += f'{sep}{",".join(ns.mounts)}'
|
|
1023
|
+
elif getattr(ns, 'in_use', False):
|
|
1024
|
+
# In use but not mounted (open LUKS/LVM/RAID mapping)
|
|
1025
|
+
emit += f'{sep}\\busy'
|
|
1026
|
+
elif getattr(ns, 'auto_blocked', False):
|
|
1027
|
+
# Auto-blocked container (LUKS/LVM/RAID/swap)
|
|
1028
|
+
emit += f'{sep}auto-blocked'
|
|
881
1029
|
elif ns.marker and ns.marker.strip() and not ns.minors:
|
|
882
1030
|
# Disk has wipe status - show it ONLY if no child partitions
|
|
883
1031
|
# (partitions take precedence over marker)
|
|
@@ -890,6 +1038,12 @@ class DeviceInfo:
|
|
|
890
1038
|
emit += sep + print_str_or_dash(ns.label, wids.label)
|
|
891
1039
|
if ns.mounts:
|
|
892
1040
|
emit += f'{sep}{",".join(ns.mounts)}'
|
|
1041
|
+
elif getattr(ns, 'in_use', False):
|
|
1042
|
+
# In use but not mounted (open LUKS/LVM/RAID mapping)
|
|
1043
|
+
emit += f'{sep}\\busy'
|
|
1044
|
+
elif getattr(ns, 'auto_blocked', False):
|
|
1045
|
+
# Auto-blocked container (LUKS/LVM/RAID/swap)
|
|
1046
|
+
emit += f'{sep}auto-blocked'
|
|
893
1047
|
else:
|
|
894
1048
|
emit += f'{sep}{ns.marker}'
|
|
895
1049
|
|
|
@@ -1045,19 +1199,30 @@ class DeviceInfo:
|
|
|
1045
1199
|
|
|
1046
1200
|
# Apply persistent blocked states
|
|
1047
1201
|
if self.persistent_state:
|
|
1202
|
+
auto_block = getattr(self.opts, 'auto_block_containers', True)
|
|
1048
1203
|
for ns in nss.values():
|
|
1049
1204
|
# Update last_seen timestamp
|
|
1050
1205
|
self.persistent_state.update_device_seen(ns)
|
|
1206
|
+
# Auto-block containers (LUKS/LVM/RAID/swap) the first time seen.
|
|
1207
|
+
# Once the user unblocks, a record exists so this won't re-block.
|
|
1208
|
+
if auto_block and getattr(ns, 'is_container', False) \
|
|
1209
|
+
and not self.persistent_state.has_device_record(ns):
|
|
1210
|
+
self.persistent_state.set_device_locked(ns, True, auto=True)
|
|
1051
1211
|
# Apply persistent block state
|
|
1052
1212
|
if self.persistent_state.get_device_locked(ns):
|
|
1053
1213
|
ns.state = 'Blk'
|
|
1214
|
+
if self.persistent_state.is_device_auto_blocked(ns):
|
|
1215
|
+
ns.auto_blocked = True
|
|
1054
1216
|
|
|
1055
1217
|
# Clear inferred states so they can be re-computed based on current job status
|
|
1056
1218
|
self.clear_inferred_states(nss)
|
|
1057
1219
|
|
|
1058
1220
|
# Re-apply Mnt state for devices with mounts (cleared above, needed for set_all_states)
|
|
1221
|
+
# Devices that are in use (mounted/swap/backing a holder) are treated as
|
|
1222
|
+
# mounted so they cannot be wiped. But Blk trumps Mnt - blocked devices
|
|
1223
|
+
# stay blocked even if mounted.
|
|
1059
1224
|
for ns in nss.values():
|
|
1060
|
-
if ns.mounts:
|
|
1225
|
+
if (ns.mounts or getattr(ns, 'in_use', False)) and ns.state != 'Blk':
|
|
1061
1226
|
ns.state = 'Mnt'
|
|
1062
1227
|
|
|
1063
1228
|
self.set_all_states(nss) # set inferred states (propagates Mnt/Busy to parents)
|