]> asedeno.scripts.mit.edu Git - linux.git/commitdiff
fuse: add readdir cache version
authorMiklos Szeredi <mszeredi@redhat.com>
Mon, 1 Oct 2018 08:07:04 +0000 (10:07 +0200)
committerMiklos Szeredi <mszeredi@redhat.com>
Mon, 1 Oct 2018 08:07:04 +0000 (10:07 +0200)
Allow the cache to be invalidated when page(s) have gone missing.  In this
case increment the version of the cache and reset to an empty state.

Add a version number to the directory stream in struct fuse_file as well,
indicating the version of the cache it's supposed to be reading.  If the
cache version doesn't match the stream's version, then reset the stream to
the beginning of the cache.

Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
fs/fuse/fuse_i.h
fs/fuse/inode.c
fs/fuse/readdir.c

index 49e42635e3ac9439c7444e6bbfb624d18685498e..8b24805e62eeebed20b04ca842282b5b19d08f44 100644 (file)
@@ -114,6 +114,9 @@ struct fuse_inode {
                /* position at end of cache (position of next entry) */
                loff_t pos;
 
+               /* version of the cache */
+               u64 version;
+
                /* protects above fields */
                spinlock_t lock;
        } rdc;
@@ -176,6 +179,10 @@ struct fuse_file {
 
                /* Offset in cache */
                loff_t cache_off;
+
+               /* Version of cache we are reading */
+               u64 version;
+
        } readdir;
 
        /** RB node to be linked on fuse_conn->polled_files */
index 892efe6351eb0651094acde025b4e99e4bc69b46..eef2ae713f75a55bcbfcb59b8d609cfc29c9e97d 100644 (file)
@@ -104,6 +104,7 @@ static struct inode *fuse_alloc_inode(struct super_block *sb)
        fi->rdc.cached = false;
        fi->rdc.size = 0;
        fi->rdc.pos = 0;
+       fi->rdc.version = 0;
        mutex_init(&fi->mutex);
        fi->forget = fuse_alloc_forget();
        if (!fi->forget) {
index 5bdc0b945d72663cc6964e6b430be6412f278a73..18318cc31c054e235fa759d1b3986421f2efc7dd 100644 (file)
@@ -36,6 +36,7 @@ static void fuse_add_dirent_to_cache(struct file *file,
        pgoff_t index;
        struct page *page;
        loff_t size;
+       u64 version;
        unsigned int offset;
        void *addr;
 
@@ -48,6 +49,7 @@ static void fuse_add_dirent_to_cache(struct file *file,
                spin_unlock(&fi->rdc.lock);
                return;
        }
+       version = fi->rdc.version;
        size = fi->rdc.size;
        offset = size & ~PAGE_MASK;
        index = size >> PAGE_SHIFT;
@@ -69,7 +71,8 @@ static void fuse_add_dirent_to_cache(struct file *file,
 
        spin_lock(&fi->rdc.lock);
        /* Raced with another readdir */
-       if (fi->rdc.size != size || WARN_ON(fi->rdc.pos != pos))
+       if (fi->rdc.version != version || fi->rdc.size != size ||
+           WARN_ON(fi->rdc.pos != pos))
                goto unlock;
 
        addr = kmap_atomic(page);
@@ -396,6 +399,14 @@ static enum fuse_parse_result fuse_parse_cache(struct fuse_file *ff,
        return res;
 }
 
+static void fuse_rdc_reset(struct fuse_inode *fi)
+{
+       fi->rdc.cached = false;
+       fi->rdc.version++;
+       fi->rdc.size = 0;
+       fi->rdc.pos = 0;
+}
+
 #define UNCACHED 1
 
 static int fuse_readdir_cached(struct file *file, struct dir_context *ctx)
@@ -421,6 +432,21 @@ static int fuse_readdir_cached(struct file *file, struct dir_context *ctx)
                spin_unlock(&fi->rdc.lock);
                return UNCACHED;
        }
+       /*
+        * If cache version changed since the last getdents() call, then reset
+        * the cache stream.
+        */
+       if (ff->readdir.version != fi->rdc.version) {
+               ff->readdir.pos = 0;
+               ff->readdir.cache_off = 0;
+       }
+       /*
+        * If at the beginning of the cache, than reset version to
+        * current.
+        */
+       if (ff->readdir.pos == 0)
+               ff->readdir.version = fi->rdc.version;
+
        WARN_ON(fi->rdc.size < ff->readdir.cache_off);
 
        index = ff->readdir.cache_off >> PAGE_SHIFT;
@@ -437,13 +463,30 @@ static int fuse_readdir_cached(struct file *file, struct dir_context *ctx)
 
        page = find_get_page_flags(file->f_mapping, index,
                                   FGP_ACCESSED | FGP_LOCK);
+       spin_lock(&fi->rdc.lock);
        if (!page) {
                /*
                 * Uh-oh: page gone missing, cache is useless
                 */
+               if (fi->rdc.version == ff->readdir.version)
+                       fuse_rdc_reset(fi);
+               spin_unlock(&fi->rdc.lock);
                return UNCACHED;
        }
 
+       /* Make sure it's still the same version after getting the page. */
+       if (ff->readdir.version != fi->rdc.version) {
+               spin_unlock(&fi->rdc.lock);
+               unlock_page(page);
+               put_page(page);
+               goto retry;
+       }
+       spin_unlock(&fi->rdc.lock);
+
+       /*
+        * Contents of the page are now protected against changing by holding
+        * the page lock.
+        */
        addr = kmap(page);
        res = fuse_parse_cache(ff, addr, size, ctx);
        kunmap(page);