Coder
分类: LINUX
2010-08-01 12:51:36
符号链接是一个普通文件,其中存放的是另一个文件的路径名。路径名可以包含符号链接,且必须由内核来解析。
例如,如果/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
……
878 err = do_lookup(nd, &this,
&next);
879 if (err)
880 break;
881
882 err = -ENOENT;
883 inode = next.dentry->d_inode;
884 if (!inode)
885 goto out_dput;
886
887 if
(inode->i_op->follow_link) {
888 err =
do_follow_link(&next, nd);
889 if (err)
890 goto return_err;
891 err = -ENOENT;
892 inode =
nd->path.dentry->d_inode;
893 if (!inode)
894 break;
895 } else
……
---------------------------------------------------------------------
在这种情况下,link_path_walk()函数调用do_follow_link(&next,
nd),前者传递给后者的参数为符号链接的路径(保存在next中)和符号链接所在的目录的路径(保存在nd中),do_follow_link()还需要将解析的结果赋给nd。do_follow_link()定义如下:
---------------------------------------------------------------------
fs/namei.c
563
/*
564 * This limits recursive symlink follows to 8,
while
565 * limiting consecutive symlinks to 40.
566 *
567 * Without that kind of total limit, nasty
chains of consecutive
568 * symlinks can cause almost arbitrarily long
lookups.
569 */
570
static inline int do_follow_link(struct path *path, struct nameidata *nd)
571
{
572 void *cookie;
573 int err = -ELOOP;
574 if (current->link_count >=
MAX_NESTED_LINKS)
575 goto loop;
576 if (current->total_link_count >=
40)
577 goto loop;
578 BUG_ON(nd->depth >=
MAX_NESTED_LINKS);
579 cond_resched();
580 err =
security_inode_follow_link(path->dentry, nd);
581 if (err)
582 goto loop;
583 current->link_count++;
584 current->total_link_count++;
585 nd->depth++;
586 err = __do_follow_link(path, nd,
&cookie);
587 if (!IS_ERR(cookie) &&
path->dentry->d_inode->i_op->put_link)
588
path->dentry->d_inode->i_op->put_link(path->dentry, nd,
cookie);
589 path_put(path);
590 current->link_count--;
591 nd->depth--;
592 return err;
593
loop:
594 path_put_conditional(path, nd);
595 path_put(&nd->path);
596 return err;
597
}
---------------------------------------------------------------------
该函数执行如下操作:
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
532
static __always_inline int
533
__do_follow_link(struct path *path, struct nameidata *nd, void **p)
534
{
535 int error;
536 struct dentry *dentry =
path->dentry;
537
538 touch_atime(path->mnt, dentry);
539 nd_set_link(nd, NULL);
540
541 if (path->mnt != nd->path.mnt) {
542 path_to_nameidata(path, nd);
543 dget(dentry);
544 }
545 mntget(path->mnt);
546 nd->last_type = LAST_BIND;
547 *p =
dentry->d_inode->i_op->follow_link(dentry, nd);
548 error = PTR_ERR(*p);
549 if (!IS_ERR(*p)) {
550 char *s = nd_get_link(nd);
551 error = 0;
552 if (s)
553 error =
__vfs_follow_link(nd, s);
554 else if (nd->last_type ==
LAST_BIND) {
555 error =
force_reval_path(&nd->path, nd);
556 if (error)
557
path_put(&nd->path);
558 }
559 }
560 return error;
561
}
---------------------------------------------------------------------
__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
498
static __always_inline int __vfs_follow_link(struct nameidata *nd, const char
*link)
499
{
500 if (IS_ERR(link))
501 goto fail;
502
503 if (*link == '/') {
504 set_root(nd);
505 path_put(&nd->path);
506 nd->path = nd->root;
507 path_get(&nd->root);
508 }
509
510 return link_path_walk(link, nd);
511
fail:
512 path_put(&nd->path);
513 return PTR_ERR(link);
514
}
---------------------------------------------------------------------
这个函数本质上依次执行下列操作:
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()然后进行下一步。