ব্লগে ফিরে যান
৭ অক্টোবর, ২০২৬Sergei Solod21 মিনিট পড়া

GNU ddrescue 4 TB ড্রাইভের 99.999654% কপি করার পর ক্ষতিগ্রস্ত APFS ক্লোন থেকে আমি যেভাবে ফাইল উদ্ধার করেছি

GNU ddrescue একটি ব্যর্থপ্রায় 4 TB HDD-এর 99.999654% কপি করেছিল, কিন্তু APFS ক্লোন তখনও ফাইলসিস্টেম পরীক্ষায় ব্যর্থ হচ্ছিল এবং The Sleuth Kit পাথ তালিকাভুক্ত করতে পারলেও শূন্য-বাইট ফাইল ফিরিয়ে দিচ্ছিল। রিড-অনলি মাস্টার ক্লোন অক্ষত রেখে, libfsapfs-এ বদলে এবং পুনরায়-শুরুযোগ্য এক্সট্রাকশন পাইপলাইন তৈরি করে আমি নির্বাচিত ডিরেক্টরি ট্রিগুলো উদ্ধার করেছি।

APFSGNU ddrescueডেটা রিকভারিlibfsapfsmacOS

GNU ddrescue দেখিয়েছিল 100.00%। আমার প্রথম কাঠামোবদ্ধ ফাইল-রিকভারি পাসে পাওয়া গিয়েছিল 0 useful recovered user files।

এই দুই ফলই একই 4 TB হার্ড-ড্রাইভ বিকল হওয়ার ঘটনা থেকে এসেছিল, আর এই বৈপরীত্যই রিকভারিটিকে “খারাপ ডিস্ক ক্লোন করো এবং ফাইল কপি করো”—এর চেয়ে অনেক বেশি আকর্ষণীয় করে তুলেছিল। ddrescue তার কাজ অসাধারণভাবে ভালো করেছিল: এটি ভৌত উৎসের প্রায় 99.999654% কপি করেছিল। ক্লোনটি ছিল সুস্থ হার্ডওয়্যারে। তবু APFS তখনও ক্ষতিগ্রস্ত ছিল, macOS স্বাভাবিকভাবে আমাকে ফাইলসিস্টেমটি দিচ্ছিল না, আর প্রথম APFS রিকভারি স্ট্যাক ডিরেক্টরি ট্রির বড় অংশ তালিকাভুক্ত করতে পারলেও সাধারণ ফাইলের বিষয়বস্তু পড়তে ব্যর্থ হচ্ছিল।

শেষ পর্যন্ত আমার প্রয়োজনীয় প্রতিটি নির্বাচিত ও তালিকাভুক্ত ফোল্ডার ট্রি আমি উদ্ধার করেছি, তবে তা সম্ভব হয়েছে ঘটনাটিকে তিনটি আলাদা সমস্যা হিসেবে দেখার পর: ভৌত ব্লক রিকভারি, ক্ষতিগ্রস্ত ফাইলসিস্টেমের ব্যাখ্যা, এবং যাচাই ও পুনরায় শুরু করার সুবিধাসহ বৃহৎ-পরিসরের ফাইল নিষ্কাশন।

প্রথমেই সমস্যাটি আর শুধু ফাইলসিস্টেমের সমস্যা ছিল না

মূল 4 TB Toshiba এমন অবস্থায় পৌঁছেছিল যে স্বাভাবিক ফাইলসিস্টেম কার্যকলাপের জন্য সেটিকে আর বিশ্বাস করতাম না। কিছু রিডে 60–75 সেকেন্ড লাগত। অপারেশন আটকে যেতে পারত। ড্রাইভটি মাঝেমধ্যে macOS থেকে অদৃশ্য হয়ে যেত, স্পষ্ট ক্লিকের শব্দ করত এবং কখনও কখনও বন্ধও হয়ে যেত।

বিকল হওয়ার অল্প আগে আমি প্রায় 300 GB অতিরিক্ত ডেটা লিখেছিলাম এবং প্রায় 500,000 ফাইল ও ডিরেক্টরিকে প্রভাবিত করে একসঙ্গে অনেক নাম পরিবর্তন করেছিলাম। সময়ের মিল দেখে এই মেটাডেটা-ভারী কাজকে সন্দেহজনক মনে হয়েছিল, কিন্তু এটিই হার্ডওয়্যার বিকলের কারণ ছিল—এ কথা আমি প্রমাণ করতে পারি না। হয়তো আগে থেকেই অসুস্থ ড্রাইভটির ওপর যথেষ্ট চাপ পড়ে সমস্যাটি কেবল প্রকাশ পেয়েছিল।

আমি যা নিশ্চিত করতে পেরেছিলাম, তা হলো ড্রাইভটির আচরণ। যান্ত্রিক স্টোরেজ যখন থেমে থেমে কাজ করে, অদৃশ্য হয়ে যায় এবং ক্লিক করে, তখন বারবার ডিরেক্টরি ব্রাউজ করা ভুল বিমূর্ততা। ডিরেক্টরি ট্রাভার্সাল আরও রিড ও সিক ঘটাতে পারে। ফাইলসিস্টেম মাউন্ট করলে মেটাডেটা-সংক্রান্ত কাজ শুরু হতে পারে। প্রতিটি পরীক্ষা এমন একমাত্র উপাদানের সময় ব্যয় করে, যার অবশিষ্ট কার্যকর আয়ু অজানা।

তাই আমি লক্ষ্য বদলে:

recover my files

থেকে করলাম:

recover as many readable sectors as possible

আমি একটি স্থায়ী mapfile সহ GNU ddrescue 1.30 ব্যবহার করেছি। উৎস ড্রাইভের সঠিক ভৌত আকার ছিল:

4,000,787,027,968 bytes

mapfile অপরিহার্য ছিল, কারণ উৎসটি একবারে কপি শেষ করার মতো স্থিতিশীল ছিল না। এটি কোন অঞ্চল ইতিমধ্যে উদ্ধার হয়েছে তা না ভুলে স্টল, সংযোগ বিচ্ছিন্ন হওয়া, পুনরায় চালু হওয়া এবং পরবর্তী পাসের মধ্যেও রিকভারিকে টিকিয়ে রেখেছিল।

কাঁচা macOS ডিভাইস পাথ নিয়েও আমি একটি বাস্তব সমস্যা পাই: প্রকৃত ডিভাইস সীমায় শেষ হওয়ার বদলে দৃশ্যমান রিকভারি সীমা অর্থহীন হয়ে যেতে পারত। তাই ওপরের জানা ভৌত আকার অনুযায়ী আমি রিকভারি ডোমেইন সীমাবদ্ধ করেছি। এই ক্ষেত্রে ইনপুট স্পষ্টভাবে সীমাবদ্ধ করা ছিল সঠিকতার ব্যবস্থা, গতির অপ্টিমাইজেশন নয়।

ddrescue-তে 100.00% দেখানো কেন ফাইলগুলো নিরাপদ ছিল—এমনটা বোঝায়নি

