🇬🇧 English | 🇪🇸 Español
A macOS TUI for low-level disk recovery and formatting, written in Rust
(ratatui + crossterm). Built for the case of an external disk (e.g. a
320GB Toshiba drive with slow/damaged sectors) that Disk Utility.app takes
forever or hangs trying to mount or verify before it even shows up.
⚠️ This tool formats and wipes disks at a low level. Formatting (GPT/MBR) and the "quick wipe" are irreversible operations: all data on the chosen disk is lost. Use it carefully and always verify the disk identifier before confirming.
Disk Utility (and the "friendly" diskutil that Finder uses) tries to mount
and sometimes verify the existing filesystem before showing a disk. If the
disk has bad sectors or a damaged partition table, that verification can take
minutes or hang entirely. DiskRecovery instead:
- Enumerates disks with
diskutil list -plist(only lists identifiers, never mounts anything). - Requests each disk's metadata with
diskutil info -plist <id>(manufacturer, size, bus, partition scheme) without forcing a mount. - Automatically flags with a
★ TOSHIBA 320GBbadge any disk whose media name contains "TOSHIBA" and whose size falls in the 300–340 GB range (manufacturers round sizes differently than macOS does).
- Disk list navigable with ↑/↓, showing identifier, manufacturer, size, bus (USB/SATA/...) and current partition scheme.
- The system boot disk is always hidden (it can never be selected or accidentally wiped).
- Format as GPT or MBR, choosing the filesystem (ExFAT, MS-DOS FAT32, or Mac OS Extended Journaled for GPT only).
- Quick wipe: zeroes the first ~40MB (protective MBR + primary GPT
header + partition table) and the last ~8MB (backup GPT header) of the
disk, using
dd-style writes against the raw device (/dev/rdiskN). This invalidates the partition table and makes the disk show up as "uninitialized" again, without rewriting the full 320GB. - SMART check (read-only, never modifies the disk): combines
diskutil info <id>(which already reportsSMART Status: Verified/Failing/Not Supported) withsmartctl -a /dev/<id>if you have smartmontools installed (brew install smartmontools), to see detailed attributes likeReallocated_Sector_Ct(already-reallocated sectors) orCurrent_Pending_Sector(suspect sectors pending reallocation). Being read-only, this action skips confirmation and the disk identifier prompt entirely — it runs as soon as you press Enter on it. - Full sweep (
Full sweep (forces reallocation of damaged sectors)): zeroes the entire disk, sector by sector, using the same mechanism as the quick wipe but without limiting itself to head/tail. Forcing a write to every sector means that, if the drive's firmware finds one it can't write reliably, it automatically reallocates it to a spare sector (the "G-list") — if the disk still has spares available. This is not a physical repair and does nothing if the spare reserve is already exhausted or the failure is mechanical (head, motor); no software tool can fix that. It's a long operation (proportional to the full disk size, not just ~48MB like the quick wipe) and just as irreversible, so it goes through the same confirmation screen requiring the disk identifier. Recommended: run a SMART check before and after to compareReallocated_Sector_Ctand confirm whether anything was actually reallocated. - Mandatory confirmation: before running any destructive action you
must type the exact disk identifier (e.g.
disk4), not a simple yes/no. - Simulation mode (
--dry-run): walks through the entire interface and shows exactly which commands it would run, without touching any real disk. Ideal for practicing before using it for real. - Real-time progress during execution:
- Quick wipe: a progress bar with a real percentage (bytes
written / total bytes, computed by writing the zeroes ourselves instead
of delegating to
dd), throughput in MB/s, and an estimated ETA. - Format (GPT/MBR):
diskutildoesn't expose a percentage foreraseDisk, so a spinner is shown instead with elapsed time and the current stage ("Step 1/2: Unmounting" → "Step 2/2: Formatting"). - In both cases: if more than 5 seconds pass without any progress update, a possible stall warning appears (useful precisely for a disk with damaged sectors: the bar stops exactly where the disk starts failing).
- When finished, the result screen shows the operation's total duration.
- Important note about 100%: for the quick wipe, reaching 100% only
means all the zero bytes have been
write-ed; after that the app callsfsyncto confirm the disk actually received them. On a disk with damaged sectors, that finalfsyncis what can hang (the same kind of kernel-level block as a hungdiskutil unmountDisk). That's why you can see the bar at 100% and the stall warning at the same time — it's not a contradiction, it's the disk failing exactly at the final confirmation step. If that happens, the process can end up in uninterruptible sleep (Ustate) — not evenkill -9will kill it until the disk responds or you physically unplug it via USB.
- Quick wipe: a progress bar with a real percentage (bytes
written / total bytes, computed by writing the zeroes ourselves instead
of delegating to
- macOS (uses
diskutil,dd-style writes, and/dev/rdiskN, all macOS/BSD-specific). - Rust (2021 edition or newer).
- Administrator privileges (
sudo) for real mode: writing to/dev/rdiskNand runningdiskutil eraseDiskon internal disks requires root.
Download the latest .zip from the Releases page, unzip
it, and open DiskRecovery.app. Since it isn't signed with a paid Apple
Developer account, the first time you open it Gatekeeper will show an
"unidentified developer" warning: right-click the app → Open, or run:
xattr -d com.apple.quarantine DiskRecovery.appThe .app opens a Terminal window and runs the TUI inside it (this is a
terminal tool, not a native GUI).
cargo build --releaseThe binary ends up at target/release/diskrecovery.
Explore the interface risk-free (runs no real commands):
./target/release/diskrecovery --dry-runReal usage (actually format / wipe):
sudo ./target/release/diskrecovery| Key | Action |
|---|---|
| ↑ / ↓ | Move selection |
| Enter | Select / confirm |
| Esc | Go back to the previous screen |
| r | Refresh the disk list (list screen) |
| q | Quit (disabled while typing a confirmation) |
- Select the disk in the list (look for the
★ TOSHIBA 320GBbadge if it's the disk Disk Utility struggles to show). - Choose an action:
SMART check,Format as GPT,Format as MBR,Quick wipe, orFull sweep. - If formatting, choose the filesystem.
- Read the warning screen and type the disk identifier (e.g.
disk4) exactly as shown, then press Enter. - Wait for it to finish and check the result (success/error with the command's output).
cargo test70+ tests cover the system without touching real hardware (using an
in-memory MockExecutor instead of diskutil/dd, and an in-memory
ratatui TestBackend instead of a real terminal):
- Parsing of
diskutil list/diskutil infoplistoutput. - Detection of the target 320GB Toshiba disk by name + size.
- Exact construction of
diskutil eraseDiskarguments for every scheme (GPT/MBR) and filesystem combination. - Computation of the "quick wipe" plan (which byte ranges get zeroed), including the edge case of very small disks.
- That the "quick wipe" only zeroes the head and tail of a test file (leaving the middle intact), and that reported progress is monotonically increasing up to 100%.
- That the duration/stall clock (
executing_started_at,last_progress_at) is initialized when confirming an action and frozen when it finishes (success or error). - That the system boot disk never appears in the selectable list.
- That confirmation requires the exact disk identifier (rejects different casing or partial text).
- The full UI state machine (navigation, menus, confirmation, execution) using the simulated executor.
- Rendering of every screen (list, action menu, format options,
confirmation, determinate/indeterminate execution, result, fatal error)
against an in-memory
TestBackend, verifying it never panics and shows the expected text.
src/disk.rs— disk data model andplistparsing.src/executor.rs— theDiskExecutorabstraction (trait) with a real implementation (RealExecutor, runsdiskutil/dd-style writes) and a simulated one for tests (testing::MockExecutor); pure construction of command arguments and the quick-wipe plan.src/safety.rs— safety guards (protected boot disk, confirmation phrase check).src/app.rs— the application's state machine (screens, navigation, transitions), independent of the terminal.src/ui.rs—ratatuirendering for each screen.src/main.rs— wiring: terminal (crossterm), event loop, threads to keep the UI responsive during long operations, CLI (--dry-run).
See .claude/skills/ for detailed guides on architecture, testing,
security, i18n, TUI design, and distribution — written to help this
project scale while keeping these same principles.
./scripts/build-macos-app.shProduces dist/DiskRecovery.app and
dist/DiskRecovery-<version>-macos.zip, ready to attach to a GitHub
Release. See .claude/skills/release-distribution/SKILL.md for details.
This project is free and open-source software (FOSS), licensed under MIT (see LICENSE). Issues and pull requests are welcome. Before submitting a change:
cargo testpasses.cargo clippy --all-targetsshows no new warnings.- If the change affects user-visible text or behavior, update both
READMEs (
README.mdin English andREADME.es.mdin Spanish).
MIT © 2026 Wonder Diaz.
This tool writes directly to block devices. Incorrect use (choosing the wrong disk) can permanently and unrecoverably destroy data. Always verify the identifier, manufacturer, and size on the confirmation screen before typing the identifier and pressing Enter.