Recovering a Weak-Head Seagate Drive with Controlled HDDSuperClone Imaging
This is a technical write-up of a real recovery workflow used on an unstable 1TB Seagate drive that would reset under sustained reads. The goal was to safely extract photos for a photographer while minimizing drive stress.
The drive would not show up in Windows on the Device Manager, and required using a SATA PCIe card to be able to hot swap the drive in after BIOS since it would lock up BIOS during hardware checks.
The drive would show up in output of lsblk and thus we took the following steps to recover data for our customer.
Tools used: Linux, smartctl, hdparm, HDDsuperclone, PhotoRec, ExifTool.
Symptoms and initial triage
The drive presented as readable for short operations (SMART and small reads), but would become unstable under sustained sequential reads.
ddrescue would stall, and imaging attempts with HDDSuperClone would trigger firmware resets and capacity/identify glitches. The error message read Source drive reports wrong size / size changed. No new clicking sounds were observed.
Quick verification commands
$ sudo smartctl -a /dev/sdX
$ sudo hdparm -I /dev/sdX
$ sudo dd if=/dev/sdX of=/dev/null bs=512 count=1
A key indicator was SMART responsiveness: after stressful imaging sessions, smartctl became slow, but returned to fast responses
after a longer cool-down. This strongly suggested thermal / sustained-load sensitivity consistent with a weak head (not a dead motor, not obvious
mechanical crash, and not immediate firmware lockup).
Why sustained imaging was failing
In this case, the drive tolerated only a limited amount of continuous reading before entering heavy internal retries and eventually resetting. The practical takeaway: avoid long, continuous reads. Use controlled reads of small sections of the drive and allow true rest between runs.
Controlled micro-burst imaging with HDDsuperclone
HDDSuperClone was used to image in small, controlled segments. The critical technique was to increase segment size gradually, only when the drive remained stable and SMART stayed responsive.
Segment sizing approach
Start conservative and increase in measured steps. Example working sizes observed during this recovery:
- 512000 sectors (~250MB)
- 640000 sectors (~320MB)
- 768000 sectors (~384MB)
If hesitation increases, SMART slows, or resets occur, drop back to the last stable size.
Operational pattern used between runs
After each successful segment, the drive was placed into standby to stop spindle rotation and park heads cleanly. This provided real rest compared to simply waiting while the drive continued spinning.
$ sudo hdparm -Y /dev/sdX
$ sudo hdparm -C /dev/sdX
The workflow was: run a segment, stop, disconnect the device handle, issue standby (hdparm -Y), rest 10–15 minutes, then resume.
After several larger segments, take a longer cool-down.
Validating the image and attempting mounts
On partial images, mounting the whole-disk image directly often fails because the filesystem lives inside a partition at an offset. First, inspect partitions within the image:
$ sudo fdisk -l badimage.imgFor a typical NTFS partition starting at sector 2048, the mount offset is 2048 * 512 = 1048576 bytes. Attempt a read-only mount via ntfs-3g:
$ sudo mkdir -p /mnt/test
$ sudo ntfs-3g -o ro,loop,offset=1048576 badimage.img /mnt/test
$ sudo umount /mnt/testIn this recovery, the image did not mount early on (likely incomplete NTFS metadata), so we pivoted to file carving.
Photo recovery with PhotoRec (JPEG-focused)
PhotoRec can recover photos from partial images even when filesystem metadata is incomplete. Run PhotoRec against the image:
$ sudo photorec badimage.img
In the PhotoRec UI, choose File Opt, press s to disable all, then enable only the photo types you care about
(JPEG/JPG and optionally PNG/RAW). This reduces junk output and speeds up the carve.
PhotoRec filenames: f######## vs t########
PhotoRec assigns generated filenames. Two common patterns:
f########.jpg– a carved file recovered by signature scant########.jpg– commonly thumbnails / small previews (often safe to separate)
If you want to isolate thumbnails for review later:
$ mkdir -p thumbs
$ mv t*.jpg thumbs/ 2>/dev/null || trueSorting recovered photos for a photographer (EXIF-based)
Carving loses original folder structure and filenames. For photographers, sorting by EXIF date is usually the fastest way to rebuild timelines. Install ExifTool:
$ sudo apt install exiftoolWe can check the contents of the metadata to see what value we want to use to sort, the best value was the Created Time value:
$ exiftool f6121520.jpg The following script was created to sort recovered photos by date based on the exif data:
#!/usr/bin/env python3
import argparse
import json
import os
import shutil
import subprocess
from pathlib import Path
from typing import Dict, List
def find_recup_dirs(src_root: Path) -> List[Path]:
if not src_root.is_dir():
raise FileNotFoundError(f"Source root not found or not a directory: {src_root}")
return sorted([p for p in src_root.iterdir() if p.is_dir() and p.name.startswith("recup")])
def iter_files_under(dirs: List[Path]) -> List[Path]:
files: List[Path] = []
for d in dirs:
for p in d.rglob("*"):
if p.is_file():
files.append(p)
return files
def chunked(lst: List[Path], n: int) -> List[List[Path]]:
return [lst[i : i + n] for i in range(0, len(lst), n)]
def exiftool_create_month(files: List[Path]) -> Dict[str, str]:
"""
Returns mapping: SourceFile -> "YYYY-MM"
Missing CreateDate will simply not exist in mapping.
Uses exiftool date formatting to output YYYY-MM directly.
"""
if not files:
return {}
cmd = ["exiftool", "-json", "-CreateDate", "-d", "%Y-%m"] + [str(f) for f in files]
try:
proc = subprocess.run(cmd, capture_output=True, text=True, check=False)
except FileNotFoundError:
raise RuntimeError("exiftool not found. Install it first (e.g., sudo apt install libimage-exiftool-perl).")
if proc.returncode != 0 and not proc.stdout.strip():
raise RuntimeError(f"exiftool failed:\n{proc.stderr.strip()}")
try:
data = json.loads(proc.stdout) if proc.stdout.strip() else []
except json.JSONDecodeError as e:
raise RuntimeError(f"Failed to parse exiftool JSON output: {e}\nStderr:\n{proc.stderr.strip()}")
out: Dict[str, str] = {}
for item in data:
src = item.get("SourceFile")
month = item.get("CreateDate") # already formatted like YYYY-MM due to -d
if src and month and isinstance(month, str) and len(month) == 7 and month[4] == "-":
out[src] = month
return out
def safe_dest_path(dest_dir: Path, filename: str) -> Path:
dest_dir.mkdir(parents=True, exist_ok=True)
base = Path(filename).stem
ext = Path(filename).suffix
candidate = dest_dir / (base + ext)
if not candidate.exists():
return candidate
i = 1
while True:
candidate = dest_dir / f"{base}_{i}{ext}"
if not candidate.exists():
return candidate
i += 1
def copy_file(src: Path, dest_dir: Path, dry_run: bool) -> Path:
dest = safe_dest_path(dest_dir, src.name)
if dry_run:
return dest
shutil.copy2(src, dest)
return dest
def move_file(src: Path, dest_dir: Path, dry_run: bool) -> Path:
dest = safe_dest_path(dest_dir, src.name)
if dry_run:
return dest
dest_dir.mkdir(parents=True, exist_ok=True)
shutil.move(str(src), str(dest))
return dest
def main():
ap = argparse.ArgumentParser(description="Sort PhotoRec recovered files into YYYY-MM folders using ExifTool CreateDate.")
ap.add_argument("--src-root", default="/mnt/samsung/photorec_out", help="Root that contains recup* directories.")
ap.add_argument("--dest-root", default="/mnt/samsung/recoveredphotos", help="Destination base directory.")
ap.add_argument("--chunk-size", type=int, default=500, help="How many files to send to exiftool per batch.")
ap.add_argument("--dry-run", action="store_true", help="Print what would happen, but don't copy/move anything.")
args = ap.parse_args()
src_root = Path(args.src_root)
dest_root = Path(args.dest_root)
thumbs_dir = dest_root / "thumbnails"
misc_dir = dest_root / "Misc"
recup_dirs = find_recup_dirs(src_root)
if not recup_dirs:
print(f"No recup* directories found under: {src_root}")
return
all_files = iter_files_under(recup_dirs)
if not all_files:
print(f"No files found under recup* directories in: {src_root}")
return
# Separate thumbnails (filename starts with 't')
thumbs = [p for p in all_files if p.name.startswith("t")]
normal = [p for p in all_files if not p.name.startswith("t")]
print(f"Found recup dirs: {len(recup_dirs)}")
print(f"Total files: {len(all_files)} | thumbnails (move): {len(thumbs)} | normal (copy): {len(normal)}")
if args.dry_run:
print("DRY RUN enabled: no changes will be made.\n")
# Move thumbnails
moved_thumbs = 0
for p in thumbs:
dest = move_file(p, thumbs_dir, args.dry_run)
moved_thumbs += 1
if moved_thumbs <= 10 or moved_thumbs % 500 == 0:
print(f"[THUMB] {p} -> {dest}")
if moved_thumbs > 10:
print(f"[THUMB] ... moved {moved_thumbs} thumbnails total")
# Copy normal files based on CreateDate -> YYYY-MM
copied = 0
to_misc = 0
for batch in chunked(normal, args.chunk_size):
month_map = exiftool_create_month(batch)
for p in batch:
month = month_map.get(str(p))
target_dir = (dest_root / month) if month else misc_dir
dest = copy_file(p, target_dir, args.dry_run)
copied += 1
if not month:
to_misc += 1
if copied <= 10 or copied % 1000 == 0:
label = month if month else "Misc"
print(f"[COPY:{label}] {p} -> {dest}")
if copied > 10:
print(f"[COPY] ... copied {copied} files total")
print("\nDone.")
print(f"Thumbnails moved: {moved_thumbs} -> {thumbs_dir}")
print(f"Normal copied: {copied} -> {dest_root}")
print(f"Missing CreateDate (copied to Misc): {to_misc} -> {misc_dir}")
if __name__ == "__main__":
main()
We use the chmod command to make the script executable, test it with the dry run parameter, and after validation we can run this to sort the photos by exif data datetime.
$ chmod +x sort_recup_photos.py
$ sudo ./sort_recup_photos.py --dry-run
$ sudo ./sort_recup_photos.py
Takeaways
- Weak-head drives can be readable in short bursts while failing under sustained reads.
- SMART responsiveness is a practical indicator of fatigue; longer cool-downs can restore stability.
- Using
hdparm -Ybetween runs provides real rest without hard power pulls. - File carving can produce usable results before a filesystem is mountable.
- For photographers, EXIF-based sorting quickly restores usable organization.
-
Need help with a failing drive in Minnesota?
Champlin Guys Data Recovery – Free evaluation.