Viewing: sanity-ec.sh

#!/usr/bin/bash
# SPDX-License-Identifier: GPL-2.0-only
#
# Run select tests by setting ONLY, or as arguments to the script.
# Skip specific tests by setting EXCEPT.
set -e
set +o posix


ONLY=${ONLY:-"$*"}

LUSTRE=${LUSTRE:-$(dirname $0)/..}
. $LUSTRE/tests/test-framework.sh
init_test_env "$@"
init_logging

ALWAYS_EXCEPT="$SANITY_EC_EXCEPT "
always_except LU-19631 12a 12b
# tests 12a: EC parity calculation produces incorrect content (LU-19631)

build_test_filter

check_and_setup_lustre

(( MDS1_VERSION >= $(version_code 2.17.52) )) ||
	skip "Need MDS version at least 2.17.52 for EC support"
DIR=${DIR:-$MOUNT}
assert_DIR
rm -rf $DIR/[Rdfs][0-9]*

(( UID != 0 || RUNAS_ID != 0 )) ||
	error "\$RUNAS_ID set to 0, but \$UID is also 0!"

check_runas_id $RUNAS_ID $RUNAS_GID $RUNAS

# Enable EC support for testing
export LFS_EC_OK=yes
stack_trap "unset LFS_EC_OK" EXIT

#
# Verify mirror count with an expected value for a given file.
#
verify_mirror_count() {
	local tf=$1
	local expected=$2
	local mirror_count=$($LFS getstripe -N $tf)

	(( mirror_count == expected )) || {
		$LFS getstripe -v $tf
		error "verify mirror count failed on $tf:" \
		      "$mirror_count != $expected"
	}
}

#
# Verify component count with an expected value for a given file.
#	$1 composite layout file
#	$2 expected component number
#
verify_comp_count() {
	local tf=$1
	local expected=$2
	local comp_count=$($LFS getstripe --component-count $tf)

	[[ $comp_count = $expected ]] || {
		$LFS getstripe -v $tf
		error "verify component count failed on $tf:" \
		      "$comp_count != $expected"
	}
}

#
# Verify component has the parity flag set
#
verify_comp_parity() {
	local tf=$1
	local comp_id=$2
	local flags=$($LFS getstripe -I$comp_id $tf |
		      awk '/lcme_flags:/ { print $2 }')

	[[ $flags =~ "parity" ]] || {
		$LFS getstripe -I$comp_id -v $tf
		error "verify parity flag failed on $tf component $comp_id:" \
		      "flags=$flags"
	}
}

#
# Enable erasure coding and restore on exit
#
enable_ec() {
	local ec_enable=$($LCTL get_param -n llite.*.enable_erasure_coding)

	$LCTL set_param llite.*.enable_erasure_coding=1
	stack_trap "$LCTL set_param -n llite.*.enable_erasure_coding=$ec_enable"
}

#
# Verify EC stripe counts (data and coding stripes)
#
verify_ec_stripe_count() {
	local tf=$1
	local comp_id=$2
	local expected_dstripe=$3
	local expected_cstripe=$4
	local dstripe=$($LFS getstripe -I$comp_id $tf | \
			awk '/lcme_dstripe_count:/ { print $2 }')
	local cstripe=$($LFS getstripe -I$comp_id $tf | \
			awk '/lcme_cstripe_count:/ { print $2 }')

	[[ $dstripe = $expected_dstripe ]] || {
		$LFS getstripe -I$comp_id -v $tf
		error "verify dstripe count failed on $tf component $comp_id:" \
		      "$dstripe != $expected_dstripe"
	}

	[[ $cstripe = $expected_cstripe ]] || {
		$LFS getstripe -I$comp_id -v $tf
		error "verify cstripe count failed on $tf component $comp_id:" \
		      "$cstripe != $expected_cstripe"
	}
}

#
# Verify component extent with expected start and end extent values
# for a given file and component ID.
#
verify_comp_extent() {
	local tf=$1
	local comp_id=$2
	local expected_start=$3
	local expected_end=$4
	local start=$($LFS getstripe -I$comp_id --component-start $tf)
	local end=$($LFS getstripe -I$comp_id --component-end $tf)

	[[ $start = $expected_start ]] || {
		$LFS getstripe -I$comp_id -v $tf
		error "verify component start failed on $tf comp $comp_id:" \
		      "$start != $expected_start"
	}

	[[ $end = $expected_end ]] || {
		$LFS getstripe -I$comp_id -v $tf
		error "verify component end failed on $tf comp $comp_id:" \
		      "$end != $expected_end"
	}
}

#
# Verify FLR state (ro, wp, sp) for a given file
#
verify_flr_state() {
	local tf=$1
	local expected_state=$2

	local state=$($LFS getstripe -v $tf | awk '/lcm_flags/{ print $2 }')
	[[ $expected_state = $state ]] ||
		error "expected: $expected_state, actual $state"
}

#
# Verify component has stale flag set
#
verify_comp_stale() {
	local tf=$1
	local comp_id=$2
	local flags=$($LFS getstripe -I$comp_id $tf |
			awk '/lcme_flags:/ { print $2 }')

	[[ $flags =~ "stale" ]] || {
		$LFS getstripe -I$comp_id -v $tf
		error "verify stale flag failed on $tf component $comp_id:" \
		      "flags=$flags"
	}
}

#
# Verify stripe size matches between data and EC components
#
verify_ec_stripe_size() {
	local tf=$1
	local data_comp_id=$2
	local ec_comp_id=$3
	local data_stripe_size=$($LFS getstripe -I$data_comp_id $tf | \
				awk '/lmm_stripe_size:/ { print $2 }')
	local ec_stripe_size=$($LFS getstripe -I$ec_comp_id $tf | \
				awk '/lmm_stripe_size:/ { print $2 }')

	[[ $data_stripe_size = $ec_stripe_size ]] || {
		$LFS getstripe -v $tf
		error "stripe size mismatch on $tf:" \
		      "data component $data_comp_id has $data_stripe_size," \
		      "EC component $ec_comp_id has $ec_stripe_size"
	}
}

#
# Read from parity mirror and check for zeros.
# Usage: check_parity_read <file> <mirror_id> <offset> <expect_zeros>
#
check_parity_read() {
	local file=$1
	local mirror_id=$2
	local offset=$3
	local expect_zeros=$4
	local rc

	# test_parity_read returns 0 if all zeros, 1 if non-zero bytes found
	$LUSTRE/tests/test_parity_read $file $mirror_id $offset \
		> /dev/null 2>&1
	rc=$?

	if [[ $expect_zeros == "yes" ]]; then
		(( rc == 0 )) ||
			error "offset $offset: expected zeros, got non-zero bytes"
	else
		(( rc == 1 )) ||
			error "offset $offset: expected parity data, got all zeros"
	fi
}

test_1a() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	# Single component with EC
	$LFS setstripe -E -1 -c 8 --ec 8+2 $tf ||
		error "setstripe failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 2

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify data mirror (mirror 1, component 0)
	verify_comp_extent $tf ${ids[0]} 0 EOF

	# Verify parity mirror (mirror 2, component 1)
	verify_comp_parity $tf ${ids[1]}
	verify_ec_stripe_count $tf ${ids[1]} 8 2
	verify_comp_extent $tf ${ids[1]} 0 EOF

	# Verify stripe size matches between data and EC components
	verify_ec_stripe_size $tf ${ids[0]} ${ids[1]}
}
run_test 1a "basic setstripe with single component and EC"

test_1b() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	# Order independence: --ec before -E
	$LFS setstripe --ec 8+2 -E -1 -c 8 $tf ||
		error "setstripe failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 2

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify data mirror (mirror 1, component 0)
	verify_comp_extent $tf ${ids[0]} 0 EOF

	# Verify parity mirror (mirror 2, component 1)
	verify_comp_parity $tf ${ids[1]}
	verify_ec_stripe_count $tf ${ids[1]} 8 2
	verify_comp_extent $tf ${ids[1]} 0 EOF
}
run_test 1b "setstripe with --ec before -E (order independence)"

test_1c() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	# Test colon separator: --ec 8:2 instead of 8+2
	$LFS setstripe -E -1 -c 8 --ec 8:2 $tf ||
		error "setstripe with colon separator failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 2

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify data mirror (mirror 1, component 0)
	verify_comp_extent $tf ${ids[0]} 0 EOF

	# Verify parity mirror (mirror 2, component 1)
	verify_comp_parity $tf ${ids[1]}
	verify_ec_stripe_count $tf ${ids[1]} 8 2
	verify_comp_extent $tf ${ids[1]} 0 EOF
}
run_test 1c "setstripe with colon separator (--ec 8:2)"

test_1d() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	# Multiple components, single EC spec
	$LFS setstripe -E 128M -E -1 --ec 8+2 $tf ||
		error "setstripe failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 4

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify data mirror components (mirror 1)
	verify_comp_extent $tf ${ids[0]} 0 134217728
	verify_comp_extent $tf ${ids[1]} 134217728 EOF

	# Verify parity mirror components (mirror 2)
	# First parity component
	verify_comp_parity $tf ${ids[2]}
	verify_ec_stripe_count $tf ${ids[2]} 8 2
	verify_comp_extent $tf ${ids[2]} 0 134217728

	# Second parity component
	verify_comp_parity $tf ${ids[3]}
	verify_ec_stripe_count $tf ${ids[3]} 8 2
	verify_comp_extent $tf ${ids[3]} 134217728 EOF
}
run_test 1d "setstripe with multiple components and single EC spec"

test_1e() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	# EC specified on first component, should inherit to second
	$LFS setstripe -E 128M --ec 8+2 -E -1 -c 4 $tf ||
		error "setstripe failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 4

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify data mirror components (mirror 1)
	verify_comp_extent $tf ${ids[0]} 0 134217728
	verify_comp_extent $tf ${ids[1]} 134217728 EOF

	# Verify parity mirror components (mirror 2)
	# First component has EC(8+2) from the spec
	verify_comp_parity $tf ${ids[2]}
	verify_ec_stripe_count $tf ${ids[2]} 8 2
	verify_comp_extent $tf ${ids[2]} 0 134217728

	# Second component has EC(4+2) - reduced from EC(8+2) because
	# the data component has -c 4, so we can't have 8 data stripes
	verify_comp_parity $tf ${ids[3]}
	verify_ec_stripe_count $tf ${ids[3]} 4 2
	verify_comp_extent $tf ${ids[3]} 134217728 EOF
}
run_test 1e "setstripe with EC inheriting to second component"

test_1f() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	# Different EC for different components
	# Note: second component has -c 4, so EC will be adjusted to 4+2
	# (can't have 8 data stripes with only 4 total stripes)
	$LFS setstripe -E 128M --ec 4+2 -E -1 -c 4 --ec 8+2 $tf ||
		error "setstripe failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 4

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify data mirror components (mirror 1)
	verify_comp_extent $tf ${ids[0]} 0 134217728
	verify_comp_extent $tf ${ids[1]} 134217728 EOF

	# Verify parity mirror components (mirror 2)
	# First component should have EC(4+2)
	verify_comp_parity $tf ${ids[2]}
	verify_ec_stripe_count $tf ${ids[2]} 4 2
	verify_comp_extent $tf ${ids[2]} 0 134217728

	# Second component has EC adjusted to 4+2 (data component has -c 4)
	verify_comp_parity $tf ${ids[3]}
	verify_ec_stripe_count $tf ${ids[3]} 4 2
	verify_comp_extent $tf ${ids[3]} 134217728 EOF
}
run_test 1f "setstripe with different EC for different components"

test_1g() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	# Different parity counts
	# Note: second component has -c 4, so EC will be adjusted to 4+1
	# (can't have 8 data stripes with only 4 total stripes)
	$LFS setstripe -E 128M --ec 4+1 -E -1 -c 4 --ec 8+1 $tf ||
		error "setstripe failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 4

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify data mirror components (mirror 1)
	verify_comp_extent $tf ${ids[0]} 0 134217728
	verify_comp_extent $tf ${ids[1]} 134217728 EOF

	# Verify parity mirror components (mirror 2)
	# First component should have EC(4+1)
	verify_comp_parity $tf ${ids[2]}
	verify_ec_stripe_count $tf ${ids[2]} 4 1
	verify_comp_extent $tf ${ids[2]} 0 134217728

	# Second component has EC adjusted to 4+1 (data component has -c 4)
	verify_comp_parity $tf ${ids[3]}
	verify_ec_stripe_count $tf ${ids[3]} 4 1
	verify_comp_extent $tf ${ids[3]} 134217728 EOF
}
run_test 1g "setstripe with different parity counts"

test_1h() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	# Test -N flag with explicit stripe counts and --ec
	# This should create:
	# Mirror 1: data mirror 2 comps ([0, 1M] with -c 1, [1M, EOF] with -c 1)
	# Mirror 2: data mirror 2 comps ([0, 1M] with -c 8, [1M, EOF] with -c 4)
	# Mirror 3: parity 2 EC comps ([0, 1M] with EC(8+2), [1M, EOF] with EC(4+1))
	#
	# This test verifies that when using -N with multiple mirrors and --ec,
	# the EC parity components correctly bind to the data components in the
	# mirror they were added with (Mirror 2), not to components from other
	# mirrors (Mirror 1).

	$LFS setstripe -N -E 1M -c 1 -E -1 -c 1 \
		-N -E 1M -c 8 --ec 8+2 -E -1 -c 4 --ec 4+1 $tf ||
		error "setstripe failed"

	verify_mirror_count $tf 3
	verify_comp_count $tf 6

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify Mirror 1: data mirror without EC
	verify_comp_extent $tf ${ids[0]} 0 1048576
	verify_comp_extent $tf ${ids[1]} 1048576 EOF

	# Verify Mirror 2: data mirror with explicit stripe counts
	verify_comp_extent $tf ${ids[2]} 0 1048576
	verify_comp_extent $tf ${ids[3]} 1048576 EOF

	# Verify Mirror 3: parity mirror with EC
	# First component should have EC(8+2) with dstripe=8
	verify_comp_parity $tf ${ids[4]}
	verify_ec_stripe_count $tf ${ids[4]} 8 2
	verify_comp_extent $tf ${ids[4]} 0 1048576

	# Second component should have EC(4+1) with dstripe=4
	verify_comp_parity $tf ${ids[5]}
	verify_ec_stripe_count $tf ${ids[5]} 4 1
	verify_comp_extent $tf ${ids[5]} 1048576 EOF
}
run_test 1h "setstripe with -N flag and explicit stripe counts with --ec"