ভৌত রিকভারির শেষের দিকে ddrescue প্রায় এই ফল দেখিয়েছিল:

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

শিরোনামসদৃশ শতাংশটি ছিল:

100.00%

কিন্তু অমীমাংসিত অবস্থাগুলোর মোট তখনও ছিল:

13,835,816 bytes

অথবা প্রায়:

13.84 MB

4,000,787,027,968-বাইট উৎসের তুলনায় উদ্ধার হওয়া অংশ ছিল প্রায়:

99.999654%

ব্লক রিকভারির হিসেবে এটি অসাধারণ ফল। এটি ফাইলের অখণ্ডতার ফল নয়।

কত বাইট হারিয়েছে তার চেয়ে সেগুলো কোথায় হারিয়েছে, তা বেশি গুরুত্বপূর্ণ। অব্যবহৃত জায়গা থেকে কয়েক মেগাবাইট হারালে দৃশ্যমান কিছুই প্রভাবিত নাও হতে পারে। একটি ভিডিওর ভেতরের ছোট একটি অপাঠ্য অঞ্চল একটি ফাইল নষ্ট করতে পারে। আবার ফাইলসিস্টেম মেটাডেটার আরও ছোট ক্ষতি অন্যথায় অক্ষত বহু ডেটা এক্সটেন্টের অবস্থান খুঁজে পাওয়া কঠিন করে দিতে পারে।

বাকি রিকভারির জন্য এটিই আমার কেন্দ্রীয় মানসিক মডেল হয়ে ওঠে:

স্তরএটি কোন প্রশ্নের উত্তর দেয়সাফল্য যা প্রমাণ করে না
ব্লক রিকভারিভৌত সেক্টরগুলো কি কপি হয়েছে?APFS প্রতিটি ফাইল পুনর্গঠন করতে পারবে—এটি নয়
ফাইলসিস্টেম রিকভারিপাথ, মেটাডেটা ও এক্সটেন্ট কি নির্ধারণ করা যায়?প্রতিটি নিষ্কাশিত বাইট বৈধ—এটি নয়
ফাইল যাচাইফাইলটি কি প্রত্যাশিত আকার বা হ্যাশসহ এসেছে?কখনও কোনো অদৃশ্য হয়ে যাওয়া ফাইল ছিল না—এটি নয়

বাকি অপাঠ্য অঞ্চলগুলোতে আমি আরও কয়েকটি শেষ চেষ্টা করেছি। Toshiba তখন জোরে ক্লিক করছিল, আর শেষ পর্যন্ত ওই চেষ্টাগুলো আর কোনো কার্যকর নতুন রিড দিচ্ছিল না। সেখানেই আমি মূল ড্রাইভটিকে সক্রিয় রিকভারি উৎস হিসেবে ব্যবহার করা বন্ধ করি।

একটি 4 TB ডিস্ক উদ্ধার করতে আমি কেন দুটি 5 TB ড্রাইভ কিনেছিলাম

প্রথম নতুন ড্রাইভটি ছিল 5 TB Seagate Expansion, যার সঠিক ভৌত ধারণক্ষমতা ছিল:

5,000,981,077,504 bytes

আমি এতে Toshiba-এর ব্লক-স্তরের ক্লোন লিখেছিলাম। উৎসের লেআউট প্রথম প্রায় 4 TB জুড়ে ছিল, ফলে কপি করা লেআউটের বাইরে প্রায় 1 TB অবশিষ্ট ছিল। সেই অতিরিক্ত জায়গা আমি ইচ্ছাকৃতভাবে অক্ষত রেখেছিলাম।

আমি APFS কনটেইনারটি বড় করিনি। সুবিধার জন্য ক্লোনটিকে পুনরায় পার্টিশন করিনি। এতে কোনো ফাইলসিস্টেম মেরামত চালাইনি। এই ডিস্কটিই হয়ে ওঠে মাস্টার ক্লোন।

তারপর আমি দ্বিতীয় একটি 5 TB ড্রাইভ কিনি। সেটি নতুন করে ফরম্যাট করা, রাইটেবল, স্বাধীনভাবে পরীক্ষা করা এবং কেবল উদ্ধার হওয়া আউটপুট রাখার জন্য ব্যবহৃত হয়েছিল।

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

অর্থাৎ প্রায় 3.26 TB ব্যবহৃত ডেটাসহ একটি ভলিউম উদ্ধার করতে আমাকে নামমাত্র প্রায় 10 TB নতুন স্টোরেজ কিনতে হয়েছিল। অতিরিক্ত ডিস্কটি ধারণক্ষমতার জন্য ছিল না। উদ্দেশ্য ছিল একটি অপরিবর্তনীয় শর্ত অক্ষুণ্ণ রাখা:

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

মাস্টারে মেরামত, রিসাইজ, রিপার্টিশন করা বা উদ্ধার হওয়া আউটপুট লেখা হলে সংরক্ষণ ও পরীক্ষানিরীক্ষা একসঙ্গে মিশে যেত। উৎস ও গন্তব্যকে আলাদা ভৌত ড্রাইভে রাখায় ভুল হলেও ফিরে আসা সম্ভব ছিল।

গন্তব্যটিকে বিশ্বাস করার আগে আমি প্রায় 10 GB রাইট/রিড পরীক্ষা চালাই। ওই পরীক্ষায় উভয় দিকেই এটি প্রায় 144.4 MB/s বজায় রেখেছিল। মাস্টার ক্লোন থেকে নমুনা কাঁচা রিড ছিল প্রায় 28–49 MB/s এবং ওই পরীক্ষাগুলোতে মূল ড্রাইভের ভৌত I/O ব্যর্থতার ধরন পুনরায় দেখা যায়নি।

এ পর্যায়ে সমস্যার প্রকৃতি বদলে গিয়েছিল। আমি আর ব্যর্থ হতে থাকা হার্ডওয়্যার ডিবাগ করছিলাম না। আমি এমন হার্ডওয়্যারে সংরক্ষিত ক্ষতিগ্রস্ত APFS মেটাডেটা ডিবাগ করছিলাম, যা আমার পরীক্ষায় স্বাভাবিক আচরণ করছিল।

APFS ক্লোনটি ডিভাইস হিসেবে পড়া যাচ্ছিল, কিন্তু ফাইলসিস্টেম হিসেবে অবৈধ ছিল

ক্লোন করা APFS ভৌত স্টোর ছিল:

4,000,650,887,168 bytes

প্রাসঙ্গিক পার্টিশন শুরু হয়েছিল সেক্টর:

264192

512-বাইট সেক্টর হিসেবে বাইট অফসেট হয়:

135,266,304 bytes

APFS ভলিউম প্রায় এই পরিমাণ ব্যবহৃত বলে জানিয়েছিল:

3,260,976,717,824 bytes

।

একটি রিড-অনলি APFS পরীক্ষা শেষ পর্যন্ত fsroot ট্রিতে পৌঁছে জানায়:

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

