afs: Write local directory changes to the cache Save local changes to AFS directories into the cache so that we don't have to refetch the directory content from the server. Signed-off-by: David Howells <dhowells@redhat.com>
diff --git a/fs/afs/dir.c b/fs/afs/dir.c index f496e4c..dbd55d6 100644 --- a/fs/afs/dir.c +++ b/fs/afs/dir.c
@@ -43,6 +43,7 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry, static int afs_dir_releasepage(struct page *page, gfp_t gfp_flags); static void afs_dir_invalidatepage(struct page *page, unsigned int offset, unsigned int length); +static void afs_dir_write_to_cache(struct afs_vnode *dvnode); static int afs_dir_set_page_dirty(struct page *page) { @@ -1371,6 +1372,19 @@ static void afs_update_dentry_version(struct afs_fs_cursor *fc, } /* + * Finish usage of a directory cookie. + */ +static void afs_dir_unuse_cookie(struct afs_vnode *dvnode) +{ + loff_t i_size = i_size_read(&dvnode->vfs_inode); + struct afs_vnode_cache_aux aux = { + .data_version = dvnode->status.data_version, + }; + + fscache_unuse_cookie(afs_vnode_cache(dvnode), &aux, &i_size); +} + +/* * create a directory on an AFS filesystem */ static int afs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) @@ -1381,6 +1395,7 @@ static int afs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) struct afs_vnode *dvnode = AFS_FS_I(dir); struct key *key; afs_dataversion_t data_version; + bool update_cache = false; int ret; mode |= S_IFDIR; @@ -1399,6 +1414,8 @@ static int afs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) goto error_scb; } + fscache_use_cookie(afs_vnode_cache(dvnode), true); + ret = -ERESTARTSYS; if (afs_begin_vnode_operation(&fc, dvnode, key, true)) { data_version = dvnode->status.data_version + 1; @@ -1425,18 +1442,24 @@ static int afs_mkdir(struct inode *dir, struct dentry *dentry, umode_t mode) if (ret == 0) { down_write(&dvnode->validate_lock); if (test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags) && - dvnode->status.data_version == data_version) + dvnode->status.data_version == data_version) { afs_edit_dir_add(dvnode, &dentry->d_name, &iget_data.fid, afs_edit_dir_for_create); + update_cache = true; + } up_write(&dvnode->validate_lock); + if (update_cache) + afs_dir_write_to_cache(dvnode); } + afs_dir_unuse_cookie(dvnode); key_put(key); kfree(scb); _leave(" = 0"); return 0; error_key: + fscache_unuse_cookie(afs_vnode_cache(dvnode), NULL, NULL); key_put(key); error_scb: kfree(scb); @@ -1471,6 +1494,7 @@ static int afs_rmdir(struct inode *dir, struct dentry *dentry) struct afs_vnode *dvnode = AFS_FS_I(dir), *vnode = NULL; struct key *key; afs_dataversion_t data_version; + bool update_cache = false; int ret; _enter("{%llx:%llu},{%pd}", @@ -1486,6 +1510,8 @@ static int afs_rmdir(struct inode *dir, struct dentry *dentry) goto error; } + fscache_use_cookie(afs_vnode_cache(dvnode), true); + /* Try to make sure we have a callback promise on the victim. */ if (d_really_is_positive(dentry)) { vnode = AFS_FS_I(d_inode(dentry)); @@ -1517,16 +1543,21 @@ static int afs_rmdir(struct inode *dir, struct dentry *dentry) afs_dir_remove_subdir(dentry); down_write(&dvnode->validate_lock); if (test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags) && - dvnode->status.data_version == data_version) + dvnode->status.data_version == data_version) { afs_edit_dir_remove(dvnode, &dentry->d_name, afs_edit_dir_for_rmdir); + update_cache = true; + } up_write(&dvnode->validate_lock); + if (update_cache) + afs_dir_write_to_cache(dvnode); } } if (vnode) up_write(&vnode->rmdir_lock); error_key: + afs_dir_unuse_cookie(dvnode); key_put(key); error: kfree(scb); @@ -1588,6 +1619,7 @@ static int afs_unlink(struct inode *dir, struct dentry *dentry) struct afs_vnode *dvnode = AFS_FS_I(dir); struct afs_vnode *vnode = AFS_FS_I(d_inode(dentry)); struct key *key; + bool update_cache = false; bool need_rehash = false; int ret; @@ -1608,6 +1640,8 @@ static int afs_unlink(struct inode *dir, struct dentry *dentry) goto error_scb; } + fscache_use_cookie(afs_vnode_cache(dvnode), true); + /* Try to make sure we have a callback promise on the victim. */ ret = afs_validate(vnode, key); if (ret < 0) @@ -1662,10 +1696,14 @@ static int afs_unlink(struct inode *dir, struct dentry *dentry) if (ret == 0) { down_write(&dvnode->validate_lock); if (test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags) && - dvnode->status.data_version == data_version) + dvnode->status.data_version == data_version) { afs_edit_dir_remove(dvnode, &dentry->d_name, afs_edit_dir_for_unlink); + update_cache = true; + } up_write(&dvnode->validate_lock); + if (update_cache) + afs_dir_write_to_cache(dvnode); } } @@ -1673,6 +1711,7 @@ static int afs_unlink(struct inode *dir, struct dentry *dentry) d_rehash(dentry); error_key: + afs_dir_unuse_cookie(dvnode); key_put(key); error_scb: kfree(scb); @@ -1693,6 +1732,7 @@ static int afs_create(struct inode *dir, struct dentry *dentry, umode_t mode, struct afs_vnode *dvnode = AFS_FS_I(dir); struct key *key; afs_dataversion_t data_version; + bool update_cache = false; int ret; mode |= S_IFREG; @@ -1710,6 +1750,8 @@ static int afs_create(struct inode *dir, struct dentry *dentry, umode_t mode, goto error; } + fscache_use_cookie(afs_vnode_cache(dvnode), true); + ret = -ENOMEM; scb = kcalloc(2, sizeof(struct afs_status_cb), GFP_KERNEL); if (!scb) @@ -1740,12 +1782,17 @@ static int afs_create(struct inode *dir, struct dentry *dentry, umode_t mode, down_write(&dvnode->validate_lock); if (test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags) && - dvnode->status.data_version == data_version) + dvnode->status.data_version == data_version) { afs_edit_dir_add(dvnode, &dentry->d_name, &iget_data.fid, afs_edit_dir_for_create); + update_cache = true; + } up_write(&dvnode->validate_lock); + if (update_cache) + afs_dir_write_to_cache(dvnode); kfree(scb); + afs_dir_unuse_cookie(dvnode); key_put(key); _leave(" = 0"); return 0; @@ -1753,6 +1800,7 @@ static int afs_create(struct inode *dir, struct dentry *dentry, umode_t mode, error_scb: kfree(scb); error_key: + afs_dir_unuse_cookie(dvnode); key_put(key); error: d_drop(dentry); @@ -1772,6 +1820,7 @@ static int afs_link(struct dentry *from, struct inode *dir, struct afs_vnode *vnode = AFS_FS_I(d_inode(from)); struct key *key; afs_dataversion_t data_version; + bool update_cache = false; int ret; _enter("{%llx:%llu},{%llx:%llu},{%pd}", @@ -1794,6 +1843,8 @@ static int afs_link(struct dentry *from, struct inode *dir, goto error_scb; } + fscache_use_cookie(afs_vnode_cache(dvnode), true); + ret = -ERESTARTSYS; if (afs_begin_vnode_operation(&fc, dvnode, key, true)) { data_version = dvnode->status.data_version + 1; @@ -1828,17 +1879,23 @@ static int afs_link(struct dentry *from, struct inode *dir, down_write(&dvnode->validate_lock); if (test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags) && - dvnode->status.data_version == data_version) + dvnode->status.data_version == data_version) { afs_edit_dir_add(dvnode, &dentry->d_name, &vnode->fid, afs_edit_dir_for_link); + update_cache = true; + } up_write(&dvnode->validate_lock); + if (update_cache) + afs_dir_write_to_cache(dvnode); + afs_dir_unuse_cookie(dvnode); key_put(key); kfree(scb); _leave(" = 0"); return 0; error_key: + afs_dir_unuse_cookie(dvnode); key_put(key); error_scb: kfree(scb); @@ -1860,6 +1917,7 @@ static int afs_symlink(struct inode *dir, struct dentry *dentry, struct afs_vnode *dvnode = AFS_FS_I(dir); struct key *key; afs_dataversion_t data_version; + bool update_cache = false; int ret; _enter("{%llx:%llu},{%pd},%s", @@ -1885,6 +1943,8 @@ static int afs_symlink(struct inode *dir, struct dentry *dentry, goto error_scb; } + fscache_use_cookie(afs_vnode_cache(dvnode), true); + ret = -ERESTARTSYS; if (afs_begin_vnode_operation(&fc, dvnode, key, true)) { data_version = dvnode->status.data_version + 1; @@ -1910,17 +1970,23 @@ static int afs_symlink(struct inode *dir, struct dentry *dentry, down_write(&dvnode->validate_lock); if (test_bit(AFS_VNODE_DIR_VALID, &dvnode->flags) && - dvnode->status.data_version == data_version) + dvnode->status.data_version == data_version) { afs_edit_dir_add(dvnode, &dentry->d_name, &iget_data.fid, afs_edit_dir_for_symlink); + update_cache = true; + } up_write(&dvnode->validate_lock); + if (update_cache) + afs_dir_write_to_cache(dvnode); + afs_dir_unuse_cookie(dvnode); key_put(key); kfree(scb); _leave(" = 0"); return 0; error_key: + afs_dir_unuse_cookie(dvnode); key_put(key); error_scb: kfree(scb); @@ -1946,6 +2012,7 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry, afs_dataversion_t orig_data_version; afs_dataversion_t new_data_version; bool new_negative = d_is_negative(new_dentry); + bool update_cache = false; int ret; if (flags) @@ -1976,6 +2043,10 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry, goto error_scb; } + fscache_use_cookie(afs_vnode_cache(orig_dvnode), true); + if (new_dvnode != orig_dvnode) + fscache_use_cookie(afs_vnode_cache(new_dvnode), true); + /* For non-directories, check whether the target is busy and if so, * make a copy of the dentry and then do a silly-rename. If the * silly-rename succeeds, the copied dentry is hashed and becomes the @@ -2063,9 +2134,9 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry, orig_dvnode->status.data_version == orig_data_version) afs_edit_dir_remove(orig_dvnode, &old_dentry->d_name, afs_edit_dir_for_rename_0); + if (orig_dvnode != new_dvnode) { up_write(&orig_dvnode->validate_lock); - down_write(&new_dvnode->validate_lock); } if (test_bit(AFS_VNODE_DIR_VALID, &new_dvnode->flags) && @@ -2096,6 +2167,10 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry, afs_update_dentry_version(&fc, new_dentry, &scb[1]); d_move(old_dentry, new_dentry); up_write(&new_dvnode->validate_lock); + + if (new_dvnode != orig_dvnode) + afs_dir_write_to_cache(new_dvnode); + afs_dir_write_to_cache(orig_dvnode); goto error_tmp; } @@ -2107,6 +2182,9 @@ static int afs_rename(struct inode *old_dir, struct dentry *old_dentry, error_tmp: if (tmp) dput(tmp); + if (new_dvnode != orig_dvnode) + afs_dir_unuse_cookie(new_dvnode); + afs_dir_unuse_cookie(orig_dvnode); key_put(key); error_scb: kfree(scb); @@ -2158,3 +2236,69 @@ static void afs_dir_invalidatepage(struct page *page, unsigned int offset, ClearPagePrivate(page); } } + +static void afs_dir_write_to_cache_done(struct fscache_io_request *req) +{ + pgoff_t index = req->pos >> PAGE_SHIFT; + pgoff_t last = index + req->nr_pages - 1; + + _enter("%lx,%x,%llx", index, req->nr_pages, req->transferred); + + afs_clear_fscache_bits(req->mapping, index, last); +} + +static const struct fscache_io_request_ops afs_dir_write_req_ops = { + .get = afs_req_get, + .put = afs_req_put, +}; + +/* + * Write the locally modified dir back to the cache. + */ +static void afs_dir_write_to_cache(struct afs_vnode *dvnode) +{ + struct afs_read *req; + struct iov_iter iter; + unsigned int x; + pgoff_t nr_pages; + loff_t i_size, size; + struct fscache_extent extent = { .start = 0, }; + int ret; + + i_size = i_size_read(&dvnode->vfs_inode); + size = round_up(i_size, PAGE_SIZE); + nr_pages = size >> PAGE_SHIFT; + + extent.block_end = nr_pages; + extent.limit = nr_pages; + x = fscache_shape_extent(afs_vnode_cache(dvnode), &extent, i_size, true); + _enter("c=%08x,%x", afs_vnode_cache(dvnode)->debug_id, x); + if (!(x & FSCACHE_WRITE_TO_CACHE)) + goto abandon; + + fscache_invalidate(afs_vnode_cache(dvnode), i_size, FSCACHE_INVAL_LIGHT); + + size = round_up(size, extent.dio_block_size); + + req = afs_alloc_read(GFP_KERNEL); + if (!req) + goto abandon; + + fscache_init_io_request(&req->cache, afs_vnode_cache(dvnode), + &afs_dir_write_req_ops); + req->cache.pos = 0; + req->cache.len = size; + req->cache.nr_pages = nr_pages; + req->cache.mapping = dvnode->vfs_inode.i_mapping; + req->cache.io_done = afs_dir_write_to_cache_done; + + iov_iter_mapping(&iter, WRITE, req->cache.mapping, 0, size); + ret = fscache_write(&req->cache, &iter); + afs_put_read(req); + _leave(" = %d", ret); + return; + +abandon: + afs_clear_fscache_bits(dvnode->vfs_inode.i_mapping, 0, nr_pages); + _leave(" [ab]"); +}
diff --git a/fs/afs/internal.h b/fs/afs/internal.h index 1a2a4d7..36f859b 100644 --- a/fs/afs/internal.h +++ b/fs/afs/internal.h
@@ -1356,6 +1356,7 @@ extern int afs_fsync(struct file *, loff_t, loff_t, int); extern vm_fault_t afs_page_mkwrite(struct vm_fault *vmf); extern void afs_prune_wb_keys(struct afs_vnode *); extern int afs_launder_page(struct page *); +extern void afs_clear_fscache_bits(struct address_space *, pgoff_t, pgoff_t); /* * xattr.c
diff --git a/fs/afs/write.c b/fs/afs/write.c index 2102a0b..82d67dc 100644 --- a/fs/afs/write.c +++ b/fs/afs/write.c
@@ -844,8 +844,8 @@ int afs_launder_page(struct page *page) * Clear the PG_fscache flag from a sequence of pages and wake up anyone who's * waiting. The last page is included in the sequence. */ -static void afs_clear_fscache_bits(struct address_space *mapping, - pgoff_t start, pgoff_t last) +void afs_clear_fscache_bits(struct address_space *mapping, + pgoff_t start, pgoff_t last) { struct page *page;