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

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>

+52 -1
+7
fs/fuse/fuse_i.h
··· 114 114 /* position at end of cache (position of next entry) */ 115 115 loff_t pos; 116 116 117 + /* version of the cache */ 118 + u64 version; 119 + 117 120 /* protects above fields */ 118 121 spinlock_t lock; 119 122 } rdc; ··· 179 176 180 177 /* Offset in cache */ 181 178 loff_t cache_off; 179 + 180 + /* Version of cache we are reading */ 181 + u64 version; 182 + 182 183 } readdir; 183 184 184 185 /** RB node to be linked on fuse_conn->polled_files */
+1
fs/fuse/inode.c
··· 104 104 fi->rdc.cached = false; 105 105 fi->rdc.size = 0; 106 106 fi->rdc.pos = 0; 107 + fi->rdc.version = 0; 107 108 mutex_init(&fi->mutex); 108 109 fi->forget = fuse_alloc_forget(); 109 110 if (!fi->forget) {
+44 -1
fs/fuse/readdir.c
··· 36 36 pgoff_t index; 37 37 struct page *page; 38 38 loff_t size; 39 + u64 version; 39 40 unsigned int offset; 40 41 void *addr; 41 42 ··· 49 48 spin_unlock(&fi->rdc.lock); 50 49 return; 51 50 } 51 + version = fi->rdc.version; 52 52 size = fi->rdc.size; 53 53 offset = size & ~PAGE_MASK; 54 54 index = size >> PAGE_SHIFT; ··· 71 69 72 70 spin_lock(&fi->rdc.lock); 73 71 /* Raced with another readdir */ 74 - if (fi->rdc.size != size || WARN_ON(fi->rdc.pos != pos)) 72 + if (fi->rdc.version != version || fi->rdc.size != size || 73 + WARN_ON(fi->rdc.pos != pos)) 75 74 goto unlock; 76 75 77 76 addr = kmap_atomic(page); ··· 399 396 return res; 400 397 } 401 398 399 + static void fuse_rdc_reset(struct fuse_inode *fi) 400 + { 401 + fi->rdc.cached = false; 402 + fi->rdc.version++; 403 + fi->rdc.size = 0; 404 + fi->rdc.pos = 0; 405 + } 406 + 402 407 #define UNCACHED 1 403 408 404 409 static int fuse_readdir_cached(struct file *file, struct dir_context *ctx) ··· 432 421 spin_unlock(&fi->rdc.lock); 433 422 return UNCACHED; 434 423 } 424 + /* 425 + * If cache version changed since the last getdents() call, then reset 426 + * the cache stream. 427 + */ 428 + if (ff->readdir.version != fi->rdc.version) { 429 + ff->readdir.pos = 0; 430 + ff->readdir.cache_off = 0; 431 + } 432 + /* 433 + * If at the beginning of the cache, than reset version to 434 + * current. 435 + */ 436 + if (ff->readdir.pos == 0) 437 + ff->readdir.version = fi->rdc.version; 438 + 435 439 WARN_ON(fi->rdc.size < ff->readdir.cache_off); 436 440 437 441 index = ff->readdir.cache_off >> PAGE_SHIFT; ··· 463 437 464 438 page = find_get_page_flags(file->f_mapping, index, 465 439 FGP_ACCESSED | FGP_LOCK); 440 + spin_lock(&fi->rdc.lock); 466 441 if (!page) { 467 442 /* 468 443 * Uh-oh: page gone missing, cache is useless 469 444 */ 445 + if (fi->rdc.version == ff->readdir.version) 446 + fuse_rdc_reset(fi); 447 + spin_unlock(&fi->rdc.lock); 470 448 return UNCACHED; 471 449 } 472 450 451 + /* Make sure it's still the same version after getting the page. */ 452 + if (ff->readdir.version != fi->rdc.version) { 453 + spin_unlock(&fi->rdc.lock); 454 + unlock_page(page); 455 + put_page(page); 456 + goto retry; 457 + } 458 + spin_unlock(&fi->rdc.lock); 459 + 460 + /* 461 + * Contents of the page are now protected against changing by holding 462 + * the page lock. 463 + */ 473 464 addr = kmap(page); 474 465 res = fuse_parse_cache(ff, addr, size, ctx); 475 466 kunmap(page);