এখানেই “ddrescue প্রায় পুরো ডিস্ক কপি করেছে” কথাটি পূর্ণাঙ্গ নির্ণয় হিসেবে আর যথেষ্ট ছিল না। কাঁচা ক্লোন ছিল। কিন্তু তার ভেতরের ফাইলসিস্টেম কাঠামো তখনও অসঙ্গত ছিল।

আমি ফাইলসিস্টেম পরীক্ষা ইচ্ছাকৃতভাবে পরিবর্তন না করে রেখেছিলাম। আমার একমাত্র উচ্চমানের মাস্টার ক্লোনের ওপর fsck_apfs -n-কে মেরামত অপারেশন-এ বদলাইনি। সাধারণ স্টোরেজে মেরামত যথাযথ হতে পারে, কিন্তু এখানে তা এমন প্রমাণ বদলে দিত, যা আমি তখনও বোঝার চেষ্টা করছিলাম।

The Sleuth Kit APFS নেমস্পেস তালিকাভুক্ত করতে পারলেও ফাইলের বিষয়বস্তুতে ব্যর্থ হচ্ছিল

The Sleuth Kit 4.15.0 ছিল প্রথম রিকভারি স্ট্যাক, যা ক্লোনটিকে আশাব্যঞ্জক মনে করিয়েছিল। পুরো ক্লোন করা ডিভাইস, জানা পার্টিশন অফসেট এবং আমি শনাক্ত করা APFS সুপারব্লক ব্যবহার করে fls দিয়ে আসল ডিরেক্টরির নাম তালিকাভুক্ত করতে পারছিলাম:

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

আমি ইচ্ছাকৃতভাবেই N ব্যবহার করছি। পুনঃসংযোগ ও রিবুটের মধ্যে macOS ডিস্ক নম্বর বদলাত, তাই আগের কোনো /dev/disk6 বরাদ্দ-কে আমি পরিচয় হিসেবে ধরিনি।

fls নেমস্পেস-এর উল্লেখযোগ্য অংশ ট্রাভার্স করতে পারত। একটি বড় ট্রিতেই প্রায় 8,900 ডিরেক্টরি দেখা গিয়েছিল। এক মুহূর্তের জন্য মনে হয়েছিল কঠিন অংশটি বুঝি সমাধান হয়ে গেছে।

তারপর আমি ফাইলের বিষয়বস্তু বের করার চেষ্টা করি।

একটি প্রতিনিধিত্বমূলক ব্যর্থতা ছিল:

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

tsk_recover এক্সট্রাকশন শুরু করে পরে একটি APFS ব্লকে ব্যর্থ হতে পারত। আলাদা icat চেষ্টাতেও একই ধরনের সমস্যা দেখা দিয়েছিল।

গুরুত্বপূর্ণ পার্থক্যটি ছিল:

directory traversal works

এ থেকে বোঝায়নি যে:

file content retrieval works

একটি পার্সারের কাছে পাথনেম খুঁজে পাওয়ার মতো অবশিষ্ট মেটাডেটা থাকতে পারে, কিন্তু পরে বাইট স্ট্রিম ফেরাতে প্রয়োজনীয় ফাইল অবজেক্ট, এক্সটেন্ট মেটাডেটা বা বিষয়বস্তু ব্লক নির্ধারণের সময় সেটি ব্যর্থ হতে পারে।

প্রথম বাল্ক রিকভারি ইঞ্জিন-কে আমি ত্রুটি-সহনশীল করেছিলাম, কিন্তু এই ক্ষতির জন্য পার্সার তখনও উপযুক্ত ছিল না

তাৎক্ষণিকভাবে পার্সার বদলানোর বদলে আমার প্রথম প্রতিক্রিয়া ছিল TSK এক্সট্রাকশনকে আরও সহনশীল করা।

fls ও icat-এর চারপাশে আমি একটি Python রিকভারি র‍্যাপার তৈরি করি। এটি SQLite-এ স্থায়ী অবস্থা রাখত, ব্যর্থতা লগ করত, পুনরায় শুরু সমর্থন করত, আংশিক আউটপুট আলাদাভাবে লিখত এবং প্রথম পাসে কম-মূল্যের macOS রক্ষণাবেক্ষণ ডেটাকে কম অগ্রাধিকার দিত।

আমি একটি “হটস্পট” নিয়মও যোগ করি: কোনো ডিরেক্টরি-তে পরপর চারটি ফাইল একই শূন্য-বাইট APFSBlock প্যাটার্ন-এ ব্যর্থ হলে স্ক্রিপ্ট ওই ব্রাঞ্চ-এর বাকি অংশে সময় ব্যয় বন্ধ করত, সেটি পরে দেখার জন্য রেখে অন্যত্র এগিয়ে যেত। কাজের অনুমান ছিল, একই রকম ব্যর্থতার একটি ক্লাস্টার হয়তো শত শত আলাদাভাবে নষ্ট পেলোড না হয়ে একটি ক্ষতিগ্রস্ত মেটাডেটা নির্ভরতা ভাগ করছে।

অর্কেস্ট্রেশন কাজে লেগেছিল। ভেতরের APFS রিডার কাজে লাগেনি।

একটি সংরক্ষিত পর্যায়ে রিকভারি ডেটাবেসে ছিল:

DEFERRED_HOTSPOT: 30,356 files
FAILED:              486 files

486টি ব্যর্থতা ভাগ করলে পাওয়া যায়:

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

এবং কাঠামোবদ্ধ পাস তৈরি করেছিল:

0 useful recovered user files

75টি আকার অমিল আমার নিজের র‍্যাপারের একটি বাগ প্রকাশ করে: কিছু সিস্টেম মেটাডেটা ফাইল ডেটা ফিরিয়েছিল, কিন্তু আমার পার্সার তাদের প্রত্যাশিত আকার শূন্য হিসেবে রেকর্ড করেছিল। সেই ব্যাখ্যা ঠিক করা গুরুত্বপূর্ণ ছিল, কিন্তু প্রধান ফল বদলায়নি। সাধারণ ব্যবহারকারীর ফাইল তখনও could not read APFSBlock সহ শূন্য বাইটে শেষ হচ্ছিল।

আমি একটি ছোট AVIF ফাইলকে পুনরুৎপাদনযোগ্য পরীক্ষা কেস হিসেবে বেছে নিই। TSK তার প্রত্যাশিত আকার জানত:

expected size: 56,309 bytes
recovered:             0 bytes

ফাইলটি আরেকটি বহু-ঘণ্টার বাল্ক পাসের চেয়ে অনেক বেশি মূল্যবান হয়ে ওঠে। নতুন কোনো পদ্ধতি যদি ধারাবাহিকভাবে ব্যর্থ হওয়া একটি 56 KB পরীক্ষা ফাইল উদ্ধার করতে না পারে, তবে অবশিষ্ট টেরাবাইটগুলোর ওপর সেটিকে চালানোর যোগ্যতা ছিল না।

ডিস্ক ব্লক আর APFS মেটাডেটা পাথ ছিল আলাদা ব্যর্থতা ডোমেইন

এ পর্যায়ে আমার তিনটি পর্যবেক্ষণ ছিল:

GNU ddrescue:
almost the entire physical source was copied

