"内核OVERLAY--删除文件"

  "删除文件"

Posted by Xu on January 11, 2018

内核OVERLAY–删除文件

删除文件同创建文件一样,都会经过系统调用到vfs的转换然后找到对应文件系统的索引节点,再根据索引节点的操作函数来调用overlay文件系统的文件删除操作,overlay同样做了一层转换,在分析层次信息后,转换到对应联合挂载的目录所在的文件系统,同样经过第二次vfs转换调用该文件在对应文件系统索引节点的删除操作函数。

DeleteFile

这里省去系统调用经过vfs转换到overlay索引节点的删除函数的过程,直接进入overlay文件系统的删除操作。overlay文件系统的删除操作函数有两个:ovl_unlink()和ovl_rmdir()

static int ovl_unlink(struct inode *dir, struct dentry *dentry)
{
	return ovl_do_remove(dentry, false);
}

static int ovl_rmdir(struct inode *dir, struct dentry *dentry)
{
	return ovl_do_remove(dentry, true);
}

这两个函数都是对ovl_do_remove()函数的封装调用,我们看一下该函数的实现:

ovl_do_remove():

static int ovl_do_remove(struct dentry *dentry, bool is_dir)
{
	enum ovl_path_type type;
	int err;

	err = ovl_check_sticky(dentry);
	if (err)
		goto out;

	err = ovl_want_write(dentry);//获取写权限
	if (err)
		goto out;

	err = ovl_copy_up(dentry->d_parent);//COW检测
	if (err)
		goto out_drop_write;

	type = ovl_path_type(dentry);//根据层次信息获取文件路径类型
	if (OVL_TYPE_PURE_UPPER(type)) {
		err = ovl_remove_upper(dentry, is_dir);//如果该文件只位于upper层,调用ovl_remove_upper
	} else {
		const struct cred *old_cred;
		struct cred *override_cred;

		err = -ENOMEM;
		override_cred = prepare_creds();
		if (!override_cred)
			goto out_drop_write;

		/*
		 * CAP_SYS_ADMIN for setting xattr on whiteout, opaque dir
		 * CAP_DAC_OVERRIDE for create in workdir, rename
		 * CAP_FOWNER for removing whiteout from sticky dir
		 * CAP_FSETID for chmod of opaque dir
		 * CAP_CHOWN for chown of opaque dir
		 */
		 //设置权限位
		cap_raise(override_cred->cap_effective, CAP_SYS_ADMIN);
		cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE);
		cap_raise(override_cred->cap_effective, CAP_FOWNER);
		cap_raise(override_cred->cap_effective, CAP_FSETID);
		cap_raise(override_cred->cap_effective, CAP_CHOWN);
		old_cred = override_creds(override_cred);

		err = ovl_remove_and_whiteout(dentry, is_dir);//若底层有该文件则调用ovl_remove_and_whiteout

		revert_creds(old_cred);
		put_cred(override_cred);
	}
out_drop_write:
	ovl_drop_write(dentry);
out:
	return err;
}

ovl_remove_upper():

当文件只位于上层时:ovl_remove_upper()

static int ovl_remove_upper(struct dentry *dentry, bool is_dir)
{
	struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);//获取父目录在upper层的目录项
	struct inode *dir = upperdir->d_inode;//获取父目录在upper层的索引节点对象
	struct dentry *upper;
	int err;

	mutex_lock_nested(&dir->i_mutex, I_MUTEX_PARENT);
	upper = lookup_one_len(dentry->d_name.name, upperdir,
			       dentry->d_name.len);//根据upper层父目录的目录项结构和要删除的文件名称查询要删除文件的目录项
	err = PTR_ERR(upper);
	if (IS_ERR(upper))
		goto out_unlock;

	err = -ESTALE;
	if (upper == ovl_dentry_upper(dentry)) {//如果查询到的目录项结构和层次信息中记录的目录项结构吻合则继续
		if (is_dir)
			err = vfs_rmdir(dir, upper);//如果是目录调用vfs_rmdir,进行第二次vfs转换
		else
			err = vfs_unlink(dir, upper, NULL);//如果不是目录调用vfs_unlink,进行第二次vfs转换

		ovl_dentry_version_inc(dentry->d_parent);
	}
	dput(upper);

	/*
	 * Keeping this dentry hashed would mean having to release
	 * upperpath/lowerpath, which could only be done if we are the
	 * sole user of this dentry.  Too tricky...  Just unhash for
	 * now.
	 */
	if (!err)
		d_drop(dentry);
