Bloga dön
7 Ekim 2026Sergei Solod19 dk okuma

GNU ddrescue 4 TB'lık Bir Diski 99.999654% Kopyaladıktan Sonra Bozuk Bir APFS Klonundan Dosyaları Nasıl Kurtardım

GNU ddrescue arızalanmakta olan 4 TB'lık HDD'nin 99.999654%'ünü kopyaladı, ancak APFS klonu dosya sistemi kontrollerinde yine başarısız oldu ve The Sleuth Kit yolları listeleyebilmesine rağmen sıfır baytlık dosyalar döndürdü. Salt okunur bir ana klonu koruyup libfsapfs'e geçerek ve devam ettirilebilir bir çıkarma hattı oluşturarak seçtiğim dizin ağaçlarını kurtardım.

APFSGNU ddrescueVeri KurtarmalibfsapfsmacOS

GNU ddrescue 100.00% dedi. İlk yapılandırılmış dosya kurtarma geçişim 0 useful recovered user files sonucu verdi.

Bu iki sonuç aynı 4 TB sabit disk arızasından çıktı ve bu çelişki, kurtarma sürecini “bozuk diski klonla ve dosyaları kopyala” yaklaşımından çok daha ilginç hâle getirdi. ddrescue işini olağanüstü iyi yapmıştı: fiziksel kaynağın yaklaşık %99.999654'ünü kopyalamıştı. Klon sağlıklı donanımdaydı. Buna rağmen APFS hâlâ bozuktu, macOS dosya sistemini normal şekilde kullanıma sunmuyordu ve ilk APFS kurtarma yığını dizin ağacının büyük bölümlerini listeleyebilse de sıradan dosyaların içeriklerini okuyamıyordu.

Sonunda ihtiyaç duyduğum seçilmiş ve listelenebilmiş tüm klasör ağaçlarını kurtardım; ancak bunu, olayı üç ayrı sorun olarak ele aldıktan sonra yapabildim: fiziksel blok kurtarma, hasarlı dosya sistemini yorumlama ve doğrulama ile devam ettirme desteğine sahip büyük ölçekli dosya çıkarma.

Arıza önce bir dosya sistemi sorunu olmaktan çıktı

Orijinal 4 TB Toshiba, artık normal dosya sistemi etkinlikleri konusunda güvenemediğim bir noktaya gelmişti. Bazı okumalar 60–75 saniye sürüyordu. İşlemler takılı kalabiliyordu. Disk zaman zaman macOS'tan kayboluyor, duyulabilir şekilde tıklıyor ve bazen kendi kendine kapanıyordu.

Arızadan kısa süre önce yaklaşık 300 GB ek veri yazmış ve yaklaşık 500,000 dosya ile dizini etkileyen toplu bir yeniden adlandırma işlemi yapmıştım. Zamanlama, meta veri ağırlıklı bu iş yükünü şüpheli gösteriyordu; ancak donanım arızasına bunun neden olduğunu kanıtlayamam. Zaten sağlıksız olan diski sorunu görünür hâle getirecek kadar zorlamış da olabilir.

Kesin olarak belirleyebildiğim şey diskin davranışıydı. Mekanik depolama takılmaya, kaybolmaya ve tıklamaya başladığında dizinleri tekrar tekrar gezinmek yanlış soyutlama seviyesidir. Dizinlerde gezinmek daha fazla okuma ve kafa hareketi tetikleyebilir. Bir dosya sistemini bağlamak meta veri işlemlerini tetikleyebilir. Her deney, ne kadar ömrü kaldığını bilmediğiniz tek bileşenin zamanını tüketir.

Bu yüzden hedefimi şundan:

recover my files

şuna çevirdim:

recover as many readable sectors as possible

Kalıcı bir mapfile ile GNU ddrescue 1.30 kullandım. Kaynak diskin tam fiziksel boyutu şuydu:

4,000,787,027,968 bytes

Mapfile kritik önemdeydi; çünkü kaynak tek seferlik bir kopyaya yetecek kadar kararlı değildi. Kurtarma işleminin hangi bölgelerin daha önce kurtarıldığını unutmadan takılmalardan, bağlantı kopmalarından, yeniden başlatmalardan ve sonraki geçişlerden sağ çıkmasını sağladı.

Ham macOS aygıt yoluyla ilgili pratik bir sorun da yaşadım: görünen kurtarma kapsamı gerçek aygıt sınırında bitmek yerine anlamsız bir değere uzayabiliyordu. Bu yüzden kurtarma alanını yukarıdaki bilinen fiziksel boyutla sınırlandırdım. Bu durumda girdiyi açıkça sınırlamak bir hız optimizasyonu değil, doğruluk önlemiydi.

ddrescue'nun 100.00% göstermesi dosyaların güvende olduğu anlamına gelmiyordu

Fiziksel kurtarmanın sonlarına doğru ddrescue yaklaşık olarak şunu raporladı:

