Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 44 additions & 21 deletions lib/bash/file/lib_file.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,32 @@ __preserve_file_mode__() {
chmod "$source_mode" "$target_file"
}

__file_remove_temp_paths__() {
local path

for path; do
[[ -n "$path" ]] && rm -f -- "$path"
done
return 0
}

__file_make_target_temp__() {
local result_name="$1" target_file="$2"
local target_dir target_base temp_dir temp_path

target_dir="$(dirname -- "$target_file")"
target_base="$(basename -- "$target_file")"
temp_dir="$(cd -- "$target_dir" && pwd -P)" || return 1

temp_path="$(mktemp "$temp_dir/$target_base.XXXXXX" 2>/dev/null)" || return 1
if ! std_register_cleanup_path "$temp_path"; then
rm -f -- "$temp_path"
return 1
fi

printf -v "$result_name" '%s' "$temp_path"
}

__file_section_markers_ordered__() {
local target_file="$1" beginning_marker="$2" end_marker="$3"

Expand Down Expand Up @@ -165,24 +191,22 @@ update_file_section() {

local current_content_file="" new_content_file="" temp_file
if [[ "$remove_section" == false ]]; then
new_content_file=$(mktemp "${TMPDIR:-/tmp}/base-file-section-new.XXXXXX")
if [[ ! -f "$new_content_file" ]]; then
if ! std_make_temp_file new_content_file base-file-section-new; then
log_error "Failed to create temporary content file for '$target_file'."
return 1
fi

if ! printf '%s' "$new_content_string" > "$new_content_file"; then
log_error "Failed to write replacement content for '$target_file'."
rm -f "$new_content_file"
__file_remove_temp_paths__ "$new_content_file"
return 1
fi
fi

if [[ "$section_exists" == true && "$remove_section" == false ]]; then
current_content_file=$(mktemp "${TMPDIR:-/tmp}/base-file-section-current.XXXXXX")
if [[ ! -f "$current_content_file" ]]; then
if ! std_make_temp_file current_content_file base-file-section-current; then
log_error "Failed to create temporary current content file for '$target_file'."
rm -f "$new_content_file"
__file_remove_temp_paths__ "$new_content_file"
return 1
fi

Expand All @@ -204,28 +228,27 @@ update_file_section() {
}
' "$target_file" > "$current_content_file"; then
log_error "Failed to read existing section in '$target_file'."
rm -f "$current_content_file" "$new_content_file"
__file_remove_temp_paths__ "$current_content_file" "$new_content_file"
return 1
fi

if cmp -s "$current_content_file" "$new_content_file"; then
log_debug "Section already up to date in '$target_file'."
rm -f "$current_content_file" "$new_content_file"
__file_remove_temp_paths__ "$current_content_file" "$new_content_file"
return 0
fi
rm -f "$current_content_file"
__file_remove_temp_paths__ "$current_content_file"
fi

log_info "Updating '$target_file'"
temp_file=$(mktemp "${target_file}.XXXXXX")
if [[ ! -f "$temp_file" ]]; then
if ! __file_make_target_temp__ temp_file "$target_file"; then
log_error "Failed to create temporary file for '$target_file'."
rm -f "$new_content_file"
__file_remove_temp_paths__ "$new_content_file"
return 1
fi
if ! __preserve_file_mode__ "$target_file" "$temp_file"; then
log_error "Failed to preserve permissions for '$target_file'."
rm -f "$temp_file" "$new_content_file"
__file_remove_temp_paths__ "$temp_file" "$new_content_file"
return 1
fi

Expand All @@ -248,7 +271,7 @@ update_file_section() {
}
}
' "$target_file" > "$temp_file" && mv -f "$temp_file" "$target_file"; then
rm -f "$new_content_file"
__file_remove_temp_paths__ "$new_content_file"
return 0
fi
else
Expand All @@ -274,26 +297,26 @@ update_file_section() {
print $0
}
' "$target_file" > "$temp_file" && mv -f "$temp_file" "$target_file"; then
rm -f "$new_content_file"
__file_remove_temp_paths__ "$new_content_file"
return 0
fi
fi

log_error "Failed to process sections in '$target_file'."
rm -f "$temp_file" "$new_content_file"
__file_remove_temp_paths__ "$temp_file" "$new_content_file"
return 1
else
# Markers not found in the file
if ! cp "$target_file" "$temp_file"; then
log_error "Failed to copy '$target_file' to '$temp_file'."
rm -f "$temp_file" "$new_content_file"
__file_remove_temp_paths__ "$temp_file" "$new_content_file"
return 1
fi

if [[ -s "$temp_file" ]] && [[ $(tail -c 1 "$temp_file" 2>/dev/null | wc -l) -eq 0 ]]; then
if ! printf '\n' >> "$temp_file"; then
log_error "Failed to add trailing newline to '$temp_file'."
rm -f "$temp_file" "$new_content_file"
__file_remove_temp_paths__ "$temp_file" "$new_content_file"
return 1
fi
fi
Expand All @@ -304,17 +327,17 @@ update_file_section() {
printf '%s\n' "$end_marker"
} >> "$temp_file"; then
log_error "Failed to add new section to '$target_file'."
rm -f "$temp_file" "$new_content_file"
__file_remove_temp_paths__ "$temp_file" "$new_content_file"
return 1
fi

if ! mv -f "$temp_file" "$target_file"; then
log_error "Failed to replace '$target_file' with '$temp_file'."
rm -f "$temp_file" "$new_content_file"
__file_remove_temp_paths__ "$temp_file" "$new_content_file"
return 1
fi

rm -f "$new_content_file"
__file_remove_temp_paths__ "$new_content_file"
return 0
fi
}
38 changes: 32 additions & 6 deletions lib/bash/file/tests/lib_file.bats
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,32 @@ EOF
[ "$(cat "$target")" = $'before\n# BEGIN\nfirst\nsecond\nthird\n# END\nafter' ]
}

