#!/bin/bash
# felhom-thin-check — list the thin device ids a thin pool holds next to the ones LVM knows (R-1012). READ-ONLY: it
# names an orphan, it never removes one. Installed as /usr/local/sbin/felhom-thin-check (config bundle).
#
#   felhom-thin-check <vg> <pool>
#
# A full host root while vzdump takes its snapshot can leave a thin snapshot inside the pool that LVM no longer knows
# (demo-hp 2026-10-14: `thin_ls` device 4, LVM only 1 and 2); from then on every snapshot backup of that guest fails
# („Creation of new snapshot 4 of device 2 failed") and nothing said why. The repair (`dmsetup message … delete <id>`)
# stays a HUMAN step, decided by the operator — this helper only reads.
#
# Reading the live pool's device list needs a metadata snapshot: the kernel's documented way to read metadata while
# the pool is in use (`reserve_metadata_snap`, `thin_ls --metadata-snap`, `release_metadata_snap`). It changes no data
# and no mapping; it is held for the length of one `thin_ls -o DEV` (well under a second on these pools). Rules:
#   1. the arguments are an LVM name pair; <vg>/<pool> is a thin pool (lv_attr starts with `t`);
#   2. only one run at a time (a lock file); a metadata snapshot that someone ELSE holds is never touched — the run
#      refuses (exit 3) and does not release it;
#   3. LVM's list is read before AND after the pool's, so a volume created in between is not reported as an orphan
#      (the caller treats an id as an orphan only when it is in the pool and in neither LVM list).
# Output, one record per line:  lvm-before <id> <lv> | pool <id> | lvm-after <id> <lv> | ok
# Exit: 0 read, 1 refused (arguments / not a thin pool), 3 a metadata snapshot is already held or cannot be reserved,
# 4 the pool could not be read. Pinned by configs/test_felhom_thin_check.py.
set -u
LVS=/usr/sbin/lvs
DMSETUP=/usr/sbin/dmsetup
THIN_LS=/usr/sbin/thin_ls
LOCK=/run/lock/felhom-thin-check.lock
NAME_RE='^[A-Za-z0-9_+][A-Za-z0-9_.+-]{0,63}$'

die() { echo "felhom-thin-check: REFUSED: $1" >&2; exit "${2:-1}"; }  # $1 the reason, $2 the exit code

lvm_ids() { # $1 tag → "<tag> <id> <lv>" for every thin volume of this pool
	"$LVS" --noheadings --separator , -o lv_name,thin_id --select "pool_lv=$POOL" -- "$VG" 2>/dev/null |
		awk -F, -v tag="$1" '{ gsub(/ /, ""); if ($2 ~ /^[0-9]+$/) print tag, $2, $1 }'
}

release() { "$DMSETUP" message "$TPOOL" 0 release_metadata_snap >/dev/null 2>&1; }

main() {
	[[ $# -eq 2 ]] || die "usage: felhom-thin-check <vg> <pool>"
	VG=$1 POOL=$2
	local attr held ids
	[[ "$VG" =~ $NAME_RE && "$POOL" =~ $NAME_RE ]] || die "not an LVM name pair"

	attr=$("$LVS" --noheadings -o lv_attr -- "$VG/$POOL" 2>/dev/null | tr -d ' ')
	[[ "$attr" == t* ]] || die "$VG/$POOL is not a thin pool"

	DM="${VG//-/--}-${POOL//-/--}"
	TPOOL="$DM-tpool"
	TMETA="/dev/mapper/${DM}_tmeta"

	exec 9>"$LOCK" || die "cannot open the lock file" 4
	flock -w 30 9 || die "another check holds the lock" 3

	# Field 7 of the thin-pool status is the held metadata root: "-" when no metadata snapshot exists.
	held=$("$DMSETUP" status "$TPOOL" 2>/dev/null | awk '$3 == "thin-pool" { print $7 }')
	[[ -n "$held" ]] || die "cannot read the status of $TPOOL" 4
	[[ "$held" == "-" ]] || die "a metadata snapshot of $TPOOL is already held (by someone else) — not touched" 3

	lvm_ids lvm-before
	"$DMSETUP" message "$TPOOL" 0 reserve_metadata_snap >/dev/null 2>&1 || die "could not reserve a metadata snapshot of $TPOOL" 3
	trap release EXIT
	trap 'release; trap - EXIT; exit 4' INT TERM HUP
	if ! ids=$("$THIN_LS" --metadata-snap --no-headers -o DEV "$TMETA" 2>/dev/null); then
		die "thin_ls could not read $TMETA" 4
	fi
	release
	trap - EXIT INT TERM HUP
	awk '$1 ~ /^[0-9]+$/ { print "pool", $1 }' <<<"$ids"
	lvm_ids lvm-after
	echo ok
}

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then main "$@"; fi
