Chinaunix首页 | 论坛 | 博客
  • 博客访问: 819808
  • 博文数量: 117
  • 博客积分: 2583
  • 博客等级: 少校
  • 技术积分: 1953
  • 用 户 组: 普通用户
  • 注册时间: 2008-12-06 22:58
个人简介

Coder

文章分类
文章存档

2013年(1)

2012年(10)

2011年(12)

2010年(77)

2009年(13)

2008年(4)

分类: 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()还需要将解析的结果赋给nddo_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_countcurrent->total_link_countnd->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.nameidatasaved_names用于存放相应深度的符号链接的所指向的路径名,以深度为索引。调用nd_set_link()来初始化当前深度的符号链接的所指向的路径名为NULLnd_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->mntpath->dentry赋值给nd->path.mntnd->path.dentry

 

d.增加path->mnt的引用计数,并设置nd->last_typeLAST_BIND,以说明最后一个分量是连接到特殊文件系统的符号链接。

 

e.调用与具体文件系统相关的函数实现的follow_link方法,给它传递的参数为dentrynd。它读取存放在符号链接索引节点中的路径名,并把这个路径名保存在nd->saved_names数组的合适项中。如果读取出错,则返回错误码。

 

f.读取没有出错。且nd->saved_names数组的合适项中是有效的路径名,则调用__vfs_follow_link(nd, s),给它传递的参数为地址ndnd->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_countnd->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()最后终止时,它把局部变量nextdentry字段设为目录项对象的地址,而这个地址由符号链接传递给原先就执行的link_path_walk()link_path_walk()然后进行下一步。

阅读(3518) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~