#!/bin/sh

# 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.

#######################################################################
# these directories are allowed to exist in the package, but they
# must be mode 0755 and owned by root:root. if a dir from this list
# exists but is empty, that's an error. if a top-level directory
# exists that's *not* in this list (such as /dev), that's an error.
topleveldirs="bin boot etc lib lib64 opt sbin srv usr var run"

# these directories are *required* to exist, and must be mode 0755, root:root.
# if a dir from this list exists but is empty, that's an error. note
# that the install/ dir no longer exists by the time we run (installpkg
# deleted it already); slack-desc is checked by the pre-doinst test.
requireddirs="usr/doc/$PRGNAM-$VERSION"

# these directories are the only ones allowed to exist directly under /usr.
# do not include X11R6 or local here, and do not include dirs which are
# symlinks on Slackware (X11 adm dict spool tmp).
# don't list (x86_64|i586|etc)-slackware-linux, it's checked separately.
usrdirs="bin doc games include info lib lib64 libexec man sbin share src x86_64-slackware-linux dict"

# these directories *must not* exist. no need to list top-level dirs here,
# the topleveldirs check already catches those. also, don't list stuff caught
# by the $usrdirs check (e.g. usr/local, usr/etc, usr/man1).
baddirs="etc/ld.so.conf.d usr/share/doc usr/share/man usr/share/info"

# these directories may only contain files with +x permissions. in
# other words, no non-executable files may live here. note that
# bindirs is a subset of fileonlydirs.
bindirs="bin usr/bin sbin usr/sbin usr/games"

# these directories may exist, but must contain only files or symlinks,
# and must be mode 0755, root:root. I thought usr/share/pixmaps
# belonged here, but quite a few packages create subdirs there for
# images required at runtime that aren't the app icon.
fileonlydirs="$bindirs usr/man/man1 usr/man/man2 usr/man/man3 usr/man/man4 usr/man/man5 usr/man/man6 usr/man/man7 usr/man/man8 usr/man/man9 usr/man/manl usr/man/mann usr/lib/pkgconfig usr/lib64/pkgconfig usr/share/pkgconfig"

# these directories may exist, but must contain only subdirectories
# (no files, symlinks, devices, etc). "." (the top-level package dir)
# doesn't need to be included here; it's checked separately.
nofiledirs="usr usr/doc usr/share usr/man usr/doc/HTML usr/share/terminfo"

# these directories may exist but must not have executable files
# anywhere under them. I would put usr/doc and etc here, but too many
# packages break that rule. usr/share/applications is listed here,
# even though Slackware's KDE packages (erroneously) install .desktop
# files +x.
noexecdirs="usr/include usr/man usr/share/pixmaps usr/share/icons usr/share/applications usr/share/appdata usr/share/mime usr/share/mime-info usr/share/glib-2.0 usr/doc/HTML usr/share/terminfo usr/lib/pkgconfig usr/lib64/pkgconfig usr/share/pkgconfig"

# these files must exist, unless -s option given.
requiredfiles="usr/doc/$PRGNAM-$VERSION/$PRGNAM.SlackBuild"

# these files must not exist.
badfiles="\
usr/lib64/perl5/perllocal.pod \
usr/lib/perl5/perllocal.pod \
usr/share/perl5/perllocal.pod \
usr/share/perl5/vendor_perl/perllocal.pod \
etc/passwd \
etc/passwd.new \
etc/shadow \
etc/shadow.new \
etc/group \
etc/group.new \
etc/gshadow \
etc/gshadow.new \
etc/ld.so.conf"

#######################################################################

# include 'hidden' files/dirs in * wildcard expansion.
shopt -s dotglob

dir_ok() {
	[ -d "$1" ] && \
		[ "$( stat -c '%A %U %G' "$1" )" = "drwxr-xr-x root root" ]
}

dir_empty() {
	[ "$( find "$1" -mindepth 1 -maxdepth 1 )" = "" ]
}

warn_badperms() {
	warn "bad permissions/owner (should be 0755 root:root): $1"
}

if LANG=C echo "$PRGNAM" | grep -q [^-._+A-Za-z0-9]; then
	warn "package name has invalid characters, only A-Z, a-z, 0-9, - + . _ are allowed";
fi

