うっかりエンジニアのメモ

未来の自分に宛てたメモ

APFSでフォーマットした外付けSSDが認識されなくなったのでdratで中身をサルベージした

MacBook Proに接続したこの外付けSSDへ巨大なデータをコピーしていると、なぜかコピー処理がハングした。外付けSSDを取り出そうにも「プログラムが使用している可能性があります」とか言われて通常の取り出し操作ができなくなった。選択肢に「強制的に取り出す」があったので一瞬嫌な予感がしたもののしょうがないのでクリックしたところ、案の定、外付けSSDが認識されなくなった。

ストレージ動作不良の時に使う定番のFirst Aidを試すも復旧できず。diskutilでストレージの状態を調べると

% sudo diskutil list
Password:
/dev/disk0 (internal, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *2.0 TB     disk0
   1:                        EFI EFI                     314.6 MB   disk0s1
   2:                 Apple_APFS Container disk1         2.0 TB     disk0s2

/dev/disk1 (synthesized):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      APFS Container Scheme -                      +2.0 TB     disk1
                                 Physical Store disk0s2
   1:                APFS Volume Macintosh HD            11.2 GB    disk1s1
   2:                APFS Volume Macintosh HD - Data     609.6 GB   disk1s2
   3:                APFS Volume Preboot                 86.6 MB    disk1s3
   4:                APFS Volume Recovery                530.0 MB   disk1s4
   5:                APFS Volume VM                      5.4 GB     disk1s5

/dev/disk2 (external, physical):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      GUID_partition_scheme                        *960.2 GB   disk2
   1:                        EFI EFI                     209.7 MB   disk2s1
   2:                 Apple_APFS Container disk3         960.0 GB   disk2s2

/dev/disk3 (synthesized):
   #:                       TYPE NAME                    SIZE       IDENTIFIER
   0:      APFS Container Scheme -                      +ERROR      disk3
                                 Physical Store disk2s2

/dev/disk3 のボリューム0のSIZEが ERROR になってる。 「Mac ストレージ 復旧」とかで検索すると復旧できそうなサードパーティーアプリが見つかるものの、怪しい中華製アプリ入れたくないし$100も払いたくないので、もう少し悪足掻きすることにした。

Drat

インターネットを検索していると、DratというOSSのツールに辿り着いた。 github.com

Drat is a tool for analysing and recovering data from APFS (Apple File System) partitions.

DratはAPFSパーティションからデータを復旧するためのツールということで、どうやらこれが使えそう。

実際、

Currently, all of Drat's commands (except modify, which is currently disabled as it is not fit for use) operate in a read-only fashion, as they are intended to be used in situations involving data recovery or data forensics.

とあり、Dratの実行が対象データに変更を加えないことが謳われていることは安心材料にもなった。

Dratの主要機能

Dratには複数のコマンドがあるが、今回使うのは listrecover

list

指定したパスに存在するファイルとディレクトリの一覧を表示する。
たとえば、以下のコマンドでは /dev/disk2s2 コンテナのボリュームIDが 0 のボリュームの / (ルートディレクトリ)に存在するファイルとディレクトリの一覧を表示する。

% sudo ./drat list /dev/disk2s2 0 /
Opening file at `/dev/disk2s2` in read-only mode ... OK.
Simulating a mount of the APFS container.
Validating checksum of block 0x0 ... OK.
Locating the checkpoint descriptor area:
- Its length is 280 blocks.
- It is contiguous.
- The address of its first block is 0x1.
Loading the checkpoint descriptor area into memory ... OK.
Locating the most recent well-formed container superblock in the checkpoint descriptor area:
- It lies at index 169 within the checkpoint descriptor area.
- The corresponding checkpoint starts at index 168 within the checkpoint descriptor area, and spans 2 blocks.

Loading the corresponding checkpoint ... OK.
- There are 9 checkpoint-mappings in this checkpoint.

Reading the Ephemeral objects used by this checkpoint ... OK.
Validating the Ephemeral objects ... OK.
The container superblock states that the container object map has Physical OID 0x13fa2d.
Loading the container object map ... OK.
Validating the container object map ... OK.
Reading the root node of the container object map B-tree ... OK.
Validating the root node of the container object map B-tree ... OK.
The container superblock lists 1 APFS volumes, whose superblocks have the following Virtual OIDs:
- 0x402

Reading the APFS volume superblocks ... OK.
Validating the APFS volume superblocks ... OK.

 Volume list
================
 0: Extreme 900
The volume object map has Physical OID 0x13f96d.
Reading the volume object map ... OK.
Validating the volume object map ... OK.
Reading the root node of the volume object map B-tree ... OK.
Validating the root node of the volume object map B-tree ... OK.
The file-system tree root for this volume has Virtual OID 0x404.
Looking up this Virtual OID in the volume object map ... corresponding block address is 0x140fa9.
Reading ... validating ... OK.

Records for file-system object 0x2 -- `/` --
- INODE
- XATTR || name = purgeable-drecs-fixed
- DIR REC || Dirctry || target ID =     0xa4 || name = TownHallOrgan_SP
- DIR REC || Dirctry || target ID =     0xa1 || name = SanDisk
- DIR REC || Dirctry || target ID =     0x9e || name = JGV1_20180521
- DIR REC || Dirctry || target ID =     0x10 || name = .Spotlight-V100
- DIR REC || Dirctry || target ID =     0xa3 || name = STEAM
- DIR REC || Dirctry || target ID =     0x9b || name = e-onkyo
- DIR REC || Dirctry || target ID =     0xa0 || name = PREMIER SOUND FACTORY 2.0
- DIR REC || Dirctry || target ID =     0x9d || name = Ivory Items
- DIR REC || Dirctry || target ID =  0x2dde4 || name = Logic200531
- DIR REC || Dirctry || target ID =     0xa5 || name = V-Metal Library
- DIR REC || Dirctry || target ID =  0x30dab || name = Logic
- DIR REC || Dirctry || target ID =     0x9c || name = Hummingbird
- DIR REC || Dirctry || target ID =     0xa2 || name = Spitfire
- DIR REC || Dirctry || target ID =  0x2dd90 || name = .Trashes
- DIR REC || Dirctry || target ID =     0x1a || name = .fseventsd
- DIR REC || Dirctry || target ID =  0x2ec46 || name = .DocumentRevisions-V100
- DIR REC || Dirctry || target ID =     0x9f || name = Play Libraries
- DIR REC || Dirctry || target ID =  0x2ec4e || name = .TemporaryItems

recover

指定したパスに存在する単一ファイルを抽出する。
たとえば、以下のコマンドでは /dev/disk2s2 コンテナのボリュームIDが 0 のボリュームの /a/b/c.jpg のパスに存在するJPGファイルをカレントディレクトリに同名ファイルとして出力する。

sudo ./drat recover /dev/disk2s2 0 /a/b/c.jpg >& ./c.jpg

Dratで再帰的にディレクトリの中身をサルベージする

残念ながらDratの recover コマンドには重要な欠点がある。2023/1/1時点の最新版であるv0.1.3では、単一ファイルの復元にのみ対応しており、ディレクトリを対象にした復元ができない点だ。このissueによると、v0.2.0でディレクトリを対象とした復元も実装予定だが、現時点では未対応とのこと。(※注:Dratにはやばいトラップがあり、readthedocsのドキュメントはまだリリースされていないv0.2.0の仕様を前提に書かれている。v0.1.3の使用時には参考にしてはいけない

そこで、指定したパスを再帰的に探索し、ディレクトリの構造を維持した状態でパス直下をすべて recover で復元するスクリプトPythonで実装することにした。以下のGitHubレポジトリで公開している。 github.com

実行方法もREADMEに記載した。