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

#set -o posix
set -o errexit
set -o pipefail
set -o nounset
#set -o xtrace

#$title$ filesystem presence and sizing
#$check$ filesystems specified for this node profile are present and have the required size; lv-autosize metadata is added to the filesystem header if needed
#$ref$ KB:LogicalVolumeManagement
#$author$ Rafal Rzeczkowski
#$version$ 1.6.12

level_check long

#CHANGELOG
#0.50	initial
#0.60	size check support according to Bcfg2 parcel
#0.61	size inequality specification
#0.62	handle multiple filesystems in one pass
#0.63	filer profile
#0.64	skip further checks on NFS mounts
#0.65	BUG: allow empty match for filesystem type
#0.66	include sizing templates for the primary profile
#0.67	decrease /var/log requirement for monitor from 32 to 16
#0.68	code cleanup
#0.69	specification for filesystems that must NOT be mounted
#0.70	new NOMSv2 settings
#0.71	corrected logic error for obsolete mountpoints
#0.72	converted backup profile specs to the new format
#0.73	allow larger /var/log in smtp profile
#0.74	metadata headers, help messages
#0.75	clamp /var/lib/xen sizing to 32GiB since it is not being used yet anyway
#0.76	no more /var/lib/hvm
#0.77	recalculate /var/lib/xen size based on the largest VM memory (for migrations)
#0.78	root filesystem attributes
#0.79	root filesystem attributes - only "i"
#0.80	attempt to translate /dev/root to actual block device (Cobalt Raq only?)
#0.81	add LSN profile with increased /var/log size (for bind9 query logs)
#0.82	include (potential) DRBD overhead in volume size calculations
#0.83	/tmp permissions check (mount point and tmpfs)
#0.84	restyle according to https://kb.clearcable.ca/KB/ProgrammingStyleStandards
#0.85	allow larger root filesystem size for SOE4.3 monitor
#0.86	allow larger var filesystem size for SOE4.3 monitor
#0.87	remove temporary bind mount directory after use
#0.88	new software profile requirements for idp and netconf
#0.89	allow per-release specs; add noms43 spec
#0.90	downgrade failure report level to caution
#0.91	increase root space on NOMS43 spec
#0.92	increase filesystem sizes for monitor 4.3
#0.93	count HVM total memory instead of VM max memory for /var/lib/xen
#0.94	add profile for RPKI
#0.95	update profile for RPKI (larger /var size for database)
#0.96	increase root filesystem size for LSN (kernel upgrades)
#0.97	increase root filesystem size for Xen (kernel upgrades)
#1.0.0	cleanup and refactor logic to reduce profile variability
#1.0.1	use the correct PROFILE variable to generate the list of keys
#1.0.2	focus on SYSTEM_VG_NAME vg when checking for small system
#1.0.3	add profile for VDI
#1.0.4	add profile for stats (=noms43)
#1.0.5	detect spurious files in root and tmpfs overlays
#1.0.6	detect any non-directory inodes in the root directory
#1.0.7	add generic default (not assigned to any parcel)
#1.0.8	allow for merged /usr directories
#1.1.0	integrate with the the FIX API
#1.1.1	synchronize with the new version of the plural_text API
#1.1.2	import BCFG2_PARCEL and BCFG2_DEBIAN_VERSION
#1.1.3	find leftover files in /var/tmp
#1.2.0	detect bad ownership of root directories (Prefix)
#1.2.1	avoid the use of an empty list
#1.2.2	add pumpX host type with large partitions
#1.2.3	add SOE_SOFTWARE partition layout format and set all SW VMs to it
#1.3.0	recognize non-standard filesystem options
#1.3.1	accept data=ordered as the default journaling mode for ext4
#1.3.2	set sizes for RTBH standalone VM
#1.3.3	disable filesystem target size marking for auto-resize
#1.3.4	add /srv volume for NFCT service on lsn
#1.3.5	add fwprov profile (same as provX)
#1.3.6	update profile for RPKI (larger /var size for database)
#1.4.0	correct static analysis lint warnings from shellcheck
#1.4.1	ignore NULL keys in the generic profile
#1.4.2	store profile templates in arrays
#1.4.3	scan for temporary files in the root filesystem
#1.4.4	add a profile for RT (larger /var size for MySQL)
#1.4.5	add rns profile (identical to ns)
#1.4.5	add mneng (stats clone) and mnengdb (pgdb clone) profiles
#1.4.6	adjust root partition sizing for monitor profile
#1.4.7	update profile for Nomad v4 (/srv instead of /var/spool)
#1.4.8	ignore PrivateTmp directories in /var/tmp
#1.4.9	add dns profile (DNS DoT) with bind9 standard log volume
#1.4.10	add a profile for KB (larger /var size for Foswiki)
#1.5.0	examine UsrMerge status on SOE4.3
#1.6.0	validate ext3/ext4 filesystem labels
#1.6.1	allow larger /var on roundcube profile
#1.6.2	allow larger /var/log on [ha]proxy and webdb profiles
#1.6.3	validate labels in /etc/fstab if used
#1.6.4	restrict LABEL validation to non-SOE systems
#1.6.5	split RPKI profile into D10 and D12 versions
#1.6.5  add D12 monitor profile
#1.6.6  add RGW profile
#1.6.7  add sbc profile
#1.6.8  functionize filesystem mount opts and skip on debian-EOL
#1.6.9	source /etc/os-release from syscheck supervisor
#1.6.10	select OS version based on VERSION_ID not BCFG2_GROUPS membership
#1.6.11 Make 4GB default for / partition 
#1.6.12 Make 8GB default for /var partition on mailX/prefixX
#1.6.13 Standardize on 4/4/6 for SOE5 partitions
#1.6.14 ignore frr directories in `/var/tmp`

declare -r HDD_SIZE_SMALL=30 # soe-install:stage1

declare -r -a PROBLEMS=(IMMUTABLE_ATTRIBUTE_MISSING INODES_HIDDEN_BY_OVERLAY_MOUNT
	FILES_IN_VAR_TMP ROOT_FILE_SPURIOUS ROOT_TMP_FILES ROOT_INODE_OWNERSHIP TMPFS_PERMISSIONS
	TMP_MOUNT_PERMISSIONS EXT_STRIPE EXT_RESUID EXT_UNKNOWN USRMERGE LABEL_EXT34 LABELFSTAB_EXT34)
test -n ${#PROBLEMS[@]}

function error_set {
	ERROR_FLAG=1
	ERROR_MSG="$*"
	echodebug "$ERROR_MSG"
}


# root directory secured
LSATTR=$(lsattr -d /)
ATTRIBUTES=${LSATTR% /}
case $ATTRIBUTES in
	*i*)
		;;
	*)
		fail caution 'attributes on the root filesystem should include {immutable}'
		helpmsg 'use {chattr +i /}'
		problem IMMUTABLE_ATTRIBUTE_MISSING /
		exit
		;;
esac

# root directory clean
declare -a ROOT_FILES=()
while IFS= read -r -d '' file; do
	ROOT_FILES+=( "$file" )