TSK fls:
many real paths were discoverable

TSK icat:
many ordinary file contents still failed

লুকআপ শৃঙ্খলটিকে ধারণাগতভাবে আলাদা করলে পর্যবেক্ষণগুলো পরস্পরের সঙ্গে সামঞ্জস্যপূর্ণ:

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

নিচের দিকের ডেটা ব্লক বেঁচে থাকতে পারে, অথচ মেটাডেটা শৃঙ্খলের ওপরের কোনো লিঙ্ক ক্ষতিগ্রস্ত হতে পারে। আরেকটি সম্ভাবনা হলো, দুটি APFS ইমপ্লিমেন্টেশন একই ক্ষতিগ্রস্ত কাঠামো ভিন্নভাবে ট্রাভার্স করে।

সব ব্যর্থতা ব্যাখ্যা করে এমন একটি করাপ্ট APFS অবজেক্ট আমি আলাদা করে শনাক্ত করতে পারিনি, তাই সেটিকে নিশ্চিত মূল কারণ বলে দাবি করব না। প্রমাণ বরং আরও কার্যকর একটি পরীক্ষা সমর্থন করেছিল: ক্লোন করা বাইট অপরিবর্তিত রাখো, পার্সার বদলাও।

142টি APFS চেকপয়েন্ট তাৎক্ষণিক রোলব্যাকের পথ হয়ে ওঠেনি

APFS চেকপয়েন্ট ডিসক্রিপ্টর এলাকার একটি কাঁচা, রিড-অনলি স্ক্যানে 142টি সম্ভাব্য NXSB চেকপয়েন্ট সুপারব্লক পাওয়া যায়, যাদের ট্রানজ্যাকশন ID শুরু ছিল:

223133

এবং নেমে গিয়েছিল:

222992

স্বাভাবিক প্রশ্ন ছিল, পুরোনো কোনো চেকপয়েন্ট কি আরও সুস্থ মেটাডেটা ট্রিকে রেফারেন্স করছিল?

আমি apfs-fuse বিল্ড করি এবং fuse-t দিয়ে বিভিন্ন চেকপয়েন্ট ট্রানজ্যাকশন ID চেষ্টা করি। নতুনতম চেকপয়েন্ট আটকে যায়। পুরোনো XID-গুলোর ক্ষেত্রেও একই হয়। আমি প্রতি-চেকপয়েন্ট কঠোর টাইমআউট যোগ করি, আর পরপর 50টির বেশি চেষ্টা কোনো ব্যবহারযোগ্য মাউন্ট দিতে পারেনি। NFS ও SMB—দুই ধরনের fuse-t ব্যাকএন্ড-ও চেষ্টা করেছি।

এতে প্রমাণ হয়নি যে সব চেকপয়েন্টই করাপ্ট ছিল। পরীক্ষাটি চেকপয়েন্ট, apfs-fuse, fuse-t, macOS ডিভাইস আচরণ এবং মাউন্ট ব্যাকএন্ডের ওপর নির্ভর করছিল। এই স্ট্যাকের যেকোনো জায়গায় ব্যর্থতা একই দৃশ্যমান ফল দিতে পারত।

পরবর্তী একটি পার্সার শূন্য APFS স্ন্যাপশট দেখায়, যা আরেকটি পার্থক্য পরিষ্কার করে: ওই চেকপয়েন্ট ট্রানজ্যাকশন অবস্থাগুলো ব্যবহারকারীর কাছে দৃশ্যমান APFS স্ন্যাপশটের সমান ছিল না।

--help-এ আটকে যাওয়া পার্সার আমার ডিস্কের সমস্যা নির্ণয় করতে পারে না

আমি go-apfs-v2-ও বিল্ড করেছিলাম। বিল্ড শেষ হয়েছিল, কিন্তু এমনকি:

apfs --help

আটকে যেত এবং টাইমআউট দিয়ে বন্ধ করতে হতো।

কাঁচা ও বাফার্ড—উভয় ডিভাইস পাথে ন্যূনতম ব্লক পরিদর্শন-ও আটকে যায়। apfsutil দিয়ে একই ধরনের সীমাবদ্ধ পরীক্ষা করি; দুই ডিভাইসের রূপই প্রায় 15 সেকেন্ড পর টাইমআউট হয়।

এগুলো উপকারী ব্যর্থতা ছিল, কারণ এগুলো আমাকে ভুল সিদ্ধান্তে পৌঁছাতে বাধা দিয়েছিল। যে টুল নিজের হেল্প পাথ-ই নির্ভরযোগ্যভাবে শেষ করতে পারে না, ক্ষতিগ্রস্ত ফাইল উদ্ধারযোগ্য কি না সে বিষয়ে তার ব্যর্থতা দুর্বল প্রমাণ।

আমার নিয়ম দাঁড়াল:

রিকভারি টুলের ব্যর্থতাকে ডেটা সম্পর্কে প্রমাণ হিসেবে ধরার আগে টুলটিকেই যাচাই করো।

macOS-কে ক্লোন স্বয়ংক্রিয়ভাবে মাউন্ট করা থেকে আটকানো আরেকটি ঝুঁকি সরিয়ে দিয়েছিল

macOS নিজেও আরেকটি পরিবর্তনশীল অংশ ছিল। আমি সুস্থ গন্তব্যটিকে স্বাভাবিকভাবে মাউন্ট করা রাখতে চেয়েছিলাম, কিন্তু স্টোরেজ পুনরায় সংযোগ দিলেই Disk Arbitration যেন ক্ষতিগ্রস্ত APFS ক্লোন স্বয়ংক্রিয়ভাবে মাউন্ট করার চেষ্টা না করে, সেটিই চেয়েছিলাম।

শেষ পর্যন্ত আমি যে নিরাপদ ক্রম ব্যবহার করেছি তা ছিল:

  1. সুস্থ রিকভারি গন্তব্য সংযোগ দিয়ে মাউন্ট করা।
  2. তার ভলিউম পরিচয় ও খালি জায়গা যাচাই করা।
  3. diskarbitrationd ফ্রিজ করা।
  4. কোনো সক্রিয় mount_apfs প্রক্রিয়া নেই তা যাচাই করা।
  5. মাস্টার ক্লোন সংযুক্ত করা।
  6. জানা ভৌত আকার ও APFS পরিচয় দিয়ে মাস্টারকে ডায়নামিকভাবে শনাক্ত করা।
  7. মাস্টার মাউন্ট হয়নি তা যাচাই করা।
  8. রিড-অনলি রিকভারি কাজ করা।
  9. থামার সময় অবস্থা সংরক্ষণ করা, Disk Arbitration পুনরায় শুরু করা, তারপর স্বাভাবিকভাবে শাট ডাউন বা ইজেক্ট করা।

Disk Arbitration ফ্রিজ করতে আমি আসলে যে কমান্ড ব্যবহার করেছি তা ছিল:

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

আমি প্রক্রিয়া অবস্থায় T আছে কি না যাচাই করেছিলাম এবং এগোনোর আগে অনাকাঙ্ক্ষিত mount_apfs প্রক্রিয়া খুঁজে দেখেছিলাম।