for i in *; do
	if [ ! -d "$i" ]; then
		warn "package root dir contains non-directory: $i"
	elif ! echo "$topleveldirs" | grep -q "\\<$i\\>"; then
		warn "package root dir contains non-standard directory: $i"
	elif ! dir_ok "$i"; then
		warn_badperms "$i"
	elif dir_empty "$i"; then
		warn "package contains empty top-level directory: $i"
	fi
done

for i in usr/*; do
	if [ -e "$i" ]; then
		case "$i" in
			*-slackware-linux) ;; # OK
			*)
				if ! echo "$usrdirs" | grep -q "\\<$( basename $i )\\>"; then
					warn "nonstandard directory in /usr: $i"
				fi
			;;
		esac
	fi
done

for i in $requireddirs; do
	if [ ! -d "$i" ]; then
		warn "missing required directory: $i"
	elif ! dir_ok "$i"; then
		warn_badperms "$i"
	fi
done

for i in $baddirs; do
	if [ -d "$i" ]; then
		warn "forbidden directory exists: $i"
	elif [ -e "$i" ]; then
		warn "forbidden directory exists as a non-directory: $i"
	fi
done

# 20220414 bkw: don't complain about broken symlinks here, they get checked elsewhere.
for i in $fileonlydirs; do
	[ -d "$i" ] || continue
	dir_ok "$i" || warn_badperms "$i"
	find_warnfiles "$i should only contain files, not:" \
		-L "$i" -mindepth 1 -maxdepth 1 \! \( -type l -o -type f \)
done

for i in $bindirs; do
	[ -d "$i" ] || continue
	find_warnfiles "$i should only contain executable files, not:" \
		-L "$i" -mindepth 1 -maxdepth 1 -type f \! -perm /0111
done

for i in $nofiledirs; do
	[ -d "$i" ] || continue
	dir_ok "$i" || warn_badperms "$i"
	find_warnfiles "$i should only contain directories, not:" \
		-L "$i" -mindepth 1 -maxdepth 1 \! -type d
done

if [ -z "$SLACKBUILD_MISSING_OK" ]; then
	for i in $requiredfiles; do
		[ -f "$i" ] || warn "missing required file: $i"
	done
fi

for i in $noexecdirs; do
	[ -d "$i" ] || continue
	find_warnfiles "$i should not contain files with executable permission:" \
		"$i" -type f -a -perm /0111
done

for i in $badfiles; do
	[ -e "$i" ] && warn "forbidden file: $i"
done

# 20220414 bkw: absolute symlinks used to be an error, but there are just
# too many packages that use them. so only flag them if they're broken links.
# 20230627 bkw: note absolute symlinks outside the package. used in my own
# smc build, for instance. up to the human to decide whether it's a problem.
find . -type l -lname '/*' | while read f; do
	target="$( readlink "$f" | sed 's,^/*,,' )"
	if [ ! -e "$target" ]; then
		if [ -e "/$target" ]; then
			ls -bld "$f" > .outlinks.$$
		else
			ls -bld "$f" > .badlinks.$$
		fi
	fi
done

if [ -s .badlinks.$$ ]; then
	warn "package contains broken absolute symlinks:"
	cat .badlinks.$$
fi

if [ -s .outlinks.$$ ]; then
	note "package contains symlinks outside the package (which is OK, if intentional):"
	cat .outlinks.$$
fi

rm -f .badlinks.$$ .outlinks.$$

find_warnfiles "package contains broken relative symlinks:" \
	-L . -type l \! -lname '/*'

# 20230320 bkw: empty directories. this isn't an error, just a note, and
# we ignore any empty dirs under /var because lots of packages need these.
# 20241008 bkw: ignore empty bin sbin usr/bin usr/sbin, these are caught below.
find_warnfiles --note "package contains empty dirs, are these necessary?" \
	. -type d -a -empty \
	-a \! -path "./var/*" \
	-a \! -path ./bin \
	-a \! -path ./sbin \
	-a \! -path ./usr/bin \
	-a \! -path ./usr/sbin

# 20241008 bkw: if any of /bin /sbin /usr/bin or /usr/sbin exists, they must
# not be empty.
for i in bin sbin usr/bin usr/sbin; do
	if [ -e $i -a -z "$( ls -bld $i/* 2>/dev/null )" ]; then
		warn "package contains empty $i directory, this is probably a bug in the script."
	fi
done

find_warnfiles "package contains files owned by UID/GID >= 1000" \
	. \( -uid +999 -o -gid +999 \)
