Linux虚拟文件系统--文件路径名的解析(1)--整体过程

时间:2022-11-26 07:57:35

        文件路径名的解析是VFS中最基本也是最频繁用到的一个部分,它的代码实现还是十分繁杂的,主要是因为除了普通文件名的解析,内核还要考虑各种可能出现的情况,如一个目录下挂载了多个文件系统,路径中的符号链接等等……后面我会分几次将整个过程进行一个尽量仔细的分析,其中所涉及到的各种数据结构在ULK等相关内核书籍上都有比较详细的介绍,就不列出来了

       文件路径名的解析路口函数为path_lookup(),如下:

int path_lookup(const char *name, unsigned int flags,
			struct nameidata *nd)
{
	return do_path_lookup(AT_FDCWD, name, flags, nd);
}

name:路径名

flags:查找操作的标识

struct nameidata:用于保存当前的相关查找结果


这里可以看到path_lookup()只是对do_path_lookup()的一层封装

 

static int do_path_lookup(int dfd, const char *name,
				unsigned int flags, struct nameidata *nd)
{
	/*path_init进行一些搜索前的初始化工作,主要是确定起始搜索的起点并保存在nd中*/
	int retval = path_init(dfd, name, flags, nd);
	
	if (!retval)//初始化没问题的话就开始解析
		retval = path_walk(name, nd);
	if (unlikely(!retval && !audit_dummy_context() && nd->path.dentry &&
				nd->path.dentry->d_inode))
		audit_inode(name, nd->path.dentry);
	if (nd->root.mnt) {
		path_put(&nd->root);
		nd->root.mnt = NULL;
	}
	return retval;
}

 

 