@test "update_file_section registers internal temp files for cleanup" {
local registration_file="$TEST_TMPDIR/registered-cleanup-paths.txt"
local target="$TEST_TMPDIR/config.txt"
cat <<'EOF' > "$target"
before
# BEGIN
old
# END
after
EOF

eval "$(declare -f std_register_cleanup_path | sed '1s/std_register_cleanup_path/__orig_std_register_cleanup_path/')"
std_register_cleanup_path() {
printf '%s\n' "$@" >> "$registration_file"
__orig_std_register_cleanup_path "$@"
}

update_file_section "$target" "# BEGIN" "# END" "new"
unset -f std_register_cleanup_path __orig_std_register_cleanup_path

[ "$(cat "$target")" = $'before\n# BEGIN\nnew\n# END\nafter' ]
[[ "$(cat "$registration_file")" == *"base-file-section-new."* ]]
[[ "$(cat "$registration_file")" == *"base-file-section-current."* ]]
[[ "$(cat "$registration_file")" == *"config.txt."* ]]
}

@test "update_file_section skips unchanged existing section" {
local before_inode
local target="$TEST_TMPDIR/config.txt"
Expand All @@ -160,15 +186,15 @@ after
EOF
before_inode="$(file_inode "$target")"

bats_run update_file_section "$target" "# BEGIN" "# END" "same" "content"
capture_command update_file_section "$target" "# BEGIN" "# END" "same" "content"

[ "$status" -eq 0 ]
[[ "$output" != *"Updating '$target'"* ]]
[ "$(file_inode "$target")" = "$before_inode" ]
[ "$(cat "$target")" = $'before\n# BEGIN\nsame\ncontent\n# END\nafter' ]

set_log_level DEBUG
bats_run update_file_section "$target" "# BEGIN" "# END" "same" "content"
capture_command update_file_section "$target" "# BEGIN" "# END" "same" "content"

[ "$status" -eq 0 ]
[[ "$output" == *"Section already up to date in '$target'."* ]]
Expand Down Expand Up @@ -201,7 +227,7 @@ EOF
[ "$(cat "$target")" = $'before\n# BEGIN\nsecret\nvalue\n# END\nafter' ]
}

