GNU ddrescue menampilkan 100.00%. Proses pertama pemulihan file terstruktur saya menghasilkan 0 useful recovered user files.
Kedua hasil itu berasal dari kegagalan hard drive 4 TB yang sama, dan kontradiksi inilah yang membuat pemulihan ini jauh lebih menarik daripada sekadar “kloning disk yang rusak lalu salin file.” ddrescue telah melakukan tugasnya dengan sangat baik: sekitar 99.999654% dari sumber fisik berhasil disalin. Klon berada di perangkat keras yang sehat. Namun APFS tetap rusak, macOS tidak dapat menyediakan filesystem secara normal, dan rangkaian pemulihan APFS pertama dapat menginventarisasi sebagian besar pohon direktori tetapi gagal membaca isi file biasa.
Pada akhirnya saya berhasil memulihkan setiap pohon folder terpilih yang dapat diinventarisasi dan saya perlukan, tetapi hanya setelah memperlakukan insiden ini sebagai tiga masalah terpisah: pemulihan blok fisik, interpretasi filesystem yang rusak, dan ekstraksi file skala besar dengan validasi serta dukungan resume.
Kegagalan ini lebih dulu berhenti menjadi masalah filesystem
Toshiba 4 TB asli sudah mencapai titik ketika saya tidak lagi mempercayainya untuk aktivitas filesystem normal. Sebagian operasi baca membutuhkan 60–75 detik. Operasi bisa macet. Drive sesekali menghilang dari macOS, mengeluarkan bunyi klik yang terdengar jelas, dan kadang mati sendiri.
Tak lama sebelum kegagalan, saya menulis kira-kira 300 GB data tambahan dan melakukan penggantian nama massal yang memengaruhi sekitar 500.000 file dan direktori. Waktunya membuat beban kerja yang berat pada metadata itu tampak mencurigakan, tetapi saya tidak dapat membuktikan bahwa itulah penyebab kegagalan perangkat keras. Bisa saja beban tersebut hanya cukup menekan drive yang memang sudah tidak sehat sehingga masalahnya terlihat.
Yang dapat saya pastikan adalah perilaku drive tersebut. Ketika penyimpanan mekanis mulai macet, menghilang, dan berbunyi klik, berulang kali menjelajahi direktori adalah abstraksi yang keliru. Traversal direktori dapat memicu lebih banyak operasi baca dan seek. Melakukan mount filesystem dapat memicu pekerjaan metadata. Setiap eksperimen menghabiskan waktu pada satu-satunya komponen yang sisa umur pakainya tidak diketahui.
Karena itu, saya mengubah tujuan dari:
memulihkan file saya
menjadi:
memulihkan sebanyak mungkin sektor yang masih dapat dibaca
Saya menggunakan GNU ddrescue 1.30 dengan mapfile persisten. Ukuran fisik persis drive sumber adalah:
4,000,787,027,968 bytes
Mapfile sangat penting karena sumber tidak cukup stabil untuk disalin sekali jalan. Dengan mapfile, proses penyelamatan dapat bertahan dari stall, koneksi terputus, restart, dan pass berikutnya tanpa melupakan wilayah mana yang sudah berhasil dipulihkan.
Saya juga menghadapi masalah praktis pada path perangkat raw macOS: cakupan penyelamatan yang terlihat dapat menjadi tidak masuk akal alih-alih berhenti pada batas perangkat yang sebenarnya. Karena itu saya membatasi domain penyelamatan ke ukuran fisik yang sudah diketahui di atas. Dalam kasus ini, membatasi input secara eksplisit adalah langkah untuk menjaga kebenaran proses, bukan optimasi kecepatan.
Mengapa ddrescue menampilkan 100.00% bukan berarti file sudah aman
Menjelang akhir pemulihan fisik, ddrescue melaporkan kurang lebih:
domain size: 4000 GB
rescued: 4000 GB
non-tried: 13481 kB
non-trimmed: 327680 B
non-scraped: 0 B
bad-sector: 27136 B
Persentase utamanya adalah:
100.00%
Namun status yang belum terselesaikan masih berjumlah:
13,835,816 bytes
atau kira-kira:
13.84 MB
Dibandingkan dengan sumber berukuran 4.000.787.027.968 byte, bagian yang berhasil dipulihkan kira-kira:
99.999654%
Itu adalah hasil pemulihan blok yang sangat baik. Itu bukan hasil integritas file.
Lokasi byte yang hilang lebih penting daripada jumlah yang tampak pada angka utama. Beberapa megabyte yang hilang dari ruang tak terpakai mungkin tidak menimbulkan dampak yang terlihat. Wilayah kecil yang tidak terbaca di dalam sebuah video dapat merusak satu file. Kehilangan yang jauh lebih kecil pada metadata filesystem dapat membuat banyak extent data yang sebenarnya masih utuh menjadi sulit ditemukan.
Inilah model mental utama yang saya gunakan untuk sisa proses pemulihan:
| Lapisan | Pertanyaan yang dijawab | Hal yang tidak dibuktikan oleh keberhasilan |
|---|---|---|
| Pemulihan blok | Apakah sektor fisik berhasil disalin? | Bahwa APFS dapat merekonstruksi setiap file |
| Pemulihan filesystem | Apakah path, metadata, dan extent dapat diresolusikan? | Bahwa setiap byte yang diekstrak valid |
| Validasi file | Apakah file diterima dengan ukuran atau hash yang diharapkan? | Bahwa tidak pernah ada file yang kini tidak dapat ditemukan |
Saya melakukan beberapa upaya terakhir pada wilayah yang masih belum terbaca. Pada akhirnya upaya itu berhenti menghasilkan data baru yang berguna sementara Toshiba terus berbunyi klik keras. Pada titik itulah saya berhenti menggunakan drive asli sebagai sumber pemulihan aktif.
Mengapa saya membeli dua drive 5 TB untuk memulihkan satu disk 4 TB
Drive baru pertama adalah Seagate Expansion 5 TB dengan kapasitas fisik persis:
5,000,981,077,504 bytes
Saya menulis klon Toshiba tingkat blok ke drive itu. Tata letak sumber menempati kira-kira 4 TB pertama, sehingga tersisa sekitar 1 TB di luar tata letak yang disalin. Saya sengaja membiarkan kapasitas tambahan itu tidak tersentuh.
Saya tidak memperbesar container APFS. Saya tidak mempartisi ulang klon demi kenyamanan. Saya tidak menjalankan perbaikan filesystem pada klon tersebut. Disk itu menjadi klon master.
Lalu saya membeli drive 5 TB kedua. Drive itu baru diformat, dapat ditulis, diuji secara terpisah, dan hanya digunakan untuk output hasil pemulihan.
HDD 4 TB yang gagal
│
│ GNU ddrescue
▼
drive 5 TB #1
klon tingkat blok master
HANYA-BACA
│
│ parsing dan ekstraksi APFS
▼
drive 5 TB #2
file hasil pemulihan
DAPAT-DITULIS
Artinya saya membeli sekitar 10 TB penyimpanan baru secara nominal untuk memulihkan volume yang berisi sekitar 3,26 TB data terpakai. Disk tambahan itu bukan soal kapasitas. Tujuannya adalah mempertahankan satu invariant:
Jika suatu eksperimen salah, saya dapat kembali ke klon master yang sama dan tetap tidak tersentuh.
Memperbaiki, mengubah ukuran, mempartisi ulang, atau menulis output hasil pemulihan ke klon master akan mencampurkan preservasi dengan eksperimen. Menjaga sumber dan tujuan pada drive fisik terpisah membuat kesalahan tetap dapat dipulihkan.
Sebelum mempercayai drive tujuan, saya menjalankan pengujian tulis/baca sekitar 10 GB. Dalam pengujian itu, kecepatannya bertahan di sekitar 144,4 MB/s pada kedua arah. Sampel pembacaan raw dari klon master sekitar 28–49 MB/s dan selama pemeriksaan tersebut tidak mereproduksi pola kegagalan I/O fisik drive asli.
Pada titik itu, sifat masalahnya berubah. Saya tidak lagi men-debug perangkat keras yang gagal. Saya men-debug metadata APFS rusak yang tersimpan pada perangkat keras yang, dalam pengujian saya, berperilaku normal.
Klon APFS dapat dibaca sebagai perangkat tetapi tidak valid sebagai filesystem
Physical store APFS hasil kloning berukuran:
4,000,650,887,168 bytes
Partisi yang relevan dimulai pada sektor:
264192
Dengan sektor 512 byte, offset byte-nya adalah:
135,266,304 bytes
Volume APFS melaporkan sekitar:
3,260,976,717,824 bytes
terpakai.
Pemeriksaan APFS hanya-baca akhirnya mencapai pohon fsroot dan melaporkan:
Checking fsroot tree.
error: (oid 0xe36cb) apfs_root:
btn: dev_read_finish(3828538, 1): Input/output error
fsroot tree is invalid.
Pada titik inilah “ddrescue menyalin hampir seluruh disk” tidak lagi cukup berguna sebagai diagnosis lengkap. Klon raw memang ada. Struktur filesystem di dalamnya tetap tidak konsisten.
Saya sengaja menjaga pemeriksaan filesystem agar tidak melakukan modifikasi. Saya tidak mengubah fsck_apfs -n menjadi operasi perbaikan terhadap satu-satunya klon master berkualitas tinggi yang saya miliki. Perbaikan bisa tepat untuk penyimpanan biasa, tetapi di sini itu akan mengubah bukti yang masih sedang saya coba pahami.
The Sleuth Kit dapat mencantumkan namespace APFS tetapi gagal pada isi file
The Sleuth Kit 4.15.0 adalah rangkaian pemulihan pertama yang membuat klon terlihat menjanjikan. Dengan seluruh perangkat hasil kloning, offset partisi yang diketahui, dan superblock APFS yang telah saya identifikasi, saya dapat menginventarisasi nama direktori nyata dengan fls:
fls \\
-o 264192 \\
-B 4594668 \\
-p \\
/dev/rdiskN
Saya sengaja menggunakan N. Nomor disk macOS berubah setelah reconnect dan reboot, jadi saya tidak menganggap penetapan /dev/disk6 sebelumnya sebagai identitas.
fls dapat menelusuri sebagian besar namespace. Satu pohon besar saja menampilkan sekitar 8.900 direktori. Sesaat, masalah tersulit tampak seperti sudah selesai.
Lalu saya mencoba mengambil isi file.
Contoh kegagalan yang mewakili adalah:
libc++abi: terminating due to uncaught exception
of type std::runtime_error:
could not read APFSBlock
tsk_recover dapat mulai mengekstrak lalu gagal pada sebuah blok APFS. Percobaan icat satu per satu menunjukkan kelas masalah yang sama.
Perbedaan pentingnya adalah:
traversal direktori berfungsi
tidak berarti:
pengambilan isi file berfungsi
Sebuah parser dapat memiliki cukup metadata yang selamat untuk menemukan pathname, tetapi tetap gagal kemudian ketika meresolusikan objek file, metadata extent, atau blok konten yang diperlukan untuk mengembalikan stream byte.
Saya membuat mesin pemulihan massal pertama tahan terhadap kegagalan, tetapi parser-nya tetap tidak cocok untuk kerusakan ini
Respons pertama saya adalah membuat ekstraksi TSK lebih tangguh, alih-alih langsung mengganti parser.
Saya membuat wrapper pemulihan Python di sekitar fls dan icat. Wrapper itu menyimpan state yang tahan lama di SQLite, mencatat kegagalan, mendukung resume, menulis output parsial secara terpisah, dan menurunkan prioritas data pemeliharaan macOS bernilai rendah pada pass pertama.
Saya juga menambahkan aturan “hotspot”: jika empat file berurutan dalam satu direktori gagal dengan pola APFSBlock zero-byte yang sama, skrip berhenti menghabiskan waktu pada sisa cabang tersebut, menundanya, lalu melanjutkan ke tempat lain. Hipotesis kerjanya adalah bahwa sekumpulan kegagalan identik mungkin berbagi satu dependensi metadata yang rusak, alih-alih mewakili ratusan payload yang hancur secara independen.
Orkestrasi yang saya buat berguna. Pembaca APFS yang mendasarinya tidak.
Pada salah satu titik yang saya rekam, database pemulihan berisi:
DEFERRED_HOTSPOT: 30,356 files
FAILED: 486 files
Ke-486 kegagalan itu terbagi menjadi:
409 APFSBlock crashes
75 rc=0 but output-size mismatch
2 other rc=1 failures
Dan pass terstruktur tersebut menghasilkan:
0 useful recovered user files
Ke-75 ketidakcocokan ukuran itu mengungkap bug pada wrapper saya sendiri: beberapa file metadata sistem sebenarnya mengembalikan data, tetapi parser saya mencatat ukuran yang diharapkan sebagai nol. Memperbaiki interpretasi itu penting, tetapi tidak mengubah hasil dominan. File pengguna biasa tetap berakhir dengan nol byte dan could not read APFSBlock.
Saya memilih satu file AVIF kecil sebagai kasus uji yang dapat direproduksi. TSK mengetahui ukuran yang diharapkan:
expected size: 56,309 bytes
recovered: 0 bytes
File itu menjadi jauh lebih berharga daripada pass massal lain yang memakan waktu berjam-jam. Jika pendekatan baru tidak dapat memulihkan satu file uji 56 KB yang secara konsisten gagal, pendekatan itu tidak layak diberi akses ke terabyte data yang tersisa.
Blok disk dan jalur metadata APFS merupakan domain kegagalan yang berbeda
Pada titik ini saya memiliki tiga observasi:
GNU ddrescue:
hampir seluruh sumber fisik berhasil disalin
TSK fls:
banyak path nyata dapat ditemukan
TSK icat:
isi banyak file biasa masih gagal dibaca
Ketiga observasi itu selaras jika rantai lookup dipisahkan secara konseptual:
pathname
↓
record direktori
↓
objek file / metadata inode
↓
pemetaan extent
↓
blok data fisik
Blok data di bagian bawah rantai dapat tetap utuh sementara sebuah tautan yang lebih tinggi pada rantai metadata rusak. Kemungkinan lain adalah dua implementasi APFS menelusuri struktur rusak yang sama dengan cara berbeda.
Saya tidak berhasil mengisolasi satu objek APFS rusak yang menjelaskan semua kegagalan, jadi saya tidak akan menyebutnya sebagai akar masalah yang sudah terkonfirmasi. Yang memang didukung oleh bukti adalah eksperimen yang jauh lebih berguna: biarkan byte hasil kloning tetap tidak berubah dan ganti parser.
142 checkpoint APFS tidak serta-merta menjadi jalur rollback instan
Pemindaian raw hanya-baca pada area descriptor checkpoint APFS menemukan 142 kandidat superblock checkpoint NXSB, dengan transaction ID mulai dari:
223133
hingga:
222992
Pertanyaan yang jelas adalah apakah checkpoint yang lebih lama merujuk ke pohon metadata yang lebih sehat.
Saya membangun apfs-fuse dan mencoba transaction ID checkpoint yang berbeda melalui fuse-t. Checkpoint terbaru macet. XID yang lebih lama melakukan hal yang sama. Saya menambahkan timeout keras per checkpoint, dan lebih dari 50 percobaan berturut-turut gagal menghasilkan mount yang dapat digunakan. Saya juga mencoba backend fuse-t NFS maupun SMB.
Itu tidak membuktikan bahwa semua checkpoint rusak. Eksperimen ini bergantung pada checkpoint, apfs-fuse, fuse-t, perilaku perangkat macOS, dan backend mount. Kegagalan pada bagian mana pun dari stack tersebut dapat menghasilkan gejala yang sama.
Parser lain yang saya coba kemudian melaporkan nol snapshot APFS. Hal itu juga menegaskan perbedaan yang harus terus saya jaga: state transaksi checkpoint tersebut bukan hal yang sama dengan snapshot APFS yang terlihat oleh pengguna.
Parser yang macet pada --help tidak dapat mendiagnosis disk saya
Saya juga membangun go-apfs-v2. Build berhasil, tetapi bahkan:
apfs --help
macet dan harus dihentikan oleh timeout.
Inspeksi blok minimal terhadap path perangkat raw maupun buffered juga macet. Saya melakukan pengujian berbatas serupa dengan apfsutil; kedua bentuk perangkat timeout setelah sekitar 15 detik.
Kegagalan itu tetap berguna karena mencegah saya menarik kesimpulan yang salah. Tool yang bahkan tidak dapat menyelesaikan jalur help-nya sendiri secara andal adalah bukti yang lemah untuk menilai apakah file yang rusak masih dapat dipulihkan.
Aturan saya menjadi:
Validasi tool pemulihan sebelum menjadikan kegagalan tool tersebut sebagai bukti tentang kondisi data.
Mencegah macOS melakukan auto-mount pada klon menghilangkan satu sumber risiko lain
macOS sendiri merupakan komponen lain yang terus bergerak. Saya ingin drive tujuan yang sehat di-mount secara normal, tetapi saya tidak ingin Disk Arbitration otomatis mencoba me-mount klon APFS yang rusak setiap kali penyimpanan disambungkan kembali.
Urutan aman yang akhirnya saya gunakan adalah:
- Sambungkan dan mount tujuan pemulihan yang sehat.
- Verifikasi identitas volume dan ruang kosongnya.
- Bekukan
diskarbitrationd. - Pastikan tidak ada proses
mount_apfsyang aktif. - Sambungkan klon master.
- Identifikasi klon master secara dinamis berdasarkan ukuran fisik yang diketahui dan identitas APFS.
- Pastikan klon master tidak sedang di-mount.
- Lakukan pekerjaan pemulihan hanya-baca.
- Saat berhenti, simpan state, lanjutkan kembali Disk Arbitration, lalu matikan komputer atau eject secara normal.
Perintah yang benar-benar saya gunakan untuk membekukan Disk Arbitration adalah:
sudo kill -STOP "$(pgrep -x diskarbitrationd)"
Saya memverifikasi bahwa state proses berisi T, dan saya memeriksa proses mount_apfs liar sebelum melanjutkan.
Pelajaran keselamatan yang penting bukanlah nomor disk tertentu. Justru sebaliknya: jangan pernah percaya nomor disk kemarin. Setelah reboot, /dev/disk6 yang sebelumnya dipakai dapat menunjuk ke perangkat fisik yang berbeda. Saya menggunakan UUID APFS dan ukuran fisik yang diketahui sebagai identitas, lalu menurunkan path perangkat saat ini dari keduanya.
libfsapfs memulihkan file yang sama yang dikembalikan TSK sebagai nol byte
Terobosan datang dari libfsapfs, implementasi APFS yang berbeda.
Saya membangunnya dari source di macOS. Binary fsapfsinfo yang dihasilkan mengidentifikasi dirinya sebagai:
fsapfsinfo 20260923
Kemudian muncul perbedaan penting pada antarmuka perangkat macOS.
Partisi character-device raw:
/dev/rdiskNs2
dengan cepat gagal karena pembacaan invalid-argument di dekat offset 4096.
Bentuk block-device buffered:
/dev/diskNs2
berhasil.
Klon fisik yang sama. Partisi APFS yang sama. Antarmuka perangkat macOS yang berbeda.
fsapfsinfo membuka container dan menemukan satu volume. Lalu saya menguji tepat file berukuran 56.309 byte yang gagal diekstrak oleh TSK.
TSK menghasilkan:
expected: 56,309
recovered: 0
APFSBlock failure
Melalui libfsapfs, entri file tersebut menghasilkan:
size: 56,309
MD5: c6f56db33eafc1de0f52a035bc255dc7
RC: 0
Inilah hasil pertama yang benar-benar mengubah diagnosis. Byte hasil kloning tidak berubah. File uji tidak berubah. APFS yang rusak tidak diperbaiki.
Yang berubah adalah implementasi APFS.
Setidaknya, kini saya tahu bahwa file pengguna nyata yang melalui TSK tampak tidak dapat dipulihkan ternyata masih dapat dijangkau cukup jauh oleh libfsapfs untuk membaca seluruh isinya dan menghitung digest.
Saya mengekstrak satu file yang sudah diketahui gagal sebelum mempercayakan terabyte data kepada libfsapfs
Digest yang berhasil belum cukup bagi saya untuk memulai pemulihan multi-terabyte. Saya ingin byte sebenarnya tertulis di disk tujuan.
Alih-alih menebak-nebak API C libfsapfs, saya memeriksa source library dan mengikuti jalur baca yang sudah digunakan fsapfsinfo saat menghitung digest. Lalu saya membuat extractor kecil hanya-baca untuk satu file uji yang sudah diketahui.
Extractor menulis tepat:
56,309 bytes
ke disk pemulihan terpisah. MD5 file hasil ekstraksi adalah:
c6f56db33eafc1de0f52a035bc255dc7
Nilai itu cocok dengan digest sebelumnya.
Baru setelah itu saya memperbesar skala pendekatan tersebut. Uji satu file telah membuktikan tiga hal terpisah: pathname dapat diresolusikan, jumlah byte lengkap sesuai ekspektasi dapat diekstrak, dan byte yang diekstrak menghasilkan digest yang sama dengan pembacaan isi penuh sebelumnya.
Masalah pemulihan massal terutama soal membatasi dampak kegagalan dan mendukung resume
Setelah libfsapfs dapat memulihkan file yang gagal dipulihkan TSK, masalah tersulit kembali berubah. Saya membutuhkan sistem yang dapat memproses pohon direktori sangat besar tanpa satu cabang rusak, satu reboot, atau satu Ctrl+C mengubah seluruh pekerjaan menjadi restart dari nol.
Karena itu, pipeline pemulihan massal menggunakan beberapa invariant yang ketat:
- Klon master dibuka hanya-baca.
- Output hasil pemulihan ditulis hanya ke disk 5 TB kedua.
- Nama direktori dan file dipertahankan.
- Progress disimpan di SQLite agar tetap bertahan setelah proses keluar dan komputer reboot.
- Setiap file lebih dulu ditulis ke path sementara.
- File sementara baru diganti namanya ke path final setelah seluruh ukuran yang diharapkan selesai ditulis.
- File yang sudah ada dengan ukuran sesuai ekspektasi dapat dikenali saat resume.
- Pekerjaan yang gagal atau bermasalah dipisahkan dari pekerjaan yang sudah selesai.
- Ruang kosong diperiksa dan cadangan ruang aman dipertahankan.
- Artefak pengembangan yang dapat dibuat ulang serta metadata sistem bernilai rendah dapat dilewati atau diturunkan prioritasnya.
Satu perubahan performa langsung terasa penting: saya berhenti membuka ulang container APFS secara terpisah untuk setiap file.
Fast path memproses satu direktori setiap kali. Worker membuka sumber, menginventarisasi direktori itu, memulihkan file regular langsung di dalamnya, lalu mengembalikan direktori anak ke antrean. Jika suatu direktori gagal atau timeout, controller menandainya sebagai DEFERRED lalu melanjutkan, alih-alih memblokir pass global.
Setelah tidak ada pekerjaan pending normal yang tersisa, direktori yang ditunda dikunjungi kembali melalui fallback path yang lebih lambat dengan pekerjaan per file yang lebih terisolasi. Kegagalan yang masih tersisa kemudian dapat dicoba ulang secara independen.
temukan direktori
↓
pulihkan file langsung
↓
verifikasi ukuran yang diharapkan
↓
commit state persisten
↓
antrekan direktori anak
↓
tunda kegagalan lokal
↓
lanjutkan secara global
↓
fallback dan coba ulang nanti
Arsitektur ini jauh lebih sesuai dengan pola kegagalan sebenarnya dibandingkan satu perintah rekursif raksasa. Kerusakannya tidak seragam, jadi sistem pemulihan juga tidak seharusnya memaksakan progress yang seragam.
Mengapa saya memakai pemeriksaan ukuran yang diharapkan dan atomic rename alih-alih melakukan hash pada jutaan file
MD5 berguna pada pembuktian satu file karena saya membutuhkan bukti kuat bahwa parser benar-benar membaca isi lengkap file yang tidak dapat dibaca oleh TSK.
Membaca ulang seluruh byte hasil pemulihan untuk kedua kalinya hanya demi menghitung hash jutaan file akan menambah I/O dalam jumlah besar. Untuk pass ekstraksi utama, saya memakai invariant yang berbeda.
Untuk setiap file regular, metadata APFS menyediakan ukuran yang diharapkan. Worker menulis ke file sementara dan baru mempromosikannya ke pathname final setelah pembacaan lengkap cocok dengan ukuran yang diharapkan tersebut.
Artinya, interupsi tidak seharusnya meninggalkan file pendek yang menyamar dengan nama final.
Kecocokan ukuran bukan bukti integritas kriptografis. Saya tidak memperlakukannya sebagai bukti seperti itu. Namun verifikasi ukuran yang diharapkan ditambah atomic rename merupakan batas kebenaran yang praktis untuk pass bervolume tinggi, sementara hash terarah tetap berguna untuk sampel dan kasus kegagalan yang sudah diketahui.
SQLite membuat reboot menjadi hal biasa, bukan bencana
Pemulihan berlangsung cukup lama sehingga saya perlu mematikan komputer dan melanjutkannya kemudian. Kebutuhan itu mengubah desain dari sekadar “skrip” menjadi “workflow yang dapat dipulihkan”.
Ctrl+C tidak sekadar meninggalkan child process yang sedang aktif. Controller menangkap interupsi, menghentikan worker, mengembalikan direktori aktif ke state yang dapat dipulihkan, melakukan commit SQLite, lalu keluar.
Secara konseptual, penghentian yang bersih tampak seperti:
CURRENT DIRECTORY -> PENDING
CTRL+C: RECOVERY STOPPED SAFELY
STATE SAVED. RUN THE SAME COMMAND TO RESUME.
Setelah reboot, saya mengulangi pemeriksaan identitas disk dan menjalankan perintah pemulihan yang sama. Database state melanjutkan antrean yang sudah ada.
Salah satu restart berikutnya dimulai dengan:
DEFERRED: 1
DONE: 73,015
PENDING: 7,254
Itu jauh lebih bermakna daripada progress bar generik. Angka tersebut menunjukkan bahwa puluhan ribu unit direktori yang sudah selesai tetap tersimpan setelah reboot dan bahwa antrean yang tersisa tercatat secara eksplisit.
Pada resume yang lebih awal, direktori yang terinterupsi pada sesi sebelumnya diambil lagi. File yang sudah ada dengan ukuran yang diharapkan dikenali, sehingga hanya pekerjaan yang belum ada yang perlu ditulis. Itulah perilaku yang saya inginkan: memulai kembali pemulihan harus menjadi rutinitas, bukan sesuatu yang menakutkan.
326.799 file menjadi bukti pertama bahwa metode ini dapat diskalakan
Sebelum memperluas extractor baru ke seluruh data terpilih, saya menggunakan satu pohon prioritas besar sebagai target validasi massal.
State yang selesai melaporkan:
directories completed: 8,940
new files written: 302,541
existing/resumed files: 24,258
recorded failed files: 0
Tujuan berisi:
326,799 files
145,039,215,948 bytes
atau sekitar:
135.08 GiB
Jumlah file tepat saling cocok:
302,541 + 24,258 = 326,799
Tidak ada file .partial.* yang tersisa pada pohon yang sudah selesai itu.
File uji 56.309 byte telah membuktikan bahwa parser dapat berhasil ketika TSK gagal. Pemulihan 326.799 file membuktikan bahwa pendekatan yang sama dapat bertahan pada hierarki nyata yang besar dengan logika resume, deteksi file yang sudah ada, dan tanpa kegagalan file yang tercatat pada pass yang selesai tersebut.
Pemulihan yang lebih besar melampaui 1,6 juta file baru sebelum selesai
Setelah pohon prioritas selesai dengan bersih, saya memperluas pemulihan ke data top-level terpilih yang tersisa.
Pada salah satu penghentian aman yang disengaja, SQLite melaporkan:
DONE directories: 39,015
PENDING directories: 17,017
DEFERRED directories: 1
new files written: 1,636,305
new bytes written: 902,715,716,335
Nilainya kira-kira:
840.72 GiB
data baru yang tercatat telah ditulis oleh pass direktori pada titik tersebut.
Satu direktori DEFERRED itu tidak berarti data hilang. Itu berarti fast path sengaja berhenti membiarkan masalah lokal tersebut menunda pekerjaan yang tidak terkait. Fase fallback memang dibuat khusus untuk mengunjungi kembali kasus semacam itu nanti.
Sesi berikutnya melanjutkan dari database yang sama. Jumlah yang selesai bertambah dan antrean pending berkurang. Pada akhirnya, saya memulihkan setiap pohon folder terpilih yang dapat diinventarisasi dan saya perlukan.
Apa yang secara jujur dapat saya klaim tentang hasil pemulihan akhir
Saya tidak akan menggambarkan hasilnya sebagai “setiap byte berhasil dipulihkan.” Bukti yang ada tidak mendukung klaim itu.
Map ddrescue asli masih memuat sekitar 13,84 MB yang belum terkonfirmasi berhasil disalin. Saya juga tidak dapat membuktikan bahwa tidak ada objek filesystem yang menjadi sama sekali tidak dapat ditemukan karena metadata yang diperlukan untuk menginventarisasinya termasuk di antara wilayah yang rusak.
Keterbatasan ini penting karena pemulihan terstruktur dapat membuktikan bahwa objek yang dapat diinventarisasi berhasil diekstrak; proses tersebut tidak dapat membuktikan bahwa secara historis tidak pernah ada objek yang kini tidak lagi dapat diungkap oleh namespace yang rusak.
Pernyataan akhir terkuat yang dapat saya buat lebih sempit:
Setiap pohon folder terpilih yang dapat diinventarisasi dan saya perlukan berhasil dipulihkan melalui proses pemulihan terstruktur.
Saya tidak perlu memperbaiki klon master secara in-place. Saya tidak perlu menggunakan kembali Toshiba asli yang berbunyi klik untuk ekstraksi massal. Saya juga tidak memerlukan pass carving ala PhotoRec pada seluruh disk yang akan mengorbankan struktur direktori dan nama file.
Tool yang gagal tetap memberikan bukti yang berguna
Jika dilihat kembali, jalur yang berhasil terdengar sederhana:
klon ddrescue
↓
libfsapfs
↓
extractor yang dapat dilanjutkan
↓
file hasil pemulihan
Namun saat investigasi berlangsung, rasanya tidak sesederhana itu; menghapus pendekatan yang gagal juga akan menghilangkan banyak pelajaran engineering yang berguna.
TSK mengajarkan saya bahwa traversal namespace APFS dan pengambilan isi file adalah dua mode kegagalan yang berbeda.
Bug pada wrapper pertama saya mengajarkan bahwa klasifikasi error milik skrip pemulihan sendiri bukan ground truth.
Mekanisme hotspot mengajarkan saya untuk mengisolasi kegagalan lokal alih-alih membiarkannya menghentikan progress global.
Eksperimen checkpoint mengajarkan saya agar tidak mencampuradukkan checkpoint transaksi APFS dengan snapshot.
Percobaan FUSE mengajarkan bahwa mount yang gagal dapat melibatkan beberapa lapisan selain data filesystem itu sendiri.
Pembaca APFS yang macet pada --help mengajarkan saya untuk memvalidasi tool sebelum menafsirkan diagnosisnya.
Perilaku /dev/rdiskNs2 dibandingkan /dev/diskNs2 mengajarkan bahwa jalur I/O sistem operasi dapat mengubah perilaku parser meskipun disk dasarnya identik.
Dan arsitektur dua drive memberi saya kebebasan untuk membuat kesalahan di tempat lain tanpa mengubah klon master.
Workflow pemulihan yang akan saya gunakan lagi
- Hentikan aktivitas filesystem biasa pada media penyimpanan yang mengalami kegagalan mekanis. Jika pembacaan macet, perangkat menghilang, atau berbunyi klik, saya akan memprioritaskan klon tingkat blok yang dapat dilanjutkan daripada menjelajah lewat Finder.
- Gunakan GNU ddrescue dengan mapfile persisten dan domain penyelamatan yang sudah diverifikasi. Mapfile mempertahankan progress; ukuran sumber yang diketahui mencegah kebingungan ukuran perangkat ikut menjadi bagian dari masalah pemulihan.
- Pertahankan satu klon master dalam mode hanya-baca. Jangan memperbaikinya, mengubah ukurannya, mempartisi ulang, atau menggunakannya sebagai penyimpanan file hasil pemulihan.
- Tulis file hasil pemulihan ke disk fisik kedua. Preservasi sumber dan penyimpanan output adalah dua pekerjaan berbeda.
- Diagnosis filesystem hasil kloning lebih dulu dalam mode hanya-baca. Klon fisik sehat yang mengandung metadata APFS rusak adalah masalah pemulihan logis, bukan masalah yang sama dengan disk sumber yang berbunyi klik.
- Pilih satu file gagal yang dapat direproduksi sebagai uji parser. Satu file 56 KB yang sudah diketahui gagal memberi saya lebih banyak informasi tentang parser alternatif daripada berjam-jam ekstraksi massal secara buta.
- Validasi parser itu sendiri. Jika sebuah tool macet sebelum benar-benar membaca sumber secara bermakna, jangan menafsirkannya sebagai bukti bahwa data sudah hilang.
- Jangan berasumsi bahwa satu implementasi APFS menentukan apakah data dapat dipulihkan. TSK dan
libfsapfsberperilaku sangat berbeda pada byte hasil kloning yang sama. - Identifikasi disk dengan properti yang stabil, bukan nomor perangkat sementara. UUID filesystem dan ukuran fisik yang diketahui lebih aman daripada
/dev/diskNmilik kemarin. - Buat ekstraksi jangka panjang dapat dilanjutkan sejak awal. State persisten, file sementara, atomic rename, pekerjaan yang ditunda, retry terbatas, dan penanganan shutdown bersih merupakan bagian dari kebenaran pada skala ini.
- Pisahkan level validasi. Persentase ddrescue, pathname yang terlihat, kecocokan ukuran yang diharapkan, dan hash konten membuktikan hal yang berbeda.
Angka yang tampak seperti garis akhir ternyata hanya akhir tahap pertama
Angka yang paling menyesatkan dalam seluruh proses pemulihan tetap:
100.00%
Angka itu tampak seperti jawaban atas pertanyaan “Apakah disk saya berhasil diselamatkan?”
Padahal yang sebenarnya dijawab jauh lebih sempit:
Seberapa besar bagian domain penyelamatan fisik yang berhasil disalin ddrescue?
Angka itu tidak menjawab apakah APFS dapat merekonstruksi namespace. Tidak menjawab apakah satu parser dapat menelusuri metadata rusak yang ditolak parser lain. Tidak menjawab apakah file hasil pemulihan memiliki panjang yang diharapkan. Dan tidak menjawab apakah ekstraksi jutaan file dapat bertahan dari error dan reboot tanpa merusak state-nya sendiri.
Saya membutuhkan bukti terpisah untuk setiap lapisan.
Pemulihan fisik berhasil lebih dulu. Filesystem tetap rusak. Parser pertama dapat menampilkan nama tetapi gagal pada isi banyak file. Implementasi APFS yang berbeda berhasil membaca file uji yang sama. Bukti itu kemudian menjadi extractor satu file, extractor berkembang menjadi mesin pemulihan yang dapat dilanjutkan, dan mesin tersebut akhirnya memulihkan pohon folder terpilih yang saya perlukan ke disk kedua sementara klon master tetap tidak tersentuh.
Hard drive asli tidak pernah kembali sehat. APFS tidak tiba-tiba memperbaiki dirinya sendiri. Yang berubah adalah model pemulihannya.
Pemulihan blok, pemulihan filesystem, dan validasi file adalah tahap engineering yang terpisah. Memperlakukan semuanya sebagai satu masalah membuat situasinya tampak nyaris tanpa harapan. Memisahkannya membuat masalah itu dapat ditangani.