Fix crash when viewing snapshots (and other list views)
Root cause: under `set -euo pipefail`, the view functions built their body with `body="$(zfs ... | awk ...)"`. If the pipeline exited non-zero — either because `zfs list -t snapshot` returned non-zero (e.g. the no-snapshots case) or because the large report was handed to `whiptail --msgbox` as one argv string and newt choked — `set -e` aborted the entire script, dropping the whole TUI back to the shell. The snapshot view is the most exposed since it is both the largest body and the most likely to hit a non-zero zfs result. Fix: route every list view through a hardened show() that runs the report producer with `set -e` disabled (so an inner non-zero can never kill the TUI) and renders via `whiptail --textbox <tmpfile>` (reads from a file instead of taking the report as an argv string, and scrolls large content cleanly). Each view_* is now a thin wrapper over a _report_* producer that prints to stdout. Verified with a stubbed harness: failing pipeline, 5000-row list, and a non-zero whiptail all now survive; the previous pattern crashed (exit 1). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
936db8b5ff
commit
c1bc79efd8
@ -60,10 +60,23 @@ fi
|
|||||||
msg() { whiptail --title "$APP_TITLE" --msgbox "$1" "${2:-12}" "${3:-72}"; }
|
msg() { whiptail --title "$APP_TITLE" --msgbox "$1" "${2:-12}" "${3:-72}"; }
|
||||||
yesno(){ whiptail --title "$APP_TITLE" --yesno "$1" "${2:-12}" "${3:-72}"; }
|
yesno(){ whiptail --title "$APP_TITLE" --yesno "$1" "${2:-12}" "${3:-72}"; }
|
||||||
|
|
||||||
# Show arbitrary text in a scrollable box.
|
# Display the stdout of a producer command/function in a scrollable box.
|
||||||
|
#
|
||||||
|
# The producer runs with `set -e` disabled and its exit status discarded, so a
|
||||||
|
# non-zero result from zfs / awk / pipefail can never abort the whole TUI — this
|
||||||
|
# is what made "View all snapshots" crash back to the shell. The captured report
|
||||||
|
# is shown via --textbox (which reads from a file) instead of --msgbox (which
|
||||||
|
# takes the entire report as one argv string and chokes on large snapshot lists).
|
||||||
show() {
|
show() {
|
||||||
local title="$1" body="$2"
|
local title="$1"; shift
|
||||||
whiptail --title "$title" --scrolltext --msgbox "${body:-(no output)}" 24 90
|
local out tmp
|
||||||
|
set +e
|
||||||
|
out="$("$@" 2>&1)"
|
||||||
|
set -e
|
||||||
|
tmp="$(mktemp)"
|
||||||
|
printf '%s\n' "${out:-(no output)}" >"$tmp"
|
||||||
|
whiptail --title "$title" --scrolltext --textbox "$tmp" 24 90 || true
|
||||||
|
rm -f "$tmp"
|
||||||
}
|
}
|
||||||
|
|
||||||
# Run a command, capturing output, and show the result in a scrollable box.
|
# Run a command, capturing output, and show the result in a scrollable box.
|
||||||
@ -144,14 +157,15 @@ latest_common_snapshot() {
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Snapshots menu
|
# Snapshots menu
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
view_snapshots() {
|
# Report producers print to stdout; show() captures them with `set -e` off.
|
||||||
local body
|
_report_snapshots() {
|
||||||
body="$(list_snapshots | awk -F'\t' \
|
local rows; rows="$(list_snapshots)"
|
||||||
|
[[ -z "$rows" ]] && { echo "(no snapshots found)"; return 0; }
|
||||||
|
printf '%s\n' "$rows" | awk -F'\t' \
|
||||||
'BEGIN{printf "%-48s %8s %8s %s\n","SNAPSHOT","USED","REFER","CREATED"}
|
'BEGIN{printf "%-48s %8s %8s %s\n","SNAPSHOT","USED","REFER","CREATED"}
|
||||||
{printf "%-48s %8s %8s %s\n",$1,$2,$3,$4}')"
|
{printf "%-48s %8s %8s %s\n",$1,$2,$3,$4}'
|
||||||
[[ -z "$body" ]] && body="(no snapshots found)"
|
|
||||||
show "ZFS Snapshots" "$body"
|
|
||||||
}
|
}
|
||||||
|
view_snapshots() { show "ZFS Snapshots" _report_snapshots; }
|
||||||
|
|
||||||
create_snapshot() {
|
create_snapshot() {
|
||||||
local ds name
|
local ds name
|
||||||
@ -305,14 +319,14 @@ pick_cron() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
view_schedules() {
|
_report_schedules() {
|
||||||
local body
|
local rows; rows="$(parse_snapshot_schedules)"
|
||||||
body="$(parse_snapshot_schedules | awk -F'\t' \
|
[[ -z "$rows" ]] && { echo "(no snapshot schedules configured)"; return 0; }
|
||||||
|
printf '%s\n' "$rows" | awk -F'\t' \
|
||||||
'BEGIN{printf "%-40s %-16s %s\n","DATASET","CRON","KEEP"}
|
'BEGIN{printf "%-40s %-16s %s\n","DATASET","CRON","KEEP"}
|
||||||
{printf "%-40s %-16s %s\n",$1,$2,$3}')"
|
{printf "%-40s %-16s %s\n",$1,$2,$3}'
|
||||||
[[ -z "$body" ]] && body="(no snapshot schedules configured)"
|
|
||||||
show "Snapshot Schedules" "$body"
|
|
||||||
}
|
}
|
||||||
|
view_schedules() { show "Snapshot Schedules" _report_schedules; }
|
||||||
|
|
||||||
add_schedule() {
|
add_schedule() {
|
||||||
local ds cron keep snaps scrubs
|
local ds cron keep snaps scrubs
|
||||||
@ -378,14 +392,14 @@ menu_schedules() {
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Datasets menu
|
# Datasets menu
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
view_datasets() {
|
_report_datasets() {
|
||||||
local body
|
local rows; rows="$(list_datasets)"
|
||||||
body="$(list_datasets | awk -F'\t' \
|
[[ -z "$rows" ]] && { echo "(no datasets found — is 'zfs list' working as root?)"; return 0; }
|
||||||
|
printf '%s\n' "$rows" | awk -F'\t' \
|
||||||
'BEGIN{printf "%-40s %8s %8s %s\n","NAME","USED","AVAIL","MOUNTPOINT"}
|
'BEGIN{printf "%-40s %8s %8s %s\n","NAME","USED","AVAIL","MOUNTPOINT"}
|
||||||
{printf "%-40s %8s %8s %s\n",$1,$2,$3,$4}')"
|
{printf "%-40s %8s %8s %s\n",$1,$2,$3,$4}'
|
||||||
[[ -z "$body" ]] && body="(no datasets found — is 'zfs list' working as root?)"
|
|
||||||
show "ZFS Datasets" "$body"
|
|
||||||
}
|
}
|
||||||
|
view_datasets() { show "ZFS Datasets" _report_datasets; }
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# LXC mountpoints menu (Proxmox pct)
|
# LXC mountpoints menu (Proxmox pct)
|
||||||
@ -408,18 +422,20 @@ lxc_mountpoints() {
|
|||||||
}'
|
}'
|
||||||
}
|
}
|
||||||
|
|
||||||
view_mounts() {
|
_report_mounts() {
|
||||||
local body="" ctid line
|
local ctid slot host target found=0
|
||||||
while IFS= read -r ctid; do
|
while IFS= read -r ctid; do
|
||||||
[[ -z "$ctid" ]] && continue
|
[[ -z "$ctid" ]] && continue
|
||||||
while IFS=$'\t' read -r slot host target; do
|
while IFS=$'\t' read -r slot host target; do
|
||||||
[[ -z "$slot" ]] && continue
|
[[ -z "$slot" ]] && continue
|
||||||
body+="$(printf 'CT %-6s %-6s %-30s -> %s' "$ctid" "$slot" "$host" "$target")"$'\n'
|
printf 'CT %-6s %-6s %-30s -> %s\n' "$ctid" "$slot" "$host" "$target"
|
||||||
|
found=1
|
||||||
done < <(lxc_mountpoints "$ctid")
|
done < <(lxc_mountpoints "$ctid")
|
||||||
done < <(list_lxc_ids)
|
done < <(list_lxc_ids)
|
||||||
[[ -z "$body" ]] && body="(no LXC mountpoints found)"
|
[[ "$found" -eq 0 ]] && echo "(no LXC mountpoints found)"
|
||||||
show "LXC Mountpoints" "$body"
|
return 0
|
||||||
}
|
}
|
||||||
|
view_mounts() { show "LXC Mountpoints" _report_mounts; }
|
||||||
|
|
||||||
add_mount() {
|
add_mount() {
|
||||||
local ctid host target used slot=0
|
local ctid host target used slot=0
|
||||||
@ -509,21 +525,23 @@ EOF
|
|||||||
chmod 755 "$REPLICATION_SCRIPT"
|
chmod 755 "$REPLICATION_SCRIPT"
|
||||||
}
|
}
|
||||||
|
|
||||||
view_replication() {
|
_report_replication() {
|
||||||
local body="" src tgt common latest status
|
local src tgt common latest status found=0
|
||||||
while IFS=$'\t' read -r src tgt; do
|
while IFS=$'\t' read -r src tgt; do
|
||||||
[[ -z "$src" ]] && continue
|
[[ -z "$src" ]] && continue
|
||||||
|
found=1
|
||||||
latest="$(latest_snapshot "$src")"; latest="${latest:--}"
|
latest="$(latest_snapshot "$src")"; latest="${latest:--}"
|
||||||
common="$(latest_common_snapshot "$src" "$tgt")"; common="${common:--}"
|
common="$(latest_common_snapshot "$src" "$tgt")"; common="${common:--}"
|
||||||
if ! dataset_exists "$tgt"; then status="target missing (full send needed)"
|
if ! dataset_exists "$tgt"; then status="target missing (full send needed)"
|
||||||
elif [[ "$common" == "$latest" && "$common" != "-" ]]; then status="up-to-date"
|
elif [[ "$common" == "$latest" && "$common" != "-" ]]; then status="up-to-date"
|
||||||
elif [[ "$common" == "-" ]]; then status="no common snap (mismatch)"
|
elif [[ "$common" == "-" ]]; then status="no common snap (mismatch)"
|
||||||
else status="incremental pending"; fi
|
else status="incremental pending"; fi
|
||||||
body+="$(printf '%-30s -> %-30s [%s]' "$src" "$tgt" "$status")"$'\n'
|
printf '%-30s -> %-30s [%s]\n' "$src" "$tgt" "$status"
|
||||||
done < <(load_replication)
|
done < <(load_replication)
|
||||||
[[ -z "$body" ]] && body="(no replication targets configured)"
|
[[ "$found" -eq 0 ]] && echo "(no replication targets configured)"
|
||||||
show "Replication Targets" "$body"
|
return 0
|
||||||
}
|
}
|
||||||
|
view_replication() { show "Replication Targets" _report_replication; }
|
||||||
|
|
||||||
add_replication() {
|
add_replication() {
|
||||||
local src tgt cfg
|
local src tgt cfg
|
||||||
@ -588,17 +606,20 @@ menu_replication() {
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Scrub menu
|
# Scrub menu
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
view_scrub() {
|
_report_scrub() {
|
||||||
local body="" pool sched line
|
local pool sched found=0
|
||||||
while IFS= read -r pool; do
|
while IFS= read -r pool; do
|
||||||
[[ -z "$pool" ]] && continue
|
[[ -z "$pool" ]] && continue
|
||||||
|
found=1
|
||||||
sched="$(parse_scrub_schedules | awk -F'\t' -v p="$pool" '$1==p {print $2; exit}')"
|
sched="$(parse_scrub_schedules | awk -F'\t' -v p="$pool" '$1==p {print $2; exit}')"
|
||||||
body+="$(printf '===== %s (schedule: %s) =====' "$pool" "${sched:-none}")"$'\n'
|
printf '===== %s (schedule: %s) =====\n' "$pool" "${sched:-none}"
|
||||||
body+="$(zpool status "$pool" 2>&1 | grep -E '^[[:space:]]*(state|scan|errors):' )"$'\n\n'
|
zpool status "$pool" 2>&1 | grep -E '^[[:space:]]*(state|scan|errors):' || true
|
||||||
|
echo
|
||||||
done < <(list_pools)
|
done < <(list_pools)
|
||||||
[[ -z "$body" ]] && body="(no pools found)"
|
[[ "$found" -eq 0 ]] && echo "(no pools found)"
|
||||||
show "Pool Scrub Status" "$body"
|
return 0
|
||||||
}
|
}
|
||||||
|
view_scrub() { show "Pool Scrub Status" _report_scrub; }
|
||||||
|
|
||||||
scrub_start() {
|
scrub_start() {
|
||||||
local pool
|
local pool
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user