@test "update_file_section removes a marked block with -r" {
@test "update_file_section removes a marked block with remove option" {
local target="$TEST_TMPDIR/config.txt"
cat <<'EOF' > "$target"
before
Expand All @@ -216,7 +242,7 @@ EOF
[ "$(cat "$target")" = $'before\nafter' ]
}

@test "update_file_section removes only the first matching marked block with -r" {
@test "update_file_section removes only the first matching marked block with remove option" {
local target="$TEST_TMPDIR/config.txt"
cat <<'EOF' > "$target"
before
Expand Down Expand Up @@ -287,7 +313,7 @@ EOF
@test "update_file_section is a no-op for a missing target file" {
local target="$TEST_TMPDIR/missing.txt"

bats_run update_file_section "$target" "# BEGIN" "# END" "value"
capture_command update_file_section "$target" "# BEGIN" "# END" "value"

[ "$status" -eq 0 ]
[ ! -e "$target" ]
Expand All @@ -312,7 +338,7 @@ EOF
return 1
}

bats_run update_file_section "$target" "# BEGIN" "# END" "value"
capture_command update_file_section "$target" "# BEGIN" "# END" "value"
unset -f cp

[ "$status" -eq 1 ]
Expand Down
2 changes: 1 addition & 1 deletion lib/bash/git/lib_git.sh
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ git_update_repo() {
return 1
fi

git_log="$(mktemp "${TMPDIR:-/tmp}/git_log.XXXXXX")" || {
std_make_temp_file git_log git_log || {
log_error "Unable to create temporary git log file."
return 1
}
Expand Down
40 changes: 31 additions & 9 deletions lib/bash/git/tests/lib_git.bats
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ setup() {
}

@test "git_update_repo usage names the current function" {
bats_run git_update_repo
capture_command git_update_repo

[ "$status" -eq 1 ]
[[ "$output" == *"Usage: git_update_repo /path/to/repo [allowed_dirty_path] [expected_branch]"* ]]
Expand All @@ -131,7 +131,7 @@ setup() {
printf 'local change\n' > "$repo/data.txt"
set_log_level DEBUG

bats_run git_update_repo "$repo"
capture_command git_update_repo "$repo"

[ "$status" -eq 0 ]
[[ "$output" == *"has local changes; skipping auto-update"* ]]
Expand All @@ -145,7 +145,7 @@ setup() {
commit_all "$repo" "Initial commit"
set_log_level DEBUG

bats_run git_update_repo "$repo" "" release
capture_command git_update_repo "$repo" "" release

[ "$status" -eq 0 ]
[[ "$output" == *"not 'release'. Skipping update"* ]]
Expand All @@ -160,7 +160,7 @@ setup() {
commit_all "$repo" "Initial commit"
before_head="$(git -C "$repo" rev-parse HEAD)"

bats_run git_update_repo "$repo" "" main
capture_command git_update_repo "$repo" "" main

[ "$status" -eq 1 ]
[[ "$output" == *"git pull failed on repo '$repo'"* ]]
Expand All @@ -178,7 +178,7 @@ setup() {
before_head="$(git -C "$repo" rev-parse HEAD)"
git -C "$repo" remote set-url origin "$TEST_TMPDIR/missing-remote.git"

bats_run git_update_repo "$repo" "" main
capture_command git_update_repo "$repo" "" main

[ "$status" -eq 1 ]
[[ "$output" == *"git pull failed on repo '$repo'"* ]]
Expand All @@ -204,7 +204,7 @@ setup() {
git -C "$other" commit --amend -m "Rewrite remote history" >/dev/null 2>&1
git -C "$other" push --force origin main >/dev/null 2>&1

bats_run git_update_repo "$repo" "" main
capture_command git_update_repo "$repo" "" main

[ "$status" -eq 1 ]
[[ "$output" == *"git pull failed on repo '$repo'"* ]]
Expand All @@ -230,7 +230,7 @@ setup() {
git -C "$other" push origin main >/dev/null 2>&1
printf 'local untracked\n' > "$repo/local-notes.md"

bats_run git_update_repo "$repo" "" main
capture_command git_update_repo "$repo" "" main

[ "$status" -eq 1 ]
[[ "$output" == *"git pull failed on repo '$repo'"* ]]
Expand All @@ -249,7 +249,7 @@ setup() {
printf 'local change\n' > "$repo/data.txt"
set_log_level DEBUG

bats_run git_update_repo "$repo"
capture_command git_update_repo "$repo"

[ "$status" -eq 0 ]
[[ "$output" == *"has local changes; skipping auto-update"* ]]
Expand Down Expand Up @@ -397,7 +397,7 @@ setup() {
printf 'local change\n' > "$repo/data.txt"

trap 'printf "outer return trap\n"' RETURN
TMPDIR="$temp_dir" bats_run git_update_repo "$repo"
TMPDIR="$temp_dir" capture_command git_update_repo "$repo"
return_trap="$(trap -p RETURN)"
trap - RETURN

Expand All @@ -406,6 +406,28 @@ setup() {
! compgen -G "$temp_dir/git_log.*" >/dev/null
}

@test "git_update_repo registers temp log for cleanup" {
local repo="$TEST_TMPDIR/repo"
local registration_file="$TEST_TMPDIR/registered-cleanup-paths.txt"

init_git_repo "$repo"
printf 'base\n' > "$repo/data.txt"
commit_all "$repo" "Initial commit"
printf 'local change\n' > "$repo/data.txt"

eval "$(declare -f std_register_cleanup_path | sed '1s/std_register_cleanup_path/__orig_std_register_cleanup_path/')"
std_register_cleanup_path() {
printf '%s\n' "$@" >> "$registration_file"
__orig_std_register_cleanup_path "$@"
}

capture_command git_update_repo "$repo"
unset -f std_register_cleanup_path __orig_std_register_cleanup_path

[ "$status" -eq 0 ]
[[ "$(cat "$registration_file")" == *"git_log."* ]]
}

@test "_git_update_repo_finish removes temp log after success" {
local git_log="$TEST_TMPDIR/git.log"

Expand Down
9 changes: 6 additions & 3 deletions lib/bash/std/lib_std.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1033,7 +1033,7 @@ __std_run_with_timeout_fallback__() {
local timeout_marker command_pid timer_pid command_status
local kill_grace_seconds=1

timeout_marker="$(mktemp "${TMPDIR:-/tmp}/base-bash-libs-timeout.XXXXXXXXXX" 2>/dev/null)" || return 127
std_make_temp_file timeout_marker base-bash-libs-timeout || return 127

"$@" &
command_pid=$!
Expand Down Expand Up @@ -1229,10 +1229,13 @@ safe_truncate() {

__std_get_exit_trap_command__() {
local result_name="$1" trap_spec=""
local trap_prefix="trap -- '" trap_suffix="' EXIT"

trap_spec="$(trap -p EXIT || true)"
if [[ "$trap_spec" =~ ^trap\ --\ \'(.*)\'\ EXIT$ ]]; then
printf -v "$result_name" '%s' "${BASH_REMATCH[1]}"
if [[ "$trap_spec" == "$trap_prefix"*"$trap_suffix" ]]; then
trap_spec="${trap_spec#"$trap_prefix"}"
trap_spec="${trap_spec%"$trap_suffix"}"
printf -v "$result_name" '%s' "$trap_spec"
else
printf -v "$result_name" '%s' ""
fi
Expand Down
Loading
Loading