A failing drive full of photos
A friend's 500 GB FAT32 USB drive contained a large collection of photos and other files. Some regions still read at normal speeds; others were painfully slow or returned read errors.
I recovered 12,825 files through the filesystem, keeping their original names and directories. PhotoRec then found more material to sort through, although its output needed careful comparison with the files already recovered.
The biggest improvement came from the second copy of the FAT32 allocation table. Using it in the working image made another 4,081 files accessible. Almost all the disk's bytes had been recovered by that point; finding the files within them was a separate problem.
I worked through the recovery in three stages:
- Create the most complete possible image of the failing disk with
ddrescue. - Recover files through the FAT32 filesystem, including using the second copy of the FAT.
- Use PhotoRec to carve files directly from the image where filesystem-level recovery was insufficient.
The priority was to minimise work against the failing physical disk. Once I had an image, I kept the filesystem investigation and recovery on that image.
The device names, paths and FAT offsets below belong to this particular recovery. In particular, the FAT replacement command uses this disk's geometry and writes to a working image with a backup retained. It is not a generic repair command to copy onto another disk.
Image the disk before investigating the filesystem
The source was /dev/sdb. I stored sonic.img and its companion map file on a healthy disk under /media/mediaTwo/Rescue/, then started with:
sudo ddrescue -n \
/dev/sdb \
/media/mediaTwo/Rescue/sonic.img \
/media/mediaTwo/Rescue/sonic.mapThe -n option skips the scraping phase. The aim was to secure readable data before spending more time on the difficult regions. The GNU ddrescue manual explains the recovery phases and map-file behaviour.
The map file is important because it records the state of every region of the source disk. Subsequent ddrescue runs can therefore continue using the same image without rereading areas that have already been recovered.
You can check the recovery's progress with:
ddrescuelog -t /media/mediaTwo/Rescue/sonic.mapThe map distinguishes the following states:
rescued— successfully read and written to the image.non-tried— not yet attempted.non-trimmed— an area surrounding a failed read that still needs narrowing down.non-scraped— failed areas that have not yet been attempted sector-by-sector.bad-sector— sectors that ddrescue has attempted and could not read.
Skip the slow region and come back to it
The beginning of this disk was particularly difficult to read. Rather than keep hammering that region, I restarted recovery from 100 GiB:
sudo ddrescue -n \
-i 100GiB \
/dev/sdb \
/media/mediaTwo/Rescue/sonic.img \
/media/mediaTwo/Rescue/sonic.map-i 100GiB specifies the starting input position.
I reused the same image and map file. With no separate output position specified, the output position follows the input position, so recovered data stays at the corresponding offset in sonic.img. The map keeps track of which regions have already been read.
From approximately 100 GiB onwards, the drive read consistently at around 9–10 MB/s with no read errors for most of the remaining disk.
That let me save the large healthy portion before returning to the troublesome region.
Approach the first 100 GiB backwards
After recovering the area above 100 GiB, I approached the first 100 GiB from the opposite direction. ddrescue can copy backwards, letting it enter a problematic region from the other side rather than repeatedly approaching the same failing boundary.
The map file continued to track the recovered areas as ddrescue worked through several passes, including forward and backward copying and trimming failed blocks.
The result eventually reached approximately:
mapfile extent: 500107 MB
non-tried: 0 B
rescued: 500099 MB
non-trimmed: 0 B
non-scraped: 8528 kB
bad-sector: 176640 BAlmost the entire 500 GB disk was now in the image.
Stop when the remaining reads stop paying off
With the easy data secured, I ran ddrescue without -n:
sudo ddrescue \
/dev/sdb \
/media/mediaTwo/Rescue/sonic.img \
/media/mediaTwo/Rescue/sonic.mapThe existing map let ddrescue continue with the outstanding areas instead of recopying the recovered data.
At this point it entered:
Scraping failed blocks... (forwards)Scraping attempts to recover the remaining failed regions at a much finer granularity.
In this case the drive was producing very little additional data and an increasing number of read errors. For example:
non-scraped: 8335 kB
bad-sector: 294400 B
read errors: 225I stopped the scraping run: the extra reads were producing very little useful data. The rest of the work could happen on the image.
Keep an untouched copy of the recovery
Before modifying the recovered filesystem, I retained copies of both the image and map.
At this stage the recovery directory contained:
sonic.img
sonic.img.after-trim
sonic.map
sonic.map.after-trim
sonic.map.bakThat gave me an untouched copy to return to if a filesystem experiment went wrong. From this point onwards, the failing physical disk could be disconnected.
Inspect and mount the image read-only
I checked the recovered image with fdisk:
sudo fdisk -l /media/mediaTwo/Rescue/sonic.imgThis showed:
Disk sonic.img: 465.76 GiB
Disklabel type: dos
Device Start End Sectors Size Id Type
sonic.img1 2048 976750591 976748544 465.8G b W95 FAT32parted confirmed the same layout:
sudo parted /media/mediaTwo/Rescue/sonic.img unit s printResult:
Number Start End Size Type File system
1 2048s 976750591s 976748544s primary fat32The tools could read the partition table, and both placed the FAT32 partition at sector 2048.
Attach the image to a loop device
I attached the image to a loop device:
sudo losetup --find --show --partscan --read-only \
/media/mediaTwo/Rescue/sonic.imgIn this session, the command returned /dev/loop0. Use the device returned on your own system; it may have a different number.
--partscan causes Linux to discover partitions within the image, resulting in:
/dev/loop0
/dev/loop0p1I identified the filesystem with:
sudo blkid -p /dev/loop0p1which reported:
TYPE="vfat"
VERSION="FAT32"
FSBLOCKSIZE="32768"I also inspected the boot sector:
sudo file -s /dev/loop0p1and:
sudo hexdump -C -n 512 /dev/loop0p1The output was consistent with a FAT32 boot sector; it did not establish that the rest of the filesystem was intact.
I then mounted the partition read-only:
sudo mkdir -p /mnt/sonic
sudo mount -o ro \
/dev/loop0p1 \
/mnt/sonicMounting it read-only prevented the operating system from modifying filesystem metadata inside the recovery image.
A mountable filesystem is not necessarily a healthy one
The filesystem mounted and the directory structure appeared. That looked promising, until I tried listing its directories:
find /mnt/sonic -maxdepth 2 -type dproduced several errors including:
find: '/mnt/sonic/2012': Input/output error
find: '/mnt/sonic/photos/Edinburgh 2014': Input/output error
find: '/mnt/sonic/PHONE PICS 2017': Input/output error
find: '/mnt/sonic/PHONE 2016': Input/output errorThis showed that the disk image was mountable but that portions of the FAT32 filesystem metadata or file data were damaged.
Recover the readable files with rsync
I copied everything the mounted filesystem would let me read:
sudo rsync -a --info=progress2 \
/mnt/sonic/ \
/mnt/newDrive/sonic-recovery/ \
2> ~/sonic-rsync-errors.logrsync was useful here because a failure reading one file did not prevent the remainder of the filesystem from being processed.
Errors were captured separately in:
~/sonic-rsync-errors.logThe first pass recovered 8,744 files, totalling about 24 GB. Plenty of others, including JPEG photographs, returned:
Input/output error (5)I needed to work out whether those errors meant the file data was missing or the FAT32 allocation metadata was damaged.
Investigate the two FAT copies
The FAT32 boot sector provided the following relevant values:
Partition start: sector 2048
Bytes per sector: 512
Sectors per cluster: 64
Reserved sectors: 32
Number of FATs: 2
Sectors per FAT: 119203The filesystem therefore used:
64 × 512 = 32768 bytesper cluster.
More importantly, there were two copies of the File Allocation Table. FAT32 uses the FAT to record the cluster chains belonging to files. If a file occupies clusters:
1000 -> 1001 -> 4500 -> 4501the FAT contains the links required to reconstruct that chain.
Damage to the FAT can therefore make a file inaccessible even when its actual data clusters were successfully recovered by ddrescue.
Extract the allocation tables
FAT1 starts immediately after the 32 reserved sectors. With the partition starting at disk sector 2048, that puts FAT1 at:
FAT1 start = 2048 + 32
= 2080I extracted FAT1 with:
sudo dd \
if=/media/mediaTwo/Rescue/sonic.img \
of=/mnt/newDrive/fat1.bin \
bs=512 \
skip=$((2048 + 32)) \
count=119203 \
status=progressFAT2 starts immediately after FAT1:
FAT2 start = 2048 + 32 + 119203I extracted FAT2 with:
sudo dd \
if=/media/mediaTwo/Rescue/sonic.img \
of=/mnt/newDrive/fat2.bin \
bs=512 \
skip=$((2048 + 32 + 119203)) \
count=119203 \
status=progressEach extracted FAT was about 58.2 MiB (119203 × 512 bytes).
Compare the two copies
Hashes immediately established that the FATs were different:
sha256sum /mnt/newDrive/fat1.bin /mnt/newDrive/fat2.binI then counted the differing bytes:
cmp -l /mnt/newDrive/fat1.bin /mnt/newDrive/fat2.bin | wc -lResult:
899063The two supposedly mirrored FATs therefore differed in almost 900,000 byte positions.
I also checked where the differences started and ended:
Differing bytes: 899063
First difference: 24
Last difference: 5357566The differences covered a substantial range near the beginning of the tables.
Look at the allocation entries
Each FAT32 entry occupies four bytes. I used a small Python script to count zero and non-zero entries. This was a rough comparison of the tables, not a full validation of cluster chains: non-zero entries can also include reserved values, bad-cluster markers and end-of-chain markers.
The results were:
FAT1 FAT2
Entries 15,257,984 15,257,984
Zero 14,206,410 13,898,850
Non-zero 1,051,574 1,359,134FAT2 contained 307,560 more non-zero entries than FAT1. That suggested it retained allocation information missing from FAT1, although the counts alone could not prove which copy was correct.
That provided a plausible explanation for the large number of inaccessible files despite ddrescue having recovered almost all physical sectors.
Try FAT2 in the working image
With a backup retained, I tried replacing FAT1 in the working image with FAT2. This was an experiment based on this recovery's findings, not an assumption that the second FAT is always the better copy.
First, I unmounted the filesystem and detached the loop device:
sudo umount /mnt/sonic
sudo losetup -d /dev/loop0I then wrote FAT2 into the FAT1 location in the working image:
sudo dd \
if=/mnt/newDrive/fat2.bin \
of=/media/mediaTwo/Rescue/sonic.img \
bs=512 \
seek=2080 \
conv=notrunc \
status=progressconv=notrunc is important because the destination is the complete disk image. The command replaces only the FAT region rather than truncating the image.
After:
syncthe image was attached again:
sudo losetup --find --show --partscan --read-only \
/media/mediaTwo/Rescue/sonic.imgand mounted again, using the loop-device number returned by losetup (still /dev/loop0 in this example):
sudo mount -t vfat -o ro \
/dev/loop0p1 \
/mnt/sonicCheck that the files can actually be read
I tried listing the directories that had previously returned I/O errors:
ls -lah "/mnt/sonic/2012"
ls -lah "/mnt/sonic/PHONE PICS 2017"
ls -lah "/mnt/sonic/PHONE 2016"
ls -lah "/mnt/sonic/other photos"
ls -lah "/mnt/sonic/photos/Edinburgh 2014"Four of the five previously inaccessible directories became accessible. Edinburgh 2014 continued to fail.
Seeing filenames was encouraging, but I wanted to know whether I could read the files themselves. I read several files from start to finish:
find "/mnt/sonic/PHONE PICS 2017" -type f -print0 \
| head -z -n 10 \
| xargs -0 -n1 sh -c \
'printf "%s\n" "$0"; cat "$0" >/dev/null && echo " OK" || echo " FAILED"'The tested photographs all returned:
OKThe sampled files could now be read all the way through. That supported the FAT2 approach, although a successful read does not by itself prove that a photograph is undamaged.
Copy the newly accessible files
I ran rsync again against the same destination, this time with --ignore-existing:
sudo rsync -a \
--ignore-existing \
--info=progress2 \
/mnt/sonic/ \
/mnt/newDrive/sonic-recovery/ \
2> ~/sonic-rsync-fat2-errors.logThis preserved files already present at the destination and added missing ones. --ignore-existing skips any existing destination file; it does not check that the earlier copy is complete or correct.
The filesystem recovery grew from 8,744 files / 24 GB to 12,825 files / 40 GB: another 4,081 files and roughly 16 GB, with their original filenames and directories.
Only a few rsync errors remained, mainly for:
FOUND.000
photos/Edinburgh 2014Recover more candidates with PhotoRec
I then tried PhotoRec to look for files without relying on the FAT filesystem.
PhotoRec performs file carving: it looks for known file signatures in the underlying data rather than depending on the directory structure or FAT cluster chains.
I ran it against the recovered image:
sudo photorec /media/mediaTwo/Rescue/sonic.imgI scanned the FAT32 partition and wrote the results to a separate recovery directory, /media/mediaTwo/Rescue/photorec.
The final PhotoRec recovery contained:
21,521 JPG
160 PNG
139 MOV
121 MP3
120 MP4
74 3GP
34 PLIST
33 WMA
33 M4P
24 PDF
15 DOC
7 TXT
3 ODT
2 DOCX
1 XML
1 INI
1 BMPTotal:
22,289 filesPhotoRec does not generally preserve the original filenames or directory structure, so I kept these files separate from the filesystem recovery.
Compare the recoveries by content
I expected many PhotoRec files to be copies of files already recovered through FAT1 or FAT2. To compare their contents, I generated SHA-256 hashes for the filesystem recovery:
mkdir -p ~/sonic-analysis
find /mnt/newDrive/sonic-recovery -type f -print0 \
| xargs -0 sha256sum \
> ~/sonic-analysis/sonic-files.sha256and for the PhotoRec recovery:
find /media/mediaTwo/Rescue/photorec -type f -print0 \
| xargs -0 sha256sum \
> ~/sonic-analysis/photorec-files.sha256I extracted the unique hashes:
cut -d' ' -f1 ~/sonic-analysis/sonic-files.sha256 \
| sort -u \
> ~/sonic-analysis/sonic-hashes
cut -d' ' -f1 ~/sonic-analysis/photorec-files.sha256 \
| sort -u \
> ~/sonic-analysis/photorec-hashesTo count exact content present in both recoveries, I used:
comm -12 \
~/sonic-analysis/sonic-hashes \
~/sonic-analysis/photorec-hashes \
| wc -lTo count PhotoRec content missing from the filesystem recovery, I used:
comm -13 \
~/sonic-analysis/sonic-hashes \
~/sonic-analysis/photorec-hashes \
| wc -lThe results were:
Unique hashes in filesystem recovery: 11,454
Unique hashes in PhotoRec recovery: 19,545
Exact hashes present in both: 9,579
PhotoRec-only hashes: 9,966These counts compare unique file contents, not numbers of photographs. Matching hashes identify byte-for-byte duplicates; a different hash only means different content, which may still be another version or a damaged copy of the same photo. No files were deleted by these commands.
Extra JPEGs are candidates, not a final photo count
My PhotoRec-only file list contained 11,087 JPEG candidates, totalling about 2.75 GB. This is a file count rather than a unique-hash count, so duplicates can occur within it. It does not mean 11,087 additional lost photographs.
Raw carving can also recover:
- thumbnails;
- cached images;
- deleted files;
- multiple copies of the same image;
- embedded images;
- partial or damaged files.
The PhotoRec output still gave me another place to look for files I could not recover through either FAT.
What I recovered
The failing disk ultimately produced:
| Recovery stage | Result |
|---|---|
| Physical image | ~500 GB |
| Files recovered using FAT1 | 8,744 |
| Additional files recovered using FAT2 | 4,081 |
| Combined filesystem recovery | 12,825 files / ~40 GB |
| PhotoRec carved files | 22,289 |
| PhotoRec JPEGs | 21,521 |
| Unique filesystem hashes | 11,454 |
| Unique PhotoRec hashes | 19,545 |
| Exact hashes shared by both | 9,579 |
| PhotoRec-only hashes | 9,966 |
| PhotoRec-only JPEG candidates | 11,087 / ~2.75 GB |
The lesson was that recovering the disk's bytes and recovering its files are separate jobs. ddrescue got almost the entire disk into an image, but damaged FAT metadata still left thousands of files inaccessible. Trying the second FAT restored access to another 4,081 files with their original names and directories. PhotoRec gave me more candidates to inspect, although its output still needed sorting and checking.
All filesystem changes stayed on the working image. The original failing disk was used only as the source for imaging. That left me free to investigate the filesystem without asking the failing hardware to survive every experiment.