diff --git a/fs/cifs/cifsglob.h b/fs/cifs/cifsglob.h index 8880a26f234f..b6c2a787be06 100644 --- a/fs/cifs/cifsglob.h +++ b/fs/cifs/cifsglob.h @@ -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; }; /* diff --git a/fs/cifs/misc.c b/fs/cifs/misc.c index e869c2a51034..af5e68a77811 100644 --- a/fs/cifs/misc.c +++ b/fs/cifs/misc.c @@ -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; diff --git a/fs/cifs/readdir.c b/fs/cifs/readdir.c index 1929e80c09ee..ccf34481d801 100644 --- a/fs/cifs/readdir.c +++ b/fs/cifs/readdir.c @@ -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; diff --git a/fs/cifs/smb2ops.c b/fs/cifs/smb2ops.c index ed2f8a69658d..7e2c86e0cede 100644 --- a/fs/cifs/smb2ops.c +++ b/fs/cifs/smb2ops.c @@ -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)