test_1i() {
	enable_ec
	local td=$DIR/$tdir
	local ids

	stack_trap "rm -rf $td"

	test_mkdir $td

	# Set default EC layout on directory
	$LFS setstripe -E 128M -E -1 --ec 8+2 $td ||
		error "setstripe on directory failed"

	# Create file in directory - should inherit EC layout
	touch $td/$tfile || error "touch failed"

	verify_mirror_count $td/$tfile 2
	verify_comp_count $td/$tfile 4

	# Get component IDs
	ids=($($LFS getstripe $td/$tfile | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify data mirror components (mirror 1)
	verify_comp_extent $td/$tfile ${ids[0]} 0 134217728
	verify_comp_extent $td/$tfile ${ids[1]} 134217728 EOF

	# Verify parity mirror components (mirror 2)
	verify_comp_parity $td/$tfile ${ids[2]}
	verify_ec_stripe_count $td/$tfile ${ids[2]} 8 2
	verify_comp_extent $td/$tfile ${ids[2]} 0 134217728

	verify_comp_parity $td/$tfile ${ids[3]}
	verify_ec_stripe_count $td/$tfile ${ids[3]} 8 2
	verify_comp_extent $td/$tfile ${ids[3]} 134217728 EOF

	# Create another file to verify inheritance works consistently
	touch $td/${tfile}.2 || error "touch second file failed"
	verify_mirror_count $td/${tfile}.2 2
	verify_comp_count $td/${tfile}.2 4
}
run_test 1i "default EC layout on directory"

test_1j() {
	enable_ec
	local tf=$DIR/$tfile
	local ids
	local data_stripe_size
	local ec_stripe_size

	stack_trap "rm -f $tf"

	# Test with explicit stripe size
	$LFS setstripe -E -1 -S 4M -c 8 --ec 8+2 $tf ||
		error "setstripe with explicit stripe size failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 2

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify data mirror (mirror 1, component 0)
	verify_comp_extent $tf ${ids[0]} 0 EOF

	# Verify parity mirror (mirror 2, component 1)
	verify_comp_parity $tf ${ids[1]}
	verify_ec_stripe_count $tf ${ids[1]} 8 2
	verify_comp_extent $tf ${ids[1]} 0 EOF

	# Verify stripe size matches between data and EC components
	verify_ec_stripe_size $tf ${ids[0]} ${ids[1]}

	# Verify the stripe size is what we set (4M)
	data_stripe_size=$($LFS getstripe -I${ids[0]} $tf | \
			awk '/lmm_stripe_size:/ { print $2 }')
	ec_stripe_size=$($LFS getstripe -I${ids[1]} $tf | \
			awk '/lmm_stripe_size:/ { print $2 }')

	[[ $data_stripe_size = 4194304 ]] || {
		$LFS getstripe -v $tf
		error "data component stripe size should be 4M (4194304)," \
		      "got $data_stripe_size"
	}

	[[ $ec_stripe_size = 4194304 ]] || {
		$LFS getstripe -v $tf
		error "EC component stripe size should be 4M (4194304)," \
		      "got $ec_stripe_size"
	}
}
run_test 1j "verify EC component inherits stripe size from data component"

test_1k() {
	local td=$DIR/$tdir
	local subdir=$td/subdir
	local tf=$subdir/$tfile
	local ef=$subdir/explicit
	local ids

	(( OSTCOUNT >= 6 )) || skip_env "needs >= 6 OSTs"

	enable_ec

	test_mkdir $td

	# Set default EC layout on the parent directory
	$LFS setstripe -E 128M -E -1 --ec 4+2 $td ||
		error "setstripe for $td failed"

	# create another dir layer to verify the link id is inherited correctly
	mkdir $subdir || error "mkdir for $subdir failed"

	# ... and create a file in that subdir
	touch $tf || error "touch for $tf failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 4

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify data mirror components (mirror 1)
	verify_comp_extent $tf ${ids[0]} 0 134217728
	verify_comp_extent $tf ${ids[1]} 134217728 EOF

	# Verify parity mirror components (mirror 2)
	verify_comp_parity $tf ${ids[2]}
	verify_ec_stripe_count $tf ${ids[2]} 4 2
	verify_comp_extent $tf ${ids[2]} 0 134217728

	verify_comp_parity $tf ${ids[3]}
	verify_ec_stripe_count $tf ${ids[3]} 4 2
	verify_comp_extent $tf ${ids[3]} 134217728 EOF

	# verify that explicit EC layouts on new files overwrite dir defaults
	$LFS setstripe -E -1 --ec 2+1 $ef ||
		error "explicit setstripe fir $ef failed"
	verify_mirror_count $ef 2
	verify_comp_count $ef 2
	ids=($($LFS getstripe $ef | awk '/lcme_id/{print $2}' | tr '\n' ' '))
	verify_comp_parity $ef ${ids[1]}
	verify_ec_stripe_count $ef ${ids[1]} 2 1
}
run_test 1k "default EC layout inherited through nested directories"

test_1l() {
	local td=$DIR/$tdir
	local subdir=$td/subdir
	local tf=$subdir/$tfile
	local ids

	(( OSTCOUNT >= 6 )) || skip_env "needs >= 6 OSTs"

	enable_ec

	test_mkdir $td

	# Two EC mirrors, each a PFL with mixed EC params
	#   mirror 1: [0,128M) 2+1, [128M,EOF) 4+2
	#   mirror 2: [0,64M)  4+2, [64M,EOF)  3+2
	# yields 4 mirrors (2 data + 2 parity) and 8 components.
	$LFS setstripe -N -E 128M --ec 2+1 -E -1 --ec 4+2 \
		       -N -E 64M --ec 4+2 -E -1 --ec 3+2 $td ||
		error "setstripe complex EC layout for $td failed"

	# A subdir inherits the complex default, and a file created in it must
	# inherit the full layout (every component/mirror and its EC params).
	mkdir $subdir || error "mkdir for $subdir failed"
	touch $tf || error "touch for $tf failed"

	verify_mirror_count $tf 4
	verify_comp_count $tf 8

	# Get component IDs (4 mirrors x 2 PFL comps, in mirror order)
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Mirror 1 data components
	verify_comp_extent $tf ${ids[0]} 0 134217728
	verify_comp_extent $tf ${ids[1]} 134217728 EOF

	# Mirror 1 parity components (2+1, then 4+2)
	verify_comp_parity $tf ${ids[2]}
	verify_ec_stripe_count $tf ${ids[2]} 2 1
	verify_comp_extent $tf ${ids[2]} 0 134217728

	verify_comp_parity $tf ${ids[3]}
	verify_ec_stripe_count $tf ${ids[3]} 4 2
	verify_comp_extent $tf ${ids[3]} 134217728 EOF

	# Mirror 2 data components
	verify_comp_extent $tf ${ids[4]} 0 67108864
	verify_comp_extent $tf ${ids[5]} 67108864 EOF

	# Mirror 2 parity components (4+2, then 3+2)
	verify_comp_parity $tf ${ids[6]}
	verify_ec_stripe_count $tf ${ids[6]} 4 2
	verify_comp_extent $tf ${ids[6]} 0 67108864

	verify_comp_parity $tf ${ids[7]}
	verify_ec_stripe_count $tf ${ids[7]} 3 2
	verify_comp_extent $tf ${ids[7]} 67108864 EOF
}
run_test 1l "default EC layout inherited through nested dirs (complex)"

test_2a() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	# Single data mirror with EC using -N
	$LFS setstripe -N -E 128M -E -1 --ec 8+2 $tf ||
		error "setstripe failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 4

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify data mirror (mirror 1)
	verify_comp_extent $tf ${ids[0]} 0 134217728
	verify_comp_extent $tf ${ids[1]} 134217728 EOF

	# Verify parity mirror (mirror 2)
	verify_comp_parity $tf ${ids[2]}
	verify_ec_stripe_count $tf ${ids[2]} 8 2
	verify_comp_extent $tf ${ids[2]} 0 134217728

	verify_comp_parity $tf ${ids[3]}
	verify_ec_stripe_count $tf ${ids[3]} 8 2
	verify_comp_extent $tf ${ids[3]} 134217728 EOF
}
run_test 2a "setstripe with -N and EC"

test_2b() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	# Two data mirrors, one with EC
	$LFS setstripe -N -E 128M -E -1 --ec 8+2 \
			-N -E 256M -E -1 \
			$tf || error "setstripe failed"

	verify_mirror_count $tf 3
	verify_comp_count $tf 6

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify mirror 1 (data with EC)
	verify_comp_extent $tf ${ids[0]} 0 134217728
	verify_comp_extent $tf ${ids[1]} 134217728 EOF

	# Verify mirror 2 (parity for mirror 1)
	verify_comp_parity $tf ${ids[2]}
	verify_ec_stripe_count $tf ${ids[2]} 8 2
	verify_comp_extent $tf ${ids[2]} 0 134217728

	verify_comp_parity $tf ${ids[3]}
	verify_ec_stripe_count $tf ${ids[3]} 8 2
	verify_comp_extent $tf ${ids[3]} 134217728 EOF

	# Verify mirror 3 (data without EC)
	verify_comp_extent $tf ${ids[4]} 0 268435456
	verify_comp_extent $tf ${ids[5]} 268435456 EOF
}
run_test 2b "setstripe with -N: two data mirrors, one with EC"

test_2c() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	# Two data mirrors, both with EC
	$LFS setstripe -N -E 128M --ec 4+2 -E -1 --ec 8+2 \
			-N -E 256M --ec 6+1 -E -1 \
			$tf || error "setstripe failed"

	verify_mirror_count $tf 4
	verify_comp_count $tf 8

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify mirror 1 (data)
	verify_comp_extent $tf ${ids[0]} 0 134217728
	verify_comp_extent $tf ${ids[1]} 134217728 EOF

	# Verify mirror 2 (parity for mirror 1)
	verify_comp_parity $tf ${ids[2]}
	verify_ec_stripe_count $tf ${ids[2]} 4 2
	verify_comp_extent $tf ${ids[2]} 0 134217728

	verify_comp_parity $tf ${ids[3]}
	verify_ec_stripe_count $tf ${ids[3]} 8 2
	verify_comp_extent $tf ${ids[3]} 134217728 EOF

	# Verify mirror 3 (data)
	verify_comp_extent $tf ${ids[4]} 0 268435456
	verify_comp_extent $tf ${ids[5]} 268435456 EOF

	# Verify mirror 4 (parity for mirror 3)
	verify_comp_parity $tf ${ids[6]}
	verify_ec_stripe_count $tf ${ids[6]} 6 1
	verify_comp_extent $tf ${ids[6]} 0 268435456

	verify_comp_parity $tf ${ids[7]}
	verify_ec_stripe_count $tf ${ids[7]} 6 1
	verify_comp_extent $tf ${ids[7]} 268435456 EOF
}
run_test 2c "setstripe with -N: two data mirrors, both with EC"

test_2d() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	# Multiple identical EC mirrors using -N count
	$LFS setstripe -N2 -E 128M -E -1 --ec 8+2 $tf ||
		error "setstripe failed"

	verify_mirror_count $tf 4
	verify_comp_count $tf 8

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify mirror 1 (data)
	verify_comp_extent $tf ${ids[0]} 0 134217728
	verify_comp_extent $tf ${ids[1]} 134217728 EOF

	# Verify mirror 2 (parity for mirror 1)
	verify_comp_parity $tf ${ids[2]}
	verify_ec_stripe_count $tf ${ids[2]} 8 2
	verify_comp_extent $tf ${ids[2]} 0 134217728

	verify_comp_parity $tf ${ids[3]}
	verify_ec_stripe_count $tf ${ids[3]} 8 2
	verify_comp_extent $tf ${ids[3]} 134217728 EOF

	# Verify mirror 3 (data, identical to mirror 1)
	verify_comp_extent $tf ${ids[4]} 0 134217728
	verify_comp_extent $tf ${ids[5]} 134217728 EOF

	# Verify mirror 4 (parity for mirror 3, identical to mirror 2)
	verify_comp_parity $tf ${ids[6]}
	verify_ec_stripe_count $tf ${ids[6]} 8 2
	verify_comp_extent $tf ${ids[6]} 0 134217728

	verify_comp_parity $tf ${ids[7]}
	verify_ec_stripe_count $tf ${ids[7]} 8 2
	verify_comp_extent $tf ${ids[7]} 134217728 EOF
}
run_test 2d "setstripe with -N2: multiple identical EC mirrors"

test_2e() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	# Mixed: regular mirror + EC mirror
	$LFS setstripe -N -E -1 -c 4 \
			-N -E -1 -c 8 --ec 8+2 \
			$tf || error "setstripe failed"

	verify_mirror_count $tf 3
	verify_comp_count $tf 3

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify mirror 1 (data, no EC)
	verify_comp_extent $tf ${ids[0]} 0 EOF

	# Verify mirror 2 (data with EC)
	verify_comp_extent $tf ${ids[1]} 0 EOF

	# Verify mirror 3 (parity for mirror 2)
	verify_comp_parity $tf ${ids[2]}
	verify_ec_stripe_count $tf ${ids[2]} 8 2
	verify_comp_extent $tf ${ids[2]} 0 EOF
}
run_test 2e "setstripe with -N: mixed regular and EC mirrors"

test_2f() {
	enable_ec
	local td=$DIR/$tdir
	local ids

	stack_trap "rm -rf $td"

	test_mkdir $td

	# Set default EC layout on directory using mirror mode (-N)
	$LFS setstripe -N -E 128M -E -1 --ec 8+2 $td ||
		error "setstripe with -N on directory failed"

	# Create file in directory - should inherit EC layout
	touch $td/$tfile || error "touch failed"

	verify_mirror_count $td/$tfile 2
	verify_comp_count $td/$tfile 4

	# Get component IDs
	ids=($($LFS getstripe $td/$tfile | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify data mirror (mirror 1)
	verify_comp_extent $td/$tfile ${ids[0]} 0 134217728
	verify_comp_extent $td/$tfile ${ids[1]} 134217728 EOF

	# Verify parity mirror (mirror 2)
	verify_comp_parity $td/$tfile ${ids[2]}
	verify_ec_stripe_count $td/$tfile ${ids[2]} 8 2
	verify_comp_extent $td/$tfile ${ids[2]} 0 134217728

	verify_comp_parity $td/$tfile ${ids[3]}
	verify_ec_stripe_count $td/$tfile ${ids[3]} 8 2
	verify_comp_extent $td/$tfile ${ids[3]} 134217728 EOF

	# Test with multiple data mirrors + EC
	$LFS setstripe -d $td || error "delete default layout failed"
	$LFS setstripe -N -E -1 -c 4 -N -E -1 --ec 8+2 $td ||
		error "setstripe with mixed mirrors on directory failed"

	touch $td/${tfile}.mixed || error "touch mixed file failed"

	# Should have 3 mirrors: 1 regular data + 1 EC data + 1 EC parity
	verify_mirror_count $td/${tfile}.mixed 3
	verify_comp_count $td/${tfile}.mixed 3
}
run_test 2f "default EC layout on directory using mirror mode"

# Test 3: lfs mirror create with EC
test_3a() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	$LFS mirror create -N -E 128M -E -1 --ec 8+2 $tf ||
		error "failed to create mirrored file with EC"

	verify_mirror_count $tf 2
	verify_comp_count $tf 4

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify parity components (components 2 and 3 in parity mirror)
	verify_comp_parity $tf ${ids[2]}
	verify_comp_parity $tf ${ids[3]}

	# Verify EC stripe counts
	verify_ec_stripe_count $tf ${ids[2]} 8 2
	verify_ec_stripe_count $tf ${ids[3]} 8 2
}
run_test 3a "lfs mirror create with single EC mirror"

test_3b() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	$LFS mirror create -N2 -E 128M -E -1 --ec 8+2 $tf ||
		error "failed to create mirrored file with -N2"

	verify_mirror_count $tf 4
	verify_comp_count $tf 8

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify parity components (components 2,3 and 6,7)
	verify_comp_parity $tf ${ids[2]}
	verify_comp_parity $tf ${ids[3]}
	verify_comp_parity $tf ${ids[6]}
	verify_comp_parity $tf ${ids[7]}

	# Verify EC stripe counts
	verify_ec_stripe_count $tf ${ids[2]} 8 2
	verify_ec_stripe_count $tf ${ids[3]} 8 2
	verify_ec_stripe_count $tf ${ids[6]} 8 2
	verify_ec_stripe_count $tf ${ids[7]} 8 2
}
run_test 3b "lfs mirror create with -N2 and EC"

test_3c() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	$LFS mirror create -N -E 128M -E -1 --ec 8+2 \
			   -N -E 256M -E -1 $tf ||
		error "failed to create mixed mirror file"

	verify_mirror_count $tf 3
	verify_comp_count $tf 6

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify parity components (only first mirror has EC, components 2,3)
	verify_comp_parity $tf ${ids[2]}
	verify_comp_parity $tf ${ids[3]}

	# Verify EC stripe counts
	verify_ec_stripe_count $tf ${ids[2]} 8 2
	verify_ec_stripe_count $tf ${ids[3]} 8 2
}
run_test 3c "lfs mirror create with mixed EC and regular mirrors"

test_3d() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	$LFS mirror create -N -E 128M -E -1 --ec 8+2 \
			   -N -E 256M -E -1 --ec 4+1 $tf ||
		error "failed to create file with different EC configs"

	verify_mirror_count $tf 4
	verify_comp_count $tf 8

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify parity components (components 2,3 and 6,7)
	verify_comp_parity $tf ${ids[2]}
	verify_comp_parity $tf ${ids[3]}
	verify_comp_parity $tf ${ids[6]}
	verify_comp_parity $tf ${ids[7]}

	# Verify EC stripe counts
	verify_ec_stripe_count $tf ${ids[2]} 8 2
	verify_ec_stripe_count $tf ${ids[3]} 8 2
	verify_ec_stripe_count $tf ${ids[6]} 4 1
	verify_ec_stripe_count $tf ${ids[7]} 4 1
}
run_test 3d "lfs mirror create with different EC configs per mirror"

test_3e() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	$LFS mirror create -N -E 64M --ec 4+1 -E 128M --ec 8+2 -E -1 $tf ||
		error "failed to create file with multiple EC specs"

	verify_mirror_count $tf 2
	verify_comp_count $tf 6

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify parity components (components 3,4,5)
	verify_comp_parity $tf ${ids[3]}
	verify_comp_parity $tf ${ids[4]}
	verify_comp_parity $tf ${ids[5]}

	# Verify EC stripe counts
	verify_ec_stripe_count $tf ${ids[3]} 4 1
	verify_ec_stripe_count $tf ${ids[4]} 8 2
	verify_ec_stripe_count $tf ${ids[5]} 8 2
}
run_test 3e "lfs mirror create with multiple EC specs in one mirror"

test_3f() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	# Test EC inheritance with "holes" - components without --ec
	# First component uses 4+2, second has no --ec (should inherit 4+2
	# from first), third uses 8+2
	$LFS mirror create -N -E 128M --ec 4+2 -E 512M -E -1 --ec 8+2 $tf ||
		error "failed to create file with EC holes"

	verify_mirror_count $tf 2
	verify_comp_count $tf 6

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify parity components (components 3,4,5)
	verify_comp_parity $tf ${ids[3]}
	verify_comp_parity $tf ${ids[4]}
	verify_comp_parity $tf ${ids[5]}

	# Verify EC stripe counts
	# First component: 4+2 (explicitly set)
	verify_ec_stripe_count $tf ${ids[3]} 4 2
	# Second component: 4+2 (inherited from first --ec 4+2)
	verify_ec_stripe_count $tf ${ids[4]} 4 2
	# Third component: 8+2 (explicitly set)
	verify_ec_stripe_count $tf ${ids[5]} 8 2
}
run_test 3f "EC inheritance with holes"

