summaryrefslogtreecommitdiff
path: root/fs/fuse/readdir.c
diff options
context:
space:
mode:
authorMiklos Szeredi <mszeredi@redhat.com>2018-10-01 10:07:04 +0200
committerMiklos Szeredi <mszeredi@redhat.com>2018-10-01 10:07:04 +0200
commit3494927e090bf511e54eecaf33a8e56e5c0463db (patch)
tree0015407dc519f878d5dfc5233cfb1dc50e7ff7bd /fs/fuse/readdir.c
parent5d7bc7e8680c7ca4c8a4f139ce2a54ccb8131ef0 (diff)
fuse: add readdir cache version
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>
Diffstat (limited to 'fs/fuse/readdir.c')
-rw-r--r--fs/fuse/readdir.c45
1 files changed, 44 insertions, 1 deletions
diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c
index 5bdc0b945d72..18318cc31c05 100644
--- a/fs/fuse/readdir.c
+++ b/fs/fuse/readdir.c
@@ -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 @@ retry:
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 @@ retry:
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);