這是一個硬碟的老梗問題,呆呆翰大概在國小的時候就遇過這種問題:資料讀到一半會硬碟掛掉,你得把硬碟電源拔掉、傳輸線(當年IDE現在是SATA)拔掉,然後重新接上去(IDE的年代是重新開機)才能繼續讀資料。重開後讀取沒多久,硬碟又會掛掉,你得重複一次以上的動作才能。
以前沒有USB的那個年代,要從這樣的景況挽救資料幾乎是不可能,不過拜現代科技所賜,我們有USB轉接線--將IDE轉為USB,就可以把整台電腦重開機,變成拔掉USB傳輸線、關掉外接電源、重新打開外接電源、接上USB傳輸線;如果是SATA的硬碟更是可以開啟熱插拔(不過根據測試,SATA熱插拔遇到這種讀到一半掛掉的硬碟,似乎不太能清除乾淨,建議還是透過USB轉接)。總之,在現代要從這樣的硬碟讀出資料已經不再是天方夜談了(但同樣的,現代硬碟資料更多,你得花更多時間處理)。
在開始打撈前往基隆港外海打撈金剛資料之前,我們得先對電腦的硬碟稍微有點了解。首先要先來了解「封裝」的概念:
封裝的概念說起來很複雜、講起來其實也沒什麼。
以現實生活來講:就是你要寫字你不會想說「左手食指肌肉用多少力、在往什麼方向走多少、接著肌肉再用多少把筆提起來、在OOXX……」,你會直接想說「我要寫個王字」;
又或者是說你要彈鋼琴,你不會想說「左手食指、中指、小拇指,以小拇指對準第XX個琴鍵按下去」,反而會想說「我要按個C和弦」;
以程式設計來講:就是把功能包成一個function,這樣在使用時只要呼叫function就可以簡單地使用這項功能,像是你瀏覽網頁不會去研究說你到底送出了甚麼樣的電流出去,你只會在一你按了哪個連結。
這就是封裝的概念,功能一層一層的包下去。我們這裡以寫字來示範,電腦怎麼封裝:「肌肉力道的控制」→封裝→「中國字的基本筆畫,橫豎撇捺等等」→再封裝→「中國字的基本偏旁,耳月三點水等等等」→再封裝→「單詞,舉起、生命、示範、寫字等等」……
電腦的硬碟也有類似的結構,如果你曾經上過計算機概論應該會知道:「一塊一塊的磁區」→「組成一條一條的磁軌」→「組成一片一片的磁盤」→「組成一顆一顆的硬碟」
而電腦存取檔案的時候,也是一層一層呼叫:「讀取檔案」→呼叫「檔案系統管理」→呼叫「硬碟存取特定磁區」
如果我們我們在「檔案系統」這一層讀取這顆壞掉的硬碟,非常的不實際(假如說一個影片存在12,13,14,15,16,17這六個磁區,而你的硬碟一讀到14號磁區就掛點要重開,你要怎麼把後面16,17號磁區的資料取出?)而且也不方便(如果檔案系統有些許故障需要檢查,偏偏一檢查就掛掉你要怎麼繼續讀取資料),因此我們得先回到最開始的「磁區」這一層,以磁區為單位複製資料到另外一顆硬碟,再去讀取裡面的資料。
而且像這次呆呆翰遇到的狀況,就是檔案系統已經故障,需要做fsck來修復檔案系統後,才能存取裡面的檔案。偏偏這硬碟讀取沒多久就掛點,fsck都無法完成要怎麼繼續讀取資料?所以這次的救援是:將壞掉檔案系統從壞掉的硬碟複製出去→將複製出來的資料存回一顆可以正常使用的硬碟→fsck修復檔案系統→把資料讀出來→嘗試修復硬碟(Seatools可以修復部分損壞,但是也可能造成原本資料讀不出來)
但是以「磁區」為單位的複製工具要上哪裡找呢?其實大部分的Linux都有包含一個名為dd的程式(Disk Dump),他就是完全以磁區為單位去作複製的工具(也就是說就算500GB的分割只裝45G的資料,他還是要複製500G的資料歐),正符合我們的需求。而他的指令如下:
dd if=輸入裝置 skip=跳過區塊數 of=輸出裝置 seek=輸出的從多少個區塊後開始 bs=區塊大小
其中,區塊大小就是硬碟的磁區大小,常見的規格應該是4K,早期的是512Byte,不過dd指令接受的單位都是Byte,所以這裡只會有兩種數字:512或4096。
但是dd這個程式還有一個缺點:預設他要全部完成(或是遇到錯誤)才會顯示訊息,可是有時候讀取很久沒進度已經可以直接切掉,這該怎麼辦?其實dd這個程式只要傳送一個USR1的訊號,他就會告訴你已經處裡多少資料。在這裡,我們另外一個視窗輸入以下指令:
watch -n 5 pkill -USR1 ^dd$
這樣,watch這個程式就會每五秒送一個USR1的訊號給dd這隻程式瞜,這樣如果看等超過五秒沒有新進度也可以提前把USB線拔掉節省時間。
接著呆呆翰為了安全起見,先把分割區存成檔案,等之後再回復(安全比較重要)。呆呆翰要輩分的分割區是sdc7,呆呆翰想把備份檔案存到/home/partimage/,作法是這樣:
dd if=/dev/sdc7 of=/home/partimage/sdc1.0s-ns bs=512
接著過了一陣子,他就出現了I/O錯誤,以及以下的訊息:
184565760+0 records in
184565760+0 records out
94497669120 bytes (94 GB) copied, 3145.27 s, 30.0 MB/s
這告訴我們,dd在出現I/O錯誤之前,成功讀取到184565760個區塊。所以在這裡,我們試試看從184565760這個區塊開始繼續讀取:
dd if=/dev/sdc7 skip=184565760 of=/home/partimage/sdc1.184565760s.img bs=512
沒多久,I/O錯誤還是會出現的,重複以上的動作(可能會需要超過50次甚至更多)把整個硬碟讀完吧。如果要偷懶可以撰寫以下的腳本:
function backup() {
echo "dd if=/dev/sdc7 skip=${1} of=/home/partimage/sdc1.${1}s.img bs=512"
"dd if=/dev/sdc7 skip=${1} of=/home/partimage/sdc1.${1}s.img bs=512"
}
這樣只要輸入以下的指令,就可以完成上上面好長好長的指令:
backup 184565760
在這裡先講,這是個相當辛苦的工作,呆呆翰有一口氣讀取88GB的資料,也有一次只讀取44KB資料。一次能讀取多少資料真的都看運氣,會不會剛好壞掉檔案配置表FAT也很難說,總之非常非常需要有耐心恆心毅力,
完成讀取後,再用同樣的工具把檔案一個一個寫回去吧(已經建立一個sda8分割區,容量與原本的sdc7相同,且磁區數量也相同):
dd if=/home/partimage/sdc1.0s.img of=/dev/sda8 bs=512
dd if=/home/partimage/sdc1.184565760s.img of=/dev/sda8 seek=184565760 bs=512
...
...
到這裡,sda8已經被寫入原本sdc7壞掉的分割區了,呆呆翰的sdc7是btrfs的檔案系統,所以用以下指令修復:
fsck.btrfs /dev/sda8
之後,來試試看把它掛上去吧:
mount -o ro -t btrfs /dev/sda8 /mnt/
如果成功,就趕快把重要資料複製出來吧!!
在這裡祝大家都能把資料順利撈出來~呆呆翰寫這篇文章的時候還在撈(認真)
最後,呆呆翰的硬碟,雖然大部分的區塊都可以讀取,但是最重要的紀錄檔案位置與檔名的部分已經無法讀取,所以最後甚麼資料也沒撈出來(汗顏),務必養成備份重要資料的習慣歐!
參考資料:
SYBASE資料庫技術,資料庫恢復-顯示dd指令的進度
遊手好弦 信步塗鴉-dd sKip 和 seek參數理解
鳥歌的Linux私房菜-fdisk