Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

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>

+109 -1
+15
fs/fuse/fuse_i.h
··· 103 103 /** List of writepage requestst (pending or sent) */ 104 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 121 /** Miscellaneous bits describing inode state */ 107 122 unsigned long state; 108 123
+4
fs/fuse/inode.c
··· 100 100 INIT_LIST_HEAD(&fi->queued_writes); 101 101 INIT_LIST_HEAD(&fi->writepages); 102 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 107 mutex_init(&fi->mutex); 104 108 fi->forget = fuse_alloc_forget(); 105 109 if (!fi->forget) {
+90 -1
fs/fuse/readdir.c
··· 9 9 10 10 #include "fuse_i.h" 11 11 #include <linux/posix_acl.h> 12 + #include <linux/pagemap.h> 13 + #include <linux/highmem.h> 12 14 13 15 static bool fuse_use_readdirplus(struct inode *dir, struct dir_context *ctx) 14 16 { ··· 28 26 return false; 29 27 } 30 28 29 + static void fuse_add_dirent_to_cache(struct file *file, 30 + struct fuse_dirent *dirent, loff_t pos) 31 + { 32 + struct fuse_inode *fi = get_fuse_inode(file_inode(file)); 33 + size_t reclen = FUSE_DIRENT_SIZE(dirent); 34 + pgoff_t index; 35 + struct page *page; 36 + loff_t size; 37 + unsigned int offset; 38 + void *addr; 39 + 40 + spin_lock(&fi->rdc.lock); 41 + /* 42 + * Is cache already completed? Or this entry does not go at the end of 43 + * cache? 44 + */ 45 + if (fi->rdc.cached || pos != fi->rdc.pos) { 46 + spin_unlock(&fi->rdc.lock); 47 + return; 48 + } 49 + size = fi->rdc.size; 50 + offset = size & ~PAGE_MASK; 51 + index = size >> PAGE_SHIFT; 52 + /* Dirent doesn't fit in current page? Jump to next page. */ 53 + if (offset + reclen > PAGE_SIZE) { 54 + index++; 55 + offset = 0; 56 + } 57 + spin_unlock(&fi->rdc.lock); 58 + 59 + if (offset) { 60 + page = find_lock_page(file->f_mapping, index); 61 + } else { 62 + page = find_or_create_page(file->f_mapping, index, 63 + mapping_gfp_mask(file->f_mapping)); 64 + } 65 + if (!page) 66 + return; 67 + 68 + spin_lock(&fi->rdc.lock); 69 + /* Raced with another readdir */ 70 + if (fi->rdc.size != size || WARN_ON(fi->rdc.pos != pos)) 71 + goto unlock; 72 + 73 + addr = kmap_atomic(page); 74 + if (!offset) 75 + clear_page(addr); 76 + memcpy(addr + offset, dirent, reclen); 77 + kunmap_atomic(addr); 78 + fi->rdc.size = (index << PAGE_SHIFT) + offset + reclen; 79 + fi->rdc.pos = dirent->off; 80 + unlock: 81 + spin_unlock(&fi->rdc.lock); 82 + unlock_page(page); 83 + put_page(page); 84 + } 85 + 86 + static void fuse_readdir_cache_end(struct file *file, loff_t pos) 87 + { 88 + struct fuse_inode *fi = get_fuse_inode(file_inode(file)); 89 + loff_t end; 90 + 91 + spin_lock(&fi->rdc.lock); 92 + /* does cache end position match current position? */ 93 + if (fi->rdc.pos != pos) { 94 + spin_unlock(&fi->rdc.lock); 95 + return; 96 + } 97 + 98 + fi->rdc.cached = true; 99 + end = ALIGN(fi->rdc.size, PAGE_SIZE); 100 + spin_unlock(&fi->rdc.lock); 101 + 102 + /* truncate unused tail of cache */ 103 + truncate_inode_pages(file->f_mapping, end); 104 + } 105 + 31 106 static bool fuse_emit(struct file *file, struct dir_context *ctx, 32 107 struct fuse_dirent *dirent) 33 108 { 109 + struct fuse_file *ff = file->private_data; 110 + 111 + if (ff->open_flags & FOPEN_CACHE_DIR) 112 + fuse_add_dirent_to_cache(file, dirent, ctx->pos); 113 + 34 114 return dir_emit(ctx, dirent->name, dirent->namelen, dirent->ino, 35 115 dirent->type); 36 116 } ··· 333 249 err = req->out.h.error; 334 250 fuse_put_request(fc, req); 335 251 if (!err) { 336 - if (plus) { 252 + if (!nbytes) { 253 + struct fuse_file *ff = file->private_data; 254 + 255 + if (ff->open_flags & FOPEN_CACHE_DIR) 256 + fuse_readdir_cache_end(file, ctx->pos); 257 + } else if (plus) { 337 258 err = parse_dirplusfile(page_address(page), nbytes, 338 259 file, ctx, attr_version); 339 260 } else {