Commit d87c48ce authored by Ronnie Sahlberg's avatar Ronnie Sahlberg Committed by Steve French
Browse files

cifs: cache the dirents for entries in a cached directory



This adds caching of the directory entries for a cached directory while we keep
a lease on the directory.

Reviewed-by: default avatarPaulo Alcantara (SUSE) <pc@cjr.nz>
Reviewed-by: default avatarEnzo Matsumiya <ematsumiya@suse.de>
Signed-off-by: default avatarRonnie Sahlberg <lsahlber@redhat.com>
Signed-off-by: default avatarSteve French <stfrench@microsoft.com>
parent 5752bf64
Loading
Loading
Loading
Loading
+22 −0
Original line number Diff line number Diff line
@@ -1052,6 +1052,27 @@ struct cifs_fattr {
	u32             cf_cifstag;
};

struct cached_dirent {
	struct list_head entry;
	char *name;
	int namelen;
	loff_t pos;

	struct cifs_fattr fattr;
};

struct cached_dirents {
	bool is_valid:1;
	bool is_failed:1;
	struct dir_context *ctx; /*
				  * Only used to make sure we only take entries
				  * from a single context. Never dereferenced.
				  */
	struct mutex de_mutex;
	int pos;		 /* Expected ctx->pos */
	struct list_head entries;
};

