Thread (3 messages) 3 messages, 1 author, 2021-12-16

Re: [PATCH RFC] btrfs: reject transaction creation for read-only mount except for log recovery

From: Qu Wenruo <hidden>
Date: 2021-12-16 09:32:26


On 2021/12/16 17:14, Qu Wenruo wrote:
quoted hunk ↗ jump to hunk
[BUG]
The following super simple script would crash btrfs at unmount time, if
CONFIG_BTRFS_ASSERT() is set.

  mkfs.btrfs -f $dev
  mount $dev $mnt
  xfs_io -f -c "pwrite 0 4k" $mnt/file
  umount $mnt
  mount -r ro $dev $mnt
  btrfs scrub start -Br $mnt
  umount $mnt

This will trigger the following ASSERT() introduced by commit
0a31daa4b602 ("btrfs: add assertion for empty list of transactions at
late stage of umount").

That patch is deifnitely not the cause, it just makes enough noise for
us developer.

[CAUSE]
We will start transaction for the following call chain during scrub:

scrub_enumerate_chunks()
|- btrfs_inc_block_group_ro()
    |- btrfs_join_transaction()

However for RO mount, there is no running transaction at all, thus
btrfs_join_transaction() will start a new transaction.

But since the fs is already read-only, there is no way to commit the
transaction, thus triggering the ASSERT().

The bug should be there for a long time. As I can still reproduce the
crash at v5.10 kernel.

[FIX]
Currently I choose to separate the log recovery code transaction with
other transactions, and reject all other transactions if the filesystem
is mounted read-only.

But I'm not sure if this is the best solution, thus this patch still
requires for comments.

There is some alternatives I can thing of:

- Don't start new transaction in btrfs_inc_block_group_ro().
   We have btrfs_join_transaction_nostart(), but even with that we still
   can't ensure there is no new transaction started and committed after
   we called btrfs_join_transaction_nostart() and got -ENOENT.

- Allow btrfs to commit empty transaction without writing any thing
   If we know this transaction contains no dirty metadata, we allow
   it to be "committed" even on RO mount, although no real data will
   be written to disk.

And even with current fix, I'm not 100% sure if we won't get a crash if
we run read-only scrub with frequently ro/rw remount.

Signed-off-by: Qu Wenruo <redacted>
---
  fs/btrfs/block-group.c |  6 +++++-
  fs/btrfs/transaction.c | 14 ++++++++++++++
  fs/btrfs/transaction.h |  5 +++++
  fs/btrfs/tree-log.c    |  2 +-
  4 files changed, 25 insertions(+), 2 deletions(-)
diff --git a/fs/btrfs/block-group.c b/fs/btrfs/block-group.c
index 1db24e6d6d90..13391d562189 100644
--- a/fs/btrfs/block-group.c
+++ b/fs/btrfs/block-group.c
@@ -2546,6 +2546,9 @@ int btrfs_inc_block_group_ro(struct btrfs_block_group *cache,
  
  	do {
  		trans = btrfs_join_transaction(root);
+		if (IS_ERR(trans) && PTR_ERR(trans) == -EROFS)
+			return 0;
+
Here I should still go through ro_block_group_mutex locking and 
inc_block_group_ro().

By this, we don't need to change the BUG_ON() call.

But the core idea is still the same.

Thanks,
Qu
quoted hunk ↗ jump to hunk
  		if (IS_ERR(trans))
  			return PTR_ERR(trans);
  
@@ -2621,7 +2624,8 @@ void btrfs_dec_block_group_ro(struct btrfs_block_group *cache)
  	struct btrfs_space_info *sinfo = cache->space_info;
  	u64 num_bytes;
  
-	BUG_ON(!cache->ro);
+	if (cache->ro)
+		return;
  
  	spin_lock(&sinfo->lock);
  	spin_lock(&cache->lock);
diff --git a/fs/btrfs/transaction.c b/fs/btrfs/transaction.c
index 03de89b45f27..306eaeb41ec9 100644
--- a/fs/btrfs/transaction.c
+++ b/fs/btrfs/transaction.c
@@ -591,6 +591,13 @@ start_transaction(struct btrfs_root *root, unsigned int num_items,
  	if (BTRFS_FS_ERROR(fs_info))
  		return ERR_PTR(-EROFS);
  
+	/*
+	 * If the FS is mounted RO, we only allow transaction for log recovery,
+	 * no regular transaction can be started.
+	 */
+	if (sb_rdonly(fs_info->sb) && !(type & __TRANS_START_IGNORE_RO))
+		return ERR_PTR(-EROFS);
+
  	if (current->journal_info) {
  		WARN_ON(type & TRANS_EXTWRITERS);
  		h = current->journal_info;
@@ -781,6 +788,13 @@ struct btrfs_trans_handle *btrfs_start_transaction(struct btrfs_root *root,
  				 BTRFS_RESERVE_FLUSH_ALL, true);
  }
  
+struct btrfs_trans_handle *btrfs_start_transaction_log_recover(
+			struct btrfs_root *root, unsigned int num_items)
+{
+	return start_transaction(root, num_items, TRANS_START_LOG_RECOVER,
+				 BTRFS_RESERVE_FLUSH_ALL, true);
+}
+
  struct btrfs_trans_handle *btrfs_start_transaction_fallback_global_rsv(
  					struct btrfs_root *root,
  					unsigned int num_items)
diff --git a/fs/btrfs/transaction.h b/fs/btrfs/transaction.h
index 1852ed9de7fd..24a743d91eff 100644
--- a/fs/btrfs/transaction.h
+++ b/fs/btrfs/transaction.h
@@ -106,8 +106,11 @@ struct btrfs_transaction {
  #define __TRANS_JOIN_NOLOCK	(1U << 12)
  #define __TRANS_DUMMY		(1U << 13)
  #define __TRANS_JOIN_NOSTART	(1U << 14)
+#define __TRANS_START_IGNORE_RO	(1U << 15)
  
  #define TRANS_START		(__TRANS_START | __TRANS_FREEZABLE)
+#define TRANS_START_LOG_RECOVER	(__TRANS_START | __TRANS_FREEZABLE | \
+				 __TRANS_START_IGNORE_RO)
  #define TRANS_ATTACH		(__TRANS_ATTACH)
  #define TRANS_JOIN		(__TRANS_JOIN | __TRANS_FREEZABLE)
  #define TRANS_JOIN_NOLOCK	(__TRANS_JOIN_NOLOCK)
@@ -201,6 +204,8 @@ static inline void btrfs_clear_skip_qgroup(struct btrfs_trans_handle *trans)
  int btrfs_end_transaction(struct btrfs_trans_handle *trans);
  struct btrfs_trans_handle *btrfs_start_transaction(struct btrfs_root *root,
  						   unsigned int num_items);
+struct btrfs_trans_handle *btrfs_start_transaction_log_recover(
+			struct btrfs_root *root, unsigned int num_items);
  struct btrfs_trans_handle *btrfs_start_transaction_fallback_global_rsv(
  					struct btrfs_root *root,
  					unsigned int num_items);
diff --git a/fs/btrfs/tree-log.c b/fs/btrfs/tree-log.c
index 69f901813ea8..42369fa9a038 100644
--- a/fs/btrfs/tree-log.c
+++ b/fs/btrfs/tree-log.c
@@ -6478,7 +6478,7 @@ int btrfs_recover_log_trees(struct btrfs_root *log_root_tree)
  
  	set_bit(BTRFS_FS_LOG_RECOVERING, &fs_info->flags);
  
-	trans = btrfs_start_transaction(fs_info->tree_root, 0);
+	trans = btrfs_start_transaction_log_recover(fs_info->tree_root, 0);
  	if (IS_ERR(trans)) {
  		ret = PTR_ERR(trans);
  		goto error;
  
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help