গুরুত্বপূর্ণ নিরাপত্তার শিক্ষা নির্দিষ্ট ডিস্ক নম্বর ছিল না; বরং ঠিক উল্টো: গতকালের ডিস্ক নম্বর কখনও বিশ্বাস কোরো না। রিবুটের পর আগের /dev/disk6 অন্য একটি ভৌত ডিভাইসকে নির্দেশ করতে পারে। আমি APFS UUID এবং জানা ভৌত আকারকে পরিচয় হিসেবে ধরেছি, তারপর সেখান থেকে বর্তমান ডিভাইস পাথ নির্ধারণ করেছি।

TSK যে ফাইলকে শূন্য বাইট ফিরিয়েছিল, libfsapfs সেই একই ফাইল উদ্ধার করেছিল

অগ্রগতি আসে libfsapfs থেকে, যা একটি ভিন্ন APFS ইমপ্লিমেন্টেশন।

আমি macOS-এ উৎস থেকে এটি বিল্ড করি। তৈরি হওয়া fsapfsinfo বাইনারি নিজেকে এভাবে শনাক্ত করেছিল:

fsapfsinfo 20260923

তারপর macOS ডিভাইস ইন্টারফেসের একটি গুরুত্বপূর্ণ পার্থক্য সামনে আসে।

কাঁচা ক্যারেক্টার-ডিভাইস পার্টিশন:

/dev/rdiskNs2

অফসেট 4096-এর কাছাকাছি invalid-argument রিড দিয়ে দ্রুত ব্যর্থ হয়।

বাফার্ড ব্লক-ডিভাইসের রূপ:

/dev/diskNs2

কাজ করেছিল।

একই ভৌত ক্লোন। একই APFS পার্টিশন। ভিন্ন macOS ডিভাইস ইন্টারফেস।

fsapfsinfo কনটেইনার খুলে একটি ভলিউম খুঁজে পায়। এরপর আমি ঠিক সেই 56,309-বাইট ফাইলটি পরীক্ষা করি, যা TSK এক্সট্র্যাক্ট করতে ব্যর্থ হয়েছিল।

TSK দিয়েছিল:

expected:  56,309
recovered:      0
APFSBlock failure

libfsapfs দিয়ে ফাইল এন্ট্রি দিয়েছিল:

size: 56,309
MD5:  c6f56db33eafc1de0f52a035bc255dc7
RC:   0

এটাই প্রথম ফল, যা নির্ণয়কে বাস্তব অর্থে বদলে দেয়। ক্লোন করা বাইট বদলায়নি। পরীক্ষা ফাইল বদলায়নি। ক্ষতিগ্রস্ত APFS মেরামত করা হয়নি।

বদলেছিল APFS ইমপ্লিমেন্টেশন।

অন্তত এতটুকু তখন আমি জানতাম: TSK দিয়ে যা অনুদ্ধারযোগ্য মনে হচ্ছিল, সেই বাস্তব ব্যবহারকারী ফাইল-টি libfsapfs দিয়ে যথেষ্ট গভীর পর্যন্ত পৌঁছানো যাচ্ছিল, যাতে পুরো বিষয়বস্তু পড়ে ডাইজেস্ট হিসাব করা যায়।

টেরাবাইট পরিমাণ ডেটার জন্য libfsapfs-কে বিশ্বাস করার আগে আমি একটি জানা-খারাপ ফাইল এক্সট্র্যাক্ট করেছি

একটি সফল ডাইজেস্ট বহু-টেরাবাইট রিকভারি শুরু করার জন্য আমার কাছে যথেষ্ট ছিল না। গন্তব্য ডিস্কে আসল বাইটগুলো চাইছিলাম।

libfsapfs C API অনুমান করার বদলে আমি লাইব্রেরি উৎস পরীক্ষা করি এবং ডাইজেস্ট হিসাবের সময় fsapfsinfo যে রিড পাথ ইতিমধ্যে ব্যবহার করছিল সেটিই অনুসরণ করি। তারপর ওই একটি জানা পরীক্ষা ফাইলের জন্য ছোট একটি রিড-অনলি এক্সট্র্যাক্টর তৈরি করি।

এক্সট্র্যাক্টর ঠিক এই পরিমাণ লিখেছিল:

56,309 bytes

আলাদা রিকভারি ডিস্কে। এক্সট্র্যাক্ট করা ফাইলের MD5 ছিল:

c6f56db33eafc1de0f52a035bc255dc7

এটি আগের ডাইজেস্টের সঙ্গে মিলেছিল।

তারপরই কেবল আমি পদ্ধতিটি বড় পরিসরে চালাই। এক-ফাইলের পরীক্ষা তিনটি আলাদা বিষয় প্রমাণ করেছিল: পাথনেম সমাধান করা যাচ্ছিল, সম্পূর্ণ প্রত্যাশিত বাইট গণনা এক্সট্র্যাক্ট করা যাচ্ছিল, এবং এক্সট্র্যাক্ট করা বাইট আগের পূর্ণ-বিষয়বস্তু রিডের মতো একই ডাইজেস্ট তৈরি করছিল।

বাল্ক রিকভারির প্রধান সমস্যা ছিল ত্রুটি সীমাবদ্ধকরণ ও পুনরায় শুরু

libfsapfs যখন TSK-এর ব্যর্থ হওয়া একটি ফাইল উদ্ধার করতে পারল, কঠিন সমস্যাটি আবার বদলে গেল। আমার এমন একটি সিস্টেম দরকার ছিল, যা একটি ক্ষতিগ্রস্ত ব্রাঞ্চ, একটি রিবুট বা একটি Ctrl+C-এর কারণে পুরো কাজকে শূন্য থেকে রিস্টার্ট না করেই বিশাল ডিরেক্টরি ট্রি প্রক্রিয়া করতে পারে।

তাই বাল্ক রিকভারি পাইপলাইন কয়েকটি কঠোর অপরিবর্তনীয় শর্ত ব্যবহার করেছিল:

  • মাস্টার ক্লোন রিড-অনলি হিসেবে খোলা হতো।
  • উদ্ধার হওয়া আউটপুট কেবল দ্বিতীয় 5 TB ডিস্কে লেখা হতো।
  • ডিরেক্টরি ও ফাইল নাম অক্ষুণ্ণ রাখা হতো।
  • অগ্রগতি SQLite-এ থাকত, যাতে প্রক্রিয়া এক্সিট ও রিবুটের পরও টিকে থাকে।
  • প্রতিটি ফাইল প্রথমে অস্থায়ী পাথে লেখা হতো।
  • সম্পূর্ণ প্রত্যাশিত আকার লেখা শেষ হওয়ার পরেই অস্থায়ী ফাইলকে চূড়ান্ত পাথে রিনেম করা হতো।
  • পুনরায় শুরু করার সময় প্রত্যাশিত আকারের বিদ্যমান ফাইল শনাক্ত করা যেত।
  • ব্যর্থ বা সমস্যাযুক্ত কাজ সম্পন্ন কাজ থেকে আলাদা রাখা হতো।
  • খালি জায়গা পরীক্ষা করা হতো এবং নিরাপত্তা রিজার্ভ বজায় রাখা হতো।
  • পুনরায় তৈরি করা যায় এমন ডেভেলপমেন্ট আর্টিফ্যাক্ট এবং কম-মূল্যের সিস্টেম মেটাডেটা বাদ দেওয়া বা কম অগ্রাধিকার দেওয়া যেত।

