# SPDX-License-Identifier: GPL-2.0+
#!/bin/bash
# FS QA Test No. 556
#
# Test the basic functionality of filesystems with case-insensitive
# support.

. ./common/preamble
_begin_fstest auto quick casefold

. ./common/filter
. ./common/casefold
. ./common/attr

_supported_fs generic
_require_encrypted_casefold
_require_scratch_nocheck
_require_scratch_casefold
_require_symlinks
_require_check_dmesg
_require_attrs

sdev=$(_short_dev ${SCRATCH_DEV})

filename1="file.txt"
filename2="FILE.TXT"

pt_file1=$(echo -e "coração")
pt_file2=$(echo -e "corac\xcc\xa7\xc3\xa3o" | tr a-z A-Z)

fr_file2=$(echo -e "french_caf\xc3\xa9.txt")
fr_file1=$(echo -e "french_cafe\xcc\x81.txt")

ar_file1=$(echo -e "arabic_\xdb\x92\xd9\x94.txt")
ar_file2=$(echo -e "arabic_\xdb\x93.txt" | tr a-z A-Z)

jp_file1=$(echo -e "japanese_\xe3\x82\xb2.txt")
jp_file2=$(echo -e "japanese_\xe3\x82\xb1\xe3\x82\x99.txt")

# '\xc3\x00' is an invalid sequence. Despite that, the sequences
# below could match, if we ignored the error.  But we don't want
# to be greedy at normalization, so at the first error we treat
# the entire sequence as an opaque blob.  Therefore, these two
# must NOT match.
blob_file1=$(echo -e "corac\xcc\xa7\xc3")
blob_file2=$(echo -e "coraç\xc3")

filter_touch()
{
    _filter_touch | _filter_scratch
}

# Test helpers
basic_create_lookup()
{
	local basedir=${1}
	local exact=${2}
	local lookup=${3}

	touch "${basedir}/${exact}"
	[ -f "${basedir}/${lookup}" ] || \
		echo "lookup of ${exact} using ${lookup} failed"
	_casefold_check_exact_name "${basedir}" "${exact}" || \
		echo "Created file ${exact} with wrong name."
}

# CI search should fail.
bad_basic_create_lookup()
{
	local basedir=${1}
	local exact=${2}
	local lookup=${3}

	touch "${basedir}/${exact}"
	[ -f "${basedir}/${lookup}" ] && \
		echo "Lookup of ${exact} using ${lookup} should fail"
}

# Testcases
test_casefold_lookup()
{
	local basedir=${SCRATCH_MNT}/casefold_lookup

	mkdir -p ${basedir}
	_casefold_set_attr ${basedir}

	basic_create_lookup "${basedir}" "${filename1}" "${filename2}"
	basic_create_lookup "${basedir}" "${pt_file1}" "${pt_file2}"
	basic_create_lookup "${basedir}" "${fr_file1}" "${fr_file2}"
	basic_create_lookup "${basedir}" "${ar_file1}" "${ar_file2}"
	basic_create_lookup "${basedir}" "${jp_file1}" "${jp_file2}"
}

test_bad_casefold_lookup()
{
	local basedir=${SCRATCH_MNT}/casefold_lookup

	mkdir -p ${basedir}

	bad_basic_create_lookup ${basedir} ${blob_file1} ${blob_file2}
}

do_create_and_remove()
{
	local basedir=${1}
	local exact=${2}
	local casefold=${3}

	basic_create_lookup ${basedir} ${exact} ${casefold}
	rm -f ${basedir}/${exact}
	[ -f ${basedir}/${exact} ] && \
		echo "File ${exact} was not removed using exact name"

	basic_create_lookup ${basedir} ${exact} ${casefold}
	rm -f ${basedir}/${casefold}
	[ -f ${basedir}/${exact} ] && \
		echo "File ${exact} was not removed using inexact name"
}

# remove and recreate
test_create_and_remove()
{
	local basedir=${SCRATCH_MNT}/create_and_remove
	mkdir -p ${basedir}

	_casefold_set_attr ${basedir}
	do_create_and_remove "${basedir}" "${pt_file1}" "${pt_file2}"
	do_create_and_remove "${basedir}" "${jp_file1}" "${jp_file2}"
	do_create_and_remove "${basedir}" "${ar_file1}" "${ar_file2}"
	do_create_and_remove "${basedir}" "${fr_file1}" "${fr_file2}"
}