static int path_init(int dfd, const char *name, unsigned int flags, struct nameidata *nd) { int retval = 0; int fput_needed; struct file *file; nd->last_type = LAST_ROOT; /* if there are only slashes... */ nd->flags = flags; nd->depth = 0; nd->root.mnt = NULL; if (*name=='/') {//如果文件路径是以绝对路径的形式给出 set_root(nd);//设置nd的根目录 nd->path = nd->root;//当前的path从根目录开始 path_get(&nd->root); } else if (dfd == AT_FDCWD) { struct fs_struct *fs = current->fs; read_lock(&fs->lock); nd->path = fs->pwd;//获取当前目录的path path_get(&fs->pwd); read_unlock(&fs->lock); } else { struct dentry *dentry; /*根据dfd,从当前进程的fs_struct结构的fdtable中取出文件描述符指针*/ file = fget_light(dfd, &fput_needed); retval = -EBADF; if (!file) goto out_fail; //从文件描述符中获取dentry dentry = file->f_path.dentry; retval = -ENOTDIR; if (!S_ISDIR(dentry->d_inode->i_mode))//如果不是目录 goto fput_fail; //相应的权限检查 retval = file_permission(file, MAY_EXEC); if (retval) goto fput_fail; nd->path = file->f_path;//获取path path_get(&file->f_path); fput_light(file, fput_needed); } return 0; fput_fail: fput_light(file, fput_needed); out_fail: return retval; }

注意之前传递进来的dfd为AT_FDCWD,因此path_init中只有可能出现前两种情况:1.路径以绝对路径的方式给出 2.路径以相对路径的方式给出。此时nd中的path保存了查找的起始目录,对于第一种情况,即为'/',第二种情况起始目录要从当前进程的fs结构中提取。

下面通过path_walk()开始进行解析,我们直接进入path_walk()-->link_path_walk()-->__link_path_walk()进行分析,因为前面几个函数都没做什么实质性的工作。

static int __link_path_walk(const char *name, struct nameidata *nd)
{
	struct path next;
	struct inode *inode;
	int err;
	unsigned int lookup_flags = nd->flags;
	
	while (*name=='/')//忽略文件名前面的'/'
		name++;
	if (!*name)
		goto return_reval;

	inode = nd->path.dentry->d_inode;//获取当前目录的inode
	if (nd->depth)
		lookup_flags = LOOKUP_FOLLOW | (nd->flags & LOOKUP_CONTINUE);

	/* At this point we know we have a real path component. */
	for(;;) {
		unsigned long hash;
		struct qstr this;
		unsigned int c;

		nd->flags |= LOOKUP_CONTINUE;
		err = exec_permission_lite(inode);//进行访问权限的检查
 		if (err)
			break;
		
		this.name = name;//当前进行解析的文件名分量
		c = *(const unsigned char *)name;

		hash = init_name_hash();//hash值初始化为0
		/*计算当前文件名分量的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);//保存当前文件名分量的hash值

		/* remove trailing slashes? */
		if (!c)//文件名解析完毕
			goto last_component;
		while (*++name == '/');//跳过'/'
		if (!*name)
			goto last_with_slashes;

		/*
		 * "." 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://文件名长度为2并且下一个字符也为'.'则表明要退回到上级目录	
				if (this.name[1] != '.')
					break;
				follow_dotdot(nd);
				inode = nd->path.dentry->d_inode;
				//注意这里没有break,因此将通过后面的continue直接回到循环开头
			case 1://长度为1表示当前目录,则直接忽略即可
				continue;
		}

		/*下面的部分用来处理普通的文件路径分量(不为.和..)*/
		 
		 /*如果底层文件系统的dentry定义了d_op和d_hash则调用文件系统中的d_hash进行hash值的计算*/
		if (nd->path.dentry->d_op && nd->path.dentry->d_op->d_hash) {
			err = nd->path.dentry->d_op->d_hash(nd->path.dentry,
							    &this);
			if (err < 0)
				break;
		}
		
		//do_lookup执行实际的查找,注意nd中的path对应的是父目录,而this是对应的当前解析的路径分量
		err = do_lookup(nd, &this, &next);
		if (err)
			break;

		err = -ENOENT;
		inode = next.dentry->d_inode;//获取刚刚解析的路径分量的inode
		if (!inode)
			goto out_dput;

		if (inode->i_op->follow_link) {//如果刚刚解析的路径为符号链接,则通过do_follow_link进行处理
			err = do_follow_link(&next, nd);
			if (err)
				goto return_err;
			err = -ENOENT;
			inode = nd->path.dentry->d_inode;
			if (!inode)
				break;
		} else//不为符号链接,则路径分量解析完毕,根据next更新nd中的path准备解析下一个分量
			path_to_nameidata(&next, nd);
		err = -ENOTDIR; 
		if (!inode->i_op->lookup)//如果刚刚解析的路径分量对应的inode没有定义lookup函数,
			break;				  //则无法以此为父目录进行解析了

		continue;//这里标识一次解析完毕,跳转到开头继续解析下一个分量				
		
		/* here ends the main loop */

last_with_slashes:
		lookup_flags |= LOOKUP_FOLLOW | LOOKUP_DIRECTORY;
last_component://到了最后一个分量
		/* Clear LOOKUP_CONTINUE iff it was previously unset */
		nd->flags &= lookup_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);
				inode = nd->path.dentry->d_inode;
				/* fallthrough */
			case 1:
				goto return_reval;
		}
		if (nd->path.dentry->d_op && nd->path.dentry->d_op->d_hash) {
			err = nd->path.dentry->d_op->d_hash(nd->path.dentry,
							    &this);
			if (err < 0)
				break;
		}
		err = do_lookup(nd, &this, &next);
		if (err)
			break;
		inode = next.dentry->d_inode;
		if (follow_on_final(inode, lookup_flags)) {
			err = do_follow_link(&next, nd);
			if (err)
				goto return_err;
			inode = nd->path.dentry->d_inode;
		} else
			path_to_nameidata(&next, nd);
		err = -ENOENT;
		if (!inode)
			break;
		if (lookup_flags & LOOKUP_DIRECTORY) {
			err = -ENOTDIR; 
			if (!inode->i_op->lookup)
				break;
		}
		goto return_base;
lookup_parent://如果目标是最后一个分量的父目录,则不用进行查找了,
				//因为nd的path总是指向当前要分析的路径的父目录的
				//下面只需要对nd的last_tpye字段进行处理,表示最后一个分量的类型
		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->path.dentry && nd->path.dentry->d_sb &&
		    (nd->path.dentry->d_sb->s_type->fs_flags & FS_REVAL_DOT)) {
			err = -ESTALE;
			/* Note: we do not d_invalidate() */
			if (!nd->path.dentry->d_op->d_revalidate(
					nd->path.dentry, nd))
				break;
		}
return_base:
		return 0;
out_dput:
		path_put_conditional(&next, nd);
		break;
	}
	path_put(&nd->path);
return_err:
	return err;
}


其中几个比较重要的函数,follow_dotdot()--退回到上级目录, do_lookup()--普通文件名的实际查找工作, do_follow_link()--追踪符号链接,在下篇博文再拿出来进行具体分析。