domain size:  4000 GB
rescued:      4000 GB
non-tried:   13481 kB
non-trimmed: 327680 B
non-scraped:      0 B
bad-sector:   27136 B

Öne çıkan yüzde şuydu:

100.00%

Ancak çözümlenmemiş durumların toplamı hâlâ şuydu:

13,835,816 bytes

yani yaklaşık:

13.84 MB

4,000,787,027,968 baytlık kaynakta kurtarılan pay yaklaşık olarak şuydu:

99.999654%

Bu, blok kurtarma açısından mükemmel bir sonuçtur. Dosya bütünlüğü açısından bir sonuç değildir.

Eksik baytların nerede olduğu, başlıktaki toplam miktardan daha önemlidir. Kullanılmayan alandan kaybolan birkaç megabayt görünür hiçbir şeyi etkilemeyebilir. Bir videonun içindeki küçük bir okunamayan bölge tek bir dosyayı bozabilir. Dosya sistemi meta verisindeki çok daha küçük bir kayıp ise normalde sağlam olan birçok veri uzantısının bulunmasını zorlaştırabilir.

Kurtarmanın geri kalanında kullandığım temel zihinsel model bu oldu:

KatmanYanıtladığı soruBaşarı neyi kanıtlamaz
Blok kurtarmaFiziksel sektörler kopyalandı mı?APFS'nin her dosyayı yeniden oluşturabildiğini
Dosya sistemi kurtarmaYollar, meta veriler ve uzantılar çözümlenebiliyor mu?Çıkarılan her baytın geçerli olduğunu
Dosya doğrulamaDosya beklenen boyut veya hash ile geldi mi?Keşfedilemeyen hiçbir dosyanın hiç var olmadığını

Kalan okunamayan bölgelere karşı birkaç son deneme yaptım. Toshiba yoğun biçimde tıklarken bu denemeler sonunda yararlı yeni okumalar üretmeyi bıraktı. Orijinal diski aktif kurtarma kaynağı olarak kullanmayı o noktada bıraktım.

Bir 4 TB diski kurtarmak için neden iki adet 5 TB disk aldım

İlk yeni disk, tam fiziksel kapasitesi şu olan 5 TB Seagate Expansion'dı:

5,000,981,077,504 bytes

Toshiba'nın blok düzeyindeki klonunu buna yazdım. Kaynak düzeni kabaca ilk 4 TB'ı kaplıyor ve kopyalanan düzenin ötesinde yaklaşık 1 TB alan bırakıyordu. Bu ek kapasiteye bilinçli olarak dokunmadım.

APFS kapsayıcısını büyütmedim. Kolaylık olsun diye klonu yeniden bölümlemedim. Üzerinde dosya sistemi onarımı çalıştırmadım. Bu disk ana klon oldu.

Ardından ikinci bir 5 TB disk aldım. Yeni biçimlendirilmişti, yazılabilirdi, bağımsız olarak test edilmişti ve yalnızca kurtarılan çıktı için kullanıldı.

failing 4 TB HDD
        │
        │ GNU ddrescue
        ▼
5 TB drive #1
master block-level clone
READ-ONLY
        │
        │ APFS parsing and extraction
        ▼
5 TB drive #2
recovered files
WRITABLE

Bu, yaklaşık 3.26 TB kullanılmış veri içeren bir birimi kurtarmak için nominal olarak yaklaşık 10 TB yeni depolama satın almak anlamına geliyordu. Ek disk kapasite için değildi. Bir değişmezi korumak içindi:

If an experiment is wrong, I can return to the same untouched master clone.

Ana klonu onarmak, yeniden boyutlandırmak, yeniden bölümlemek veya kurtarılan çıktıyı üzerine yazmak, korumayı deney yapmayla iç içe geçirirdi. Kaynak ve hedefi ayrı fiziksel disklerde tutmak hataları geri alınabilir hâle getirdi.

Hedefe güvenmeden önce yaklaşık 10 GB'lık bir yazma/okuma testi yaptım. Bu testte her iki yönde de yaklaşık 144.4 MB/s sürdürülebilir hız gördüm. Ana klondan örnek ham okumalar yaklaşık 28–49 MB/s idi ve bu kontroller sırasında orijinal diskin fiziksel G/Ç arıza modelini yeniden üretmedi.

Bu noktada sorun değişmişti. Artık arızalanan donanımın hatasını ayıklamıyordum. Testlerimde normal davranan donanım üzerinde korunmuş hasarlı APFS meta verisinin hatasını ayıklıyordum.

APFS klonu aygıt olarak okunabiliyordu ama dosya sistemi olarak geçersizdi

Klonlanmış APFS fiziksel deposu şuydu:

4,000,650,887,168 bytes

İlgili bölüm şu sektörde başlıyordu:

264192

512 baytlık sektörlerle bu, şu bayt ofsetine karşılık gelir:

135,266,304 bytes

APFS birimi yaklaşık olarak şu kadar alanın kullanıldığını bildiriyordu:

3,260,976,717,824 bytes

.

