refactor: extract rescue_transcript helper from close and clean

cmd_close and cmd_clean each rescued <party dir>/log to
$HOME/party-<name>-<timestamp>.log before removing the dir, with
close's drain poll as the only difference. Share the mechanism in one
helper and keep each caller's own messages and control flow.
This commit is contained in:
veg 2026-09-21 09:36:49 +00:00
parent 9980d2918a
commit 45368232b4

89
party
View file

@ -879,6 +879,40 @@ EOF
echo "Party '$name' started. Attach from a fresh shell with: party join $name" echo "Party '$name' started. Attach from a fresh shell with: party join $name"
} }
# Rescue <dir>/log to $HOME/party-<name>-<UTC timestamp>.log before the
# per-party dir is removed. When drain=1 (cmd_close, right after
# kill-server), the pipe-pane `cat` children may still be draining
# their last bytes, so poll for two equal size samples (bounded,
# integer sleeps only) before moving; cmd_clean's dead-server check
# already confirmed nothing is still writing, so it passes drain=0. A
# same-filesystem mv is a rename and would keep catching late writes
# regardless, but $HOME and PARTY_SOCKET_DIR are commonly different
# filesystems (tmpfs /tmp vs a home partition), where mv is copy+unlink
# and tail bytes written after the copy started would be lost from the
# copy.
#
# Sets _rt_dest to the destination path on a successful move, or to ''
# when there was nothing to rescue. Returns 0 in both those cases; 2
# when HOME is unset; 1 when mv failed.
rescue_transcript() {
_rt_dir="$1" _rt_name="$2" _rt_drain="$3"
_rt_dest=''
[ -s "$_rt_dir/log" ] || return 0
[ -n "$HOME" ] || return 2
if [ "$_rt_drain" = 1 ]; then
_rt_prev=-1
for _rt_i in 1 2 3; do
_rt_size=$(wc -c < "$_rt_dir/log")
[ "$_rt_size" -eq "$_rt_prev" ] && break
_rt_prev="$_rt_size"
sleep 1
done
fi
_rt_dest="$HOME/party-$_rt_name-$(date -u '+%Y%m%d-%H%M%S').log"
mv "$_rt_dir/log" "$_rt_dest" 2>/dev/null || { _rt_dest=''; return 1; }
return 0
}
cmd_close() { cmd_close() {
name= name=
while [ $# -gt 0 ]; do while [ $# -gt 0 ]; do
@ -955,38 +989,21 @@ cmd_close() {
# Rescue a transcript before the dir vanishes: losing the night's # Rescue a transcript before the dir vanishes: losing the night's
# log to a routine close is exactly the accident class the threat # log to a routine close is exactly the accident class the threat
# model says to prevent. After kill-server the pipe-pane `cat` # model says to prevent. A failed rescue (HOME unset, mv error)
# children are exiting but may still be draining their last bytes, # leaves the party dir in place rather than deleting it out from
# so poll for two equal size samples (bounded, integer sleeps only) # under a promise the message just made; kill-server already ran,
# before moving it. A same-filesystem mv is a rename and would keep # so the party is dead either way, and `party close` (or `party
# catching late writes regardless, but $HOME and PARTY_SOCKET_DIR # clean`) can be re-run once the transcript is off to safety.
# are commonly different filesystems (tmpfs /tmp vs a home if rescue_transcript "$expected_dir" "$name" 1; then
# partition), where mv is copy+unlink and tail bytes written after [ -z "$_rt_dest" ] || echo "party close: transcript saved to $_rt_dest"
# the copy started would be lost from the copy. A failed rescue else
# (HOME unset, mv error) leaves the party dir in place rather than _rt_rc=$?
# deleting it out from under a promise the message just made; if [ "$_rt_rc" -eq 2 ]; then
# kill-server already ran, so the party is dead either way, and
# `party close` (or `party clean`) can be re-run once the transcript
# is off to safety.
if [ -s "$expected_dir/log" ]; then
if [ -z "$HOME" ]; then
echo "party close: HOME is unset; transcript kept, party dir left in place ($expected_dir/log); re-run close after fixing." >&2 echo "party close: HOME is unset; transcript kept, party dir left in place ($expected_dir/log); re-run close after fixing." >&2
exit 1
fi
_cl_prev=-1
for _cl_i in 1 2 3; do
_cl_size=$(wc -c < "$expected_dir/log")
[ "$_cl_size" -eq "$_cl_prev" ] && break
_cl_prev="$_cl_size"
sleep 1
done
_cl_dest="$HOME/party-$name-$(date -u '+%Y%m%d-%H%M%S').log"
if mv "$expected_dir/log" "$_cl_dest" 2>/dev/null; then
echo "party close: transcript saved to $_cl_dest"
else else
echo "party close: could not save transcript; party dir left in place ($expected_dir/log); re-run close after fixing." >&2 echo "party close: could not save transcript; party dir left in place ($expected_dir/log); re-run close after fixing." >&2
exit 1
fi fi
exit 1
fi fi
rm -rf "$expected_dir" rm -rf "$expected_dir"
@ -1029,20 +1046,18 @@ EOF
# already confirmed dead by the liveness check above, so unlike # already confirmed dead by the liveness check above, so unlike
# close there's no pipe-pane `cat` still draining and no # close there's no pipe-pane `cat` still draining and no
# quiescence poll is needed before moving it. # quiescence poll is needed before moving it.
if [ -s "$d/log" ]; then
if [ -z "$HOME" ]; then
echo "party clean: skipping $d (HOME is unset; leaving transcript)" >&2
continue
fi
_cn_name=${d##*"party-$USER:"} _cn_name=${d##*"party-$USER:"}
_cn_name=${_cn_name%.d} _cn_name=${_cn_name%.d}
_cn_dest="$HOME/party-$_cn_name-$(date -u '+%Y%m%d-%H%M%S').log" if rescue_transcript "$d" "$_cn_name" 0; then
if mv "$d/log" "$_cn_dest" 2>/dev/null; then [ -z "$_rt_dest" ] || echo "party clean: transcript saved to $_rt_dest"
echo "party clean: transcript saved to $_cn_dest" else
_rt_rc=$?
if [ "$_rt_rc" -eq 2 ]; then
echo "party clean: skipping $d (HOME is unset; leaving transcript)" >&2
else else
echo "party clean: skipping $d (could not save transcript)" >&2 echo "party clean: skipping $d (could not save transcript)" >&2
continue
fi fi
continue
fi fi
rm -rf "$d" rm -rf "$d"