#!/usr/bin/env bash
#
# Check if a given custom skeleton or overlay complies to the merged
# requirements:
#
# - for unmerged:
#   /bin            missing*, or an existing directory; not a symlink
#   /lib            missing*, or an existing directory; not a symlink
#   /sbin           missing*, or an existing directory; not a symlink
#   /usr/bin/       missing*, or an existing directory; not a symlink
#   /usr/lib/       missing*, or an existing directory; not a symlink
#   /usr/sbin/      missing*, or an existing directory; not a symlink
#
# *: must be present for skeletons, can be missing for overlays
#
# - for merged-usr: all of the above, except:
#   /bin            missing, or a relative symlink to usr/bin
#   /lib            missing, or a relative symlink to usr/lib
#   /sbin           missing, or a relative symlink to usr/sbin
#
# - for merged-bin: all of the above, except:
#   /usr/sbin       missing, or a relative symlink to bin (thus points
#                   to /usr/bin)
#
# Input:
#   --type TYPE     the type of root to check: 'skeleton' or 'overlay'
#   --merged-usr    check for merged /usr
#   --merged-bin    check for merged /usr/bin
#   $*:             the root directories (skeleton, overlays) to check
# Output:
#   stdout:         the list of non-compliant paths (empty if compliant).
# Exit code:
#   0:              in case of success (stdout will be empty)
#   !0:             if any directory to check is improperly merged
#

opts="type:,merged-usr,merged-bin"
ARGS="$(getopt -n check-merged -o "" -l "${opts}" -- "${@}")" || exit 1
eval set -- "${ARGS}"

type=
merged_usr=false
merged_bin=false
while :; do
	case "${1}" in
	(--type)
		type="${2}"
		shift 2
		;;
	(--merged-usr)
		merged_usr=true
		shift
		;;
	(--merged-bin)
		merged_bin=true
		shift
		;;
	(--)
		shift
		break
		;;
	esac
done

if [ "${type}" = "skeleton" ]; then
	strict=true
else
	strict=false
fi

report_error() {
	local type="${1}"
	local root="${2}"
	local fmt="${3}"
	shift 3

	if ${first}; then
		printf "The %s in %s is not properly setup:\n" \
			"${type}" "${root}"
	fi
	first=false
	# shellcheck disable=SC2059  # fmt *is* a format string
	printf "  - ${fmt}" "${@}"
	is_success=false
}

test_merged() {
	local type="${1}"
	local root="${2}"
	local base="${3}"
	local dir1="${4}"
	local dir2="${5}"

	if ! test -e "${root}${base}${dir1}"; then
		return 0
	elif [ "$(readlink "${root}${base}${dir1}")" = "${dir2}" ]; then
		return 0
	fi

	# Otherwise, this directory is not merged
	report_error "${type}" "${root}" \
		'%s%s should be missing, or be a relative symlink to %s\n' \
		"${base}" "${dir1}" "${dir2}"
}

test_dir() {
	local type="${1}"
	local root="${2}"
	local base="${3}"
	local dir="${4}"

	if ! test -e "${root}${base}${dir}" && ! ${strict}; then
		return 0
	elif test -d "${root}${base}${dir}" && ! test -L "${root}${base}${dir}"; then
		return 0
	fi

	# Otherwise, this entry is not a proper directory
	report_error "${type}" "${root}" \
		"%s%s should exist, be a directory, and not be a symlink\n" \
		"${base}" "${dir}"
}

is_success=true
for root; do
	first=true
	test_dir "${type}" "${root}" "/" "usr/bin"
	test_dir "${type}" "${root}" "/" "usr/lib"
	if ${merged_usr}; then
		test_merged "${type}" "${root}" "/" "bin" "usr/bin"
		test_merged "${type}" "${root}" "/" "lib" "usr/lib"
		test_merged "${type}" "${root}" "/" "sbin" "usr/sbin"
		if ${merged_bin}; then
			test_merged "${type}" "${root}" "/usr/" "sbin" "bin"
		else
			test_dir "${type}" "${root}" "/" "usr/sbin"
		fi
	else
		test_dir "${type}" "${root}" "/" "bin"
		test_dir "${type}" "${root}" "/" "lib"
		test_dir "${type}" "${root}" "/" "sbin"
		test_dir "${type}" "${root}" "/" "usr/sbin"
	fi
done

${is_success}
