實戰分析ext2檔案系統下恢復誤刪除檔案(轉)

gugu99發表於2007-08-10
實戰分析ext2檔案系統下恢復誤刪除檔案(轉)[@more@]

  本系的 BBS 系統真是多災多難 (嗯....其實是因為我的疏忽,才會這麼多災多難 ....),繼這幾日系統時間不正確,造成許多人的ID誤砍後,又一次因系統設定上的問題,將BBS的重要備份檔給殺了。這件事是學弟發現後告訴我的,當我上站來一見到他的 mail, 當真是欲哭無淚,差點沒去撞牆。

  那時已是週六晚11:00左右,我一邊想著要編一套說辭向大家解釋無法替大家恢復舊信件與設定了,一邊還在想是否能夠挽回局面。大家知道,UNIXlike的系統是很難像 M$ 的系統一樣,做到 undelete 的,所有網管前輩都曾再三警告我們,要小心! 小心!砍檔之前三思而後行,砍了之後再後悔也沒用。雖然我已漸漸做到砍檔三思而後行,但之次誤砍事件是系統在背景中定時執行的,等到我找出原因時已是檔案被砍後一個多小時。

  我憑著一點點的印象,想起在網路上,有人討論過在 Linux ext2 filesystem中 undelete的可能性,但我所見到的多半是負面的答案,但好象真的有人做過這件事,於是我第一個所做的,就是馬上將該檔案原來所在的 partition mount成 read-only, 禁止任何的寫入動作,不是怕再有檔案被誤砍(因為已沒什麼可砍的了),而是怕有新檔案寫進來,新資料可能會覆蓋到舊資料原本存在的磁區(block)。我們現在唯一個指望,就是企圖將檔案原來存在的磁區一個個找回來,並且「希望」這些磁區上的舊資料都還在,然後將這些磁區串成一個檔案。終於被我找到了!!原來這方面的技術檔案就存在我自己的系統中 :-)) /usr/doc/HOWTO/mini/Ext2fs-ndeletion.gz

  於是我就按照這份檔案的指示一步步來,總算將一個長達 8MB 的壓縮檔救回了 99%, 還有一個長達 1.1 MB 的壓縮檔完整無缺地救了回來。感謝上帝、 Linux 的設計者、寫那篇檔案的作者、曾經討論過此技術的人、以及 Linux 如此優秀的 ext2filesystem,讓我有機會搶救過去。現在,我將我的搶救步驟做一個整理讓大家參考,希望有派得上用場的時候 (喔! 不,最好是希望大家永遠不要有機會用到以下的步數 :-))) 在此嚴正宣告!! 寫這篇文章的目的,是給那些處於萬不得已情況下的人們,有一個挽回的機會,並不意味著從此我們就可以大意,砍檔不需要三思。前面提到,我有一個檔案無法100%救回,事實上,長達 8MB 的檔案能救回 99% 已是幸運中的幸運,一般的情況下若能救回 70% - 80% 已經要愉笑了。所以,不要指望 undelete 能救回一切。預防勝於治療! 請大家平時就養成好習慣,砍檔前請三思!!! 理論分析

  我們能救回的機會有多大? 在 kernel-2.0.X 系列中 (本站所用的 kernel 是 2.0.33) ,取決以下兩點:

  檔案原來所在的磁區是否沒有被覆寫?

  檔案是否完全連續?

  第一點我們可以與時間競賽,就是當一發現檔案誤砍時,要以最快的速度 umount 該filesystem,或將該filesystemremount成唯讀。就這次的情況而言,檔案誤砍是在事發一個小時後才發現的,但由於該filesystem寫入的機會很少(我幾乎可確定一天才只有一次,做 backup),所以第一點算是過關了。

  第二點真的是要聽天由命了,就本站所使用的kernel,必須要在假設「長檔案」所佔的 block 完全連續的情況下,才有可能完全救回來! 一個 block 是 1024 bytes,長達 8 MB的檔案就有超過 8000 個 block。在經常讀寫的 filesystem 中,可以想見長檔案很難完全連續,但在我們的系統中,這一點似乎又多了幾分指望。同時,Linuxext2如此精良的filesystem,能做到前7950多個block都連續,這一點也功不可沒。

  好了,以下我就講一下我的步驟。

  搶救步驟 I - mount filesystem readonly

  該檔案的位置原來是在 /var/hda/backup/home/bbs 下,我們系統的filesystem 組態是:

  root@bbs:/home/ftp/rescue# df

Filesystem 1024-blocks Used Available Capacity Mounted on

/dev/sda1 396500 312769 63250 83% /

/dev/sda3 777410 537633 199615 73% /home

