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

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>

+161 -4
+2
fs/fuse/file.c
··· 59 59 } 60 60 61 61 INIT_LIST_HEAD(&ff->write_entry); 62 + mutex_init(&ff->readdir.lock); 62 63 refcount_set(&ff->count, 1); 63 64 RB_CLEAR_NODE(&ff->polled_node); 64 65 init_waitqueue_head(&ff->poll_wait); ··· 74 73 void fuse_file_free(struct fuse_file *ff) 75 74 { 76 75 fuse_request_free(ff->reserved_req); 76 + mutex_destroy(&ff->readdir.lock); 77 77 kfree(ff); 78 78 } 79 79
+15
fs/fuse/fuse_i.h
··· 163 163 /** Entry on inode's write_files list */ 164 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 181 /** RB node to be linked on fuse_conn->polled_files */ 167 182 struct rb_node polled_node; 168 183
+144 -4
fs/fuse/readdir.c
··· 289 289 return 0; 290 290 } 291 291 292 - int fuse_readdir(struct file *file, struct dir_context *ctx) 292 + static int fuse_readdir_uncached(struct file *file, struct dir_context *ctx) 293 293 { 294 294 int plus, err; 295 295 size_t nbytes; ··· 299 299 struct fuse_req *req; 300 300 u64 attr_version = 0; 301 301 bool locked; 302 - 303 - if (is_bad_inode(inode)) 304 - return -EIO; 305 302 306 303 req = fuse_get_req(fc, 1); 307 304 if (IS_ERR(req)) ··· 346 349 347 350 __free_page(page); 348 351 fuse_invalidate_atime(inode); 352 + return err; 353 + } 354 + 355 + enum fuse_parse_result { 356 + FOUND_ERR = -1, 357 + FOUND_NONE = 0, 358 + FOUND_SOME, 359 + FOUND_ALL, 360 + }; 361 + 362 + static enum fuse_parse_result fuse_parse_cache(struct fuse_file *ff, 363 + void *addr, unsigned int size, 364 + struct dir_context *ctx) 365 + { 366 + unsigned int offset = ff->readdir.cache_off & ~PAGE_MASK; 367 + enum fuse_parse_result res = FOUND_NONE; 368 + 369 + WARN_ON(offset >= size); 370 + 371 + for (;;) { 372 + struct fuse_dirent *dirent = addr + offset; 373 + unsigned int nbytes = size - offset; 374 + size_t reclen = FUSE_DIRENT_SIZE(dirent); 375 + 376 + if (nbytes < FUSE_NAME_OFFSET || !dirent->namelen) 377 + break; 378 + 379 + if (WARN_ON(dirent->namelen > FUSE_NAME_MAX)) 380 + return FOUND_ERR; 381 + if (WARN_ON(reclen > nbytes)) 382 + return FOUND_ERR; 383 + if (WARN_ON(memchr(dirent->name, '/', dirent->namelen) != NULL)) 384 + return FOUND_ERR; 385 + 386 + if (ff->readdir.pos == ctx->pos) { 387 + res = FOUND_SOME; 388 + if (!dir_emit(ctx, dirent->name, dirent->namelen, 389 + dirent->ino, dirent->type)) 390 + return FOUND_ALL; 391 + ctx->pos = dirent->off; 392 + } 393 + ff->readdir.pos = dirent->off; 394 + ff->readdir.cache_off += reclen; 395 + 396 + offset += reclen; 397 + } 398 + 399 + return res; 400 + } 401 + 402 + #define UNCACHED 1 403 + 404 + static int fuse_readdir_cached(struct file *file, struct dir_context *ctx) 405 + { 406 + struct fuse_file *ff = file->private_data; 407 + struct inode *inode = file_inode(file); 408 + struct fuse_inode *fi = get_fuse_inode(inode); 409 + enum fuse_parse_result res; 410 + pgoff_t index; 411 + unsigned int size; 412 + struct page *page; 413 + void *addr; 414 + 415 + /* Seeked? If so, reset the cache stream */ 416 + if (ff->readdir.pos != ctx->pos) { 417 + ff->readdir.pos = 0; 418 + ff->readdir.cache_off = 0; 419 + } 420 + 421 + retry: 422 + spin_lock(&fi->rdc.lock); 423 + if (!fi->rdc.cached) { 424 + spin_unlock(&fi->rdc.lock); 425 + return UNCACHED; 426 + } 427 + WARN_ON(fi->rdc.size < ff->readdir.cache_off); 428 + 429 + index = ff->readdir.cache_off >> PAGE_SHIFT; 430 + 431 + if (index == (fi->rdc.size >> PAGE_SHIFT)) 432 + size = fi->rdc.size & ~PAGE_MASK; 433 + else 434 + size = PAGE_SIZE; 435 + spin_unlock(&fi->rdc.lock); 436 + 437 + /* EOF? */ 438 + if ((ff->readdir.cache_off & ~PAGE_MASK) == size) 439 + return 0; 440 + 441 + page = find_get_page_flags(file->f_mapping, index, 442 + FGP_ACCESSED | FGP_LOCK); 443 + if (!page) { 444 + /* 445 + * Uh-oh: page gone missing, cache is useless 446 + */ 447 + return UNCACHED; 448 + } 449 + 450 + addr = kmap(page); 451 + res = fuse_parse_cache(ff, addr, size, ctx); 452 + kunmap(page); 453 + unlock_page(page); 454 + put_page(page); 455 + 456 + if (res == FOUND_ERR) 457 + return -EIO; 458 + 459 + if (res == FOUND_ALL) 460 + return 0; 461 + 462 + if (size == PAGE_SIZE) { 463 + /* We hit end of page: skip to next page. */ 464 + ff->readdir.cache_off = ALIGN(ff->readdir.cache_off, PAGE_SIZE); 465 + goto retry; 466 + } 467 + 468 + /* 469 + * End of cache reached. If found position, then we are done, otherwise 470 + * need to fall back to uncached, since the position we were looking for 471 + * wasn't in the cache. 472 + */ 473 + return res == FOUND_SOME ? 0 : UNCACHED; 474 + } 475 + 476 + int fuse_readdir(struct file *file, struct dir_context *ctx) 477 + { 478 + struct fuse_file *ff = file->private_data; 479 + struct inode *inode = file_inode(file); 480 + int err; 481 + 482 + if (is_bad_inode(inode)) 483 + return -EIO; 484 + 485 + mutex_lock(&ff->readdir.lock); 486 + 487 + err = UNCACHED; 488 + if (ff->open_flags & FOPEN_CACHE_DIR) 489 + err = fuse_readdir_cached(file, ctx); 490 + if (err == UNCACHED) 491 + err = fuse_readdir_uncached(file, ctx); 492 + 493 + mutex_unlock(&ff->readdir.lock); 494 + 349 495 return err; 350 496 }