linux 恢復數據

對於不少 Linux 的用戶來講,可能有一個問題一直都很是頭疼:對於那些不當心刪除的數據來講,怎樣才能恢復出來呢?你們知道,在 Windows 系統上,回收站中保存了最近使用資源管理器時刪除的文件。即使是對於那些在命令行中刪除的文件來講,也有不少工具(例如recover4all,FinalData Recovery)能夠把這些已經刪除的文件恢復出來。在Linux 下這一切是否可能呢?node

實際上,爲了方便用戶的使用,如今 Linux 上流行的桌面管理工具(例如gnome和KDE)中都已經集成了回收站的功能。其基本思想是在桌面管理工具中捕獲對文件的刪除操做,將要刪除的文件移動到用戶根目錄下的 .Trash 文件夾中,但卻並不真正刪除該文件。固然,像在 Windows 上同樣,若是用戶在刪除文件的同時,按下了 Shift 鍵並確認刪除該文件,那麼這個文件就不會被移動到 .Trash 文件夾中,也就無從恢復了。此時,習慣了使用 Windows 上各類恢復工具的人就會頓足捶胸,抱怨 Linux 上工具的缺少了。可是請稍等一下,難道按照這種方式刪除的文件就真的無從恢復了麼?或者換一個角度來看,使用 rm 命令刪除的文件是否還有辦法可以恢復出來呢?linux

背景知識

在開始真正進行實踐以前,讓咱們首先來了解一下在 Linux 系統中,文件是如何進行存儲和定位的,這對於理解如何恢復文件來講很是重要。咱們知道,數據最終以數據塊的形式保存在磁盤上,而操做系統是經過文件系統來管理這些數據的。ext2/ext3 是 Linux 上應用最爲普遍的文件系統,本文將以 ext2 文件系統爲例展開介紹。數組

咱們知道,在操做系統中,文件系統是採用一種層次化的形式表示的,一般能夠表示成一棵倒置的樹。全部的文件和子目錄都是經過查找其父目錄項來定位的,目錄項中經過匹配文件名能夠找到對應的索引節點號(inode),經過查找索引節點表(inode table)就能夠找到文件在磁盤上的位置,整個過程如圖1所示。app

圖 1. 文件數據定位過程
文件數據定位過程

對於 ext2 類型的文件系統來講,目錄項是使用一個名爲 ext2_dir_entry_2 的結構來表示的,該結構定義以下所示:ide

清單 1. ext2_dir_entry_2 結構定義
struct ext2_dir_entry_2 {
        __le32  inode;                  /* 索引節點號 */
        __le16  rec_len;                /* 目錄項的長度 */
        __u8    name_len;               /* 文件名長度 */
        __u8    file_type;              /* 文件類型 */
        char    name[EXT2_NAME_LEN];    /* 文件名 */
};

在 Unix/Linux 系統中,目錄只是一種特殊的文件。目錄和文件是經過 file_type 域來區分的,該值爲 1 則表示是普通文件,該值爲 2 則表示是目錄。函數

對於每一個 ext2 分區來講,其在物理磁盤上的佈局如圖 2 所示:工具

圖 2. ext2 分區的佈局
ext2 分區的佈局

從圖 2 中能夠看到,對於 ext2 文件系統來講,磁盤被劃分紅一個個大小相同的數據塊,每一個塊的大小能夠是102四、2048 或 4096 個字節。其中,第一個塊稱爲引導塊,通常保留作引導扇區使用,所以 ext2 文件系統通常都是從第二個塊開始的。剩餘的塊被劃分爲一個個的塊組,ext2 文件系統會試圖儘可能將相同文件的數據塊都保存在同一個塊組中,而且儘可能保證文件在磁盤上的連續性,從而提升文件讀寫時的性能。佈局

至於一個分區中到底有多少個塊組,這取決於兩個因素:性能

  1. 分區大小。測試

  2. 塊大小。

最終的計算公式以下:

分區中的塊組數=分區大小/(塊大小*8)

這是因爲在每一個塊組中使用了一個數據塊位圖來標識數據塊是否空閒,所以每一個塊組中最多能夠有(塊大小*8)個塊;該值除上分區大小就是分區中總的塊組數。

每一個塊組都包含如下內容:

  1. 超級塊。存放文件系統超級塊的一個拷貝。

  2. 組描述符。該塊組的組描述符。

  3. 數據塊位圖。標識相應的數據塊是否空閒。

  4. 索引節點位圖。標識相應的索引節點是否空閒。

  5. 索引節點表。存放全部索引節點的數據。

  6. 數據塊。該塊組中用來保存實際數據的數據塊。

在每一個塊組中都保存了超級塊的一個拷貝,默認狀況下,只有第一個塊組中的超級塊結構纔會被系統內核使用;其餘塊組中的超級塊能夠在 e2fsck 之類的程序對磁盤上的文件系統進行一致性檢查使用。在 ext2 文件系統中,超級塊的結構會經過一個名爲 ext2_super_block 的結構進行引用。該結構的一些重要域以下所示:

清單 2. ext2_super_block 結構定義
struct ext2_super_block {
        __le32  s_inodes_count;         /* 索引節點總數 */
        __le32  s_blocks_count;         /* 塊數,即文件系統以塊爲單位的大小 */
        __le32  s_r_blocks_count;       /* 系統預留的塊數 */
        __le32  s_free_blocks_count;    /* 空閒塊數 */
        __le32  s_free_inodes_count;    /* 空閒索引節點數 */
        __le32  s_first_data_block;     /* 第一個可用數據塊的塊號 */
        __le32  s_log_block_size;       /* 塊大小 */
        __le32  s_blocks_per_group;     /* 每一個塊組中的塊數 */
        __le32  s_inodes_per_group;     /* 每一個塊組中的索引節點個數 */
        ...
}

每一個塊組都有本身的組描述符,在 ext2 文件系統中是經過一個名爲 ext2_group_desc的結構進行引用的。該結構的定義以下:

清單 3. ext2_group_desc 結構定義
/*
 * Structure of a blocks group descriptor
 */
struct ext2_group_desc
{
        __le32  bg_block_bitmap;        /* 數據塊位圖的塊號 */
        __le32  bg_inode_bitmap;        /* 索引節點位圖的塊號 */
        __le32  bg_inode_table;         /* 第一個索引節點表的塊號 */
        __le16  bg_free_blocks_count;   /* 該組中空閒塊數 */
        __le16  bg_free_inodes_count;   /* 該組中空閒索引節點數 */
        __le16  bg_used_dirs_count;     /* 該組中的目錄項 */
        __le16  bg_pad;
        __le32  bg_reserved[3];
};

數據塊位圖和索引節點位圖分別佔用一個塊的大小,其每一位描述了對應數據塊或索引節點是否空閒,若是該位爲0,則表示空閒;若是該位爲1,則表示已經使用。

索引節點表存放在一系列連續的數據塊中,每一個數據塊中能夠包括若干個索引節點。每一個索引節點在 ext2 文件系統中都經過一個名爲 ext2_inode 的結構進行引用,該結構大小固定爲 128 個字節,其中一些重要的域以下所示:

清單 4. ext2_inode 結構定義
/*
 * Structure of an inode on the disk
 */
struct ext2_inode {
        __le16  i_mode;         /* 文件模式 */
        __le16  i_uid;          /* 文件全部者的 uid */
        __le32  i_size;         /* 以字節爲單位的文件長度 */
        __le32  i_atime;        /* 最後一次訪問該文件的時間 */
        __le32  i_ctime;        /* 索引節點最後改變的時間 */
        __le32  i_mtime;        /* 文件內容最後改變的時間 */
        __le32  i_dtime;        /* 文件刪除的時間 */
        __le16  i_gid;          /* 文件全部者的 gid */
        __le16  i_links_count;  /* 硬連接數 */
        __le32  i_blocks;       /* 文件的數據塊數 */
        ...
        __le32  i_block[EXT2_N_BLOCKS];/* 指向數據塊的指針 */
        ...
};

第一個索引節點所在的塊號保存在該塊組描述符的 bg_inode_table 域中。請注意 i_block 域,其中就包含了保存數據的數據塊的位置。有關如何對數據塊進行尋址,請參看後文「數據塊尋址方式」一節的內容。

須要知道的是,在普通的刪除文件操做中,操做系統並不會逐一清空保存該文件的數據塊的內容,而只會釋放該文件所佔用的索引節點和數據塊,方法是將索引節點位圖和數據塊位圖中的相應標識位設置爲空閒狀態。所以,若是咱們能夠找到文件對應的索引節點,由此查到相應的數據塊,就可能從磁盤上將已經刪除的文件恢復出來。

幸運的是,這一切都是可能的!本文將經過幾個實驗來了解一下如何從磁盤上恢復刪除的文件。

數據塊尋址方式

回想一下,ext2_inode 結構的 i_block 域是一個大小爲 EXT2_N_BLOCKS 的數組,其中保存的就是真正存放文件數據的數據塊的位置。一般來講,EXT2_N_BLOCKS 大小爲 15。在 ext2 文件系統,採用了直接尋址和間接尋址兩種方式來對數據塊進行尋址,原理如圖3 所示:

圖 3. 數據塊尋址方式
數據塊尋址方式
  • 對於 i_block 的前 12 個元素(i_block[0]到i_block[11])來講,其中存放的就是實際的數據塊號,即對應於文件的 0 到 11 塊。這種方式稱爲直接尋址。

  • 對於第13個元素(i_block[12])來講,其中存放的是另一個數據塊的邏輯塊號;這個塊中並不存放真正的數據,而是存放真正保存數據的數據塊的塊號。即 i_block[12] 指向一個二級數組,其每一個元素都是對應數據塊的邏輯塊號。因爲每一個塊號須要使用 4 個字節表示,所以這種尋址方式能夠訪問的對應文件的塊號範圍爲 12 到 (塊大小/4)+11。這種尋址方式稱爲間接尋址。

  • 對於第14個元素(i_block[13])來講,其中存放也是另一個數據塊的邏輯塊號。與間接尋址方式不一樣的是,i_block[13] 所指向的是一個數據塊的邏輯塊號的二級數組,而這個二級數組的每一個元素又都指向一個三級數組,三級數組的每一個元素都是對應數據塊的邏輯塊號。這種尋址方式稱爲二次間接尋址,對應文件塊號的尋址範圍爲 (塊大小/4)+12 到 (塊大小/4)2+(塊大小/4)+11。

  • 對於第15個元素(i_block[14])來講,則利用了三級間接索引,其第四級數組中存放的纔是邏輯塊號對應的文件塊號,其尋址範圍從 (塊大小/4)2+(塊大小/4)+12 到 (塊大小/4)3+ (塊大小/4)2+(塊大小/4)+11。

ext2 文件系統能夠支持102四、2048和4096字節三種大小的塊,對應的尋址能力以下表所示:

表 1. 各類數據塊對應的文件尋址範圍
塊大小 直接尋址 間接尋址 二次間接尋址 三次間接尋址
1024 12KB 268KB 64.26MB 16.06GB
2048 24KB 1.02MB 513.02MB 265.5GB
4096 48KB 4.04MB 4GB ~ 4TB

掌握上面介紹的知識以後,咱們就能夠開始恢復文件的實驗了。

準備文件系統

爲了防止破壞已有系統,本文將採用一個新的分區進行恢復刪除文件的實驗。

首先讓咱們準備好一個新的分區,並在上面建立 ext2 格式的文件系統。下面的命令能夠幫助建立一個 20GB 的分區:

清單 5. 新建磁盤分區
# fdisk /dev/sdb << END
n

+20G
p
w
q
END

在筆者的機器上,這個分區是 /dev/sdb6。而後建立文件系統:

清單 6. 在新分區上建立 ext2 文件系統
# mke2fs /dev/sdb6

並將其掛載到系統上來:

清單 7. 掛載建立的 ext2 文件系統
# mkdir /tmp/test
# mount /dev/sdb6 /tmp/test

在真正使用這個文件系統以前,讓咱們首先使用系統提供的一個命令 dumpe2fs 來熟悉一下這個文件系統的一些具體參數:

清單 8. 使用 dumpe2fs 熟悉這個文件系統的參數
# dumpe2fs /dev/sdb6 
dumpe2fs 1.39 (29-May-2006)
Filesystem volume name:   <none>
Last mounted on:          <not available>
Filesystem UUID:          d8b10aa9-c065-4aa5-ab6f-96a9bcda52ce
Filesystem magic number:  0xEF53
Filesystem revision #:    1 (dynamic)
Filesystem features:      ext_attr resize_inode dir_index filetype sparse_super large_file
Default mount options:    (none)
Filesystem state:         not clean
Errors behavior:          Continue
Filesystem OS type:       Linux
Inode count:              2443200
Block count:              4885760
Reserved block count:     244288
Free blocks:              4797829
Free inodes:              2443189
First block:              0
Block size:               4096
Fragment size:            4096
Reserved GDT blocks:      1022
Blocks per group:         32768
Fragments per group:      32768
Inodes per group:         16288
Inode blocks per group:   509
Filesystem created:       Mon Oct 29 20:04:16 2007
Last mount time:          Mon Oct 29 20:06:52 2007
Last write time:          Mon Oct 29 20:08:31 2007
Mount count:              1
Maximum mount count:      39
Last checked:             Mon Oct 29 20:04:16 2007
Check interval:           15552000 (6 months)
Next check after:         Sat Apr 26 20:04:16 2008
Reserved blocks uid:      0 (user root)
Reserved blocks gid:      0 (group root)
First inode:              11
Inode size:               128
Default directory hash:   tea
Directory Hash Seed:      d1432419-2def-4762-954a-1a26fef9d5e8


Group 0: (Blocks 0-32767)
  Primary superblock at 0, Group descriptors at 1-2
  Reserved GDT blocks at 3-1024
  Block bitmap at 1025 (+1025), Inode bitmap at 1026 (+1026)
  Inode table at 1027-1535 (+1027)
  31224 free blocks, 16276 free inodes, 2 directories
  Free blocks: 1543-22535, 22537-32767
  Free inodes: 12, 14-16288

...

Group 149: (Blocks 4882432-4885759)
  Block bitmap at 4882432 (+0), Inode bitmap at 4882433 (+1)
  Inode table at 4882434-4882942 (+2)
  2817 free blocks, 16288 free inodes, 0 directories
  Free blocks: 4882943-4885759
  Free inodes: 2426913-2443200

應用前面介紹的一些知識,咱們能夠看到,這個文件系統中,塊大小(Block size)爲4096字節,所以每一個塊組中的塊數應該是4096*8=32768個(Blocks per group),每一個塊組的大小是 128MB,整個分區被劃分紅20GB/(4KB*32768)=160個。可是爲何咱們只看到 150 個塊組(0到149)呢?實際上,在 fdisk 中,咱們雖然輸入要建立的分區大小爲 20GB,但實際上,真正分配的空間並非嚴格的20GB,而是隻有大約 20*109 個字節,準確地說,應該是 (4885760 * 4096) / (1024*1024*1024) = 18.64GB。這是因爲不一樣程序的計數單位的不一樣形成的,在使用存儲設備時常常遇到這種問題。所以,這個分區被劃分紅 150 個塊組,前 149 個塊組分別包含 32768 個塊(即 128B),最後一個塊組只包含 3328 個塊。

另外,咱們還能夠看出,每一個索引節點的大小是 128 字節,每一個塊組中包含 16288 個索引節點,在磁盤上使用 509 個塊來存儲(16288*128/4096),在第一個塊組中,索引節點表保存在 1027 到 1535 塊上。

數據塊和索引節點是否空閒,是分別使用塊位圖和索引節點位圖來標識的,在第一個塊組中,塊位圖和索引節點位圖分別保存在 1025 和 1026 塊上。

dumpe2fs 的輸出結果中還包含了其餘一些信息,咱們暫時先不用詳細關心這些信息。

準備測試文件

如今請將附件中的 createfile.sh 文件下載到本地,並將其保存到 /tmp/test 目錄中,這個腳本能夠幫助咱們建立一個特殊的文件,其中每行包含 1KB 字符,最開始的14個字符表示行號。之因此採用這種文件格式,是爲了方便地確認所恢復出來的文件與原始文件之間的區別。這個腳本的用法以下:

清單 9. createfile.sh 腳本的用法
# ./createfile.sh [size in KB] [filename]

第 1 個參數表示所生成的文件大小,單位是 KB;第 2 個參數表示所生成文件的名字。

下面讓咱們建立幾個測試文件:

清單 10. 準備測試文件
# cd /tmp/test
#./createfile.sh 35 testfile.35K
#./createfile.sh 10240 testfile.10M

# cp testfile.35K testfile.35K.orig
# cp testfile.10M testfile.10M.orig

上面的命令新建立了大小爲 35 KB 和 9000KB 的兩個文件,併爲它們各自保存了一個備份,備份文件的目的是爲了方便使用 diff 之類的工具驗證最終恢復出來的文件與原始文件徹底一致。

ls 命令的 –i 選項能夠查看有關保存文件使用的索引節點的信息:

清單11. 查看文件的索引節點號
# ls -li | sort
11 drwx------ 2 root root    16384 Oct 29 20:08 lost+found
12 -rwxr-xr-x 1 root root     1406 Oct 29 20:09 createfile.sh
13 -rw-r--r-- 1 root root    35840 Oct 29 20:09 testfile.35K
14 -rw-r--r-- 1 root root 10485760 Oct 29 20:10 testfile.10M
15 -rw-r--r-- 1 root root    35840 Oct 29 20:10 testfile.35K.orig
16 -rw-r--r-- 1 root root 10485760 Oct 29 20:11 testfile.10M.orig

第一列中的數字就是索引節點號。從上面的輸出結果咱們能夠看出,索引節點號是按照咱們建立文件的順序而逐漸自增的,咱們剛纔建立的 35K 大小的文件的索引節點號爲 13,10M 大小的文件的索引節點號爲 14。debugfs 中提供了不少工具,能夠幫助咱們瞭解進一步的信息。如今執行下面的命令:

清單12. 查看索引節點 <13> 的詳細信息
# echo "stat <13>" | debugfs /dev/sdb6
debugfs 1.39 (29-May-2006)
Inode: 13  Type: regular    Mode:  0644   Flags: 0x0   Generation: 2957086759
User:     0   Group:     0   Size: 35840
File ACL: 0    Directory ACL: 0
Links: 1   Blockcount: 72
Fragment:  Address: 0    Number: 0    Size: 0
ctime: 0x47268467 -- Mon Oct 29 20:09:59 2007
atime: 0x4726849d -- Mon Oct 29 20:10:53 2007
mtime: 0x47268467 -- Mon Oct 29 20:09:59 2007
BLOCKS:
(0-8):4096-4104
TOTAL: 9

輸出結果顯示的就是索引節點 13 的詳細信息,從中咱們能夠看到諸如文件大小(35840=35K)、權限(0644)等信息,尤爲須要注意的是最後 3 行的信息,即該文件被保存到磁盤上的 4096 到 4104 總共 9 個數據塊中。

下面再看一下索引節點 14 (即 testfile.10M 文件)的詳細信息:

清單13. 查看索引節點 <14> 的詳細信息
# echo "stat <14>" | debugfs /dev/sdb6
debugfs 1.39 (29-May-2006)
Inode: 14  Type: regular  Mode: 0644  Flags: 0x0   Generation: 2957086760
User:     0   Group:     0   Size: 10485760
File ACL: 0    Directory ACL: 0
Links: 1   Blockcount: 20512
Fragment:  Address: 0    Number: 0    Size: 0
ctime: 0x47268485 -- Mon Oct 29 20:10:29 2007
atime: 0x472684a5 -- Mon Oct 29 20:11:01 2007
mtime: 0x47268485 -- Mon Oct 29 20:10:29 2007
BLOCKS:
(0-11):24576-24587, (IND):24588, (12-1035):24589-25612, (DIND):25613, (IND):25614, 
(1036-2059):25615-26638, (IND):26639, (2060-2559):26640-27139
TOTAL: 2564

和索引節點 13  相比,兩者之間最重要的區別在於 BLOCKS 的數據,testfile.10M 在磁盤上總共佔用了 2564 個數據塊,因爲須要採用二級間接尋址模式進行訪問,因此使用了4個塊來存放間接尋址的信息,分別是2458八、2561三、25614和26639,其中25613塊中存放的是二級間接尋址的信息。

恢復刪除文件

如今將剛纔建立的兩個文件刪除:

清單14. 刪除測試文件
# rm -f testfile.35K testfile.10M

debugfs 的 lsdel 命令能夠查看文件系統中刪除的索引節點的信息:

清單15. 使用 lsdel 命令搜索已刪除的文件
# echo "lsdel" | debugfs /dev/sdb6
debugfs 1.39 (29-May-2006)
 Inode  Owner  Mode    Size    Blocks   Time deleted
    13      0 100644  35840    9/9      Mon Oct 29 20:32:05 2007
    14      0 100644 10485760 2564/2564 Mon Oct 29 20:32:05 2007
2 deleted inodes found.

回想一下 inode 結構中有 4 個有關時間的域,分別是 i_atime、i_ctime、i_mtime和i_dtime,分別表示該索引節點的最近訪問時間、建立時間、修改時間和刪除時間。其中 i_dtime域只有在該索引節點對應的文件或目錄被刪除時纔會被設置。dubugfs 的 lsdel 命令會去掃描磁盤上索引節點表中的全部索引節點,其中 i_dtime 不爲空的項就被認爲是已經刪除的文件所對應的索引節點。

從上面的結果能夠看到,剛纔刪除的兩個文件都已經找到了,咱們能夠經過文件大小區分這兩個文件,兩者一個大小爲35K,另一個大小爲10M,正式咱們剛纔刪除的兩個文件。debugfs 的 dump 命令能夠幫助恢復文件:

清單16. 使用 dump 命令恢復已刪除的文件
# echo "dump <13> /tmp/recover/testfile.35K.dump" | debugfs /dev/sdb6
# echo "dump <14> /tmp/recover/testfile.10M.dump" | debugfs /dev/sdb6

執行上面的命令以後,在 /tmp/recover 目錄中會生成兩個文件,比較這兩個文件與咱們前面備份的文件的內容就會發現,testfile.35K.dump 與 testfile.35K.orig 的內容徹底相同,而 testfile.10M.dump 文件中則僅有前 48K 數據是對的,後面的數據所有爲 0 了。這是否意味着刪除文件時間已經把數據也同時刪除了呢?實際上不是,咱們仍是有辦法把數據所有恢復出來的。記得咱們剛纔使用 debugfs 的 stat 命令查看索引節點 14 時的 BLOCKS 的數據嗎?這些數據記錄了整個文件在磁盤上存儲的位置,有了這些數據就能夠把整個文件恢復出來了,請執行下面的命令:

清單 17. 使用 dd 命令手工恢復已刪除的文件
# dd if=/dev/sdb6 of=/tmp/recover/testfile.10M.dd.part1 bs=4096 count=12 skip=24576
# dd if=/dev/sdb6 of=/tmp/recover/testfile.10M.dd.part2 bs=4096 count=1024 skip=24589
# dd if=/dev/sdb6 of=/tmp/recover/testfile.10M.dd.part2 bs=4096 count=1024 skip=25615
# dd if=/dev/sdb6 of=/tmp/recover/testfile.10M.dd.part4 bs=4096 count=500 skip=26640

# cat /tmp/recover/testfile.10M.dd.part[1-4] > /tmp/recover/ testfile.10M.dd

比較一下最終的 testfile.10M.dd 文件和已經備份過的 testfile.10M.orig 文件就會發現,兩者徹底相同:

清單 18. 使用 diff 命令對恢復文件和原文件進行比較
# diff /tmp/recover/ testfile.10M.dd /tmp/test/ testfile.10M.orig

數據明明存在,可是剛纔咱們爲何無法使用 debugfs 的 dump 命令將數據恢復出來呢?如今使用 debugfs 的 stat 命令再次查看一下索引節點 14 的信息:

清單 19. 再次查看索引節點 <14> 的詳細信息
# echo "stat <14>" | debugfs /dev/sdb6
debugfs 1.39 (29-May-2006)
Inode: 14  Type: regular  Mode:  0644  Flags: 0x0   Generation: 2957086760
User:     0   Group:     0   Size: 10485760
File ACL: 0    Directory ACL: 0
Links: 0   Blockcount: 20512
Fragment:  Address: 0    Number: 0    Size: 0
ctime: 0x47268995 -- Mon Oct 29 20:32:05 2007
atime: 0x472684a5 -- Mon Oct 29 20:11:01 2007
mtime: 0x47268485 -- Mon Oct 29 20:10:29 2007
dtime: 0x47268995 -- Mon Oct 29 20:32:05 2007
BLOCKS:(0-11):24576-24587, (IND):24588, (DIND):25613TOTAL: 14

與前面的結果比較一下不難發現,BLOCKS後面的數聽說明總塊數爲 14,並且也沒有整個文件所佔據的數據塊的詳細說明了。既然文件的數據所有都沒有發生變化,那麼間接尋址所使用的那些索引數據塊會不會有問題呢?如今咱們來查看一下 24588 這個間接索引塊中的內容:

清單 20. 查看間接索引塊 24588 中的內容
# dd if=/dev/sdb6 of=block. 24588 bs=4096 count=1 skip=24588

# hexdump block. 24588
0000000 0000 0000 0000 0000 0000 0000 0000 0000
*
0001000

顯然,這個數據塊的內容被所有清零了。debugfs 的dump 命令按照原來的尋址方式試圖恢復文件時,所訪問到的實際上都是第0 個數據塊(引導塊)中的內容。這個分區不是可引導分區,所以這個數據塊中沒有寫入任何數據,所以 dump 恢復出來的數據只有前48K是正確的,其後全部的數據所有爲0。

實際上,ext2 是一種很是優秀的文件系統,在磁盤空間足夠的狀況下,它老是試圖將數據寫入到磁盤上的連續數據塊中,所以咱們能夠假定數據是連續存放的,跳過間接索引所佔據的 2458八、2561三、25614和26639,將從24576 開始的其他 2500 個數據塊讀出,就能將整個文件完整地恢復出來。可是在磁盤空間有限的狀況下,這種假設並不成立,若是系統中磁盤碎片較多,或者同一個塊組中已經沒有足夠大的空間來保存整個文件,那麼文件勢必會被保存到一些不連續的數據塊中,此時上面的方法就沒法正常工做了。

反之,若是在刪除文件的時候可以將間接尋址使用的索引數據塊中的信息保存下來,那麼無論文件在磁盤上是否連續,就均可以將文件完整地恢復出來了,可是這樣就須要修改 ext2 文件系統的實現了。在 ext2 的實現中,與之有關的有兩個函數:ext2_free_data 和 ext2_free_branches(都在 fs/ext2/inode.c 中)。2.6 版本內核中這兩個函數的實現以下:

清單 21. 內核中 ext2_free_data 和 ext2_free_branches 函數的實現
814 /**
815  *      ext2_free_data - free a list of data blocks
816  *      @inode: inode we are dealing with
817  *      @p:     array of block numbers
818  *      @q:     points immediately past the end of array
819  *
820  *      We are freeing all blocks refered from that array (numbers are
821  *      stored as little-endian 32-bit) and updating @inode->i_blocks
822  *      appropriately.
823  */
824 static inline void ext2_free_data(struct inode *inode, __le32 *p, __le32 *q)
825 {
826         unsigned long block_to_free = 0, count = 0;
827         unsigned long nr;
828 
829         for ( ; p < q ; p++) {
830                 nr = le32_to_cpu(*p);
831                 if (nr) {
832                         *p = 0;
833                         /* accumulate blocks to free if they're contiguous */
834                         if (count == 0)
835                                 goto free_this;
836                         else if (block_to_free == nr - count)
837                                 count++;
838                         else {
839                                 mark_inode_dirty(inode);
840                                 ext2_free_blocks (inode, block_to_free, count);
841                         free_this:
842                                 block_to_free = nr;
843                                 count = 1;
844                         }
845                 }
846         }
847         if (count > 0) {
848                 mark_inode_dirty(inode);
849                 ext2_free_blocks (inode, block_to_free, count);
850         }
851 }
852 
853 /**
854  *      ext2_free_branches - free an array of branches
855  *      @inode: inode we are dealing with
856  *      @p:     array of block numbers
857  *      @q:     pointer immediately past the end of array
858  *      @depth: depth of the branches to free
859  *
860  *      We are freeing all blocks refered from these branches (numbers are
861  *      stored as little-endian 32-bit) and updating @inode->i_blocks
862  *      appropriately.
863  */
864 static void ext2_free_branches(struct inode *inode, __le32 *p, __le32 *q, int depth)
865 {
866         struct buffer_head * bh;
867         unsigned long nr;
868 
869         if (depth--) {
870                 int addr_per_block = EXT2_ADDR_PER_BLOCK(inode->i_sb);
871                 for ( ; p < q ; p++) {
872                         nr = le32_to_cpu(*p);
873                         if (!nr)
874                                 continue;
875                         *p = 0;
876                         bh = sb_bread(inode->i_sb, nr);
877                         /*
878                          * A read failure? Report error and clear slot
879                          * (should be rare).
880                          */ 
881                         if (!bh) {
882                                 ext2_error(inode->i_sb, "ext2_free_branches",
883                                         "Read failure, inode=%ld, block=%ld",
884                                         inode->i_ino, nr);
885                                 continue;
886                         }
887                         ext2_free_branches(inode,
888                                            (__le32*)bh->b_data,
889                                            (__le32*)bh->b_data + addr_per_block,
890                                            depth);
891                         bforget(bh);
892                         ext2_free_blocks(inode, nr, 1);
893                         mark_inode_dirty(inode);
894                 }
895         } else
896                 ext2_free_data(inode, p, q);
897 }

注意第 832 和 875 這兩行就是用來將對應的索引項置爲 0 的。將這兩行代碼註釋掉(對於最新版本的內核 2.6.23 能夠下載本文給的補丁)並從新編譯 ext2 模塊,而後從新加載新編譯出來的模塊,並重覆上面的實驗,就會發現利用 debugfs 的 dump 命令又能夠完美地恢復出整個文件來了。

顯然,這個補丁並不完善,由於這個補丁中的處理只是保留了索引數據塊中的索引節點數據,可是尚未考慮數據塊位圖的處理,若是對應的數據塊沒有設置爲正在使用的狀態,而且恰好這些數據塊被重用了,其中的索引節點數據就有可能會被覆蓋掉了,這樣就完全沒有辦法再恢復文件了。感興趣的讀者能夠沿用這個思路自行開發一個比較完善的補丁。

相關文章
相關標籤/搜索