#!/bin/bash

TS_TOPDIR="${0%/*}/../.."
TS_DESC="count file contents in core"

. "$TS_TOPDIR"/functions.sh
ts_init "$*"

ts_check_test_command "$TS_CMD_FINDMNT"
ts_check_test_command "$TS_HELPER_SYSINFO"

FS="$("$TS_CMD_FINDMNT" -nr -o FSTYPE -T "$PWD")"
if [[ "$FS" = "tmpfs" || "$FS" = "overlay" || "$FS" = "" ]]; then
	ts_skip "fincore does not work on tmpfs or unknown fs"
fi

# https://github.com/util-linux/util-linux/pull/3529
if [ "$($TS_HELPER_SYSINFO fts)" != FTS ]; then
    ts_skip "fts_open() is unreliable on this setup"
fi

function footer
{
    echo "return value: $1"
}

function make_input_name
{
    header=$1
    prefix=i_
    echo ${prefix}$(sed -e "s/[^-+a-zA-Z0-9_/]/_/g"<<<"$header")
}

function _dd
{
	local msg
	local ret=0

	msg=$(dd "$@" 2>&1)
	ret=$?
	if [ $ret != 0 ]; then
		echo "failed: dd $@" >&2
		echo "$msg" >&2
	fi
	return $ret
}

function check_dd_fs_feat
{
	local testf="$TS_OUTDIR/ddtest"
	rm -f "$testf"
	touch "$testf"

	# NFS seems to fail for direct AND append
	_dd if=/dev/zero of="$testf" bs=1k count=2 oflag=direct,append &>/dev/null \
		|| ts_skip "unsupported: dd oflag=direct,append"

	# TODO: Should we check for sparse file support?

	rm -f "$testf"
}

function run_dd_test
{
    header=$1
    bs=$2
    flags=$3

    input=$(make_input_name "$header")
    INPUT="${INPUT} ${input}"
	rm -f "$input"

    if [ "$bs" = 0 ]; then
		touch $input
    else
		_dd if=/dev/zero of=$input count=1 bs=$bs $flags || return
    fi

    $TS_CMD_FINCORE --raw --output $OUT_COLUMNS  --bytes --noheadings $input

    footer "$?"
}

function run_dd_dd_test
{
    header=$1
    flags0=$2
    flags1=$3

    bs=$PAGE_SIZE

    input=$(make_input_name "$header")
    INPUT="${INPUT} ${input}"
	rm -f "$input"

    _dd if=/dev/zero of=$input count=1 bs=$bs $flags0 || return
    _dd if=/dev/zero of=$input count=1 bs=$bs $flags1 || return

    $TS_CMD_FINCORE --raw --output $OUT_COLUMNS --bytes --noheadings $input

    footer "$?"
}


PAGE_SIZE=$($TS_HELPER_SYSINFO pagesize)
WINDOW_SIZE=$(( 32 * 1024 * PAGE_SIZE ))

AGG_OUT="$TS_OUTDIR/count.aggregate"
AGG_ERR="$TS_OUTDIR/count.err.aggregate"

# we use PAGE_SIZE dependent output for a few systems
if test -f "$TS_EXPECTED.$PAGE_SIZE"; then
	cat "$TS_EXPECTED.$PAGE_SIZE" >"$AGG_OUT"
	OUT_COLUMNS="PAGES,SIZE,FILE"
else
	cat "$TS_EXPECTED.nosize" >"$AGG_OUT"
	OUT_COLUMNS="PAGES,FILE"
fi

cat "$TS_EXPECTED.total" >>"$AGG_OUT"
cat "$TS_EXPECTED_ERR" >"$AGG_ERR"

if ! $TS_CMD_FINCORE --recursive |& grep -q 'recursive option is not supported' ; then
	RECURSIVE=1
	if test -f "$TS_EXPECTED.$PAGE_SIZE"; then
		cat "$TS_EXPECTED.$PAGE_SIZE.recursive" >> "$AGG_OUT"
	else
		cat "$TS_EXPECTED.nosize.recursive" >> "$AGG_OUT"
	fi
	echo '[ RECURSIVE SCAN ]' >> "$AGG_ERR"
fi

TS_EXPECTED="$AGG_OUT"
TS_EXPECTED_ERR="$AGG_ERR"

ts_check_test_command "$TS_CMD_FINCORE"
ts_cd "$TS_OUTDIR"

check_dd_fs_feat

INPUT=
input=

