aboutsummaryrefslogtreecommitdiffstats
path: root/fs/fuse
diff options
context:
space:
mode:
authorMiklos Szeredi <mszeredi@redhat.com>2018-10-01 04:07:04 -0400
committerMiklos Szeredi <mszeredi@redhat.com>2018-10-01 04:07:04 -0400
commit69e34551152a286f827d54dcb5700da6aeaac1fb (patch)
treed18a844cec3ea412706240835f80a82bd41b2fb8 /fs/fuse
parent18172b10b674a7cd5340b2dd70202ce6622400bd (diff)
fuse: allow caching readdir
This patch just adds the cache filling functions, which are invoked if FOPEN_CACHE_DIR flag is set in the OPENDIR reply. Cache reading and cache invalidation are added by subsequent patches. The directory cache uses the page cache. Directory entries are packed into a page in the same format as in the READDIR reply. A page only contains whole entries, the space at the end of the page is cleared. The page is locked while being modified. Multiple parallel readdirs on the same directory can fill the cache; the only constraint is that continuity must be maintained (d_off of last entry points to position of current entry). Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
Diffstat (limited to 'fs/fuse')
-rw-r--r--fs/fuse/fuse_i.h15
-rw-r--r--fs/fuse/inode.c4
-rw-r--r--fs/fuse/readdir.c91
3 files changed, 109 insertions, 1 deletions
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index dfe10c2df6a9..d2fa7588533e 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -103,6 +103,21 @@ struct fuse_inode {
103 /** List of writepage requestst (pending or sent) */ 103 /** List of writepage requestst (pending or sent) */
104 struct list_head writepages; 104 struct list_head writepages;
105 105
106 /* readdir cache */
107 struct {
108 /* true if fully cached */
109 bool cached;
110
111 /* size of cache */
112 loff_t size;
113
114 /* position at end of cache (position of next entry) */
115 loff_t pos;
116
117 /* protects above fields */
118 spinlock_t lock;
119 } rdc;
120
106 /** Miscellaneous bits describing inode state */ 121 /** Miscellaneous bits describing inode state */
107 unsigned long state; 122 unsigned long state;
108 123
diff --git a/fs/fuse/inode.c b/fs/fuse/inode.c
index 9383b47b3d9b..892efe6351eb 100644
--- a/fs/fuse/inode.c
+++ b/fs/fuse/inode.c
@@ -100,6 +100,10 @@ static struct inode *fuse_alloc_inode(struct super_block *sb)
100 INIT_LIST_HEAD(&fi->queued_writes); 100 INIT_LIST_HEAD(&fi->queued_writes);
101 INIT_LIST_HEAD(&fi->writepages); 101 INIT_LIST_HEAD(&fi->writepages);
102 init_waitqueue_head(&fi->page_waitq); 102 init_waitqueue_head(&fi->page_waitq);
103 spin_lock_init(&fi->rdc.lock);
104 fi->rdc.cached = false;
105 fi->rdc.size = 0;
106 fi->rdc.pos = 0;
103 mutex_init(&fi->mutex); 107 mutex_init(&fi->mutex);
104 fi->forget = fuse_alloc_forget(); 108 fi->forget = fuse_alloc_forget();
105 if (!fi->forget) { 109 if (!fi->forget) {
diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c
index 65336c93c1f4..6c5ada164f7e 100644
--- a/fs/fuse/readdir.c
+++ b/fs/fuse/readdir.c
@@ -9,6 +9,8 @@
9 9
10#include "fuse_i.h" 10#include "fuse_i.h"
11#include <linux/posix_acl.h> 11#include <linux/posix_acl.h>
12#include <linux/pagemap.h>
13#include <linux/highmem.h>
12 14
13static bool fuse_use_readdirplus(struct inode *dir, struct dir_context *ctx) 15static bool fuse_use_readdirplus(struct inode *dir, struct dir_context *ctx)
14{ 16{
@@ -26,9 +28,91 @@ static bool fuse_use_readdirplus(struct inode *dir, struct dir_context *ctx)
26 return false; 28 return false;
27} 29}
28 30
31static void fuse_add_dirent_to_cache(struct file *file,
32 struct fuse_dirent *dirent, loff_t pos)
33{
34 struct fuse_inode *fi = get_fuse_inode(file_inode(file));
35 size_t reclen = FUSE_DIRENT_SIZE(dirent);
36 pgoff_t index;
37 struct page *page;
38 loff_t size;
39 unsigned int offset;
40 void *addr;
41
42 spin_lock(&fi->rdc.lock);
43 /*
44 * Is cache already completed? Or this entry does not go at the end of
45 * cache?
46 */
47 if (fi->rdc.cached || pos != fi->rdc.pos) {
48 spin_unlock(&fi->rdc.lock);
49 return;
50 }
51 size = fi->rdc.size;
52 offset = size & ~PAGE_MASK;
53 index = size >> PAGE_SHIFT;
54 /* Dirent doesn't fit in current page? Jump to next page. */
55 if (offset + reclen > PAGE_SIZE) {
56 index++;
57 offset = 0;
58 }
59 spin_unlock(&fi->rdc.lock);
60
61 if (offset) {
62 page = find_lock_page(file->f_mapping, index);
63 } else {
64 page = find_or_create_page(file->f_mapping, index,
65 mapping_gfp_mask(file->f_mapping));
66 }
67 if (!page)
68 return;
69
70 spin_lock(&fi->rdc.lock);
71 /* Raced with another readdir */
72 if (fi->rdc.size != size || WARN_ON(fi->rdc.pos != pos))
73 goto unlock;
74
75 addr = kmap_atomic(page);
76 if (!offset)
77 clear_page(addr);
78 memcpy(addr + offset, dirent, reclen);
79 kunmap_atomic(addr);
80 fi->rdc.size = (index << PAGE_SHIFT) + offset + reclen;
81 fi->rdc.pos = dirent->off;
82unlock:
83 spin_unlock(&fi->rdc.lock);
84 unlock_page(page);
85 put_page(page);
86}
87
88static void fuse_readdir_cache_end(struct file *file, loff_t pos)
89{
90 struct fuse_inode *fi = get_fuse_inode(file_inode(file));
91 loff_t end;
92
93 spin_lock(&fi->rdc.lock);
94 /* does cache end position match current position? */
95 if (fi->rdc.pos != pos) {
96 spin_unlock(&fi->rdc.lock);
97 return;
98 }
99
100 fi->rdc.cached = true;
101 end = ALIGN(fi->rdc.size, PAGE_SIZE);
102 spin_unlock(&fi->rdc.lock);
103
104 /* truncate unused tail of cache */
105 truncate_inode_pages(file->f_mapping, end);
106}
107
29static bool fuse_emit(struct file *file, struct dir_context *ctx, 108static bool fuse_emit(struct file *file, struct dir_context *ctx,
30 struct fuse_dirent *dirent) 109 struct fuse_dirent *dirent)
31{ 110{
111 struct fuse_file *ff = file->private_data;
112
113 if (ff->open_flags & FOPEN_CACHE_DIR)
114 fuse_add_dirent_to_cache(file, dirent, ctx->pos);
115
32 return dir_emit(ctx, dirent->name, dirent->namelen, dirent->ino, 116 return dir_emit(ctx, dirent->name, dirent->namelen, dirent->ino,
33 dirent->type); 117 dirent->type);
34} 118}
@@ -249,7 +333,12 @@ int fuse_readdir(struct file *file, struct dir_context *ctx)
249 err = req->out.h.error; 333 err = req->out.h.error;
250 fuse_put_request(fc, req); 334 fuse_put_request(fc, req);
251 if (!err) { 335 if (!err) {
252 if (plus) { 336 if (!nbytes) {
337 struct fuse_file *ff = file->private_data;
338
339 if (ff->open_flags & FOPEN_CACHE_DIR)
340 fuse_readdir_cache_end(file, ctx->pos);
341 } else if (plus) {
253 err = parse_dirplusfile(page_address(page), nbytes, 342 err = parse_dirplusfile(page_address(page), nbytes,
254 file, ctx, attr_version); 343 file, ctx, attr_version);
255 } else { 344 } else {