test_casefold_flag_basic()
{
	local basedir=${SCRATCH_MNT}/basic

	mkdir -p ${basedir}
	_casefold_set_attr ${basedir}
	_casefold_lsattr_dir ${basedir} | _filter_scratch

	_casefold_unset_attr ${basedir}
	_casefold_lsattr_dir ${basedir} | _filter_scratch
}

test_casefold_flag_removal()
{
	local basedir=${SCRATCH_MNT}/casefold_flag_removal

	mkdir -p ${basedir}
	_casefold_set_attr ${basedir}
	_casefold_lsattr_dir ${basedir} | _filter_scratch

	# Try to remove +F attribute on non empty directory
	touch ${basedir}/${filename1}
	_casefold_unset_attr ${basedir} &>/dev/null
	_casefold_lsattr_dir ${basedir} | _filter_scratch
}

# Test Inheritance of casefold flag
test_casefold_flag_inheritance()
{
	local basedir=${SCRATCH_MNT}/flag_inheritance
	local dirpath1="d1/d2/d3"
	local dirpath2="D1/D2/D3"

	mkdir -p ${basedir}
	_casefold_set_attr ${basedir}

	mkdir -p ${basedir}/${dirpath1}
	_casefold_lsattr_dir ${basedir}/${dirpath1} | _filter_scratch

	[ -d ${basedir}/${dirpath2} ] || \
		echo "Directory CI Lookup failed."
	_casefold_check_exact_name "${basedir}" "${dirpath1}" || \
		echo "Created directory with wrong name."

	touch ${basedir}/${dirpath2}/${filename1}
	[ -f ${basedir}/${dirpath1}/${filename2} ] || \
		echo "Couldn't create file on casefolded parent."
}

# Test nesting of sensitive directory inside insensitive directory.
test_nesting_sensitive_insensitive_tree_simple()
{
	local basedir=${SCRATCH_MNT}/sd1

	mkdir -p ${basedir}
	_casefold_set_attr ${basedir}

	mkdir -p ${basedir}/sd1
	_casefold_set_attr ${basedir}/sd1

	mkdir ${basedir}/sd1/sd2
	_casefold_unset_attr ${basedir}/sd1/sd2

	touch ${basedir}/sd1/sd2/${filename1}
	[ -f ${basedir}/sd1/sd2/${filename1} ] || \
		echo "Exact nested file lookup failed."
	[ -f ${basedir}/sd1/SD2/${filename1} ] || \
		echo "Nested file lookup failed."
	[ -f ${basedir}/sd1/SD2/${filename2} ] && \
		echo "Wrong file lookup passed, should have fail."
}

test_nesting_sensitive_insensitive_tree_complex()
{
	# Test nested-directories
	local basedir=${SCRATCH_MNT}/nesting

	mkdir -p ${basedir}
	_casefold_set_attr ${basedir}

	mkdir ${basedir}/nd1
	_casefold_set_attr ${basedir}/nd1
	mkdir ${basedir}/nd1/nd2
	_casefold_unset_attr ${basedir}/nd1/nd2
	mkdir ${basedir}/nd1/nd2/nd3
	_casefold_set_attr ${basedir}/nd1/nd2/nd3
	mkdir ${basedir}/nd1/nd2/nd3/nd4
	_casefold_unset_attr ${basedir}/nd1/nd2/nd3/nd4
	mkdir ${basedir}/nd1/nd2/nd3/nd4/nd5
	_casefold_set_attr ${basedir}/nd1/nd2/nd3/nd4/nd5

	[ -d ${basedir}/ND1/ND2/nd3/ND4/nd5 ] || \
		echo "Nest-dir Lookup failed."
	[ -d ${basedir}/nd1/nd2/nd3/nd4/ND5 ] && \
		echo "ND5: Nest-dir Lookup passed, it should fail."
	[ -d ${basedir}/nd1/nd2/nd3/ND4/nd5 ] || \
		echo "Nest-dir Lookup failed."
	[ -d ${basedir}/nd1/nd2/ND3/nd4/ND5 ] && \
		echo "ND3: Nest-dir Lookup passed, it should fail."
}

test_symlink_with_inexact_name()
{
	local basedir=${SCRATCH_MNT}/symlink

	mkdir -p ${basedir}
	_casefold_set_attr ${basedir}

	mkdir ${basedir}/ind1
	mkdir ${basedir}/ind2
	_casefold_set_attr ${basedir}/ind1
	touch ${basedir}/ind1/target

	ln -s ${basedir}/ind1/TARGET ${basedir}/ind2/link
	[ -L ${basedir}/ind2/link ] || echo "Not a symlink."
	readlink -e ${basedir}/ind2/link | _filter_scratch
}

