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:
parent
5752bf645f
commit
d87c48ce4d
|
@ -1052,6 +1052,27 @@ struct cifs_fattr {
|
||||||
u32 cf_cifstag;
|
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 {
|
struct cached_fid {
|
||||||
bool is_valid:1; /* Do we have a useable root fid */
|
bool is_valid:1; /* Do we have a useable root fid */
|
||||||
bool file_all_info_is_valid:1;
|
bool file_all_info_is_valid:1;
|
||||||
|
@ -1064,6 +1085,7 @@ struct cached_fid {
|
||||||
struct dentry *dentry;
|
struct dentry *dentry;
|
||||||
struct work_struct lease_break;
|
struct work_struct lease_break;
|
||||||
struct smb2_file_all_info file_all_info;
|
struct smb2_file_all_info file_all_info;
|
||||||
|
struct cached_dirents dirents;
|
||||||
};
|
};
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
@ -114,6 +114,8 @@ tconInfoAlloc(void)
|
||||||
kfree(ret_buf);
|
kfree(ret_buf);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
INIT_LIST_HEAD(&ret_buf->crfid.dirents.entries);
|
||||||
|
mutex_init(&ret_buf->crfid.dirents.de_mutex);
|
||||||
|
|
||||||
atomic_inc(&tconInfoAllocCount);
|
atomic_inc(&tconInfoAllocCount);
|
||||||
ret_buf->status = TID_NEW;
|
ret_buf->status = TID_NEW;
|
||||||
|
|
|
@ -840,9 +840,109 @@ find_cifs_entry(const unsigned int xid, struct cifs_tcon *tcon, loff_t pos,
|
||||||
return rc;
|
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,
|
static int cifs_filldir(char *find_entry, struct file *file,
|
||||||
struct dir_context *ctx,
|
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 cifsFileInfo *file_info = file->private_data;
|
||||||
struct super_block *sb = file_inode(file)->i_sb;
|
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 cifs_fattr fattr;
|
||||||
struct qstr name;
|
struct qstr name;
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
ino_t ino;
|
|
||||||
|
|
||||||
rc = cifs_fill_dirent(&de, find_entry, file_info->srch_inf.info_level,
|
rc = cifs_fill_dirent(&de, find_entry, file_info->srch_inf.info_level,
|
||||||
file_info->srch_inf.unicode);
|
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);
|
cifs_prime_dcache(file_dentry(file), &name, &fattr);
|
||||||
|
|
||||||
ino = cifs_uniqueid_to_ino_t(fattr.cf_uniqueid);
|
return !cifs_dir_emit(ctx, name.name, name.len,
|
||||||
return !dir_emit(ctx, name.name, name.len, ino, fattr.cf_dtype);
|
&fattr, cfid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -941,8 +1040,9 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
|
||||||
int rc = 0;
|
int rc = 0;
|
||||||
unsigned int xid;
|
unsigned int xid;
|
||||||
int i;
|
int i;
|
||||||
|
struct tcon_link *tlink = NULL;
|
||||||
struct cifs_tcon *tcon;
|
struct cifs_tcon *tcon;
|
||||||
struct cifsFileInfo *cifsFile = NULL;
|
struct cifsFileInfo *cifsFile;
|
||||||
char *current_entry;
|
char *current_entry;
|
||||||
int num_to_fill = 0;
|
int num_to_fill = 0;
|
||||||
char *tmp_buf = NULL;
|
char *tmp_buf = NULL;
|
||||||
|
@ -950,6 +1050,8 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
|
||||||
unsigned int max_len;
|
unsigned int max_len;
|
||||||
const char *full_path;
|
const char *full_path;
|
||||||
void *page = alloc_dentry_path();
|
void *page = alloc_dentry_path();
|
||||||
|
struct cached_fid *cfid = NULL;
|
||||||
|
struct cifs_sb_info *cifs_sb = CIFS_FILE_SB(file);
|
||||||
|
|
||||||
xid = get_xid();
|
xid = get_xid();
|
||||||
|
|
||||||
|
@ -959,6 +1061,56 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
|
||||||
goto rddir2_exit;
|
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
|
* Ensure FindFirst doesn't fail before doing filldir() for '.' and
|
||||||
* '..'. Otherwise we won't be able to notify VFS in case of failure.
|
* '..'. 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?
|
is in current search buffer?
|
||||||
if it before then restart search
|
if it before then restart search
|
||||||
if after then keep searching till find it */
|
if after then keep searching till find it */
|
||||||
|
|
||||||
cifsFile = file->private_data;
|
cifsFile = file->private_data;
|
||||||
if (cifsFile->srch_inf.endOfSearch) {
|
if (cifsFile->srch_inf.endOfSearch) {
|
||||||
if (cifsFile->srch_inf.emptyDir) {
|
if (cifsFile->srch_inf.emptyDir) {
|
||||||
|
@ -993,12 +1144,18 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
|
||||||
tcon = tlink_tcon(cifsFile->tlink);
|
tcon = tlink_tcon(cifsFile->tlink);
|
||||||
rc = find_cifs_entry(xid, tcon, ctx->pos, file, full_path,
|
rc = find_cifs_entry(xid, tcon, ctx->pos, file, full_path,
|
||||||
¤t_entry, &num_to_fill);
|
¤t_entry, &num_to_fill);
|
||||||
|
open_cached_dir(xid, tcon, full_path, cifs_sb, &cfid);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
cifs_dbg(FYI, "fce error %d\n", rc);
|
cifs_dbg(FYI, "fce error %d\n", rc);
|
||||||
goto rddir2_exit;
|
goto rddir2_exit;
|
||||||
} else if (current_entry != NULL) {
|
} else if (current_entry != NULL) {
|
||||||
cifs_dbg(FYI, "entry %lld found\n", ctx->pos);
|
cifs_dbg(FYI, "entry %lld found\n", ctx->pos);
|
||||||
} else {
|
} 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");
|
cifs_dbg(FYI, "Could not find entry\n");
|
||||||
goto rddir2_exit;
|
goto rddir2_exit;
|
||||||
}
|
}
|
||||||
|
@ -1028,7 +1185,7 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
|
||||||
*/
|
*/
|
||||||
*tmp_buf = 0;
|
*tmp_buf = 0;
|
||||||
rc = cifs_filldir(current_entry, file, ctx,
|
rc = cifs_filldir(current_entry, file, ctx,
|
||||||
tmp_buf, max_len);
|
tmp_buf, max_len, cfid);
|
||||||
if (rc) {
|
if (rc) {
|
||||||
if (rc > 0)
|
if (rc > 0)
|
||||||
rc = 0;
|
rc = 0;
|
||||||
|
@ -1036,6 +1193,12 @@ int cifs_readdir(struct file *file, struct dir_context *ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx->pos++;
|
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 ==
|
if (ctx->pos ==
|
||||||
cifsFile->srch_inf.index_of_last_entry) {
|
cifsFile->srch_inf.index_of_last_entry) {
|
||||||
cifs_dbg(FYI, "last entry in buf at pos %lld %s\n",
|
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);
|
kfree(tmp_buf);
|
||||||
|
|
||||||
rddir2_exit:
|
rddir2_exit:
|
||||||
|
if (cfid)
|
||||||
|
close_cached_dir(cfid);
|
||||||
free_dentry_path(page);
|
free_dentry_path(page);
|
||||||
free_xid(xid);
|
free_xid(xid);
|
||||||
return rc;
|
return rc;
|
||||||
|
|
|
@ -699,6 +699,7 @@ smb2_close_cached_fid(struct kref *ref)
|
||||||
{
|
{
|
||||||
struct cached_fid *cfid = container_of(ref, struct cached_fid,
|
struct cached_fid *cfid = container_of(ref, struct cached_fid,
|
||||||
refcount);
|
refcount);
|
||||||
|
struct cached_dirent *dirent, *q;
|
||||||
|
|
||||||
if (cfid->is_valid) {
|
if (cfid->is_valid) {
|
||||||
cifs_dbg(FYI, "clear cached root file handle\n");
|
cifs_dbg(FYI, "clear cached root file handle\n");
|
||||||
|
@ -718,6 +719,21 @@ smb2_close_cached_fid(struct kref *ref)
|
||||||
dput(cfid->dentry);
|
dput(cfid->dentry);
|
||||||
cfid->dentry = NULL;
|
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)
|
void close_cached_dir(struct cached_fid *cfid)
|
||||||
|
|
Loading…
Reference in New Issue