#!/usr/bin/env bash
. "${0%/*}"/../lib/common.bashlib || exit 1 #C#
init
depend diff grep portageq qfile qlist sed sort tr uniq
esanitize
include atomf
usage <<-EOU
	Usage: ${0##*/} [option]... <atom>

	Perform basic QA checks based on vdb's contents (/var/db/pkg). Currently
	supports comparing RDEPEND with DT_NEEDED (i.e. from \`scanelf -n\`), and
	doing simple verification of binding operators / slots.

	<atom> can be the exact vdb entry, or anything portage accepts
	  e.g. sys-devel/gcc:10, portage-utils, ~sys-apps/portage-3.0.20

	Options:
	  -s, --no-slot            Exclude slots (e.g. needing :3 on gtk+, or :*)
	  -b, --no-bind            Exclude binding operators (implies --no-overbind)
	  -o, --no-overbind        Exclude superfluous binds (i.e. for subslot 0)
	  -x, --no-extra           Exclude listed dependencies that seem unused
	(using all --no-* or -sbx will limit to entirely missing deps)

	  -e, --exclude=LIST       Comma separated list of category/package to
	                           exclude, can also be specified multiple times
	                           (example: dev-python/*,sys-libs/glibc,*/gcc)
	      --exclude-slot=LIST  Like --exclude but to selectively --no-slot
	      --exclude-bind=LIST  Like --exclude but to selectively --no-bind
	      --exclude-extra=LIST Like --exclude but to selectively --no-extra
	      --exclude-lib=LIST   Similar to --exclude but takes .so libraries
	                           as arguments and excludes if seen in DT_NEEDED
	(packages that provide no shared libraries are always excluded)

	  -D, --depend             Check against DEPEND rather than RDEPEND
	                           (virtuals will still use RDEPEND)

	  -W, --confirm            Show confirmation if no issues rather than silence

	  -u, --ignore-uninstalled Allows to keep going if a dependency that should
	                           be in vdb, isn't. In theory this should never
	                           happen without package.provided or bugs.

	      --ldconf=CONF        ld.so.conf to use (default: ${EROOT}/etc/ld.so.conf)
	  -p, --no-ldpath          Disable using ld.so.conf to find libraries' path
	(ldpath allows more accurate reports, but may miss rpath-based dependencies)

	  -U, --unified            Use \`diff -U\` for output instead builtin
	  -F, --full               When showing differences, display unchanged as well

	  -c, --no-color           Disable use of colors

	  -r, --no-default-exclude Ignore configuration files for excludes
	      --confdir=PATH       Configuration dir to use instead of defaults
	                           (@confdir@ + ${XDG_CONFIG_HOME:-~/.config}/@package@)
	      --dumpconfig         Display config and exit (> ${0##*/}.conf)

	      --root=PATH          Set ROOT (command-line-only, default: '${ROOT}')
	      --eprefix=PATH       Set EPREFIX (likewise, default: '${EPREFIX}')

	  -h, --help               Display usage information and exit
	      --version            Display version information and exit

	*Notes*
	Output is not a hard indication that something needs fixing (especially
	with seemingly unused dependencies that may still be using dlopen(),
	executables, or data files) and needs a human to interpret it. Exclusions
	can be used if something is known to be right.

	*Known Limitations*
	Missing slots won't be displayed when a binding operator (:=) is used as
	portage records the slot even if it was missing from the ebuild.

	No warning is given for libraries that are missing entirely or not associated
	with an installed package. For the former, please use portage's default enabled
	FEATURES="qa-unresolved-soname-deps" to be informed.

	*Portage Integration*
	Can be integrated by using ${EROOT}/etc/portage/bashrc, either by using the
	example ${ROOT}@datadir@/bashrc or by manually adding:

	    source @datadir@/${0##*/}.bashrc

	    post_pkg_postinst() {
	        qa-vdb_post_pkg_postinst
	    }

	bashrc environment options (export/make.conf/package.env):
	  QA_VDB=y | =n         Enable or disable, can also use IWDT_ALL=y | =n
	  QA_VDB_CMD=${0##*/}     This script, needs to be changed if not in PATH
	  QA_VDB_ARGS=          Extra arguments to pass, see options above
	  QA_VDB_LOG=eqawarn    Portage output command, can also use IWDT_LOG=ewarn
	Note: eqawarn post-emerge log needs "qa" in make.conf's PORTAGE_ELOG_CLASSES
EOU
optauto args "${@}" <<-EOO
	s|!slot=bool:true
	b|!bind=bool:true
	o|!overbind=bool:true
	x|!extra=bool:true
	e|exclude=str:
	exclude-slot=str:
	exclude-bind=str:
	exclude-extra=str:
	exclude-lib=str:
	ignore=str:
	D|depend=bool:false
	W|confirm=bool:false
	u|ignore-uninstalled=bool:false
	ldconf=str:${EROOT}/etc/ld.so.conf
	p|!ldpath=bool:true
	U|unified=bool:false
	F|full=bool:false
	r|!default-exclude=bool:true
	c|!color=bool:true
EOO
set -- "${args[@]}"; unset args

(( ${#} == 1 )) || die "no atom specified, see \`${0##*/} --help\`"

# vdb-get_dep2entry <dependency>
#	Echos the conversion of a full ">=category/package-ver:slot/sub[use]"
#	to a matching vdb entry. May not always be a perfect match.
vdb-get_dep2entry() {
	# qlist understands slots/ranges, but can't handle subslots, :* without
	# range operators, or [use]. Try to use closest sanitized dep possible.
	# Also ignore failure as it's normal if dep is not installed,
	# fickle but relevant when checking for || ( ) deps.
	local a
	atomsp a "${1}" || die
	[[ ${a[6]} == '*' ]] && a[6]=
	qlist -CqveI -- "${a[1]}${a[2]}/${a[3]}${a[4]:+-${a[4]}}${a[5]:+-r${a[5]}}${a[6]:+:${a[6]}}" | tail -n 1
}

# vdb-get_entry <atom>
# 	Echos ${CATEGORY}/${PF} (matching vdb entry) for <atom>.
#	e.g. sys-devel/gcc:10 -> sys-devel/gcc-10.3.0-r2
vdb-get_entry() {
	# check for exact match first
	if [[ -r ${VDB}/${1#=}/EAPI ]]; then
		echo "${1#=}"
		return
	fi

	# perform a search
	local match
	match=$(portageq match "${EROOT:-/}" "${1}") \
		|| die "portageq match failed for '${match}'"

	if [[ ${match} == *$'\n'* ]]; then
		msg "Multiple vdb entries for '${1}':"
		msg "${match}"
		die "specific match required"
	fi

	[[ -r ${VDB}/${match}/EAPI ]] || die "'${1}' was not found in vdb"

	echo "${match}"
}

# vdb-get_expected_slot <CATEGORY/PN(dep)> <SLOT> <SUBSLOT> <CATEGORY/PN(pkg)> [prebuilt]
#	Echos :SLOT= string that'd be expected in dependencies.
#	- if slot is non-zero, should have a :N
#	- if subslot is non-zero, should (usually) have :=
#	 (dev-qt/* notable exception to the above)
#	- if both are 0, this returns nothing
#   - prebuilt cause a special exclusion for binding operators
#     TODO: maybe it should show exact subslots?
vdb-get_expected_slot() {
	local slot=${2} subslot=${3}

	if ! ${O[slot]} || vdb-is_excluded exclude-slot "${1}" "${4}"; then
		 slot=
	fi
	if ! ${O[bind]} || vdb-is_excluded exclude-bind "${1}" "${4}" || [[ ${5:-} == prebuilt ]]; then
		subslot=0
	fi

	[[ ${slot} == 0 ]] && slot=
	[[ ${subslot} != 0 ]] && slot+='='
	[[ ${slot} ]] && echo ":${slot}"
}

# vdb-get_libdiff <CATEGORY/PN>
#	Echoes difference between vdb-get_libdeps and VDB_LIBNEED,
#	or nothing if no changes.
vdb-get_libdiff() {
	vdb-set_libneed "${1}"
	vdb-set_libdeps "${1}"

	local f1 f2
	f1=("${VDB_LIBDEPS[@]}")
	f2=("${VDB_LIBNEED[@]}")

	# insert blank line if a line isn't identical minus slot
	# same | same
	# diff <
	#      > diff
	(( ${#f1[@]} < ${#f2[@]} )) &&
		local -n f1ref=f2 f2ref=f1 || local -n f1ref=f1 f2ref=f2
	local -i i j
	for ((i=0; i < ${#f1ref[@]}; i++)); do
		for ((j=i; j < ${#f2ref[@]}; j++)); do
			if [[ ${f1ref[i]%:*} == "${f2ref[j]%:*}" &&
				! ( ${f1ref[i]} == *:* && ${f2ref[j]} == *:* &&
					${f1ref[i]##*:*[!=]} == "${f2ref[j]##*:*[!=]}" &&
					${f1ref[i]#*:} != "${f2ref[j]#*:}" ) ]]
			# ^ same name + bind, both have slot but is diff, then exception
			# no(pkg:3= | pkg:4=) yes(pkg:3= | pkg:4) yes(pkg | pkg:4=) ...
			then
				(( i == j )) || f1ref=("${f1ref[@]::i}" '' "${f1ref[@]:i}")
				continue 2
			fi
		done
		f2ref=("${f2ref[@]::i}" '' "${f2ref[@]:i}")
	done

	# create combined output
	local -i len=0
	local o mark changes=false qmlwarn=false
	for ((i=0; i < (${#f1[@]}>${#f2[@]}?${#f1[@]}:${#f2[@]}); i++)); do
		: "${f1[i]:=}${f2[i]:=}"
		if [[ ${f1[i]} == "${f2[i]}" ]]; then
			${O[full]} || continue
			mark=' '
		elif [[ ${f1[i]%:*} == "${f2[i]%:*}" ]]; then
			mark='|'; changes=true
		elif [[ ! ${f1[i]} ]]; then
			mark='>'; changes=true
		else
			mark='<'; changes=true
			[[ ${f1[i]} == @(dev-qt|kde-frameworks)/* ]] && qmlwarn=true
		fi
		(( ${#f1[i]} > len )) && len=${#f1[i]}
		o[i*2]=${f1[i]}
		o[i*2+1]="${mark} ${f2[i]}"
	done

	${changes} || return 1

	printf "%-${len}s %s\n" "${o[@]}" | map o

	# colorize, done last so not considered for alignment
	local s1s s1sc s2s s2sc
	if ${O[color]}; then
		for o in "${o[@]}"; do
			if [[ ${o} == *\|* ]]; then
				s1=${o%|*}; s1s=; s1sc=
				s2=${o#*|}; s2s=; s2sc=
				if [[ ${s1} == *:* ]]; then
					s1s=:${s1#*:}
					s1s=${s1s%% *}
				fi
				if [[ ${s2} == *:* ]]; then
					s2s=:${s2#*:}
				fi

				i=0
				while [[ ${s1s:i} || ${s2s:i} ]]; do
					if [[ ${s1s:i:1} == "${s2s:i:1}" ]]; then
						s1sc+=${C[a]}${s1s:i:1}
						s2sc+=${C[a]}${s2s:i:1}
					else
						s1sc+=${C[r]}${s1s:i:1}
						s2sc+=${C[g]}${s2s:i:1}
					fi
					((i++))
				done

				o="${C[a]}${s1//${s1s}/${s1sc}}${C[y]}|${C[a]}${s2//${s2s}/${s2sc}}${C[n]}"
			elif [[ ${o} == *\<* ]]; then
				o=${C[r]}${o}${C[n]}
			elif [[ ${o} == *\>* ]]; then
				o=${C[g]}${o}${C[n]}
			else
				o=${C[a]}${o}${C[n]}
			fi
			echo "${o}"
		done
	else
		printarray o
	fi

	${qmlwarn} && [[ " ${VDB_LIBNEED[*]}" == *" dev-qt/qtdeclarative"* ]] &&
		echo "Warning: KDE/QT may be using QML plugins, review before removing dependencies"
}

# vdb-get_libdiff_unified <CATEGORY/PN>
#	Same as vdb-get_libdiff but uses `diff -U` for output
vdb-get_libdiff_unified() {
	vdb-set_libneed "${1}"
	vdb-set_libdeps "${1}"

	local context=0
	${O[full]} && context=9999

	local output
	diff -U${context} \
		<(printarray VDB_LIBDEPS) \
		<(printarray VDB_LIBNEED) \
		| grep -v '^@@\|^---\|^+++' | map output
	[[ ${PIPESTATUS[*]} == [01]\ [01]\ 0 ]] || die "diff failed for ${1}"

	(( ${#output[@]} )) || return 0

	local color qmlwarn=false
	for output in "${output[@]}"; do
		case ${output::1} in
			+) color=${C[g]};;
			-) color=${C[r]}
				[[ ${output} == -@(dev-qt|kde-frameworks)/* ]] && qmlwarn=true
			;;
			*) color=${C[a]};;
		esac
		echo "${color}${output}${C[n]}"
	done
	${qmlwarn} && [[ " ${VDB_LIBNEED[*]}" == *" dev-qt/qtdeclarative"* ]] &&
		echo "Warning: KDE/QT may be using QML plugins, review before removing dependencies"
}

# vdb-get_ldpath <array> <libraries...>
#	Set <array> to full path first match for all libraries based on
#	VDB_LDPATH, or keep same without path if not found.
#	Returns true if all paths were found, false otherwise unless
#	O[ldpath] is disabled.
declare -A VDB_LDPATH_CACHE
vdb-get_ldpath() {
	local fullpath errno=0
	local -n outref=${1} #!SC2178
	shift
	outref=()
	while (( ${#} )); do
		if ! ${O[ldpath]}; then
			outref+=("${1}")
			shift
			continue
		fi

		if [[ ! ${VDB_LDPATH_CACHE[${1}]+x} ]]; then
			for fullpath in "${VDB_LDPATH[@]/%//${1}}"; do
				if [[ -e ${fullpath} ]]; then
					VDB_LDPATH_CACHE[${1}]=${fullpath#"${ROOT}"}
					break
				fi
			done
			[[ ${VDB_LDPATH_CACHE[${1}]+x} ]] || VDB_LDPATH_CACHE[${1}]=${1}
		fi

		[[ ${VDB_LDPATH_CACHE[${1}]} != "${1}" ]] || errno=1

		outref+=("${VDB_LDPATH_CACHE[${1}]}")

		shift
	done
	return ${errno}
}

# vdb-get_deps <CATEGORY/PF> [glob|-] [DEPEND|BDEPEND]
#	Echos vdb's RDEPEND as "CATEGORY/PF CATEGORY/PN SLOT SUBSLOT"
#	- For || ( ) deps, only installed version is shown.
#	- If slot is '*', will be converted to vdb's current slot
#	- When unspecified, SLOT/SUBSLOT will default to 0
#	- if glob is set, only return matching dependencies (- to pass)
#   - if *DEPEND is set, match against it rather than default
vdb-get_deps() {
	${O[depend]} && deptype=DEPEND || deptype=RDEPEND
	(( ${#} >= 3 )) && deptype=${3}

	[[ -e ${VDB}/${1}/${deptype} ]] || return 0

	local atom entry rdep
	local -A deps=()
	local -i group=0
	split- rdep < "${VDB}/${1}/${deptype}" || die "failed to read ${deptype} for '${1}'"
	for rdep in "${rdep[@]}"; do
		[[ ${rdep} == !* ]] && continue # ignore blockers

		# Handle ( ) and || ( ) deps loosely (allow missing deps within a group)
		[[ ${rdep} == '||' ]] && continue
		if [[ ${rdep} == '(' ]]; then
			(( group++ ))
			continue
		fi
		if [[ ${rdep} == ')' ]]; then
			(( group-- ))
			continue
		fi

		# check for [glob] against atom
		# note: would prefer to do this post-atomsp but this gets ~30x slower
		[[ ${#} -ge 2 && ${2} != - ]] && [[ ${rdep} != *${2}* ]] && continue

		# find vdb entry for dep
		entry=$(vdb-get_dep2entry "${rdep}")

		if [[ ! ${entry} ]]; then
			(( group )) && continue
			${O[ignore-uninstalled]} && continue
			die "no installed provider in vdb for '${rdep}' (may be due to binpkgs ordering / --nodeps / package.provided)"
		fi

		atomsp atom "${rdep}" || die
		: "${atom[6]:=0}"
		: "${atom[7]:=0}"

		# replace wildcard by vdb's used slot
		[[ ${atom[6]} == \* ]] && atom[6]=$(vdb-get_slot "${entry}")

		# pretend have a non-zero subslot if bound to simplify
		[[ ${atom[8]} == = && ${atom[7]} == 0 ]] && atom[7]=1

		# in case of duplicates, keep the one that defines a subslot
		if [[ ${deps[${entry}]+x} ]]; then
			split rdep "${deps[${entry}]}"
			[[ ${atom[7]} == 0 || ${rdep[2]} != 0 ]] && continue
		fi

		deps[${entry}]="${entry} ${atom[2]}/${atom[3]} ${atom[6]} ${atom[7]}"
	done

	printarray deps
}

# vdb-get_slot <CATEGORY/PF>
#	Echos slot from vdb
vdb-get_slot() {
	local slot

	if [[ -e "${VDB}"/${1}/SLOT ]]; then
		slot=$(<"${VDB}/${1}/SLOT") || die "failed to read slot for '${1}'"
		echo "${slot%/*}"
		return
	fi

	echo 0
}

# vdb-get_subslot <CATEGORY/PF>
#	Echos subslot from vdb
vdb-get_subslot() {
	local subslot

	if [[ -e "${VDB}"/${1}/SLOT ]]; then
		subslot=$(<"${VDB}/${1}/SLOT") || die "failed to read subslot for '${1}'"
		if [[ ${subslot} == */* ]]; then
			echo "${subslot%*/}"
			return
		fi
	fi

	echo 0
}

# vdb-is_excluded <exclude-*|ignore> [CATEGORY/PN(dep) or lib.so] <CATEGORY/PN(pkg)>
#	Return true if dep shouldn't be considered when checking pkg
#	Types are based on --exclude* options and qa-vdb.* config files
#	(dep) can be missing for ignore type
vdb-is_excluded() {
	[[ ${O[${1}]} ]] || return 1

	# strip = prefixes if matches package to activate them
	[[ ${2%:*} =~ ^(${O[${1}]//${3:-nil}=/})$ ]]
}

# vdb-is_prebuilt <CATEGORY/PF>
#	Return true if QA_PREBUILT is set at all
#	TODO: verify contents/regexes for partially prebuilt situations
vdb-is_prebuilt() {
	[[ -e ${VDB}/${1}/QA_PREBUILT ]]
}

# vdb-is_providing <CATEGORY/PF>
#	Return true if providing shared libraries
vdb-is_providing() {
	[[ -e ${VDB}/${1}/PROVIDES ]] || return 1

	# special exception that relies on .pc rpath injection
	[[ ${1} == */ffmpeg-compat-* ]] && return 0

	# This file can contain modules and executables we aren't
	# interested in and modules sometime have "lib*.so" names.
	# To rule them out, check if exists in LDPATH
	local unused provided #!SC2034
	split- provided < "${VDB}/${1}/PROVIDES" || die "could not read PROVIDES for ${1}"
	for provided in "${provided[@]}"; do
		[[ ${provided} == *.so* ]] || continue

		${O[ldpath]} || return 0

		vdb-get_ldpath unused "${provided}" && return 0

		# special exception for haskell as it doesn't use linker path
		# TODO: is this going to need a configuration file?
		[[ ${provided} == *-ghc* ]] && return 0
	done

	return 1
}

# vdb-print_libdiff <CATEGORY/PF>
# 	Prints output of vdb_get_libdiff (if any) with header
vdb-print_libdiff() {
	local diff deptype

	if ${O[unified]}; then
		diff=$(vdb-get_libdiff_unified "${1}")
	else
		diff=$(vdb-get_libdiff "${1}")
	fi

	${O[depend]} && deptype=DEPEND || deptype=RDEPEND

	if [[ ${diff} ]]; then
		msg "VDB: detected possibly incorrect ${deptype} (${1})"
		msg "${diff}"

		if ! [[ ${O[exclude]} || ${O[exclude-slot]} || ${O[exclude-bind]} || ${O[exclude-extra]} || ${O[exclude-lib]} ]] &&
			${O[default-exclude]}; then
			msg "Note: no exclusions set (e.g. glibc/gcc), please verify configuration files"
		fi
	elif ${O[confirm]}; then
		msg "VDB: ${deptype} is as expected, no issues here (${1})"
	fi
}

# vdb-set_exclude <exclude-*>
#   Load exclude configs and prepare supplied ${O[exclude*]} for use
vdb-set_exclude() {
	if ${O[default-exclude]}; then
		local file
		local -a conf=()

		for file in "${CONFDIRS[@]/%//${0##*/}.${1}}"; do
			[[ -r ${file} ]] && conf+=("${file}")
		done

		if (( ${#conf[@]} )); then
			O[${1}]+=,$(sed 's/#.*//' "${conf[@]}" | tr '\r\n' ',,' \
				|| die "failed reading exclude configuration files")
		fi
	fi

	# create a simple a|b|c, escape +/., and * -> .*
	O[${1}]=$(sed \
		-e 's/[^A-Za-z0-9_/.+*,=-]//g' \
		-e 's/,,*/|/g;s/^|//;s/|$//' \
		-e 's/+/\\+/g;s/\./\\./g;s/\*/.*/g' \
		<<< "${O[${1}]}") || die "sed failed"
}

# vdb-set_libdeps <CATEGORY/PF>
#	Set VDB_LIBDEPS array to the package list providing shared libraries
#	based on <CATEGORY/PF>'s vdb RDEPEND.
#	Output should be identical to VDB_LIBNEED if no missing deps.
#	Note: vdb-set_libneed must be called first
vdb-set_libdeps() {
	VDB_LIBDEPS=()

	local pkg prebuilt=
	pkg=$(atomf %p "${1}") || die
	vdb-is_prebuilt "${1}" && prebuilt=prebuilt

	local provides
	local -a rdep vrdep
	vdb-get_deps "${1}" | while split- rdep; do
		# check if requested for exclusion through --exclude
		vdb-is_excluded exclude "${rdep[1]}" "${pkg}" && continue

		# need to skip entries that do not provide shared libraries but,
		# to know this, need to check what virtuals resolve to
		provides=false
		if [[ ${rdep[0]} == virtual/* ]]; then
			vdb-get_deps "${rdep[0]}" - RDEPEND | while split- vrdep; do
				if vdb-is_providing "${vrdep[0]}"; then
					provides=true
					break
				fi
			done
		elif vdb-is_providing "${rdep[0]}"; then
			provides=true
		fi
		${provides} || continue

		# if --no-overbind, discard subslot if vdb's subslot is 0
		if ! ${O[overbind]}; then
			[[ $(vdb-get_subslot "${rdep[0]}") == 0 ]] && rdep[3]=0
		fi

		# if --no-extra, exclude if not found in VDB_LIBNEED
		if ! ${O[extra]} || vdb-is_excluded exclude-extra "${rdep[1]}" "${pkg}"; then
			[[ " ${VDB_LIBNEED[*]%:*} " == *" ${rdep[1]} "* ]] || continue
		fi

		echo "${rdep[1]}$(vdb-get_expected_slot "${rdep[1]}" "${rdep[2]}" "${rdep[3]}" "${pkg}" "${prebuilt}")"
	done | sort | map VDB_LIBDEPS
	(( ${?} )) && die # indirect ${?} not to mask ERR trap !SC2181
}

# vdb-set_libneed <CATEGORY/PF>
# 	Set VDB_LIBNEED array to the package list providing shared libraries
#	based on <CATEGORY/PF>'s vdb REQUIRES.
#	- If a virtual is used, attempts to list it instead of real package.
vdb-set_libneed() {
	VDB_LIBNEED=()

	[[ -e ${VDB}/${1}/REQUIRES ]] || return 0 # no libs

	local prebuilt=
	vdb-is_prebuilt "${1}" && prebuilt=prebuilt

	local pkg
	pkg=$(atomf %p "${1}") || die

	# read REQUIRES and strip excluded libraries
	local -a req=()
	for entry in $(
		sed 's/[^ ]*: //g' < "${VDB}/${1}/REQUIRES" \
			|| die "failed to get dependencies from REQUIRES for '${1}'"); do
		vdb-is_excluded exclude-lib "${entry%.so*}.so" "${pkg}" || req+=("${entry}")
	done
	(( ${#req[@]} )) || return 0 # all libs excluded

	# try to use LDPATH to find full paths, lets qfile ignore private libraries
	vdb-get_ldpath req "${req[@]}"

	# workaround for usr-merge when /var/db/pkg is incorrect, this would trip if
	# / and /usr have same-name libraries but this is not an expected scenario
	# TODO: remove when all ::gentoo ebuild are usr-merge aware from src_install
	for entry in "${req[@]}"; do
		[[ ${entry} =~ ^/usr ]] && req+=("${entry#/usr}")
	done

	# use qfile to find providers
	local -a providers
	if ! qfile -CqvP -- "${req[@]}" | sort | uniq | map providers; then
		${O[ignore-uninstalled]} && return 0 # pretend no libs
		die "qfile couldn't find any dependencies of '${1}' (may be due to binpkgs ordering), tried to find: '${req[*]}'"
	fi

	local dep entry slot subslot output
	local -a vrdep
	for entry in "${providers[@]}"; do
		dep=$(atomf %p "${entry}") || die

		# check rdepend for virtuals and the the virtual's rdepend for this dep
		vdb-get_deps "${1}" "virtual/*" | while split- vrdep; do
			# special exception for packages using virtual/jack and pipewire at once
			# i.e. virtual/jack "can" provide pipewire making it look superfluous
			# likely more cases of this but difficult to cover
			[[ ${dep} == media-video/pipewire && ${vrdep[1]} == virtual/jack &&
				" ${providers[*]} " =~ (' media-sound/jack-audio-connection-kit-'|' media-sound/jack2-')  ]] \
				&& break

			# special exception for mesa "providing" libglvnd (not really)
			output=$(vdb-get_deps "${vrdep[0]}" "${dep}" RDEPEND)
			if [[ ${output} ]] ||
				[[ ${dep} == media-libs/libglvnd && ${vrdep[1]} == virtual/opengl ]]; then
				entry=${vrdep[0]}
				dep=${vrdep[1]}
				break
			fi
		done

		# with final cat/pn, check if requested for exclusion through --exclude
		vdb-is_excluded exclude "${dep}" "${pkg}" && continue

		# get SLOT information
		slot=0; subslot=0
		if [[ -e ${VDB}/${entry}/SLOT ]]; then
			slot=$(<"${VDB}/${entry}/SLOT") || die "failed to read slot for '${entry}'"
			if [[ ${slot} == */* ]]; then
				subslot=${slot#*/}
				slot=${slot%/*}
			fi
		fi

		echo "${dep}$(vdb-get_expected_slot "${dep}" "${slot}" "${subslot}" "${pkg}" "${prebuilt}")"
	done | sort | uniq | map VDB_LIBNEED
	(( ${?} )) && die # indirect ${?} not to mask ERR trap !SC2181
}

# vdb-set_ldpath
#	Read paths from ld.so.conf and store them in VDB_LDPATH
VDB_LDPATH=()
vdb-set_ldpath() {
	${O[ldpath]} || return 0

	set +f
	_vdb-set_ldpath "${O[ldconf]}"
	set -f
}
_vdb-set_ldpath() {
	local path
	while read -r path; do
		if [[ ${path::1} == / ]]; then
			[[ -d ${ROOT}${path} ]] && VDB_LDPATH+=("${ROOT}${path}")
		elif [[ ${path} =~ ^include\ ([^#]*) ]]; then
			path=${BASH_REMATCH[1]}
			if [[ ${path::1} == / ]]; then
				path=${EROOT}${path}
			else
				path=${1%/*}/${path}
			fi
			for path in ${path}; do
				[[ -r ${path} ]] || continue # ignore bad includes
				_vdb-set_ldpath "${path}"
			done
		fi
		:
	done < "${1}" || die "failed to read '${1}', use --no-ldpath to disable"
}

# vdb-set_vdb
#	Set VDB variable to vdb's path (usually /var/db/pkg)
vdb-set_vdb() {
	VDB=$(portageq vdb_path) || die "portageq failed to return vdb path"
	[[ -d ${VDB} ]] || die "found VDB (${VDB}) but does not appear valid"

	export Q_VDB=${VDB#"${ROOT}"} # ensure q tool use right vdb
}

# vdb-init
#	Init various settings
vdb-init() {
	vdb-set_ldpath
	vdb-set_vdb
	vdb-set_exclude exclude
	vdb-set_exclude exclude-slot
	vdb-set_exclude exclude-bind
	vdb-set_exclude exclude-extra
	vdb-set_exclude exclude-lib
	vdb-set_exclude ignore
}

# vdb-skip_ignore <CATEGORY/PF>
#	Exit if CATEGORY/PF is matched by qa-vdb.ignore config file
#	Do so silently unless O[confirm] is set
vdb-skip_ignore() {
	local catpn
	catpn=$(atomf %p "${1}") || die

	if vdb-is_excluded ignore "${catpn}"; then
		${O[confirm]} && msg "VDB: skipped due to package being in qa-vdb.ignore (${1})"
		exit 0
	fi
}

# vdb-skip_unneeded <CATEGORY/PF>
#	Skips checks that have no relevance for a package type.
#	Exit if all checks are disabled.
vdb-skip_unneeded() {
	# If no DT_NEEDED (meaning static link, or no ELF files), then there's
	# really not much we can do but the SLOT check. Binding operators also
	# rarely matter here (at most maybe if this is a static-only package
	# that wants to rebuild with new libraries on bumps, but well...)
	if ! [[ -e ${VDB}/${1}/NEEDED ]]; then
		O[extra]=false
		O[bind]=false

		if ${O[slot]}; then
			${O[confirm]} && \
				msg "Warning: ${1} has no DT_NEEDED libraries, most ${0##*/} checks are disabled"
		else
			${O[confirm]} && \
				msg "VDB: skipped due to all possible ${0##*/} checks being disabled (${1})"
			exit 0
		fi
	fi
}

vdb-init

vdbentry=$(vdb-get_entry "${1}")

vdb-skip_ignore "${vdbentry}"
vdb-skip_unneeded "${vdbentry}"
vdb-print_libdiff "${vdbentry}"

:

# vim: ts=4
