Skip to content

Repository files navigation

DiskRecovery

🇬🇧 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.

Why it doesn't go through Disk Utility

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 320GB badge any disk whose media name contains "TOSHIBA" and whose size falls in the 300–340 GB range (manufacturers round sizes differently than macOS does).

Features

  • 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 reports SMART Status: Verified/Failing/Not Supported) with smartctl -a /dev/<id> if you have smartmontools installed (brew install smartmontools), to see detailed attributes like Reallocated_Sector_Ct (already-reallocated sectors) or Current_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 compare Reallocated_Sector_Ct and 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): diskutil doesn't expose a percentage for eraseDisk, 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 calls fsync to confirm the disk actually received them. On a disk with damaged sectors, that final fsync is what can hang (the same kind of kernel-level block as a hung diskutil 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 (U state) — not even kill -9 will kill it until the disk responds or you physically unplug it via USB.

Requirements

  • 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/rdiskN and running diskutil eraseDisk on internal disks requires root.

Installation

Option 1: download the prebuilt .app

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.app

The .app opens a Terminal window and runs the TUI inside it (this is a terminal tool, not a native GUI).

Option 2: build from source

cargo build --release

The binary ends up at target/release/diskrecovery.

Usage

Explore the interface risk-free (runs no real commands):

./target/release/diskrecovery --dry-run

Real usage (actually format / wipe):

sudo ./target/release/diskrecovery

Controls

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)

Typical flow

  1. Select the disk in the list (look for the ★ TOSHIBA 320GB badge if it's the disk Disk Utility struggles to show).
  2. Choose an action: SMART check, Format as GPT, Format as MBR, Quick wipe, or Full sweep.
  3. If formatting, choose the filesystem.
  4. Read the warning screen and type the disk identifier (e.g. disk4) exactly as shown, then press Enter.
  5. Wait for it to finish and check the result (success/error with the command's output).

Unit tests

cargo test

70+ 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 info plist output.
  • Detection of the target 320GB Toshiba disk by name + size.
  • Exact construction of diskutil eraseDisk arguments 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.

Code layout

  • src/disk.rs — disk data model and plist parsing.
  • src/executor.rs — the DiskExecutor abstraction (trait) with a real implementation (RealExecutor, runs diskutil/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.rsratatui rendering 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.

Building the distribution package (.app)

./scripts/build-macos-app.sh

Produces 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.

Contributing

This project is free and open-source software (FOSS), licensed under MIT (see LICENSE). Issues and pull requests are welcome. Before submitting a change:

  1. cargo test passes.
  2. cargo clippy --all-targets shows no new warnings.
  3. If the change affects user-visible text or behavior, update both READMEs (README.md in English and README.es.md in Spanish).

License

MIT © 2026 Wonder Diaz.

Final warning

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.

About

macOS TUI for low-level disk recovery and formatting built for external drives with bad sectors that Disk Utility hangs on. GPT/MBR format, quick wipe, full sector-reallocation sweep, SMART check, dry-run mode. MIT licensed, FOSS.

Topics

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages