#!/bin/bash
# vim: cindent:shiftwidth=4:tabstop=4:smarttab:textwidth=100

#$title$ Debian package installation status
#$check$ unconfigured, broken or partially uninstalled packages
#$ref$ dpkg --list
#$author$ Rafal Rzeczkowski
#$version$ 0.8.5

level_check long

#CHANGELOG
#0.5.0	initial
#0.5.1	BUG: error level exit handling
#0.5.2	BUG: count the number of packages not dpkg output lines
#0.5.3	code cleanup
#0.5.4	assign the shell DEBUG flag correctly to awk
#0.5.5	metadata headers, help message
#0.5.6	scan for obsolete configuration files
#0.5.7	generalize obsolete configuration file extension name
#0.5.8	correct static analysis lint warnings from shellcheck
#0.5.9	downgrade failure report level to caution
#0.6.0	synchronize singular/plural wording with object count
#0.7.0	refactor awk integration code to avoid the use of temporary files
#0.7.1	export performance counters
#0.7.2	ingest obsolete config file names individually
#0.7.3	initialize empty array for obsolete config file names
#0.7.4	integrate with the the FIX API
#0.7.5	scan for missing checksums
#0.7.6	display all inconsistent packages
#0.8.0	move CHECKSUMS_MISSING handler to the file-integrity plugin
#0.8.1	check for leftover policy layer interface script
#0.8.2	recognize additional obsolete configuration file names
#0.8.3	recognize additional obsolete configuration file names (Debian 12)
#0.8.4	present path of policy layer helper script in failure message
#0.8.5	scan for orphaned configuration files

declare -r -a PROBLEMS=(PACKAGE_STATE_INCONSISTENT LEFTOVER_POLICY
	CONFIGURATION_FILE_OBSOLETE CONFIGURATION_FILE_ORPHANED)
test -n ${#PROBLEMS[@]}

AWK_OUTPUT=$(
COLUMNS=200 dpkg --list |
	awk --assign DEBUG="$DEBUG" '
	function errprint(msg) { if (DEBUG) {print msg > "/dev/stderr"} }
	BEGIN {
		header_skip=5
	}
	{
		if (NR > header_skip && $1 != "ii") {
			errprint("package " $2 " is in an inconsistent state {" $1 "}")
			bad_package[$2]=$1
		}
	}
	END {
		printf "%d\t",NR-header_skip
		for (package in bad_package)
			printf "%s ",package
	}')

read -r TOTAL_COUNT BAD <<< "$AWK_OUTPUT"
read -r -a BAD <<< "$BAD"
BAD_COUNT=${#BAD[@]}

performance packages_total "$TOTAL_COUNT"
performance packages_bad "$BAD_COUNT"

if [[ $BAD_COUNT -eq 0 ]]; then
	echodebug "$TOTAL_COUNT packages analyzed (all OK)"
else
	PACKAGE_S="$(plural_text "$BAD_COUNT" package)"
	fail caution "$BAD_COUNT software $PACKAGE_S in an inconsistent state: ${BAD[*]}"
	helpmsg 'reinstall/upgrade the affected packages'
	problem PACKAGE_STATE_INCONSISTENT "$(printf '%s;' "${BAD[@]}")"
	exit
fi

declare -a OLD_CONFIG_FILES=()
while IFS= read -r -d '' CONFIG_FILE; do
	OLD_CONFIG_FILES+=("$CONFIG_FILE");
done < <(find /etc -type f -a \
	\( -name '*.dpkg-*' -o -name '*.ucf-*' -o -name '*.merge-error' -o -name '*.update-*' \) -print0)
performance old_config_files ${#OLD_CONFIG_FILES[@]}
if [[ ${#OLD_CONFIG_FILES[@]} -eq 0 ]]; then
	echodebug 'no obsolete configuration files found'
else
	FILE_S=$(plural_text ${#OLD_CONFIG_FILES[@]} file)
	fail caution "${#OLD_CONFIG_FILES[@]} configuration $FILE_S obsolete"
	helpmsg "${OLD_CONFIG_FILES[*]}"
	problem CONFIGURATION_FILE_OBSOLETE "$(printf '%s;' "${OLD_CONFIG_FILES[@]}")"
	exit
fi

declare -a orphaned_config_files=()
while IFS= read -r -d '' config_file_orig; do
	config_file_base=${config_file_orig%.orig}
	if [[ ! -s "$config_file_base" ]]; then
		orphaned_config_files+=("$config_file_orig");
	fi
done < <(find /etc -type f -a \( -name '*.orig' \) -print0)
performance orphaned_config_files ${#orphaned_config_files[@]}
if [[ ${#orphaned_config_files[@]} -eq 0 ]]; then
	echodebug 'no orphaned configuration files found'
else
	FILE_S=$(plural_text ${#orphaned_config_files[@]} file)
	fail caution "${#orphaned_config_files[@]} configuration $FILE_S orphaned"
	helpmsg "${orphaned_config_files[*]}"
	problem CONFIGURATION_FILE_ORPHANED "$(printf '%s;' "${orphaned_config_files[@]}")"
	exit
fi

declare -r policy_layer_helper='/usr/sbin/policy-rc.d'
if [[ -e "$policy_layer_helper" ]]; then
	fail caution "leftover policy layer interface script $policy_layer_helper"
	helpmsg 'man invoke-rc.d'
	problem LEFTOVER_POLICY "$policy_layer_helper"
	exit
fi

ok