test_3g() {
	(( OSTCOUNT >= 6 )) || skip_env "needs >= 6 OSTs"
	enable_ec

	local tf=$DIR/$tfile
	local pool_name=$TESTNAME
	local ids
	local data_pool
	local ec_pool

	stack_trap "rm -f $tf"

	# create a new OST pool and add all OSTs to it
	create_pool $FSNAME.$pool_name ||
		error "create OST pool $pool_name failed"

	pool_add_targets $pool_name 0 $((OSTCOUNT - 1)) ||
		error "add OSTs into pool $pool_name failed"

	# Create EC file with data + parity mirror. The data mirror is
	# explicitly on pool; parity mirror should inherit it.
	$LFS setstripe -N -E -1 -c 4 -p $pool_name --ec 4+2 $tf ||
		error "create EC file with pool failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 2

	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' |
	     tr '\n' ' '))

	# Component 0: data mirror; Component 1: parity mirror
	data_pool=$($LFS getstripe -I${ids[0]} -p $tf)
	ec_pool=$($LFS getstripe -I${ids[1]} -p $tf)

	[[ $data_pool = $pool_name ]] ||
		error "data mirror pool $data_pool != $pool_name"

	[[ $ec_pool = $pool_name ]] ||
		error "EC mirror pool $ec_pool != $pool_name"

	destroy_test_pools
	}
run_test 3g "EC parity mirror inherits pool from data mirror"

# Test 4: Invalid EC parameters
test_4a() {
	enable_ec
	local tf=$DIR/$tfile

	# Invalid: parity > data
	$LFS setstripe -E -1 --ec 4+5 $tf 2>&1 |
		grep -qi "parity.*must be less than or equal" ||
		error "should reject EC with parity > data"

	! [[ -f $tf ]] || error "file should not have been created"
}
run_test 4a "reject invalid EC parameters: parity > data"

test_4b() {
	enable_ec
	local tf=$DIR/$tfile

	# Invalid: data stripe count = 1 (must be at least 2)
	$LFS setstripe -E -1 --ec 1+0 $tf 2>&1 |
		grep -qi "invalid data stripe count" ||
		error "should reject EC with data count < 2"

	# Invalid: data stripe count = 0
	$LFS setstripe -E -1 --ec 0+1 $tf 2>&1 |
		grep -qi "invalid data stripe count" ||
		error "should reject EC with data count = 0"

	! [[ -f $tf ]] || error "file should not have been created"
}
run_test 4b "reject invalid EC parameters: data count < 2"

test_4c() {
	enable_ec
	local tf=$DIR/$tfile

	# Invalid: parity = 0
	$LFS setstripe -E -1 --ec 4+0 $tf 2>&1 |
		grep -qi "invalid parity stripe count" ||
		error "should reject EC with parity = 0"

	! [[ -f $tf ]] || error "file should not have been created"
}
run_test 4c "reject invalid EC parameters: parity = 0"

test_4d() {
	enable_ec
	local tf=$DIR/$tfile

	# Invalid: malformed EC specification (missing +)
	$LFS setstripe -E -1 --ec 42 $tf 2>&1 | grep -qi "invalid.*format" ||
		error "should reject malformed EC spec (missing +)"

	# Invalid: malformed EC specification (non-numeric)
	$LFS setstripe -E -1 --ec abc+def $tf 2>&1 | grep -qi "invalid" ||
		error "should reject malformed EC spec (non-numeric)"

	! [[ -f $tf ]] || error "file should not have been created"
}
run_test 4d "reject malformed EC specifications"

test_4e() {
	enable_ec
	local tf=$DIR/$tfile
	local ids

	stack_trap "rm -f $tf"

	# --ec without -E should automatically create [0,EOF] component
	$LFS setstripe --ec 4+2 $tf || error "setstripe failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 2

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify data component has [0,EOF] extent
	verify_comp_extent $tf ${ids[0]} 0 EOF

	# Verify parity component
	verify_comp_parity $tf ${ids[1]}
	verify_ec_stripe_count $tf ${ids[1]} 4 2
	verify_comp_extent $tf ${ids[1]} 0 EOF
}
run_test 4e "auto-create [0,EOF] component when --ec without -E"

test_4f() {
	enable_ec
	local tf=$DIR/$tfile

	# Invalid: --ec with --foreign
	$LFS setstripe -E -1 --ec 4+2 --foreign=none --xattr=test $tf 2>&1 |
		grep -qi "only.*options are valid with --foreign" ||
		error "should reject --ec with --foreign"

	! [[ -f $tf ]] || error "file should not have been created"
}
run_test 4f "reject --ec with incompatible options (--foreign)"

test_4g() {
	enable_ec
	local tf=$DIR/$tfile
	local output

	stack_trap "rm -f $tf"

	# Test 1: Reject data stripe count > 32 without --ec-expert
	output=$($LFS setstripe -E -1 -c 64 --ec 64+2 $tf 2>&1) ||
		true
	echo "$output" | grep -qi "exceeds supported limit.*--ec-expert" ||
		error "should reject data count > 32 without --ec-expert"
	[[ -f $tf ]] && error "file should not have been created"

	# Test 2: Reject parity stripe count > 4 without --ec-expert
	output=$($LFS setstripe -E -1 -c 16 --ec 16+5 $tf 2>&1) ||
		true
	echo "$output" | grep -qi "exceeds supported limit.*--ec-expert" ||
		error "should reject parity count > 4 without --ec-expert"
	[[ -f $tf ]] && error "file should not have been created"

	# Test 3: Allow data stripe count > 32 with --ec-expert
	# (may fail for other reasons, but not due to limit check)
	output=$($LFS setstripe -E -1 -c 64 --ec-expert 64+2 $tf 2>&1) ||
		true
	echo "$output" | grep -qi "exceeds supported limit" &&
		error "should not reject data count > 32 with --ec-expert"

	rm -f $tf

	# Test 4: Allow parity stripe count > 4 with --ec-expert
	# (may fail for other reasons, but not due to limit check)
	output=$($LFS setstripe -E -1 -c 16 --ec-expert 16+5 $tf 2>&1) ||
		true
	echo "$output" | grep -qi "exceeds supported limit" &&
		error "should not reject parity count > 4 with --ec-expert"
	return 0
}
run_test 4g "reject EC stripe counts exceeding limits without --ec-expert"

test_5a() {
	enable_ec

	local ids
	local tf=$DIR/$tfile

	stack_trap "rm -f $tf $TMP/$tfile.mirror $TMP/$tfile.data"

	# Create EC file with data mirror + parity mirror
	# Layout: N1 = data, N2 = data, N3 = parity (4+1)
	$LFS setstripe -N -E 1M -c 1 -E -1 -c 1 \
		-N -E 1M -c 4 --ec 4+1 -E -1 -c 4 --ec 4+1 $tf ||
		error "create EC file failed"

	# Get component IDs
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify EC parameters on parity components (mirror 3, N3)
	# Components: 0,1=N1(data), 2,3=N2(data), 4,5=N3(parity)
	verify_comp_parity $tf ${ids[4]}
	verify_ec_stripe_count $tf ${ids[4]} 4 1
	verify_comp_parity $tf ${ids[5]}
	verify_ec_stripe_count $tf ${ids[5]} 4 1

	# Test 1: Read from empty parity mirror - should return immediately
	# with 0 bytes (not hang)
	$LFS mirror read -N3 -o $TMP/$tfile.mirror $tf ||
		error "mirror read from empty parity failed"
	local empty_size=$(stat -c %s $TMP/$tfile.mirror)
	(( empty_size == 0 )) ||
		error "empty parity read should return 0 bytes, got $empty_size"
	rm -f $TMP/$tfile.mirror

	# Test 2: Write data, resync, then read from parity
	cp /etc/passwd $tf || error "failed to write data"

	# Resync to compute parity
	$LFS mirror resync $tf || error "mirror resync failed"

	# Read from parity mirror (N3) - should work after resync
	$LFS mirror read -N3 -o $TMP/$tfile.mirror $tf ||
		error "mirror read from parity failed"

	# Parity data won't match /etc/passwd directly (it's computed parity),
	# but verify we got non-empty data of reasonable size
	local parity_size=$(stat -c %s $TMP/$tfile.mirror)
	(( parity_size > 0 )) || error "parity mirror read returned empty"

	# Also verify data mirror can be read correctly
	$LFS mirror read -N1 -o $TMP/$tfile.data $tf ||
		error "mirror read from data failed"
	cmp $TMP/$tfile.data /etc/passwd ||
		error "data mirror read mismatch"
}
run_test 5a "EC mirror read/write commands"

test_5b() {
	enable_ec

	local tf=$DIR/$tfile
	local flags
	local ids

	stack_trap "rm -f $tf"

	# Create EC file with lfs setstripe
	# Layout: Mirror 1 has 3 data components [0,128M], [128M,1G], [1G,EOF]
	#         Mirror 2 has 3 EC components at same extents
	$LFS setstripe -E 128M -E 1G -E -1 --ec 4+2 $tf ||
		error "setstripe failed"

	# Verify file starts in RDONLY state
	verify_flr_state $tf "ro"

	# Get component IDs for both mirrors
	ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' | tr '\n' ' '))
	echo "Component IDs: ${ids[@]}"

	# Verify EC parameters on parity components (mirror 2)
	verify_comp_parity $tf ${ids[3]}
	verify_ec_stripe_count $tf ${ids[3]} 4 2
	verify_comp_parity $tf ${ids[4]}
	verify_ec_stripe_count $tf ${ids[4]} 4 2
	verify_comp_parity $tf ${ids[5]}
	verify_ec_stripe_count $tf ${ids[5]} 4 2

	# Write to first component (0-1M, within [0,128M] extent)
	dd if=/dev/zero of=$tf conv=notrunc bs=1M count=1 ||
		error "write to first component failed"

	# Verify file is now in WRITE_PENDING state
	verify_flr_state $tf "wp"

	# Verify the corresponding EC component in mirror 2 is stale
	# Mirror 1 components are ids[0], ids[1], ids[2]
	# Mirror 2 components are ids[3], ids[4], ids[5]
	# After writing to first component, ids[3] should be stale
	verify_comp_stale $tf ${ids[3]}

	# Verify other EC components are NOT stale (component-level granularity)
	flags=$($LFS getstripe -I${ids[4]} $tf |
			awk '/lcme_flags:/ { print $2 }')
	[[ ! $flags =~ "stale" ]] ||
		error "component ${ids[4]} should not be stale after write to first component"

	flags=$($LFS getstripe -I${ids[5]} $tf |
			awk '/lcme_flags:/ { print $2 }')
	[[ ! $flags =~ "stale" ]] ||
		error "component ${ids[5]} should not be stale after write to first component"

	# Resync the file
	$LFS mirror resync $tf || error "mirror resync failed"

	# Verify file is back to RDONLY state
	verify_flr_state $tf "ro"

	# Verify component is no longer stale
	local flags=$($LFS getstripe -I${ids[3]} $tf |
			awk '/lcme_flags:/ { print $2 }')
	[[ ! $flags =~ "stale" ]] ||
		error "component ${ids[3]} still stale after resync: $flags"

	echo "** Write to second component **"

	# Write to second component (at offset 256M, within [128M,1G] extent)
	dd if=/dev/zero of=$tf conv=notrunc bs=1M count=1 seek=256 ||
		error "write to second component failed"

	# Verify file is in WRITE_PENDING state
	verify_flr_state $tf "wp"

	# Verify the corresponding EC component in mirror 2 is stale
	# ids[4] is the second EC component
	verify_comp_stale $tf ${ids[4]}

	# Verify other EC components are NOT stale (component-level granularity)
	flags=$($LFS getstripe -I${ids[3]} $tf |
			awk '/lcme_flags:/ { print $2 }')
	[[ ! $flags =~ "stale" ]] ||
		error "component ${ids[3]} should not be stale after write to second component"

	flags=$($LFS getstripe -I${ids[5]} $tf |
			awk '/lcme_flags:/ { print $2 }')
	[[ ! $flags =~ "stale" ]] ||
		error "component ${ids[5]} should not be stale after write to second component"

	# Resync the file
	$LFS mirror resync $tf || error "mirror resync failed"

	# Verify file is back to RDONLY state
	verify_flr_state $tf "ro"

	# Verify component is no longer stale
	flags=$($LFS getstripe -I${ids[4]} $tf |
			awk '/lcme_flags:/ { print $2 }')
	[[ ! $flags =~ "stale" ]] ||
		error "component ${ids[4]} still stale after resync: $flags"
}
run_test 5b "EC FLR state transitions with writes to different components"

test_6a() {
	enable_ec

	local tf=$DIR/$tfile

	stack_trap "rm -f $tf"

	# Create EC file with two components in each mirror
	# Mirror 1 (data): [0, 1M], [1M, EOF]
	# Mirror 2 (parity): [0, 1M], [1M, EOF] with EC 4+2
	$LFS setstripe -E 1M -c 2 -E -1 -c 4 --ec 4+2 $tf ||
		error "create EC file failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 4

	# Get component IDs
	local ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' |
			tr '\n' ' '))
	echo "Component IDs: ${ids[@]}"

	local data_comp1=${ids[0]}
	local data_comp2=${ids[1]}
	local parity_comp1=${ids[2]}
	local parity_comp2=${ids[3]}

	# Verify EC parameters on all parity components
	verify_comp_parity $tf $parity_comp1
	verify_ec_stripe_count $tf $parity_comp1 2 2
	verify_comp_parity $tf $parity_comp2
	verify_ec_stripe_count $tf $parity_comp2 4 2

	# Test 1: Cannot set prefer on parity component
	$LFS setstripe --comp-set -I $parity_comp1 --comp-flags=prefer \
		$tf 2>&1 |
		grep -q "cannot set prefer flags on parity component" ||
		error "should not allow prefer on parity component"

	# Test 2: Cannot set prefrd on parity component
	$LFS setstripe --comp-set -I $parity_comp2 --comp-flags=prefrd \
		$tf 2>&1 |
		grep -q "cannot set prefer flags on parity component" ||
		error "should not allow prefrd on parity component"

	# Test 3: Cannot set prefwr on parity component
	$LFS setstripe --comp-set -I $parity_comp1 --comp-flags=prefwr \
		$tf 2>&1 |
		grep -q "cannot set prefer flags on parity component" ||
		error "should not allow prefwr on parity component"

	# Test 4: Can set prefwr on data component in data mirror
	$LFS setstripe --comp-set -I $data_comp1 --comp-flags=prefwr \
		$tf || error "should allow prefwr on data component"
	$LFS getstripe -I$data_comp1 $tf | grep -q "prefwr" ||
		error "prefwr flag not set on data component"

	# Test 5: Can set prefer on data component in data mirror
	$LFS setstripe --comp-set -I $data_comp2 --comp-flags=prefer \
		$tf || error "should allow prefer on data component"
	$LFS getstripe -I$data_comp2 $tf | grep -q "prefer" ||
		error "prefer flag not set on data component"

	# Test 6: Can set prefrd on data component in data mirror
	$LFS setstripe --comp-set -I $data_comp1 --comp-flags=^prefwr \
		$tf || error "failed to clear prefwr"
	$LFS setstripe --comp-set -I $data_comp1 --comp-flags=prefrd \
		$tf || error "should allow prefrd on data component"
	$LFS getstripe -I$data_comp1 $tf | grep -q "prefrd" ||
		error "prefrd flag not set on data component"
}
run_test 6a "Block setting prefer flags on parity components"

