Ddrescue reset usb device on read timeout

data-recoveryddrescuehard driveusb

I have a damaged external usb hdd. When i connect the device to the pc i can access the file system for round about a minute. After that period the disk keeps spinning but every io operation times out.

To rescue my data i want to use ddrescue but since the device stops working every minute, this won't recover much, when i don't reset the usb device each time a read timeout occurs, since the most probable reason for that is, that the device hang up again. Is there a way to let ddrescue execute a shell command or so, whenever a read timeout occurs?

It is not possible to connect the external hdd via sata, since there isn't a accessible sata connector inside.

Best Answer

I know nobody's gonna read this, so I won't elaborate it too much. And it's a pity because this is the only method that works.

PROBLEM: Trying to rescue lots of valuable photos from a HDD with lots of bad sectors. When the reading software stumbles on a bad sector, it hangs (becomes unresponsive) and the only thing you can do is to unplug the USB.

THINGS TRIED: (didn't work)

All software promise that they "skip bad sectors without stop". False.

  • DataRescue DD. Simplistic and obsolete.
  • EaseUS Data Recovery. Recovers until it finds a bad sector.
  • HDD Raw Copy Tool.
  • Parted Magic. It's the same as using plain Linux distro + ddrescue.
  • Clonezilla. Tried several setups: VM, Live CD; USB and SATA connections.
  • AOMEI Backupper option "Disk Clone". Clones nothing.
  • WinHex option Clone Disk.
  • EaseUS Partition Master.

Besides, these tools don't allow to select a block o resume.

Not tried: DeepSpar Disk Imager (hardware, costs $3.000+).

APPROACHING THE SOLUTION

ddrescue is a complex program where everything is configurable. It runs on Linux and on Windows (with Cygwin). Tutorial. I wasn't able to run DDRescue-GUI (some XOpenDisplay error), besides the GUI simpler than the command-line.

Things tried with ddrescue. Some people suggested some workarounds:

  • Run ddrescue in a loop and set timeout options that stop execution.
  • Change OS HDD timeout to speed up. /sys/block/sdb/device/timeout
  • Reset the USB HDD from the inside. /sys/bus/usb/drivers/usb unbind-bind; usb_modeswitch -v 0x.... -p 0x.... --reset-usb. Using Window's Device Manager is the same.
  • End the ddrescue process (killall, taskkill). Example script. Doesn't work, as the process is unresponsive.

SOLUTION

  1. A host with a Virtual Machine (VirtualBox). The host runs a server that listens to attach/detach commands and sends them to the VM.

  2. A Windows guest VM that runs a manager script that controls the workflow: start/stop ddrescue, send attach/detach commands to host, and move forward the position in the mapfile.

A Linux (Debian) VM didn't work. After detaching, VirtualBox says: "Failed to attach the USB device to the VM"

####Script1 server.py runs in host####
import subprocess
from bottle import route, run

exe = "C:/Program Files/Oracle/VirtualBox/VBoxManage.exe"
vm = "Win7"
id = "54a7249b-930a-4d49-a679-9a7b8810adcc"  # VBoxManage list usbhost

def execute(cmd):
    p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
    (output, err) = p.communicate()
    p_status = p.wait()
    res = output.decode("utf-8")
    if "error" in res:
        return "1"
    else:
        return "0"
    return

@route('/attach')
def cmd1():
    res = execute('"' + exe + '" controlvm ' + vm + " usbattach " + id)
    return res

@route('/detach')
def cmd2():
    res = execute('"' + exe + '" controlvm ' + vm + " usbdetach " + id)
    return res

run(host='0.0.0.0', port=80)

.

####Script2 manager.py runs in guest####
import requests 
import subprocess
import time
import re
import os

def get_id(disk):
    p = subprocess.Popen("wmic diskdrive get Index, Model", stdout=subprocess.PIPE, shell=True)
    (output, err) = p.communicate()
    p_status = p.wait()
    res = output.decode("utf-8")
    lines = res.splitlines()
    id = ""
    for line in lines:
        if line.find(disk) > 1:
            id = line[0]
    return id

def check_disk(disk):
    p = subprocess.Popen("wmic diskdrive get Model", stdout=subprocess.PIPE, shell=True)
    (output, err) = p.communicate()
    p_status = p.wait()
    res = output.decode("utf-8")
    if disk in res:
        return 1
    else:
        return 0

def mod_disk(disk, cmd):
    v=1 if cmd=='attach' else 0
    for i in range(3):
        response = requests.get('http://192.168.1.20/'+cmd)
        res = response.content.decode("utf-8")
        if "0" in res:
            while True:
                if(check_disk(disk)==v):
                    break
                time.sleep(1)
                print("[" + disk + " " + cmd + " wait]")
            print("[" + disk + " " + cmd + " ok]")
            break
        time.sleep(3)

def dec2hex(n):
    return "%X" % n

def hex2dec(s):
    return int(s, 16)

def update_mapfile():
    skip = 5000000 #5Mb
    with open(mapfile, 'r') as file:
        data = file.readlines()
    line7 = data[6]
    p = re.compile('^0x(.*?) ')
    hval = p.findall(line7)[0] 
    dval = hex2dec(hval)
    dval2 = dval + skip
    hval2 = dec2hex(dval2)
    line7b = re.sub(hval, hval2, line7)
    data[6] = line7b
    with open(mapfile, 'w') as file:
        file.writelines(data)

############################
disk = "WD 3200AAJ"
mapfile = "E:/mapfile.log"
inpt = "/dev/sdb" if get_id(disk) == "1" else "/dev/sdc"
cmdl = 'c:/cygwin/bin/ddrescue.exe -v -d -n -O ' + inpt + ' E:/image.img E:/mapfile.log'

#Start ddrescue
proc = subprocess.Popen(cmdl, shell=True, stdin=None, stdout=None, stderr=None, close_fds=True)
print("Start ddrescue")
time.sleep(60)

while True:
    ft = os.path.getmtime(mapfile)
    n = time.time()
    if n-ft > 60:
        #send sigterm
        p = subprocess.Popen('taskkill /f /fi "imagename eq ddrescue.exe"', stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True)
        (output, err) = p.communicate()
        p_status = p.wait()
        print("send sigterm")
        #detach HDD
        mod_disk(disk, 'detach')
        time.sleep(2)
        print("detach HDD")
        #update modfile
        update_mapfile()
        print("update modfile")
        #attach HDD
        mod_disk(disk, 'attach')
        time.sleep(2)
        print("attach HDD")
        #restart ddrescue
        inpt = "/dev/sdb" if get_id(disk) == "1" else "/dev/sdc"
        cmdl = 'c:/cygwin/bin/ddrescue.exe -v -d -n -O ' + inpt + ' E:/image.img E:/mapfile.log'
        proc = subprocess.Popen(cmdl, shell=True, stdin=None, stdout=None, stderr=None, close_fds=True)
        print("restart ddrescue")
    time.sleep(60)

Obviously you will need to adapt the configuration to your system.

Related Question