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: Paulo Alcantara (SUSE) <pc@cjr.nz>
Reviewed-by: Enzo Matsumiya <ematsumiya@suse.de>
Signed-off-by: Ronnie Sahlberg <lsahlber@redhat.com>
Signed-off-by: Steve French <stfrench@microsoft.com>
This commit is contained in:
Ronnie Sahlberg 2022-05-10 09:42:07 +10:00 committed by Steve French
parent 5752bf645f
commit d87c48ce4d
4 changed files with 213 additions and 8 deletions

View File

@ -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;
};
/*

View File

@ -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;

View File

@ -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)
struct dir_context *ctx,
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;

View File

@ -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)