diff options
| author | Paul Buetow <paul@buetow.org> | 2026-06-14 15:43:12 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-06-14 15:43:12 +0300 |
| commit | fe4e83cd5159809a70f24301fcabc08779b51706 (patch) | |
| tree | 29b71066a22db0630ed37ced8e8710e8175b83f7 /src | |
| parent | 3eeef1e0f68d750e7817748180a2007095e99ef3 (diff) | |
6m0 clean up BASH_ENV context tempfile via traps on interrupt
source_template_file created its context tempfile with mktemp but only ran
rm -f on the success and explicit-failure paths. When a render was interrupted
by a signal (terminate_active_generation SIGTERMs the backgrounded render
subtree on Ctrl-C/abort) the tempfile leaked.
Register cleanup in exactly one place, inline in source_template_file's body:
- A RETURN trap covers normal and error returns and clears all of these traps
(including itself) so it cannot linger and fire on an enclosing function's
return against the out-of-scope context_file local (set -u). The trap must be
set in the function body, not a helper: without functrace a RETURN trap is not
function-scoped and would fire when a helper returns, deleting the file before
the render runs.
- INT/TERM/HUP handlers remove the file, clear the traps and re-raise so the
process still exits with the signal's default disposition. PIPE is not
trapped (the internal context-build pipeline emits legitimate SIGPIPE);
SIGKILL is untrappable, leaving only the OS-reaped KILL-escalation case.
The redundant rm -f calls are removed. Adds
test_template_interrupt_removes_context_file, which runs an in-flight render and
SIGTERMs it, asserting the context file is gone.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Diffstat (limited to 'src')
| -rw-r--r-- | src/lib/template.source.sh | 36 |
1 files changed, 34 insertions, 2 deletions
diff --git a/src/lib/template.source.sh b/src/lib/template.source.sh index a42e602..152a8be 100644 --- a/src/lib/template.source.sh +++ b/src/lib/template.source.sh @@ -296,6 +296,7 @@ source_template_file() { local -r output_path="$1"; shift local -r render_vars_name="$1"; shift local context_file + local sig local -i status=0 context_file=$(mktemp) @@ -304,6 +305,39 @@ source_template_file() { return "$status" fi + # Single cleanup point for the BASH_ENV context tempfile. The traps MUST be + # registered here in source_template_file's own body (not in a helper): a + # RETURN trap is not function-scoped unless functrace is enabled, so a trap + # set inside a helper would fire when that helper returns and delete the file + # before the render even runs. + # + # The RETURN trap covers normal and error returns (errexit unwinds through + # it) and clears ALL of these traps, including itself, so a lingering RETURN + # trap cannot fire again on an enclosing function's return against the now + # out-of-scope context_file local (which would trip set -u). + # + # A RETURN trap alone does NOT fire when a signal terminates the shell with + # its default disposition. source_template_file also runs in backgrounded + # render subshells (see queue_album_view_render_job) that get SIGTERM'd by + # terminate_active_generation on interrupt, so we additionally trap the + # terminating signals: each handler removes the file, clears the traps and + # re-raises the original signal so the process still exits with that signal's + # default disposition. + # + # We deliberately do NOT trap PIPE: the internal "{ ... } | bash" pipeline + # below legitimately produces SIGPIPE when the reader closes early, and + # trapping it would tear the render down mid-flight. SIGKILL cannot be + # trapped either, so a KILL escalation may still leak a file that the OS tmp + # reaper later clears; that is the only unavoidable residual case. + trap 'rm -f "$context_file"; trap - INT TERM HUP RETURN' RETURN + for sig in INT TERM HUP; do + # $sig is intentionally expanded now (so each handler re-raises its own + # signal); $context_file and $$ are escaped to expand when the trap runs. + # shellcheck disable=SC2064 + trap "rm -f \"\$context_file\"; trap - INT TERM HUP RETURN; \ + kill -s $sig \$\$" "$sig" + done + if { declare -p TEMPLATE_RENDER_FIELD_SPECS declare -p "$render_vars_name" @@ -316,7 +350,6 @@ source_template_file() { status=$? fi if (( status != 0 )); then - rm -f "$context_file" return "$status" fi @@ -326,7 +359,6 @@ source_template_file() { else status=$? fi - rm -f "$context_file" if (( status != 0 )); then return "$status" fi |
