符号链接的查找

转:

http://blog.chinaunix.net/uid-12567959-id-160999.html

符号链接是一个普通文件,其中存放的是另一个文件的路径名。路径名可以包含符号链接,且必须由内核来解析。

例如,如果/foo/bar是指向(包含路径名)../dir的一个符号链接,那么,/foo/bar/file路径名必须由内核解析为对/dir/file的引用。在这个例子中,内核必须提取它的内容并把它解析为另一个路径名。第二个路径名操作从第一个操作所达到的目录开始,继续到符号链接路径名的最后一个分量被解析。接下来,原来的查找操作从第二个操作所达到的目录项恢复,且有了原目录名中紧随符号链接的分量。
对于更复杂的情况,比如含有符号链接的路径名可能包含其他的符号链接,则代码是递归的。
然而,难以驾驭的递归本质上是危险的。例如,假定一个符号链接指向自己。当然解析含有这样符号链接的路径名可能导致无休止的递归调用流,这又依次引发内核栈的溢出。当前进程描述符中的link_count字段用来避免这种问题:每次递归执行前增加这个字段的值,执行之后减少其值。如果该字段的值达到8,整个循环操作就以错误码结束。因此,符号链接嵌套的层数不超过7。
此外,当前进程的描述符中的total_link_count字段记录在元查找操作中有多少符号链接(甚至非嵌套的)被跟踪。如果这个计数器的值到40,则查找操作终止。没有这个计数器,怀有恶意的用户就可能创建一个病态的路径名,让其中包含很多连续的符号链接,是内核在无休止的查找操作中冻结。

这就是代码基本工作方式:一旦link_path_walk()函数检索到与路径名分量相关的目录项对象,就检查相应的索引节点是否有自定义的follow_link方法。如果是,索引节点就是一个符号链接,在原路径名的查找操作就必须先对这个符号链接进行解释。link_path_walk()函数中相关代码如下:

fs/namei.c
……
                 err = do_lookup(nd, &this, &next);
                 if (err)
                         break;

                 err = -ENOENT;
                 inode = next.dentry->d_inode;
                 if (!inode)
                         goto out_dput;

                 if (inode->i_op->follow_link) {
                         err = do_follow_link(&next, nd);
                         if (err)
                                 goto return_err;
                         err = -ENOENT;
                         inode = nd->path.dentry->d_inode;
                         if (!inode)
                                 break;
                 } else
……

在这种情况下,link_path_walk()函数调用do_follow_link(&next, nd),前者传递给后者的参数为符号链接的路径(保存在next中)和符号链接所在的目录的路径(保存在nd中),do_follow_link()还需要将解析的结果赋给nd。do_follow_link()定义如下:

fs/namei.c
 /*
  * This limits recursive symlink follows to 8, while
  * limiting consecutive symlinks to 40.
  *
  * Without that kind of total limit, nasty chains of consecutive
  * symlinks can cause almost arbitrarily long lookups.
  */
 static inline int do_follow_link(struct path *path, struct nameidata *nd)
 {
         void *cookie;
         int err = -ELOOP;
         if (current->link_count >= MAX_NESTED_LINKS)
                 goto loop;
         if (current->total_link_count >= 40)
                 goto loop;
         BUG_ON(nd->depth >= MAX_NESTED_LINKS);
         cond_resched();
         err = security_inode_follow_link(path->dentry, nd);
         if (err)
                 goto loop;
         current->link_count++;
         current->total_link_count++;
         nd->depth++;
         err = __do_follow_link(path, nd, &cookie);
         if (!IS_ERR(cookie) && path->dentry->d_inode->i_op->put_link)
                 path->dentry->d_inode->i_op->put_link(path->dentry, nd, cookie);
         path_put(path);
         current->link_count--;
         nd->depth--;
         return err;
 loop:
         path_put_conditional(path, nd);
         path_put(&nd->path);
         return err;
 }

该函数执行如下操作:
1、检查current->link_count是否大于等于MAX_NESTED_LINKS(当前内核定义为8),若是,则返回错误码-ELOOP。
2、检查current->total_link_count是否大于等于40,若是,则返回错误码-ELOOP。
3、若使当前进程需要,则调用cond_resched()进行进程交换(设置当前进程描述符thread_info中的TIF_NEED_RESCHED)。
4、调用security_inode_follow_link(path->dentry, nd)来对解析符号链接进行安全检查,若失败则返回错误码。
5、递增current->link_count、current->total_link_count和nd->depth
6、调用__do_follow_link(path, nd, &cookie),该函数定义为:

fs/namei.c
 static __always_inline int
 __do_follow_link(struct path *path, struct nameidata *nd, void **p)
 {
         int error;
         struct dentry *dentry = path->dentry;

         touch_atime(path->mnt, dentry);
         nd_set_link(nd, NULL);

         if (path->mnt != nd->path.mnt) {
                 path_to_nameidata(path, nd);
                 dget(dentry);
         }
         mntget(path->mnt);
         nd->last_type = LAST_BIND;
         *p = dentry->d_inode->i_op->follow_link(dentry, nd);
         error = PTR_ERR(*p);
         if (!IS_ERR(*p)) {
                 char *s = nd_get_link(nd);
                 error = 0;
                 if (s)
                         error = __vfs_follow_link(nd, s);
                 else if (nd->last_type == LAST_BIND) {
                         error = force_reval_path(&nd->path, nd);
                         if (error)
                                 path_put(&nd->path);
                 }
         }
         return error;
 }

__do_follow_link()执行如下操作:
a.调用touch_atime(path->mnt, dentry)更新与要解析的符号链接关联的索引节点的访问时间。
 
b.nameidata的saved_names用于存放相应深度的符号链接的所指向的路径名,以深度为索引。调用nd_set_link()来初始化当前深度的符号链接的所指向的路径名为NULL。nd_set_link()定义为:

include/linux/namei.h
static inline void nd_set_link(struct nameidata *nd, char *path)
{
    nd->saved_names[nd->depth] = path;
}
c.判断path->mnt是否不等于nd->path.mnt,如果不等于,则意味着有一个文件系统被挂载在了符号链接上。这也同时意味着在向指向目录符号链接挂载文件系统时,已经对符号链接的路径(struct path)进行了适当的更新,也即是path中保存了有效的路径。所以调用path_to_nameidata(path, nd),分别将path->mnt和path->dentry赋值给nd->path.mnt和nd->path.dentry。
d.增加path->mnt的引用计数,并设置nd->last_type为LAST_BIND,以说明最后一个分量是连接到特殊文件系统的符号链接。
e.调用与具体文件系统相关的函数实现的follow_link方法,给它传递的参数为dentry和nd。它读取存放在符号链接索引节点中的路径名,并把这个路径名保存在nd->saved_names数组的合适项中。如果读取出错,则返回错误码。
f.读取没有出错。且nd->saved_names数组的合适项中是有效的路径名,则调用__vfs_follow_link(nd, s),给它传递的参数为地址nd和nd->saved_names数组中路径名的地址。返回调用__vfs_follow_link()的返回值,__vfs_follow_link()随后说明。
g.读取没有出错,但返回了NULL,则检查nd->last_type是否设置了LAST_BIND,若是,则这表明是有一个文件系统挂载在了符号链接上。于是调用force_reval_path(&nd->path, nd)来使目录项validate。如果出错,则减少对nd->path的引用计数。返回调用force_reval_path()的返回值。
7、如果__do_follow_link()正常返回,并且定义了索引节点对象的put_link方法,就执行它,释放由follow_link分配的临时数据结构。
8、减少path的引用计数,减少current->link_count和nd->depth。
9、返回由__do_follow_link()函数返回的错误码(0表示无错误)。
__vfs_follow_link()函数定义如下:

fs/namei.c
 static __always_inline int __vfs_follow_link(struct nameidata *nd, const char *link)
 {
         if (IS_ERR(link))
                 goto fail;

         if (*link == '/') {
                 set_root(nd);
                 path_put(&nd->path);
                 nd->path = nd->root;
                 path_get(&nd->root);
         }

         return link_path_walk(link, nd);
 fail:
         path_put(&nd->path);
         return PTR_ERR(link);
 }

这个函数本质上依次执行下列操作:
1、检查符号链接指向的路径的路径名的第一个字符时是否是“/”,若是,则已经找到一个绝对路径名,因此没有必要在内存中保留前一个路径的任何信息。则调用set_root(nd)将root字段设置为current->fs ->root,并增加其引用计数。并将path字段同样设为nd->root,再次增加其引用计数。
2、调用link_path_walk()解析符号链接的路径名,传递给它的参数为路径名和nd。
3、返回从link_path_walk(link, nd)返回的值。
当do_follow_link()最后终止时,它把局部变量next的dentry字段设为目录项对象的地址,而这个地址由符号链接传递给原先就执行的link_path_walk()。link_path_walk()然后进行下一步。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值