একটি কর্মক্ষমতা পরিবর্তন সঙ্গে সঙ্গেই গুরুত্বপূর্ণ হয়ে ওঠে: প্রতিটি ফাইলের জন্য আলাদাভাবে APFS কনটেইনার পুনরায় খোলা বন্ধ করি।

দ্রুত পাথ একবারে একটি ডিরেক্টরি প্রক্রিয়া করত। একটি ওয়ার্কার উৎস খুলত, ডিরেক্টরিটি তালিকাভুক্ত করত, তার সরাসরি নিয়মিত ফাইলগুলো উদ্ধার করত এবং চাইল্ড ডিরেক্টরিগুলো কিউতে ফেরত দিত। কোনো ডিরেক্টরি ব্যর্থ বা টাইমআউট হলে কন্ট্রোলার সেটিকে DEFERRED চিহ্নিত করে সামগ্রিক পাস আটকে না রেখে এগিয়ে যেত।

স্বাভাবিক অপেক্ষমাণ কাজ আর না থাকলে পরে রাখা ডিরেক্টরিগুলো আরও বিচ্ছিন্ন প্রতি-ফাইল কাজ-সহ ধীর ফলব্যাক পাথ দিয়ে আবার দেখা হতো। অবশিষ্ট ব্যর্থতা-গুলো তখন স্বাধীনভাবে পুনরায় চেষ্টা করা যেত।

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

একটি বিশাল রিকার্সিভ কমান্ডের তুলনায় এই স্থাপত্য বাস্তব ব্যর্থতা প্যাটার্নের সঙ্গে অনেক বেশি মানানসই ছিল। ক্ষতি সমানভাবে ছড়ানো ছিল না, তাই রিকভারি সিস্টেমের অগ্রগতিও সমান হওয়া উচিত নয়।

কেন লক্ষ লক্ষ ফাইল হ্যাশ না করে প্রত্যাশিত-আকার পরীক্ষা ও অ্যাটমিক রিনেম ব্যবহার করেছি

এক-ফাইলের প্রমাণের সময় MD5 কার্যকর ছিল, কারণ TSK যে ফাইল পড়তে পারছিল না, পার্সার সেটির সম্পূর্ণ বিষয়বস্তু পড়ছে—এ বিষয়ে শক্ত প্রমাণ দরকার ছিল।

শুধু লক্ষ লক্ষ ফাইল হ্যাশ করার জন্য প্রতিটি উদ্ধার হওয়া বাইট দ্বিতীয়বার সম্পূর্ণ পড়লে বিপুল অতিরিক্ত I/O যোগ হতো। মূল এক্সট্রাকশন পাসের জন্য আমি অন্য একটি অপরিবর্তনীয় শর্ত ব্যবহার করেছি।

প্রতিটি নিয়মিত ফাইলের জন্য APFS মেটাডেটা একটি প্রত্যাশিত আকার দিত। ওয়ার্কার একটি অস্থায়ী ফাইল-এ লিখত এবং সম্পূর্ণ রিড ওই প্রত্যাশিত আকারের সঙ্গে মিললেই কেবল সেটিকে চূড়ান্ত পাথনেমে স্থানান্তর করত।

অর্থাৎ বিঘ্ন হলে ছোট হয়ে যাওয়া ফাইলের চূড়ান্ত নাম নিয়ে থেকে যাওয়ার কথা নয়।

আকার মিল কোনো ক্রিপ্টোগ্রাফিক অখণ্ডতা প্রমাণ নয়। আমি এটিকে তা হিসেবে বিবেচনা করি না। তবে প্রত্যাশিত-আকার যাচাই ও অ্যাটমিক রিনেম একসঙ্গে উচ্চ-ভলিউম পাসের জন্য বাস্তবসম্মত সঠিকতা সীমা ছিল, আর নমুনা ও জানা ব্যর্থতা কেসের জন্য লক্ষ্যভিত্তিক হ্যাশ তখনও উপকারী ছিল।

SQLite রিবুট-কে বিপর্যয়ের বদলে একঘেয়ে করে দিয়েছিল

রিকভারি এত দীর্ঘ চলেছিল যে আমাকে কম্পিউটার বন্ধ করে পরে পুনরায় শুরু করতে হয়েছিল। এই প্রয়োজন নকশাকে “স্ক্রিপ্ট” থেকে “রিকভারযোগ্য কর্মপ্রবাহ”-তে বদলে দেয়।

Ctrl+C সক্রিয় চাইল্ড প্রক্রিয়া-টিকে কেবল ফেলে রেখে যেত না। কন্ট্রোলার বিঘ্ন ধরত, ওয়ার্কার থামাত, সক্রিয় ডিরেক্টরি-কে রিকভারযোগ্য অবস্থায় ফিরিয়ে দিত, SQLite কমিট করত এবং এক্সিট করত।

একটি পরিষ্কার থামা ধারণাগতভাবে এমন দেখাত:

CURRENT DIRECTORY -> PENDING

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

রিবুটের পর আমি ডিস্ক পরিচয় পরীক্ষা আবার করি এবং একই রিকভারি কমান্ড চালাই। অবস্থা ডেটাবেস বিদ্যমান কিউ থেকেই পুনরায় শুরু করত।

পরবর্তী একটি রিস্টার্ট শুরু হয়েছিল এভাবে:

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

এটি সাধারণ অগ্রগতি বারের চেয়ে অনেক বেশি অর্থবহ ছিল। এতে দেখা যাচ্ছিল, সম্পন্ন হওয়া কয়েক দশ হাজার ডিরেক্টরি ইউনিট রিবুটের পরও টিকে আছে এবং বাকি কিউ স্পষ্টভাবে জানা।

আরও আগের একটি পুনরায় শুরু করার সময় আগের সেশন-এ বিঘ্নিত ডিরেক্টরি আবার নেওয়া হয়েছিল। প্রত্যাশিত আকারে আগে থেকেই থাকা ফাইল শনাক্ত হয়েছিল, এবং কেবল অনুপস্থিত কাজই লিখতে হয়েছিল। আমি ঠিক এই আচরণই চেয়েছিলাম: রিকভারি রিস্টার্ট করা যেন স্বাভাবিক ব্যাপার হয়, ভয়ের নয়।

326,799টি ফাইল ছিল পদ্ধতিটি স্কেল করতে পারে—এর প্রথম প্রমাণ

নতুন এক্সট্র্যাক্টরকে নির্বাচিত সব ডেটায় প্রসারিত করার আগে বাল্ক যাচাই লক্ষ্য হিসেবে আমি একটি বড় অগ্রাধিকার ট্রি ব্যবহার করেছি।

