Linux open系统调用(四)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/u010039418/article/details/88067658

注:本文分析基于3.10.0-693.el7内核版本,即CentOS 7.4

上回说到普通目录的情况,这次接着说说符号链接的事。

static inline int walk_component(struct nameidata *nd, struct path *path,
		int follow)
{
	...
	if (unlikely(nd->last_type != LAST_NORM)) 
		return handle_dots(nd, nd->last_type); 	//.和..的处理
		
	err = lookup_fast(nd, path, &inode);	//普通目录的处理,先在内存中找
	if (unlikely(err)) {
		err = lookup_slow(nd, path);  //调用对应文件系统的查找函数找
	}
	err = -ENOENT;
	if (!inode || d_is_negative(path->dentry))
		goto out_path_put;
	//如果这是个符号链接,就要另外处理了
	if (should_follow_link(path->dentry, follow)) {
		//如果处于RCU模式,要先切换到ref-walk模式,因为符号链接追踪可能会涉及底层驱动
		//会有阻塞发生,因此不能在RCU模式下
		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;
	}
	...
}

如果普通目录的查找结果表明该dentry是个符号链接,那就要跟踪这个链接,找到实际目录。这种情况下,walk_component函数返回值为1。

static int link_path_walk(const char *name, struct nameidata *nd)
{
	...
	err = walk_component(nd, &next, LOOKUP_FOLLOW);
	if (err < 0)
		return err;

	if (err) {
		//处理符号链接的情况
		err = nested_symlink(&next, nd);
		if (err)
			return err;
	}
	...
}

返回到link_path_walk函数,接着处理符号链接,调用nested_symlink函数。

static inline int nested_symlink(struct path *path, struct nameidata *nd)
{
	int res;
	//通过进程信息,检查符号链接嵌套深度,最大为MAX_NESTED_LINKS,8层
	if (unlikely(current->link_count >= MAX_NESTED_LINKS)) {
		path_put_conditional(path, nd);
		path_put(&nd->path);
		return -ELOOP;
	}
	//通过遍历路径的记录来检查符号链接嵌套深度
	BUG_ON(nd->depth >= MAX_NESTED_LINKS);

	nd->depth++;
	current->link_count++;
	//这里do-while循环是因为有可能符号链接指向的还是一个符号链接
	//因此
	do {
		struct path link = *path;
		void *cookie;
		//跟踪符号链接
		res = follow_link(&link, nd, &cookie);
		if (res)
			break;
		//到这里也就找到了符号链接最终目标的最后一级目录,还需再判断是否是符号链接
		res = walk_component(nd, path, LOOKUP_FOLLOW);
		put_link(nd, &link, cookie);
	} while (res > 0);//如果不是符号链接,就可以返回了,算是完成了符号链接的跟踪

	current->link_count--;
	nd->depth--;
	return res;
}

由于符号链接可能出现环路,为避免死循环,必须对符号跟踪深度做个限制,然后再跟随这个符号链接去寻找目标。

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;
	//即使没有循环嵌套,也有可能每一级目录都是一个符号链接
	//限定一个路径中最多的符号链接数为40
	if (unlikely(current->total_link_count >= 40))
		goto out_put_nd_path;

	cond_resched();
	current->total_link_count++;
	//更新符号链接的访问时间
	touch_atime(link);
	//置空nd->saved_names数组对应链接深度
	//该变量用于保存对应符号链接深度指向的目标
	nd_set_link(nd, NULL);

	error = security_inode_follow_link(link->dentry, nd);
	if (error)
		goto out_put_nd_path;

	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);
	}
	...
}

follow_link最后会调用对应文件系统的跟随函数去寻找对应的目标。该目标存在nd->saved_names数组中。因为符号链接最大递归深度是8,saved_names数组为每一个递归深度的目标目录准备了存放的位置。

enum { MAX_NESTED_LINKS = 8 };

struct nameidata {
	...
	char *saved_names[MAX_NESTED_LINKS + 1];
	RH_KABI_EXTEND(unsigned  m_seq)
};

顺着符号链接找到目标后,就要开启新一轮的跟踪,因为这又是一个新的目录路径。

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;
	//接着调用路径查找函数link_path_walk
	//别忘了,我们当前也还在link_path_walk函数中
	//但是这并不是一次嵌套,调用nested_symlink才算试一次嵌套
	ret = link_path_walk(link, nd);
	return ret;
fail:
	path_put(&nd->path);
	return PTR_ERR(link);
}

其实当前我们仍然还在link_path_walk函数中,调用link_path_walk函数是因为符号链接指向的路径和最开始需要处理的目录一样,也需要遍历到最后一个分量。比如,一开始open的文件是/home/a/file,目录遍历到a/分量时,发现是个符号链接,那此时其实就是又要从open开始,执行打开/home/realdir/的操作了,类似的流程再来一遍。

最终,follow_link函数获取到符号链接的目标目录的最后一级目录项,返回后,会继续调用walk_component检查最后一级目录分量是否是符号链接,如果还是符号链接,那就进入下一级嵌套中。如果是个普通目录,那就将nd更新至目标dentry。符号链接的跟踪也算是完成了。

下面我们通过以下场景来具体阐述一下符号链接的处理流程。

场景描述:

open(/home/test/file, ……),其中路径分量test/是一个符号链接,指向/home/open/realdir/,home/、open/和realdir/目录都是普通目录,则此时代码路径如下:

path_openat
	-> link_path_walk
		-> walk_component(判断是否为符号链接)
			-> should_follow_link(此时test/是个符号链接)
				return follow
			-> return 1
		-> nested_symlink(跟踪符号链接)
			-> do {
				-> follow_link
					-> __vfs_follow_link(获取到test/符号链接的目标:/home/open/realdir/)
						-> link_path_walk
							-> walk_component
								-> should_follow_link(此时home/和open/都是普通目录)
									-> return don't_follow
								-> return 0
							-> return 0
						-> return 0
					-> return 0
				-> walk_component
					-> should_follow_link(最后一级目标realdir/是普通目录)
						-> return don't_follow
					-> return 0
			}while(如果符号链接的目标仍然是符号链接,继续跟踪)
			-> return 0
		-> return 0
	-> do_something

主要流程:
1、通过link_path_walk对路径/home/test/进行查找,设该层遍历为A;

2、假定home/目录是普通目录,直到walk_component函数遍历到test/目录时,发现test/是个符号链接;

3、返回到A层遍历link_path_walk函数中,继续调用nested_symlink函数处理符号链接;

4、nested_symlink函数中通过follow_link函数获取到test/指向的文件路径为/home/open/realdir/,然后通过__vfs_follow_link函数对路径/home/open/realdir/进行新一轮的查找工作;

5、__vfs_follow_link函数中调用link_path_walk函数对路径/home/open/realdir/进行遍历(设该层遍历为B),最终获取路径的最后一个目录项realdir/,这里假定home/和open/目录都是普通目录;

6、返回到nested_symlink函数,调用walk_component函数判断最后一级目录项realdir/为普通目录,更新nd信息,完成符号链接的跟踪,并返回A层遍历的link_path_walk函数中。

如果目录项realdir/依然是个符号链接,那就只能在do-while循环里再走一遭,直到最终的目录不是符号链接为止。

猜你喜欢

转载自blog.csdn.net/u010039418/article/details/88067658