done < <(find / -xdev -maxdepth 1 -not -type d -and -not -type l -print0)
if [[ ${#ROOT_FILES[@]} -gt 0 ]]; then
	FILE_PLURAL=$(plural_text ${#ROOT_FILES[@]} 'file')
	fail caution "${#ROOT_FILES[@]} spurious $FILE_PLURAL in the root directory"
	helpmsg "determine the origin of the anomaly and cleanup the $FILE_PLURAL ${ROOT_FILES[*]}"
	problem ROOT_FILE_SPURIOUS "$(printf '%s;' "${ROOT_FILES[@]}")"
	exit
fi

# root directory ownership
declare -a ROOT_INODES=()
while IFS= read -r -d '' inode; do
	ROOT_INODES+=( "$inode" )
done < <(find / -xdev -maxdepth 1 -not -user root -print0)
if [[ ${#ROOT_INODES[@]} -gt 0 ]]; then
	INODE_PLURAL=$(plural_text ${#ROOT_INODES[@]} 'inode')
	fail caution "${#ROOT_INODES[@]} $INODE_PLURAL with wrong owner in the root directory"
	helpmsg "determine the origin of the anomaly and update ${ROOT_INODES[*]}"
	problem ROOT_INODE_OWNERSHIP "$(printf '%s;' "${ROOT_INODES[@]}")"
	exit
fi

# tmpfs overlay mount permissions
declare -r TMP_ROOT_PERM=0755
declare -r TMP_TMPFS_PERM=1777
TMP_TMPFS_STATUS=$(find /tmp -maxdepth 0 -type d -perm $TMP_TMPFS_PERM)
if [[ -z "$TMP_TMPFS_STATUS" ]]; then
	fail warning "/tmp is unusable; required permissions are $TMP_TMPFS_PERM"
	helpmsg 'ensure that the tmpfs overlay is mounted and specified in /etc/fstab'
	problem TMPFS_PERMISSIONS $TMP_TMPFS_PERM
	exit
fi
TMP_TEST_DIR=$(mktemp -d)
mount --bind / "$TMP_TEST_DIR"
TMP_ROOT_STATUS=$(find "$TMP_TEST_DIR/tmp" -maxdepth 0 -type d -perm $TMP_ROOT_PERM)
umount "$TMP_TEST_DIR"
rmdir "$TMP_TEST_DIR"
if [[ -z "$TMP_ROOT_STATUS" ]]; then
	fail caution '/tmp mount point has wrong permissions'
	helpmsg "unmount /tmp and set the mount point permissions to $TMP_ROOT_PERM"
	problem TMP_MOUNT_PERMISSIONS $TMP_ROOT_PERM
	exit
fi

# tmpfs overlay hidden inodes
TMP_TEST_DIR=$(mktemp -d)
mount --bind / "$TMP_TEST_DIR"
declare -a TMPFS_DIRS=()
while IFS= read -r -d '' dir; do
	TMPFS_DIRS+=( "$dir" )
done < <(awk '{if ($3=="tmpfs" && !index(substr($2,2),"/")){printf "%s\0",$2}}' /proc/mounts)
for SRC_DIR in "${TMPFS_DIRS[@]}"; do
	declare -a HIDDEN_INODES=()
	while IFS= read -r -d '' inode; do
		HIDDEN_INODES+=( "$inode" )
	done < <(find "$TMP_TEST_DIR$SRC_DIR" -xdev -mindepth 1 -print0)
	if [[ ${#HIDDEN_INODES[@]} -gt 0 ]]; then
		umount "$TMP_TEST_DIR"
		rmdir "$TMP_TEST_DIR"
		INODE_PLURAL=$(plural_text ${#HIDDEN_INODES[@]} 'inode')
		fail caution "${#HIDDEN_INODES[@]} $INODE_PLURAL hidden by $SRC_DIR tmpfs overlay mount"
		helpmsg 'delete them'
		problem INODES_HIDDEN_BY_OVERLAY_MOUNT "$SRC_DIR"
		exit
	fi
done
umount "$TMP_TEST_DIR"
rmdir "$TMP_TEST_DIR"

# files in persistent tmp location
VAR_TMP_FILES=$(find '/var/tmp' -mindepth 1 -not -path '*/systemd-private-*' -not -path '*/frr*')
if [[ -n "$VAR_TMP_FILES" ]]; then
	fail caution 'files found in /var/tmp'
	helpmsg 'delete temp'
	problem FILES_IN_VAR_TMP '/var/tmp'
	exit
fi

# miscellaneous temporary files
declare -a ROOT_TMP_FILES=()
while IFS= read -r -d '' file; do
	ROOT_TMP_FILES+=( "$file" )
done < <(find / -xdev -name '*~' -print0)
if [[ ${#ROOT_TMP_FILES[@]} -gt 0 ]]; then
	FILE_PLURAL=$(plural_text ${#ROOT_TMP_FILES[@]} 'file')
	fail caution "${#ROOT_TMP_FILES[@]} temporary $FILE_PLURAL in the root filesystem"
	helpmsg "determine the origin of the anomaly and cleanup the $FILE_PLURAL ${ROOT_TMP_FILES[*]}"
	problem ROOT_TMP_FILES "$(printf '%s;' "${ROOT_TMP_FILES[@]}")"
	exit
fi


check_mount_options() {
# non-standard filesystem mount options
FS_SPEC_MNTOPS=$(awk '
{
	#fstab(5)
	fs_spec=$1
	fs_file=$2
	fs_vfstype=$3
	fs_mntops=$4
	fs_freq=$5
	fs_passno=$6

	if (index(fs_vfstype,"ext")==1) {
		split(fs_mntops,options,",")
		for (i in options) {
			switch (options[i]) {
				case "ro":
				case "rw":
				case "relatime":
				case "noatime":
				case "nosuid":
				case "nodev":
				case "noexec":
				case "discard":
				case "data=ordered":
					break
				default:
					print fs_spec "\t" options[i]
					exit
			}
		}
	}
}' /proc/mounts)
FS_SPEC=${FS_SPEC_MNTOPS%%	*}
FS_MNTOPS=${FS_SPEC_MNTOPS##*	}
case "$FS_MNTOPS" in
	'') echodebug 'all mount options are normal';;
	stripe=*)
		fail caution "non-default alignment option {$FS_MNTOPS} for $FS_SPEC"
		problem EXT_STRIPE "$FS_SPEC"
		exit
		;;
	resuid=*)
		fail caution "non-default reserved filesystem blocks user {$FS_MNTOPS} for $FS_SPEC"
		problem EXT_RESUID "$FS_SPEC"
		exit
		;;
	*)
		fail caution "unknown mount option {$FS_MNTOPS} for $FS_SPEC"
		problem EXT_UNKNOWN "$FS_SPEC"
		exit
		;;
esac
}

if [[ $VERSION_ID -ge 8 ]]; then
	check_mount_options
fi


echodebug "filesystem presence/size check for Bcfg2 parcel $BCFG2_PARCEL($BCFG2_DEBIAN_VERSION)"

declare -a DEFAULT
case $BCFG2_DEBIAN_VERSION in
	lenny|squeeze)
		DEFAULT=(/:'=1' /var:'=1' /var/log:'=1')
		;;
	jessie|buster)
		DEFAULT=(/:'=4' /var:'=2' /var/log:'=2')
		;;
	bookworm)
		DEFAULT=(/:'=4' /var:'=4' /var/log:'=6')
		;;
	*)
		unknown 'unknown OS version'
		exit 1
		;;
esac

declare -r SYSTEM_VG_NAME='vg'
if [[ -d "/dev/$SYSTEM_VG_NAME" ]]; then
	PV_SIZE=$(LVM_SUPPRESS_FD_WARNINGS=1 lvm \
		vgs --units b --noheadings --nosuffix --options vg_size $SYSTEM_VG_NAME)
	if [[ $PV_SIZE -lt $(( HDD_SIZE_SMALL * 2**30 )) ]]; then
		DEFAULT=(/:'>0' /var:'>0' /var/log:'>0')
	fi
fi

if [[ $(stat --format=%h /usr/src) -gt 2 ]]; then
	DEFAULT+=(/:'>2')
fi

# profile templates (PT_*)
declare -a -r PT_ACTIVE_LOG=(/var/log:'>6')
declare -a -r PT_SOE_SOFTWARE=(/:'=4' /var:'=4' /var/log:'>6' /opt:'>4' /srv:'>8')

declare -a PROFILE=()
case "${BCFG2_PARCEL}-${BCFG2_DEBIAN_VERSION}" in
	master-*)		PROFILE=(/srv:'>100');;
	xen-*)			PROFILE=(/:'=3' /var/lib/xen:'>0');;
	filer-*)		PROFILE=();;
	nas-*)			PROFILE=();;
	monitor-lenny)	PROFILE=(/var:'>4' /var/log:'>16');;
	monitor-buster)	PROFILE=(/:'=8' /var:'>12' /var/log:'>24');;
	monitor-bookworm)	PROFILE=(/:'=6' /var:'=24' /var/log:'>24' /opt:'=8' /srv:'>24');;
	rtbh-buster)	PROFILE=(/:'=6' /var:'>4' /var/log:'>6' /srv:'>32');;
	noms-lenny)		PROFILE=(/:'=2' /var:'=2' /var/log:'>6' /var/lib:'>2' /var/cache:'>2');;
	noms-buster)	PROFILE=("${PT_SOE_SOFTWARE[@]}");;
	nomsdb-*)		PROFILE=("${PT_SOE_SOFTWARE[@]}");;
	stats-*)		PROFILE=("${PT_SOE_SOFTWARE[@]}");;
	portal-*)		PROFILE=("${PT_SOE_SOFTWARE[@]}");;
	pgdb-*)			PROFILE=("${PT_SOE_SOFTWARE[@]}");;
	mneng-*)		PROFILE=("${PT_SOE_SOFTWARE[@]}");;
	mnengdb-*)		PROFILE=("${PT_SOE_SOFTWARE[@]}");;
	netconf-*)		PROFILE=("${PT_SOE_SOFTWARE[@]}");;
	idp-*)			PROFILE=("${PT_SOE_SOFTWARE[@]}");;
	prov-*)			PROFILE=("${PT_ACTIVE_LOG[@]}" /srv:'>6');;
	fwprov-*)		PROFILE=("${PT_ACTIVE_LOG[@]}" /srv:'>6');;
	lsn-*)			PROFILE=(/srv:'>0');;
	ns-*)			PROFILE=("${PT_ACTIVE_LOG[@]}");;
	rns-*)			PROFILE=("${PT_ACTIVE_LOG[@]}");;
	dns-*)			PROFILE=("${PT_ACTIVE_LOG[@]}");;
	radius-*)		PROFILE=();;
	roundcube-*)	PROFILE=(/var:'>4');;
	nomad-lenny)	PROFILE=("${PT_ACTIVE_LOG[@]}" /:'=2' /var:'=2' /var/spool:'>4');;
	nomad-squeeze)	PROFILE=("${PT_ACTIVE_LOG[@]}" /:'=2' /var:'=2' /var/spool:'>4');;
	nomad-buster)	PROFILE=("${PT_ACTIVE_LOG[@]}" /:'=4' /var:'=4' /srv:'>4');;
	sbc-*)			PROFILE=("${PT_ACTIVE_LOG[@]}" /:'=4' /var:'=4' /srv:'>4');;
	mail-*)			PROFILE=("${PT_ACTIVE_LOG[@]}" /var:'=8');;
	prefix-*)		PROFILE=("${PT_ACTIVE_LOG[@]}" /var:'=8');;
	backup-*)		PROFILE=(/srv:'>0');;
	rpki-buster)	PROFILE=(/var:'=24');;
	rpki-bookworm)	PROFILE=(/var:'=8');;
	iot-*)			PROFILE=();;
	mirror-*)		PROFILE=(/var/log:'>10');;
	proxy-buster)	PROFILE=(/var/log:'=4');;
	web-buster)		PROFILE=(/var/log:'=4' /var/www:'>4');;
	webdb-buster)	PROFILE=(/var/log:'=4');;
	vdi-*)			PROFILE=(/:'=4');;
	pump-*)			PROFILE=(/:'=8' /var:'=4' /var/log:'>8');;
	flow-*)			PROFILE=(/:'=8' /var:'=4' /var/log:'=8' /var/www:'>8');;
	flowcaster-*)	PROFILE=(/:'=8' /var:'=4' /var/log:'=8');;
	kb-*)			PROFILE=(/var:'>4');;
	rt-*)			PROFILE=(/var:'>8');;
	pad-*)			PROFILE=(/var:'>2');;
	rgw-*)			PROFILE=(/:'=8' /var:'=8' /var/log:'=8' /var/lib/ceph:'=8');;
    pws-*)          PROFILE=("${PT_SOE_SOFTWARE[@]}");;
