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:
Родитель
5752bf645f
Коммит
d87c48ce4d
|
@ -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;
|
||||
};
|
||||
|
||||
/*
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
¤t_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;
|
||||
|
|
|
@ -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)
|
||||
|
|
Загрузка…
Ссылка в новой задаче