summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-06-12 15:28:39 +0300
committerPaul Buetow <paul@buetow.org>2026-06-12 15:28:39 +0300
commitd5a58a1115bb3faa45caefeb053a5808406afa35 (patch)
tree013229daf22e5c062fdf5e860c457eb9d0c976bc
parent81a3be6379198c697de2f8639dcaa1cdc9d66a53 (diff)
Harden status-tested validation paths for bm0
-rwxr-xr-xbin/shuriken117
-rw-r--r--src/lib/action.source.sh94
-rw-r--r--src/lib/template.source.sh23
-rwxr-xr-xtests/cli.sh164
4 files changed, 354 insertions, 44 deletions
diff --git a/bin/shuriken b/bin/shuriken
index d3db424..3c815ed 100755
--- a/bin/shuriken
+++ b/bin/shuriken
@@ -741,7 +741,6 @@ source_template_file() {
local -r output_path="$1"; shift
local -r render_vars_name="$1"; shift
local context_file
- local restore_errexit=no
local -i status=0
context_file=$(mktemp)
@@ -750,18 +749,16 @@ source_template_file() {
return "$status"
fi
- if [[ "$-" == *e* ]]; then
- restore_errexit=yes
- set +e
- fi
- (
- set -e
- serialize_template_render_context "$render_vars_name"
- printf 'unset BASH_ENV\n'
- ) > "$context_file"
- status=$?
- if [ "$restore_errexit" = yes ]; then
- set -e
+ if {
+ declare -p TEMPLATE_RENDER_FIELD_SPECS
+ declare -p "$render_vars_name"
+ declare -f
+ printf 'serialize_template_render_context %q\n' "$render_vars_name"
+ printf "printf 'unset BASH_ENV\\\\n'\n"
+ } | bash -euo pipefail > "$context_file"; then
+ status=0
+ else
+ status=$?
fi
if (( status != 0 )); then
rm -f "$context_file"
@@ -3168,12 +3165,87 @@ run_simple_action() {
esac
}
+run_action_body_context() {
+ local name
+ local -a variable_names=(
+ VERSION
+ DEFAULTRC
+ PACKAGED_TEMPLATE_DIR
+ PACKAGED_ASSET_DIR
+ DEFAULT_TEMPLATE_DIR
+ DEFAULT_ASSET_DIR
+ SHURIKEN_SOURCE_DIR
+ SHURIKEN_OUTPUT_MODE
+ SHURIKEN_ACTIVE_GENERATION_PID
+ SHURIKEN_FORCE_GENERATE
+ SHURIKEN_CURRENT_DATE_TEXT
+ SHURIKEN_CONFIG_SOURCE
+ SHURIKEN_FINAL_DIST_DIR
+ INCOMING_DIR
+ DIST_DIR
+ TEMPLATE_DIR
+ TITLE
+ HEIGHT
+ THUMBHEIGHT
+ MAXPREVIEWS
+ IMAGE_JOBS
+ IMAGEMAGICK_TIMEOUT
+ TAR_TIMEOUT
+ ORIGINAL_BASEPATH
+ RANDOM_SEED
+ SHUFFLE
+ SPLASH_PAGE
+ TARBALL_INCLUDE
+ TARBALL_SUFFIX
+ TAR_OPTS
+ SYNC_DELETE
+ SYNC_DESTINATIONS
+ TEMPLATE_RENDER_FIELD_SPECS
+ )
+
+ for name in "${variable_names[@]}"; do
+ declare -p "$name" 2>/dev/null || true
+ done
+ declare -f
+}
+
run_action_body() {
- if [[ "$-" == *e* ]]; then
- "$@"
+ local -r action_name="$1"; shift
+ local -i status=0
+
+ if {
+ run_action_body_context
+ printf '%q "$@"\n' "$action_name"
+ } | bash -euo pipefail -s -- "$@"; then
+ status=0
+ else
+ status=$?
+ fi
+
+ return "$status"
+}
+
+run_action_body_direct() {
+ "$@"
+}
+
+run_configured_action_body() {
+ local -r action_name="$1"; shift
+ local -r runner="${SHURIKEN_ACTION_BODY_RUNNER:-run_action_body}"
+ local -i status=0
+
+ if [ "$runner" = run_action_body_direct ]; then
+ "$runner" "$action_name" "$@"
+ return
+ fi
+
+ if "$runner" "$action_name" "$@"; then
+ status=0
else
- ( set -e; "$@" )
+ status=$?
fi
+
+ return "$status"
}
load_configured_action() {
@@ -3268,7 +3340,7 @@ run_configured_action() {
return "$status"
fi
- run_action_body generate_staged
+ run_configured_action_body generate_staged
status=$?
if (( status != 0 )); then
return "$status"
@@ -3281,7 +3353,7 @@ run_configured_action() {
return "$status"
fi
- run_action_body refresh_splash
+ run_configured_action_body refresh_splash
status=$?
if (( status != 0 )); then
return "$status"
@@ -3294,7 +3366,7 @@ run_configured_action() {
return "$status"
fi
- run_action_body sync_dist
+ run_configured_action_body sync_dist
status=$?
if (( status != 0 )); then
return "$status"
@@ -3307,7 +3379,7 @@ run_configured_action() {
return "$status"
fi
- run_action_body dry_run
+ run_configured_action_body dry_run
status=$?
if (( status != 0 )); then
return "$status"
@@ -3320,7 +3392,7 @@ run_configured_action() {
return "$status"
fi
- run_action_body print_config
+ run_configured_action_body print_config
status=$?
if (( status != 0 )); then
return "$status"
@@ -3341,7 +3413,8 @@ run_action() {
fi
;;
--clean|--generate|--refresh-splash|--sync|--dry-run|--print-config)
- run_configured_action
+ SHURIKEN_ACTION_BODY_RUNNER=run_action_body_direct \
+ run_configured_action
status=$?
if (( status != 0 )); then
return "$status"
diff --git a/src/lib/action.source.sh b/src/lib/action.source.sh
index f3e92be..77b7f8c 100644
--- a/src/lib/action.source.sh
+++ b/src/lib/action.source.sh
@@ -23,12 +23,87 @@ run_simple_action() {
esac
}
+run_action_body_context() {
+ local name
+ local -a variable_names=(
+ VERSION
+ DEFAULTRC
+ PACKAGED_TEMPLATE_DIR
+ PACKAGED_ASSET_DIR
+ DEFAULT_TEMPLATE_DIR
+ DEFAULT_ASSET_DIR
+ SHURIKEN_SOURCE_DIR
+ SHURIKEN_OUTPUT_MODE
+ SHURIKEN_ACTIVE_GENERATION_PID
+ SHURIKEN_FORCE_GENERATE
+ SHURIKEN_CURRENT_DATE_TEXT
+ SHURIKEN_CONFIG_SOURCE
+ SHURIKEN_FINAL_DIST_DIR
+ INCOMING_DIR
+ DIST_DIR
+ TEMPLATE_DIR
+ TITLE
+ HEIGHT
+ THUMBHEIGHT
+ MAXPREVIEWS
+ IMAGE_JOBS
+ IMAGEMAGICK_TIMEOUT
+ TAR_TIMEOUT
+ ORIGINAL_BASEPATH
+ RANDOM_SEED
+ SHUFFLE
+ SPLASH_PAGE
+ TARBALL_INCLUDE
+ TARBALL_SUFFIX
+ TAR_OPTS
+ SYNC_DELETE
+ SYNC_DESTINATIONS
+ TEMPLATE_RENDER_FIELD_SPECS
+ )
+
+ for name in "${variable_names[@]}"; do
+ declare -p "$name" 2>/dev/null || true
+ done
+ declare -f
+}
+
run_action_body() {
- if [[ "$-" == *e* ]]; then
- "$@"
+ local -r action_name="$1"; shift
+ local -i status=0
+
+ if {
+ run_action_body_context
+ printf '%q "$@"\n' "$action_name"
+ } | bash -euo pipefail -s -- "$@"; then
+ status=0
+ else
+ status=$?
+ fi
+
+ return "$status"
+}
+
+run_action_body_direct() {
+ "$@"
+}
+
+run_configured_action_body() {
+ local -r action_name="$1"; shift
+ local -r runner="${SHURIKEN_ACTION_BODY_RUNNER:-run_action_body}"
+ local -i status=0
+
+ if [ "$runner" = run_action_body_direct ]; then
+ "$runner" "$action_name" "$@"
+ return
+ fi
+
+ if "$runner" "$action_name" "$@"; then
+ status=0
else
- ( set -e; "$@" )
+ status=$?
fi
+
+ return "$status"
}
load_configured_action() {
@@ -123,7 +198,7 @@ run_configured_action() {
return "$status"
fi
- run_action_body generate_staged
+ run_configured_action_body generate_staged
status=$?
if (( status != 0 )); then
return "$status"
@@ -136,7 +211,7 @@ run_configured_action() {
return "$status"
fi
- run_action_body refresh_splash
+ run_configured_action_body refresh_splash
status=$?
if (( status != 0 )); then
return "$status"
@@ -149,7 +224,7 @@ run_configured_action() {
return "$status"
fi
- run_action_body sync_dist
+ run_configured_action_body sync_dist
status=$?
if (( status != 0 )); then
return "$status"
@@ -162,7 +237,7 @@ run_configured_action() {
return "$status"
fi
- run_action_body dry_run
+ run_configured_action_body dry_run
status=$?
if (( status != 0 )); then
return "$status"
@@ -175,7 +250,7 @@ run_configured_action() {
return "$status"
fi
- run_action_body print_config
+ run_configured_action_body print_config
status=$?
if (( status != 0 )); then
return "$status"
@@ -196,7 +271,8 @@ run_action() {
fi
;;
--clean|--generate|--refresh-splash|--sync|--dry-run|--print-config)
- run_configured_action
+ SHURIKEN_ACTION_BODY_RUNNER=run_action_body_direct \
+ run_configured_action
status=$?
if (( status != 0 )); then
return "$status"
diff --git a/src/lib/template.source.sh b/src/lib/template.source.sh
index 8ab87b2..c7d9ded 100644
--- a/src/lib/template.source.sh
+++ b/src/lib/template.source.sh
@@ -296,7 +296,6 @@ source_template_file() {
local -r output_path="$1"; shift
local -r render_vars_name="$1"; shift
local context_file
- local restore_errexit=no
local -i status=0
context_file=$(mktemp)
@@ -305,18 +304,16 @@ source_template_file() {
return "$status"
fi
- if [[ "$-" == *e* ]]; then
- restore_errexit=yes
- set +e
- fi
- (
- set -e
- serialize_template_render_context "$render_vars_name"
- printf 'unset BASH_ENV\n'
- ) > "$context_file"
- status=$?
- if [ "$restore_errexit" = yes ]; then
- set -e
+ if {
+ declare -p TEMPLATE_RENDER_FIELD_SPECS
+ declare -p "$render_vars_name"
+ declare -f
+ printf 'serialize_template_render_context %q\n' "$render_vars_name"
+ printf "printf 'unset BASH_ENV\\\\n'\n"
+ } | bash -euo pipefail > "$context_file"; then
+ status=0
+ else
+ status=$?
fi
if (( status != 0 )); then
rm -f "$context_file"
diff --git a/tests/cli.sh b/tests/cli.sh
index c747594..42522a2 100755
--- a/tests/cli.sh
+++ b/tests/cli.sh
@@ -2130,6 +2130,71 @@ BASH
test::teardown
}
+test_generate_action_failure_fails_status_tested_dispatcher() {
+ local config_file
+ local continued_file
+ local output
+ local success_file
+ local -i status=0
+
+ test::setup
+ config_file="$TEST_TMPDIR/shuriken.conf"
+ continued_file="$TEST_TMPDIR/dry-run-continued"
+ success_file="$TEST_TMPDIR/dispatcher-success"
+ mkdir -p "$TEST_TMPDIR/incoming"
+ test::write_preflight_config \
+ "$config_file" "$TEST_TMPDIR/incoming" "$TEST_TMPDIR/dist" \
+ "$TEST_REPO_ROOT/share/templates/default"
+
+ set +e
+ output=$(
+ bash -euo pipefail -s \
+ "$TEST_SHURIKEN" \
+ "$config_file" \
+ "$continued_file" \
+ "$success_file" \
+ 2>&1 \
+ <<'BASH'
+shuriken="$1"; shift
+config_file="$1"; shift
+continued_file="$1"; shift
+success_file="$1"; shift
+export continued_file success_file
+
+# shellcheck source=/dev/null
+source <(sed '$d' "$shuriken")
+
+dry_run() {
+ false
+ printf 'dry_run continued\n' > "$continued_file"
+}
+
+SHURIKEN_CLI_ACTION=--dry-run
+SHURIKEN_CLI_CONFIG_FILE="$config_file"
+SHURIKEN_CLI_HAS_CONFIG_OVERRIDES=no
+SHURIKEN_CLI_OVERRIDES=()
+SHURIKEN_CLI_SYNC_DESTINATIONS=()
+SHURIKEN_FORCE_GENERATE=no
+
+if run_configured_action; then
+ printf 'dispatcher reported success\n' > "$success_file"
+fi
+BASH
+ )
+ status=$?
+ set -e
+
+ if (( status != 0 )); then
+ printf 'FAIL: expected child shell to finish status-tested dispatch\n' >&2
+ printf '%s\n' "$output" >&2
+ exit 1
+ fi
+
+ test::assert_path_absent "$continued_file"
+ test::assert_path_absent "$success_file"
+ test::teardown
+}
+
test_generate_real_failure_returns_with_errexit_disabled() {
local config_file
local fake_bin
@@ -3656,6 +3721,99 @@ BASH
test::teardown
}
+test_template_setup_failure_fails_status_tested_render() {
+ local context_file
+ local fake_bin
+ local output
+ local ran_file
+ local success_file
+ local template_dir
+ local -i status=0
+
+ test::setup
+ fake_bin="$TEST_TMPDIR/bin"
+ template_dir="$TEST_TMPDIR/templates"
+ context_file="$TEST_TMPDIR/template-context"
+ ran_file="$TEST_TMPDIR/template-ran"
+ success_file="$TEST_TMPDIR/template-success"
+ mkdir -p "$fake_bin" "$template_dir" "$TEST_TMPDIR/dist"
+ {
+ printf '#!/usr/bin/env bash\n'
+ # shellcheck disable=SC2016
+ printf 'printf %%s\\\\n \"$SHURIKEN_FAKE_CONTEXT_FILE\"\n'
+ } > "$fake_bin/mktemp"
+ chmod 0755 "$fake_bin/mktemp"
+ {
+ printf 'printf rendered >> %q\n' "$ran_file"
+ printf 'printf rendered\n'
+ } > "$template_dir/preview.tmpl"
+
+ set +e
+ output=$(
+ bash -euo pipefail -s \
+ "$TEST_SHURIKEN" \
+ "$fake_bin" \
+ "$template_dir" \
+ "$TEST_TMPDIR/dist" \
+ "$context_file" \
+ "$success_file" \
+ 2>&1 \
+ <<'BASH'
+shuriken="$1"; shift
+fake_bin="$1"; shift
+template_dir="$1"; shift
+dist_dir="$1"; shift
+context_file="$1"; shift
+success_file="$1"; shift
+
+# shellcheck source=/dev/null
+source <(sed '$d' "$shuriken")
+
+PATH="$fake_bin:$PATH"
+SHURIKEN_FAKE_CONTEXT_FILE="$context_file"
+export SHURIKEN_FAKE_CONTEXT_FILE
+DIST_DIR="$dist_dir"
+TEMPLATE_DIR="$template_dir"
+TITLE='Template status test'
+HEIGHT=''
+THUMBHEIGHT=30
+MAXPREVIEWS=40
+ORIGINAL_BASEPATH=''
+TARBALL_INCLUDE=no
+SHURIKEN_OUTPUT_MODE=quiet
+
+serialize_template_render_context() {
+ false
+ printf 'partial_context=yes\n'
+}
+
+if template preview out.html \
+ animation_class '' \
+ backhref '#' \
+ html_dir . \
+ page_num 1 \
+ photo photo.jpg \
+ preview_num 1 \
+ thumbs_dir thumbs; then
+ printf 'template reported success\n' > "$success_file"
+fi
+BASH
+ )
+ status=$?
+ set -e
+
+ if (( status != 0 )); then
+ printf 'FAIL: expected child shell to finish status-tested render\n' >&2
+ printf '%s\n' "$output" >&2
+ exit 1
+ fi
+
+ test::assert_path_absent "$context_file"
+ test::assert_path_absent "$ran_file"
+ test::assert_path_absent "$success_file"
+ test::teardown
+}
+
test_generate_swap_failure_restores_dist() {
local config_file
local fake_bin
@@ -4476,6 +4634,9 @@ main() {
'--generate action runs with errexit active' \
test_generate_action_runs_with_errexit_active
test::run_case \
+ '--generate action failure fails status-tested dispatcher' \
+ test_generate_action_failure_fails_status_tested_dispatcher
+ test::run_case \
'--generate real failure returns with errexit disabled' \
test_generate_real_failure_returns_with_errexit_disabled
test::run_case \
@@ -4575,6 +4736,9 @@ main() {
'template setup failure removes context file with errexit' \
test_template_setup_failure_removes_context_file_with_errexit
test::run_case \
+ 'template setup failure fails status-tested render' \
+ test_template_setup_failure_fails_status_tested_render
+ test::run_case \
'--generate swap failure restores final dist' \
test_generate_swap_failure_restores_dist
test::run_case \