do_test_name_preserve()
{
	local basedir=${1}
	local exact=${2}
	local casefold=${3}

	touch ${basedir}/${exact}
	rm ${basedir}/${exact}

	touch ${basedir}/${casefold}
	_casefold_check_exact_name ${basedir} ${casefold} ||
		echo "${casefold} was not created with exact name"
}

# Name-preserving tests
# We create a file with a name, delete it and create again with an
# equivalent name.  If the negative dentry wasn't invalidated, the
# file might be created using $1 instead of $2.
test_name_preserve()
{
	local basedir=${SCRATCH_MNT}/test_name_preserve

	mkdir -p ${basedir}
	_casefold_set_attr ${basedir}

	do_test_name_preserve "${basedir}" "${pt_file1}" "${pt_file2}"
	do_test_name_preserve "${basedir}" "${jp_file1}" "${jp_file2}"
	do_test_name_preserve "${basedir}" "${ar_file1}" "${ar_file2}"
	do_test_name_preserve "${basedir}" "${fr_file1}" "${fr_file2}"
}

do_test_dir_name_preserve()
{
	local basedir=${1}
	local exact=${2}
	local casefold=${3}

	mkdir ${basedir}/${exact}
	rmdir ${basedir}/${exact}

	mkdir ${basedir}/${casefold}
	_casefold_check_exact_name ${basedir} ${casefold} ||
		echo "${casefold} was not created with exact name"
}

test_dir_name_preserve()
{
	local basedir=${SCRATCH_MNT}/"dir-test_name_preserve"

	mkdir -p ${basedir}
	_casefold_set_attr ${basedir}

	do_test_dir_name_preserve "${basedir}" "${pt_file1}" "${pt_file2}"
	do_test_dir_name_preserve "${basedir}" "${jp_file1}" "${jp_file2}"
	do_test_dir_name_preserve "${basedir}" "${ar_file1}" "${ar_file2}"
	do_test_dir_name_preserve "${basedir}" "${fr_file1}" "${fr_file2}"
}

test_name_reuse()
{
	local basedir=${SCRATCH_MNT}/reuse
	local reuse1=fileX
	local reuse2=FILEX

	mkdir ${basedir}
	_casefold_set_attr ${basedir}

	touch ${basedir}/${reuse1}
	rm -f ${basedir}/${reuse1} || echo "File lookup failed."
	touch ${basedir}/${reuse2}
	_casefold_check_exact_name "${basedir}" "${reuse2}" || \
		echo "File created with wrong name"
	_casefold_check_exact_name "${basedir}" "${reuse1}" && \
		echo "File created with the old name"
}

test_create_with_same_name()
{
	local basedir=${SCRATCH_MNT}/same_name

	mkdir ${basedir}
	_casefold_set_attr ${basedir}

	mkdir -p ${basedir}/same1/same1
	touch ${basedir}/SAME1/sAME1/sAMe1
	touch -c ${basedir}/SAME1/sAME1/same1 ||
		echo "Would create a new file instead of using old one"
}

test_file_rename()
{
	local basedir=${SCRATCH_MNT}/rename

	mkdir -p ${basedir}
	_casefold_set_attr ${basedir}

	touch ${basedir}/rename

	# Move to an equivalent name should not work
	mv ${basedir}/rename ${basedir}/RENAME 2>&1 | \
		_filter_scratch

	_casefold_check_exact_name ${basedir} "rename" || \
		echo "Name shouldn't change."
}

test_toplevel_dir_rename()
{
	local dir=${SCRATCH_MNT}/dir_rename

	# With the cache cold, rename a casefolded directory located in the
	# top-level directory.  If $MOUNT_OPTIONS contains
	# test_dummy_encryption, this detects the bug that was fixed by
	# 'f2fs: don't use casefolded comparison for "." and ".."'.
	mkdir ${dir}
	_casefold_set_attr ${dir}
	sync
	echo 2 > /proc/sys/vm/drop_caches
	mv ${dir} ${dir}.new
}

# Test openfd with casefold.
# 1. Delete a file after gettings its fd.
# 2. Then create new dir with same name
test_casefold_openfd()
{
	local basedir=${SCRATCH_MNT}/openfd
	local ofd1="openfd"
	local ofd2="OPENFD"

	mkdir -p ${basedir}
	_casefold_set_attr ${basedir}

	exec 3<> ${basedir}/${ofd1}
	rm -rf ${basedir}/${ofd1}
	mkdir ${basedir}/${ofd2}
	[ -d ${basedir}/${ofd2} ] || echo "Not a directory"
	_casefold_check_exact_name ${basedir} "${ofd2}" ||
		echo "openfd file was created using old name"
	rm -rf ${basedir}/${ofd2}
	exec 3>&-
}