ts_log_both "[ NON EXISTING FILE ]"
{
    input=no_such_file
    INPUT="${INPUT} ${input}"

    $TS_CMD_FINCORE --raw --output $OUT_COLUMNS --bytes --noheadings $input
    footer "$?"
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
    run_dd_test "EMPTY FILE" 0
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
    run_dd_test "PAGESIZE -1 (incore)" $(( PAGE_SIZE - 1 ))
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
    run_dd_test "JUST PAGESIZE(incore)" $(( PAGE_SIZE ))
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
    run_dd_test "JUST PAGESIZE(directio)" $(( PAGE_SIZE )) "oflag=direct"
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
    run_dd_test "TWO PAGES(incore)" $(( 2 * PAGE_SIZE ))
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
    run_dd_test "TWO PAGES(directio)" $(( 2 * PAGE_SIZE )) "oflag=direct"
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
    run_dd_dd_test "TWO PAGES(mixed directio,incore)" \
			    oflag=direct \
			    "oflag=append seek=1"
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
    run_dd_dd_test "TWO PAGES(mixed incore,directio)" \
		   "" \
		   "oflag=direct,append seek=1"
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
    hole_count=$(( WINDOW_SIZE / PAGE_SIZE - 2 ))
    run_dd_dd_test "WINDOW SIZE(incore-sparse-incore)" \
		   "" \
		   "oflag=append seek=$hole_count"
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
    hole_count=$(( WINDOW_SIZE / PAGE_SIZE - 2 ))
    run_dd_dd_test "WINDOW SIZE(directio-sparse-directio)" \
		   "oflag=direct" \
		   "oflag=append,direct seek=$hole_count"
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
    hole_count=$(( WINDOW_SIZE / PAGE_SIZE - 2 ))
    run_dd_dd_test "WINDOW SIZE(incore-sparse-directio)" \
		   "" \
		   "oflag=append,direct seek=$hole_count"
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
    hole_count=$(( WINDOW_SIZE / PAGE_SIZE - 2 ))
    run_dd_dd_test "WINDOW SIZE(directio-sparse-incore)" \
		   "oflag=direct" \
		   "oflag=append seek=$hole_count"
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
    hole_count=$(( WINDOW_SIZE / PAGE_SIZE - 1 ))
    run_dd_dd_test "WINDOW SIZE + 1 page(incore-sparse-incore)" \
		   "" \
		   "oflag=append seek=$hole_count"
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
    hole_count=$(( WINDOW_SIZE / PAGE_SIZE - 1 ))
    run_dd_dd_test "WINDOW SIZE + 1 page(directio-sparse-directio)" \
		   "oflag=direct" \
		   "oflag=append,direct seek=$hole_count"
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
    hole_count=$(( WINDOW_SIZE / PAGE_SIZE - 1 ))
    run_dd_dd_test "WINDOW SIZE + 1 page(incore-sparse-directio)" \
		   "" \
		   "oflag=append,direct seek=$hole_count"
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
    hole_count=$(( WINDOW_SIZE / PAGE_SIZE - 1 ))
    run_dd_dd_test "WINDOW SIZE + 1 page(directio-sparse-incore)" \
		   "oflag=direct" \
		   "oflag=append seek=$hole_count"
} >> $TS_OUTPUT 2>> $TS_ERRLOG

ts_log_both "[ MULTIPLE FILES ]"
{
    $TS_CMD_FINCORE --raw --output $OUT_COLUMNS --bytes $INPUT
    footer "$?"
} >> $TS_OUTPUT 2>> $TS_ERRLOG

rm -f $INPUT
INPUT=

OLD_COLUMNS=$OUT_COLUMNS
OUT_COLUMNS=SIZE,FILE

{
	run_dd_test "EMPTY" 0
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
	run_dd_test 1b 1
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
	run_dd_test 1k 1024
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
	run_dd_test 10k 10240
} >> $TS_OUTPUT 2>> $TS_ERRLOG

ts_log_both "[ GRAND TOTAL SIZE ]"
{
    $TS_CMD_FINCORE --raw --output $OUT_COLUMNS --bytes --noheadings --total $INPUT
    footer "$?"
} >> $TS_OUTPUT 2>> $TS_ERRLOG

rm -f $INPUT
INPUT=

OUT_COLUMNS=PAGES,FILE

{
	run_dd_test "EMPTY" 0
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
	run_dd_test 1b 1
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
	run_dd_test 'ONE PAGE' $PAGE_SIZE
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
	run_dd_test 'TWO PAGES' $(( 2 * PAGE_SIZE ))
} >> $TS_OUTPUT 2>> $TS_ERRLOG

{
	run_dd_test 'TEN PAGES' $(( 10 * PAGE_SIZE ))
} >> $TS_OUTPUT 2>> $TS_ERRLOG

ts_log_both "[ GRAND TOTAL PAGES ]"
{
    $TS_CMD_FINCORE --raw --output $OUT_COLUMNS --bytes --noheadings --total $INPUT
    footer "$?"
} >> $TS_OUTPUT 2>> $TS_ERRLOG

OUT_COLUMNS=$OLD_COLUMNS

if [ -n "$RECURSIVE" ]; then
	dir=$(make_input_name dir)
	mkdir -p "$dir"

	{
	    run_dd_test "dir/EMPTY FILE" 0
	} >> $TS_OUTPUT 2>> $TS_ERRLOG

	{
	    run_dd_test "dir/PAGESIZE -1 (incore)" $(( PAGE_SIZE - 1 ))
	} >> $TS_OUTPUT 2>> $TS_ERRLOG

	{
	    run_dd_test "dir/JUST PAGESIZE(incore)" $(( PAGE_SIZE ))
	} >> $TS_OUTPUT 2>> $TS_ERRLOG

	ts_log_both "[ RECURSIVE SCAN ]"
	{
	    $TS_CMD_FINCORE --raw --output $OUT_COLUMNS --bytes --recursive --raw "$dir" |sort
	    footer "$?"
	} >> $TS_OUTPUT 2>> $TS_ERRLOG
fi

rm -f $INPUT
ts_finalize