সম্পন্ন অবস্থা জানিয়েছিল:

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

গন্তব্যে ছিল:

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

অথবা প্রায়:

135.08 GiB

ফাইল গণনা একদম মিলে গিয়েছিল:

302,541 + 24,258 = 326,799

সম্পন্ন ট্রিতে কোনো অবশিষ্ট .partial.* ফাইল ছিল না।

56,309-বাইট পরীক্ষা ফাইল প্রমাণ করেছিল, TSK যেখানে ব্যর্থ হয়েছে সেখানে পার্সার সফল হতে পারে। 326,799-ফাইল রিকভারি প্রমাণ করেছিল, একই পদ্ধতি পুনরায় শুরু লজিক, বিদ্যমান-ফাইল শনাক্তকরণ এবং ওই সম্পন্ন পাসে কোনো রেকর্ড করা ফাইল ব্যর্থতা ছাড়াই একটি উল্লেখযোগ্য বাস্তব স্তরক্রম সামলাতে পারে।

শেষ হওয়ার আগেই বৃহত্তর রিকভারি 1.6 মিলিয়ন নতুন ফাইল অতিক্রম করেছিল

অগ্রাধিকার ট্রি পরিষ্কারভাবে শেষ হওয়ার পর আমি রিকভারি-কে বাকি নির্বাচিত টপ-লেভেল ডেটায় প্রসারিত করি।

ইচ্ছাকৃত একটি নিরাপদ বিরতিতে SQLite জানিয়েছিল:

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

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

এটি ছিল প্রায়:

840.72 GiB

ওই পর্যায়ে ডিরেক্টরি পাস-গুলোতে রেকর্ড হওয়া নতুন লেখা ডেটা।

একটি DEFERRED ডিরেক্টরি মানে ডেটা হারিয়ে যাওয়া নয়। এর মানে দ্রুত পাথ ইচ্ছাকৃতভাবে ওই স্থানীয় সমস্যাকে সম্পর্কহীন কাজ বিলম্বিত করতে দেওয়া বন্ধ করেছিল। এমন কেস পরে আবার দেখার জন্যই ফলব্যাক ধাপ ছিল।

পরের সেশন-গুলো একই ডেটাবেস থেকে পুনরায় শুরু হয়। সম্পন্ন গণনা বাড়ে এবং অপেক্ষমাণ কিউ কমে। শেষ পর্যন্ত আমার প্রয়োজনীয় প্রতিটি নির্বাচিত ও তালিকাভুক্ত ফোল্ডার ট্রি আমি উদ্ধার করেছি।

চূড়ান্ত রিকভারি সম্পর্কে আমি সত্যিই কী দাবি করতে পারি

আমি ফলাফলকে “প্রতিটি বাইট উদ্ধার হওয়া” বলে বর্ণনা করব না। প্রমাণ তা সমর্থন করে না।

মূল ddrescue ম্যাপ-এ তখনও প্রায় 13.84 MB ছিল, যা সফলভাবে কপি হয়েছে বলে নিশ্চিত করা যায়নি। এটাও আমি প্রমাণ করতে পারি না যে কোনো ফাইলসিস্টেম অবজেক্ট পুরোপুরি অনাবিষ্কৃত হয়ে যায়নি, কারণ সেটিকে তালিকাভুক্ত করার জন্য প্রয়োজনীয় মেটাডেটা ক্ষতিগ্রস্ত অঞ্চলের মধ্যে থাকতে পারে।

এই সীমাবদ্ধতা গুরুত্বপূর্ণ, কারণ কাঠামোবদ্ধ রিকভারি প্রমাণ করতে পারে যে একটি তালিকাভুক্ত অবজেক্ট এক্সট্র্যাক্ট হয়েছে; কিন্তু ক্ষতিগ্রস্ত নেমস্পেস আর দেখাতে পারে না এমন কোনো অবজেক্টটি ঐতিহাসিকভাবে কখনও ছিল না—এটি প্রমাণ করতে পারে না।

সবচেয়ে শক্তিশালী চূড়ান্ত বক্তব্য আরও সীমিত:

আমার প্রয়োজনীয় প্রতিটি নির্বাচিত ও তালিকাভুক্ত ফোল্ডার ট্রি কাঠামোবদ্ধ রিকভারি প্রক্রিয়ার মাধ্যমে সফলভাবে উদ্ধার হয়েছে।

মাস্টার ক্লোনকে ইন-প্লেস মেরামত করতে হয়নি। বাল্ক এক্সট্রাকশনের জন্য মূল ক্লিক করা Toshiba আবার ব্যবহার করতে হয়নি। ডিরেক্টরি কাঠামো ও ফাইলনাম হারিয়ে ফেলত এমন পুরো-ডিস্ক PhotoRec-ধাঁচের কার্ভিং পাস-ও দরকার হয়নি।

ব্যর্থ টুলগুলোও উপকারী প্রমাণ ছিল

পেছন ফিরে দেখলে সফল পথটি সহজ শোনায়:

ddrescue clone
    ↓
libfsapfs
    ↓
resumable extractor
    ↓
recovered files

কাজ চলার সময় অনুসন্ধান এমন সহজ মনে হয়নি, আর ব্যর্থ পদ্ধতিগুলো বাদ দিলে ইঞ্জিনিয়ারিং শিক্ষার বড় অংশও হারিয়ে যেত।

TSK আমাকে শিখিয়েছিল যে APFS নেমস্পেস ট্রাভার্সাল আর বিষয়বস্তু পুনরুদ্ধার আলাদা ব্যর্থতা মোড।

আমার প্রথম র‍্যাপার বাগ শিখিয়েছিল, রিকভারি স্ক্রিপ্ট-এর নিজের ত্রুটি শ্রেণিবিন্যাস কোনো চূড়ান্ত সত্য নয়।

হটস্পট ব্যবস্থা শিখিয়েছিল সামগ্রিক অগ্রগতি আটকে না দিয়ে স্থানীয় ব্যর্থতা বিচ্ছিন্ন করতে।

চেকপয়েন্ট পরীক্ষা শিখিয়েছিল APFS ট্রানজ্যাকশন চেকপয়েন্টকে স্ন্যাপশটের সঙ্গে গুলিয়ে না ফেলতে।

FUSE চেষ্টাগুলো শিখিয়েছিল, ব্যর্থ মাউন্টের জন্য ফাইলসিস্টেম ডেটা ছাড়াও স্ট্যাকের আরও কয়েকটি স্তর দায়ী হতে পারে।

--help-এ আটকে যাওয়া APFS রিডার আমাকে ডায়াগনস্টিক ব্যাখ্যা করার আগে টুলটিকে যাচাই করতে শিখিয়েছিল।

/dev/rdiskNs2 বনাম /dev/diskNs2 আচরণ শিখিয়েছিল, অন্তর্নিহিত ডিস্ক একই থাকলেও অপারেটিং-সিস্টেম I/O পাথ পার্সারের আচরণ বদলে দিতে পারে।

