路径名查找
当进程必须识别一个文件时,就把它的文件路径名传递给某个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(¤t->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(¤t->fs->lock);
if (__emul_lookup_dentry(name,nd))
return 0;
read_lock(¤t->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(¤t->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;
}