esac

declare -a KEYS
for OBJ in "${DEFAULT[@]}" "${PROFILE[@]:-}"; do
	##echodebug "OBJ:{$OBJ}"
	if [[ -z "$OBJ" ]]; then
		continue
	fi
	KEY=${OBJ%:*}
	if ! is_element_of "$KEY" "${KEYS[@]:-}"; then
		KEYS+=("$KEY")
	fi
done

declare -a REQ
##echodebug "KEYS:{${KEYS[@]}}"
for KEY in "${KEYS[@]}"; do
	for OBJ_DEFAULT in "${DEFAULT[@]}"; do
		KEY_DEFAULT=${OBJ_DEFAULT%:*}
		VAL_DEFAULT=${OBJ_DEFAULT#*:}
		if [[ $KEY = "$KEY_DEFAULT" ]]; then
			VAL=$VAL_DEFAULT
		fi
	done
	for OBJ_PROFILE in "${PROFILE[@]:-}"; do
		KEY_PROFILE=${OBJ_PROFILE%:*}
		VAL_PROFILE=${OBJ_PROFILE#*:}
		if [[ $KEY = "$KEY_PROFILE" ]]; then
			VAL=$VAL_PROFILE
		fi
	done
	REQ+=("$KEY:$VAL")
done

echodebug "required filesystems specification is {${REQ[*]}}"
ERROR_FLAG=0
ERROR_MSG=''
for FS_SPEC in "${REQ[@]}"; do
	FS_MOUNT=${FS_SPEC%:*}
	SIZE_SPEC=${FS_SPEC#*:}

	SIZE_CMP=${SIZE_SPEC:0:1}
	SIZE_REQ=${SIZE_SPEC:1:8}
	FS_TYPE=$(awk --assign fs="$FS_MOUNT" '{ if ($2 == fs) {print $3;exit} }' /proc/mounts)
	if [[ "$FS_TYPE" = 'nfs' ]]; then
		continue
	fi

	BLOCK_DEV=$(awk --assign fs="$FS_MOUNT" '{ if ($2 == fs && $1 ~ "/" ) {print $1} }' /proc/mounts)
	if [[ $SIZE_CMP = '=' && $SIZE_REQ -eq 0 ]]; then
		if [[ -n "$BLOCK_DEV" ]]; then
			error_set "$FS_MOUNT is mounted but is obsolete"
			helpmsg 'copy data to the parent filesystem; remove the overlay'
		fi
		continue
	fi

	if [[ -z "$BLOCK_DEV" ]]; then
		error_set "$FS_MOUNT is not mounted but is required"
		helpmsg 'create a new volume and filesystem; mount it; MOVE data from the parent filesystem to the overlay'
		continue
	fi

	if [[ ! -b "$BLOCK_DEV" ]]; then
		MAJOR_MINOR=$(stat --format=%d "$FS_MOUNT")
		MAJOR=$(( MAJOR_MINOR / 256 ))
		MINOR=$(( MAJOR_MINOR % 256 ))
		# shellcheck source=/dev/null
		source "/sys/dev/block/$MAJOR:$MINOR/uevent"
		BLOCK_DEV="/dev/$DEVNAME"
	fi

	if [[ ! -b "$BLOCK_DEV" ]]; then
		error_set "block device $BLOCK_DEV for filesystem $FS_MOUNT does not exist"
		helpmsg "unexpected error; check the status of $BLOCK_DEV file for filesystem {$FS_MOUNT}"
		continue
	fi

	# DRBD Users Guide 8.4
	# 17.1.3. Estimating meta data size
	# Figure 17.1. Calculating DRBD meta data size (exactly)
	Cs=$(blockdev --getsz "$BLOCK_DEV") # data device size in sectors
	Ss=$(blockdev --getss "$BLOCK_DEV") # logical block (sector) size
	Ms=$(( 8 * ( Cs + 2**18-1 ) / 2**18 + 72 ))
	M=$(( Ms * Ss ))

	SIZE64=$(blockdev --getsize64 "$BLOCK_DEV")
	SIZE64_G=$(( ( SIZE64 + M ) / 2**30 ))

	case $SIZE_CMP in
		'=')
		if [[ $SIZE_REQ -ne $SIZE64_G ]]; then
			error_set "$FS_MOUNT is ${SIZE64_G}GiB but is required to be exactly ${SIZE_REQ}GiB"
			helpmsg 'resize the filesystem using lv-autosize'
			continue
		fi
		;;

		'<')
		if [[ $SIZE_REQ -lt $SIZE64_G ]]; then
			error_set "$FS_MOUNT is too large at ${SIZE64_G}GiB - required size is ${SIZE_REQ}GiB or less"
			helpmsg 'resize the filesystem using lv-autosize'
			continue
		fi
		;;

		'>')
		if [[ $SIZE_REQ -gt $SIZE64_G ]]; then
			error_set "$FS_MOUNT is too small at ${SIZE64_G}GiB - required size is ${SIZE_REQ}GiB or more"
			helpmsg 'resize the filesystem using lv-autosize'
			continue
		fi
		;;

		*)
		fail warning "$FS_MOUNT invalid size comparison operator {$SIZE_CMP}"
		helpmsg 'internal error'
		exit 1
		;;
	esac
	echodebug "$FS_MOUNT is OK at ${SIZE64_G}GiB ($SIZE_SPEC)"