আর দুই-ড্রাইভ স্থাপত্য আমাকে মাস্টার ক্লোন অপরিবর্তিত রেখে অন্য সব জায়গায় ভুল করার স্বাধীনতা দিয়েছিল।

যে রিকভারি কর্মপ্রবাহ আমি আবারও ব্যবহার করব

  1. যান্ত্রিকভাবে ব্যর্থ হতে থাকা স্টোরেজে স্বাভাবিক ফাইলসিস্টেম কার্যকলাপ বন্ধ করব। রিড যদি থমকে যায়, ডিভাইস অদৃশ্য হয় বা ক্লিক করে, Finder দিয়ে ঘোরাঘুরির বদলে পুনরায়-শুরুযোগ্য ব্লক-লেভেল ক্লোনকে অগ্রাধিকার দেব।
  2. স্থায়ী mapfile ও যাচাইকৃত রিকভারি ডোমেইনসহ GNU ddrescue ব্যবহার করব। mapfile অগ্রগতি সংরক্ষণ করে; জানা উৎস আকার ডিভাইস-আকার বিভ্রান্তিকে রিকভারির অংশ হয়ে উঠতে বাধা দেয়।
  3. একটি মাস্টার ক্লোন রিড-অনলি রাখব। সেটিকে মেরামত, রিসাইজ, রিপার্টিশন করব না এবং উদ্ধার হওয়া ফাইল স্টোরেজ হিসেবেও ব্যবহার করব না।
  4. উদ্ধার হওয়া ফাইল দ্বিতীয় ভৌত ডিস্কে লিখব। উৎস সংরক্ষণ আর আউটপুট স্টোরেজ আলাদা কাজ।
  5. প্রথমে ক্লোন করা ফাইলসিস্টেমকে রিড-অনলি অবস্থায় সমস্যা নির্ণয় করব। করাপ্ট APFS মেটাডেটাসহ সুস্থ ভৌত ক্লোন একটি লজিক্যাল রিকভারি সমস্যা; ক্লিক করা উৎস ডিস্কের সমস্যার সঙ্গে এটি একই নয়।
  6. পার্সার পরীক্ষা হিসেবে একটি পুনরুৎপাদনযোগ্য ব্যর্থ ফাইল বেছে নেব। একটি 56 KB জানা-খারাপ ফাইল আমাকে বিকল্প পার্সার সম্পর্কে ঘণ্টার পর ঘণ্টা অন্ধ বাল্ক এক্সট্রাকশনের চেয়ে বেশি তথ্য দিয়েছিল।
  7. পার্সারকেই যাচাই করব। উৎস অর্থপূর্ণভাবে পড়ার আগেই কোনো টুল আটকে গেলে সেটিকে ডেটা হারিয়ে যাওয়ার প্রমাণ হিসেবে ধরব না।
  8. একটি APFS ইমপ্লিমেন্টেশনই উদ্ধারযোগ্যতা নির্ধারণ করে—এমন ধরে নেব না। একই ক্লোন করা বাইটে TSK ও libfsapfs খুব ভিন্ন আচরণ করেছিল।
  9. অস্থায়ী ডিভাইস নম্বর নয়, স্থিতিশীল বৈশিষ্ট্য দিয়ে ডিস্ক শনাক্ত করব। ফাইলসিস্টেম UUID ও জানা ভৌত আকার গতকালের /dev/diskN-এর চেয়ে নিরাপদ।
  10. দীর্ঘ এক্সট্রাকশন শুরু থেকেই পুনরায়-শুরুযোগ্য করব। স্থায়ী অবস্থা, অস্থায়ী ফাইল, অ্যাটমিক রিনেম, পরে রাখা কাজ, সীমাবদ্ধ পুনরায় চেষ্টা এবং পরিষ্কার শাটডাউন হ্যান্ডলিং—এই স্কেলে সঠিকতার অংশ।
  11. যাচাই স্তর আলাদা রাখব। ddrescue শতাংশ, দৃশ্যমান পাথনেম, প্রত্যাশিত-আকার মিল এবং বিষয়বস্তু হ্যাশ—প্রতিটি আলাদা বিষয় প্রমাণ করে।

যে সংখ্যাটি শেষসীমা মনে হয়েছিল, সেটি আসলে শুধু প্রথম ধাপের শেষ ছিল

পুরো রিকভারিতে সবচেয়ে বিভ্রান্তিকর সংখ্যা তখনও ছিল:

100.00%

এটি যেন “আমি কি ডিস্কটি বাঁচাতে পেরেছি?” প্রশ্নের উত্তর মনে হচ্ছিল।

আসলে এটি আরও সীমিত একটি প্রশ্নের উত্তর দিয়েছিল:

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

APFS নেমস্পেস পুনর্গঠন করতে পারবে কি না—এটির উত্তর এটি দেয়নি। একটি পার্সার যে ক্ষতিগ্রস্ত মেটাডেটা ট্রাভার্স করতে পারে, অন্যটি তা প্রত্যাখ্যান করলে সেই পার্থক্যের উত্তরও দেয়নি। উদ্ধার হওয়া ফাইলের প্রত্যাশিত দৈর্ঘ্য আছে কি না, তাও বলেনি। আর বহু-মিলিয়ন-ফাইল এক্সট্রাকশন ত্রুটি ও রিবুট সহ্য করে নিজের অবস্থা করাপ্ট না করে টিকে থাকতে পারবে কি না, সেটিও বলেনি।

প্রতিটি স্তরের জন্য আমার আলাদা প্রমাণ দরকার ছিল।

ভৌত রিকভারি প্রথমে সফল হয়। ফাইলসিস্টেম তখনও ক্ষতিগ্রস্ত ছিল। প্রথম পার্সার নাম দেখাতে পারত, কিন্তু বহু ফাইলের বিষয়বস্তুতে ব্যর্থ হচ্ছিল। ভিন্ন একটি APFS ইমপ্লিমেন্টেশন একই পরীক্ষা ফাইল সফলভাবে পড়ে। সেই প্রমাণ থেকে এক-ফাইল এক্সট্র্যাক্টর তৈরি হয়, এক্সট্র্যাক্টর থেকে পুনরায়-শুরুযোগ্য রিকভারি ইঞ্জিন, আর ইঞ্জিন শেষ পর্যন্ত মাস্টার ক্লোন অক্ষত রেখেই আমার প্রয়োজনীয় নির্বাচিত ফোল্ডার ট্রিগুলো দ্বিতীয় ডিস্কে উদ্ধার করে।

মূল হার্ড ড্রাইভ কখনও আবার সুস্থ হয়ে ওঠেনি। APFS-ও জাদুর মতো নিজেকে মেরামত করেনি। বদলেছিল রিকভারি মডেল।

ব্লক রিকভারি, ফাইলসিস্টেম রিকভারি এবং ফাইল যাচাই আলাদা ইঞ্জিনিয়ারিং ধাপ। এগুলোকে এক সমস্যা হিসেবে দেখলে পরিস্থিতি প্রায় আশাহীন মনে হচ্ছিল। আলাদা করায় সমস্যাটি সামলানো সম্ভব হয়।