return ret;
}
+#ifdef CONFIG_BTRFS_DEBUG
+/*
+ * Write-hole invariant checker, called for every raid56 write operation
+ * (full-stripe and sub-stripe/RMW alike). With stripe-exclusive
+ * allocation, a raid56 data stripe may only be written while an open or
+ * draining stripe run covers it: once its run retires and drains, nothing
+ * may ever write it again, and a write outside any run means an
+ * allocation bypassed the policy. Either way it is the write hole about
+ * to happen, caught deterministically at the point of the write instead
+ * of probabilistically after a crash plus a device failure.
+ *
+ * Block groups that hosted relocation-class runs are skipped:
+ * relocation legitimately overwrites its preallocated extents in place
+ * after their runs drain.
+ */
+void btrfs_stripe_check_write(struct btrfs_fs_info *fs_info,
+ u64 full_stripe_start, bool sub_stripe)
+{
+ struct btrfs_open_stripe_run *run;
+ struct btrfs_block_group *bg;
+ unsigned long flags;
+ bool live = false;
+ u64 fsl;
+
+ if (!btrfs_test_opt(fs_info, STRIPE_ALLOC))
+ return;
+ bg = btrfs_lookup_block_group(fs_info, full_stripe_start);
+ if (!bg)
+ return;
+ if (!btrfs_is_stripe_alloc_bg(bg) ||
+ test_bit(BLOCK_GROUP_FLAG_STRIPE_RELOC_USED, &bg->runtime_flags))
+ goto out;
+ fsl = bg->full_stripe_len;
+ spin_lock_irqsave(&bg->stripe_run_lock, flags);
+ list_for_each_entry(run, &bg->open_stripe_runs, list) {
+ if (full_stripe_start < run->end &&
+ full_stripe_start + fsl > run->start) {
+ live = true;
+ break;
+ }
+ }
+ spin_unlock_irqrestore(&bg->stripe_run_lock, flags);
+ WARN_ONCE(!live,
+"btrfs: %s write to stripe %llu (block group %llu) outside any live stripe run: write hole window violated",
+ sub_stripe ? "sub-stripe" : "full-stripe",
+ full_stripe_start, bg->start);
+out:
+ btrfs_put_block_group(bg);
+}
+#endif
+
/*
* Drop the dedication of a block group to data relocation. Shared by the
* zoned allocator and the stripe allocation policy; both dedicate one
BLOCK_GROUP_FLAG_NEW,
BLOCK_GROUP_FLAG_FULLY_REMAPPED,
BLOCK_GROUP_FLAG_STRIPE_REMOVAL_PENDING,
+ /*
+ * The group has hosted relocation-class stripe runs. Relocation
+ * overwrites its preallocated extents in place after their runs
+ * drain, so the write-hole debug check cannot tell those writes
+ * from violations and skips such groups (sticky, debug only).
+ */
+ BLOCK_GROUP_FLAG_STRIPE_RELOC_USED,
};
enum btrfs_caching_type {
void btrfs_release_data_reloc_bg(struct btrfs_fs_info *fs_info);
bool btrfs_stripe_alloc_forces_cow(struct btrfs_fs_info *fs_info, u64 bytenr);
bool btrfs_is_stripe_alloc_bg(const struct btrfs_block_group *bg);
+#ifdef CONFIG_BTRFS_DEBUG
+void btrfs_stripe_check_write(struct btrfs_fs_info *fs_info,
+ u64 full_stripe_start, bool sub_stripe);
+#else
+static inline void btrfs_stripe_check_write(struct btrfs_fs_info *fs_info,
+ u64 full_stripe_start,
+ bool sub_stripe) { }
+#endif
struct btrfs_block_group *btrfs_inc_nocow_writers(struct btrfs_fs_info *fs_info,
u64 bytenr);
void btrfs_dec_nocow_writers(struct btrfs_block_group *bg);
btrfs_clear_data_reloc_bg(block_group);
return 1;
}
+ if (class == BTRFS_STRIPE_RUN_RELOC)
+ set_bit(BLOCK_GROUP_FLAG_STRIPE_RELOC_USED,
+ &block_group->runtime_flags);
ffe_ctl->found_offset = offset;
ffe_ctl->search_start = offset;
return 0;
#include "async-thread.h"
#include "file-item.h"
#include "btrfs_inode.h"
+#include "block-group.h"
/* set when additional merges to this rbio are not allowed */
#define RBIO_RMW_LOCKED_BIT 1
int sectornr;
int ret = 0;
+ /*
+ * Write-hole invariant check: every write must land in a stripe
+ * covered by a live stripe run when stripe-exclusive allocation is
+ * enabled. The bios gathered in this rbio have not reported their
+ * IO done yet, so their runs cannot drain under us.
+ */
+ btrfs_stripe_check_write(rbio->bioc->fs_info,
+ rbio->bioc->full_stripe_logical,
+ !rbio_is_full(rbio));
+
/*
* Allocate the pages for parity first, as P/Q pages will always be
* needed for both full-stripe and sub-stripe writes.