Salt okunur bir APFS denetimi sonunda fsroot ağacına ulaştı ve şunu raporladı:

Checking fsroot tree.
error: (oid 0xe36cb) apfs_root:
btn: dev_read_finish(3828538, 1): Input/output error
fsroot tree is invalid.

“ddrescue diskin neredeyse tamamını kopyaladı” ifadesinin tam bir teşhis olarak işe yaramayı bıraktığı nokta buydu. Ham klon mevcuttu. İçindeki dosya sistemi yapısı ise hâlâ tutarsızdı.

Dosya sistemi kontrolünü bilinçli olarak değişiklik yapmayan biçimde tuttum. Elimdeki tek yüksek kaliteli ana klonda fsck_apfs -n komutunu bir onarım işlemine dönüştürmedim. Normal depolamada onarım uygun olabilir; ancak burada hâlâ anlamaya çalıştığım kanıtı değiştirmiş olurdu.

The Sleuth Kit APFS ad alanını listeleyebildi ama dosya içeriklerinde başarısız oldu

The Sleuth Kit 4.15.0, klonu umut verici gösteren ilk kurtarma yığınıydı. Klonlanmış aygıtın tamamını, bilinen bölüm ofsetini ve belirlediğim APFS süper bloğunu kullanarak fls ile gerçek dizin adlarını listeleyebildim:

fls \\
  -o 264192 \\
  -B 4594668 \\
  -p \\
  /dev/rdiskN

N harfini bilinçli olarak kullanıyorum. macOS disk numaraları yeniden bağlama ve yeniden başlatma sonrasında değiştiği için önceki bir /dev/disk6 atamasını kimlik olarak kabul etmedim.

fls ad alanının önemli bölümlerinde gezinebiliyordu. Yalnızca büyük bir ağaç yaklaşık 8,900 dizin ortaya çıkardı. Kısa bir an için en zor kısmın çözülmüş gibi görünmesini sağladı.

Ardından dosya içeriklerini almaya çalıştım.

Temsili bir hata şuydu:

libc++abi: terminating due to uncaught exception
of type std::runtime_error:
could not read APFSBlock

tsk_recover çıkarmaya başlayıp daha sonra bir APFS bloğunda hata verebiliyordu. Tek tek icat denemeleri de aynı tür sorunu gösterdi.

Önemli ayrım şuydu:

directory traversal works

şunu gerektirmiyordu:

file content retrieval works

Bir ayrıştırıcı, bir yol adını keşfetmeye yetecek kadar sağlam meta veriye sahip olabilir; ancak dosya nesnesini, uzantı meta verisini veya bayt akışını döndürmek için gereken içerik bloklarını çözümlerken daha sonra yine başarısız olabilir.

İlk toplu kurtarma motorunu hatalara dayanıklı yaptım ama ayrıştırıcı bu hasar için yine de yanlıştı

İlk tepkim, ayrıştırıcıyı hemen değiştirmek yerine TSK ile çıkarma işlemini daha dayanıklı hâle getirmek oldu.

fls ve icat etrafında Python ile bir kurtarma sarmalayıcısı oluşturdum. Kalıcı durumu SQLite'ta tutuyor, hataları kaydediyor, devam ettirmeyi destekliyor, kısmi çıktıyı ayrı yazıyor ve ilk geçişte düşük değerli macOS bakım verilerini düşük önceliğe alıyordu.

Ayrıca bir “hotspot” kuralı ekledim: aynı dizindeki art arda dört dosya aynı sıfır baytlık APFSBlock modeliyle başarısız olursa betik dalın geri kalanına zaman harcamayı bırakıyor, onu erteliyor ve başka yerde devam ediyordu. Çalışma hipotezim, aynı türdeki bir hata kümesinin yüzlerce bağımsız olarak yok olmuş veri içeriği yerine tek bir hasarlı meta veri bağımlılığını paylaşabileceğiydi.

Orkestrasyon yararlıydı. Alttaki APFS okuyucu değildi.

Kaydedilmiş bir anda kurtarma veritabanında şunlar vardı:

DEFERRED_HOTSPOT: 30,356 files
FAILED:              486 files

486 başarısızlığın dağılımı şöyleydi:

409  APFSBlock crashes
75   rc=0 but output-size mismatch
2    other rc=1 failures

Ve yapılandırılmış geçiş şu sonucu üretmişti:

0 useful recovered user files

75 boyut uyuşmazlığı kendi sarmalayıcımdaki bir hatayı ortaya çıkardı: bazı sistem meta veri dosyaları veri döndürmüştü ama ayrıştırıcım beklenen boyutu sıfır olarak kaydetmişti. Bu yorumu düzeltmek önemliydi, ancak baskın sonucu değiştirmedi. Sıradan kullanıcı dosyaları hâlâ could not read APFSBlock ile sıfır baytta bitiyordu.

Tekrarlanabilir bir test vakası olarak küçük bir AVIF dosyası seçtim. TSK beklenen boyutunu biliyordu:

expected size: 56,309 bytes
recovered:             0 bytes

Bu dosya, saatler süren bir başka toplu geçişten çok daha değerli hâle geldi. Yeni bir yaklaşım sürekli başarısız olan 56 KB'lık tek bir test dosyasını kurtaramıyorsa kalan terabaytlara erişmeyi hak etmiyordu.

Disk blokları ile APFS meta veri yolu farklı arıza alanlarıydı

Bu noktada üç gözlemim vardı:

GNU ddrescue:
almost the entire physical source was copied

TSK fls:
many real paths were discoverable

TSK icat:
many ordinary file contents still failed

Arama zinciri kavramsal olarak ayrıldığında bu gözlemler birbiriyle uyumludur:

pathname
   ↓
directory record
   ↓
file object / inode metadata
   ↓
extent mapping
   ↓
physical data blocks

Alttaki veri blokları sağlam kalırken meta veri zincirinin daha yukarısındaki bir bağlantı hasarlı olabilir. Başka bir olasılık da iki APFS uygulamasının aynı hasarlı yapılarda farklı şekilde gezinmesidir.

Tüm arızaları açıklayan tek bir bozuk APFS nesnesini izole etmedim; bu nedenle bunu doğrulanmış kök neden olarak ileri sürmem. Kanıtların desteklediği şey çok daha yararlı bir deneydi: klonlanmış baytları değiştirmeden bırak ve ayrıştırıcıyı değiştir.

142 APFS checkpoint anında geri dönüş yolu olmadı

APFS checkpoint tanımlayıcı alanının ham, salt okunur taraması 142 aday NXSB checkpoint süper bloğu buldu; işlem kimlikleri şu değerden:

223133

şu değere kadar iniyordu:

222992

Bariz soru, daha eski bir checkpoint'in daha sağlıklı bir meta veri ağacına başvurup başvurmadığıydı.

apfs-fuse'u derledim ve fuse-t üzerinden farklı checkpoint işlem kimliklerini denedim. En yeni checkpoint takıldı. Daha eski XID'ler de aynı şeyi yaptı. Her checkpoint için katı zaman aşımı sınırları ekledim ve art arda 50'den fazla deneme kullanılabilir bir bağlama noktası üretemedi. Hem NFS hem de SMB fuse-t arka uçlarını da denedim.

Bu, tüm checkpoint'lerin bozuk olduğunu kanıtlamadı. Deney checkpoint'e, apfs-fuse'a, fuse-t'ye, macOS aygıt davranışına ve bağlama arka ucuna bağlıydı. Bu yığındaki herhangi bir katmanın başarısız olması aynı görünür sonucu üretebilirdi.

Daha sonraki bir ayrıştırıcı sıfır APFS snapshot raporladı; bu da net tutmam gereken bir ayrımı güçlendirdi: bu checkpoint işlem durumları, kullanıcıya görünür APFS snapshot'larıyla aynı şey değildi.

--help komutunda takılan bir ayrıştırıcı diskime teşhis koyamaz

go-apfs-v2'yi de derledim. Derleme tamamlandı, ancak şu basit komut bile:

apfs --help

takıldı ve zaman aşımıyla sonlandırılması gerekti.

Hem ham hem de arabellekli aygıt yollarına karşı en basit blok incelemesi de takıldı. apfsutil ile aynı tür sınırlı testi yaptım; her iki aygıt biçimi de yaklaşık 15 saniye sonra zaman aşımına uğradı.

Bunlar yararlı başarısızlıklardı; çünkü yanlış sonuca varmamı engellediler. Kendi yardım yolunu güvenilir biçimde tamamlayamayan bir araç, hasarlı bir dosyanın kurtarılabilir olup olmadığı konusunda zayıf kanıttır.

Kuralım şu oldu:

Kurtarma aracının başarısızlığını veriler hakkında kanıt saymadan önce kurtarma aracını doğrula.

macOS'un klonu otomatik bağlamasını engellemek başka bir risk kaynağını ortadan kaldırdı

macOS'un kendisi de değişkenlerden biriydi. Sağlıklı hedefin normal biçimde bağlı olmasını istiyordum; ancak depolamayı her yeniden bağladığımda Disk Arbitration'ın hasarlı APFS klonunu otomatik olarak bağlamaya çalışmasını istemiyordum.

Sonunda kullandığım güvenli sıra şuydu:

  1. Sağlıklı kurtarma hedefini bağla ve mount et.
  2. Birim kimliğini ve boş alanını doğrula.
  3. diskarbitrationd'yi dondur.
  4. Aktif bir mount_apfs süreci olmadığını doğrula.
  5. Ana klonu bağla.
  6. Ana klonu bilinen fiziksel boyut ve APFS kimliğiyle dinamik olarak belirle.
  7. Ana klonun mount edilmediğini doğrula.
  8. Salt okunur kurtarma çalışmasını gerçekleştir.
  9. Durdururken durumu kaydet, Disk Arbitration'ı devam ettir ve ardından normal biçimde kapat veya çıkar.

