summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--drivers/md/bcache/fs.c102
1 files changed, 75 insertions, 27 deletions
diff --git a/drivers/md/bcache/fs.c b/drivers/md/bcache/fs.c
index 1388d915014a..edd596a6900c 100644
--- a/drivers/md/bcache/fs.c
+++ b/drivers/md/bcache/fs.c
@@ -136,18 +136,6 @@ static int inode_maybe_clear_dirty(struct bch_inode_info *ei,
return 0;
}
-/*
- * For truncate: We need to set I_SIZE_DIRTY atomically with setting the new
- * (truncated, smaller) size
- */
-static int inode_set_size_and_dirty(struct bch_inode_info *ei,
- struct bch_inode *bi)
-{
- bi->i_flags |= BCH_INODE_I_SIZE_DIRTY;
- bi->i_size = ei->vfs_inode.i_size;
- return 0;
-}
-
static int inode_set_dirty(struct bch_inode_info *ei,
struct bch_inode *bi)
{
@@ -200,7 +188,8 @@ static int __must_check __bch_write_inode(struct cache_set *c,
ret = bch_btree_insert_at(&iter, &keylist_single(&inode.k_i),
NULL, &ei->journal_seq,
- BTREE_INSERT_ATOMIC);
+ BTREE_INSERT_ATOMIC|
+ BTREE_INSERT_NOFAIL);
} while (ret == -EINTR);
if (!ret) {
@@ -654,6 +643,30 @@ out:
return ret;
}
+/*
+ * For truncate: We need to set I_SIZE_DIRTY atomically with setting the new
+ * (truncated, smaller) size
+ */
+static int inode_set_size_and_dirty(struct bch_inode_info *ei,
+ struct bch_inode *bi)
+{
+ bi->i_flags |= BCH_INODE_I_SIZE_DIRTY;
+ bi->i_size = ei->vfs_inode.i_size;
+ return 0;
+}
+
+static int inode_set_size(struct bch_inode_info *ei,
+ struct bch_inode *bi)
+{
+ if (!atomic_long_read(&ei->i_size_dirty_count)) {
+ bi->i_flags &= ~BCH_INODE_I_SIZE_DIRTY;
+ bi->i_size = ei->vfs_inode.i_size;
+ } else if (ei->vfs_inode.i_size < ei->i_size) {
+ bi->i_size = ei->vfs_inode.i_size;
+ }
+ return 0;
+}
+
static int bch_setattr(struct dentry *dentry, struct iattr *iattr)
{
struct inode *inode = dentry->d_inode;
@@ -678,6 +691,11 @@ static int bch_setattr(struct dentry *dentry, struct iattr *iattr)
* i_size, and must be set atomically with setting the new
* i_size:
*/
+
+ /*
+ * XXX: do the i_size_write after the inode update succeeds, so
+ * we're not inconsistent on failure
+ */
mutex_lock(&ei->update_lock);
i_size_dirty_get(ei);
i_size_write(inode, iattr->ia_size);
@@ -691,6 +709,11 @@ static int bch_setattr(struct dentry *dentry, struct iattr *iattr)
if (unlikely(ret))
return ret;
+ /*
+ * XXX: if we error, we leak i_size_dirty count - and we can't
+ * just put it, because it actually is still dirty
+ */
+
if (iattr->ia_size > inode->i_size)
pagecache_isize_extended(inode, inode->i_size,
iattr->ia_size);
@@ -708,12 +731,20 @@ static int bch_setattr(struct dentry *dentry, struct iattr *iattr)
*/
i_size_dirty_put(ei);
inode->i_mtime = inode->i_ctime = CURRENT_TIME;
+
+ mutex_lock(&ei->update_lock);
+ setattr_copy(inode, iattr);
+ ret = __bch_write_inode(c, ei, inode_set_size);
+ mutex_unlock(&ei->update_lock);
+ } else {
+ mutex_lock(&ei->update_lock);
+ setattr_copy(inode, iattr);
+ ret = bch_write_inode(c, ei);
+ mutex_unlock(&ei->update_lock);
+
}
- mutex_lock(&ei->update_lock);
- setattr_copy(inode, iattr);
- ret = bch_write_inode(c, ei); /* clears I_SIZE_DIRTY */
- mutex_unlock(&ei->update_lock);
+ BUG_ON(inode->i_size < ei->i_size);
if (unlikely(ret))
return ret;
@@ -856,6 +887,13 @@ static int bch_mmap(struct file *file, struct vm_area_struct *vma)
return 0;
}
+static int inode_set_partial_size(struct bch_inode_info *ei,
+ struct bch_inode *bi)
+{
+ bi->i_size = ei->i_size;
+ return 0;
+}
+
static int bch_fsync(struct file *file, loff_t start, loff_t end, int datasync)
{
struct inode *inode = file->f_mapping->host;
@@ -871,18 +909,28 @@ static int bch_fsync(struct file *file, loff_t start, loff_t end, int datasync)
if (datasync && end <= ei->i_size)
goto out;
- /* lock inode before checking i_size_dirty_count: */
- if (atomic_long_read(&ei->i_size_dirty_count)) {
- /*
- * We really just want to sync all the PageAppend pages:
- */
- filemap_write_and_wait_range(inode->i_mapping, 0, LLONG_MAX);
- inode_dio_wait(inode);
- }
+ /*
+ * If i_size is dirty, and disk i_size < end < memory i_size, then -
+ * it's safe to write an i_size out that's intermediate, because... XXX
+ * explain
+ */
mutex_lock(&ei->update_lock);
- BUG_ON(atomic_long_read(&ei->i_size_dirty_count));
- ret = bch_write_inode(c, ei);
+
+ if (inode->i_size == ei->i_size) {
+ /* nothing to do */
+ } else if (!atomic_long_read(&ei->i_size_dirty_count)) {
+ ret = bch_write_inode(c, ei);
+ } else if (inode->i_size > ei->i_size) {
+ ei->i_size = min_t(u64, inode->i_size,
+ roundup(end, PAGE_SIZE));
+
+ ret = __bch_write_inode(c, ei, inode_set_partial_size);
+ } else {
+ /* truncate.. */
+ BUG();
+ }
+
mutex_unlock(&ei->update_lock);
out:
inode_unlock(inode);