struct cached_fid {
	bool is_valid:1;	/* Do we have a useable root fid */
	bool file_all_info_is_valid:1;
@@ -1064,6 +1085,7 @@ struct cached_fid {
	struct dentry *dentry;
	struct work_struct lease_break;
	struct smb2_file_all_info file_all_info;
	struct cached_dirents dirents;
};

/*
+2 −0
Original line number Diff line number Diff line
@@ -114,6 +114,8 @@ tconInfoAlloc(void)
		kfree(ret_buf);
		return NULL;
	}
	INIT_LIST_HEAD(&ret_buf->crfid.dirents.entries);
	mutex_init(&ret_buf->crfid.dirents.de_mutex);

	atomic_inc(&tconInfoAllocCount);
	ret_buf->status = TID_NEW;
+173 −8
Original line number Diff line number Diff line
@@ -840,9 +840,109 @@ find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos,
	return rc;
}

static bool emit_cached_dirents(struct cached_dirents *cde,
				struct dir_context *ctx)
{
	struct cached_dirent *dirent;
	int rc;

	list_for_each_entry(dirent, &cde->entries, entry) {
		if (ctx->pos >= dirent->pos)
			continue;
		ctx->pos = dirent->pos;
		rc = dir_emit(ctx, dirent->name, dirent->namelen,
			      dirent->fattr.cf_uniqueid,
			      dirent->fattr.cf_dtype);
		if (!rc)
			return rc;
	}
	return true;
}

static void update_cached_dirents_count(struct cached_dirents *cde,
					struct dir_context *ctx)
{
	if (cde->ctx != ctx)
		return;
	if (cde->is_valid || cde->is_failed)
		return;

	cde->pos++;
}

static void finished_cached_dirents_count(struct cached_dirents *cde,
					struct dir_context *ctx)
{
	if (cde->ctx != ctx)
		return;
	if (cde->is_valid || cde->is_failed)
		return;
	if (ctx->pos != cde->pos)
		return;

	cde->is_valid = 1;
}

static void add_cached_dirent(struct cached_dirents *cde,
			      struct dir_context *ctx,
			      const char *name, int namelen,
			      struct cifs_fattr *fattr)
{
	struct cached_dirent *de;

	if (cde->ctx != ctx)
		return;
	if (cde->is_valid || cde->is_failed)
		return;
	if (ctx->pos != cde->pos) {
		cde->is_failed = 1;
		return;
	}
	de = kzalloc(sizeof(*de), GFP_ATOMIC);
	if (de == NULL) {
		cde->is_failed = 1;
		return;
	}
	de->namelen = namelen;
	de->name = kstrndup(name, namelen, GFP_ATOMIC);
	if (de->name == NULL) {
		kfree(de);
		cde->is_failed = 1;
		return;
	}
	de->pos = ctx->pos;

	memcpy(&de->fattr, fattr, sizeof(struct cifs_fattr));

	list_add_tail(&de->entry, &cde->entries);
}

static bool cifs_dir_emit(struct dir_context *ctx,
			  const char *name, int namelen,
			  struct cifs_fattr *fattr,
			  struct cached_fid *cfid)
{
	bool rc;
	ino_t ino = cifs_uniqueid_to_ino_t(fattr->cf_uniqueid);

	rc = dir_emit(ctx, name, namelen, ino, fattr->cf_dtype);
	if (!rc)
		return rc;

	if (cfid) {
		mutex_lock(&cfid->dirents.de_mutex);
		add_cached_dirent(&cfid->dirents, ctx, name, namelen,
				  fattr);
		mutex_unlock(&cfid->dirents.de_mutex);
	}

	return rc;
}

static int cifs_filldir(char *find_entry, struct file *file,
			struct dir_context *ctx,
		char *scratch_buf, unsigned int max_len)
			char *scratch_buf, unsigned int max_len,
			struct cached_fid *cfid)
{
	struct cifsFileInfo *file_info = file->private_data;
	struct super_block *sb = file_inode(file)->i_sb;
@@ -851,7 +951,6 @@ static int cifs_filldir(char *find_entry, struct file *file,
	struct cifs_fattr fattr;
	struct qstr name;
	int rc = 0;
	ino_t ino;

	rc = cifs_fill_dirent(&de, find_entry, file_info->srch_inf.info_level,
			      file_info->srch_inf.unicode);
@@ -931,8 +1030,8 @@ static int cifs_filldir(char *find_entry, struct file *file,

	cifs_prime_dcache(file_dentry(file), &name, &fattr);

	ino = cifs_uniqueid_to_ino_t(fattr.cf_uniqueid);
	return !dir_emit(ctx, name.name, name.len, ino, fattr.cf_dtype);
	return !cifs_dir_emit(ctx, name.name, name.len,
			      &fattr, cfid);
}


@@ -941,8 +1040,9 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
	int rc = 0;
	unsigned int xid;
	int i;
	struct tcon_link *tlink = NULL;
	struct cifs_tcon *tcon;
	struct cifsFileInfo *cifsFile = NULL;
	struct cifsFileInfo *cifsFile;
	char *current_entry;
	int num_to_fill = 0;
	char *tmp_buf = NULL;
@@ -950,6 +1050,8 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
	unsigned int max_len;
	const char *full_path;
	void *page = alloc_dentry_path();
	struct cached_fid *cfid = NULL;
	struct cifs_sb_info *cifs_sb = CIFS_FILE_SB(file);

	xid = get_xid();

@@ -959,6 +1061,56 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
		goto rddir2_exit;
	}

	if (file->private_data == NULL) {
		tlink = cifs_sb_tlink(cifs_sb);
		if (IS_ERR(tlink))
			goto cache_not_found;
		tcon = tlink_tcon(tlink);
	} else {
		cifsFile = file->private_data;
		tcon = tlink_tcon(cifsFile->tlink);
	}

	rc = open_cached_dir(xid, tcon, full_path, cifs_sb, &cfid);
	cifs_put_tlink(tlink);
	if (rc)
		goto cache_not_found;

	mutex_lock(&cfid->dirents.de_mutex);
	/*
	 * If this was reading from the start of the directory
	 * we need to initialize scanning and storing the
	 * directory content.
	 */
	if (ctx->pos == 0 && cfid->dirents.ctx == NULL) {
		cfid->dirents.ctx = ctx;
		cfid->dirents.pos = 2;
	}
	/*
	 * If we already have the entire directory cached then
	 * we can just serve the cache.
	 */
	if (cfid->dirents.is_valid) {
		if (!dir_emit_dots(file, ctx)) {
			mutex_unlock(&cfid->dirents.de_mutex);
			goto rddir2_exit;
		}
		emit_cached_dirents(&cfid->dirents, ctx);
		mutex_unlock(&cfid->dirents.de_mutex);
		goto rddir2_exit;
	}
	mutex_unlock(&cfid->dirents.de_mutex);

