#!/bin/bash -x

# Copyright(c) 2015-2017 Intel Corporation. All rights reserved.
#
# This program is free software; you can redistribute it and/or modify it
# under the terms of version 2 of the GNU General Public License as
# published by the Free Software Foundation.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
# General Public License for more details.

MNT=test_btt_mnt
FILE=image
blockdev=""
rc=77

. ./common

cleanup()
{
	rm -f $FILE
	rm -f $MNT/$FILE
	if [ -n "$blockdev" ]; then
		umount "/dev/$blockdev"
	else
		rc=77
	fi
	rmdir $MNT
}

force_raw()
{
	raw="$1"
	if grep -q "$MNT" /proc/mounts; then umount $MNT; fi
	$NDCTL disable-namespace "$dev"
	echo "$raw" > "/sys/bus/nd/devices/$dev/force_raw"
	$NDCTL enable-namespace "$dev"
	echo "Set $dev to raw mode: $raw"
	if [[ "$raw" == "1" ]]; then
		raw_bdev=${blockdev%s}
		test -b "/dev/$raw_bdev"
	else
		raw_bdev=""
	fi
}

check_min_kver "4.15" || do_skip "may lack BTT error handling"

set -e
mkdir -p $MNT
trap 'err $LINENO cleanup' ERR

# setup (reset nfit_test dimms)
modprobe nfit_test
$NDCTL disable-region -b $NFIT_TEST_BUS0 all
$NDCTL zero-labels -b $NFIT_TEST_BUS0 all
$NDCTL enable-region -b $NFIT_TEST_BUS0 all

rc=1

# create a btt namespace and clear errors (if any)
dev="x"
json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -t pmem -m sector)
eval "$(echo "$json" | json2var)"
[ $dev = "x" ] && echo "fail: $LINENO" && exit 1

force_raw 1
if read -r sector len < "/sys/block/$raw_bdev/badblocks"; then
	dd of=/dev/$raw_bdev if=/dev/zero oflag=direct bs=512 seek="$sector" count="$len"
fi
force_raw 0

mkfs.ext4 "/dev/$blockdev" -b 4096
mount -o nodelalloc "/dev/$blockdev" $MNT

# prepare an image file with random data
dd if=/dev/urandom of=$FILE bs=4096 count=1
test -s $FILE

# copy it to the file system
cp $FILE $MNT/$FILE

# Get the start sector for the file
start_sect=$(filefrag -v -b512 $MNT/$FILE | grep -E "^[ ]+[0-9]+.*" | head -1 | awk '{ print $4 }' | cut -d. -f1)
start_4k=$((start_sect/8))
test -n "$start_sect"
echo "start sector of the file is: $start_sect (512B) or $start_4k (4096B)"

# figure out the btt offset

force_raw 1

# calculate start of the map
map=$(hexdump -s 96 -n 4 "/dev/$raw_bdev" | head -1 | cut -d' ' -f2-)
map=$(tr -d ' ' <<< "0x${map#* }${map%% *}")
printf "btt map starts at: %x\n" "$map"

# calculate map entry byte offset for the file's block
map_idx=$((map + (4 * start_4k)))
printf "btt map entry location for sector %x: %x\n" "$start_4k" "$map_idx"

# read the map entry
map_ent=$(hexdump -s $map_idx -n 4 "/dev/$raw_bdev" | head -1 | cut -d' ' -f2-)
map_ent=$(tr -d ' ' <<< "0x${map_ent#* }${map_ent%% *}")
map_ent=$((map_ent & 0x3fffffff))
printf "btt map entry: 0x%x\n" "$map_ent"

# calculate the data offset
dataoff=$(((map_ent * 4096) + 4096))
printf "dataoff: 0x%x\n" "$dataoff"

bb_inj=$((dataoff/512))

# inject badblocks for one page at the start of the file
$NDCTL inject-error --block="$bb_inj" --count=8 $dev
$NDCTL start-scrub && $NDCTL wait-scrub

force_raw 0
mount -o nodelalloc "/dev/$blockdev" $MNT

# make sure reading the first block of the file fails as expected
: The following 'dd' is expected to hit an I/O Error
dd if=$MNT/$FILE of=/dev/null iflag=direct bs=4096 count=1 && err $LINENO || true

# write via btt to clear the error
dd if=/dev/zero of=$MNT/$FILE oflag=direct bs=4096 count=1

# read again and that should succeed
dd if=$MNT/$FILE of=/dev/null iflag=direct bs=4096 count=1


## ensure we get an EIO for errors in namespace metadata

# reset everything to get a clean log
if grep -q "$MNT" /proc/mounts; then umount $MNT; fi
$NDCTL disable-region -b $NFIT_TEST_BUS0 all
$NDCTL zero-labels -b $NFIT_TEST_BUS0 all
$NDCTL enable-region -b $NFIT_TEST_BUS0 all
dev="x"
json=$($NDCTL create-namespace -b $NFIT_TEST_BUS0 -t pmem -m sector)
eval "$(echo "$json" | json2var)"
[ $dev = "x" ] && echo "fail: $LINENO" && exit 1

# insert error at an arbitrary offset in the map (sector 0)
force_raw 1
map=$(hexdump -s 96 -n 4 "/dev/$raw_bdev" | head -1 | cut -d' ' -f2-)
map=$(tr -d ' ' <<< "0x${map#* }${map%% *}")
bb_inj=$((map/512))
$NDCTL inject-error --block="$bb_inj" --count=1 $dev
$NDCTL start-scrub && $NDCTL wait-scrub
force_raw 0

# make sure reading the first block of the namespace fails
: The following 'dd' is expected to hit an I/O Error
dd if=/dev/$blockdev of=/dev/null iflag=direct bs=4096 count=1 && err $LINENO || true

# done, exit
$NDCTL disable-region -b $NFIT_TEST_BUS0 all
$NDCTL zero-labels -b $NFIT_TEST_BUS0 all
$NDCTL enable-region -b $NFIT_TEST_BUS0 all
_cleanup
exit 0