/dev/hda1 199047 36927 151840 20% /var/hda

/dev/hda2 1029023 490998 485710 50% /home/ftp

  因此 /var/hda 這個 filesystem 要馬上 mount 成 readonly (以下請用 root 身份):mount -o remount,ro /var/hda

  當然也可以直接 umount 它,但有時候可能有某些 process 正在此 filesystem下運作,您可能無法直接 umount 它。因此我選擇 mount readonly。但您也可以用: fuser -v -m /usr

  看一下目前是那些 process 在用這個 filesystem, 然後一一砍掉,再 umount。

  搶救步驟 II

  執行 echo lsdel | debugfs /dev/hda1 | less

  看一下該 filesystem 最近被砍的 inode (檔案) 有那些 (為什麼是 /dev/hda1?請見上頭的df列表)?在這F檔案的重要資訊,如大小、時間、屬性等等。就我們的系統而言,其列示如下:

  debugfs: 92 deleted inodes found.

Inode Owner Mode Size Blocks Time deleted

....................................................................

29771 0 100644 1255337 14/14 Sat Jan 30 22:37:10 1999

29772 0 100644 5161017 14/14 Sat Jan 30 22:37:10 1999

29773 0 100644 8220922 14/14 Sat Jan 30 22:37:10 1999

29774 0 100644 5431 6/6 Sat Jan 30 22:37:10 1999

  請注意!我們必須要在檔案大小、被砍時間等資訊中判斷出要救回的檔案是那一個。在此,我們要救回 29773 這個 inode。 搶救步驟 III

  執行 echo "stat <29773>" | debugfs /dev/hda1

  列出該 inode 的所有資訊,如下:

  debugfs: stat <29773>

Inode: 29773 Type: regular Mode: 0644 Flags: 0x0 Version: 1

User: 0 Group: 0 Size: 8220922

File ACL: 0 Directory ACL: 0

Links: 0 Blockcount: 16124

Fragment: Address: 0 Number: 0 Size: 0

ctime: 0x36b31916 -- Sat Jan 30 22:37:10 1999

atime: 0x36aebee4 -- Wed Jan 27 15:23:16 1999

mtime: 0x36adec25 -- Wed Jan 27 00:24:05 1999

dtime: 0x36b31916 -- Sat Jan 30 22:37:10 1999

BLOCKS:

123134 123136 123137 123138 123140 131404 131405 131406

131407 131408 131409 131 410 131411 131668

TOTAL: 14

  現在的重點是,必須將該inode所指的檔案,所指的block全部找回來。在這它?14

  個block?不對啊!應該要有8000多個block才對啊!在這Filesystem的奧妙了。上頭所列的前 12 個 block 是真正指到檔案資料的 block, 稱之為 direct block 。第 13個稱為第一階 indirect block, 第 14 個稱為第二階 indirect block 。什麼意思? 該檔案的資料所在的 block 位置如下:

  各位明白嗎? 第 13 個 (131411) 與第 14 個 block 其實不是 data, 而是 index,它指出接下來的 block 的位置。由於一個 block 的大小是 1024 bytes, 一個 int 在 32 位系統中是 4 bytes, 故一個 block 可以記錄 256 筆資料。以 131411 block 為例,它所記錄的資料即為 (在檔案未砍前): 131412 131413 131414 .... 131667 (共 256 筆)

  而這256個block就真正記錄了檔案資料,所以我們稱為第一階。同理,第二階就有

  兩個層 index, 以 131668 來說,它可能記錄了: 131669 131926 132182 .... (最多有 256 筆)

  而 131669 的 block 記錄為: 131670 131671 131672 .... 131925 (共 256筆)而這256個block才是真正儲存檔案資料的。而我們要的,就是這些真正儲存檔案資料的block。理論上,我們只要將這些indexblock的內容全部讀出來,然後照這些 index把所有的block全部讀到手,就能100%救回檔案(假設這些block全部沒有被新檔案覆寫的話)。工程很大,但是可行。不幸的是,在 kernel-2.0.33, 其設計是,如果該檔案被砍了,則這些 index block 全部會規零,因此我所讀到的是 0 0 0 0 0 ..... (共 256 筆)

  哇!沒辦法知道這些datablock真正所在的位置。所以,在此我們做了一個很大的假

  設:整個檔案所在的block是連續的!也就是我上頭的例子。這也就是為什麼說,只有連續block(是指後頭的indirectblock)的檔案才能完整救回,而這一點就要聽天由命了。 搶救步驟 IV

  好了,現在我們只好假設所有的檔案處於連續的block上,現在請用http://archie.ncu.edu.tw去找這個工具:fsgrab-1.2.tar.gz,並將它安裝起來。因為步驟很簡單,故在此我就不多談。我們要用它將所需的 block 全部抓出來。它的用法如下: fsgrab -c count -s skip device

  其中 count 是隻要 (連續) 讀幾個, skip是指要從第幾個開始讀,例如我要從 131670 開始連續讀 256 個,就這樣下指令: fsgrab -c 256 -s 131670 /dev/hda1 > recover

  現在我們就開始救檔案吧! 以上頭的資料,我們必須用以下的指令來救: (注意到頭開的 12 個 block 並沒有完全連續!!!)

  fsgrab -c 1 -s 123134 /dev/hda1 > recover