Disk Arbitration'ı dondurmak için gerçekten kullandığım komut şuydu:

sudo kill -STOP "$(pgrep -x diskarbitrationd)"

Süreç durumunda T bulunduğunu doğruladım ve devam etmeden önce başıboş mount_apfs süreçleri olup olmadığını kontrol ettim.

Önemli güvenlik dersi belirli disk numarası değildi. Tam tersiydi: dünün disk numarasına asla güvenme. Yeniden başlatmadan sonra önceki bir /dev/disk6 başka bir fiziksel aygıtı gösterebilir. Kimlik olarak APFS UUID'sini ve bilinen fiziksel boyutu kullandım, ardından güncel aygıt yolunu bunlardan türettim.

libfsapfs, TSK'nin sıfır bayt döndürdüğü aynı dosyayı kurtardı

Dönüm noktası farklı bir APFS uygulaması olan libfsapfs ile geldi.

macOS'ta kaynak koddan derledim. Ortaya çıkan fsapfsinfo ikilisi kendisini şöyle tanımladı:

fsapfsinfo 20260923

Ardından macOS aygıt arabiriminde önemli bir fark ortaya çıktı.

Ham karakter aygıtı bölümü:

/dev/rdiskNs2

4096 ofseti civarında geçersiz argümanlı bir okumayla hızlıca başarısız oldu.

Arabellekli blok aygıtı biçimi:

/dev/diskNs2

çalıştı.

Aynı fiziksel klon. Aynı APFS bölümü. Farklı macOS aygıt arabirimi.

fsapfsinfo kapsayıcıyı açtı ve bir birim buldu. Ardından TSK'nin çıkaramadığı tam 56,309 baytlık dosyayı test ettim.

TSK şunu üretmişti:

expected:  56,309
recovered:      0
APFSBlock failure

libfsapfs üzerinden dosya girdisi şunu üretti:

size: 56,309
MD5:  c6f56db33eafc1de0f52a035bc255dc7
RC:   0

Bu, teşhisi maddi olarak değiştiren ilk sonuçtu. Klonlanmış baytlar değişmemişti. Test dosyası değişmemişti. Hasarlı APFS onarılmamıştı.

Değişen APFS uygulamasıydı.

En azından artık, TSK üzerinden kurtarılamaz görünen gerçek bir kullanıcı dosyasının libfsapfs üzerinden tam içeriğinin okunmasına ve bir özet hesaplanmasına yetecek kadar erişilebilir olduğunu biliyordum.

libfsapfs'e terabaytlar emanet etmeden önce bilinen bozuk tek bir dosyayı çıkardım

Başarılı bir özet, çok terabaytlık kurtarmayı başlatmam için yeterli değildi. Gerçek baytları hedef diskte görmek istiyordum.

libfsapfs C API'sini tahmin etmek yerine kütüphanenin kaynak kodunu inceledim ve özet hesaplanırken fsapfsinfo'nun zaten kullandığı okuma yolunu izledim. Ardından bilinen tek test dosyası için küçük, salt okunur bir çıkarıcı oluşturdum.

Çıkarıcı tam olarak şunu yazdı:

56,309 bytes

ayrı kurtarma diskine. Çıkarılan dosyanın MD5 değeri şuydu:

c6f56db33eafc1de0f52a035bc255dc7

Bu, önceki özetle eşleşti.

Ancak bundan sonra yaklaşımı ölçeklendirdim. Tek dosyalık test üç ayrı şeyi kanıtlamıştı: yol adı çözümlenebiliyordu, beklenen tam bayt sayısı çıkarılabiliyordu ve çıkarılan baytlar önceki tam içerik okumasıyla aynı özeti üretiyordu.

Toplu kurtarma sorunu çoğunlukla arızayı sınırlama ve devam ettirme ile ilgiliydi

libfsapfs, TSK'nin kurtaramadığı bir dosyayı kurtarabildiğinde zor problem yeniden değişti. Tek bir hasarlı dal, tek bir yeniden başlatma veya tek bir Ctrl+C nedeniyle işin sıfırdan başlamadığı, çok büyük bir dizin ağacını işleyebilen bir sisteme ihtiyacım vardı.

Bu nedenle toplu kurtarma hattı birkaç katı değişmez kullandı:

  • Ana klon salt okunur olarak açıldı.
  • Kurtarılan çıktı yalnızca ikinci 5 TB diske yazıldı.
  • Dizin ve dosya adları korundu.
  • İlerleme SQLite'ta tutuldu; böylece süreç sonlandırmalarından ve yeniden başlatmalardan sağ çıktı.
  • Her dosya önce geçici bir yola yazıldı.
  • Geçici dosya ancak beklenen tam boyut yazıldıktan sonra nihai yoluna yeniden adlandırıldı.
  • Beklenen boyuttaki mevcut dosyalar devam ettirme sırasında tanınabiliyordu.
  • Başarısız veya sorunlu işler tamamlanmış işlerden ayrı tutuldu.
  • Boş alan kontrol edildi ve bir güvenlik payı korundu.
  • Yeniden üretilebilir geliştirme çıktıları ve düşük değerli sistem meta verileri atlanabilir veya düşük önceliğe alınabilirdi.

