aboutsummaryrefslogtreecommitdiffstats
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
commit5d7bc7e8680c7ca4c8a4f139ce2a54ccb8131ef0 (patch)
treee1d3c9478d7fb32a90e20ec02e55b0ff40a647b8
parent69e34551152a286f827d54dcb5700da6aeaac1fb (diff)
fuse: allow using readdir cache
The cache is only used if it's completed, not while it's still being filled; this constraint could be lifted later, if it turns out to be useful. Introduce state in struct fuse_file that indicates the position within the cache. After a seek, reset the position to the beginning of the cache and search the cache for the current position. If the current position is not found in the cache, then fall back to uncached readdir. It can also happen that page(s) disappear from the cache, in which case we must also fall back to uncached readdir. Signed-off-by: Miklos Szeredi <mszeredi@redhat.com>
-rw-r--r--fs/fuse/file.c2
-rw-r--r--fs/fuse/fuse_i.h15
-rw-r--r--fs/fuse/readdir.c148
3 files changed, 161 insertions, 4 deletions
diff --git a/fs/fuse/file.c b/fs/fuse/file.c
index d15c14912e72..e10c0443c56f 100644
--- a/fs/fuse/file.c
+++ b/fs/fuse/file.c
@@ -59,6 +59,7 @@ struct fuse_file *fuse_file_alloc(struct fuse_conn *fc)
59 } 59 }
60 60
61 INIT_LIST_HEAD(&ff->write_entry); 61 INIT_LIST_HEAD(&ff->write_entry);
62 mutex_init(&ff->readdir.lock);
62 refcount_set(&ff->count, 1); 63 refcount_set(&ff->count, 1);
63 RB_CLEAR_NODE(&ff->polled_node); 64 RB_CLEAR_NODE(&ff->polled_node);
64 init_waitqueue_head(&ff->poll_wait); 65 init_waitqueue_head(&ff->poll_wait);
@@ -73,6 +74,7 @@ struct fuse_file *fuse_file_alloc(struct fuse_conn *fc)
73void fuse_file_free(struct fuse_file *ff) 74void fuse_file_free(struct fuse_file *ff)
74{ 75{
75 fuse_request_free(ff->reserved_req); 76 fuse_request_free(ff->reserved_req);
77 mutex_destroy(&ff->readdir.lock);
76 kfree(ff); 78 kfree(ff);
77} 79}
78 80
diff --git a/fs/fuse/fuse_i.h b/fs/fuse/fuse_i.h
index d2fa7588533e..49e42635e3ac 100644
--- a/fs/fuse/fuse_i.h
+++ b/fs/fuse/fuse_i.h
@@ -163,6 +163,21 @@ struct fuse_file {
163 /** Entry on inode's write_files list */ 163 /** Entry on inode's write_files list */
164 struct list_head write_entry; 164 struct list_head write_entry;
165 165
166 /* Readdir related */
167 struct {
168 /*
169 * Protects below fields against (crazy) parallel readdir on
170 * same open file. Uncontended in the normal case.
171 */
172 struct mutex lock;
173
174 /* Dir stream position */
175 loff_t pos;
176
177 /* Offset in cache */
178 loff_t cache_off;
179 } readdir;
180
166 /** RB node to be linked on fuse_conn->polled_files */ 181 /** RB node to be linked on fuse_conn->polled_files */
167 struct rb_node polled_node; 182 struct rb_node polled_node;
168 183
diff --git a/fs/fuse/readdir.c b/fs/fuse/readdir.c
index 6c5ada164f7e..5bdc0b945d72 100644
--- a/fs/fuse/readdir.c
+++ b/fs/fuse/readdir.c
@@ -289,7 +289,7 @@ static int parse_dirplusfile(char *buf, size_t nbytes, struct file *file,
289 return 0; 289 return 0;
290} 290}
291 291
292int fuse_readdir(struct file *file, struct dir_context *ctx) 292static int fuse_readdir_uncached(struct file *file, struct dir_context *ctx)
293{ 293{
294 int plus, err; 294 int plus, err;
295 size_t nbytes; 295 size_t nbytes;
@@ -300,9 +300,6 @@ int fuse_readdir(struct file *file, struct dir_context *ctx)
300 u64 attr_version = 0; 300 u64 attr_version = 0;
301 bool locked; 301 bool locked;
302 302
303 if (is_bad_inode(inode))
304 return -EIO;
305
306 req = fuse_get_req(fc, 1); 303 req = fuse_get_req(fc, 1);
307 if (IS_ERR(req)) 304 if (IS_ERR(req))
308 return PTR_ERR(req); 305 return PTR_ERR(req);
@@ -351,3 +348,146 @@ int fuse_readdir(struct file *file, struct dir_context *ctx)
351 fuse_invalidate_atime(inode); 348 fuse_invalidate_atime(inode);
352 return err; 349 return err;
353} 350}
351
352enum fuse_parse_result {
353 FOUND_ERR = -1,
354 FOUND_NONE = 0,
355 FOUND_SOME,
356 FOUND_ALL,
357};
358
359static enum fuse_parse_result fuse_parse_cache(struct fuse_file *ff,
360 void *addr, unsigned int size,
361 struct dir_context *ctx)
362{
363 unsigned int offset = ff->readdir.cache_off & ~PAGE_MASK;
364 enum fuse_parse_result res = FOUND_NONE;
365
366 WARN_ON(offset >= size);
367
368 for (;;) {
369 struct fuse_dirent *dirent = addr + offset;
370 unsigned int nbytes = size - offset;
371 size_t reclen = FUSE_DIRENT_SIZE(dirent);
372
373 if (nbytes < FUSE_NAME_OFFSET || !dirent->namelen)
374 break;
375
376 if (WARN_ON(dirent->namelen > FUSE_NAME_MAX))
377 return FOUND_ERR;
378 if (WARN_ON(reclen > nbytes))
379 return FOUND_ERR;
380 if (WARN_ON(memchr(dirent->name, '/', dirent->namelen) != NULL))
381 return FOUND_ERR;
382
383 if (ff->readdir.pos == ctx->pos) {
384 res = FOUND_SOME;
385 if (!dir_emit(ctx, dirent->name, dirent->namelen,
386 dirent->ino, dirent->type))
387 return FOUND_ALL;
388 ctx->pos = dirent->off;
389 }
390 ff->readdir.pos = dirent->off;
391 ff->readdir.cache_off += reclen;
392
393 offset += reclen;
394 }
395
396 return res;
397}
398
399#define UNCACHED 1
400
401static int fuse_readdir_cached(struct file *file, struct dir_context *ctx)
402{
403 struct fuse_file *ff = file->private_data;
404 struct inode *inode = file_inode(file);
405 struct fuse_inode *fi = get_fuse_inode(inode);
406 enum fuse_parse_result res;
407 pgoff_t index;
408 unsigned int size;
409 struct page *page;
410 void *addr;
411
412 /* Seeked? If so, reset the cache stream */
413 if (ff->readdir.pos != ctx->pos) {
414 ff->readdir.pos = 0;
415 ff->readdir.cache_off = 0;
416 }
417
418retry:
419 spin_lock(&fi->rdc.lock);
420 if (!fi->rdc.cached) {
421 spin_unlock(&fi->rdc.lock);
422 return UNCACHED;
423 }
424 WARN_ON(fi->rdc.size < ff->readdir.cache_off);
425
426 index = ff->readdir.cache_off >> PAGE_SHIFT;
427
428 if (index == (fi->rdc.size >> PAGE_SHIFT))
429 size = fi->rdc.size & ~PAGE_MASK;
430 else
431 size = PAGE_SIZE;
432 spin_unlock(&fi->rdc.lock);
433
434 /* EOF? */
435 if ((ff->readdir.cache_off & ~PAGE_MASK) == size)
436 return 0;
437
438 page = find_get_page_flags(file->f_mapping, index,
439 FGP_ACCESSED | FGP_LOCK);
440 if (!page) {
441 /*
442 * Uh-oh: page gone missing, cache is useless
443 */
444 return UNCACHED;
445 }
446
447 addr = kmap(page);
448 res = fuse_parse_cache(ff, addr, size, ctx);
449 kunmap(page);
450 unlock_page(page);
451 put_page(page);
452
453 if (res == FOUND_ERR)
454 return -EIO;
455
456 if (res == FOUND_ALL)
457 return 0;
458
459 if (size == PAGE_SIZE) {
460 /* We hit end of page: skip to next page. */
461 ff->readdir.cache_off = ALIGN(ff->readdir.cache_off, PAGE_SIZE);
462 goto retry;
463 }
464
465 /*
466 * End of cache reached. If found position, then we are done, otherwise
467 * need to fall back to uncached, since the position we were looking for
468 * wasn't in the cache.
469 */
470 return res == FOUND_SOME ? 0 : UNCACHED;
471}
472
473int fuse_readdir(struct file *file, struct dir_context *ctx)
474{
475 struct fuse_file *ff = file->private_data;
476 struct inode *inode = file_inode(file);
477 int err;
478
479 if (is_bad_inode(inode))
480 return -EIO;
481
482 mutex_lock(&ff->readdir.lock);
483
484 err = UNCACHED;
485 if (ff->open_flags & FOPEN_CACHE_DIR)
486 err = fuse_readdir_cached(file, ctx);
487 if (err == UNCACHED)
488 err = fuse_readdir_uncached(file, ctx);
489
490 mutex_unlock(&ff->readdir.lock);
491
492 return err;
493}