done

if [[ $ERROR_FLAG -gt 0 ]]; then
	fail caution "$ERROR_MSG"
	exit
fi

# The Debian /usr Merge
# https://wiki.debian.org/UsrMerge
if [[ $VERSION_ID -ge 10 ]]; then
	if [[ -L '/lib' ]]; then
		echodebug 'UsrMerge complete'
	else
		fail caution 'UsrMerge pending'
		helpmsg 'upgrade to the UsrMerge layout with the help of the usrmerge package'
		problem USRMERGE 0
		exit
	fi
fi

if [[ $VERSION_ID -ge 8 ]]; then
	# https://tldp.org/HOWTO/Partition/labels.html
	declare -r -i LABEL_LENGTH_MAX=16
	echodebug 'filesystem label validation:'
	while read fs_spec fs_file fs_vfstype fs_mntops fs_freq fs_passno; do
		case $fs_vfstype in
			ext[34]) ;;
			*) continue;;
		esac
		label_cur=$(e2label $fs_spec)
		label_req=${fs_file//\//_}
		label_req=${label_req#_}
		if [[ -z "$label_req" ]]; then
			label_req='root'
		fi
		if [[ ${#label_req} -gt $LABEL_LENGTH_MAX ]]; then
			continue
		fi
		if [[ "$label_cur" = "$label_req" ]]; then
			blkid_output=$(blkid $fs_spec)
			echodebug "$blkid_output"
		else
			fail caution "label for $fs_spec (on $fs_file) is {$label_cur}; expected {$label_req}"
			helpmsg 'set the expected label and adjust fstab if required'
			problem LABEL_EXT34 $fs_spec:$label_req
			exit
		fi
		if is_element_of domK "${BCFG2_GROUPS[@]}"; then
		if grep --silent "^LABEL.*${fs_file}" /etc/fstab; then
			fstab_label="$( grep "\s${fs_file}\s" /etc/fstab | grep --only-matching "^LABEL=\S*" )"
			fstab_label="${fstab_label//LABEL=/}"
			echo " fstab=$fstab_label"
			if [[ "${fstab_label}" != "$label_req" ]]; then
				fail warning "label for $fs_file in fstab is incorrect; got {$fstab_label}, expected {$label_req}"
				helpmsg 'adjust fstab to use the correct label'
				problem LABELFSTAB_EXT34 $fstab_label:$label_req
				exit
			fi
		else
			echo
		fi
		fi
	done < '/proc/mounts'
fi

ok
