return found_bytenr == logical && !bad_fsid;
}
+/*
+ * Is @bytenr a tree block the committed extent tree still references? Read
+ * the commit root without locking, as the caching thread does, so this is
+ * safe from the raid56 worker with no transaction to join; a lookup that
+ * cannot answer counts the block as live.
+ */
+static bool rmw_block_referenced(struct btrfs_fs_info *fs_info, u64 bytenr,
+ u64 header_gen, u64 *refs, u64 *extent_gen)
+{
+ struct btrfs_root *extent_root = btrfs_extent_root(fs_info, bytenr);
+ struct btrfs_path *path;
+ struct btrfs_key key;
+ bool live = true;
+ int ret;
+
+ *refs = 0;
+ *extent_gen = 0;
+ path = btrfs_alloc_path();
+ if (!path)
+ return true;
+ path->search_commit_root = 1;
+ path->skip_locking = 1;
+ key.objectid = bytenr;
+ key.type = BTRFS_METADATA_ITEM_KEY;
+ key.offset = (u64)-1;
+ down_read(&fs_info->commit_root_sem);
+ ret = btrfs_search_slot(NULL, extent_root, &key, path, 0, 0);
+ if (ret > 0)
+ ret = btrfs_previous_extent_item(extent_root, path, bytenr);
+ if (ret == 0) {
+ btrfs_item_key_to_cpu(path->nodes[0], &key, path->slots[0]);
+ if (key.objectid == bytenr &&
+ (key.type == BTRFS_METADATA_ITEM_KEY ||
+ key.type == BTRFS_EXTENT_ITEM_KEY)) {
+ struct btrfs_extent_item *ei;
+
+ ei = btrfs_item_ptr(path->nodes[0], path->slots[0],
+ struct btrfs_extent_item);
+ *refs = btrfs_extent_refs(path->nodes[0], ei);
+ *extent_gen = btrfs_extent_generation(path->nodes[0], ei);
+ /*
+ * An extent item at this bytenr from a LATER
+ * transaction than the header on disk belongs to a
+ * new block allocated here whose write has not landed
+ * yet; the header is the dead previous occupant.
+ * Only an item of the header's own generation makes
+ * the block on disk live.
+ */
+ live = (*refs != 0 && *extent_gen == header_gen);
+ } else {
+ live = false;
+ }
+ } else if (ret > 0) {
+ live = false;
+ }
+ up_read(&fs_info->commit_root_sem);
+ btrfs_free_path(path);
+ return live;
+}
+
/*
* Decide whether an uncovered read-modify-write is a write hole or only a
* cost, by looking at what is actually in the stripe.
atomic64_inc(&fs_info->stripe_park_stats.meta_rmw_cur);
continue;
}
- atomic64_inc(&fs_info->stripe_park_stats.meta_rmw_old);
- btrfs_warn_rl(fs_info,
-"read-modify-write of stripe %llu rewrites parity over committed tree block at generation %llu while writing generation %llu: write hole",
- rbio->bioc->full_stripe_logical, gen, write_gen);
+ /*
+ * An older header is only a write hole if the block is still
+ * referenced in the committed extent tree: a stripe reclaimed
+ * after every block in it died is claimed fully free and its
+ * dead blocks keep their headers, and a torn write over those
+ * damages nothing a reader can reach. The committed tree is
+ * the witness a degraded read after a crash would consult, so
+ * ask it (no transaction: delayed refs of the running one are
+ * not yet what a crash would see).
+ */
+ {
+ const int stripe_nr = i / rbio->stripe_nsectors;
+ const int sectornr = i % rbio->stripe_nsectors;
+ const u64 logical = rbio->bioc->full_stripe_logical +
+ (u64)stripe_nr * BTRFS_STRIPE_LEN +
+ (u64)sectornr * fs_info->sectorsize;
+ u64 refs;
+ u64 egen;
+
+ if (!rmw_block_referenced(fs_info, logical, gen, &refs,
+ &egen)) {
+ atomic64_inc(&fs_info->stripe_park_stats.meta_rmw_ghost);
+ continue;
+ }
+ atomic64_inc(&fs_info->stripe_park_stats.meta_rmw_old);
+ btrfs_warn_rl(fs_info,
+"read-modify-write of stripe %llu rewrites parity over committed tree block %llu at generation %llu (refs %llu, extent generation %llu) while writing generation %llu: write hole",
+ rbio->bioc->full_stripe_logical, logical,
+ gen, refs, egen, write_gen);
+ }
}
}