# Test openfd with casefold.
# 1. Delete a file after gettings its fd.
# 2. Then create new file with same name
# 3. Read from open-fd and write into new file.
test_casefold_openfd2()
{
	local basedir=${SCRATCH_MNT}/openfd2
	local ofd1="openfd"
	local ofd2="OPENFD"

	mkdir ${basedir}
	_casefold_set_attr ${basedir}

	date > ${basedir}/${ofd1}
	exec 3<> ${basedir}/${ofd1}
	rm -rf ${basedir}/${ofd1}
	touch ${basedir}/${ofd1}
	[ -f ${basedir}/${ofd2} ] || echo "Not a file"
	read data <&3
	echo $data >> ${basedir}/${ofd1}
	exec 3>&-
}

test_hard_link_lookups()
{
	local basedir=${SCRATCH_MNT}/hard_link

	mkdir ${basedir}
	_casefold_set_attr ${basedir}

	touch ${basedir}/h1
	ln ${basedir}/H1 ${SCRATCH_MNT}/h1
	cnt=`stat -c %h ${basedir}/h1`
	[ $cnt -eq 1 ] && echo "Unable to create hardlink"

	# Create hardlink for casefold dir file and inside regular dir.
	touch ${SCRATCH_MNT}/h2
	ln ${SCRATCH_MNT}/h2 ${basedir}/H2
	cnt=`stat -c %h ${basedir}/h2`
	[ $cnt -eq 1 ] && echo "Unable to create hardlink"
}

test_xattrs_lookups()
{
	local basedir=${SCRATCH_MNT}/xattrs

	mkdir ${basedir}
	_casefold_set_attr ${basedir}

	mkdir -p ${basedir}/x

	${SETFATTR_PROG} -n user.foo -v bar ${basedir}/x
	${GETFATTR_PROG} --absolute-names -n user.foo \
		${basedir}/x | _filter_scratch

	touch ${basedir}/x/f1
	${SETFATTR_PROG} -n user.foo -v bar ${basedir}/x/f1
	${GETFATTR_PROG} --absolute-names -n user.foo \
		${basedir}/x/f1 | _filter_scratch
}

test_lookup_large_directory()
{
	local basedir=${SCRATCH_MNT}/large

	mkdir -p ${basedir}
	_casefold_set_attr ${basedir}

	touch $(seq -f "${basedir}/file%g" 0 2000)

	# We really want to spawn a single process here, to speed up the
	# test, but we don't want the output of 2k files, except for
	# errors.
	cat $(seq -f "${basedir}/FILE%g" 0 2000) || \
		echo "Case on large dir failed"
}

test_strict_mode_invalid_filename()
{
	local basedir=${SCRATCH_MNT}/strict

	mkdir -p ${basedir}
	_casefold_set_attr ${basedir}

	# These creation commands should fail, since we are on strict
	# mode.
	touch "${basedir}/${blob_file1}" 2>&1 | filter_touch
	touch "${basedir}/${blob_file2}" 2>&1 | filter_touch
}

#############
# Run tests #
#############

_scratch_mkfs_casefold >>$seqres.full 2>&1

_scratch_mount

_check_dmesg_for \
	"\(${sdev}\): Using encoding defined by superblock: utf8" || \
	_fail "Could not mount with encoding: utf8"

test_casefold_flag_basic
test_casefold_lookup
test_bad_casefold_lookup
test_create_and_remove
test_casefold_flag_removal
test_casefold_flag_inheritance
test_nesting_sensitive_insensitive_tree_simple
test_nesting_sensitive_insensitive_tree_complex
test_symlink_with_inexact_name
test_name_preserve
test_dir_name_preserve
test_name_reuse
test_create_with_same_name
test_file_rename
test_toplevel_dir_rename
test_casefold_openfd
test_casefold_openfd2
test_hard_link_lookups
test_xattrs_lookups
test_lookup_large_directory

_scratch_unmount
_check_scratch_fs

# Test Strict Mode
_scratch_mkfs_casefold_strict >>$seqres.full 2>&1
_scratch_mount

test_strict_mode_invalid_filename

_scratch_unmount
_check_scratch_fs

status=0
exit
