#!/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 # # 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 `) # 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; / 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 | pool | lvm-after | 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 → " " 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=$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