test_6b() {
	enable_ec

	local tf=$DIR/$tfile

	stack_trap "rm -f $tf"

	# Create EC file: data mirror + parity mirror
	$LFS setstripe -E -1 -c 4 --ec 4+2 $tf ||
		error "create EC file failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 2

	# Find data and parity component IDs via the parity flag rather than
	# relying on mirror id ordering.
	local data_comp_id=$($LFS getstripe $tf |
		awk '/lcme_id:/ {id=$2}
		     /lcme_flags:/ && !/parity/ {print id; exit}')
	local parity_comp_id=$($LFS getstripe $tf |
		awk '/lcme_id:/ {id=$2}
		     /lcme_flags:.*parity/ {print id; exit}')

	echo "Data component: $data_comp_id, Parity component: $parity_comp_id"

	[[ -n "$data_comp_id" ]] || error "could not find data component ID"
	[[ -n "$parity_comp_id" ]] ||
		error "could not find parity component ID"

	# Verify EC parameters on parity component
	verify_comp_parity $tf $parity_comp_id
	verify_ec_stripe_count $tf $parity_comp_id 4 2

	# Write to file - should select data mirror as primary
	dd if=/dev/urandom of=$tf bs=1M count=2 ||
		error "write to EC file failed"

	# After write, data mirror should be non-stale (init),
	# parity mirror should be stale
	$LFS getstripe -I$data_comp_id $tf | grep -q "init" ||
		error "data mirror should be init after write"

	# Verify parity mirror is stale
	$LFS getstripe -I$parity_comp_id $tf | grep -q "stale" ||
		error "parity mirror should be stale after write"

	# Snapshot the data before resync so we can verify integrity across it
	local sum1=$(md5sum $tf | awk '{print $1}')

	# Resync to update parity mirror
	$LFS mirror resync $tf || error "mirror resync failed"

	# After resync, both mirrors should be init (non-stale)
	$LFS getstripe -I$data_comp_id $tf | grep -q "init" ||
		error "data mirror should be init after resync"
	$LFS getstripe -I$parity_comp_id $tf | grep -q "init" ||
		error "parity mirror should be init after resync"

	local sum2=$(md5sum $tf | awk '{print $1}')
	[[ "$sum1" == "$sum2" ]] ||
		error "data changed after resync: $sum1 vs $sum2"
}
run_test 6b "EC write selects data mirror, not parity mirror"

test_6c() {
	enable_ec

	(( OSTCOUNT >= 4 )) || skip "needs >= 4 OSTs"

	local tf=$DIR/$tfile

	stack_trap "rm -f $tf"

	# Create file with 2 data mirrors + 1 EC mirror (data + parity)
	# Mirror 1 (data): stripe on OST0,1
	# Mirror 2 (data): stripe on OST2,3
	# Mirror 3 (data): EC data component with 2 stripes
	# Mirror 4 (parity): EC parity component with 2+2
	# EC 2+2 keeps the OST requirement at >= 4 to match the skip above.
	$LFS setstripe -N -E -1 -c 2 -o 0,1 \
		-N -E -1 -c 2 -o 2,3 \
		-N -E -1 -c 2 --ec 2+2 $tf ||
		error "create multi-mirror EC file failed"

	verify_mirror_count $tf 4
	verify_comp_count $tf 4

	# Get component IDs for EC verification
	local ids=($($LFS getstripe $tf | awk '/lcme_id/{print $2}' |
			tr '\n' ' '))
	echo "Component IDs: ${ids[@]}"

	# Verify EC parameters on EC parity component (mirror 4)
	verify_comp_parity $tf ${ids[3]}
	verify_ec_stripe_count $tf ${ids[3]} 2 2

	# Write to file - should select one of the data mirrors
	dd if=/dev/urandom of=$tf bs=1M count=2 ||
		error "write to EC file failed"
	$LFS getstripe $tf

	# Get mirror IDs
	local mirror_ids=($($LFS getstripe $tf |
		awk '/lcme_mirror_id:/ {print $2}' | sort -u))
	echo "Mirror IDs: ${mirror_ids[@]}"

	# Count non-stale mirrors (should be 1 - the primary data mirror)
	local non_stale_count=0
	local stale_count=0
	local parity_mirror_id=""

	for mirror_id in "${mirror_ids[@]}"; do
		# Get first component of this mirror
		# lcme_id comes before lcme_mirror_id, so we need to save it
		local comp_id=$($LFS getstripe $tf |
			awk -v mid="$mirror_id" \
			'/lcme_id:/ {id=$2} \
			/lcme_mirror_id:/ {if ($2 == mid) {print id; exit}}')

		if $LFS getstripe -I$comp_id $tf | grep -q "stale"; then
			((stale_count++))
		else
			((non_stale_count++))
		fi

		# Check if this is the parity mirror
		if $LFS getstripe -I$comp_id $tf | grep -q "parity"; then
			parity_mirror_id=$mirror_id
		fi
	done

	echo "Non-stale mirrors: $non_stale_count, Stale mirrors: $stale_count"
	echo "Parity mirror ID: $parity_mirror_id"

	# Should have exactly 1 non-stale mirror (the selected data mirror)
	(( non_stale_count == 1 )) ||
		error "expected 1 non-stale mirror, got $non_stale_count"

	# Should have 3 stale mirrors (2 other data mirrors + parity mirror)
	(( stale_count == 3 )) ||
		error "expected 3 stale mirrors, got $stale_count"

	# Parity mirror should be stale
	[[ -n "$parity_mirror_id" ]] ||
		error "could not find parity mirror"
	local parity_comp_id=$($LFS getstripe $tf |
		awk -v mid="$parity_mirror_id" \
		'/lcme_id:/ {id=$2} \
		/lcme_mirror_id:/ {if ($2 == mid) {print id; exit}}')
	$LFS getstripe -I$parity_comp_id $tf | grep -q "stale" ||
		error "parity mirror should be stale after write"
}
run_test 6c "EC with multiple data mirrors - parity never selected"

test_6d() {
	enable_ec

	local tf=$DIR/$tfile

	stack_trap "rm -f $tf"

	# Create EC file: data mirror + parity mirror
	$LFS setstripe -E -1 -c 4 --ec 4+2 $tf ||
		error "create EC file failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 2

	# Find data and parity component IDs via the parity flag rather than
	# relying on mirror id ordering.
	local data_comp_id=$($LFS getstripe $tf |
		awk '/lcme_id:/ {id=$2}
		     /lcme_flags:/ && !/parity/ {print id; exit}')
	local parity_comp_id=$($LFS getstripe $tf |
		awk '/lcme_id:/ {id=$2}
		     /lcme_flags:.*parity/ {print id; exit}')

	echo "Data component: $data_comp_id, Parity component: $parity_comp_id"

	[[ -n "$data_comp_id" ]] || error "could not find data component ID"
	[[ -n "$parity_comp_id" ]] ||
		error "could not find parity component ID"

	# Verify EC parameters on parity component
	verify_comp_parity $tf $parity_comp_id
	verify_ec_stripe_count $tf $parity_comp_id 4 2

	# Write and resync to get both mirrors in sync
	dd if=/dev/urandom of=$tf bs=1M count=2 ||
		error "write to EC file failed"
	$LFS mirror resync $tf || error "mirror resync failed"

	# Verify both mirrors are in sync (init, not stale)
	$LFS getstripe -I$data_comp_id $tf | grep -q "init" ||
		error "data mirror should be init after resync"
	$LFS getstripe -I$parity_comp_id $tf | grep -q "init" ||
		error "parity mirror should be init after resync"

	# Manually mark parity mirror stale
	$LFS setstripe --comp-set -I $parity_comp_id --comp-flags=stale \
		$tf || error "failed to mark parity mirror stale"

	# Verify parity mirror is now stale
	$LFS getstripe -I$parity_comp_id $tf | grep -q "stale" ||
		error "parity mirror should be stale after manual marking"

	# Verify data mirror is still init
	$LFS getstripe -I$data_comp_id $tf | grep -q "init" ||
		error "data mirror should still be init"

	# Resync should restore parity mirror from data mirror
	$LFS mirror resync $tf || error "mirror resync failed"

	# After resync, both should be init again
	$LFS getstripe -I$data_comp_id $tf | grep -q "init" ||
		error "data mirror should be init after resync"
	$LFS getstripe -I$parity_comp_id $tf | grep -q "init" ||
		error "parity mirror should be init after resync"

	# Verify data integrity
	local sum1=$(md5sum $tf | awk '{print $1}')
	$LFS mirror resync $tf || error "final resync failed"
	local sum2=$(md5sum $tf | awk '{print $1}')
	[[ "$sum1" == "$sum2" ]] ||
		error "data changed after resync: $sum1 vs $sum2"
}
run_test 6d "Manually mark parity mirror stale and resync"

test_6e() {
	enable_ec

	local tf=$DIR/$tfile

	stack_trap "rm -f $tf"

	# Create EC file: data mirror + parity mirror
	$LFS setstripe -E -1 -c 4 --ec 4+2 $tf ||
		error "create EC file failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 2

	# Find data and parity component IDs via the parity flag rather than
	# relying on mirror id ordering.
	local data_comp_id=$($LFS getstripe $tf |
		awk '/lcme_id:/ {id=$2}
		     /lcme_flags:/ && !/parity/ {print id; exit}')
	local parity_comp_id=$($LFS getstripe $tf |
		awk '/lcme_id:/ {id=$2}
		     /lcme_flags:.*parity/ {print id; exit}')

	echo "Data component: $data_comp_id, Parity component: $parity_comp_id"

	[[ -n "$data_comp_id" ]] || error "could not find data component ID"
	[[ -n "$parity_comp_id" ]] ||
		error "could not find parity component ID"

	# Verify EC parameters on parity component
	verify_comp_parity $tf $parity_comp_id
	verify_ec_stripe_count $tf $parity_comp_id 4 2

	# Write and resync to get both mirrors in sync
	dd if=/dev/urandom of=$tf bs=1M count=2 ||
		error "write to EC file failed"
	$LFS mirror resync $tf || error "mirror resync failed"

	# Verify both mirrors are in sync (init, not stale)
	$LFS getstripe -I$data_comp_id $tf | grep -q "init" ||
		error "data mirror should be init after resync"
	$LFS getstripe -I$parity_comp_id $tf | grep -q "init" ||
		error "parity mirror should be init after resync"

	# Manually mark data mirror stale, leaving only parity mirror valid
	$LFS setstripe --comp-set -I $data_comp_id --comp-flags=stale $tf ||
		error "failed to mark data mirror stale"

	# Verify data mirror is now stale
	$LFS getstripe -I$data_comp_id $tf | grep -q "stale" ||
		error "data mirror should be stale after manual marking"

	# Verify parity mirror is still init (non-stale)
	$LFS getstripe -I$parity_comp_id $tf | grep -q "init" ||
		error "parity mirror should still be init"

	# Attempt to write with only parity mirror non-stale should fail
	# with ENODATA (61 - No data available) because parity mirrors
	# cannot be selected as write targets
	dd if=/dev/urandom of=$tf bs=1M count=1 conv=notrunc 2>&1 |
		grep -q "No data available" ||
		error "write should fail with ENODATA when only parity" \
			"mirror is valid"

	# Verify file state unchanged - data mirror still stale
	$LFS getstripe -I$data_comp_id $tf | grep -q "stale" ||
		error "data mirror should still be stale after failed write"
}
run_test 6e "Write fails when only parity mirror is non-stale"

test_7a() {
	enable_ec

	local tf=$DIR/$tfile

	stack_trap "rm -f $tf"

	# Create EC file: data mirror + parity mirror
	$LFS setstripe -E -1 -c 4 --ec 4+2 $tf ||
		error "create EC file failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 2

	# Get component IDs - data mirror is mirror_id 1, parity is mirror_id 2
	local data_comp_id=$($LFS getstripe $tf |
		awk '/lcme_id:/ {id=$2} /lcme_mirror_id:.*1$/ {print id; exit}')
	local parity_comp_id=$($LFS getstripe $tf |
		awk '/lcme_id:/ {id=$2} /lcme_mirror_id:.*2$/ {print id; exit}')

	echo "Data component: $data_comp_id, Parity component: $parity_comp_id"

	[[ -n "$data_comp_id" ]] || error "could not find data component ID"
	[[ -n "$parity_comp_id" ]] ||
		error "could not find parity component ID"

	# Verify EC parameters on parity component
	verify_comp_parity $tf $parity_comp_id
	verify_ec_stripe_count $tf $parity_comp_id 4 2

	# Write initial data and resync
	dd if=/dev/urandom of=$tf bs=1M count=2 ||
		error "write to EC file failed"
	$LFS mirror resync $tf || error "mirror resync failed"

	# Verify both mirrors are in sync (init, not stale)
	$LFS getstripe -I$data_comp_id $tf | grep -q "init" ||
		error "data mirror should be init after resync"
	$LFS getstripe -I$parity_comp_id $tf | grep -q "init" ||
		error "parity mirror should be init after resync"

	# Save checksum of parity mirror
	local sum0=$($LFS mirror read -N2 $tf | dd bs=1M count=2 | md5sum)
	echo "Initial parity mirror checksum: $sum0"

	# Set nosync flag on parity mirror to snapshot it
	$LFS setstripe --comp-set -I $parity_comp_id --comp-flags=nosync \
		$tf || error "failed to set nosync on parity mirror"

	# Verify nosync flag is set
	$LFS getstripe -I$parity_comp_id $tf | grep -q "nosync" ||
		error "nosync flag not set on parity mirror"

	# Write new data - this should update data mirror but not parity
	dd if=/dev/urandom of=$tf bs=1M count=2 ||
		error "second write to EC file failed"

	# Resync - parity mirror should remain stale due to nosync flag
	$LFS mirror resync $tf || error "mirror resync failed"

	# Verify parity mirror is still stale and has nosync flag
	$LFS getstripe -I$parity_comp_id $tf | grep -q "stale" ||
		error "parity mirror should be stale after resync with nosync"
	$LFS getstripe -I$parity_comp_id $tf | grep -q "nosync" ||
		error "nosync flag should still be set on parity mirror"

	# Verify parity mirror content hasn't changed
	local sum1=$($LFS mirror read -N1 $tf | md5sum)
	local sum2=$($LFS mirror read -N2 $tf | dd bs=1M count=2 | md5sum)

	echo "Data mirror checksum: $sum1"
	echo "Parity mirror checksum: $sum2"
	[[ $sum0 = $sum2 ]] ||
		error "parity mirror changed: $sum0 vs $sum2"

	# Clear nosync flag and resync to update parity mirror
	$LFS setstripe --comp-set -I $parity_comp_id \
		--comp-flags=^nosync $tf ||
		error "failed to clear nosync on parity mirror"

	$LFS mirror resync $tf || error "final resync failed"

	# After clearing nosync and resyncing, both mirrors should be init
	$LFS getstripe -I$data_comp_id $tf | grep -q "init" ||
		error "data mirror should be init after final resync"
	$LFS getstripe -I$parity_comp_id $tf | grep -q "init" ||
		error "parity mirror should be init after final resync"

	# Verify parity mirror is now updated
	sum1=$($LFS mirror read -N1 $tf | md5sum)
	sum2=$($LFS mirror read -N2 $tf | md5sum)
	echo "Final data mirror checksum: $sum1"
	echo "Final parity mirror checksum: $sum2"
}
run_test 7a "nosync flag on parity mirror prevents resync updates"

test_7b() {
	(( OSTCOUNT >= 6 )) || skip_env "needs >= 6 OSTs"

	enable_ec

	local tf=$DIR/$tfile
	local ids

	# Create file with EC
	$LFS setstripe -E -1 -c 4 --ec 4+2 $tf ||
		error "setstripe failed"

	verify_mirror_count $tf 2

	# Get mirror IDs
	ids=($($LFS getstripe $tf | awk '/lcme_mirror_id:/ {print $2}' |
		sort -u | tr '\n' ' '))

	(( ${#ids[@]} == 2 )) ||
		error "expected 2 mirrors, got ${#ids[@]}"

	# Determine which mirror is parity (has LCME_FL_PARITY flag)
	local data_mirror_id=${ids[0]}
	local parity_mirror_id=${ids[1]}
	local flags=$($LFS getstripe --mirror-id=${ids[1]} $tf |
		      awk '/lcme_flags:/ {print $2; exit}')

	if [[ ! $flags =~ "parity" ]]; then
		# First mirror is parity, swap them
		data_mirror_id=${ids[1]}
		parity_mirror_id=${ids[0]}
	fi

	# Verify we identified them correctly
	flags=$($LFS getstripe --mirror-id=$parity_mirror_id $tf |
		awk '/lcme_flags:/ {print $2; exit}')
	[[ $flags =~ "parity" ]] ||
		error "mirror $parity_mirror_id should be parity mirror"

	# Try to split parity mirror without -d (should fail)
	$LFS mirror split --mirror-id $parity_mirror_id $tf 2>&1 |
		grep -q "parity mirror" ||
		error "split without -d should fail for parity mirror"

	# Verify mirror count unchanged
	verify_mirror_count $tf 2

	# Split parity mirror with -d (should succeed)
	$LFS mirror split --mirror-id $parity_mirror_id -d $tf ||
		error "split -d should succeed for parity mirror"

	# Verify only data mirror remains
	verify_mirror_count $tf 1

	# Verify remaining mirror is the data mirror
	local remaining_id=$($LFS getstripe $tf |
			     awk '/lcme_mirror_id:/ {print $2; exit}')
	(( remaining_id == data_mirror_id )) ||
		error "remaining mirror should be data mirror"
}
run_test 7b "mirror split -d works for parity mirrors, regular split fails"

test_7c() {
	(( OSTCOUNT >= 6 )) || skip_env "needs >= 6 OSTs"

	enable_ec

	local tf=$DIR/$tfile
	local ids

	# Create file with multiple components and EC
	$LFS setstripe -E 128M --ec 4+2 -E -1 --ec 4+2 $tf ||
		error "setstripe failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 4

	# Get mirror IDs
	ids=($($LFS getstripe $tf | awk '/lcme_mirror_id:/ {print $2}' |
		sort -u | tr '\n' ' '))

	(( ${#ids[@]} == 2 )) ||
		error "expected 2 mirrors, got ${#ids[@]}"

	# Find parity mirror
	local parity_mirror_id
	local flags=$($LFS getstripe --mirror-id=${ids[1]} $tf |
		      awk '/lcme_flags:/ {print $2; exit}')

	if [[ $flags =~ "parity" ]]; then
		parity_mirror_id=${ids[1]}
	else
		parity_mirror_id=${ids[0]}
	fi

	# Split parity mirror with -d
	$LFS mirror split --mirror-id $parity_mirror_id -d $tf ||
		error "split -d failed for parity mirror"

	# Verify only data mirror remains with 2 components
	verify_mirror_count $tf 1
	verify_comp_count $tf 2
}
run_test 7c "mirror split -d works for multi-component parity mirrors"

test_7d() {
	(( OSTCOUNT >= 6 )) || skip_env "needs >= 6 OSTs"

	enable_ec

	local tf=$DIR/$tfile
	local ids

	# Create file with regular mirror + EC mirror
	$LFS mirror create -N -E -1 -c 2 \
			   -N -E -1 -c 4 --ec 4+2 $tf ||
		error "mirror create failed"

	verify_mirror_count $tf 3

	# Get all mirror IDs
	ids=($($LFS getstripe $tf | awk '/lcme_mirror_id:/ {print $2}' |
		sort -u | tr '\n' ' '))

	(( ${#ids[@]} == 3 )) ||
		error "expected 3 mirrors, got ${#ids[@]}"

	# Find parity mirror (should be the last one)
	local parity_mirror_id
	for id in "${ids[@]}"; do
		local flags=$($LFS getstripe --mirror-id=$id $tf |
			      awk '/lcme_flags:/ {print $2; exit}')
		if [[ $flags =~ "parity" ]]; then
			parity_mirror_id=$id
			break
		fi
	done

	[[ -n $parity_mirror_id ]] ||
		error "could not find parity mirror"

	# Split parity mirror with -d
	$LFS mirror split --mirror-id $parity_mirror_id -d $tf ||
		error "split -d failed for parity mirror"

	# Verify 2 mirrors remain (2 data mirrors)
	verify_mirror_count $tf 2
}
run_test 7d "mirror split -d works with mixed regular and EC mirrors"

test_10() {
	local tf=${DIR}/${tdir}/$tfile

	# The number of parity stripes in the EC mirror must be equal to or
	# less than the number of data stripes in the same EC mirror.
	(( OSTCOUNT >= 5 )) || skip_env "needs >= 5 OSTs"
	enable_ec

	test_mkdir $DIR/$tdir

	# Test that creating an ec 2+3 mirror fails (parity > data)
	$LFS setstripe -E -1 -S 4M -c 4 --ec 2+3 $tf >/dev/null &&
		error "setstripe --ec 2+3 succeeded when it shouldn't"

	return 0
}
run_test 10 "cannot create overly large ec mirrors"

test_11() {
	# The number of parity stripes in the EC mirror can be equal to
	# the number of data stripes in the same EC mirror.
	(( OSTCOUNT >= 4 )) || skip_env "needs >= 4 OSTs"
	enable_ec

	test_mkdir $DIR/$tdir
	# Test that creating ec 2+2 mirror works
	$LFS setstripe -E -1 -S 4M -c 4 --ec 2+2 $DIR/$tdir/$tfile ||
	    error "setstripe --ec 2+2 failed"
}
run_test 11 "can create --ec 2+2"

test_12a() {
	local tf=${DIR}/${tdir}/$tfile
	local tf_data=${DIR}/${tdir}/${tfile}.data
	local tf_ec=${DIR}/${tdir}/${tfile}.ec

	# test resyncing a stale ec mirror
	(( OSTCOUNT >= 4 )) || skip_env "needs >= 4 OSTs"
	enable_ec


	test_mkdir $DIR/$tdir

	$LFS setstripe -E -1 -S 4M -c 4 --ec 2+2 $tf ||
	    error "setstripe --ec 2+2 failed"

	# Write the first 3 stripes with \001, \002 and \003
	tr "\000" "\001" < /dev/zero | dd bs=64k count=64          \
		iflag=fullblock of=$tf 2>/dev/null
	tr "\000" "\002" < /dev/zero | dd bs=64k count=64 seek=64  \
		iflag=fullblock of=$tf 2>/dev/null
	tr "\000" "\003" < /dev/zero | dd bs=64k count=64 seek=128 \
		iflag=fullblock of=$tf 2>/dev/null

	# Expected file content:
	#  od -t x1 -A x $tf
	#  000000 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
	#  *
	#  400000 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
	#  *
	#  800000 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03
	#  *
	#  c00000

	echo "fa9fe1782aee74e978e806fb6a0e7a4a1c83610f $tf" \
	    | sha1sum -c - || error "wrong content in $tf after write"

	# resync the ec mirror:
	$LFS mirror resync $tf || error "failed to resync ec mirror"

	# Verify the mirror is no longer stale
	$LFS getstripe $tf | grep lcme_flags | grep stale &&
	    error "after resyncing $tf, it still contains stale component"

	# verify the file content did not change after updating ec mirror
	echo "fa9fe1782aee74e978e806fb6a0e7a4a1c83610f $tf" |
		sha1sum -c - || error "wrong content in $tf after resync"

	# Verify the data mirror is still correct
	rm -f $tf_data
	$LFS mirror read --mirror-id 1 -o $tf_data $tf
	echo "fa9fe1782aee74e978e806fb6a0e7a4a1c83610f $tf_data" |
		sha1sum -c - || error "wrong content in data mirror"

	# Expected content of the ec mirror (2 parity stripes for 2 data stripes):
	# Parity stripe 0: XOR of data stripe 0 (0x01) and data stripe 2 (0x03) = 0x9a
	# Parity stripe 1: XOR of data stripe 1 (0x02) and data stripe 3 (0x00) = 0xfa
	#  000000 9a 9a 9a 9a 9a 9a 9a 9a 9a 9a 9a 9a 9a 9a 9a 9a
	#  *
	#  400000 fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
	#  *
	#  800000

	# Verify we have expected content in the ec mirror
	rm -f $tf_ec
	$LFS mirror read --mirror-id 2 -o $tf_ec $tf
	echo "aca75f6b8ae9a16aa64f8ca38160bfa39bd2a785 $tf_ec" |
	    sha1sum -c - || error "wrong content in ec mirror"
}
run_test 12a "resync stale parities"

test_12b() {
	# ZFS lseek does not reliably report holes for dirty data
	# (dmu_offset_next() returns EBUSY and the txg_wait_synced() retry in
	# osd_lseek() is not enough), so the resync computes and writes ec
	# parities across the whole sparse region instead of skipping the holes,
	# eventually exhausting OST space and failing with ENOSPC.
	#
	# This seems to be related to LU-14217 where the fix was not enough. It
	# needs to be investigated separately.
	[[ "$ost1_FSTYPE" != "zfs" ]] ||
		skip "LU-14217: lseek holes unreliable on ZFS"

	# test resyncing a stale ec mirror with huge sparse file
	(( OSTCOUNT >= 3 )) || skip_env "needs >= 3 OSTs"
	enable_ec

	test_mkdir $DIR/$tdir

	$LFS setstripe -E -1 -S 4M -c 3 --ec 2+1 $DIR/$tdir/$tfile ||
		error "setstripe --ec 2+1 failed"

	# gauge I/O performance so resync can be bounded relative to it later
	local start=$SECONDS
	# Write the first 3 stripes with \001, \002 and \003
	tr "\000" "\001" < /dev/zero | dd bs=64k count=64          \
		iflag=fullblock of=$DIR/$tdir/$tfile 2>/dev/null
	# Write some data at ~4 TiB
	tr "\000" "\002" < /dev/zero | dd bs=64k count=64          \
		seek=64000000  \
		iflag=fullblock of=$DIR/$tdir/$tfile 2>/dev/null
	local elapsed=$((SECONDS - start))

	# Truncate to 10 TiB to have a hole at the end
	$TRUNCATE $DIR/$tdir/$tfile 10995116277760 ||
		error "failed to truncate file to 10 TiB"

	# resync the ec mirror. With holes skipped, this should only touch two
	# data regions and should finish well within 10x of the initial write
	# time above. A regression would then hit the timeout instead of running
	# for hours
	local timeout=$((elapsed * 10))
	echo "mirror resync timeout set to $timeout"
	timeout -k 10 $timeout $LFS mirror resync $DIR/$tdir/$tfile ||
		error "failed to resync ec mirror"

	# Verify the mirror is no longer stale
	$LFS getstripe $DIR/$tdir/$tfile | grep lcme_flags | grep stale &&
		error "$DIR/$tdir/$tfile still stale after resync" || true
}
run_test 12b "resync huge sparse file (10 TiB)"

test_12c() {
	# refer to 12b for the test case description
	[[ "$ost1_FSTYPE" != "zfs" ]] ||
		skip "LU-14217: lseek holes unreliable on ZFS"

	(( OSTCOUNT >= 6 )) || skip_env "needs >= 6 OSTs"
	enable_ec

	local tf=${DIR}/${tdir}/$tfile
	local stripe_size=$((4 * 1024 * 1024))
	local stripe_set_size=$((4 * stripe_size))
	local ec_set_size=$((2 * stripe_size))

	test_mkdir $DIR/$tdir

	$LFS setstripe -E -1 -S 4M -c 4 --ec 4+2 $tf ||
		error "setstripe --ec 4+2 failed"

	# Write three full stripe sets, then resync and verify.
	dd if=/dev/urandom of=$tf bs=$stripe_set_size count=3 2>/dev/null ||
		error "failed to write three stripe sets"

	$LFS mirror resync $tf || error "failed to resync ec mirror"
	$LFS getstripe $tf | grep lcme_flags | grep stale &&
		error "stale component after initial resync"
	$LFS mirror verify $tf ||
		error "mirror verify failed after initial resync"

	# Punch out the middle stripe set on the data mirror.
	local hole_off=$stripe_set_size
	local fallocate_out=$(fallocate -p --offset $hole_off \
		-l $stripe_set_size $tf 2>&1) ||
		skip_eopnotsupp "$fallocate_out|fallocate punch failed"

	(( $($LUSTRE/tests/lseek_test -l 0 $tf) == hole_off )) ||
		error "expected data hole at $hole_off after punch"
	(( $($LUSTRE/tests/lseek_test -d $hole_off $tf) == $((2 * stripe_set_size)) )) ||
		error "expected next data at $((2 * stripe_set_size)) after punch"

	$LFS getstripe $tf | grep lcme_flags | grep stale ||
		error "parity should be stale after punching data"

	$LFS mirror resync $tf || error "failed to resync after punch"
	$LFS getstripe $tf | grep lcme_flags | grep stale &&
		error "stale component after resync following punch"

	#  lcme_id:             131074
	#  lcme_mirror_id:      2
	#  lcme_flags:          init,parity
	local parity_mirror_id=$($LFS getstripe $tf |
		grep -B1 "lcme_flags.*parity" |
		grep "lcme_mirror_id" | awk '{print $2}')
	# it's expected that only the parity for the second stripe set is absent
	check_parity_read $tf $parity_mirror_id 0 "no"
	check_parity_read $tf $parity_mirror_id $ec_set_size "yes"
	check_parity_read $tf $parity_mirror_id $((2 * ec_set_size)) "no"

	$LFS mirror verify $tf ||
		error "mirror verify failed after punch resync"
}
run_test 12c "resync skips holed stripe sets"

test_13() {
	local tf=${DIR}/${tdir}/$tfile
	local tf_data=${DIR}/${tdir}/${tfile}.data
	local tf_ec=${DIR}/${tdir}/${tfile}.ec

	(( OSTCOUNT >= 4 )) || skip_env "needs >= 4 OSTs"
	enable_ec

	test_mkdir $DIR/$tdir

	$LFS setstripe -E -1 -S 4M -c 1 --ec 3+1 $tf ||
	    error "setstripe -c 1 --ec 3+1 failed"

	# Write the first 3 stripes with \001, \002 and \003
	tr "\000" "\001" < /dev/zero | dd bs=64k iflag=fullblock count=64          \
		of=$tf 2>/dev/null
	tr "\000" "\002" < /dev/zero | dd bs=64k iflag=fullblock count=64 seek=64  \
		of=$tf 2>/dev/null
	tr "\000" "\003" < /dev/zero | dd bs=64k iflag=fullblock count=64 seek=128 \
		of=$tf 2>/dev/null

	# resync the ec mirror:
	$LFS mirror resync $tf || error "failed to resync ec mirror"

	# Expected content of both data and "ec" mirror
	#  od -t x1 -A x $tf
	#  000000 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01 01
	#  *
	#  400000 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02 02
	#  *
	#  800000 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03 03
	#  *
	#  c00000
	rm -f $tf_data
	stack_trap "rm -f $tf_data"
	$LFS mirror read --mirror-id 1 -o $tf_data $tf
	echo "fa9fe1782aee74e978e806fb6a0e7a4a1c83610f $tf_data" |
	    sha1sum -c - || error "wrong content in data mirror"

	rm -f $tf_ec
	stack_trap "rm -f $tf_ec"
	$LFS mirror read --mirror-id 2 -o $tf_ec $tf
	echo "fa9fe1782aee74e978e806fb6a0e7a4a1c83610f $tf_ec" |
	    sha1sum -c - || error "wrong content in ec mirror"
}
run_test 13 "parity of single stripe data is just a copy"

test_20() {
	local tf=${DIR}/${tdir}/$tfile

	(( OSTCOUNT >= 4 )) || skip_env "needs >= 4 OSTs"
	enable_ec

	test_mkdir $DIR/$tdir

	# 4M stripe size
	$LFS setstripe -E -1 -S 4M -c 1 --ec 3+1 $tf ||
	    error "setstripe -c 1 -S 4M --ec 3+1 failed"

	$LFS getstripe $tf | grep lmm_stripe_size | grep -v 4194304 2>/dev/null &&
		error "Stripe size mismatch for -S 4M"
	rm -f $tf

	# 1M stripe size
	$LFS setstripe -E -1 -S 1M -c 1 --ec 3+1 $tf ||
	    error "setstripe -c 1 -S 1M --ec 3+1 failed"

	$LFS getstripe $tf |
	    grep lmm_stripe_size | grep -v 1048576 2>/dev/null &&
	    error "Stripe size mismatch for -S 1M"
	rm -f $tf

	# 64k stripe size
	$LFS setstripe -E -1 -S 64k -c 1 --ec 3+1 $tf ||
	    error "setstripe -c 1 -S 64k --ec 3+1 failed"

	$LFS getstripe $tf | grep lmm_stripe_size | grep -v 65536 2>/dev/null &&
		error "Stripe size mismatch for -S 64k"

	return 0
}
run_test 20 "test that stripe size of parity mirror is set correctly"

# Test 21: lfs migrate with EC layouts
test_21a() {
	(( OSTCOUNT >= 3 )) || skip_env "needs >= 3 OSTs"
	enable_ec

	local tf=$DIR/$tdir/$tfile

	test_mkdir $DIR/$tdir
	stack_trap "rm -rf $DIR/$tdir"

	# Create a plain (non-EC) file with data
	$LFS setstripe -c 1 $tf || error "setstripe failed"
	dd if=/dev/urandom of=$tf bs=1M count=5 ||
		error "write to plain file failed"
	local old_chksum=$(md5sum $tf | awk '{print $1}')

	# Migrate plain file to EC layout
	$LFS migrate -E -1 -c 2 --ec 2+1 $tf ||
		error "migrate (plain -> EC) failed"

	# Verify EC layout after migration
	verify_mirror_count $tf 2
	verify_comp_count $tf 2

	# Get component IDs
	local ids=($($LFS getstripe $tf |
		awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify parity component layout
	verify_comp_parity $tf ${ids[1]}
	verify_ec_stripe_count $tf ${ids[1]} 2 1

	# Verify data integrity
	local new_chksum=$(md5sum $tf | awk '{print $1}')
	[[ "$old_chksum" == "$new_chksum" ]] ||
		error "data changed after migrate: $old_chksum != $new_chksum"

	# For multi-stripe EC (2+1), parity is XOR across stripes, so
	# parity content must differ from the data mirror content.
	local data_sum=$($LFS mirror read -N1 $tf | md5sum)
	local parity_sum=$($LFS mirror read -N2 $tf | md5sum)
	[[ "$data_sum" != "$parity_sum" ]] ||
		error "parity identical to data - migrate didn't compute parity"
}
run_test 21a "migrate plain file to EC layout"

test_21b() {
	(( OSTCOUNT >= 3 )) || skip_env "needs >= 3 OSTs"
	enable_ec

	local tf=$DIR/$tdir/$tfile

	test_mkdir $DIR/$tdir
	stack_trap "rm -rf $DIR/$tdir"

	# Create EC file with data
	$LFS setstripe -E -1 -c 2 --ec 2+1 $tf ||
		error "setstripe EC failed"
	dd if=/dev/urandom of=$tf bs=1M count=5 ||
		error "write to EC file failed"
	$LFS mirror resync $tf || error "resync failed"

	local old_chksum=$(md5sum $tf | awk '{print $1}')

	# Verify EC layout before migration
	verify_mirror_count $tf 2
	verify_comp_count $tf 2

	# Migrate EC file to plain layout
	$LFS migrate -c 2 $tf || error "migrate (EC -> plain) failed"

	# Verify no parity components remain
	$LFS getstripe $tf | grep -q "parity" &&
		error "parity components should not exist after migrate to plain"

	# Verify no composite/FLR layout remains
	$LFS getstripe $tf | grep -q "lcm_mirror_count" &&
		error "should not have mirror layout after migrate to plain"

	# Verify data integrity
	local new_chksum=$(md5sum $tf | awk '{print $1}')
	[[ "$old_chksum" == "$new_chksum" ]] ||
		error "data changed after migrate: $old_chksum != $new_chksum"
}
run_test 21b "migrate EC file to plain layout"

test_21c() {
	(( OSTCOUNT >= 4 )) || skip_env "needs >= 4 OSTs"
	enable_ec

	local tf=$DIR/$tdir/$tfile

	test_mkdir $DIR/$tdir
	stack_trap "rm -rf $DIR/$tdir"

	# Create EC file with 2+1 and write data
	$LFS setstripe -E -1 -c 2 --ec 2+1 $tf ||
		error "setstripe EC 2+1 failed"
	dd if=/dev/urandom of=$tf bs=1M count=5 ||
		error "write to EC file failed"
	$LFS mirror resync $tf || error "initial resync failed"

	local old_chksum=$(md5sum $tf | awk '{print $1}')

	# Migrate EC 2+1 to EC 3+1
	$LFS migrate -E -1 -c 3 --ec 3+1 $tf ||
		error "migrate (EC 2+1 -> EC 3+1) failed"

	# Verify new EC layout
	verify_mirror_count $tf 2
	verify_comp_count $tf 2

	local ids=($($LFS getstripe $tf |
		awk '/lcme_id/{print $2}' | tr '\n' ' '))

	verify_comp_parity $tf ${ids[1]}
	verify_ec_stripe_count $tf ${ids[1]} 3 1

	# Verify data integrity
	local new_chksum=$(md5sum $tf | awk '{print $1}')
	[[ "$old_chksum" == "$new_chksum" ]] ||
		error "data changed after migrate: $old_chksum != $new_chksum"

	# For multi-stripe EC, parity is XOR across stripes and must
	# differ from data content.
	local data_sum=$($LFS mirror read -N1 $tf | md5sum)
	local parity_sum=$($LFS mirror read -N2 $tf | md5sum)
	[[ "$data_sum" != "$parity_sum" ]] ||
		error "parity identical to data after EC config change"
}
run_test 21c "migrate between different EC configurations"

test_21d() {
	(( OSTCOUNT >= 3 )) || skip_env "needs >= 3 OSTs"
	enable_ec

	local tf=$DIR/$tdir/$tfile

	test_mkdir $DIR/$tdir
	stack_trap "rm -rf $DIR/$tdir"

	# Create multi-component PFL file with data
	$LFS setstripe -E 1M -c 1 -E -1 -c 2 $tf ||
		error "setstripe PFL failed"
	dd if=/dev/urandom of=$tf bs=1M count=5 ||
		error "write to PFL file failed"

	local old_chksum=$(md5sum $tf | awk '{print $1}')

	# Migrate PFL to EC with multiple components
	$LFS migrate -E 1M -c 2 -E -1 -c 2 --ec 2+1 $tf ||
		error "migrate (PFL -> EC) failed"

	# Verify EC layout
	verify_mirror_count $tf 2
	verify_comp_count $tf 4

	local ids=($($LFS getstripe $tf |
		awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Verify parity components
	verify_comp_parity $tf ${ids[2]}
	verify_comp_parity $tf ${ids[3]}
	verify_ec_stripe_count $tf ${ids[2]} 2 1
	verify_ec_stripe_count $tf ${ids[3]} 2 1

	# Verify data integrity
	local new_chksum=$(md5sum $tf | awk '{print $1}')
	[[ "$old_chksum" == "$new_chksum" ]] ||
		error "data changed after migrate: $old_chksum != $new_chksum"
}
run_test 21d "migrate PFL file to EC layout"

# Test 22: mirror split with EC
test_22a() {
	(( OSTCOUNT >= 6 )) || skip_env "needs >= 6 OSTs"
	enable_ec

	local tf=$DIR/$tdir/$tfile

	test_mkdir $DIR/$tdir
	stack_trap "rm -rf $DIR/$tdir"

	# Create file with plain data mirror + EC data mirror + parity mirror
	$LFS setstripe -N -E -1 -c 2 \
		-N -E -1 -c 2 --ec 2+2 $tf ||
		error "setstripe failed"
	dd if=/dev/urandom of=$tf bs=1M count=3 ||
		error "write failed"
	$LFS mirror resync $tf || error "resync failed"

	local old_chksum=$(md5sum $tf | awk '{print $1}')

	# Should have 3 mirrors: data, data+EC, parity
	verify_mirror_count $tf 3

	# Split off mirror 1 (plain data mirror) - should work fine
	$LFS mirror split --mirror-id 1 -d $tf ||
		error "split plain data mirror failed"

	# Should now have 2 mirrors: data+EC, parity
	verify_mirror_count $tf 2

	# Parity component should still exist
	$LFS getstripe $tf | grep -q "parity" ||
		error "parity component lost after split"

	# Verify data integrity
	local new_chksum=$(md5sum $tf | awk '{print $1}')
	[[ "$old_chksum" == "$new_chksum" ]] ||
		error "data changed after split: $old_chksum != $new_chksum"

	# Verify remaining EC layout is functional: write new data,
	# resync, and confirm parity is recomputed
	dd if=/dev/urandom of=$tf bs=1M count=1 conv=notrunc ||
		error "write after split failed"
	verify_flr_state $tf "wp"
	$LFS mirror resync $tf || error "resync after split+write failed"
	verify_flr_state $tf "ro"

	# For multi-stripe EC (2+2), parity is computed across stripes and
	# must differ from data content, confirming EC is functional.
	local data_sum=$($LFS mirror read -N1 $tf | md5sum)
	local parity_sum=$($LFS mirror read -N2 $tf | md5sum)
	[[ "$data_sum" != "$parity_sum" ]] ||
		error "parity identical to data after split - EC not functional"
}
run_test 22a "mirror split removes plain mirror, keeps EC"

# Test 23: truncate and fallocate with EC
test_23a() {
	enable_ec

	local tf=$DIR/$tdir/$tfile
	local ids

	test_mkdir $DIR/$tdir
	stack_trap "rm -rf $DIR/$tdir"

	# Create EC file and write data
	$LFS setstripe -E -1 -c 4 --ec 4+2 $tf ||
		error "setstripe failed"
	dd if=/dev/urandom of=$tf bs=1M count=5 ||
		error "write failed"
	$LFS mirror resync $tf || error "resync failed"
	verify_flr_state $tf "ro"

	# Save parity mirror checksum before truncate
	local parity_sum_before=$($LFS mirror read -N2 $tf | md5sum)

	# Truncate to smaller size
	$TRUNCATE $tf 2097152 || error "truncate to 2M failed"

	# After truncate, file should be in write-pending state
	verify_flr_state $tf "wp"

	# Get component IDs
	ids=($($LFS getstripe $tf |
		awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Parity component should be stale after truncate
	verify_comp_stale $tf ${ids[1]}

	# Resync - this should recompute parity for truncated data
	$LFS mirror resync $tf || error "resync after truncate failed"
	verify_flr_state $tf "ro"

	local size=$(stat -c%s $tf)
	(( size == 2097152 )) ||
		error "file size wrong after truncate: $size != 2097152"

	# Verify parity was actually recomputed (checksum should change
	# because parity of 2M of data differs from parity of 5M of data)
	local parity_sum_after=$($LFS mirror read -N2 $tf | md5sum)
	[[ "$parity_sum_before" != "$parity_sum_after" ]] ||
		error "parity mirror unchanged after truncate+resync"

	# Verify data mirror still has correct content
	local data_sum=$($LFS mirror read -N1 $tf | md5sum)
	local file_sum=$(md5sum < $tf)
	[[ "$data_sum" == "$file_sum" ]] ||
		error "data mirror content mismatch after truncate"
}
run_test 23a "truncate EC file to smaller size"

test_23b() {
	enable_ec

	local tf=$DIR/$tdir/$tfile
	local ids

	test_mkdir $DIR/$tdir
	stack_trap "rm -rf $DIR/$tdir"

	# Create EC file and write data
	$LFS setstripe -E -1 -c 4 --ec 4+2 $tf ||
		error "setstripe failed"
	dd if=/dev/urandom of=$tf bs=1M count=2 ||
		error "write failed"
	$LFS mirror resync $tf || error "resync failed"
	verify_flr_state $tf "ro"

	# Truncate to larger size (extends with zeros)
	$TRUNCATE $tf 10485760 || error "truncate to 10M failed"

	# After truncate, file should be in write-pending state
	verify_flr_state $tf "wp"

	# Get component IDs
	ids=($($LFS getstripe $tf |
		awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# Parity component should be stale after truncate
	verify_comp_stale $tf ${ids[1]}

	# Resync - parity must be recomputed for the extended data
	$LFS mirror resync $tf || error "resync after truncate failed"
	verify_flr_state $tf "ro"

	local size=$(stat -c%s $tf)
	(( size == 10485760 )) ||
		error "file size wrong after truncate: $size != 10485760"

	# Verify data mirror content matches file read
	local data_sum=$($LFS mirror read -N1 $tf | md5sum)
	local file_sum=$(md5sum < $tf)
	[[ "$data_sum" == "$file_sum" ]] ||
		error "data mirror content mismatch after truncate"

	# Verify parity covers the extended region: write non-zero data
	# into the previously zero-extended area and confirm parity is
	# recomputed (a zero-only extension produces zero parity, which
	# is indistinguishable from unwritten parity storage, so we need
	# real data to meaningfully exercise parity over the new range).
	local parity_sum_before=$($LFS mirror read -N2 $tf | md5sum)
	dd if=/dev/urandom of=$tf bs=1M count=4 seek=4 conv=notrunc ||
		error "write into extended region failed"
	$LFS mirror resync $tf || error "resync after extended write failed"
	verify_flr_state $tf "ro"
	local parity_sum_after=$($LFS mirror read -N2 $tf | md5sum)
	[[ "$parity_sum_before" != "$parity_sum_after" ]] ||
		error "parity mirror unchanged after write into extended region"
}
run_test 23b "truncate EC file to larger size"

test_23c() {
	enable_ec
	check_set_fallocate_or_skip

	local tf=$DIR/$tdir/$tfile
	local ids

	test_mkdir $DIR/$tdir
	stack_trap "rm -rf $DIR/$tdir"

	# Create EC file
	$LFS setstripe -E -1 -c 4 --ec 4+2 $tf ||
		error "setstripe failed"

	# fallocate space - preallocate without writing data
	fallocate -l 5M $tf || error "fallocate failed"

	# After fallocate, parity should be stale
	verify_flr_state $tf "wp"
	ids=($($LFS getstripe $tf |
		awk '/lcme_id/{print $2}' | tr '\n' ' '))
	verify_comp_stale $tf ${ids[1]}

	# Resync parity for the preallocated region
	$LFS mirror resync $tf || error "resync after fallocate failed"
	verify_flr_state $tf "ro"

	# Now write actual data into the preallocated region
	dd if=/dev/urandom of=$tf bs=1M count=5 conv=notrunc ||
		error "write to preallocated file failed"

	# Parity must go stale again from the actual write
	verify_flr_state $tf "wp"
	verify_comp_stale $tf ${ids[1]}

	# Resync parity with real data
	$LFS mirror resync $tf || error "resync after write failed"
	verify_flr_state $tf "ro"

	# Verify data mirror matches file content (proving EC resync
	# correctly processed the preallocated+written region)
	local file_sum=$(md5sum < $tf)
	local data_sum=$($LFS mirror read -N1 $tf | md5sum)
	[[ "$file_sum" == "$data_sum" ]] ||
		error "data mirror content mismatch after fallocate+write"

	# For multi-stripe EC (4+2), parity is XOR across stripes and
	# must differ from data content.
	local parity_sum=$($LFS mirror read -N2 $tf | md5sum)
	[[ "$data_sum" != "$parity_sum" ]] ||
		error "parity mirror identical to data - not real parity"
}
run_test 23c "fallocate on EC file"

# Test 24: comp-add/del with EC
test_24a() {
	enable_ec

	local tf=$DIR/$tdir/$tfile

	test_mkdir $DIR/$tdir
	stack_trap "rm -rf $DIR/$tdir"

	# Create EC file
	$LFS setstripe -E -1 -c 4 --ec 4+2 $tf ||
		error "setstripe failed"

	# Attempting --comp-add with --ec should be rejected
	local output
	output=$($LFS setstripe --comp-add -E -1 --ec 4+2 $tf 2>&1) &&
		error "should reject --ec with --comp-add"
	echo "$output" | grep -q "cannot be used with" ||
		error "unexpected error message: $output"

	return 0
}
run_test 24a "reject --ec with --comp-add"

test_24b() {
	enable_ec

	local tf=$DIR/$tdir/$tfile

	test_mkdir $DIR/$tdir
	stack_trap "rm -rf $DIR/$tdir"

	# Create EC file with two components per mirror
	# Mirror 1 (data): [0, 1M], [1M, EOF]
	# Mirror 2 (parity): [0, 1M], [1M, EOF]
	$LFS setstripe -E 1M -c 2 -E -1 -c 4 --ec 4+2 $tf ||
		error "setstripe failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 4

	local ids=($($LFS getstripe $tf |
		awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# ids[0], ids[1] = data mirror components
	# ids[2], ids[3] = parity mirror components

	# Attempting to delete data component that is protected by
	# parity should fail
	$LFS setstripe --comp-del -I ${ids[1]} $tf 2>&1 &&
		error "should not allow deleting data comp protected by parity"

	# Verify layout unchanged
	verify_comp_count $tf 4

	# Attempting to delete parity component directly should also fail
	$LFS setstripe --comp-del -I ${ids[2]} $tf 2>&1 &&
		error "should not allow deleting parity component directly"

	verify_comp_count $tf 4

	return 0
}
run_test 24b "reject deleting data component protected by parity"

# Test 25: lfs find with EC attributes
test_25a() {
	enable_ec

	local td=$DIR/$tdir

	test_mkdir $td
	stack_trap "rm -rf $td"

	# Create EC file
	$LFS setstripe -E -1 -c 4 --ec 4+2 $td/ec_file ||
		error "setstripe EC failed"

	# Create plain file
	$LFS setstripe -c 1 $td/plain_file ||
		error "setstripe plain failed"

	# Create mirrored file without EC
	$LFS mirror create -N2 -E -1 -c 2 $td/mirror_file ||
		error "mirror create failed"

	# Find files with parity components
	local found=$($LFS find $td --component-flags parity | wc -l)
	(( found == 1 )) ||
		error "expected 1 file with parity, found $found"

	# The found file should be the EC file
	$LFS find $td --component-flags parity | grep -q "ec_file" ||
		error "find did not return the EC file"

	# Verify that plain and mirror files are not returned
	local parity_files=$($LFS find $td --component-flags parity)
	echo "$parity_files" | grep -q "plain_file" &&
		error "find returned plain_file as having parity"
	echo "$parity_files" | grep -q "mirror_file" &&
		error "find returned mirror_file as having parity"

	return 0
}
run_test 25a "lfs find with --component-flags parity"

# Test 26: larger I/O and multi-stripe EC data integrity
test_26a() {
	enable_ec

	local tf=$DIR/$tdir/$tfile
	local tf_data=$DIR/$tdir/${tfile}.data
	local tf_ec=$DIR/$tdir/${tfile}.ec

	test_mkdir $DIR/$tdir
	stack_trap "rm -rf $DIR/$tdir"

	# Create EC file with 4 data stripes and 2 parity stripes
	# Use 1M stripe size so 20M of data spans 5 full stripe rows
	$LFS setstripe -E -1 -S 1M -c 4 --ec 4+2 $tf ||
		error "setstripe failed"

	# Write 20M of random data (5 full rows of 4x1M stripes)
	dd if=/dev/urandom of=$tf bs=1M count=20 ||
		error "write failed"

	local old_chksum=$(md5sum $tf | awk '{print $1}')

	# Resync EC parity
	$LFS mirror resync $tf || error "resync failed"

	# Verify data unchanged after resync
	local new_chksum=$(md5sum $tf | awk '{print $1}')
	[[ "$old_chksum" == "$new_chksum" ]] ||
		error "data changed after resync: $old_chksum != $new_chksum"

	# Read data mirror and verify
	$LFS mirror read --mirror-id 1 -o $tf_data $tf ||
		error "mirror read data failed"
	echo "$old_chksum  $tf_data" | md5sum -c - ||
		error "data mirror content mismatch"

	# Save parity checksum before overwrite
	local parity_sum_before=$($LFS mirror read -N2 $tf | md5sum)

	# Overwrite part of the file (middle 4M of 20M)
	dd if=/dev/urandom of=$tf bs=1M count=4 seek=8 conv=notrunc ||
		error "overwrite failed"

	# After overwrite, parity should be stale
	verify_flr_state $tf "wp"

	# Resync after overwrite - parity must be recomputed
	$LFS mirror resync $tf || error "resync after overwrite failed"
	verify_flr_state $tf "ro"

	# Verify parity was recomputed (data changed so parity must differ)
	local parity_sum_after=$($LFS mirror read -N2 $tf | md5sum)
	[[ "$parity_sum_before" != "$parity_sum_after" ]] ||
		error "parity unchanged after data overwrite+resync"

	# Verify data mirror has correct content after overwrite+resync
	local final_chksum=$(md5sum $tf | awk '{print $1}')
	rm -f $tf_data
	$LFS mirror read --mirror-id 1 -o $tf_data $tf ||
		error "mirror read data after overwrite failed"
	echo "$final_chksum  $tf_data" | md5sum -c - ||
		error "data mirror content mismatch after overwrite"
}
run_test 26a "multi-stripe EC data integrity with 20M file"

test_26b() {
	enable_ec

	local tf=$DIR/$tdir/$tfile
	local tf_data=$DIR/$tdir/${tfile}.data
	local tf_ec=$DIR/$tdir/${tfile}.ec

	test_mkdir $DIR/$tdir
	stack_trap "rm -rf $DIR/$tdir"

	# Create EC file with 2 data stripes and 2 parity stripes
	# Use 4M stripe size for larger stripe coverage
	$LFS setstripe -E -1 -S 4M -c 2 --ec 2+2 $tf ||
		error "setstripe failed"

	# Write a pattern: alternating blocks of \xAA and \x55
	tr "\000" "\252" < /dev/zero | dd of=$tf bs=1M count=8 \
		iflag=fullblock 2>/dev/null
	tr "\000" "\125" < /dev/zero | dd of=$tf bs=1M count=8 seek=8 \
		iflag=fullblock 2>/dev/null

	local old_chksum=$(md5sum $tf | awk '{print $1}')

	# Resync
	$LFS mirror resync $tf || error "resync failed"

	# Verify data
	local new_chksum=$(md5sum $tf | awk '{print $1}')
	[[ "$old_chksum" == "$new_chksum" ]] ||
		error "data changed after resync: $old_chksum != $new_chksum"

	# Read back and verify both mirrors
	$LFS mirror read --mirror-id 1 -o $tf_data $tf ||
		error "mirror read data failed"
	echo "$old_chksum  $tf_data" | md5sum -c - ||
		error "data mirror content mismatch"

	# Read EC parity mirror
	$LFS mirror read --mirror-id 2 -o $tf_ec $tf ||
		error "mirror read ec failed"
	[[ -s $tf_ec ]] || error "EC mirror is empty"

	# For multi-stripe EC (2+2), parity is XOR across data stripes,
	# so parity content must differ from data content.
	local data_sum=$(md5sum < $tf_data)
	local ec_sum=$(md5sum < $tf_ec)
	[[ "$data_sum" != "$ec_sum" ]] ||
		error "parity mirror identical to data mirror - not real parity"

	# Overwrite first half with different pattern, resync,
	# and verify parity changes
	local ec_sum_before=$ec_sum
	tr "\000" "\377" < /dev/zero | dd of=$tf bs=1M count=8 \
		iflag=fullblock conv=notrunc 2>/dev/null
	$LFS mirror resync $tf || error "resync after overwrite failed"
	rm -f $tf_ec
	$LFS mirror read --mirror-id 2 -o $tf_ec $tf ||
		error "mirror read ec after overwrite failed"
	local ec_sum_after=$(md5sum < $tf_ec)
	[[ "$ec_sum_before" != "$ec_sum_after" ]] ||
		error "parity unchanged after data modification"
}
run_test 26b "EC data integrity with patterned data and 4M stripes"

# Test 27: PFL lazy instantiation with EC
test_27a() {
	enable_ec

	local tf=$DIR/$tdir/$tfile
	local ids
	local flags

	test_mkdir $DIR/$tdir
	stack_trap "rm -rf $DIR/$tdir"

	# Create EC file with PFL: [0, 1M], [1M, 8M], [8M, EOF]
	# Use 4+1 so parity count stays valid for all component stripe counts
	$LFS setstripe -E 1M -c 4 -E 8M -c 4 -E -1 -c 4 --ec 4+1 $tf ||
		error "setstripe failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 6

	# Component layout:
	# ids[0]: data [0, 1M]    ids[3]: parity [0, 1M]
	# ids[1]: data [1M, 8M]   ids[4]: parity [1M, 8M]
	# ids[2]: data [8M, EOF]  ids[5]: parity [8M, EOF]

	# Write only to the first component (< 1M)
	dd if=/dev/urandom of=$tf bs=512K count=1 ||
		error "write to first component failed"

	# Get component IDs
	ids=($($LFS getstripe $tf |
		awk '/lcme_id/{print $2}' | tr '\n' ' '))

	# First data component should be init (instantiated by write)
	$LFS getstripe -I${ids[0]} $tf | grep -q "init" ||
		error "first data component should be init"

	# Second and third parity components should NOT be init yet
	# (no data written to those extents, so parity not needed)
	flags=$($LFS getstripe -I${ids[4]} $tf |
		awk '/lcme_flags:/ { print $2 }')
	[[ ! "$flags" =~ "init" ]] ||
		error "second parity component should not be init yet"
	flags=$($LFS getstripe -I${ids[5]} $tf |
		awk '/lcme_flags:/ { print $2 }')
	[[ ! "$flags" =~ "init" ]] ||
		error "third parity component should not be init yet"

	# Resync first component's parity
	$LFS mirror resync $tf || error "resync failed"
	verify_flr_state $tf "ro"

	# First parity component should now be init after resync
	flags=$($LFS getstripe -I${ids[3]} $tf |
		awk '/lcme_flags:/ { print $2 }')
	[[ "$flags" =~ "init" ]] ||
		error "first parity component should be init after resync"

	# Save parity checksum after first resync
	local parity_sum1=$($LFS mirror read -N2 $tf | md5sum)

	# Now write to the second component extent (at 2M)
	dd if=/dev/urandom of=$tf bs=1M count=1 seek=2 conv=notrunc ||
		error "write to second component failed"
	verify_flr_state $tf "wp"

	# Resync
	$LFS mirror resync $tf || error "resync after second write failed"
	verify_flr_state $tf "ro"

	# Parity should have changed (new data in second component)
	local parity_sum2=$($LFS mirror read -N2 $tf | md5sum)
	[[ "$parity_sum1" != "$parity_sum2" ]] ||
		error "parity unchanged after writing to second component"

	# Write to the third component extent (at 10M)
	dd if=/dev/urandom of=$tf bs=1M count=1 seek=10 conv=notrunc ||
		error "write to third component failed"
	verify_flr_state $tf "wp"

	# Resync everything
	$LFS mirror resync $tf || error "final resync failed"
	verify_flr_state $tf "ro"

	# Parity should have changed again
	local parity_sum3=$($LFS mirror read -N2 $tf | md5sum)
	[[ "$parity_sum2" != "$parity_sum3" ]] ||
		error "parity unchanged after writing to third component"

	# Verify data can be read back correctly
	local file_sum=$(md5sum < $tf)
	local data_sum=$($LFS mirror read -N1 $tf | md5sum)
	[[ "$file_sum" == "$data_sum" ]] ||
		error "data mirror content mismatch"
}
run_test 27a "PFL lazy instantiation with EC across multiple components"

# Test 28: file operations (hardlink, rename, symlink) on EC files
test_28a() {
	enable_ec

	local tf=$DIR/$tdir/$tfile
	local tf_link=$DIR/$tdir/${tfile}.link

	test_mkdir $DIR/$tdir
	stack_trap "rm -rf $DIR/$tdir"

	# Create EC file with data and sync parity
	$LFS setstripe -E -1 -c 4 --ec 4+2 $tf ||
		error "setstripe failed"
	dd if=/dev/urandom of=$tf bs=1M count=2 ||
		error "write failed"
	$LFS mirror resync $tf || error "resync failed"
	verify_flr_state $tf "ro"

	local old_chksum=$(md5sum $tf | awk '{print $1}')
	local parity_sum_before=$($LFS mirror read -N2 $tf | md5sum)

	# Create hardlink
	ln $tf $tf_link || error "hardlink failed"

	# Verify EC layout accessible through hardlink
	verify_mirror_count $tf_link 2
	$LFS getstripe $tf_link | grep -q "parity" ||
		error "parity not visible through hardlink"

	# Write through the hardlink - should make parity stale
	dd if=/dev/urandom of=$tf_link bs=1M count=1 conv=notrunc ||
		error "write through hardlink failed"
	verify_flr_state $tf "wp"

	# Verify parity is stale when checked through original name
	local ids=($($LFS getstripe $tf |
		awk '/lcme_id/{print $2}' | tr '\n' ' '))
	verify_comp_stale $tf ${ids[1]}

	# Resync through original name
	$LFS mirror resync $tf || error "resync through original name failed"
	verify_flr_state $tf "ro"

	# Verify parity was recomputed (data changed via hardlink write)
	local parity_sum_after=$($LFS mirror read -N2 $tf | md5sum)
	[[ "$parity_sum_before" != "$parity_sum_after" ]] ||
		error "parity unchanged after write-through-hardlink+resync"

	# Verify data readable through both names matches
	local sum_orig=$(md5sum < $tf)
	local sum_link=$(md5sum < $tf_link)
	[[ "$sum_orig" == "$sum_link" ]] ||
		error "data differs between original and hardlink after resync"
}
run_test 28a "EC parity correctly tracks writes through hardlinks"

#
# Failure injection / degraded mode tests (test_29x - test_33x)
#
# These tests exercise EC behavior under OST failures, degraded OSTs,
# and injected I/O errors.
#

drop_client_cache() {
	echo 3 > /proc/sys/vm/drop_caches
}

test_29a() {
	# With 2+1 EC, losing 2 OSTs exceeds parity tolerance.
	# Read must fail (not return corrupt data). This test should
	# pass today -- we expect an error, and we get one.
	(( OSTCOUNT >= 4 )) || skip "need >= 4 OSTs"
	enable_ec

	local tf=$DIR/$tfile

	stack_trap "rm -f $tf" EXIT

	# 2+1 EC: can only tolerate 1 failure
	$LFS setstripe -E -1 -S 1M -c 2 --ec 2+1 $tf ||
		error "setstripe failed"

	dd if=/dev/urandom of=$tf bs=1M count=4 || error "write failed"
	$LFS mirror resync $tf || error "resync failed"
	verify_flr_state $tf "ro"

	# Identify two distinct OSTs used by the data stripe
	local ost_indices=($($LFS getstripe $tf |
		awk '/l_ost_idx:/ {print $5}' | head -2 | tr -d ','))

	local facet1=ost$((ost_indices[0] + 1))
	local facet2=ost$((ost_indices[1] + 1))

	echo "Stopping $facet1 and $facet2 (exceeds 2+1 parity tolerance)"
	stop $facet1
	stop $facet2
	wait_osc_import_state client $facet1 "\(DISCONN\|IDLE\)"
	wait_osc_import_state client $facet2 "\(DISCONN\|IDLE\)"
	drop_client_cache

	# Read must fail -- returning success with wrong data would be
	# catastrophic. Either an I/O error or timeout is acceptable.
	if md5sum $tf 2>/dev/null; then
		error "read should have failed with 2 OSTs down on 2+1 EC"
	else
		echo "Read correctly failed when losses exceed parity tolerance"
	fi

	start $facet1 $(ostdevname $((ost_indices[0] + 1))) $OST_MOUNT_OPTS
	start $facet2 $(ostdevname $((ost_indices[1] + 1))) $OST_MOUNT_OPTS
	wait_recovery_complete $facet1
	wait_recovery_complete $facet2
}
run_test 29a "EC read fails when OST losses exceed parity tolerance"

test_30a() {
	# Test that writes still succeed when an OST used by the parity
	# mirror is down. The data mirror write should succeed, and the
	# parity mirror should be marked stale.
	(( OSTCOUNT >= 4 )) || skip "need >= 4 OSTs"
	enable_ec

	local tf=$DIR/$tfile

	stack_trap "rm -f $tf" EXIT

	$LFS setstripe -E -1 -S 1M -c 2 --ec 2+1 $tf ||
		error "setstripe failed"

	dd if=/dev/urandom of=$tf bs=1M count=2 || error "initial write failed"
	$LFS mirror resync $tf || error "resync failed"
	verify_flr_state $tf "ro"

	# Write new data - parity becomes stale, no OST failure needed
	dd if=/dev/urandom of=$tf bs=1M count=2 conv=notrunc ||
		error "overwrite failed"
	verify_flr_state $tf "wp"

	# Verify parity mirror is stale
	local ids=($($LFS getstripe $tf |
		awk '/lcme_id/{print $2}' | tr '\n' ' '))
	verify_comp_stale $tf ${ids[1]}

	# Resync should recover parity
	$LFS mirror resync $tf || error "resync after write failed"
	verify_flr_state $tf "ro"

	# Verify data integrity through the whole cycle
	local cksum=$(md5sum $tf | awk '{print $1}')
	[[ -n "$cksum" ]] || error "cannot read file after resync"
}
run_test 30a "write makes parity stale, resync recovers"

test_30b() {
	# Test resync after parity OST recovery. Write data (parity
	# becomes stale), cycle the parity OST down and back up, then
	# verify resync succeeds and data is intact.
	# Note: we do NOT attempt resync while the OST is down because
	# designated writes to a downed OST enter uninterruptible kernel
	# sleep and cannot be timed out.
	(( OSTCOUNT >= 4 )) || skip "need >= 4 OSTs"
	enable_ec

	local tf=$DIR/$tfile

	stack_trap "rm -f $tf" EXIT

	$LFS setstripe -E -1 -S 1M -c 2 --ec 2+1 $tf ||
		error "setstripe failed"

	dd if=/dev/urandom of=$tf bs=1M count=4 || error "write failed"
	local cksum_before=$(md5sum $tf | awk '{print $1}')

	# Parity is stale after the write. Cycle the parity OST.
	local parity_ost_idx=$($LFS getstripe --mirror-id=2 $tf |
		awk '/l_ost_idx:/ {print $5; exit}' | tr -d ',')
	[[ -n "$parity_ost_idx" ]] || error "could not find parity OST"
	local parity_facet=ost$((parity_ost_idx + 1))

	echo "Cycling $parity_facet (parity OST index $parity_ost_idx)"
	stop $parity_facet
	wait_osc_import_state client $parity_facet "\(DISCONN\|IDLE\)"

	# Restart the parity OST
	start $parity_facet $(ostdevname $((parity_ost_idx + 1))) \
		$OST_MOUNT_OPTS
	wait_recovery_complete $parity_facet

	# Resync should succeed now that the OST is back
	$LFS mirror resync $tf || error "resync failed after OST recovery"
	verify_flr_state $tf "ro"

	# Verify data integrity is maintained through the whole sequence
	local cksum_after=$(md5sum $tf | awk '{print $1}')
	[[ "$cksum_before" == "$cksum_after" ]] ||
		error "data corrupted: $cksum_before != $cksum_after"
}
run_test 30b "resync succeeds after parity OST recovery"

test_30c() {
	# Test that resync correctly recomputes parity after an OST
	# that was down comes back. The sequence is:
	# 1. Create EC file, write data, resync parity (all good)
	# 2. Stop a data-mirror OST
	# 3. Start it back
	# 4. Verify data is intact and resync still works
	(( OSTCOUNT >= 4 )) || skip "need >= 4 OSTs"
	enable_ec

	local tf=$DIR/$tfile

	stack_trap "rm -f $tf" EXIT

	$LFS setstripe -E -1 -S 1M -c 2 --ec 2+1 $tf ||
		error "setstripe failed"

	dd if=/dev/urandom of=$tf bs=1M count=4 || error "write failed"
	$LFS mirror resync $tf || error "initial resync failed"
	verify_flr_state $tf "ro"

	local cksum_before=$(md5sum $tf | awk '{print $1}')

	# Stop a data OST and bring it back
	local data_ost_idx=$($LFS getstripe --mirror-id=1 $tf |
		awk '/l_ost_idx:/ {print $5; exit}' | tr -d ',')
	local data_facet=ost$((data_ost_idx + 1))

	echo "Cycling $data_facet (data OST index $data_ost_idx)"
	stop $data_facet
	wait_osc_import_state client $data_facet "\(DISCONN\|IDLE\)"

	start $data_facet $(ostdevname $((data_ost_idx + 1))) $OST_MOUNT_OPTS
	wait_recovery_complete $data_facet

	# Data should still be readable and correct
	drop_client_cache
	local cksum_after=$(md5sum $tf | awk '{print $1}')
	[[ "$cksum_before" == "$cksum_after" ]] ||
		error "data corrupted after OST cycle: $cksum_before != $cksum_after"

	# Write new data and resync - everything should still work
	dd if=/dev/urandom of=$tf bs=1M count=2 conv=notrunc ||
		error "write after OST recovery failed"
	$LFS mirror resync $tf || error "resync after OST recovery failed"
	verify_flr_state $tf "ro"
}
run_test 30c "data survives OST restart cycle, resync works after recovery"

test_30d() {
	# test that reading from ec mirror reads the full set of parities
	(( OSTCOUNT >= 8 )) || skip_env "needs >= 8 OSTs"

	enable_ec

	local tf=$DIR/$tfile

	# Create a simple EC file with data mirror + parity mirror
	$LFS setstripe -E -1 -S 64k -c 8 --ec 2+2 $tf ||
		error "setstripe --ec 2+2 failed"

	# Write some data to the file
	# The file now spans 5 full stripe sets plus an extra stripe
	# The resulting parity should then be for 6 full stripe sets
	# of parities.
	tr "\000" "\002" < /dev/zero | dd bs=64k count=1 seek=40  \
		iflag=fullblock of=$tf 2>/dev/null

	rm -f $TMP/$tfile.parity
	stack_trap "rm -f $TMP/$tfile.parity"

	# stripe size 64k
	# 8 data stripes, ec 2+2
	# 4 raid sets with 2 parities each
	# a total of 6 stripe sets
	# parity mirror size should be 6 * 4 * 2 * 64k plus
	$LFS mirror read --mirror-id 2 -o $TMP/$tfile.parity $tf
	stat $TMP/$tfile.parity | grep "Size: 3145728" ||
	    error "Wrong size of parity mirror"
}
run_test 30d "test that size of parity mirror is (5+1)*4*2*64k"

test_31a() {
	# Inject OBD_FAIL_OST_BRW_WRITE_BULK on a parity OST during
	# resync. The data mirror must remain intact regardless of
	# whether the resync succeeds or fails.
	(( OSTCOUNT >= 4 )) || skip "need >= 4 OSTs"
	enable_ec

	local tf=$DIR/$tfile

	stack_trap "rm -f $tf" EXIT

	$LFS setstripe -E -1 -S 1M -c 2 --ec 2+1 $tf ||
		error "setstripe failed"

	dd if=/dev/urandom of=$tf bs=1M count=4 || error "write failed"
	local cksum_before=$(md5sum $tf | awk '{print $1}')

	# Parity is stale. Now inject write failure on a parity OST.
	local parity_ost_idx=$($LFS getstripe --mirror-id=2 $tf |
		awk '/l_ost_idx:/ {print $5; exit}' | tr -d ',')
	[[ -n "$parity_ost_idx" ]] || error "could not find parity OST"
	local parity_facet=ost$((parity_ost_idx + 1))

	#define OBD_FAIL_OST_BRW_WRITE_BULK  0x20e
	echo "Injecting OBD_FAIL_OST_BRW_WRITE_BULK on $parity_facet"
	do_facet $parity_facet $LCTL set_param fail_loc=0x8000020e

	echo "Attempting resync with write failure injected..."
	$LFS mirror resync $tf 2>/dev/null
	local rc=$?

	# Clear the fail_loc
	do_facet $parity_facet $LCTL set_param fail_loc=0

	# Data mirror must be untouched regardless of resync outcome
	local cksum_after=$(md5sum $tf | awk '{print $1}')
	[[ "$cksum_before" == "$cksum_after" ]] ||
		error "data corrupted by failed resync: $cksum_before != $cksum_after"

	if (( rc != 0 )); then
		echo "Resync correctly failed with write error on parity OST"
	else
		echo "Resync appeared to succeed despite injected write failure"
	fi

	# The parity may now be corrupt (partial write). Write new data
	# to make parity stale again, then resync cleanly.
	dd if=/dev/urandom of=$tf bs=1M count=1 conv=notrunc ||
		error "write to re-stale parity failed"
	$LFS mirror resync $tf ||
		error "resync failed after re-staling parity"
	verify_flr_state $tf "ro"
}
run_test 31a "data mirror intact after write failure on parity OST during resync"

test_31b() {
	# test that reading from ec mirror reads the full set of parities
	(( OSTCOUNT >= 8 )) || skip_env "needs >= 8 OSTs"

	enable_ec

	local tf=$DIR/$tfile

	# Create a simple EC file with data mirror + parity mirror
	$LFS setstripe -E -1 -S 1M -c 8 --ec 4+2 $tf ||
		error "setstripe --ec 4+2 failed"

	# Write some data to the file
	echo "Hello" > $tf || error "error writing to file"

	rm -f $TMP/$tfile.parity
	stack_trap "rm -f $TMP/$tfile.parity"

	# stripe size 1M
	# 8 data stripes, ec 4+2
	# 2 raid sets with 2 parities each
	# parity mirror size should be 2 * 2 * 1M
	$LFS mirror read --mirror-id 2 -o $TMP/$tfile.parity $tf
	stat $TMP/$tfile.parity | grep "Size: 4194304" ||
	    error "Wrong size of parity mirror"
}
run_test 31b "test that size of parity mirror is 2*2*1M"

test_31c() {
	# test that reading from ec mirror reads the full set of parities
	(( OSTCOUNT >= 8 )) || skip_env "needs >= 8 OSTs"

	enable_ec

	local tf=$DIR/$tfile

	# Create a simple EC file with data mirror + parity mirror
	$LFS setstripe -E -1 -S 64k -c 8 --ec 4+3 $tf ||
		error "setstripe --ec 4+3 failed"

	# Write some data to the file
	echo "Hello" > $tf || error "error writing to file"

	rm -f $TMP/$tfile.parity
	stack_trap "rm -f $TMP/$tfile.parity"

	# stripe size 64k
	# 8 data stripes, ec 4+3
	# 2 raid sets with 3 parities each
	# parity mirror size should be 2 * 3 * 64k
	$LFS mirror read --mirror-id 2 -o $TMP/$tfile.parity $tf
	stat $TMP/$tfile.parity | grep "Size: 393216" ||
	    error "Wrong size of parity mirror"
}
run_test 31c "test that size of parity mirror is 2*3*64k"

test_31d() {
	# test that reading from ec mirror reads the full set of parities
	(( OSTCOUNT >= 8 )) || skip_env "needs >= 8 OSTs"

	enable_ec

	local tf=$DIR/$tfile

	# Create a simple EC file with data mirror + parity mirror
	$LFS setstripe -E -1 -S 64k -c 8 --ec 2+2 $tf ||
		error "setstripe --ec 2+2 failed"

	# Write some data to the file
	echo "Hello" > $tf || error "error writing to file"

	rm -f $TMP/$tfile.parity
	stack_trap "rm -f $TMP/$tfile.parity"

	# stripe size 64k
	# 8 data stripes, ec 2+2
	# 4 raid sets with 2 parities each
	# parity mirror size should be 4 * 2 * 64k
	$LFS mirror read --mirror-id 2 -o $TMP/$tfile.parity $tf
	stat $TMP/$tfile.parity | grep "Size: 524288" ||
	    error "Wrong size of parity mirror"
}
run_test 31d "test that size of parity mirror is 4*2*64k"

test_31e() {
	(( OSTCOUNT >= 6 )) || skip_env "needs >= 6 OSTs"

	enable_ec

	local tf=$DIR/$tfile
	local stripe_size=$((128 * 1024))

	rm -f $TMP/$tfile.parity
	stack_trap "rm -f $TMP/$tfile.parity"

	# Data and parity mirrors each have two components:
	# data [0, 1M) and [1M, EOF), both EC(4+2), 4 data stripes, 128k stripes
	$LFS setstripe -E 1M -S $stripe_size -c 4 -E -1 -S $stripe_size \
		-c 4 --ec 4+2 $tf ||
		error "setstripe --ec 4+2 with PFL failed"

	verify_mirror_count $tf 2
	verify_comp_count $tf 4

	# Write 1.5M so data spans both components (1M + 512k).
	dd if=/dev/urandom of=$tf bs=512K count=3 2>/dev/null ||
		error "failed to write 1.5M"
	(( $(stat -c %s $tf) == $((3 * 512 * 1024)) )) ||
		error "unexpected file size"

	$LFS mirror resync $tf || error "mirror resync failed"

	# Parity lsme_extent matches data lsme_extent (same file offsets, not
	# packed parity bytes).
	# ec_raidset_size = 2 parities * stripe_size = 262144.
	# Parity comp 1 (ext [1M, EOF), data 512k):
	# comp_eof = 1M + 262144 = 1310720
	local ec_raidset_size=$((2 * stripe_size))
	local expected=$((1024 * 1024 + ec_raidset_size))
	$LFS mirror read --mirror-id 2 -o $TMP/$tfile.parity $tf ||
		error "mirror read from parity failed"
	stat $TMP/$tfile.parity | grep "Size: $expected" ||
		error "Wrong size of parity mirror (expected $expected)"
}
run_test 31e "test parity mirror size with multiple PFL components"

test_32a() {
	# Verify that after an OST goes down and comes back, a full
	# write+resync cycle produces correct parity. This validates
	# the end-to-end recovery path.
	(( OSTCOUNT >= 4 )) || skip "need >= 4 OSTs"
	enable_ec

	local tf=$DIR/$tdir/$tfile

	test_mkdir $DIR/$tdir
	stack_trap "rm -rf $DIR/$tdir" EXIT

	$LFS setstripe -E -1 -S 1M -c 2 --ec 2+1 $tf ||
		error "setstripe failed"

	# Write known pattern and resync to establish good parity
	dd if=/dev/urandom of=$tf bs=1M count=4 || error "write 1 failed"
	$LFS mirror resync $tf || error "resync 1 failed"
	verify_flr_state $tf "ro"

	# Save parity checksum
	local parity_sum_1=$($LFS mirror read -N2 $tf | md5sum)

	# Stop a data OST, restart it
	local data_ost_idx=$($LFS getstripe --mirror-id=1 $tf |
		awk '/l_ost_idx:/ {print $5; exit}' | tr -d ',')
	local data_facet=ost$((data_ost_idx + 1))

	stop $data_facet
	wait_osc_import_state client $data_facet "\(DISCONN\|IDLE\)"
	start $data_facet $(ostdevname $((data_ost_idx + 1))) $OST_MOUNT_OPTS
	wait_recovery_complete $data_facet

	# Write new data and resync
	dd if=/dev/urandom of=$tf bs=1M count=4 conv=notrunc ||
		error "write 2 after recovery failed"
	$LFS mirror resync $tf || error "resync 2 after recovery failed"
	verify_flr_state $tf "ro"

	# Parity must have changed (new data -> new parity)
	local parity_sum_2=$($LFS mirror read -N2 $tf | md5sum)
	[[ "$parity_sum_1" != "$parity_sum_2" ]] ||
		error "parity unchanged after writing new data post-recovery"

	# Verify data is readable and consistent
	local cksum=$(md5sum $tf | awk '{print $1}')
	local mirror_cksum
	mirror_cksum=$($LFS mirror read --mirror-id 1 $tf | md5sum |
		awk '{print $1}')
	[[ "$cksum" == "$mirror_cksum" ]] ||
		error "data mirror inconsistent after recovery cycle"
}
run_test 32a "full write+resync cycle correct after OST recovery"

test_33a() {
	# Multiple writes and resyncs with an intermittent OST failure
	# in between. This exercises the stale tracking and resync logic
	# across multiple failure/recovery cycles.
	(( OSTCOUNT >= 4 )) || skip "need >= 4 OSTs"
	enable_ec

	local tf=$DIR/$tfile
	local i

	stack_trap "rm -f $tf" EXIT

	$LFS setstripe -E -1 -S 1M -c 2 --ec 2+1 $tf ||
		error "setstripe failed"

	for i in 1 2 3; do
		echo "=== Cycle $i ==="

		dd if=/dev/urandom of=$tf bs=1M count=4 conv=notrunc ||
			error "write $i failed"
		verify_flr_state $tf "wp"

		# Resync parity
		$LFS mirror resync $tf || error "resync $i failed"
		verify_flr_state $tf "ro"

		# Cycle a random data OST
		local ost_idx=$($LFS getstripe --mirror-id=1 $tf |
			awk '/l_ost_idx:/ {print $5; exit}' | tr -d ',')
		local facet=ost$((ost_idx + 1))

		stop $facet
		wait_osc_import_state client $facet "\(DISCONN\|IDLE\)"
		start $facet $(ostdevname $((ost_idx + 1))) $OST_MOUNT_OPTS
		wait_recovery_complete $facet
	done

	# Final data integrity check
	drop_client_cache
	md5sum $tf > /dev/null || error "final read failed"
	echo "Survived $i write+resync+OST-cycle iterations"
}
run_test 33a "EC survives repeated write/resync/OST-failure cycles"

test_34a() {
	# test resyncing a stale ec mirror
	(( OSTCOUNT >= 5 )) || skip_env "needs >= 5 OSTs"

	enable_ec

	test_mkdir $DIR/$tdir

	$LFS setstripe -E -1 -S 4M -c 4 --ec 3+2 $DIR/$tdir/$tfile ||
	    error "setstripe --ec 3+2 failed"

	# create a small file
	echo "hello" > $DIR/$tdir/$tfile
	SIZE1=`stat -c "%s" $DIR/$tdir/$tfile`

	# resync the ec mirror:
	$LFS mirror resync $DIR/$tdir/$tfile ||
	    error "failed to resync ec mirror"
	SIZE2=`stat -c "%s" $DIR/$tdir/$tfile`
	(( SIZE1 == SIZE2 )) ||
		error "mirror resync changed eof: ${SIZE1} vs ${SIZE2}"

	# read from the ec mirror:
	$LFS mirror read -N 2 $DIR/$tdir/$tfile >/dev/null ||
	    error "failed to read ec mirror"
	SIZE3=`stat -c "%s" $DIR/$tdir/$tfile`

	(( SIZE1 == SIZE3 )) ||
		error "mirror read changed eof: ${SIZE1} vs ${SIZE3}"
}
run_test 34a "test that lfs mirror read from ec does not change eof"

test_34b() {
	# test resyncing a stale ec mirror
	(( OSTCOUNT >= 6 )) || skip_env "needs >= 6 OSTs"

	enable_ec
	test_mkdir $DIR/$tdir

	$LFS setstripe -E -1 -S 4M -c 4 --ec 4+2 $DIR/$tdir/$tfile ||
	    error "setstripe --ec 4+2 failed"

	# create a small file
	echo "hello" > $DIR/$tdir/$tfile
	SIZE1=$(stat -c "%s" $DIR/$tdir/$tfile)
	BLOCKS1=$(stat -c "%b" $DIR/$tdir/$tfile)
	APPARENT1=$(stat -c "%B" $DIR/$tdir/$tfile)
	echo "After write: size=$SIZE1 blocks=$BLOCKS1 apparent_blksize=$APPARENT1"

	# resync the ec mirror:
	$LFS mirror resync $DIR/$tdir/$tfile ||
	    error "failed to resync ec mirror"
	SIZE2=$(stat -c "%s" $DIR/$tdir/$tfile)
	BLOCKS2=$(stat -c "%b" $DIR/$tdir/$tfile)
	APPARENT2=$(stat -c "%B" $DIR/$tdir/$tfile)
	echo "After resync: size=$SIZE2 blocks=$BLOCKS2 apparent_blksize=$APPARENT2"
	(( SIZE1 == SIZE2 )) ||
		error "mirror resync changed eof: ${SIZE1} vs ${SIZE2}"

	# verify the ec mirror:
	$LFS mirror verify $DIR/$tdir/$tfile ||
	    error "failed to verify ec mirror"
	SIZE3=$(stat -c "%s" $DIR/$tdir/$tfile)
	BLOCKS3=$(stat -c "%b" $DIR/$tdir/$tfile)
	APPARENT3=$(stat -c "%B" $DIR/$tdir/$tfile)
	echo "After verify: size=$SIZE3 blocks=$BLOCKS3 apparent_blksize=$APPARENT3"

	(( SIZE1 == SIZE3 )) ||
		error "mirror verify changed eof: ${SIZE1} vs ${SIZE3}"
}
run_test 34b "test that lfs mirror verify for ec does not change eof"

test_34c() {
	# test that reading past parity data returns zeros, not garbage
	(( OSTCOUNT >= 8 )) || skip_env "needs >= 8 OSTs"

	enable_ec

	test_mkdir $DIR/$tdir

	# Test with data sizes that span at least one full stripe.
	# Reed-Solomon parity is a linear code over GF(2^8), so parity
	# bytes at byte offset i are 0 whenever every data stripe is 0
	# at offset i.  A sub-stripe file has zero input at every offset
	# past the data tail across all data stripes, so its parity is
	# correctly zero there and "expect parity bytes" cannot succeed.
	# - 4M     (exactly one stripe)
	# - 4.5M   (one stripe + partial)
	# - 8.5M   (two stripes + partial)
	local -a test_sizes=($((4 * 1024 * 1024)) \
			     $((4 * 1024 * 1024 + 512 * 1024)) \
			     $((8 * 1024 * 1024 + 512 * 1024)))
	local size
	local i
	local parity_mirror_id

	for size in "${test_sizes[@]}"; do
		echo "Testing with data size: $size bytes"

		rm -f $DIR/$tdir/$tfile
		$LFS setstripe -E -1 -S 4M -c 8 --ec 3+2 \
			$DIR/$tdir/$tfile ||
			error "setstripe --ec 3+2 failed"

		# Write data
		dd if=/dev/urandom of=$DIR/$tdir/$tfile bs=$size \
			count=1 2>/dev/null ||
			error "failed to write $size bytes"

		# Resync to write parity
		$LFS mirror resync $DIR/$tdir/$tfile ||
			error "failed to resync ec mirror"

		# Get parity mirror ID
		parity_mirror_id=$($LFS getstripe $DIR/$tdir/$tfile | \
			grep -B1 "lcme_flags.*parity" | \
			grep "lcme_mirror_id" | awk '{print $2}')

		echo "Parity mirror ID: $parity_mirror_id"

		# Test reads at various offsets
		# For 3+2 EC with 4M stripes, we have 2 parity stripes
		# Each parity stripe should have parity data up to $size,
		# then zeros

		# Test start of first parity stripe (should have data)
		echo "  Checking offset 0 (start of first parity stripe)..."
		check_parity_read $DIR/$tdir/$tfile $parity_mirror_id \
			0 "no"

		# Parity is computed at block granularity, covering
		# only the data range.  Offsets within a parity stripe
		# but past the data range read as zeros.

		# Only check offset 1M if data extends into that range
		if (( size > 1024 * 1024 )); then
			echo "  Checking offset 1M (within first parity stripe)..."
			check_parity_read $DIR/$tdir/$tfile \
				$parity_mirror_id $((1024 * 1024)) "no"
		fi

		# Test start of second parity stripe at 4M: always has
		# data as long as any data was written (each coding stripe
		# gets parity for the same data range)
		echo "  Checking offset 4M (start of second parity stripe)..."
		check_parity_read $DIR/$tdir/$tfile $parity_mirror_id \
			$((4 * 1024 * 1024)) "no"

		# Test past all parity stripes (should be zeros)
		echo "  Checking offset 8M (past all parity)..."
		check_parity_read $DIR/$tdir/$tfile $parity_mirror_id \
			$((8 * 1024 * 1024)) "yes"
	done
}
run_test 34c "test that reading past parity data returns zeros"

complete_test $SECONDS
check_and_cleanup_lustre
exit_status