Bir performans değişikliği hemen fark yarattı: APFS kapsayıcısını her dosya için bağımsız olarak yeniden açmayı bıraktım.

Hızlı yol her seferinde bir dizin işliyordu. Bir worker kaynağı açıyor, dizini listeliyor, doğrudan içindeki normal dosyaları kurtarıyor ve alt dizinleri kuyruğa geri veriyordu. Bir dizin başarısız olur veya zaman aşımına uğrarsa controller onu DEFERRED olarak işaretliyor ve genel geçişi engellemek yerine devam ediyordu.

Normal bekleyen iş kalmadığında ertelenen dizinler, dosya başına daha izole işlemler yapan daha yavaş bir fallback yoluyla yeniden ele alındı. Kalan hatalar daha sonra bağımsız olarak tekrar denenebiliyordu.

discover directory
    ↓
recover immediate files
    ↓
verify expected sizes
    ↓
commit durable state
    ↓
queue child directories
    ↓
defer local failures
    ↓
continue globally
    ↓
fallback and retry later

Bu mimari, gerçek arıza modeline tek bir dev özyinelemeli komuttan çok daha iyi uyuyordu. Hasar eşit dağılmamıştı; dolayısıyla kurtarma sistemi de ilerlemeyi tekdüze hâle getirmemeliydi.

Milyonlarca dosyayı hash'lemek yerine neden beklenen boyut kontrolleri ve atomik yeniden adlandırma kullandım

MD5, tek dosyalık kanıt sırasında yararlıydı; çünkü ayrıştırıcının TSK'nin okuyamadığı bir dosyanın tam içeriğini okuduğuna dair güçlü kanıta ihtiyacım vardı.

Milyonlarca dosyayı hash'lemek için kurtarılan her baytı ikinci kez tamamen okumak büyük miktarda G/Ç eklerdi. Ana çıkarma geçişinde farklı bir değişmez kullandım.

Her normal dosya için APFS meta verisi beklenen bir boyut sağlıyordu. Worker geçici bir dosyaya yazıyor ve dosyayı yalnızca tam okuma beklenen boyutla eşleştiğinde nihai yol adına taşıyordu.

Bu, bir kesintinin nihai ad altında eksik bir dosyayı tam dosyaymış gibi bırakmaması gerektiği anlamına gelir.

Boyut eşleşmesi kriptografik bütünlük kanıtı değildir. Ben de öyle kabul etmiyorum. Ancak beklenen boyut doğrulaması ile atomik yeniden adlandırma, yüksek hacimli geçiş için pratik bir doğruluk sınırıydı; hedefli hash'ler ise örnekler ve bilinen hata vakaları için yararlı olmaya devam etti.

SQLite yeniden başlatmayı felaket yerine sıradan bir işe dönüştürdü

Kurtarma yeterince uzun sürdüğü için bilgisayarı kapatıp daha sonra devam etmem gerekti. Bu gereksinim tasarımı “betik”ten “devam ettirilebilir iş akışı”na çevirdi.

Ctrl+C aktif alt süreci basitçe terk etmiyordu. Controller kesintiyi yakalıyor, worker'ı durduruyor, aktif dizini kurtarılabilir duruma döndürüyor, SQLite durumunu commit ediyor ve çıkıyordu.

Temiz bir duruş kavramsal olarak şöyle görünüyordu:

CURRENT DIRECTORY -> PENDING

CTRL+C: RECOVERY STOPPED SAFELY
STATE SAVED. RUN THE SAME COMMAND TO RESUME.

Yeniden başlatmadan sonra disk kimliği kontrollerini tekrarladım ve aynı kurtarma komutunu başlattım. Durum veritabanı mevcut kuyruğu kaldığı yerden sürdürdü.

Daha sonraki yeniden başlatmalardan biri şunlarla başladı:

DEFERRED:     1
DONE:     73,015
PENDING:   7,254

Bu, genel bir ilerleme çubuğundan çok daha anlamlıydı. On binlerce tamamlanmış dizin biriminin yeniden başlatmadan sağ çıktığını ve kalan kuyruğun açıkça tanımlı olduğunu gösteriyordu.

Daha önceki bir devam ettirmede, önceki oturumda kesilen dizin yeniden ele alındı. Beklenen boyutta zaten mevcut olan dosyalar tanındı ve yalnızca eksik işin yazılması gerekti. İstediğim davranış buydu: kurtarmayı yeniden başlatmak korkutucu değil, rutin olmalıydı.

326,799 dosya, yöntemin ölçeklenebildiğinin ilk kanıtıydı

Yeni çıkarıcıyı seçilmiş tüm verilere yaymadan önce, büyük bir öncelikli ağacı toplu doğrulama hedefi olarak kullandım.

