linux虚拟文件系统(2)-路径名查找和系统调用的实现

路径名查找

当进程必须识别一个文件时,就把它的文件路径名传递给某个VFS系统调用,如open(),mkdir(),rename(),stat(),VFS的路径名查找,本质上就是从文件路径名导出相应的索引节点
在对初始目录的索引节点进行处理的过程中,代码要检查与第一个名字匹配的目录项,以获得相应的索引节点。然后,从磁盘读出包含那个索引节点的目录文件,并检查与第二个名字匹配的目录项,以获得相应的索引节点。这个过程反复执行。

.路径名查找的时候需要考虑的几个问题:
  • 1.对每个目录的访问权限进行检查
  • 2.文件名可能是与任意一个路径名对应的符号链接,这种情况,分析必须扩展到那个路径名的所有分量。
  • 3.符号链接牵扯到的循环引用也要考虑
  • 4.文件名可能是一个已安装文件系统的安装点。
  • 5.路径名查找应该在发出系统调用的进程的命名空间中完成。
路径名查找函数:path_lookup()
/* name 指向要解析的文件路径名的指针
   flag 如何访问查找的文件
   nd nameidata数据结构的地址,这个结构存放查找操作的结果
*/

struct nameidata{
    struct dentry     *dentry        //目录项对象的地址
    struct vfs_mount  *mnt           //已安装文件系统对象的地址
    struct qstr       last           //路径名的最后一个分量
    unsigned int      flags          //查找标志
    int               last_type      //路径名最后一个分量的类型
    unsigned int      depth          //符号链接嵌套的当前级别,必须小于6
    char []          *saved_names    //与嵌套的符号链接关联的路径名数组
    union             intent         //单个成员联合体,指定如果访问文件
}

int fastcall path_lookup(const char *name, unsigned int flags, struct nameidata *nd)
{
    int retval;

    nd->last_type = LAST_ROOT; /* if there are only slashes... */
    nd->flags = flags;
    nd->depth = 0;

    read_lock(&current->fs->lock);
    if (*name=='/') {
        if (current->fs->altroot && !(nd->flags & LOOKUP_NOALT)) {
            nd->mnt = mntget(current->fs->altrootmnt);   //当前路径已安装文件系统的地址
            nd->dentry = dget(current->fs->altroot);     //当前目录项地址
            read_unlock(&current->fs->lock);
            if (__emul_lookup_dentry(name,nd))
                return 0;
            read_lock(&current->fs->lock);
        }
        nd->mnt = mntget(current->fs->rootmnt);
        nd->dentry = dget(current->fs->root);
    } else {
        nd->mnt = mntget(current->fs->pwdmnt);    //获取当前目录已安装文件系统对象
        nd->dentry = dget(current->fs->pwd);      //获取当前工作目录开始的目录项对象
    }
    read_unlock(&current->fs->lock);
    current->total_link_count = 0;
    retval = link_path_walk(name, nd);         //核心查找操作函数
    if (unlikely(current->audit_context
             && nd && nd->dentry && nd->dentry->d_inode))
        audit_inode(name,
                nd->dentry->d_inode->i_ino,
                nd->dentry->d_inode->i_rdev);
    return retval;
}

