#!/bin/bash

# sbopkglint test, must be sourced by sbopkglint (not run standalone).

# PKG, PRGNAM, VERSION, ARCH are set by sbopkglint. also the current
# directory is the root of the installed package tree.

########################################################################
# for noarch packages, do nothing.
# for everything else, make sure any ELF binaries/libraries match the
# ARCH, and that libs are in the correct directory (lib vs. lib64).
# also, check the required glibc version of each bin/lib, and complain
# if it's newer than the glibc on the system.

# warnings:
# if an i?86 package has any 64-bit ELF objects (libs or bins)
# if an x86_64 package has any 32-bit ELF objects (libs or bins)
# if an i?86 package has lib64 or usr/lib64 at all
# if an x86_64 package has 64-bit libs in lib or usr/lib
# same 32/64 checking for arm (32-bit) and aarch64 (64-bit)

# note: sometimes files in /lib/firmware are ELF, and would cause
# false "wrong directory" warnings, so we exclude that dir from the
# search.

set_glibc_ver() {
	GLIBC_VER="$( /$1/libc.so.6 --version | \
                 grep version | \
                 head -n1 | \
                 sed 's,.* \([0-9]\.[0-9]\+\)\.*$,\1,' )"
	[ "$GLIBC_VER" = "" ] && note "can't get glibc version, disabling glibc check"
}

# compare the highest required version of glibc from a binary against
# the version we have installed. ugly code, sorry.
# one approach would be to run ldd and look for "version `.*' not found",
# but it's better not to run ldd on arbitrary binaries as root...
glibc_check() {
	local ver
	local highver

	[ "$GLIBC_VER" = "" ] && return

	# get the highest required glibc verson from the binary. there *must*
	# be a cleaner way to do this, but I haven't found it yet.
	ver="$( objdump -p "$1" 2>/dev/null | \
	        sed -n '/Version Ref/,$s/GLIBC_[0-9]\./&/p' | \
			  rev | cut -d_ -f1 | rev | sort -V | tail -n 1 )"
	[ "$ver" = "" ] && return  # probably a static binary

	# highver is whichever of (our glibc, binary's highest required glibc)
	# is higher (or, same as our glibc, if they're the same)
	highver="$( ( echo $GLIBC_VER ; echo $ver ) | sort -V | tail -n 1 )"

	#echo "===> ver: $ver   highver: $highver"

	if [ "$highver" != "$GLIBC_VER" ]; then
		echo "$1" >> .badglibc.$$
	fi
}

case "$ARCH" in
	noarch)  ;; # ok, do nothing.
	i?86)    WRONGDIR="lib64"; CPU="80386"   ; set_glibc_ver "lib"   ;;
	x86_64)  WRONGDIR="lib";   CPU="x86-64"  ; set_glibc_ver "lib64" ;;
	aarch64) WRONGDIR="lib";   CPU="aarch64" ; set_glibc_ver "lib64" ;;
	arm)     WRONGDIR="lib64"; CPU="ARM"     ; set_glibc_ver "lib"   ;;
	*) warn "ARCH isn't noarch, i?86, x86_64, arm, or aarch64. don't know how to check binaries." ;;
esac

if [ -n "$WRONGDIR" ]; then
	# 20230701 bkw: special case for /usr/share/qemu, it contains BIOS and such
	# for emulated systems, some of which are ELF binaries.
	# 20241008 bkw: special cases for firmware dirs.
	find * -type f \
		-a \! -path usr/share/qemu/\* \
		-a \! -path usr/share/alsa/firmware/\* \
		-a \! -path lib/firmware/\* \
		-print0 | \
		xargs -0 file -m /etc/file/magic/elf | \
		grep 'ELF.*\(executable\|shared object\)' > .tmp.$$

		while read line; do
			file="$( echo $line | cut -d: -f1 )"
			filetype="$( echo $line | cut -d: -f2 )"
			nomachine="$( echo $line | grep 'no machine' )"

			# 20230630 bkw: don't require nomachine objects to be +x.
			# AFAIK, the only thing that uses them is guile2.2, and it
			# installs them 0644.
			if [ ! "$nomachine" ]; then
				[ ! -x "$file" ] && ls -bld "$file" >> .nonexec.$$
			fi

			case "$file" in
				# 20220414 bkw: only check for libs directly in the dir.
				# this avoids e.g. lib/udev/<executable> and usr/lib/prgnam/plugins/*.so.
				# had to relax this check; it was too strict.
				# 20231216 bkw: don't check opt/ either.
				opt/*|$WRONGDIR/*/*|usr/$WRONGDIR/*/*) continue ;;
				$WRONGDIR/*|usr/$WRONGDIR/*)
					ls -lb "$file" >> .inwrongdir.$$ ;;
				usr/share/*)
					ls -lb "$file" >> .insharedir.$$ ;;
			esac

			# 64-bit packages can contain 2 types of 32-bit binaries:
			# - statically linked.
			# - statified. very few of these exist, and we can't make
			#   them on 15.0 (statifier can't handle modern kernel/glibc
			#   and the author hasn't updated it).
			if [ "$ARCH" = "x86_64" ]; then
				echo "$filetype" | grep -q 'statically linked' && continue
				grep -q DL_RO_DYN_TEMP_CNT "$file" && continue
			fi

			# "no machine" ELF objects are allowed, but since they still come
			# in 64-bit and 32-bit varieties, they must be in the correct
			# directory.
			if ! echo "$filetype" | grep -q -e "$CPU" -e 'no machine'; then
				ls -lb "$file" >> .wrongarch.$$
			fi

			# don't check "no machine" ELF objects for being stripped.
			# our strip command doesn't know how to strip them!
			# also, check for binaries that need too new of a glibc.
			if [ ! "$nomachine" ]; then
				if echo "$filetype" | grep -q "not stripped"; then
					ls -lb "$file" >> .notstripped.$$
				fi
				glibc_check "$file"
			fi
		done < .tmp.$$
		rm -f .tmp.$$
fi

[ -s .inwrongdir.$$ ] && warn "shared lib(s) in wrong dir for ARCH:" && cat .inwrongdir.$$
[ -s .wrongarch.$$ ] && warn "ELF object(s) with wrong arch (should be $CPU):" && cat .wrongarch.$$
[ -s .notstripped.$$ ] && warn "ELF object(s) not stripped:" && cat .notstripped.$$
[ -s .nonexec.$$ ] && warn "ELF binaries/libraries should be executable:" && cat .nonexec.$$
[ -s .badglibc.$$ ] && warn "ELF binaries/libraries with bad glibc version:" && cat .badglibc.$$

if [ "$WRONGDIR" = "lib64" ]; then
	[ -e usr/lib64 ] && warn "32-bit $ARCH package may not contain /usr/lib64"
	[ -e lib64 ] && warn "32-bit $ARCH package may not contain /lib64"
elif [ "$WRONGDIR" = "lib" -a -e usr/lib ]; then
	note "64-bit $ARCH package contains /usr/lib; this may or may not be a problem."
fi

rm -f .inwrongdir.$$ .wrongarch.$$ .notstripped.$$ .nonexec.$$ .badglibc.$$