Tamamlanmış durum şunu bildirdi:

directories completed:     8,940
new files written:       302,541
existing/resumed files:   24,258
recorded failed files:         0

Hedefte şunlar vardı:

326,799 files
145,039,215,948 bytes

yani yaklaşık:

135.08 GiB

Dosya sayıları tam olarak uyuşuyordu:

302,541 + 24,258 = 326,799

Tamamlanan bu ağaçta geride kalan .partial.* dosyası yoktu.

56,309 baytlık test dosyası, ayrıştırıcının TSK'nin başarısız olduğu yerde başarılı olabildiğini kanıtlamıştı. 326,799 dosyalık kurtarma ise aynı yaklaşımın devam ettirme mantığı, mevcut dosya algılama ve tamamlanmış geçişte kaydedilmiş sıfır dosya hatasıyla büyük bir gerçek hiyerarşiyi kaldırabildiğini kanıtladı.

Daha büyük kurtarma, tamamlanmadan önce 1.6 milyon yeni dosyayı geçti

Öncelikli ağaç sorunsuz tamamlandıktan sonra kurtarmayı seçilmiş kalan üst düzey verilere genişlettim.

Bilinçli olarak yaptığım güvenli duruşlardan birinde SQLite şunu raporladı:

DONE directories:       39,015
PENDING directories:    17,017
DEFERRED directories:        1

new files written:   1,636,305
new bytes written: 902,715,716,335

Bu yaklaşık olarak:

840.72 GiB

o noktaya kadar dizin geçişleri tarafından kaydedilmiş yeni yazılan veriydi.

Tek bir DEFERRED dizin veri kaybı anlamına gelmiyordu. Hızlı yolun yerel bir sorunun ilgisiz işleri geciktirmesine bilinçli olarak izin vermediği anlamına geliyordu. Fallback aşaması özellikle bu tür vakalara daha sonra dönmek için vardı.

Sonraki oturumlar aynı veritabanından devam etti. Tamamlananların sayısı arttı ve bekleyen kuyruk azaldı. Sonunda ihtiyaç duyduğum seçilmiş ve listelenebilmiş tüm klasör ağaçlarını kurtardım.

Nihai kurtarma hakkında dürüstçe ne söyleyebilirim

Sonucu “her bayt kurtarıldı” diye tanımlamayacağım. Kanıtlar bunu desteklemiyor.

Orijinal ddrescue haritasında başarıyla kopyalandığı doğrulanmamış yaklaşık 13.84 MB hâlâ vardı. Ayrıca, listelenmesi için gereken meta veri hasarlı bölgeler arasında olduğu için hiçbir dosya sistemi nesnesinin tamamen keşfedilemez hâle gelmediğini de kanıtlayamam.

Bu sınırlamalar önemlidir; çünkü yapılandırılmış kurtarma listelenmiş bir nesnenin çıkarıldığını kanıtlayabilir, ancak hasarlı ad alanının artık gösteremediği bir nesnenin geçmişte hiç var olmadığını kanıtlayamaz.

En güçlü nihai ifade daha dardır:

İhtiyaç duyduğum seçilmiş ve listelenebilmiş her klasör ağacı yapılandırılmış kurtarma süreciyle başarıyla kurtarıldı.

Ana klonu yerinde onarmam gerekmedi. Toplu çıkarma için orijinal tıklayan Toshiba'yı yeniden kullanmam gerekmedi. Dizin yapısını ve dosya adlarını feda edecek tüm disk çapında PhotoRec tarzı bir carving geçişine ihtiyacım olmadı.

Başarısız araçlar yine de yararlı kanıtlardı

Geriye dönüp bakınca başarılı yol basit görünüyor:

ddrescue clone
    ↓
libfsapfs
    ↓
resumable extractor
    ↓
recovered files

Araştırma sürerken böyle hissettirmiyordu ve başarısız yaklaşımları çıkarmak yararlı mühendislik derslerinin büyük bölümünü de çıkarırdı.

TSK bana APFS ad alanında gezinmenin ve içerik almanın farklı arıza biçimleri olduğunu öğretti.

İlk sarmalayıcımdaki hata, bir kurtarma betiğinin kendi hata sınıflandırmasının mutlak gerçek olmadığını öğretti.

Hotspot mekanizması, yerel arızayı genel ilerlemeyi durdurmasına izin vermek yerine izole etmeyi öğretti.

Checkpoint deneyi, APFS işlem checkpoint'lerini snapshot'larla karıştırmamam gerektiğini öğretti.

FUSE denemeleri, başarısız bir mount işleminin dosya sistemi verisinin dışında da birkaç katmanı işaret edebileceğini öğretti.

--help sırasında takılan APFS okuyucusu, teşhislerini yorumlamadan önce aracı doğrulamam gerektiğini öğretti.

/dev/rdiskNs2 ile /dev/diskNs2 arasındaki davranış farkı, alttaki disk aynı olsa bile işletim sisteminin G/Ç yolunun bir ayrıştırıcının davranışını değiştirebileceğini öğretti.

