diff --git a/party b/party index 3fbe308..10fd8ae 100755 --- a/party +++ b/party @@ -879,6 +879,40 @@ EOF echo "Party '$name' started. Attach from a fresh shell with: party join $name" } +# Rescue /log to $HOME/party--.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 + _cn_name=${d##*"party-$USER:"} + _cn_name=${_cn_name%.d} + 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 - 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" else echo "party clean: skipping $d (could not save transcript)" >&2 - continue fi + continue fi rm -rf "$d"