Linux
linuxデスクトップ
サルベージ

SDカードから消失したファイルをサルベージした話

背景

スマホの調子が悪くなったので電源を切って入れ直したら、SDカードの、写真を入れていたフォルダだけがきれいに消失した。
サルベージしたら、だいたい戻ってきた気がするので、備忘録として、また、同じ憂き目に遭った人のお役に立てればと、文章に残すことにした。

なお、筆者の環境は、Arch Linuxを使用している。
(たぶん、使ったソフト自体はWindowsやMacでもあると思う)

免責事項

  • 本記事によって生じたいかなる損害も筆者は保証できません。どうしても必要なデータが消失した場合は専門家などに相談されることを強くお勧めします。(筆者はデータ復旧の専門家を利用したことがないため、ご紹介することはできません。ご了承下さい)
  • 異常が発生しているディスクやSDカード等に対し不用意な操作をすると、事態が悪化する可能性があります。本記事で紹介している操作を行うかどうかは、ご自身の自己責任でご判断ください。
  • 本記事はコンピュータオタク向けです。また、コメント欄などで助けを求められても、力になれない可能性が高いです。ご了承下さい。

TL;DR

ddrescueとphotorec最高

ディスクをダンプする

壊れている可能性があるカードをいつまでも触り続けるのは怖いので、まずは、カードの中身をファイルに書き出しました。
書き出す先(通常は、PCのSSDなりHDDなりになるかな、と思います。場合によっては他の同容量のSDカードだったりするかもしれません)には、カードまるまるのディスク容量が必要です。
(8GBのカードなら、たとえ1GBしか使っていなくても8GB全部の空き容量が必要になります)

SDカードを差して、マウントはしない状態にしました。

最初はddコマンドでバックアップしていたのですが、途中でI/Oエラーが起きました。
何度か試しましたが、別のところでI/Oエラーが起きたり、同じ箇所でI/Oエラーが起きて同じファイルサイズになっても、sha1を取ると違ったりしました。

ddはしんどそうなので別の方法を探すと、ddrescueコマンドを知りました。
https://superspeed-fall.com/entry/2015/03/26/224512

ミスって書き換えちゃったときのために、取り出したイメージを念の為コピーとっておきました。

ファイルを救出する

もうSDカードは抜いちゃって大丈夫です。この後の操作は、取り出したイメージに対してやっています。
イメージ書き換えちゃったら、コピーとっといたやつを上書きしなおしてます。

fsck.vfat では、出てきませんでした。
調べたら testdiskphotorec を知りました。
testdisk で消したファイルが復活できるっぽいですが、残念ながら、目的のフォルダは見つかりませんでした。

次に photorec を試しました。Arch Linuxでは、testdiskといっしょに入ってきました。
https://pctrouble.net/software/photorec.html
どうやら
https://www.cgsecurity.org/wiki/PhotoRec
を見る限り、JPEGのヘッダっぽいものを片っ端から探して復旧するみたいです。

PhotoRecを使うと、消失したファイルが復旧できました。
(全部できたかどうかは、元々の数を把握していないので分からないですが、概ねできている印象です)

ファイル名は無茶苦茶になりますが、どうやらファイルのタイムスタンプはEXIF情報に合わせてくれたっぽいです。
ただ、たまにEXIFの日付が変なファイルもありました。

ファイル名をタイムスタンプに合わせる

ファイル名があまりに分かりにくく、ソートとかもしづらいので、ファイル名をタイムスタンプにリネームするスクリプトを書きました。

rename.py
import datetime
import os

def pathsplit(path):
    dirname = os.path.dirname(path)
    basename = os.path.basename(path)
    root, ext = os.path.splitext(basename)
    return dirname, root, ext

def pathjoin(dirname, root, ext):
    s = os.path.join(dirname, root)
    if root and ext:
        s += ext
    return s

def get_newpath(current_path):
    stat = os.stat(current_path)
    d, r, e = pathsplit(current_path)
    timestr = datetime.datetime.fromtimestamp(stat.st_mtime).strftime('%F_%H%M%S')
    suffix = ''
    n = 1
    while 1:
        newpath = pathjoin(d, timestr + suffix, e)
        if os.path.exists(newpath):
            suffix = '_%03d' % (n,)
            n += 1
        else:
            return newpath

for p in os.listdir():
    os.rename(p, get_newpath(p))

まとめ

復活できてよかった。ddrescueとphotorec最高。