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"
}
# 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() {
name=
while [ $# -gt 0 ]; do
@ -955,38 +989,21 @@ cmd_close() {
# Rescue a transcript before the dir vanishes: losing the night's
# log to a routine close is exactly the accident class the threat
# model says to prevent. After kill-server the pipe-pane `cat`
# children are exiting but may still be draining their last bytes,
# so poll for two equal size samples (bounded, integer sleeps only)
# before moving it. 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. A failed rescue
# (HOME unset, mv error) leaves the party dir in place rather than
# deleting it out from under a promise the message just made;
# 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
# model says to prevent. A failed rescue (HOME unset, mv error)
# leaves the party dir in place rather than deleting it out from
# under a promise the message just made; 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 rescue_transcript "$expected_dir" "$name" 1; then
[ -z "$_rt_dest" ] || echo "party close: transcript saved to $_rt_dest"
else
_rt_rc=$?
if [ "$_rt_rc" -eq 2 ]; then
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
echo "party close: could not save transcript; party dir left in place ($expected_dir/log); re-run close after fixing." >&2
exit 1
fi
exit 1
fi
rm -rf "$expected_dir"
@ -1029,20 +1046,18 @@ EOF
# already confirmed dead by the liveness check above, so unlike
# close there's no pipe-pane `cat` still draining and no
# 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=${_cn_name%.d}
_cn_dest="$HOME/party-$_cn_name-$(date -u '+%Y%m%d-%H%M%S').log"
if mv "$d/log" "$_cn_dest" 2>/dev/null; then
echo "party clean: transcript saved to $_cn_dest"
if rescue_transcript "$d" "$_cn_name" 0; then
[ -z "$_rt_dest" ] || echo "party clean: transcript saved to $_rt_dest"
else
_rt_rc=$?
if [ "$_rt_rc" -eq 2 ]; then
echo "party clean: skipping $d (HOME is unset; leaving transcript)" >&2
else
echo "party clean: skipping $d (could not save transcript)" >&2
continue
fi
continue
fi
rm -rf "$d"