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;