Commit 6b492649 authored by Linus Torvalds's avatar Linus Torvalds
Browse files
Pull btrfs fixes from David Sterba:
 "A few more stability fixes. There's one patch adding export of MIPS
  cmpxchg helper, used in the error propagation fix.

   - fix error propagation from split bios to the original btrfs bio

   - fix merging of adjacent extents (normal operation, defragmentation)

   - fix potential use after free after freeing btrfs device structures"

* tag 'for-6.12-rc5-tag' of git://git.kernel.org/pub/scm/linux/kernel/git/kdave/linux:
  btrfs: fix defrag not merging contiguous extents due to merged extent maps
  btrfs: fix extent map merging not happening for adjacent extents
  btrfs: fix use-after-free of block device file in __btrfs_free_extra_devids()
  btrfs: fix error propagation of split bios
  MIPS: export __cmpxchg_small()
parents 7b83601d 77b0d113
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -102,3 +102,4 @@ unsigned long __cmpxchg_small(volatile void *ptr, unsigned long old,
			return old;
	}
}
EXPORT_SYMBOL(__cmpxchg_small);
+13 −24
Original line number Diff line number Diff line
@@ -49,6 +49,7 @@ void btrfs_bio_init(struct btrfs_bio *bbio, struct btrfs_fs_info *fs_info,
	bbio->end_io = end_io;
	bbio->private = private;
	atomic_set(&bbio->pending_ios, 1);
	WRITE_ONCE(bbio->status, BLK_STS_OK);
}

/*
@@ -113,42 +114,30 @@ static void __btrfs_bio_end_io(struct btrfs_bio *bbio)
	}
}

static void btrfs_orig_write_end_io(struct bio *bio);

static void btrfs_bbio_propagate_error(struct btrfs_bio *bbio,
				       struct btrfs_bio *orig_bbio)
{
	/*
	 * For writes we tolerate nr_mirrors - 1 write failures, so we can't
	 * just blindly propagate a write failure here.  Instead increment the
	 * error count in the original I/O context so that it is guaranteed to
	 * be larger than the error tolerance.
	 */
	if (bbio->bio.bi_end_io == &btrfs_orig_write_end_io) {
		struct btrfs_io_stripe *orig_stripe = orig_bbio->bio.bi_private;
		struct btrfs_io_context *orig_bioc = orig_stripe->bioc;

		atomic_add(orig_bioc->max_errors, &orig_bioc->error);
	} else {
		orig_bbio->bio.bi_status = bbio->bio.bi_status;
	}
}

void btrfs_bio_end_io(struct btrfs_bio *bbio, blk_status_t status)
{
	bbio->bio.bi_status = status;
	if (bbio->bio.bi_pool == &btrfs_clone_bioset) {
		struct btrfs_bio *orig_bbio = bbio->private;

		if (bbio->bio.bi_status)
			btrfs_bbio_propagate_error(bbio, orig_bbio);
		btrfs_cleanup_bio(bbio);
		bbio = orig_bbio;
	}

	if (atomic_dec_and_test(&bbio->pending_ios))
	/*
	 * At this point, bbio always points to the original btrfs_bio. Save
	 * the first error in it.
	 */
	if (status != BLK_STS_OK)
		cmpxchg(&bbio->status, BLK_STS_OK, status);

	if (atomic_dec_and_test(&bbio->pending_ios)) {
		/* Load split bio's error which might be set above. */
		if (status == BLK_STS_OK)
			bbio->bio.bi_status = READ_ONCE(bbio->status);
		__btrfs_bio_end_io(bbio);
	}
}

static int next_repair_mirror(struct btrfs_failed_bio *fbio, int cur_mirror)
{
+3 −0
Original line number Diff line number Diff line
@@ -79,6 +79,9 @@ struct btrfs_bio {
	/* File system that this I/O operates on. */
	struct btrfs_fs_info *fs_info;

	/* Save the first error status of split bio. */
	blk_status_t status;

	/*
	 * This member must come last, bio_alloc_bioset will allocate enough
	 * bytes for entire btrfs_bio but relies on bio being last.
+5 −5
Original line number Diff line number Diff line
@@ -763,12 +763,12 @@ static struct extent_map *defrag_lookup_extent(struct inode *inode, u64 start,
	 * We can get a merged extent, in that case, we need to re-search
	 * tree to get the original em for defrag.
	 *
	 * If @newer_than is 0 or em::generation < newer_than, we can trust
	 * this em, as either we don't care about the generation, or the
	 * merged extent map will be rejected anyway.
	 * This is because even if we have adjacent extents that are contiguous
	 * and compatible (same type and flags), we still want to defrag them
	 * so that we use less metadata (extent items in the extent tree and
	 * file extent items in the inode's subvolume tree).
	 */
	if (em && (em->flags & EXTENT_FLAG_MERGED) &&
	    newer_than && em->generation >= newer_than) {
	if (em && (em->flags & EXTENT_FLAG_MERGED)) {
		free_extent_map(em);
		em = NULL;
	}
+6 −1
Original line number Diff line number Diff line
@@ -230,7 +230,12 @@ static bool mergeable_maps(const struct extent_map *prev, const struct extent_ma
	if (extent_map_end(prev) != next->start)
		return false;

	if (prev->flags != next->flags)
	/*
	 * The merged flag is not an on-disk flag, it just indicates we had the
	 * extent maps of 2 (or more) adjacent extents merged, so factor it out.
	 */
	if ((prev->flags & ~EXTENT_FLAG_MERGED) !=
	    (next->flags & ~EXTENT_FLAG_MERGED))
		return false;

	if (next->disk_bytenr < EXTENT_MAP_LAST_BYTE - 1)
Loading