目录与文件:
从前面Inode的结构中我们可以看到,其中并没有定义文件名称,文件名称实际是存放在目录中的,目录也是一类特殊的文件,在ext2文件系统中,目录是由ext2_dir_entry结构组成的列表,目录是变长的结构,这样可以避免磁盘空间的浪费。对于单个文件名,长度不能超过255字符,且文件名的长度会自动使用'\0'字符填充为4个整数倍。目录中有文件和子目录。
ext2_dir_entry结构:
/* * Structure of a directory entry */ #define EXT2_NAME_LEN 255
struct ext2_dir_entry { __u32 inode; /* Inode number */ __u16 rec_len; /* Directory entry length */ __u16 name_len; /* Name length */ char name[EXT2_NAME_LEN]; /* File name */ }; |
上面是老版本的定义,新版本的结构定义:
/* * The new version of the directory entry. Since EXT2 structures are * stored in intel byte order, and the name_len field could never be * bigger than 255 chars, it's safe to reclaim the extra byte for the * file_type field. */
struct ext2_dir_entry_2 { __u32 inode; /* Inode number */ __u16 rec_len; /* Directory entry length */ __u8 name_len; /* Name length */ __u8 file_type; char name[EXT2_NAME_LEN]; /* File name */ }; |
可以看到,新版本中,将文件名的长度的属性从16比特修改为8比特,将剩余的8比特用于记录文件类型。
文件类型定义:
/ * * Ext2 directory file types. Only the low 3 bits are used. The * other bits are reserved for now. */
enum { EXT2_FT_UNKNOWN, /*未知*/ EXT2_FT_REG_FILE, /*普通文件*/ EXT2_FT_DIR, /*目录文件*/ EXT2_FT_CHRDEV, /*字符设备*/ EXT2_FT_BLKDEV, /*块设备*/ EXT2_FT_FIFO, /*管道*/ EXT2_FT_SOCK, /*套接字*/ EXT2_FT_SYMLINK, /*符号链接*/ EXT2_FT_MAX /*文件类型枚举的结尾标识*/ }; |
无论何种类型的文件,都对应一个Inode,Inode号在结构中inode属性记录。不同类型的文件使用Data Block的方式并不相同。有的文件不用来存放数据,不使用Data Block。下面是不同类型的文件使用Data Block的说明:
普通文件:
普通文件在创建时是空的,再向其中写入数据的时候,会为其分配Data Block。
目录文件:
目录文件的Data Block中存放的就是上面的ext2_dir_entry结构(或者是新的结构),name长度可变,最大不会超过255。rec_len记录了目录的大小,将它与这个目录的起始地址相加,就得到了下一个有效目录的起始地址,所以rec_len也可以看做是下一个有效目录的指针。要删除当前这个目录,只需要将inode置为0,并增加前一个目录的rec_len的值,以“跳过”当前这个删除的目录。
符号链接(软连接):
如果符号链接的路径名称小于60个字符,则使用Inode的i_block数组直接存放其值(15*4字节=60字节),如果超出60了字符,则需要为其分配一个Block,并将i_block中的指针指向对应的Block。
设备文件,管道,套接字:
这些文件类型不需要Block,所有信息都存放在Inode中。
下面是一个小例子,说明了ext2文件系统中,是如何利用目录来读出一个文件内容的,顺便也熟悉一下debugfs调试工具:
我们将1023GB的磁盘(分区:/dev/sdd1)使用ext2格式化,然后挂载到/mnt/sdd下,在下面创建了几个目录和文件,结构如下:
/mnt/sdd /mnt/sdd/root_inode_debugfs /mnt/sdd/test/new1.txt /mnt/sdd/test/testlnk.txt -> test.txt /mnt/sdd/test/test.txt /mnt/sdd/test/test1/2.txt /mnt/sdd/test/test2/2.txt |
首先,输入debugfs,进入调试:
[root@DanCentOS65 daniel]# debugfs debugfs 1.41.12 (17-May-2010) debugfs: |
接着使用open打开要调试的设备:
debugfs: open /dev/sdd1 |
查看根目录的详细信息:
debugfs: ls -l 2 40755 (2) 0 0 4096 21-Apr-2017 03:21 . 2 40755 (2) 0 0 4096 21-Apr-2017 03:21 .. 11 40700 (2) 0 0 16384 20-Apr-2017 14:16 lost+found 12 100644 (1) 0 0 4096 21-Apr-2017 02:49 root_inode_debugfs 855681 40755 (2) 0 0 4096 21-Apr-2017 07:30 test |
每一列的说明:
- 第一列为Inode号
- 第二列表示文件权限(前三位是suid/sgid/ticky,后三位的User/Group/Others的权限)
- 第三列是文件类型,(2)对应目录,(1)对应普通文件,具体可以参考一下前面ext2文件类型的枚举
- 第四列表示文件/目录的ownerid
- 第五列表示文件/目录的groupid
- 第六列表示文件占用的大小,可以除以Block大小得到文件占用的Block数量
- 第七列表示创建时间
- 最后一列是目录的名称
注意到上面输出结果中有两个特殊的目录“.”和“..”,这两个目录不能删除,用于相对路径的查找,“.”表示当前目录,“..”表示父目录。
言归正传,我们以/mnt/sdd/test/test2/2.txt来说明文件的读取过程:
我们当前查看的目录其实就是/dev/sdd1的挂载点的目录,即/mnt/sdd,所以在结果中看到的“.”目录对应的Inode号为2,也就是/mnt/sdd的根Inode号是2,进一步查看Inode 2的内容,首先通过命令将Inode 2的内容输出到文件中:
debugfs: dump_inode <2> dump1 |
接着在另一个终端中使用vi -b打开dump1文件(注意,上面dump1文件的存放目录是进入debugfs之前的那个目录,而不是我们调试的那个目录):
[root@DanCentOS65 daniel]# vi -b dump1
^B^@^@^@^L^@^A^B.^@^@^@^B^@^@^@^L^@^B^B..^@^@^K^@^@^@$^@ ^Blost+found^@^@^@^@^@^@^P^@^E^A1.txt^@^@^@^L^@^@^@D^@^R^Aroot_inode_debugfs^F^A^@^@^@^@ (^@^L^Adir1_debugfs^@^@^@^@^T^@^K^Adev_debugfs^@<81>^N^M^@<80>^O^D^Btest^@^@^@^@^@^@^@^@ ^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ ^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ ^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ ^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ …… |
上面的结果中显示了目前Inode 2中存放的内容,其中有一些乱码先忽略掉,我们先查看可读的文件名。细心的读者可能会发现除了我们在目录中看到的lost+found目录,test目录以及root_inode_debugfs文件之外,还有1.txt和dir1_debugfs两个文件,其实这两个文件之前确实是存在的,后来删除掉了,由于删除其实仅仅是修改对应的Inode和Block的标记位,在没有新的内容覆盖掉其内容之前,这些旧的条目会一直都在磁盘上,所以这里我们才能看到对应的内容,这也是一些磁盘数据恢复工具的原理所在。
当我们尝试打开/mnt/sdd/test/test2/2.txt时,文件系统首先会根据目录/mnt/sdd得到其Inode号2,并且确认其权限有读权限,从中读出Block中的内容,也就是上面这些子目录和文件的内容,从中找到我们要访问的下一个目录test,并找到对应的Inode号码855681(从上面的结果中可以看到)。接着系统会读取Inode 855681的内容:
. ntest2.tx <81>^N^M^@^L^@^A^B.^@^@^@^B^@^@^@^L^@^B^B..^@^@<85>^N^M^@^P^@^H^Anew1.txt<82>^N^M^@^P^@^E^Btest1t.s^A^ O^M^@^P^@^E^Btest2.tx<83>^N^M^@^X^@^K^Gtestlnk.txtwp^@^@^@<84>^N^M^@<a0>^O^H^Atest.txtt.swx^@^@^@^@^@^ @^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@...... |
同理,文件系统会从中找到下一级目录test2,并找到他的Inode编号855809:
debugfs: ls -l test 855681 40755 (2) 0 0 4096 21-Apr-2017 08:09 . 2 40755 (2) 0 0 4096 21-Apr-2017 09:14 .. 855685 100644 (1) 0 0 15 21-Apr-2017 06:54 new1.txt 855682 40755 (2) 0 0 4096 21-Apr-2017 05:04 test1 855809 40755 (2) 0 0 4096 21-Apr-2017 05:05 test2 855683 120777 (7) 0 0 8 21-Apr-2017 08:09 testlnk.txt 855684 100644 (1) 0 0 58 21-Apr-2017 07:30 test.txt |
继续读取其中内容:
^A^O^M^@^L^@^A^B.^@^@^@<81>^N^M^@^L^@^B^B..^@^@^D^O^M^@<e8>^O^E^A2.txt^@^@^@^@^@^@^@<d8>^O ^A.2.txt.swp^@^@^@^@^@^@<c4>^O^F^A2.txt~.swx^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@ |
进一步得到2.txt的Inode号,最终从Data Block中读出文件数据。
调试完成后,可以使用close关闭打开的设备:
debugfs: close |
需要注意的是,在调试过程中,如果我们在其他终端中对这个文件系统的文件或者目录进行调整,是不会反映到当前Debug的过程中的,想要重新载入修改的目录和文件,需要退出debugfs,重新mount一下分区,再次打开debugfs即可。
除了上面列出的命令之外,还有很多其他命令可以使用,例如可以使用features查看superblock中定义的功能特性,使用stat <inode number>查看对应的Inode的结构信息,使用stats可以查看superblock的结构等等,有兴趣的读者可以使用?查看全部命令(也可以使用man debugfs查看更详细的用户手册):
debugfs: ? Available debugfs requests:
show_debugfs_params, params Show debugfs parameters open_filesys, open Open a filesystem close_filesys, close Close the filesystem feature, features Set/print superblock features dirty_filesys, dirty Mark the filesystem as dirty init_filesys Initialize a filesystem (DESTROYS DATA) show_super_stats, stats Show superblock statistics ncheck Do inode->name translation icheck Do block->inode translation change_root_directory, chroot Change root directory change_working_directory, cd Change working directory list_directory, ls List directory show_inode_info, stat Show inode information dump_extents, extents, ex Dump extents information link, ln Create directory link unlink Delete a directory link mkdir Create a directory rmdir Remove a directory rm Remove a file (unlink and kill_file, if appropriate) kill_file Deallocate an inode and its blocks clri Clear an inode's contents freei Clear an inode's in-use flag seti Set an inode's in-use flag testi Test an inode's in-use flag freeb Clear a block's in-use flag setb Set a block's in-use flag testb Test a block's in-use flag modify_inode, mi Modify an inode by structure find_free_block, ffb Find free block(s) find_free_inode, ffi Find free inode(s) print_working_directory, pwd Print current working directory expand_dir, expand Expand directory mknod Create a special file list_deleted_inodes, lsdel List deleted inodes undelete, undel Undelete file write Copy a file from your native filesystem dump_inode, dump Dump an inode out to a file cat Dump an inode out to stdout lcd Change the current directory on your native filesystem rdump Recursively dump a directory to the native filesystem set_super_value, ssv Set superblock value set_inode_field, sif Set inode field set_block_group, set_bg Set block group descriptor field logdump Dump the contents of the journal htree_dump, htree Dump a hash-indexed directory dx_hash, hash Calculate the directory hash of a filename dirsearch Search a directory for a particular filename bmap Calculate the logical->physical block mapping for an inode imap Calculate the location of an inode dump_unused Dump unused blocks set_current_time Set current time to use when setting filesystme fields supported_features Print features supported by this version of e2fsprogs help Display info on command or topic. list_requests, lr, ? List available commands. quit, q Leave the subsystem. |
除了上面说的dumpe2fs和debugfs中的调试命令之外,下面也是一些常用的查看命令:
通过-i参数在输出结果中查看Inode编号:
[root@DanCentOS65 test]# ls -il total 16 855685 -rw-r--r-- 1 root root 15 Apr 21 06:54 new1.txt 855682 drwxr-xr-x 2 root root 4096 Apr 21 05:04 test1 855809 drwxr-xr-x 2 root root 4096 Apr 21 05:05 test2 855683 lrwxrwxrwx 1 root root 8 Apr 21 08:09 testlnk.txt -> test.txt 855684 -rw-r--r-- 1 root root 58 Apr 21 07:30 test.txt |
stat命令查看目录或文件对应的Inode信息:
[root@DanCentOS65 test]# stat test2 File: `test2' Size: 4096 Blocks: 8 IO Block: 4096 directory Device: 831h/2097d Inode: 855809 Links: 2 Access: (0755/drwxr-xr-x) Uid: ( 0/ root) Gid: ( 0/ root) Access: 2017-04-21 05:35:58.000000000 +0000 Modify: 2017-04-21 05:05:08.000000000 +0000 Change: 2017-04-21 05:05:08.000000000 +0000 |
查看Inode使用情况:
[root@DanCentOS65 daniel]# df -ih Filesystem Inodes IUsed IFree IUse% Mounted on /dev/sda1 1.9M 98K 1.8M 6% / tmpfs 873K 1 873K 1% /dev/shm /dev/sdb1 18M 12 18M 1% /mnt/resource /dev/sdf1 1.9M 36K 1.9M 2% /mnt/ubuntu /dev/sdd1 1023K 20 1023K 1% /mnt/sdd |
顺便再补充一下软连接和硬链接的知识:
我们接着上面的例子,再创建一个硬链接:
[root@DanCentOS65 daniel]# ll /mnt/sdd/test total 20 -rw-r--r-- 2 root root 15 Apr 21 06:54 new1HDlnk -rw-r--r-- 2 root root 15 Apr 21 06:54 new1.txt drwxr-xr-x 2 root root 4096 Apr 21 05:04 test1 drwxr-xr-x 2 root root 4096 Apr 21 05:05 test2 lrwxrwxrwx 1 root root 8 Apr 21 08:09 testlnk.txt -> test.txt -rw-r--r-- 1 root root 58 Apr 21 07:30 test.txt |
上面的目录中new1HDlnk文件是new1.txt的硬链接,testlnk.txt是test.txt的软连接,我们还是用debugfs来看一下二者的区别:
debugfs: ls -l test 855681 40755 (2) 0 0 4096 21-Apr-2017 09:47 . 2 40755 (2) 0 0 4096 21-Apr-2017 09:14 .. 855685 100644 (1) 0 0 15 21-Apr-2017 06:54 new1.txt 855682 40755 (2) 0 0 4096 21-Apr-2017 05:04 test1 855809 40755 (2) 0 0 4096 21-Apr-2017 05:05 test2 855683 120777 (7) 0 0 8 21-Apr-2017 08:09 testlnk.txt 855684 100644 (1) 0 0 58 21-Apr-2017 07:30 test.txt 855685 100644 (1) 0 0 15 21-Apr-2017 06:54 new1HDlnk |
可以明显看到,硬链接new1HDlnk实际上仅仅是源文件的一个别名,写到了上级目录的Data Block中,它是没有自己的Inode的,所以它的Inode号855685其实也就是源文件new1.txt的Inode号。而软连接不同,软连接是一类特殊的文件,有自己的Inode和内容。上面例子中,源文件test.txt的文件名小于60字节,所以我们可以看到testlnk.txt的Inode中并没有分配Data Block:
debugfs: stat <855683> Inode: 855683 Type: symlink Mode: 0777 Flags: 0x0 Generation: 2285856085 Version: 0x00000000 User: 0 Group: 0 Size: 8 File ACL: 0 Directory ACL: 0 Links: 1 Blockcount: 0 Fragment: Address: 0 Number: 0 Size: 0 ctime: 0x58f9be38 -- Fri Apr 21 08:09:28 2017 atime: 0x58f9be39 -- Fri Apr 21 08:09:29 2017 mtime: 0x58f9be38 -- Fri Apr 21 08:09:28 2017 Size of extra inode fields: 0 Fast_link_dest: test.txt |
我们在做一个测试,创建一个超过60字节文件名的文件file0123456789012345678901234567890123456789012345678901234567890123456789.txt,并为其创建一个软连接largerlnk:
[root@DanCentOS65 test]# ll total 28 -rw-r--r-- 1 root root 71 Apr 22 08:35 file0123456789012345678901234567890123456789012345678901234567890123456789.txt lrwxrwxrwx 1 root root 78 Apr 22 08:36 largerlnk -> file0123456789012345678901234567890123456789012345678901234567890123456789.txt -rw-r--r-- 2 root root 15 Apr 21 06:54 new1HDlnk -rw-r--r-- 2 root root 15 Apr 21 06:54 new1.txt drwxr-xr-x 2 root root 4096 Apr 21 05:04 test1 drwxr-xr-x 2 root root 4096 Apr 21 05:05 test2 lrwxrwxrwx 1 root root 8 Apr 21 08:09 testlnk.txt -> test.txt -rwxr-xr-x 1 root root 58 Apr 21 07:30 test.txt |
再来看一下largerlnk对应的Inode:
debugfs: ls -l 855681 40755 (2) 0 0 4096 22-Apr-2017 08:36 . 2 40755 (2) 0 0 4096 21-Apr-2017 09:14 .. 855685 100644 (1) 0 0 15 21-Apr-2017 06:54 new1.txt 855682 40755 (2) 0 0 4096 21-Apr-2017 05:04 test1 855809 40755 (2) 0 0 4096 21-Apr-2017 05:05 test2 855683 120777 (7) 0 0 8 21-Apr-2017 08:09 testlnk.txt 855684 100755 (1) 0 0 58 21-Apr-2017 07:30 test.txt 855685 100644 (1) 0 0 15 21-Apr-2017 06:54 new1HDlnk 855687 120777 (7) 0 0 78 22-Apr-2017 08:36 largerlnk 855688 100644 (1) 0 0 71 22-Apr-2017 08:35 file0123456789012345678901234567890123456789012345678901234567890123456789.txt debugfs: stat <855687> Inode: 855687 Type: symlink Mode: 0777 Flags: 0x0 Generation: 2439924333 Version: 0x00000000 User: 0 Group: 0 Size: 78 File ACL: 0 Directory ACL: 0 Links: 1 Blockcount: 8 Fragment: Address: 0 Number: 0 Size: 0 ctime: 0x58fb1607 -- Sat Apr 22 08:36:23 2017 atime: 0x58fb1608 -- Sat Apr 22 08:36:24 2017 mtime: 0x58fb1607 -- Sat Apr 22 08:36:23 2017 Size of extra inode fields: 0 BLOCKS: (0):219056128 TOTAL: 1 |
可以看到,当指向的文件名超过60字节时,就不能在Inode的i_block中直接存放指向的文件名了,此时文件系统会分配一个Data Block,我们看到Data Block的编号为219056128,查看一下其中的内容(当然直接在debugfs中通过cat也可以查看,只不过这里演示一下如何使用dd来读取Data Block的数据):
[root@DanCentOS65 test]# dd if=/dev/sdc1 of=block.txt skip=219056128 ibs=4096 bs=4096 count=1 1+0 records in 1+0 records out 4096 bytes (4.1 kB) copied, 0.00018398 s, 22.3 MB/s [root@DanCentOS65 test]# vi block.txt file0123456789012345678901234567890123456789012345678901234567890123456789.txt^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^@^ @^@^@^@^@^@^@^@^@^@^@^@^@…… |
可以看到,Data Block中存放的就是指向的文件的名称。
注:dd命令参数简要说明:
if=file #输入文件名,缺省为标准输入 of=file #输出文件名,缺省为标准输出 ibs=bytes #一次读入 bytes 个字节(即一个块大小为 bytes 个字节) obs=bytes #一次写 bytes 个字节(即一个块大小为 bytes 个字节) bs=bytes #同时设置读写块的大小为 bytes ,可代替 ibs 和 obs cbs=bytes #一次转换 bytes 个字节,即转换缓冲区大小 skip=blocks #从输入文件开头跳过 blocks 个块后再开始复制 seek=blocks #从输出文件开头跳过 blocks 个块后再开始复制 count=blocks #仅拷贝 blocks 个块,块大小等于 ibs 指定的字节数 conv=conversion[,conversion...] #用指定的参数转换文件 |