fsgrab -c 3 -s 123136 /dev/hda1 >> recover

fsgrab -c 1 -s 123140 /dev/hda1 >> recover

fsgrab -c 7 -s 131404 /dev/hda1 >> recover

  這是開頭的 12 個 block, 對於第一階 indirect, 就資料來看好象是連續的 :-)) fsgrab -c 256 -s 131412 /dev/hda1 >> recover

  注意要跳過 131411, 因為它是 index block。對於第二階 indirect, 我們 *假設* 它們都是連續的:

  fsgrab -c 256 -s 131670 /dev/hda1 >> recover

fsgrab -c 256 -s 131927 /dev/hda1 >> recover

fsgrab -c 256 -s 132184 /dev/hda1 >> recover

............................................

  要一直做,直到 recover 的大小超過我們所要救回的檔案大小(8220922)為止。要跳過那些 index block (如 131668, 131669, 131926, 132183, ....) 了。搶救步驟 V

  最後一步,就是把檔案「剪」出來,並看看我們救回多少了。我們重複上述步驟,弄出來的 recover 檔大小為8294400,而我們要的大小是8220922,那就這樣下指令:

  split -b 8220922recover rec

  則會做出兩個檔,一個是recaa,大小是8220922,另一個是recab則是剩下的大小,後者是垃圾,扔了即可。現在我們可以檢查這個檔案是不是「完整」的那個被誤砍的檔案了。由於我們的那個檔案是 .tar.gz 的格式,於是我們這個方法來檢查:

  mv recaa recaa.tar.gz

  zcat recaa.tar.gz > recaa.tar

  如果沒有錯誤訊息,那表示成功了!完全救回來了。但不幸的是,我們沒有成功,將弄出的 recaa.tar 改名再 gzip 之後,與原來的 recaa.tar.gz 比一下大小,發現少了 1%, 表示說該檔原來所在的 block 中最後有 1% 是不連續的 (或者被新寫入的檔案覆寫了),但這已是不幸中的大幸了。

  後記

  對於在 undelete時假設所有 block 連續的問題,那份 HOWTO 檔案說Linus與其它kernel設計者正著手研究,看能否克服這個困難,也就是在檔案砍掉時,不要將indexblock規零。我剛剛試一下kenrel-2.2.0的環境,發現已做到了!! 以下是一個已砍的檔案的 inode data (由 debugfs 所讀出):

  debugfs: Inode: 36154 Type: regular Mode: 0600 Flags: 0x0 Version: 1

User: 0 Group: 0 Size: 2165945

File ACL: 0 Directory ACL: 0

Links: 0 Blockcount: 4252

Fragment: Address: 0 Number: 0 Size: 0

ctime: 0x36b54c3b -- Mon Feb 1 14:39:55 1999

atime: 0x36b54c30 -- Mon Feb 1 14:39:44 1999

mtime: 0x36b54c30 -- Mon Feb 1 14:39:44 1999

dtime: 0x36b54c3b -- Mon Feb 1 14:39:55 1999

BLOCKS:

147740 147741 147742 147743 147744 147745 147746 147747 147748 147769

147770 157642 157643 157644 157645 157646 157647 157648 157649 157650

157651 157652 157653 157654 157655 157656 157657 157658 157659 157660

157661 157662 157663 157664 157665 157666 157667 157668 157669 157670

157671 157672 157673 157674 157675 157676 157677 157678 157679 157680

157681 157682 157683 157684 157685 157686 157687 1...................

.........9745 159746 159747 159748 159749 159750 159751 159752 159753

159754 159755 159756

TOTAL: 2126

  真是太完美了!! 這意味著在 kernel-2.2.X 的環境下,我們不必假設所有的 block 都連續,而且可以百分之百找回所有砍掉的 block! 因此上述的第二個風險就不存在了。

  以上資料,謹供參考。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/10748419/viewspace-939892/,如需轉載,請註明出處,否則將追究法律責任。

相關文章