以上几个小节说明了do_sys_open接口实现的简要说明、路径查找相关结构体的说明、路径查找初始化函数path_init的分析,本小节介绍link_path_walk接口,该接口实现了用户输入路径的查找操作,其对每一级路径均确认路径是否有效,并获取路径对应的dentry、inode等信息,下面详细介绍link_path_walk接口主要的调用流程,主要涉及普通路径的查找、链接路径的查找等内容。主要涉及的接口包括link_path_walk、walk_component、handle_dots、lookup_slow、lookup_dcache、lookup_real、nested_symlink、follow_link等接口函数的分析。
link_path_walk为路径查找接口,该接口对传入的文件路径,进行一级一级的查找,并对查找过程中遇到的链接目录进行进一步的查找,
在路径查找的过程中,可能会跨越多个文件系统,因此在查找过程中,需要更新struct nameidate类型的变量中的root参数。
路径查找接口的主要功能如下流程图,主要分为如下几个主要步骤:
1.首先去除路径名称起始字符中所有的'/'字符(这也是我们在实际linux系统中进入执行"cd //home/"命令,可正常进入家目录的原因);
2.针对传入的路径名称,循环进行查找:
a.若需要查找的目录,没有执行权限或者不能执行lookup操作,则返回查找失败;
b.若当前目录为最后一级路径(最后一级路径为文件或者为目录),则更新last_type、 last_str,该接口返回成功
c.若不是以上情况,则更新name指针的位置,移植下一级目录的首地址处
d.若不是上述b中的情况,则说明此处为一个目录项,则调用walk_component接口,查找本次目录对应的dentry以及inode:
>若查找失败,则返回路径查找失败;
>若查找成功且不是链接目录,则跳转至a,继续下一级路径的查找;
>若查找成功且是链接目录,则调用nested_symlink对链接路径对应的目标路径继续进行解
析:
i.若解析失败,则返回查找失败;
Ii.若解析成功,则跳转至a,继续下一级路径的查找。
walk_component接口分析
该接口对当前的搜索路径进行搜索操作
1.若last_type不是LAST_NORM,则调用handle_dots进行处理,针对'.' 、'..'的情形更新nd->path变量的值,程序退出
2.若last_type是LAST_NORM,说明当前路径是正常的目录,则调用lookup_fast、lookup_slow进行查找。
a.首先调用lookup_fast,从nd->patn.dentry的hash链表中,根据nd->last.name查找子dentry:
若查找成功则返回0;
若没有查找到,则返回1,调用lookup_slow进行查找;
否则,则返回失败。
本次接口调用了三个主要的接口,handle_dots、lookup_fast、lookup_slow,后续详细分析下这三个接口,这三个接口主要
对应于处理".."目录、从dcache中查找目录项、从子目录的dentry中查找目录项。
static inline int walk_component(struct nameidata *nd, struct path *path,
int follow)
{
struct inode *inode;
int err;
/*
* "." and ".." are special - ".." especially so because it has
* to be able to know about the current root directory and
* parent relationships.
*/
if (unlikely(nd->last_type != LAST_NORM))
return handle_dots(nd, nd->last_type);
err = lookup_fast(nd, path, &inode);
/*若通过hash缓存中查找失败,则根据返回值确定是否需要调用lookup_slow进行继续查找:
若err小于0,则返回失败;
若err大于0,则调用lookup_slow进行继续查找。*/
if (unlikely(err)) {
if (err < 0)
goto out_err;
/*调用lookup_slow查找符合要求的子dentry变量。*/
err = lookup_slow(nd, path);
if (err < 0)
goto out_err;
/*若查找成功,则获取该新dentry对应的inode节点变量*/
inode = path->dentry->d_inode;
}
err = -ENOENT;
if (!inode)
goto out_path_put;
/*确认该dentr是否为链接目录,若为链接目录则返回1,由link_path_walk做进一步处理*/
if (should_follow_link(inode, follow)) {
if (nd->flags & LOOKUP_RCU) {
if (unlikely(unlazy_walk(nd, path->dentry))) {
err = -ECHILD;
goto out_err;
}
}
BUG_ON(inode != path->dentry->d_inode);
return 1;
}
/*若不是链接目录,则更新nd->path、nd->inode的值*/
path_to_nameidata(path, nd);
nd->inode = inode;
return 0;
out_path_put:
path_to_nameidata(path, nd);
out_err:
terminate_walk(nd);
return err;
}
handle_dots接口分析
该接口主要处理LAST_DOT、LAST_DOTDOT这两种类型:
1.对于LAST_DOTDOT,则需要跳转至父目录,具体调用follow_dotdot
2.对于LAST_DOT,则表示为当前目录,直接返回即可。
static inline int handle_dots(struct nameidata *nd, int type)
{
if (type == LAST_DOTDOT) {
if (nd->flags & LOOKUP_RCU) {
if (follow_dotdot_rcu(nd))
return -ECHILD;
} else
follow_dotdot(nd);
}
return 0;
}
针对follow_dotdot接口,其定义如下:
该接口主要处理LAST_DOTDOT类型的路径变量,需要跳转至父目录,有如下几种情况
1.若当前搜索路径的dentry以及该dentry对应文件系统的mnt,即为当前进程的root路径及其mnt,则不能再往上查找,仍然以该目录作为查找路径。
2.若当前路径的dentry不是当前路径对应的文件系统的根dentry,则说明当前路径的父路径仍在相同的文件系统下,获取当前dentry的父节点即可。
3.若当前搜索路径的dentry即为其所在文件系统的根dentry,则调用follow_up进入其所挂载目录的上一级dentry,并修改nd->path中的mnt、dentry
成员变量以及nd->inode的值。
static void follow_dotdot(struct nameidata *nd)
{
set_root(nd);
while(1) {
struct dentry *old = nd->path.dentry;
if (nd->path.dentry == nd->root.dentry &&
nd->path.mnt == nd->root.mnt) {
break;
}
if (nd->path.dentry != nd->path.mnt->mnt_root) {
/* rare case of legitimate dget_parent()... */
/*更新nd->path的dentry为其父dentry*/
nd->path.dentry = dget_parent(nd->path.dentry);
dput(old);
break;
}
/*若当前搜索路径的dentry即为其所在文件系统的根dentry,则调用follow_up进入其所挂载目录的上一级dentry,并修改nd->path中的mnt、dentry成员变量以及nd->inode的值。*/
if (!follow_up(&nd->path))
break;
}
/*更新nd->path.mnt变量*/
follow_mount(&nd->path);
nd->inode = nd->path.dentry->d_inode;
}
针对其调用的follow_up接口,该接口为获取当前挂载点目录的dentry
1.通过path->mnt,获取该文件系统对应struct mount 类型的指针变量;
2.根据上述获取的struct mount类型的指针变量,获取挂载点对应目录的dentry以及其父mount的值,并赋值该struct path类型的指针变量中
int follow_up(struct path *path)
{
struct mount *mnt = real_mount(path->mnt);
struct mount *parent;
struct dentry *mountpoint;
br_read_lock(&vfsmount_lock);
parent = mnt->mnt_parent;
if (parent == mnt) {
br_read_unlock(&vfsmount_lock);
return 0;
}
mntget(&parent->mnt);
mountpoint = dget(mnt->mnt_mountpoint);
br_read_unlock(&vfsmount_lock);
dput(path->dentry);
path->dentry = mountpoint;
mntput(path->mnt);
path->mnt = &parent->mnt;
return 1;
}
lookup_fast接口分析
该接口的作用是在dentry的hash缓存链表中,查找名称为nd->last的dentry,且其parent dentry为nd->path.dentry
static int lookup_fast(struct nameidata *nd,
struct path *path, struct inode **inode)
{
struct vfsmount *mnt = nd->path.mnt;
struct dentry *dentry, *parent = nd->path.dentry;
int need_reval = 1;
int status = 1;
int err;
/*
* Rename seqlock is not required here because in the off chance
* of a false negative due to a concurrent rename, we're going to
* do the non-racy lookup, below.
*/
/*此处为nd->flags标记为LOOKUP_RCU时的处理流程*/
if (nd->flags & LOOKUP_RCU) {
unsigned seq;
dentry = __d_lookup_rcu(parent, &nd->last, &seq, nd->inode);
if (!dentry)
goto unlazy;
/*
* This sequence count validates that the inode matches
* the dentry name information from lookup.
*/
*inode = dentry->d_inode;
if (read_seqcount_retry(&dentry->d_seq, seq))
return -ECHILD;
/*
* This sequence count validates that the parent had no
* changes while we did the lookup of the dentry above.
*
* The memory barrier in read_seqcount_begin of child is
* enough, we can use __read_seqcount_retry here.
*/
if (__read_seqcount_retry(&parent->d_seq, nd->seq))
return -ECHILD;
nd->seq = seq;
if (unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE)) {
status = d_revalidate(dentry, nd->flags);
if (unlikely(status <= 0)) {
if (status != -ECHILD)
need_reval = 0;
goto unlazy;
}
}
path->mnt = mnt;
path->dentry = dentry;
if (unlikely(!__follow_mount_rcu(nd, path, inode)))
goto unlazy;
if (unlikely(path->dentry->d_flags & DCACHE_NEED_AUTOMOUNT))
goto unlazy;
return 0;
unlazy:
if (unlazy_walk(nd, dentry))
return -ECHILD;
} else {
/*调用__d_lookup,从parent的hash链表中查找符合条件的子dentry(目录名称为nd->last.name)*/
dentry = __d_lookup(parent, &nd->last);
}
/*若没有查找到,则将结果设置为1,需要调用lookup_slow继续进行查找*/
if (unlikely(!dentry))
goto need_lookup;
/*若需要重新使能该dentry,则调用d_revalidate使能该dentry*/
if (unlikely(dentry->d_flags & DCACHE_OP_REVALIDATE) && need_reval)
status = d_revalidate(dentry, nd->flags);
/*若status小于等于0,则说明调用d_revalidate使能dentry失败,则返回失败*/
if (unlikely(status <= 0)) {
if (status < 0) {
dput(dentry);
return status;
}
if (!d_invalidate(dentry)) {
dput(dentry);
goto need_lookup;
}
}
/*设置path变量的mnt、dentry变量,并调用*/
path->mnt = mnt;
path->dentry = dentry;
err = follow_managed(path, nd->flags);
if (unlikely(err < 0)) {
path_put_conditional(path, nd);
return err;
}
if (err)
nd->flags |= LOOKUP_JUMPED;
*inode = path->dentry->d_inode;
return 0;
need_lookup:
return 1;
}
lookup_slow接口分析
该接口实现的功能如下:
1.调用__lookup_hash查找符合要求的子dentry(该接口包括两种查找方式:1.从dcache中查找,成功则返回;2.若dcache中查找失败,
则调用dentry的lookup接口进行查找);
2.若查找成功,则更新path的mnt、dentry变量,并调用follow_managed对查找到的dentry变量,确认是否进行autofs、mountpoint、
automountpoint的处理
static int lookup_slow(struct nameidata *nd, struct path *path)
{
struct dentry *dentry, *parent;
int err;
parent = nd->path.dentry;
BUG_ON(nd->inode != parent->d_inode);
mutex_lock(&parent->d_inode->i_mutex);
dentry = __lookup_hash(&nd->last, parent, nd->flags);
mutex_unlock(&parent->d_inode->i_mutex);
if (IS_ERR(dentry))
return PTR_ERR(dentry);
path->mnt = nd->path.mnt;
path->dentry = dentry;
err = follow_managed(path, nd->flags);
if (unlikely(err < 0)) {
path_put_conditional(path, nd);
return err;
}
if (err)
nd->flags |= LOOKUP_JUMPED;
return 0;
}
针对__lookup_hash接口,其功能如下:
该接口的主要功能是从base的子dentry中查找符合要求的dentry
1.首先调用lookup_dcache接口,从base的hash链表中查找符合要求的子dentry:
若查找到则返回该dentry;
若没有查找到,则申请一个dentry变量,并标记need_lookup变量,需要lookup_real继续查找
2.调用lookup_real接口,调用该dentry对应的inode节点的lookup接口,查找符合要求的子dentry。
static struct dentry *__lookup_hash(struct qstr *name,
struct dentry *base, unsigned int flags)
{
bool need_lookup;
struct dentry *dentry;
dentry = lookup_dcache(name, base, flags, &need_lookup);
if (!need_lookup)
return dentry;
return lookup_real(base->d_inode, dentry, flags);
}
lookup_dcache接口分析
该接口主要实现如下功能:
1.调用d_lookup,查找dentry->d_hash链表中是否存在符合要求的子dentry:
若查找到,则使能该dentry;
若没有查找到,则根据查找的子dentry名称,申请一个新的dentry类型的内存空间,并设置need_lookup为true
static struct dentry *lookup_dcache(struct qstr *name, struct dentry *dir,
unsigned int flags, bool *need_lookup)
{
struct dentry *dentry;
int error;
*need_lookup = false;
dentry = d_lookup(dir, name);
if (dentry) {
if (dentry->d_flags & DCACHE_OP_REVALIDATE) {
error = d_revalidate(dentry, flags);
if (unlikely(error <= 0)) {
if (error < 0) {
dput(dentry);
return ERR_PTR(error);
} else if (!d_invalidate(dentry)) {
dput(dentry);
dentry = NULL;
}
}
}
}
if (!dentry) {
dentry = d_alloc(dir, name);
if (unlikely(!dentry))
return ERR_PTR(-ENOMEM);
*need_lookup = true;
}
return dentry;
}
lookup_real接口分析
该接口主要调用dentry的lookup接口进行子目录项的查找,这样就进入了每个文件系统的目录项查找接口。
该接口实现功能如下:
1.若该dentry为deaddir,则返回失败;
2.调用该dentry对应inode节点的lookup接口,执行查找操作,并返回查找结果
static struct dentry *lookup_real(struct inode *dir, struct dentry *dentry,
unsigned int flags)
{
struct dentry *old;
/* Don't create child dentry for a dead directory. */
if (unlikely(IS_DEADDIR(dir))) {
dput(dentry);
return ERR_PTR(-ENOENT);
}
old = dir->i_op->lookup(dir, dentry, flags);
if (unlikely(old)) {
dput(dentry);
dentry = old;
}
return dentry;
}
nested_symlink接口分析
该接口主要用于解析链接路径对应的目标路径,该接口的定义如下,其实现的功能如下(在进行路径查找时,可能会跨越不同的文件系统):
该接口主要是对进行链接路径的查找,具体实现如下功能:
1.判断当前查找的链接计数是否超过限制范围,若超过,返回失败;
2.调用follow_link进行链接路径的查找,进入链接目录对应的target目录(该接口最终调用link_path_walk进行查找操作);
3.接着调用walk_component,进行子路径的查找与更新更新nd->path、nd->inode等信息
static inline int nested_symlink(struct path *path, struct nameidata *nd)
{
int res;
/*如果当前进程查找路径出现的链接次数超过MAX_NESTED_LINKS,则返回失败*/
if (unlikely(current->link_count >= MAX_NESTED_LINKS)) {
path_put_conditional(path, nd);
path_put(&nd->path);
return -ELOOP;
}
/*如果当前nd->depth的值大于MAX_NESTED_LINKS,则返回失败*/
BUG_ON(nd->depth >= MAX_NESTED_LINKS);
/*增加nd与当前进程的depth与link_count值*/
nd->depth++;
current->link_count++;
do {
struct path link = *path;
void *cookie;
res = follow_link(&link, nd, &cookie);
if (res)
break;
/*本次路径搜索,更新相应的nd->path、nd->inode等信息*/
res = walk_component(nd, path, LOOKUP_FOLLOW);
put_link(nd, &link, cookie);
} while (res > 0);
/*当完成链接路径的查找后,则减小current->link_count、nd->depth的计数*/
current->link_count--;
nd->depth--;
return res;
}
follow_link接口分析
针对follow_link,该接口的作用是进入链接目录对应的target目录,即进入一个链接目录,这可能涉及到文件系统的跨越,
需要更新path.mnt、path.dentry
1.判断当前进程的total_link_count是否超过限制,若超过限制,返回失败;
2.调用nd_set_link,设置nd->saved_names[nd->depth]值为空,等下用于存放当前链接目录所链接的目标目录的路径值;
3.调用各文件系统follow_link的接口,设置nd->saved_names[nd->depth],主要是用于__vfs_follow_link对软链接的目录进行路径查找。
4.__vfs_follow_link接口主要是调用link_path_walk进行链接路径的查找操作。
*/
static __always_inline int
follow_link(struct path *link, struct nameidata *nd, void **p)
{
struct dentry *dentry = link->dentry;
int error;
char *s;
BUG_ON(nd->flags & LOOKUP_RCU);
if (link->mnt == nd->path.mnt)
mntget(link->mnt);
error = -ELOOP;
if (unlikely(current->total_link_count >= 40))
goto out_put_nd_path;
cond_resched();
/*该变量主要记录一次路径查找中,所解析的链接路径的次数,当链接路径的次数超过40后,亦返回失败*/
current->total_link_count++;
touch_atime(link);
nd_set_link(nd, NULL);
error = security_inode_follow_link(link->dentry, nd);
if (error)
goto out_put_nd_path;
/*针对链接路径,需将其last_type设置为LAST_BIND*/
nd->last_type = LAST_BIND;
*p = dentry->d_inode->i_op->follow_link(dentry, nd);
error = PTR_ERR(*p);
if (IS_ERR(*p))
goto out_put_nd_path;
error = 0;
s = nd_get_link(nd);
if (s) {
error = __vfs_follow_link(nd, s);
if (unlikely(error))
put_link(nd, link, *p);
}
return error;
out_put_nd_path:
*p = NULL;
path_put(&nd->path);
path_put(link);
return error;
}
__vfs_follow_link接口主要是对传入的路径“link”,进行路径查找
主要是调用link_path_walk,执行路径查找操作
*/
static __always_inline int __vfs_follow_link(struct nameidata *nd, const char *link)
{
int ret;
if (IS_ERR(link))
goto fail;
if (*link == '/') {
set_root(nd);
path_put(&nd->path);
nd->path = nd->root;
path_get(&nd->root);
nd->flags |= LOOKUP_JUMPED;
}
nd->inode = nd->path.dentry->d_inode;
ret = link_path_walk(link, nd);
return ret;
fail:
path_put(&nd->path);
return PTR_ERR(link);
}
至此完成路径查找的分析,主要包括普通路径的查找、链接路径的查找等信息。