İki diskli mimari ise ana klonu değiştirmeden geri kalan her yerde hata yapma özgürlüğü verdi.

Tekrar kullanacağım kurtarma iş akışı

  1. Mekanik olarak arızalanan depolamada normal dosya sistemi etkinliğini durdur. Okumalar takılıyorsa, aygıt kayboluyorsa veya tıklıyorsa Finder'da gezinmek yerine devam ettirilebilir blok düzeyi klonu önceliklendirirdim.
  2. Kalıcı mapfile ve doğrulanmış kurtarma alanıyla GNU ddrescue kullan. Mapfile ilerlemeyi korur; bilinen kaynak boyutu, aygıt boyutu belirsizliğinin kurtarmanın bir parçası olmasını engeller.
  3. Bir ana klonu salt okunur tut. Onarma, yeniden boyutlandırma, yeniden bölümleme veya kurtarılan dosyaların depolaması olarak kullanma.
  4. Kurtarılan dosyaları ikinci bir fiziksel diske yaz. Kaynağı korumak ile çıktı depolamak farklı işlerdir.
  5. Önce klonlanmış dosya sistemini salt okunur biçimde teşhis et. Bozuk APFS meta verisi içeren sağlıklı fiziksel klon mantıksal bir kurtarma sorunudur; tıklayan kaynak diskle aynı sorun değildir.
  6. Ayrıştırıcı testi olarak tekrarlanabilir tek bir başarısız dosya seç. Bilinen bozuk 56 KB'lık bir dosya, alternatif ayrıştırıcılar hakkında saatlerce kör toplu çıkarmadan daha fazla şey anlattı.
  7. Ayrıştırıcının kendisini doğrula. Bir araç kaynağı anlamlı biçimde okumadan önce takılıyorsa bunu verinin kaybolduğunun kanıtı olarak yorumlama.
  8. Tek bir APFS uygulamasının kurtarılabilirliği tanımladığını varsayma. TSK ve libfsapfs aynı klonlanmış baytlar üzerinde çok farklı davrandı.
  9. Diskleri geçici aygıt numaralarıyla değil kararlı özelliklerle tanımla. Dosya sistemi UUID'si ve bilinen fiziksel boyut, dünkü /dev/diskN'den daha güvenlidir.
  10. Uzun süren çıkarma işlemini en baştan devam ettirilebilir tasarla. Kalıcı durum, geçici dosyalar, atomik yeniden adlandırma, ertelenen işler, sınırlı yeniden denemeler ve temiz kapanma yönetimi bu ölçekte doğruluğun parçasıdır.
  11. Doğrulama düzeylerini ayır. Bir ddrescue yüzdesi, görünür bir yol adı, beklenen boyut eşleşmesi ve içerik hash'i farklı şeyler kanıtlar.

Bitiş çizgisi gibi görünen sayı yalnızca birinci aşamanın sonuydu

Tüm kurtarmadaki en yanıltıcı sayı hâlâ şuydu:

100.00%

“Diski kurtardım mı?” sorusunun yanıtı gibi görünüyordu.

Gerçekte yanıtladığı soru çok daha dardı:

How much of the physical rescue domain did ddrescue successfully copy?

APFS'nin ad alanını yeniden oluşturup oluşturamayacağını yanıtlamıyordu. Bir ayrıştırıcının reddettiği hasarlı meta veride başka bir ayrıştırıcının gezip gezemeyeceğini yanıtlamıyordu. Kurtarılan bir dosyanın beklenen uzunluğa sahip olup olmadığını yanıtlamıyordu. Milyonlarca dosyalık bir çıkarma işleminin kendi durumunu bozmadan hatalardan ve yeniden başlatmalardan sağ çıkıp çıkamayacağını da yanıtlamıyordu.

Her katman için ayrı kanıta ihtiyacım vardı.

Önce fiziksel kurtarma başarılı oldu. Dosya sistemi hâlâ hasarlıydı. İlk ayrıştırıcı adları gösterebiliyordu ama birçok dosyanın içeriğinde başarısız oluyordu. Farklı bir APFS uygulaması aynı test dosyasını başarıyla okudu. Bu kanıt tek dosyalık bir çıkarıcıya, çıkarıcı devam ettirilebilir bir kurtarma motoruna dönüştü ve motor sonunda ana klon dokunulmadan kalırken ihtiyaç duyduğum seçilmiş klasör ağaçlarını ikinci diske kurtardı.

Orijinal sabit disk hiçbir zaman yeniden sağlıklı olmadı. APFS sihirli biçimde kendini onarmadı. Değişen kurtarma modeliydi.

Blok kurtarma, dosya sistemi kurtarma ve dosya doğrulama ayrı mühendislik aşamalarıdır. Bunları tek sorun olarak ele almak durumu neredeyse umutsuz gösteriyordu. Ayırmak ise yönetilebilir hâle getirdi.