While we have a transaction ongoing, the VM might decide at any time
to call btree_inode->i_mapping->a_ops->writepages(), which will start
writeback of dirty pages belonging to btree nodes/leafs. This call
might return an error or the writeback might finish with an error
before we attempt to commit the running transaction. If this happens,
we might have no way of knowing that such error happened when we are
committing the transaction - because the pages might no longer be
marked dirty nor tagged for writeback (if a subsequent modification
to the extent buffer didn't happen before the transaction commit) which
makes filemap_fdata[write|wait]_range unable to find such pages (even
if they're marked with SetPageError).
So if this happens we must abort the transaction, otherwise we commit
a super block with btree roots that point to btree nodes/leafs whose
content on disk is invalid - either garbage or the content of some
node/leaf from a past generation that got cowed or deleted and is no
longer valid (for this later case we end up getting error messages like
"parent transid verify failed on 10826481664 wanted 25748 found 29562"
when reading btree nodes/leafs from disk).

Note that setting and checking AS_EIO/AS_ENOSPC in the btree inode's
i_mapping would not be enough because we need to distinguish between
log tree extents (not fatal) vs non-log tree extents (fatal) and
because the next call to filemap_fdatawait_range() will catch and clear
such errors in the mapping - and that call might be from a log sync and
not from a transaction commit, which means we would not know about the
error at transaction commit time. Also, checking for the eb flag
EXTENT_BUFFER_IOERR at transaction commit time isn't done and would
not be completely reliable, as the eb might be removed from memory and
read back when trying to get it, which clears that flag right before
reading the eb's pages from disk, making us not know about the previous
write error.

Using the BTRFS_INODE_BTREE_IO_ERR and BTRFS_INODE_BTREE_LOG_IO_ERR
inode flags also makes us achieve the goal of AS_EIO/AS_ENOSPC when
writepages() returns success, started writeback for all dirty pages
and before filemap_fdatawait_range() is called, the writeback for
all dirty pages had already finished with errors - because we were
not using AS_EIO/AS_ENOSPC, filemap_fdatawait_range() would return
success, as it could not know that writeback errors happened (the
pages were no longer tagged for writeback).

Signed-off-by: Filipe Manana <fdman...@suse.com>
---
 fs/btrfs/btrfs_inode.h |  2 ++
 fs/btrfs/extent_io.c   | 69 +++++++++++++++++++++++++++++++++++++++++++++++---
 fs/btrfs/transaction.c | 20 ++++++++++++---
 fs/btrfs/transaction.h |  3 +--
 fs/btrfs/tree-log.c    | 13 ++++++----
 5 files changed, 93 insertions(+), 14 deletions(-)

diff --git a/fs/btrfs/btrfs_inode.h b/fs/btrfs/btrfs_inode.h
index 3511031..dbe37dc 100644
--- a/fs/btrfs/btrfs_inode.h
+++ b/fs/btrfs/btrfs_inode.h
@@ -44,6 +44,8 @@
 #define BTRFS_INODE_IN_DELALLOC_LIST           9
 #define BTRFS_INODE_READDIO_NEED_LOCK          10
 #define BTRFS_INODE_HAS_PROPS                  11
+#define BTRFS_INODE_BTREE_IO_ERR               12
+#define BTRFS_INODE_BTREE_LOG_IO_ERR           13
 
 /* in memory btrfs inode */
 struct btrfs_inode {
diff --git a/fs/btrfs/extent_io.c b/fs/btrfs/extent_io.c
index 91f866c..33b113b 100644
--- a/fs/btrfs/extent_io.c
+++ b/fs/btrfs/extent_io.c
@@ -20,6 +20,7 @@
 #include "locking.h"
 #include "rcu-string.h"
 #include "backref.h"
+#include "transaction.h"
 
 static struct kmem_cache *extent_state_cache;
 static struct kmem_cache *extent_buffer_cache;
@@ -3606,6 +3607,68 @@ static void end_extent_buffer_writeback(struct 
extent_buffer *eb)
        wake_up_bit(&eb->bflags, EXTENT_BUFFER_WRITEBACK);
 }
 
+static void set_btree_ioerr(struct page *page, int err)
+{
+       struct extent_buffer *eb = (struct extent_buffer *)page->private;
+       const u64 start = eb->start;
+       const u64 end = eb->start + eb->len - 1;
+       struct btrfs_fs_info *fs_info = eb->fs_info;
+       int ret;
+
+       set_bit(EXTENT_BUFFER_IOERR, &eb->bflags);
+       SetPageError(page);
+
+       /*
+        * If writeback for a btree extent that doesn't belong to a log tree
+        * failed, set the bit BTRFS_INODE_BTREE_IO_ERR in the inode btree.
+        * We do this because while the transaction is running and before it's
+        * committing (when we call filemap_fdata[write|wait]_range against
+        * the btree inode), we might have
+        * btree_inode->i_mapping->a_ops->writepages() called by the VM - if it
+        * returns an error or an error happens during writeback, when we're
+        * committing the transaction we wouldn't know about it, since the pages
+        * can be no longer dirty nor marked anymore for writeback (if a
+        * subsequent modification to the extent buffer didn't happen before the
+        * transaction commit), which makes filemap_fdata[write|wait]_range not
+        * able to find the pages tagged with SetPageError at transaction
+        * commit time. So if this happens we must abort the transaction,
+        * otherwise we commit a super block with btree roots that point to
+        * btree nodes/leafs whose content on disk is invalid - either garbage
+        * or the content of some node/leaf from a past generation that got
+        * cowed or deleted and is no longer valid.
+        *
+        * Note: setting AS_EIO/AS_ENOSPC in the btree inode's i_mapping would
+        * not be enough - we need to distinguish between log tree extents vs
+        * non-log tree extents, and the next filemap_fdatawait_range() call
+        * will catch and clear such errors in the mapping - and that call might
+        * be from a log sync and not from a transaction commit. Also, checking
+        * for the eb flag EXTENT_BUFFER_IOERR at transaction commit time isn't
+        * done and would not be completely reliable, as the eb might be removed
+        * from memory and read back when trying to get it, which clears that
+        * flag right before reading the eb's pages from disk, making us not
+        * know about the previous write error.
+        *
+        * Using the BTRFS_INODE_BTREE_IO_ERR and BTRFS_INODE_BTREE_LOG_IO_ERR
+        * inode flags also makes us achieve the goal of AS_EIO/AS_ENOSPC when
+        * writepages() returns success, started writeback for all dirty pages
+        * and before filemap_fdatawait_range() is called, the writeback for
+        * all dirty pages had already finished with errors - because we were
+        * not using AS_EIO/AS_ENOSPC, filemap_fdatawait_range() would return
+        * success, as it could not know that writeback errors happened (the
+        * pages were no longer tagged for writeback).
+        */
+       ASSERT(fs_info->running_transaction);
+       ret = test_range_bit(&fs_info->running_transaction->dirty_pages,
+                            start, end, EXTENT_NEED_WAIT | EXTENT_DIRTY,
+                            1, NULL);
+       if (ret)
+               set_bit(BTRFS_INODE_BTREE_IO_ERR,
+                       &BTRFS_I(fs_info->btree_inode)->runtime_flags);
+       else
+               set_bit(BTRFS_INODE_BTREE_LOG_IO_ERR,
+                       &BTRFS_I(fs_info->btree_inode)->runtime_flags);
+}
+
 static void end_bio_extent_buffer_writepage(struct bio *bio, int err)
 {
        struct bio_vec *bvec;
@@ -3620,9 +3683,8 @@ static void end_bio_extent_buffer_writepage(struct bio 
*bio, int err)
                done = atomic_dec_and_test(&eb->io_pages);
 
                if (err || test_bit(EXTENT_BUFFER_IOERR, &eb->bflags)) {
-                       set_bit(EXTENT_BUFFER_IOERR, &eb->bflags);
                        ClearPageUptodate(page);
-                       SetPageError(page);
+                       set_btree_ioerr(page, err < 0 ? err : -EIO);
                }
 
                end_page_writeback(page);
@@ -3666,8 +3728,7 @@ static noinline_for_stack int write_one_eb(struct 
extent_buffer *eb,
                                         0, epd->bio_flags, bio_flags);
                epd->bio_flags = bio_flags;
                if (ret) {
-                       set_bit(EXTENT_BUFFER_IOERR, &eb->bflags);
-                       SetPageError(p);
+                       set_btree_ioerr(p, ret);
                        end_page_writeback(p);
                        if (atomic_sub_and_test(num_pages - i, &eb->io_pages))
                                end_extent_buffer_writeback(eb);
diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
index 1e272c0..f17829a 100644
--- a/fs/btrfs/transaction.c
+++ b/fs/btrfs/transaction.c
@@ -844,6 +844,7 @@ int btrfs_write_marked_extents(struct btrfs_root *root,
  * on all the pages and clear them from the dirty pages state tree
  */
 int btrfs_wait_marked_extents(struct btrfs_root *root,
+                             struct btrfs_trans_handle *trans,
                              struct extent_io_tree *dirty_pages, int mark)
 {
        int err = 0;
@@ -852,6 +853,7 @@ int btrfs_wait_marked_extents(struct btrfs_root *root,
        struct extent_state *cached_state = NULL;
        u64 start = 0;
        u64 end;
+       struct inode *btree_inode = root->fs_info->btree_inode;
 
        while (!find_first_extent_bit(dirty_pages, start, &start, &end,
                                      EXTENT_NEED_WAIT, &cached_state)) {
@@ -865,6 +867,17 @@ int btrfs_wait_marked_extents(struct btrfs_root *root,
        }
        if (err)
                werr = err;
+
+       if (dirty_pages == &trans->transaction->dirty_pages) {
+               if (test_and_clear_bit(BTRFS_INODE_BTREE_IO_ERR,
+                                      &BTRFS_I(btree_inode)->runtime_flags))
+                       werr = werr ? werr : -EIO;
+       } else {
+               if (test_and_clear_bit(BTRFS_INODE_BTREE_LOG_IO_ERR,
+                                      &BTRFS_I(btree_inode)->runtime_flags))
+                       werr = werr ? werr : -EIO;
+       }
+
        return werr;
 }
 
@@ -874,6 +887,7 @@ int btrfs_wait_marked_extents(struct btrfs_root *root,
  * those extents are on disk for transaction or log commit
  */
 static int btrfs_write_and_wait_marked_extents(struct btrfs_root *root,
+                               struct btrfs_trans_handle *trans,
                                struct extent_io_tree *dirty_pages, int mark)
 {
        int ret;
@@ -883,7 +897,7 @@ static int btrfs_write_and_wait_marked_extents(struct 
btrfs_root *root,
        blk_start_plug(&plug);
        ret = btrfs_write_marked_extents(root, dirty_pages, mark);
        blk_finish_plug(&plug);
-       ret2 = btrfs_wait_marked_extents(root, dirty_pages, mark);
+       ret2 = btrfs_wait_marked_extents(root, trans, dirty_pages, mark);
 
        if (ret)
                return ret;
@@ -892,7 +906,7 @@ static int btrfs_write_and_wait_marked_extents(struct 
btrfs_root *root,
        return 0;
 }
 
-int btrfs_write_and_wait_transaction(struct btrfs_trans_handle *trans,
+static int btrfs_write_and_wait_transaction(struct btrfs_trans_handle *trans,
                                     struct btrfs_root *root)
 {
        if (!trans || !trans->transaction) {
@@ -900,7 +914,7 @@ int btrfs_write_and_wait_transaction(struct 
btrfs_trans_handle *trans,
                btree_inode = root->fs_info->btree_inode;
                return filemap_write_and_wait(btree_inode->i_mapping);
        }
-       return btrfs_write_and_wait_marked_extents(root,
+       return btrfs_write_and_wait_marked_extents(root, trans,
                                           &trans->transaction->dirty_pages,
                                           EXTENT_DIRTY);
 }
diff --git a/fs/btrfs/transaction.h b/fs/btrfs/transaction.h
index 7dd558e..78b754a 100644
--- a/fs/btrfs/transaction.h
+++ b/fs/btrfs/transaction.h
@@ -146,8 +146,6 @@ struct btrfs_trans_handle *btrfs_attach_transaction_barrier(
                                        struct btrfs_root *root);
 struct btrfs_trans_handle *btrfs_start_ioctl_transaction(struct btrfs_root 
*root);
 int btrfs_wait_for_commit(struct btrfs_root *root, u64 transid);
-int btrfs_write_and_wait_transaction(struct btrfs_trans_handle *trans,
-                                    struct btrfs_root *root);
 
 void btrfs_add_dead_root(struct btrfs_root *root);
 int btrfs_defrag_root(struct btrfs_root *root);
@@ -167,6 +165,7 @@ int btrfs_record_root_in_trans(struct btrfs_trans_handle 
*trans,
 int btrfs_write_marked_extents(struct btrfs_root *root,
                                struct extent_io_tree *dirty_pages, int mark);
 int btrfs_wait_marked_extents(struct btrfs_root *root,
+                             struct btrfs_trans_handle *trans,
                                struct extent_io_tree *dirty_pages, int mark);
 int btrfs_transaction_blocked(struct btrfs_fs_info *info);
 int btrfs_transaction_in_commit(struct btrfs_fs_info *info);
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index 2d0fa43..22ffd32 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -2583,7 +2583,8 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans,
                        mutex_unlock(&log_root_tree->log_mutex);
                        goto out;
                }
-               btrfs_wait_marked_extents(log, &log->dirty_log_pages, mark);
+               btrfs_wait_marked_extents(log, trans, &log->dirty_log_pages,
+                                         mark);
                btrfs_free_logged_extents(log, log_transid);
                mutex_unlock(&log_root_tree->log_mutex);
                ret = -EAGAIN;
@@ -2599,7 +2600,8 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans,
        index2 = root_log_ctx.log_transid % 2;
        if (atomic_read(&log_root_tree->log_commit[index2])) {
                blk_finish_plug(&plug);
-               btrfs_wait_marked_extents(log, &log->dirty_log_pages, mark);
+               btrfs_wait_marked_extents(log, trans, &log->dirty_log_pages,
+                                         mark);
                wait_log_commit(trans, log_root_tree,
                                root_log_ctx.log_transid);
                btrfs_free_logged_extents(log, log_transid);
@@ -2623,7 +2625,8 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans,
         */
        if (btrfs_need_log_full_commit(root->fs_info, trans)) {
                blk_finish_plug(&plug);
-               btrfs_wait_marked_extents(log, &log->dirty_log_pages, mark);
+               btrfs_wait_marked_extents(log, trans, &log->dirty_log_pages,
+                                         mark);
                btrfs_free_logged_extents(log, log_transid);
                mutex_unlock(&log_root_tree->log_mutex);
                ret = -EAGAIN;
@@ -2641,8 +2644,8 @@ int btrfs_sync_log(struct btrfs_trans_handle *trans,
                mutex_unlock(&log_root_tree->log_mutex);
                goto out_wake_log_root;
        }
-       btrfs_wait_marked_extents(log, &log->dirty_log_pages, mark);
-       btrfs_wait_marked_extents(log_root_tree,
+       btrfs_wait_marked_extents(log, trans, &log->dirty_log_pages, mark);
+       btrfs_wait_marked_extents(log_root_tree, trans,
                                  &log_root_tree->dirty_log_pages,
                                  EXTENT_NEW | EXTENT_DIRTY);
        btrfs_wait_logged_extents(log, log_transid);
-- 
1.9.1

--
To unsubscribe from this list: send the line "unsubscribe linux-btrfs" in
the body of a message to majord...@vger.kernel.org
More majordomo info at  http://vger.kernel.org/majordomo-info.html

Reply via email to