深入解析ext2文件系统之解析dir文件的具体内容

760阅读 0评论2015-05-04 hitwh_Gypsy
分类:LINUX

    目录是文件的一种形式,也是最常见的一种形式。我们知道,对于普通文件来讲,文件的内容就是我们输入的内容。但是目录文件,他的数据block里面存的是什么呢?
    我新建的ext2文件系统和以前一样,分出512M的空间,挂载到/mnt/bean下面。

  1. [root@localhost bean]# cd code/
  2. [root@localhost code]# ll -i
  3. total 32
  4. 65538 drwxr-xr-x 2 root root 4096 Aug 11 14:06 C
  5. 65540 drwxr-xr-x 2 root root 4096 Aug 11 14:06 php
  6. 65541 drwxr-xr-x 3 root root 4096 Aug 11 14:17 raw
  7. 65539 drwxr-xr-x 2 root root 4096 Aug 11 14:06 shell
  8. [root@localhost code]# cd raw/
  9. [root@localhost raw]# ll
  10. total 32
  11. -rw-r--r-- 1 root root 12 Aug 11 14:07 hello
  12. drwxr-xr-x 2 root root 4096 Aug 11 14:17 sub_dir
  13. -rw-r--r-- 1 root root 13 Aug 11 14:07 test
  14. -rw-r--r-- 1 root root 21 Aug 11 14:14 test_another

  15. [root@localhost raw]# ll -ai
  16. total 48
  17. 65541 drwxr-xr-x 3 root root 4096 Aug 11 14:17 .
  18. 65537 drwxr-xr-x 6 root root 4096 Aug 11 14:06 ..
  19. 65542 -rw-r--r-- 1 root root   12 Aug 11 14:07 hello
  20. 65545 drwxr-xr-x 2 root root 4096 Aug 11 14:17 sub_dir
  21. 65543 -rw-r--r-- 1 root root   13 Aug 11 14:07 test
  22. 65544 -rw-r--r-- 1 root root   21 Aug 11 14:14 test_another

  23. [root@localhost raw]# cat hello 
  24. hello world
  25. [root@localhost raw]# cat test
  26. this is test
  27. [root@localhost raw]# cat test_another 
  28. this is another test
    下面开始我的探究之旅了。
    任何一个靠谱的ext2的教程都会有下面这个结构体。这个结构体讲述的目录下的一个条目在磁盘上的存储形式。

  1. struct ext2_dir_entry_2 {
  2.     __le32    inode;            /* Inode number */
  3.     __le16    rec_len;        /* Directory entry length */
  4.     __u8    name_len;        /* Name length */
  5.     __u8    file_type;
  6.     char    name[];            /* File name, up to EXT2_NAME_LEN */
  7. };
    其中内核中对file_type的值是这样一个枚举类型:

  1. enum {
  2.     EXT2_FT_UNKNOWN        = 0,
  3.     EXT2_FT_REG_FILE    = 1,
  4.     EXT2_FT_DIR        = 2,
  5.     EXT2_FT_CHRDEV        = 3,
  6.     EXT2_FT_BLKDEV        = 4,
  7.     EXT2_FT_FIFO        = 5,
  8.     EXT2_FT_SOCK        = 6,
  9.     EXT2_FT_SYMLINK        = 7,
  10.     EXT2_FT_MAX
  11. };

    对于我们关注的code下面的raw目录而言,下面有六个条目(算上 . 和..)。我们的raw目录文件有3个目录,三个普通文件。现在我们要做的是把目录文件所在的数据块的内容拷贝到一个文件,然后分析下目录文件在数据块上都存了那些内容。

  1. [root@localhost raw]# ll -ai
  2. total 48
  3. 65541 drwxr-xr-x 3 root root 4096 Aug 11 14:17 .
  4. 65537 drwxr-xr-x 6 root root 4096 Aug 11 14:06 ..
  5. 65542 -rw-r--r-- 1 root root 12 Aug 11 14:07 hello
  6. 65545 drwxr-xr-x 2 root root 4096 Aug 11 14:17 sub_dir
  7. 65543 -rw-r--r-- 1 root root 13 Aug 11 14:07 test
  8. 65544 -rw-r--r-- 1 root root 21 Aug 11 14:14 test_another
    因为我们的raw目录的inode号为65541,

  1. 65541 drwxr-xr-x 3 root root 4096 Aug 11 14:17 raw
  所以我们用debugfs来获取这个inode对应文件的信息。

  1. [root@localhost /]# debugfs /dev/loop0
  2. debugfs 1.39 (29-May-2006)
  3. debugfs: stat <65541>
  4. Inode: 65541 Type: directory Mode: 0755 Flags: 0x0 Generation: 3830748590
  5. User: 0 Group: 0 Size: 4096
  6. File ACL: 66562 Directory ACL: 0
  7. Links: 2 Blockcount: 16
  8. Fragment: Address: 0 Number: 0 Size: 0
  9. ctime: 0x50269f5d -- Sat Aug 11 14:07:25 2012
  10. atime: 0x50269f62 -- Sat Aug 11 14:07:30 2012
  11. mtime: 0x50269f5d -- Sat Aug 11 14:07:25 2012
  12. BLOCKS:
  13. (0):77824
  14. TOTAL: 1
    我们可以看到这个inode对应的文件是目录,基本信息都dump出来了。我们最关心的是这个inode对应的文件,也就是我们的raw这个目录文件 存储位置。我们看到一共有1个块,是整个文件系统的77824号block。我们拿到了raw这个目录文件数据块的块号,我们就能将这个块的内容dump出来。

  1. [root@localhost ext2_study]# dd if=/dev/loop0 bs=4k skip=77824 count=1 |od -tx1 -Ax > raw_info.log
  2. 1+0 records in
  3. 1+0 records out
  4. 4096 bytes (4.1 kB) copied, 6.3837e-05 seconds, 64.2 MB/s
     注意,我用了debugfs这个tool获得了raw目录文件所在的数据块。用户完全可以使用我在这个系列文章中第一篇采用的方法,直接读原始inode table 的方法,获得inode对应文件所在数据块的信息。 看inode的数据结构如下,其中偏移量为80个字节的字段为i_block数组,这个数组记录的就是inode对应文件的数据块所在的块号。既然讲到这里,我也就不偷懒了,干脆送佛送到西,直接用这种方法获得数据块所在块号的过程也展示一遍,权作复习用。 

  1. struct ext2_inode {
  2.     __le16    i_mode;        /* File mode */
  3.     __le16    i_uid;        /* Low 16 bits of Owner Uid */
  4.     __le32    i_size;        /* Size in bytes */
  5.     __le32    i_atime;    /* Access time */
  6.     __le32    i_ctime;    /* Creation time */
  7.     __le32    i_mtime;    /* Modification time */
  8.     __le32    i_dtime;    /* Deletion Time */
  9.     __le16    i_gid;        /* Low 16 bits of Group Id */
  10.     __le16    i_links_count;    /* Links count */
  11.     __le32    i_blocks;    /* Blocks count */
  12.     __le32    i_flags;    /* File flags */
  13.     union {
  14.         struct {
  15.             __le32 l_i_reserved1;
  16.         } linux1;
  17.         struct {
  18.             __le32 h_i_translator;
  19.         } hurd1;
  20.         struct {
  21.             __le32 m_i_reserved1;
  22.         } masix1;
  23.     } osd1;                /* OS dependent 1 */
  24.     __le32    i_block[EXT2_N_BLOCKS];/* Pointers to blocks */
  25.     __le32    i_generation;    /* File version (for NFS) */
  26.     __le32    i_file_acl;    /* File ACL */
  27.     __le32    i_dir_acl;    /* Directory ACL */
  28.     __le32    i_faddr;    /* Fragment address */
  29.     union {
  30.         struct {
  31.             __u8    l_i_frag;    /* Fragment number */
  32.             __u8    l_i_fsize;    /* Fragment size */
  33.             __u16    i_pad1;
  34.             __le16    l_i_uid_high;    /* these 2 fields */
  35.             __le16    l_i_gid_high;    /* were reserved2[0] */
  36.             __u32    l_i_reserved2;
  37.         } linux2;
  38.         struct {
  39.             __u8    h_i_frag;    /* Fragment number */
  40.             __u8    h_i_fsize;    /* Fragment size */
  41.             __le16    h_i_mode_high;
  42.             __le16    h_i_uid_high;
  43.             __le16    h_i_gid_high;
  44.             __le32    h_i_author;
  45.         } hurd2;
  46.         struct {
  47.             __u8    m_i_frag;    /* Fragment number */
  48.             __u8    m_i_fsize;    /* Fragment size */
  49.             __u16    m_pad1;
  50.             __u32    m_i_reserved2[2];
  51.         } masix2;
  52.     } osd2;                /* OS dependent 2 */
  53. };
    第一步判断inode表所在块

  1. [root@localhost code]# dumpe2fs /dev/loop0
  2. dumpe2fs 1.39 (29-May-2006)
  3. Filesystem volume name:
  4. .......
  5. Group 1: (Blocks 32768-65535)
  6.   Backup superblock at 32768, Group descriptors at 32769-32769
  7.   Reserved GDT blocks at 32770-32800
  8.   Block bitmap at 32801 (+33), Inode bitmap at 32802 (+34)
  9.   Inode table at 32803-33826 (+35)
  10.   31709 free blocks, 32768 free inodes, 0 directories
  11.   Free blocks: 33827-65535
  12.   Free inodes: 32769-65536

  13. Group 2: (Blocks 65536-98303)
  14. Block bitmap at 65536 (+0), Inode bitmap at 65537 (+1)
  15. Inode table at 65538-66561 (+2)
  16. 31732 free blocks, 32759 free inodes, 6 directories
  17. Free blocks: 66563-67583, 67585-69631, 69633-71679, 71681-77823, 77825-96255, 96261-98303
  18. Free inodes: 65546-98304
   第二步,得到inode表所在块的内容。虽然inode表有很多个块,但是我们的inode 65541就在inode表的第一个块。因为inode大小为128个字节,其中每个块为4096字节,换言之,一个块能容纳16个inode信息。 我们的inode65541是块组2的第5个inode(第一个inode是65537),所以一定在inode表的第一个块中。

  1. [root@localhost ext2_study]# dd if=/dev/loop0 bs=4k skip=65536 count=3 |od -tx1 -Ax > group_info.log
  2. 3+0 records in
  3. 3+0 records out
  4. 12288 bytes (12 kB) copied, 9.311e-05 seconds, 132 MB/s
  5. [root@localhost ext2_study]# vi group_info.log 
  6. Added cscope database /home/manu/work/TDA/src/trend/cscope.out
  7. Press ENTER or type command to continue
   我把块位图和inode位图对应的块的内容一并copy到了group_info.log中了。
   1 我们的inode表在group_info中的的偏移量为8KB
   2 块组2的第一个inode号是65537,我们关心的inode是65441,也就是第5个inode,第五个inode的偏移量为
  (5-1)*128 = 512
   3 i_block数组对应inode数据结构偏移量为40字节。

   所以我们的inode为65441对应的文件的数据块的块号对应的偏移量为
    8192+512+40 = 8744 = 0x2228

OK ,我们打开group_info,log 看下0x2228出的int值是不是77824

  1. 002200 ed 41 00 00 00 10 00 00 3c a2 26 50 d6 a1 26 50
  2. 002210 d6 a1 26 50 00 00 00 00 00 00 03 00 10 00 00 00
  3. 002220 00 00 00 00 00 00 00 00 00 30 01 00 00 00 00 00
  4. 002230 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
    前文提到过,是lillte-endian ,所以0x013000 = 77824. 
    殊途同归,用debugfs得到的目录文件所在的数据块的块号,和我们读块组2的inode table得到的,是一样的。

    按照教科书上的讲解,目录文件的内容以目录条目的形式,存储,我们共有6个条目,在磁盘上应该是这种形式存储的:

    需要注意的有两点
    1 文件名按4个字节对齐,不足的补0.

  1. #define EXT2_DIR_PAD 4
  2. #define EXT2_DIR_ROUND (EXT2_DIR_PAD - 1)
  3. #define EXT2_DIR_REC_LEN(name_len) (((name_len) + 8 + EXT2_DIR_ROUND) & \
  4. ~EXT2_DIR_ROUND)

   2 rec_len这个字段需要摘出来特别解释,字面的解释是当前这个记录的长度,比如hello 文件,文件名5字节,加上前面的ext2_dir_entry_2  8个字节,共13个字节,又因为4字节对齐,所以16个字节。表面看就是这个含义。其实不然。他的官方含义是 :从当前条目的rec_len的末尾到下一个条目rec_len末尾的偏移量。

   呵呵有些人说了,这不是一回事吗。其实不然,如果我把test文件删除了,你就会看出不一样。后面会提到,并解释。

   下面,写了个代码,解析dir文件数据块的内容。(注,下面的代码实在冯锐郑勇代码基础上修改而成,同时参考了UNIX Filesystems一书的第二章,光荣属于前辈)

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <ext2fs/ext2_fs.h>
  4. #include <sys/stat.h>
  5. #include <fcntl.h>
  6. #include <sys/types.h>
  7. #include <unistd.h>
  8. #include <stdio.h>
  9. #include <stdlib.h>
  10. #include <unistd.h>
  11. #include<errno.h>
  12. #include<string.h>
  13. #define BUFSZ 4096

  14. #define EXT2_DIR_PAD             4
  15. #define EXT2_DIR_ROUND             (EXT2_DIR_PAD - 1)
  16. #define EXT2_DIR_REC_LEN(name_len)    (((name_len) + 8 + EXT2_DIR_ROUND) & \
  17.                      ~EXT2_DIR_ROUND)

  18. struct ext2_dir_entry_part {
  19.         __u32 inode; /* Inode number */
  20.         __u16 rec_len; /* Directory entry length */
  21.         __u8 name_len; /* Name length */
  22.         __u8 file_type;
  23. } ;

  24. void usage()
  25. {
  26.   printf("read_dir_entry [dir entry filename]\n");
  27. }

  28. int main(int argc, char **argv)
  29. {

  30.   struct ext2_dir_entry_2 de;
  31.   char *filename = NULL;
  32.   int fd ;
  33.   int rtn = 0;
  34.   int length = 0;
  35.   char buf[BUFSZ] = {0};
  36.   char name[512] = {0};

  37.   struct ext2_dir_entry_part *dir = NULL;
  38.   if (argc < 2)
  39.   {
  40.     printf("Too few parameters!\n");
  41.     usage();
  42.     exit(1);
  43.   }

  44.   filename = argv[1];

  45.   fd = open(filename, O_RDONLY);
  46.   if (fd < 0)
  47.   {
  48.     printf("cannot open file: %s\n", filename);
  49.     exit(1);
  50.   }

  51.   printf(" offset | inode number | rec_len | name_len | file_type | name\n");
  52.   printf("======================================================================\n");
  53.   
  54.   int pos = 0;
  55.  
  56.   while ( rtn = read(fd,buf,BUFSZ))
  57.   {
  58.     if (rtn < 0)
  59.     {
  60.         fprintf(stderr, "read dir file (%s) failed ,(%s)\n ",filename,strerror(errno));
  61.         return -1;
  62.     }
  63. loop:
  64.     dir = (struct ext2_dir_entry_part*) (buf + pos);
  65.    if(dir->rec_len == 0)
  66.    {
  67.        break;
  68.    }

  69.     strncpy(name,(char*)(dir+1),dir->name_len);

  70.     
  71.    printf("%6d: %12d%12d%12d%12d %s\n",
  72.             pos, dir->inode, dir->rec_len, dir->name_len, dir->file_type,name);


  73.     pos += EXT2_DIR_REC_LEN(dir->name_len);
  74.    // pos +=dir->rec_len;
  75.     memset(name,0,512);

  76.     if (pos < rtn - sizeof(struct ext2_dir_entry_part))
  77.     {
  78.         goto loop;
  79.     }
  80.   
  81.  }


  82.   close(fd);
  83. }
    先把77824 block的内容dump出来。

  1. [root@localhost ext2_study]# dd if=/dev/loop0 of=raw_block bs=4K skip=77824 count=1
  2. 1+0 records in
  3. 1+0 records out
  4. 4096 bytes (4.1 kB) copied, 4.6787e-05 seconds, 87.5 MB/s
    我们就用我们写的C代码,按照kernel给定的结构去解析这个block块。


    在对照看下前面我用visio绘制的图,是一样的。目录文件的内容就是这样的。

参考文献
如何恢复 Linux 上删除的文件,第 2 部分
2深入linux内核架构
3 Understand Linux Kernel
4 UNIX Filesystems Evolution Design and Implementation

上一篇:关于目录连接数量的思考
下一篇:硬链接为什么不能链接目录?