#!/bin/sh
# .ci/lib-temp.sh
# Centralized, idempotent helpers for temporary files and cleanup (POSIX /bin/sh).
# - Idempotent: safe to source multiple times.
# - Exports: TMPDIR_SAFE, _TMP_TRACKER, _REGISTER_FILE, CI_TMP_HELPERS_AVAILABLE, CI_TMP_REPODIR.
# - Public helpers: _mktemp_safe, _mktemp_safe_create, _mktempdir_safe, _register_tmpfile,
# register_cleanup, _cleanup_tmpfiles, _cleanup_on_exit, acquire_lock, release_lock, atomic_replace_file.
# - Design: single-file for simple sourcing; organized in clear sections with minimal inline docs.
#
# NOTE: For extended documentation see docs/ci-lib-temp.md
# --- Idempotence guard ---
[ -n "${__CI_TMP_HELPERS_LOADED:-}" ] && return 0
__CI_TMP_HELPERS_LOADED=1
# --- Minimal portable utilities ---
_has() { command -v "$1" >/dev/null 2>&1; }
_portable_return_or_exit() { code="${1:-0}"; (return 0 2>/dev/null) && return "$code" || exit "$code"; }
_sanitize() {
printf '%s' "$1" | tr -d '\r' | tr '\n' ' ' | sed 's/[[:space:]]\+/ /g' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
}
# --- SECTION: Configuration ---
CI_BASEDIR="${CI_BASEDIR:-$(pwd 2>/dev/null || printf '%s' '')}"
CI_BASEDIR="$(_sanitize "${CI_BASEDIR:-}")"
CI_BASEDIR="${CI_BASEDIR%/}"
[ -n "${CI_BASEDIR:-}" ] || CI_BASEDIR="$(pwd 2>/dev/null || printf '%s' '')"
CI_TMP_REPODIR="${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}"
CI_TMP_REPODIR="$(_sanitize "${CI_TMP_REPODIR:-}")"
CI_TMP_REPODIR="${CI_TMP_REPODIR%/}"
CI_OUTDIR="${CI_OUTDIR:-${CI_BASEDIR%/}/docs/checkup}"
CI_OUTDIR="${CI_OUTDIR%/}"
CI_TMP_PROFILE="${CI_TMP_PROFILE:-balanced}"
KEEP_BACKUP="${KEEP_BACKUP:-0}"
LOCK_RETRIES="${LOCK_RETRIES:-20}"
LOCK_SLEEP="${LOCK_SLEEP:-1}"
CI_TMP_MAX_ENTRIES="${CI_TMP_MAX_ENTRIES:-10}"
_TMP_INIT_DONE="${_TMP_INIT_DONE:-}"
_REGISTER_FILE="${_REGISTER_FILE:-}"
_TMP_TRACKER="${_TMP_TRACKER:-}"
_PRUNE_IN_PROGRESS="${_PRUNE_IN_PROGRESS:-0}"
# Detect Termux/Android to avoid aggressive pruning there
_termux_detected=0
if [ -n "${TERMUX_VERSION:-}" ] || [ -n "${ANDROID_ROOT:-}" ] || [ -f "/data/data/com.termux/files/usr/bin/termux-info" ]; then
_termux_detected=1
fi
# --- SECTION: Utilities ---
_get_mtime() {
p="${1:-}"
[ -n "${p:-}" ] || { printf '%s\n' 0; return 0; }
if _has stat; then
stat -c %Y "$p" 2>/dev/null && return 0
stat -f %m "$p" 2>/dev/null && return 0
fi
printf '%s\n' 0
}
# --- SECTION: Prune helpers ---
_prune_repo_tmpdirs_keep_newest() {
repo_base="${1:-${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}}"
keep="${2:-${CI_TMP_MAX_ENTRIES:-10}}"
[ -n "${repo_base:-}" ] || return 0
case "$keep" in ''|*[!0-9]*) keep=10 ;; esac
[ -d "$repo_base" ] || return 0
_PRUNE_IN_PROGRESS=1
# Ensure repo_base exists to avoid mktemp failures on constrained envs
[ -d "$repo_base" ] || mkdir -p "$repo_base" 2>/dev/null || true
if _has mktemp; then
tmpfile="$(mktemp -p "$repo_base" ".prune.XXXXXX" 2>/dev/null || printf '%s' "${repo_base%/}/.prune.$$")" || {
_PRUNE_IN_PROGRESS=0
return 0
}
else
tmpfile="${repo_base%/}/.prune.$$"
fi
if _has find && _has stat && _has sort; then
find "$repo_base" -maxdepth 1 -type d -name 'ci-tmp*' ! -path "$repo_base" 2>/dev/null | while IFS= read -r d; do
case "$d" in "${repo_base%/}"/*) : ;; *) continue ;; esac
mtime="$(stat -c %Y "$d" 2>/dev/null || stat -f %m "$d" 2>/dev/null || printf '%s' 0)"
printf '%s\t%s\n' "$mtime" "$d" >> "$tmpfile"
done
else
for d in "$repo_base"/ci-tmp*; do
[ -d "$d" ] || continue
case "$d" in "${repo_base%/}"/*) : ;; *) continue ;; esac
mtime="$(_get_mtime "$d" 2>/dev/null || printf '%s' 0)"
printf '%s\t%s\n' "$mtime" "$d" >> "$tmpfile"
done
fi
total="$(wc -l < "$tmpfile" 2>/dev/null || printf '%s' 0)"
if [ "$total" -le "$keep" ]; then
rm -f "$tmpfile" 2>/dev/null || true
_PRUNE_IN_PROGRESS=0
return 0
fi
sort -nr "$tmpfile" | awk -F'\t' -v k="$keep" 'NR>k{print $2}' | while IFS= read -r old; do
[ -z "${old:-}" ] && continue
case "$old" in "${repo_base%/}"/*) : ;; *) continue ;; esac
bn="$(basename "$old" 2>/dev/null || printf '%s' "$old")"
case "$bn" in
*capture*|*export*|*secrets*)
printf '%s\n' "WARN: skipping sensitive path during prune: $old" >&2 || true
continue
;;
esac
if [ -n "$old" ] && [ -d "$old" ]; then
rm -rf -- "$old" 2>/dev/null || true
fi
done
rm -f "$tmpfile" 2>/dev/null || true
_PRUNE_IN_PROGRESS=0
return 0
}
_prune_tracker_missing_entries() {
tracker="${_TMP_TRACKER:-}"
[ -f "$tracker" ] || return 0
if _has mktemp; then
tmpkeep="$(mktemp -p "$(dirname "$tracker")" "$(basename "$tracker").keep.XXXXXX" 2>/dev/null || printf '%s' "${tracker}.keep.$$")"
else
tmpkeep="${tracker}.keep.$$"
fi
while IFS= read -r p || [ -n "${p:-}" ]; do
[ -z "${p:-}" ] && continue
if [ -e "$p" ]; then
printf '%s\n' "$p" >> "$tmpkeep"
fi
done < "$tracker"
mv "$tmpkeep" "$tracker" 2>/dev/null || cp -a "$tmpkeep" "$tracker" 2>/dev/null || true
rm -f "$tmpkeep" 2>/dev/null || true
return 0
}
# --- SECTION: Initialization ---
_TMP_INIT() {
if [ -n "${_TMP_INIT_DONE:-}" ] && [ -n "${TMPDIR_SAFE:-}" ] && [ -d "${TMPDIR_SAFE}" ]; then
printf '%s\n' "${TMPDIR_SAFE}"
return 0
fi
base="${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}"
base="$(_sanitize "${base:-}")"
base="${base%/}"
if [ -z "${base}" ] || [ "${base}" = "/" ]; then
base="${CI_BASEDIR%/}/.ci/tmp"
fi
mkdir -p "$base" 2>/dev/null || true
chmod 700 "$base" 2>/dev/null || true
CI_TMP_PRUNE_ON_INIT="${CI_TMP_PRUNE_ON_INIT:-0}"
if [ "${CI_TMP_PRUNE_ON_INIT:-0}" -eq 1 ] && [ "${_termux_detected:-0}" -eq 0 ]; then
_prune_repo_tmpdirs_keep_newest "$base" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
else
printf '%s\n' "INFO: repo tmp prune skipped on init (CI_TMP_PRUNE_ON_INIT=${CI_TMP_PRUNE_ON_INIT})" >/dev/stderr 2>/dev/null || true
fi
testfile="${base%/}/.ci-write-test.$$"
if ! : > "$testfile" 2>/dev/null; then
base="${CI_BASEDIR%/}/.ci/tmp"
mkdir -p "$base" 2>/dev/null || true
chmod 700 "$base" 2>/dev/null || true
fi
rm -f "$testfile" 2>/dev/null || true
now="$(date +%s 2>/dev/null || printf '%s' "$$")"
tmpd=""
[ -d "$base" ] || mkdir -p "$base" 2>/dev/null || true
if _has mktemp; then
out="$(mktemp -d -p "$base" "ci-tmp.XXXXXX" 2>/dev/null || true)"
if [ -n "${out:-}" ] && [ -d "$out" ]; then
tmpd="$out"
fi
fi
tmpd="${tmpd:-${base%/}/ci-tmp.$$.$now}"
mkdir -p "$tmpd" 2>/dev/null || true
chmod 700 "$tmpd" 2>/dev/null || true
TMPDIR_SAFE="$tmpd"
export TMPDIR_SAFE
if command -v _register_tmpfile >/dev/null 2>&1; then
_register_tmpfile "${TMPDIR_SAFE}" 2>/dev/null || true
fi
_TMP_TRACKER="${TMPDIR_SAFE%/}/.tracked.tmpfiles"
mkdir -p "$(dirname "${_TMP_TRACKER}")" 2>/dev/null || true
: > "${_TMP_TRACKER}" 2>/dev/null || true
chmod 600 "${_TMP_TRACKER}" 2>/dev/null || true
export _TMP_TRACKER
_REGISTER_FILE="${TMPDIR_SAFE%/}/.registered.cleanups"
mkdir -p "$(dirname "${_REGISTER_FILE}")" 2>/dev/null || true
: > "${_REGISTER_FILE}" 2>/dev/null || true
chmod 600 "${_REGISTER_FILE}" 2>/dev/null || true
export _REGISTER_FILE
_prune_tracker_missing_entries >/dev/null 2>&1 || true
if [ -z "${_LIBTEMP_TRAP_SET:-}" ]; then
# trap will call the unified _cleanup_on_exit (defined below)
trap '_cleanup_on_exit' EXIT INT TERM HUP
_LIBTEMP_TRAP_SET=1
fi
_TMP_INIT_DONE=1
CI_TMP_HELPERS_AVAILABLE=1; export CI_TMP_HELPERS_AVAILABLE
printf '%s\n' "${TMPDIR_SAFE}"
return 0
}
# Ensure TMPDIR_SAFE is initialized
[ -n "${TMPDIR_SAFE:-}" ] || _TMP_INIT >/dev/null 2>&1 || true
# --- SECTION: Hash helper ---
_sha256() {
f="${1:-}"
[ -n "${f:-}" ] || { printf '%s\n' "n/a"; return 0; }
if _has sha256sum; then
sha256sum -- "$f" 2>/dev/null | awk '{print $1}'
elif _has shasum; then
shasum -a 256 -- "$f" 2>/dev/null | awk '{print $1}'
elif _has openssl; then
openssl dgst -sha256 -- "$f" 2>/dev/null | awk '{print $NF}'
else
printf '%s\n' "n/a"
fi
}
# --- SECTION: mktemp helpers ---
_mktemp_safe() {
tmpl="${1:-tmp.XXXXXX}"
[ -n "${TMPDIR_SAFE:-}" ] || _TMP_INIT >/dev/null 2>&1 || return 1
case "$tmpl" in
*/*)
target_dir="$(dirname "$tmpl")"
name_template="$(basename "$tmpl")"
case "$target_dir" in
/*) resolved_dir="$target_dir" ;;
*) resolved_dir="${TMPDIR_SAFE%/}/${target_dir%/}" ;;
esac
[ -n "${resolved_dir:-}" ] || resolved_dir="${TMPDIR_SAFE%/}"
mkdir -p "$resolved_dir" 2>/dev/null || true
if _has mktemp; then
out="$(mktemp -p "$resolved_dir" "$name_template" 2>/dev/null || true)"
if [ -n "${out:-}" ]; then
printf '%s\n' "$out"
return 0
fi
fi
printf '%s\n' "${resolved_dir%/}/${name_template%XXXXXX}$$.$(date +%s 2>/dev/null || printf '%s' "$$")"
return 0
;;
*)
base="${TMPDIR_SAFE%/}"
if _has mktemp; then
out="$(mktemp -p "$base" "$tmpl" 2>/dev/null || true)"
if [ -n "${out:-}" ]; then
printf '%s\n' "$out"
return 0
fi
fi
printf '%s\n' "${base%/}/${tmpl%XXXXXX}$$.$(date +%s 2>/dev/null || printf '%s' "$$")"
return 0
;;
esac
}
_mktemp_safe_create() {
tmpl="${1:-tmp.XXXXXX}"
f="$(_mktemp_safe "$tmpl")" || return 1
mkdir -p "$(dirname "$f")" 2>/dev/null || true
old_umask="$(umask)"
umask 077
: > "$f" 2>/dev/null || true
umask "$old_umask"
chmod 600 "$f" 2>/dev/null || true
bn="$(basename "$f" 2>/dev/null || printf '%s' "$f")"
case "$bn" in
*capture*|*export*|*secrets*)
;;
*)
printf '%s\n' "$f" >> "${_TMP_TRACKER:-/dev/null}" 2>/dev/null || true
if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
_prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
fi
;;
esac
printf '%s\n' "$f"
}
_mktempdir_safe() {
tmpl="${1:-tmpdir.XXXXXX}"
[ -n "${TMPDIR_SAFE:-}" ] || _TMP_INIT >/dev/null 2>&1 || return 1
case "$tmpl" in
*/*)
target_dir="$(dirname "$tmpl")"
name_template="$(basename "$tmpl")"
case "$target_dir" in
/*) resolved_dir="$target_dir" ;;
*) resolved_dir="${TMPDIR_SAFE%/}/${target_dir%/}" ;;
esac
[ -n "${resolved_dir:-}" ] || resolved_dir="${TMPDIR_SAFE%/}"
mkdir -p "$resolved_dir" 2>/dev/null || true
if _has mktemp; then
out="$(mktemp -d -p "$resolved_dir" "$name_template" 2>/dev/null || true)"
if [ -n "${out:-}" ]; then
printf '%s\n' "$out"
bn="$(basename "$out" 2>/dev/null || printf '%s' "$out")"
case "$bn" in
*capture*|*export*|*secrets*)
;;
*)
printf '%s\n' "$out" >> "${_TMP_TRACKER:-/dev/null}" 2>/dev/null || true
if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
_prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
fi
;;
esac
return 0
fi
fi
d="${resolved_dir%/}/${name_template%XXXXXX}$$.$(date +%s 2>/dev/null || printf '%s' "$$")"
mkdir -p "$d" 2>/dev/null || true
chmod 700 "$d" 2>/dev/null || true
printf '%s\n' "$d"
bn="$(basename "$d" 2>/dev/null || printf '%s' "$d")"
case "$bn" in
*capture*|*export*|*secrets*)
;;
*)
printf '%s\n' "$d" >> "${_TMP_TRACKER:-/dev/null}" 2>/dev/null || true
if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
_prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
fi
;;
esac
return 0
;;
*)
base="${TMPDIR_SAFE%/}"
if _has mktemp; then
out="$(mktemp -d -p "$base" "$tmpl" 2>/dev/null || true)"
if [ -n "${out:-}" ]; then
printf '%s\n' "$out"
bn="$(basename "$out" 2>/dev/null || printf '%s' "$out")"
case "$bn" in
*capture*|*export*|*secrets*)
;;
*)
printf '%s\n' "$out" >> "${_TMP_TRACKER:-/dev/null}" 2>/dev/null || true
if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
_prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
fi
;;
esac
return 0
fi
fi
d="${base%/}/${tmpl%XXXXXX}$$.$(date +%s 2>/dev/null || printf '%s' "$$")"
mkdir -p "$d" 2>/dev/null || true
chmod 700 "$d" 2>/dev/null || true
printf '%s\n' "$d"
bn="$(basename "$d" 2>/dev/null || printf '%s' "$d")"
case "$bn" in
*capture*|*export*|*secrets*)
;;
*)
printf '%s\n' "$d" >> "${_TMP_TRACKER:-/dev/null}" 2>/dev/null || true
if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
_prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
fi
;;
esac
return 0
;;
esac
}
_mktemp_unique() {
dir="${1:-${TMPDIR_SAFE:-${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}}}"
prefix="${2:-tmp}"
mkdir -p "$dir" 2>/dev/null || true
if _has mktemp; then
out="$(mktemp -p "$dir" "${prefix}.XXXXXX" 2>/dev/null || printf '%s\n' "${dir%/}/${prefix}.$$.$(date +%s 2>/dev/null || printf '%s' "$$")")"
else
out="${dir%/}/${prefix}.$$.$(date +%s 2>/dev/null || printf '%s' "$$")"
fi
bn="$(basename "$out" 2>/dev/null || printf '%s' "$out")"
case "$bn" in
*capture*|*export*|*secrets*)
;;
*)
printf '%s\n' "$out" >> "${_TMP_TRACKER:-/dev/null}" 2>/dev/null || true
if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
_prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
fi
;;
esac
printf '%s\n' "$out"
}
_mktemp_for_find() { _mktempdir_safe "$@" || _mktemp_safe_create "$@"; }
# --- SECTION: Scoped tempfile helper ---
_with_tempfile() {
varname="$1"; tmpl="${2:-tmp.XXXXXX}"
f="$(_mktemp_safe_create "$tmpl")" || return 1
bn="$(basename "$f" 2>/dev/null || printf '%s' "$f")"
case "$bn" in
*capture*|*export*|*secrets*)
;;
*)
printf '%s\n' "$f" >> "${_TMP_TRACKER:-/dev/null}" 2>/dev/null || true
if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
_prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
fi
;;
esac
eval "$varname=\"\$f\"" 2>/dev/null || return 1
return 0
}
# --- SECTION: Writable check ---
_verify_writable() {
path="${1:-}"
[ -n "${path:-}" ] || return 1
mkdir -p "$(dirname "$path")" 2>/dev/null || true
: > "$path" 2>/dev/null
return $?
}
# --- SECTION: Simple cleanup ---
_cleanup_tmpfiles() {
for f in "$@"; do
[ -n "${f:-}" ] && rm -rf "$f" 2>/dev/null || true
done
return 0
}
# --- SECTION: wait_for ---
wait_for() {
pid="${1:-}"; timeout="${2:-5}"
[ -n "$pid" ] || return 0
i=0
while ps -p "$pid" >/dev/null 2>&1 && [ "$i" -lt "$timeout" ]; do
sleep 1
i=$((i + 1))
done
if ps -p "$pid" >/dev/null 2>&1; then return 1; fi
return 0
}
# --- SECTION: Lock helpers ---
acquire_lockdir() {
lockdir="${1:-}"
maxtries="${2:-${LOCK_RETRIES:-20}}"
sleep_sec="${3:-${LOCK_SLEEP:-1}}"
[ -n "${lockdir:-}" ] || return 1
tries=0
while [ "$tries" -lt "${maxtries}" ]; do
if mkdir "$lockdir" 2>/dev/null; then
printf '%s\n' "$$" > "${lockdir}/pid" 2>/dev/null || true
return 0
fi
tries=$((tries + 1))
sleep "${sleep_sec}" 2>/dev/null || sleep 1
done
return 1
}
release_lockdir() {
lockdir="${1:-}"
[ -n "${lockdir:-}" ] || return 1
rm -f "${lockdir}/pid" 2>/dev/null || true
rmdir "$lockdir" 2>/dev/null || true
return 0
}
acquire_lock() {
nowait=0
timeout_secs=0
while [ "${1:-}" != "" ]; do
case "$1" in
--nowait) nowait=1; shift ;;
--timeout) shift; timeout_secs="${1:-0}"; shift ;;
--) shift; break ;;
-*) shift ;;
*) break ;;
esac
done
lockpath="$1"
[ -n "${lockpath:-}" ] || return 1
[ -n "${_REGISTER_FILE:-}" ] || _TMP_INIT >/dev/null 2>&1 || true
: > "${_REGISTER_FILE}" 2>/dev/null || true
start_ts="$(date +%s 2>/dev/null || printf '%s' 0)"
tries=0
while :; do
if mkdir "${lockpath}.lockdir" 2>/dev/null; then
printf '%s\n' "$$" > "${lockpath}.lockdir/pid" 2>/dev/null || true
printf '%s\n' "rm -rf -- '${lockpath}.lockdir'" >> "${_REGISTER_FILE:-/dev/null}" 2>/dev/null || true
return 0
fi
tries=$((tries + 1))
if [ "${nowait:-0}" -eq 1 ]; then
return 1
fi
if [ "${timeout_secs:-0}" -gt 0 ]; then
now_ts="$(date +%s 2>/dev/null || printf '%s' 0)"
if [ $((now_ts - start_ts)) -ge "${timeout_secs:-0}" ]; then
return 1
fi
fi
if [ "${LOCK_RETRIES:-0}" -gt 0 ] && [ "${tries}" -ge "${LOCK_RETRIES:-0}" ]; then
return 1
fi
sleep "${LOCK_SLEEP:-1}" 2>/dev/null || sleep 1
done
}
release_lock() {
lockpath="${1:-}"
[ -n "${lockpath:-}" ] || return 1
rm -rf "${lockpath}.lockdir" 2>/dev/null || true
return 0
}
# --- SECTION: register_cleanup (whitelisted commands only) ---
register_cleanup() {
cmd="$1"
[ -n "${cmd:-}" ] || return 1
cmd="$(printf '%s' "$cmd" | sed 's/^[[:space:]]*//; s/[[:space:]]*$//')"
case "$cmd" in
rm\ -rf\ *|rm\ -f\ *|rm\ *|rmdir\ *|:|true)
mkdir -p "$(dirname "${_REGISTER_FILE:-${TMPDIR_SAFE:-${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}}}")" 2>/dev/null || true
printf '%s\n' "$cmd" >> "${_REGISTER_FILE:-/dev/null}" 2>/dev/null || true
return 0
;;
[A-Za-z_]*)
mkdir -p "$(dirname "${_REGISTER_FILE:-${TMPDIR_SAFE:-${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}}}")" 2>/dev/null || true
printf '%s\n' "$cmd" >> "${_REGISTER_FILE:-/dev/null}" 2>/dev/null || true
return 0
;;
*)
printf '%s\n' "WARN: register_cleanup rejected unsafe command: $cmd" >&2 || true
return 1
;;
esac
}
# --- SECTION: Local register helper ---
_register_local_tmpfile() {
f="$1"
[ -n "${f:-}" ] || return 0
if [ -n "${_TMP_TRACKER:-}" ]; then
printf '%s\n' "$f" >> "${_TMP_TRACKER}" 2>/dev/null || true
fi
if command -v register_cleanup >/dev/null 2>&1; then
register_cleanup "rm -rf -- '${f}'" 2>/dev/null || true
fi
if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
_prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
fi
return 0
}
# --- SECTION: Backwards-compatible aliases and shims ---
if [ -z "${_REGISTER_TMP_FILE_ALIAS_DONE:-}" ]; then
if command -v _register_local_tmpfile >/dev/null 2>&1; then
_register_tmp_file() { _register_local_tmpfile "$@"; return $?; }
else
_register_tmp_file() {
f="$1"
[ -n "${f:-}" ] || return 0
[ -n "${_TMP_TRACKER:-}" ] && printf '%s\n' "$f" >> "${_TMP_TRACKER}" 2>/dev/null || true
if command -v register_cleanup >/dev/null 2>&1; then
register_cleanup "rm -rf -- '${f}'" 2>/dev/null || true
fi
if [ "${_PRUNE_IN_PROGRESS:-0}" -ne 1 ]; then
_prune_repo_tmpdirs_keep_newest "${CI_TMP_REPODIR:-${CI_BASEDIR%/}/tmp}" "${CI_TMP_MAX_ENTRIES:-10}" >/dev/null 2>&1 || true
fi
return 0
}
fi
_REGISTER_TMP_FILE_ALIAS_DONE=1
fi
if ! command -v _register_tmpfile >/dev/null 2>&1; then
_register_tmpfile() {
if command -v _register_tmp_file >/dev/null 2>&1; then
_register_tmp_file "$@" || return $?
return 0
fi
if command -v _register_local_tmpfile >/dev/null 2>&1; then
_register_local_tmpfile "$@" || return $?
return 0
fi
f="$1"
[ -n "${f:-}" ] || return 0
[ -n "${_TMP_TRACKER:-}" ] && printf '%s\n' "$f" >> "${_TMP_TRACKER}" 2>/dev/null || true
if command -v register_cleanup >/dev/null 2>&1; then
register_cleanup "rm -rf -- '${f}'" 2>/dev/null || true
fi
return 0
}
fi
# --- SECTION: Simple cleanup-on-exit (centralized) ---
_cleanup_on_exit() {
candidates=""
candidates="${candidates} ${_TMPFILES:-}"
candidates="${candidates} ${_TMPFILES_TO_CLEANUP:-}"
candidates="${candidates} ${TMPDIR_RUN:-}"
candidates="${candidates} ${TMPDIR:-}"
candidates="${candidates} ${TMPDIR_WORK:-}"
candidates="${candidates} ${TMPLIST:-}"
candidates="${candidates} ${BADGES_TMP:-}"
candidates="${candidates} ${TMP_LOG:-}"
candidates="${candidates} ${TMP_SECRETS:-}"
candidates="${candidates} ${TMP_FOUND:-}"
candidates="${candidates} ${TMP_FIXED:-}"
candidates="${candidates} ${TMP_WARNED:-}"
candidates="${candidates} ${TMP_BADGES:-}"
candidates="${candidates} ${EXISTING:-}"
candidates="${candidates} ${BADGES_MERGED:-}"
candidates="${candidates} ${TMP_RUN_DIR:-}"
candidates="${candidates} ${TMPDIR_RUN:-}"
candidates="${candidates} ${TMP_TRACKER:-}"
candidates="${candidates} ${TMP_TRACKER_FILE:-}"
candidates="${candidates} ${_TMP_TRACKER:-}"
candidates="${candidates} ${_REGISTER_FILE:-}"
candidates="$(printf '%s' "${candidates}" | awk '{$1=$1;print}')"
if command -v _cleanup_tmpfiles >/dev/null 2>&1; then
oldifs="${IFS}"; IFS=' '
# shellcheck disable=SC2086
set -- ${candidates:-}
IFS="${oldifs}"
if [ "$#" -gt 0 ]; then
_cleanup_tmpfiles "$@" >/dev/null 2>&1 || true
fi
else
oldifs="${IFS}"; IFS=' '
# shellcheck disable=SC2086
set -- ${candidates:-}
IFS="${oldifs}"
for f in "$@"; do
[ -n "${f:-}" ] || continue
rm -rf -- "${f}" >/dev/null 2>&1 || true
done
fi
if [ -n "${_REGISTER_FILE:-}" ] && [ -f "${_REGISTER_FILE}" ]; then
while IFS= read -r cmd || [ -n "${cmd:-}" ]; do
[ -z "${cmd:-}" ] && continue
case "$cmd" in
rm\ -rf\ *|rm\ -f\ *|rm\ *|rmdir\ *|:|true)
sh -c -- "$cmd" 2>/dev/null || true
;;
[A-Za-z_]*)
if command -v "$cmd" >/dev/null 2>&1; then
"$cmd" 2>/dev/null || true
else
printf '%s\n' "WARN: cleanup function not found: $cmd" >&2 || true
fi
;;
*)
printf '%s\n' "WARN: skipped unsafe cleanup entry: $cmd" >&2 || true
;;
esac
done < "${_REGISTER_FILE}" 2>/dev/null || true
rm -f "${_REGISTER_FILE}" 2>/dev/null || true
fi
for tracker in "${TMP_TRACKER:-}" "${TMP_TRACKER_FILE:-}" "${_TMP_TRACKER:-}"; do
[ -n "${tracker:-}" ] || continue
if [ -f "${tracker}" ]; then
while IFS= read -r p || [ -n "${p:-}" ]; do
[ -n "${p:-}" ] && rm -rf -- "${p}" 2>/dev/null || true
done < "${tracker}" 2>/dev/null || true
rm -f -- "${tracker}" 2>/dev/null || true
fi
done
if [ -n "${TMPDIR_SAFE:-}" ]; then
rm -f -- "${TMPDIR_SAFE%/}/ci-verify-perms.last" >/dev/null 2>&1 || true
rm -f -- "${TMPDIR_SAFE%/}/ci-verify-perms.diag."* >/dev/null 2>&1 || true
fi
return 0
}
_libtemp_cleanup_on_exit() { _cleanup_on_exit "$@"; return $?; }
if [ -z "${_LIBTEMP_TRAP_SET:-}" ]; then
trap '_cleanup_on_exit' EXIT INT TERM HUP
_LIBTEMP_TRAP_SET=1
fi
# --- SECTION: Atomic replace helper ---
atomic_replace_file() {
target="$1"
tmp="$2"
[ -n "${target:-}" ] || return 1
[ -f "${tmp:-}" ] || return 1
mkdir -p "$(dirname "$target")" 2>/dev/null || true
if [ "${KEEP_BACKUP:-0}" -ne 0 ] && [ -f "$target" ]; then
backup="${target}.bak-$(date -u +%Y%m%d%H%M%S 2>/dev/null || printf '%s' "$(date +%s)")"
cp -a "$target" "$backup" 2>/dev/null || true
fi
mv "$tmp" "$target" 2>/dev/null || cp -a "$tmp" "$target" 2>/dev/null || true
return 0
}
# --- SECTION: Utilities and aliases ---
ci_truncate_list() {
list="${1:-}"; max="${2:-12}"
[ -n "${list:-}" ] || { printf '%s\n' ''; return 0; }
savedIFS="$IFS"; IFS=' '
out=""
i=0
for p in $list; do
i=$((i + 1))
out="${out}${p} "
[ "$i" -ge "$max" ] && break
done
IFS="$savedIFS"
printf '%s\n' "${out% }"
}
if ! command -v acquirelock >/dev/null 2>&1 && command -v acquire_lockdir >/dev/null 2>&1; then
acquirelock() { acquire_lockdir "$@"; }
fi
if ! command -v acquirelockdir >/dev/null 2>&1 && command -v acquire_lockdir >/dev/null 2>&1; then
acquirelockdir() { acquire_lockdir "$@"; }
fi
if ! command -v releaselock >/dev/null 2>&1 && command -v release_lockdir >/dev/null 2>&1; then
releaselock() { release_lockdir "$@"; }
fi
if ! command -v release_lockdir >/dev/null 2>&1 && command -v releaselock >/dev/null 2>&1; then
release_lockdir() { releaselock "$@"; }
fi
if ! command -v _atomic_write >/dev/null 2>&1; then
if command -v atomic_replace_file >/dev/null 2>&1; then
_atomic_write() { atomic_replace_file "$@"; return $?; }
fi
fi
if ! command -v compute_sha256 >/dev/null 2>&1; then
if command -v _sha256 >/dev/null 2>&1; then
compute_sha256() { _sha256 "$@"; return $?; }
fi
fi
if ! command -v _mktemp_for_find >/dev/null 2>&1 && command -v _mktempdir_safe >/dev/null 2>&1; then
_mktemp_for_find() { _mktempdir_safe "$@"; return $?; }
fi
# --- Export markers and variables ---
export TMPDIR_SAFE _TMP_TRACKER _REGISTER_FILE CI_TMP_HELPERS_AVAILABLE CI_TMP_REPODIR
# End of .ci/lib-temp.sh