out_unlock:
	mutex_unlock(&dir->i_mutex);

	return err;
}

ovl_remove_and_whiteout():

当底层存在该文件时,调用ovl_remove_and_whiteout:

static int ovl_remove_and_whiteout(struct dentry *dentry, bool is_dir)
{
	struct dentry *workdir = ovl_workdir(dentry);//获取该文件在工作目录的目录项
	struct inode *wdir = workdir->d_inode;//获取该文件在工作目录的索引节点
	struct dentry *upperdir = ovl_dentry_upper(dentry->d_parent);//获取该文件父目录在upper层的目录项
	struct inode *udir = upperdir->d_inode;//获取该文件父目录在upper层的索引节点
	struct dentry *whiteout;
	struct dentry *upper;
	struct dentry *opaquedir = NULL;
	int err;
	int flags = 0;

	if (WARN_ON(!workdir))
		return -EROFS;

	if (is_dir) {
	//如果是目录
		if (OVL_TYPE_MERGE_OR_LOWER(ovl_path_type(dentry))) {//当文件只位于lower层时
			opaquedir = ovl_check_empty_and_clear(dentry);//检查该文件是否为空, 为空则删除该空目录
			err = PTR_ERR(opaquedir);
			if (IS_ERR(opaquedir))
				goto out;
		} else {
		//当文件不仅仅位于lower时
			LIST_HEAD(list);

			/*
			 * When removing an empty opaque directory, then it
			 * makes no sense to replace it with an exact replica of
			 * itself.  But emptiness still needs to be checked.
			 */
			err = ovl_check_empty_dir(dentry, &list);//同样检测是否该目录为空
			ovl_cache_free(&list);//释放缓存列表
			if (err)
				goto out;
		}
	}

	err = ovl_lock_rename_workdir(workdir, upperdir);//获取工作目录和上层目录的重命名锁
	if (err)
		goto out_dput;

	upper = lookup_one_len(dentry->d_name.name, upperdir,
			       dentry->d_name.len);//根据上层目录的目录项查询和该删除文件的名称来获取该文件在upper目录项upper
	err = PTR_ERR(upper);
	if (IS_ERR(upper))
		goto out_unlock;

	err = -ESTALE;
	if ((opaquedir && upper != opaquedir) ||
	    (!opaquedir && ovl_dentry_upper(dentry) &&
	     upper != ovl_dentry_upper(dentry))) {
		goto out_dput_upper;
	}

	whiteout = ovl_whiteout(workdir, dentry);//在工作目录生成该删除文件的witheout文件
	err = PTR_ERR(whiteout);
	if (IS_ERR(whiteout))
		goto out_dput_upper;

	if (d_is_dir(upper))
		flags = RENAME_EXCHANGE;

	err = ovl_do_rename(wdir, whiteout, udir, upper, flags);//进行重命名操作将whiteout文件移动到upper层
	if (err)
		goto kill_whiteout;
	if (flags)
		ovl_cleanup(wdir, upper);

	ovl_dentry_version_inc(dentry->d_parent);
out_d_drop:
	d_drop(dentry);
	dput(whiteout);
out_dput_upper:
	dput(upper);
out_unlock:
	unlock_rename(workdir, upperdir);
out_dput:
	dput(opaquedir);
out:
	return err;

kill_whiteout:
	ovl_cleanup(wdir, whiteout);
	goto out_d_drop;
}

ovl_whiteout():

在工作目录生成whiteout文件:

static struct dentry *ovl_whiteout(struct dentry *workdir,
				   struct dentry *dentry)
{
	int err;
	struct dentry *whiteout;
	struct inode *wdir = workdir->d_inode;

	whiteout = ovl_lookup_temp(workdir, dentry);//生成临时whiteout文件的目录项
	if (IS_ERR(whiteout))
		return whiteout;

	err = ovl_do_whiteout(wdir, whiteout);//首先确认创建索引节点的权限,然后创建特殊索引节点
	if (err) {
		dput(whiteout);
		whiteout = ERR_PTR(err);
	}

	return whiteout;
}