	/* Drop the cache while calling initiate_cifs_search and
	 * find_cifs_entry in case there will be reconnects during
	 * query_directory.
	 */
	if (cfid) {
		close_cached_dir(cfid);
		cfid = NULL;
	}

 cache_not_found:
	/*
	 * Ensure FindFirst doesn't fail before doing filldir() for '.' and
	 * '..'. Otherwise we won't be able to notify VFS in case of failure.
@@ -977,7 +1129,6 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
		is in current search buffer?
		if it before then restart search
		if after then keep searching till find it */

	cifsFile = file->private_data;
	if (cifsFile->srch_inf.endOfSearch) {
		if (cifsFile->srch_inf.emptyDir) {
@@ -993,12 +1144,18 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
	tcon = tlink_tcon(cifsFile->tlink);
	rc = find_cifs_entry(xid, tcon, ctx->pos, file, full_path,
			     &current_entry, &num_to_fill);
	open_cached_dir(xid, tcon, full_path, cifs_sb, &cfid);
	if (rc) {
		cifs_dbg(FYI, "fce error %d\n", rc);
		goto rddir2_exit;
	} else if (current_entry != NULL) {
		cifs_dbg(FYI, "entry %lld found\n", ctx->pos);
	} else {
		if (cfid) {
			mutex_lock(&cfid->dirents.de_mutex);
			finished_cached_dirents_count(&cfid->dirents, ctx);
			mutex_unlock(&cfid->dirents.de_mutex);
		}
		cifs_dbg(FYI, "Could not find entry\n");
		goto rddir2_exit;
	}
@@ -1028,7 +1185,7 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
		 */
		*tmp_buf = 0;
		rc = cifs_filldir(current_entry, file, ctx,
				  tmp_buf, max_len);
				  tmp_buf, max_len, cfid);
		if (rc) {
			if (rc > 0)
				rc = 0;
@@ -1036,6 +1193,12 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
		}

		ctx->pos++;
		if (cfid) {
			mutex_lock(&cfid->dirents.de_mutex);
			update_cached_dirents_count(&cfid->dirents, ctx);
			mutex_unlock(&cfid->dirents.de_mutex);
		}

		if (ctx->pos ==
			cifsFile->srch_inf.index_of_last_entry) {
			cifs_dbg(FYI, "last entry in buf at pos %lld %s\n",
@@ -1050,6 +1213,8 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
	kfree(tmp_buf);

rddir2_exit:
	if (cfid)
		close_cached_dir(cfid);
	free_dentry_path(page);
	free_xid(xid);
	return rc;
+16 −0
Original line number Diff line number Diff line
@@ -699,6 +699,7 @@ smb2_close_cached_fid(struct kref *ref)
{
	struct cached_fid *cfid = container_of(ref, struct cached_fid,
					       refcount);
	struct cached_dirent *dirent, *q;

	if (cfid->is_valid) {
		cifs_dbg(FYI, "clear cached root file handle\n");
@@ -718,6 +719,21 @@ smb2_close_cached_fid(struct kref *ref)
		dput(cfid->dentry);
		cfid->dentry = NULL;
	}
	/*
	 * Delete all cached dirent names
	 */
	mutex_lock(&cfid->dirents.de_mutex);
	list_for_each_entry_safe(dirent, q, &cfid->dirents.entries, entry) {
		list_del(&dirent->entry);
		kfree(dirent->name);
		kfree(dirent);
	}
	cfid->dirents.is_valid = 0;
	cfid->dirents.is_failed = 0;
	cfid->dirents.ctx = NULL;
	cfid->dirents.pos = 0;
	mutex_unlock(&cfid->dirents.de_mutex);

}

void close_cached_dir(struct cached_fid *cfid)