Salvaging a dying disk by partitioning around the wound
Contents
badblocks tells you how many bad blocks; its -o logfile tells you where, and that changes the verdict. All 58 failures on this 8 TB drive sat in the first 5.23% of the platter. Partition past the wound, leave the damaged region unallocated, and the healthy 94.8% — about 6.7 TiB — goes back to work as disposable scratch space. Not for anything you'd miss.Every badblocks run leaves two artifacts, and the useful one is the file almost nobody opens. There's the summary line everyone reads:
Pass completed, 58 bad blocks found. (58/0/0 errors)
And there's the -o logfile beside it, listing exactly which blocks failed. On an 8 TB Toshiba MG06ACA800E (/dev/sdd, serial 62K0B0EXAMPL, six years and change of power-on time), that summary line reads like a death sentence, and SMART agreed: Reallocated_Sector_Ct stuck at 0 — the firmware had given up remapping — while Current_Pending_Sector climbed from 160 to 224 during the run and Offline_Uncorrectable sat at 126. The scarred-versus-dying question, the SMART side of it, is its own post; the verdict there was dying, bin it.
But a headcount of bad blocks tells you how many, never where — and on a spinning disk, where is the entire story.
The count lies about geography#
The run was destructive and logged:
badblocks -wsv -b 4096 -t 0x00 -o sdd.bad /dev/sdd
The -o file is the interesting artifact. It's a plain list of block numbers, and — this is the part people skip — those numbers are in units of the -b block size. I ran with -b 4096, so every number in the file is a 4096-byte block. Multiply by 4096 and you get a byte offset. A physical location.
Fifty-eight lines. The first and last:
160843
...
102158398
160843 × 4096 ≈ 0.66 GB
102158398 × 4096 ≈ 418 GB
Every one of the 58 fell between 0.66 GB and 418 GB. On an 8 TB (decimal) drive, 418 GB is 5.23% of the platter. The bad blocks weren't sprinkled across the disk — they were a single contiguous bruise in the first twentieth of it. The verify pass had just read the other 94.8% clean, front to back, without a complaint.
That changes the problem completely. Scattered rot means the whole platter is suspect and you throw the drive away. A localized wound means you have a mostly-healthy disk with a bad spot you know the exact coordinates of. You don't format around 58 blocks. You format around a region.
And the intended job for this disk made the decision easy: a disposable scratch disk. Everything on it is re-downloadable by definition. "Failing" is an acceptable adjective for storage you can rebuild from scratch — if you keep the failing part out of the filesystem.
Route around it#
The move is a partition that starts past the wound.
parted /dev/sdd --script mklabel gpt mkpart primary ext4 8% 100%
8% of 8 TB is 640 GB. That's comfortably past the 418 GB where the damage ends, with a ~220 GB shock-absorber for the pending sectors that are still turning bad. Percentages here aren't laziness — parted snaps a percentage start to an optimal (1 MiB) alignment boundary on its own, the same value you'd reach doing the sector arithmetic by hand. The first 640 GB of the disk — the wound and its margin — is now unpartitioned free space that the kernel has no address for. It is simply not part of any block device the OS will ever write to.
Then the filesystem, on the partition, not the disk:
mkfs.ext4 -F -L scratch -U 843644ea-66dc-491f-8a29-792726911b56 /dev/sdd1
The -U is worth a sentence. mkfs mints a fresh UUID every time you run it, which would strand the existing UUID=843644ea-... line in /etc/fstab and drop the mount. Feeding the drive's previous filesystem UUID back in means the fstab entry keeps working, untouched. The disk gets reincarnated in place.
The result:
$ df -h /media/8TB_MG06ACA800E
Filesystem Size Used Avail Use% Mounted on
/dev/sdd1 6.7T 2.1M 6.4T 1% /media/8TB_MG06ACA800E
6.7 TiB — about 7.4 TB — of usable throwaway space, containing zero known-bad blocks, carved out of a drive SMART had already written off. The bad sectors are still down there, somewhere below the 418 GB mark. sdd1 just doesn't begin until 640. Out of sight, out of the address space.
Three ways to route around a bad block#
Partitioning past the wound isn't the only option, and it's worth being honest about why it beat the alternatives instead of pretending it was obvious.
Mark the blocks in ext4's bad-block inode. mke2fs will take the list directly:
mke2fs -l sdd.bad /dev/sdd1
Every block in that file gets added to the bad-block inode and is never handed to a file or directory. Legitimate, built for exactly this, and wrong here for two reasons. First, it's fragile: the block numbers in the list have to match both the block size and the frame of reference of the target. My list was generated against the whole disk, /dev/sdd. Feed those whole-disk offsets to a filesystem living on /dev/sdd1, which counts blocks from its own start, and you fence off the wrong sectors — quietly. Second, and worse, the bad-block inode is written once at mkfs time and never grows. This drive's Current_Pending_Sector is still climbing. New bad blocks are coming, and a static list minted today has no idea about them. A list of known problems that can't learn about new ones is mostly there to make you feel better.
Write a big file over the bad region. The folk remedy: dd a few hundred gigabytes of zeros into a file and hope it lands on the damage so the allocator never reuses those blocks. Unreliable in the boring way — you don't control where the extent allocator puts your file. It might cover the wound. It might not. "Might" is not a storage strategy.
Start the partition past the wound. The bad region isn't marked as bad, or covered, or bookkept. It's outside the filesystem's address space entirely — there is no block number in sdd1 that maps to it. Nothing to keep in sync, nothing to regenerate, nothing to get wrong. For a contiguous fault that is the cleanest answer by a wide margin, and it's the whole reason reading the map first mattered: you can only cut above a wound if the wound has a top edge.
When this doesn't apply#
Every word above depends on "contiguous." If the 58 blocks had been smeared across all 8 TB, no start offset saves you — you can't partition around confetti, and a drive failing that way is usually too far gone to bother. The technique isn't "bad drives are fine, actually." It's "read where the damage is before you decide."
And the good region isn't guaranteed good forever. Pending sectors are still going bad on this disk; the wound may yet grow a second front behind the 640 GB line. So sdd1 lives on probation: a daily cron that reads the SMART attributes, alerts on any upward slope in pending or reallocated counts, and the standing rule is bin-it-on-first-real-error. First, not second: a second chance is how a drive earns a third. That is only a sane way to run storage because every byte on it is re-downloadable. Do not do this with data you would miss.
But for disposable bulk on a disk the SMART report calls dead? A localized wound often leaves 90% of the platter perfectly usable. The failure had a shape. I just had to read the map before throwing it in the bin — and the map said the drive was 94.8% fine.
Filed under: linux, storage, ext4, hardware
Read next#
- Reallocated sectors: is your drive dying or just scarred? — the SMART side of the same drive, and why the verdict there was "dying, bin it"
- Designing alerts you'll actually understand at 3am — catching the next one while it's still a growing counter rather than a dead platter
- All four drives at once, and why that was the good news — when every drive vanishes, the disk is the one thing that isn't broken
Sources#
- badblocks(8) — man7.org —
-bblock size and-ooutput format; list block numbers are relative to the-bsize. - badblocks — ArchWiki — the block-size caveat and the whole-disk-vs-partition offset trap when reusing a list.
- mke2fs(8) — die.net —
-lreads a bad-block list into the bad-block inode; numbers must match the mkfs block size. - mkfs.ext4(8) — die.net —
-Usets the filesystem UUID so an existingfstabentry survives a reformat. - GNU Parted — ArchWiki — percentage start/end values are snapped to optimal alignment automatically.