link_path_walk是个很长的函数
int fastcall link_path_walk(const char * name, struct nameidata *nd)
{
    struct path next;   //文件对象中的结构,存放已安装文件系统对象描述符合目录项对象
    struct inode *inode;
    int err;
    unsigned int lookup_flags = nd->flags; //初始化lookup_flags

    /*这部分是将文件名字符串的最前面的斜杠符去掉。因为斜杠符可能是多个,所以有一个
指向一个符号链接,如此递归可能造成死循环。每次处理符号链接的时候,结构nd的depth成员加
一,如果超过一个限值,就不再处理了,避免无限的符号链接。最终会得到一个不是符号链接的分量名
*/
    while (*name=='/')
        name++;
    if (!*name)
        goto return_reval;
    //如果nd中的depth为正,则当前路径的最后一个分量是符号链接,把lookup_flags标志设置为LOOKUP_FOLLOW 一般最大值为6
    inode = nd->dentry->d_inode;
    if (nd->depth)
        lookup_flags = LOOKUP_FOLLOW;

    /*__link_path_walk函数第二部分是一个循环,作用是将文件名字符串分离。文件名字符
符,意味着斜杠符之前的字符串是一个目录名。对目录名计算hash值,之后进行目录名的查找工
作。如果目录名查找成功,则进入下一轮循环。循环最终有两种情况,一种情况是得到了最终的目
标文件名,转入last_component分支处理,另一种是整个文件名字符用的是一个斜杠符结尾的,
则转入last_with_slashes分支处理。这两个分支的名字其实就说明了它们各自的功能。
*/
    /* At this point we know we have a real path component. */
    for(;;) {
        unsigned long hash;
        struct qstr this;
        unsigned int c;

        //检查存放在索引节点i_mode字段的访问模式和运行进程的特权。
        err = exec_permission_lite(inode, nd);
        if (err == -EAGAIN) { 
            err = permission(inode, MAY_EXEC, nd);
        }
        if (err)
            break;

        //这里计算name的hash值,如果碰到“/”字符,代表这一轮的名字到了结束位置
        this.name = name;
        c = *(const unsigned char *)name;

        hash = init_name_hash();
        do {
            name++;
            hash = partial_name_hash(c, hash);
            c = *(const unsigned char *)name;
        } while (c && (c != '/'));
        this.len = name - (const char *) this.name;
        this.hash = end_name_hash(hash);

        /* remove trailing slashes? */
        if (!c)
            goto last_component;
        //最后的字符是个“/”,转到last_with_slashes处理
        while (*++name == '/');
        if (!*name)
            goto last_with_slashes;

        /*__link_path_walk函数第三部分是处理文件名中特殊的点(“.”)和点(“..”)
        字符。文件名是一个点代表自身,文件名是两个点代表上一级目录。所以文件名是一个
        点的时候什么也不做,直接进入下一级循环,如果文件名是两个点,则调
        follow_dotdot寻找当前文件的上一级目录。
        */
        /*
         * "." and ".." are special - ".." especially so because it has
         * to be able to know about the current root directory and
         * parent relationships.
         */
        if (this.name[0] == '.') switch (this.len) {
            default:
                break;
            case 2: 
                if (this.name[1] != '.')
                    break;
                follow_dotdot(&nd->mnt, &nd->dentry);
                inode = nd->dentry->d_inode;
                /* fallthrough */
            case 1:
                continue;
        }
        /*
         * See if the low-level filesystem might want
         * to use its own hash..
         */
        if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {
            err = nd->dentry->d_op->d_hash(nd->dentry, &this);
            if (err < 0)
                break;
        }
        nd->flags |= LOOKUP_CONTINUE;

        /* This does the actual lookups.. */
    /*__link_path_walk函数第四部分调用do_lookup函数执行真正的查找,查找的结果通过
    一个path结构变量next返回。do_lookup执行的是最终目标文件的目录的查找,最终的
    成员(目录必须有该员),说明该inode不是一个目录,则返回ENOTDIR错误。这个错误的英文名
    字就说明了错误原因。如果i_op成员具备follow_link成员,说明inode是一个符号链接。符号
    链接必须调用do_follow_link找到符号链接真正指向的路径。
    */
last_with_slashes:
        err = do_lookup(nd, &this, &next);
        if (err)
            break;
        /* Check mountpoints.. */
        follow_mount(&next.mnt, &next.dentry);

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

        if (inode->i_op->follow_link) {
            mntget(next.mnt);
            err = do_follow_link(next.dentry, nd);
            dput(next.dentry);
            mntput(next.mnt);
            if (err)
                goto return_err;
            err = -ENOENT;
            inode = nd->dentry->d_inode;
            if (!inode)
                break;
            err = -ENOTDIR; 
            if (!inode->i_op)
                break;
        } else {
            dput(nd->dentry);
            nd->mnt = next.mnt;
            nd->dentry = next.dentry;
        }
        err = -ENOTDIR; 
        if (!inode->i_op->lookup)
            break;
        continue;
        /* here ends the main loop */
/*__link_path_walk函数第五部分是last_with_slashes分支和last_component分支。
对于last_with_slashes分支要加上一个LOOKUP_DIRECTORY标志。意味着最终查找的是一个
目录。如果参数带有LOOKUP_PARENT标志,进入lookup_parent分支处理,而不在
last_component分支处理。这个标志是目标文件有可能不存在需要创建的时候使用,这说明
last_component只处理最终目标文件存在的情况。如果有可能不存在,则需要lookup_parent
分支处理。
last_component分支同样要处理文件名是“点”和“点点”的情况,这种情况在前面已经分析过。
对最终的目标文件,last_component分支同样调用do_lookup执行最终目标文件的查找,对查
找的结果,也要处理符号链接的情况。这种情况和第四部分重合。
*/
last_with_slashes:
        lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
last_component:
        nd->flags &= ~LOOKUP_CONTINUE;
        if (lookup_flags & LOOKUP_PARENT)
            goto lookup_parent;
        if (this.name[0] == '.') switch (this.len) {
            default:
                break;
            case 2: 
                if (this.name[1] != '.')
                    break;
                follow_dotdot(&nd->mnt, &nd->dentry);
                inode = nd->dentry->d_inode;
                /* fallthrough */
            case 1:
                goto return_reval;
        }
        if (nd->dentry->d_op && nd->dentry->d_op->d_hash) {
            err = nd->dentry->d_op->d_hash(nd->dentry, &this);
            if (err < 0)
                break;
        }
        err = do_lookup(nd, &this, &next);
        if (err)
            break;
        follow_mount(&next.mnt, &next.dentry);
        inode = next.dentry->d_inode;
        if ((lookup_flags & LOOKUP_FOLLOW)
            && inode && inode->i_op && inode->i_op->follow_link) {
            mntget(next.mnt);
            err = do_follow_link(next.dentry, nd);
            dput(next.dentry);
            mntput(next.mnt);
            if (err)
                goto return_err;
            inode = nd->dentry->d_inode;
        } else {
            dput(nd->dentry);
            nd->mnt = next.mnt;
            nd->dentry = next.dentry;
        }
        err = -ENOENT;
        if (!inode)
            break;
        if (lookup_flags & LOOKUP_DIRECTORY) {
            err = -ENOTDIR; 
            if (!inode->i_op || !inode->i_op->lookup)
                break;
        }
        goto return_base;
    /*__link_path_walk函数第六部分是lookup_parent分支,这个分支专门处理最终目标
真正去执行查找。这种情况最终目标文件的查找是在open_namei函数执行的,前面已经分析过。
如果最终的目标文件名是点(“.”)或者点点(“..”),返回的last_type要表明是LAST_DOT或
者LAST_DOTDOT。   
    */
lookup_parent:
        nd->last = this;
        nd->last_type = LAST_NORM;
        if (this.name[0] != '.')
            goto return_base;
        if (this.len == 1)
            nd->last_type = LAST_DOT;
        else if (this.len == 2 && this.name[1] == '.')
            nd->last_type = LAST_DOTDOT;
        else
            goto return_base;
return_reval:
        /*
         * We bypassed the ordinary revalidation routines.
         * We may need to check the cached dentry for staleness.
         */
        if (nd->dentry && nd->dentry->d_sb &&
            (nd->dentry->d_sb->s_type->fs_flags & FS_REVAL_DOT)) {
            err = -ESTALE;
            /* Note: we do not d_invalidate() */
            if (!nd->dentry->d_op->d_revalidate(nd->dentry, nd))
                break;
        }
return_base:
        return 0;
out_dput:
        dput(next.dentry);
        break;
    }
    path_release(nd);
return_err:
    return err;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值