cifs: cache the dirents for entries in a cached directory
authorRonnie Sahlberg <lsahlber@redhat.com>
Mon, 9 May 2022 23:42:07 +0000 (09:42 +1000)
committerSteve French <stfrench@microsoft.com>
Tue, 24 May 2022 19:33:11 +0000 (14:33 -0500)
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>
fs/cifs/cifsglob.h
fs/cifs/misc.c
fs/cifs/readdir.c
fs/cifs/smb2ops.c

index 8880a26..b6c2a78 100644 (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;
 };
 
 /*
index e869c2a..af5e68a 100644 (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;
index 1929e80..ccf3448 100644 (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;
index ed2f8a6..7e2c86e 100644 (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)