2006年4月23日 星期日

Ext2fs-Undeletion

◇ [chat] 搶救過去!!!! (ext2 undelete)

作者: thhsieh (居士) 看板: Linux
標題: [chat] 搶救過去!!!! (ext2 undelete)
時間: Tue Feb 2 14:53:04 1999

以下內容可能很多人早就知道了,但我想可能還是對某些人有用,所以就
post 上來大家參考 (嗯! 最近算物理問題算得煩透了,就當我無聊灌灌
水吧 :-)))

===========================================================================

發信人: thhsieh (居士), 信區: SM
標 題: [Sys] 搶救過去!!!! (ext2 undelete)
發信站: 冷月流蘇BBS驛站 (Mon Feb 1 17:21:11 1999) , 轉信

本系的 BBS 系統真是多災多難 (嗯 .... 其實是因為我的疏忽,才會這麼多災
多難 ....) ,繼這幾日系統時間不正確,造成許多人的 ID 被誤砍後,又一次
因系統設定上的問題,將 BBS 的重要備份檔給殺了。這件事是學弟發現後告訴
我的,當我上站來一見到他的 mail, 當真是欲哭無淚,差點沒去撞牆。那個備
分檔有多重要呢? 這麼說吧! 所有等待我們恢復舊信件的使用者資料全在裏頭。

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

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

終於被我找到了!! 原來這方面的技術文件就存在我自己的系統中 :-))

/usr/doc/HOWTO/mini/Ext2fs-Undeletion.gz

於是我就按照這份文件的指示一步步來,總算將一個長達 8MB 的壓縮檔救回了
99%, 還有一個長達 1.1 MB 的壓縮檔完整無缺地救了回來。感謝上帝、 Linux
的設計者、寫那篇文件的作者、曾經討論過此技術的人、以及 Linux 如此優秀
的 ext2 filesystem, 讓我有機會搶救過去。現在,我將我的搶救步驟做一個整
理讓大家參考,希望有派得上用場的時候 (喔! 不,最好是希望大家永遠不要有
機會用到以下的步數 :-)))

************************************************************************

在此嚴正聲明!! 寫這篇文章的目的,是給那些處於萬不得已情況下的人們,有
一個挽回的機會,並不意味著從此我們就可以大意,砍檔不需要三思。前面提
到,我有一個檔案無法 100% 救回,事實上,長達 8MB 的檔案能救回 99% 已是
幸運中的幸運,一般的情況下若能救回 70% - 80% 已經要愉笑了。所以,不要
指望 undelete 能救回一切。預防勝於治療! 請大家平時就養成好習慣,砍檔前
請三思!!!

************************************************************************


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

1. 檔案原來所在的磁區是否沒有被覆寫?
2. 檔案是否完全連續?

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

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

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


1. 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。


2. 執行

echo lsdel | debugfs /dev/hda1 | less

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

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


請注意! inode 裏頭不會記錄檔案的檔名,檔名是記錄在該檔案所在的目錄的
data block 中,故在此我們見不到。因此,我們必須要在檔案大小、被砍時間
等資訊中判斷出要救回的檔案是那一個。在此,我們要救回 29773 這個 inode。


3. 執行

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 才對啊!
在這裏要談一下 UNIX like filesystem 的「奧密」了。上頭所列的前 12 個 block
是真正指到檔案資料的 block, 稱之為 direct block 。第 13 個稱為第一階
indirect block, 第 14 個稱為第二階 indirect block 。什麼意思? 該檔的資料
所在的 block 位置如下:

123134
123136
123137
123138
......
131409
131410
131411 =========> 131412
131413
131414 (第一階,共 256 個)
......
131667

131668 =========> 131669 ==========> 131670
131671
131672 (共 256 個)
......
131925
131926 ==========> 131927
131928
131929 (共 256 個)
......
132182
132183 ==========> 132184
132185 (共 256 個)
......

各位明白嗎? 第 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 。

理論上,我們只要將這些 index block 的內容全部讀出來,然後照這些 index 把
所有的 block 全部讀到手,就能 100% 救回檔案 (假設這些 block 全部沒有被新
檔案覆寫的話)。工程很大,但是可行。不幸的是,在 kernel-2.0.33, 其設計是,
如果該檔案被砍了,則這些 index block 全部會規零,因此我所讀到的是

0 0 0 0 0 ..... (共 256 筆)

哇! 沒辦法知道這些 data block 真正所在的位置。所以,在此我們做了一個很大
的假設: 整個檔案所在的 block 是連續的! 也就是我上頭的例子。這也就是為什
麼說,只有連續 block (是指後頭的 indirect block) 的檔案才能完整救回,而
這一點就要聽天由命了。


4. 好了,現在我們只好假設所有的檔案處於連續的 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, ....) 了。


5. 最後一步,就是把檔案「剪」出來,並看看我們救回多少了。在這裏我們可以用
split 這個工具,假設我們重覆上述步驟,弄出來的 recover 檔大小為 8294400,
而我們要的大小是 8220922, 那就這樣下指令:

split -b 8220922 recover 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 設計者正著手研究,看能否克服這個困難,也就是在檔案砍掉時,不要
將 index block 規零。我剛剛試一下 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
157
642 157643 157644 157645 157646 157647 157648 157649 157650 157651 157652
157653
157654 157655 157656 157657 157658 157659 157660 157661 157662 157663 157664
15
7665 157666 157667 157668 157669 157670 157671 157672 157673 157674 157675
15767
6 157677 157678 157679 157680 157681 157682 157683 157684 157685 157686
157687 1
............................................................................
9745 159746 159747 159748 159749 159750 159751 159752 159753 159754 159755
15975
6
TOTAL: 2126


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


以上資料,謹供參考。

參考文件: Ext2fs-Undeletion Mini HOWTO



──── 居 士 ────

Email: thhsieh@twcpro.phys.ntu.edu.tw

HomePage: http://twcpro.phys.ntu.edu.tw/~thhsieh
※ Origin: 臺大電機 Maxwell 站 ◆ From: twcpro.phys.ntu.edu.tw


--
Reference:
http://bbs.ee.ntu.edu.tw/boards/Linux/19/1/84.html
http://www.linuxuser.com.tw/power/list2.php?id=2215

沒有留言: