9.4 链接文件
由前面有关索引节点和目录项的介绍,我们已经知道Ext2把文件名和文件信息分开存储,其中文件信息用索引节点来描述,目录项就是用来联系文件名和索引节点的。目录项中,每一对文件名和索引节点号的一个一一对应称为一个链接,这就使同一个索引节点号出现在多个链接中成为可能,也就是说,同一个索引节点号可以对应多个不同的文件名。这种链接称为硬链接,可以用ln命令为一个已存在的 文件建立一个新的硬链接:
ln
/home/user1/file1
/home/user1/file2
建立了一个文件file2,链接到file1上。file2和file1有相同的索引节点号,也就是和file1共享同一个索引节点。在建立了一个新的硬链接后,这个索引节点中的i_links_count值将加1,i_links_count的值反映了链接到这个索引节点上的文件数。
使用硬链接的好处在于:
(1)由于在删除文件时,实际上先对i_links_count作减1,如果i_links_count不为0,则结束,即仅仅删除了一个硬链接,具体文件的数据并没有被删除。只有在i_links_count为0时,才真正将文件从磁盘上删除。这样,你可以对重要的文件作多个链接,防止文件被误删除。
(2)允许用户在不进入某个目录的情况下对该目录下面的文件进行处理。
由于同一个文件系统中,索引节点号是系统用来辨认文件的唯一标志,而两个不同的文件系统中,可能有索引节点号一样的文件,所以硬链接仅允许在同一个文件系统上进行,要在多个文件系统之间建立链接,必须用到符号链接。
符号链接与硬链接最大的不同就在于它并不与索引节点建立链接,也就是说当为一个文件建立一个符号链接时,索引节点的链接计数并不变化。当你删除一个文件时,它的符号链接文件也就失去了作用,而当你删去一个文件的符号链接文件,对该文件本身并无影响。所以,有必要区分符号链接文件和硬链接文件,符号链接文件用“l”表示,另外,符号链接文件的索引节点号与原文件的索引节点号也是不同的。而硬链接只是普通文件。硬链接的一个缺陷是你无法简单的知道哪些文件是链接到同一个文件上的。而在符号链接中,可以看到它是指向哪个文件的。
最后,硬链接只能由超级用户建立,而普通用户可以建立符号链接。建立符号链接用ls -s命令。
因为内核为符号链接文件也创建一个索引节点,但它跟普通文件的索引节点所有不同。如前所述,代表着连接节点的文件没有数据,因此,关于符号链接的操作也就比较简单。对Ext2文件系统来说,只有ext2_readlink()和ext2_follow_link()函数,这是在fs/ext2/symlink.c中定义的:
struct inode_operations ext2_fast_symlink_inode_operations = {
readlink:
ext2_readlink,
follow_link:
ext2_follow_link,
};
ext2_readlink()函数的代码如下:
static int ext2_readlink(struct dentry *dentry, char *buffer,
int buflen)
{
char *s = (char
*)dentry->d_inode->u.ext2_i.i_data;
return vfs_readlink(dentry, buffer, buflen, s);
}
如前所述,对于Ext2文件系统,连接目标的路径在ext2_ inode_info结构(即inode结构的union域)的i_data域中存放,因此字符串s就存放有连接目标的路径名。vfs_readlink()的代码在fs/namei.c中:
int vfs_readlink(struct dentry *dentry, char *buffer, int
buflen, const char *link)
{
int len;
len = PTR_ERR(link);
if (IS_ERR(link))
goto out;
len = strlen(link);
if (len > (unsigned) buflen)
len = buflen;
if (copy_to_user(buffer, link, len))
len = -EFAULT;
out:
return len;
}
从代码可以看出,该函数比较简单,即把连接目标的路径名拷贝到用户空间的缓冲区中,并返回路径名的长度。
ext2_follow_link()函数用于搜索符号连接所在的目标文件,其代码如下:
static int ext2_follow_link(struct dentry *dentry, struct
nameidata *nd)
{
char *s = (char *)dentry->d_inode->u.ext2_i.i_data;
return vfs_follow_link(nd, s);
}
这个函数与ext2_readlink()类似,值得注意的是,从ext2_readlink()中对vfs_readlink()的调用意味着从较低的层次(Ext2文件系统)回到更高的VfS层。为什么呢?这是因为符号连接的目标有可能在另一个不同的文件系统中,因此,必须通过VFS来中转,在vfs_follow_link()中必须要调用路径搜索函数link_path_walk()来找到代表着连接对象的dentry结构,函数的代码如下:
static inline int vfs_follow_link(struct nameidata *nd, const
char *link)
{
int res = 0;
char *name;
if (IS_ERR(link))
goto fail;
if (*link == '/') {
path_release(nd);
if (!walk_init_root(link, nd))
/* weird __emul_prefix() stuff did it */
goto out;
}
res = link_path_walk(link, nd);
out:
if (current->link_count || res ||
nd->last_type!=LAST_NORM)
return res;
/*
* If it is an iterative symlinks resolution in open_namei()
we
* have to copy the last component. And all that crap because of
* bloody create() on broken symlinks. Furrfu...
*/
name = __getname();
if (!name)
return -ENOMEM;
strcpy(name, nd->last.name);
nd->last.name = name;
return 0;
fail:
path_release(nd);
return PTR_ERR(link);
}
其中nameidata结构为:
struct nameidata {
struct dentry *dentry;
struct vfsmount *mnt;
struct qstr last;
unsigned int flags;
int last_type;
};
last_type域的可能取值定义于fs.h中:
/*
* Type of the last component on LOOKUP_PARENT
*/
enum {LAST_NORM, LAST_ROOT, LAST_DOT, LAST_DOTDOT,
LAST_BIND};
在路径的搜索过程中,这个域的值会随着路径名当前的搜索结果而变。例如,如果成功地找到了目标文件,那么这个域的值就变成了LAST_NORM;而如果最后停留在一个”.”上,则变成LAST_DOT。
Qstr结构用来存放路径名中当前节点的名字、长度及哈希值,其定义于include/linux/dcache.h中:
*
* "quick string" -- eases parameter passing,
but more importantly
* saves "metadata" about the string
(ie length and the hash).
*/
struct qstr {
const unsigned char * name;
unsigned int len;
unsigned int hash;
};
下面来对vfs_follow_link()函数的代码给予说明:
· 如果符号连接的路径名是以“/”开头的绝对路径,那就要通过walk_init_root()从根节点开始查找。
· 调用link_path_walk()函数查找符号链接所在目标文件对应的信息。从link_path_walk()返回时,返回值为0表示搜索成功,此时,nameidata结构中的指针dentry指向目标节点的dentry结构,指针mnt指向目标节点所在设备的安装结构,同时,这个结构中的last_type表示最后一个节点的类型,节点名则在类型为qstr结构的last中。该函数失败时,则函数返回值为一负的出错码,而nameidata结构中则提供失败的节点名等信息。
vfs_follow_link()返回值的含义与ink_path_walk()函数完全相同。