1
0
mirror of https://github.com/tw93/Mole.git synced 2026-03-23 21:35:06 +00:00

fix: fall back to safe temp directories

This commit is contained in:
Tw93
2026-03-23 17:10:15 +08:00
parent e5bb083dc0
commit f2e9ff05fe
6 changed files with 171 additions and 8 deletions

View File

@@ -221,7 +221,8 @@ is_bundle_orphaned() {
if [[ -n "$bundle_id" ]] && [[ "$bundle_id" =~ ^[a-zA-Z0-9._-]+$ ]] && [[ ${#bundle_id} -ge 5 ]]; then
# Initialize cache file if needed
if [[ -z "$ORPHAN_MDFIND_CACHE_FILE" ]]; then
ORPHAN_MDFIND_CACHE_FILE=$(mktemp "${TMPDIR:-/tmp}/mole_mdfind_cache.XXXXXX")
ensure_mole_temp_root
ORPHAN_MDFIND_CACHE_FILE=$(mktemp "$MOLE_RESOLVED_TMPDIR/mole_mdfind_cache.XXXXXX")
register_temp_file "$ORPHAN_MDFIND_CACHE_FILE"
fi
@@ -277,7 +278,8 @@ is_claude_vm_bundle_orphaned() {
fi
if [[ -z "$ORPHAN_MDFIND_CACHE_FILE" ]]; then
ORPHAN_MDFIND_CACHE_FILE=$(mktemp "${TMPDIR:-/tmp}/mole_mdfind_cache.XXXXXX")
ensure_mole_temp_root
ORPHAN_MDFIND_CACHE_FILE=$(mktemp "$MOLE_RESOLVED_TMPDIR/mole_mdfind_cache.XXXXXX")
register_temp_file "$ORPHAN_MDFIND_CACHE_FILE"
fi
@@ -449,7 +451,8 @@ clean_orphaned_system_services() {
if [[ -n "$bundle_id" ]] && [[ "$bundle_id" =~ ^[a-zA-Z0-9._-]+$ ]] && [[ ${#bundle_id} -ge 5 ]]; then
if [[ -z "$mdfind_cache_file" ]]; then
mdfind_cache_file=$(mktemp "${TMPDIR:-/tmp}/mole_mdfind_cache.XXXXXX")
ensure_mole_temp_root
mdfind_cache_file=$(mktemp "$MOLE_RESOLVED_TMPDIR/mole_mdfind_cache.XXXXXX")
register_temp_file "$mdfind_cache_file"
fi

View File

@@ -556,10 +556,93 @@ bytes_to_human_kb() {
declare -a MOLE_TEMP_FILES=()
declare -a MOLE_TEMP_DIRS=()
normalize_temp_root() {
local path="${1:-}"
[[ -z "$path" ]] && return 1
if [[ "$path" == "~"* ]]; then
path="${path/#\~/$HOME}"
fi
while [[ "$path" != "/" && "$path" == */ ]]; do
path="${path%/}"
done
[[ -n "$path" ]] || return 1
printf '%s\n' "$path"
}
probe_temp_root() {
local raw_path="$1"
local allow_create="${2:-false}"
local path
local probe=""
path=$(normalize_temp_root "$raw_path") || return 1
if [[ "$allow_create" == "true" ]]; then
ensure_user_dir "$path"
fi
[[ -d "$path" ]] || return 1
probe=$(mktemp "$path/mole.probe.XXXXXX" 2> /dev/null) || return 1
rm -f "$probe" 2> /dev/null || true
printf '%s\n' "$path"
}
ensure_mole_temp_root() {
if [[ -n "${MOLE_RESOLVED_TMPDIR:-}" ]]; then
return 0
fi
local resolved=""
local candidate="${TMPDIR:-}"
local invoking_home=""
if [[ -n "$candidate" ]]; then
resolved=$(probe_temp_root "$candidate" false || true)
fi
if [[ -z "$resolved" ]]; then
invoking_home=$(get_invoking_home)
if [[ -n "$invoking_home" ]]; then
resolved=$(probe_temp_root "$invoking_home/.cache/mole/tmp" true || true)
fi
fi
if [[ -z "$resolved" ]]; then
resolved=$(probe_temp_root "/tmp" false || true)
fi
[[ -n "$resolved" ]] || resolved="/tmp"
MOLE_RESOLVED_TMPDIR="$resolved"
export MOLE_RESOLVED_TMPDIR
}
get_mole_temp_root() {
ensure_mole_temp_root
printf '%s\n' "$MOLE_RESOLVED_TMPDIR"
}
prepare_mole_tmpdir() {
ensure_mole_temp_root
export TMPDIR="$MOLE_RESOLVED_TMPDIR"
printf '%s\n' "$MOLE_RESOLVED_TMPDIR"
}
mole_temp_path_template() {
local prefix="${1:-mole}"
ensure_mole_temp_root
printf '%s/%s.XXXXXX\n' "$MOLE_RESOLVED_TMPDIR" "$prefix"
}
# Create tracked temporary file
create_temp_file() {
local temp
temp=$(mktemp "${TMPDIR:-/tmp}/mole.XXXXXX") || return 1
ensure_mole_temp_root
temp=$(mktemp "$MOLE_RESOLVED_TMPDIR/mole.XXXXXX") || return 1
register_temp_file "$temp"
echo "$temp"
}
@@ -567,7 +650,8 @@ create_temp_file() {
# Create tracked temporary directory
create_temp_dir() {
local temp
temp=$(mktemp -d "${TMPDIR:-/tmp}/mole.XXXXXX") || return 1
ensure_mole_temp_root
temp=$(mktemp -d "$MOLE_RESOLVED_TMPDIR/mole.XXXXXX") || return 1
register_temp_dir "$temp"
echo "$temp"
}
@@ -588,9 +672,8 @@ mktemp_file() {
local prefix="${1:-mole}"
local temp
local error_msg
# Use TMPDIR if set, otherwise /tmp
# Add .XXXXXX suffix to work with both BSD and GNU mktemp
if ! error_msg=$(mktemp "${TMPDIR:-/tmp}/${prefix}.XXXXXX" 2>&1); then
if ! error_msg=$(mktemp "$(mole_temp_path_template "$prefix")" 2>&1); then
echo "Error: Failed to create temporary file: $error_msg" >&2
return 1
fi

View File

@@ -14,6 +14,7 @@ _MOLE_CORE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# Load core modules
source "$_MOLE_CORE_DIR/base.sh"
prepare_mole_tmpdir > /dev/null
source "$_MOLE_CORE_DIR/log.sh"
source "$_MOLE_CORE_DIR/timeout.sh"

View File

@@ -324,7 +324,8 @@ start_inline_spinner() {
if [[ -t 1 ]]; then
# Create unique stop flag file for this spinner instance
INLINE_SPINNER_STOP_FILE="${TMPDIR:-/tmp}/mole_spinner_$$_$RANDOM.stop"
ensure_mole_temp_root
INLINE_SPINNER_STOP_FILE="$MOLE_RESOLVED_TMPDIR/mole_spinner_$$_$RANDOM.stop"
(
local stop_file="$INLINE_SPINNER_STOP_FILE"

View File

@@ -91,6 +91,26 @@ run_clean_dry_run() {
[[ "$output" == *"full preview"* ]]
}
@test "mo clean --dry-run survives an unwritable TMPDIR" {
local blocked_tmp="$HOME/blocked-tmp"
mkdir -p "$blocked_tmp"
chmod 500 "$blocked_tmp"
set_mock_sudo_uncached
local test_path="$PATH"
if [[ -n "${TEST_MOCK_BIN:-}" ]]; then
test_path="$TEST_MOCK_BIN:$PATH"
fi
run env HOME="$HOME" TMPDIR="$blocked_tmp" MOLE_TEST_MODE=1 PATH="$test_path" \
"$PROJECT_ROOT/mole" clean --dry-run
[ "$status" -eq 0 ]
[[ "$output" != *"mktemp:"* ]]
[[ "$output" != *"Failed to create temporary file"* ]]
[ -d "$HOME/.cache/mole/tmp" ]
}
@test "mo clean --dry-run reports user cache without deleting it" {
mkdir -p "$HOME/Library/Caches/TestApp"
echo "cache data" > "$HOME/Library/Caches/TestApp/cache.tmp"

View File

@@ -76,6 +76,61 @@ setup() {
[ -d "$result" ]
}
@test "get_mole_temp_root uses writable TMPDIR when available" {
local writable_tmp="$HOME/custom-tmp"
mkdir -p "$writable_tmp"
result=$(env HOME="$HOME" TMPDIR="$writable_tmp" bash -c "source '$PROJECT_ROOT/lib/core/base.sh'; get_mole_temp_root")
[ "$result" = "$writable_tmp" ]
}
@test "get_mole_temp_root falls back to user cache when TMPDIR is not writable" {
local blocked_tmp="$HOME/blocked-tmp"
mkdir -p "$blocked_tmp"
chmod 500 "$blocked_tmp"
result=$(env HOME="$HOME" TMPDIR="$blocked_tmp" bash -c "source '$PROJECT_ROOT/lib/core/base.sh'; get_mole_temp_root")
[ "$result" = "$HOME/.cache/mole/tmp" ]
[ -d "$HOME/.cache/mole/tmp" ]
}
@test "get_mole_temp_root caches the first resolved directory" {
local first_tmp="$HOME/first-tmp"
local second_tmp="$HOME/second-tmp"
mkdir -p "$first_tmp" "$second_tmp"
result=$(env HOME="$HOME" TMPDIR="$first_tmp" bash -c "
source '$PROJECT_ROOT/lib/core/base.sh'
ensure_mole_temp_root
first=\$MOLE_RESOLVED_TMPDIR
export TMPDIR='$second_tmp'
ensure_mole_temp_root
second=\$MOLE_RESOLVED_TMPDIR
printf '%s|%s\n' \"\$first\" \"\$second\"
")
[ "$result" = "$first_tmp|$first_tmp" ]
}
@test "get_mole_temp_root falls back to /tmp when TMPDIR and invoking home are unavailable" {
result=$(env HOME="$HOME" TMPDIR="/var/empty" bash -c "
source '$PROJECT_ROOT/lib/core/base.sh'
get_invoking_home() { echo '/var/empty'; }
get_mole_temp_root
")
[ "$result" = "/tmp" ]
}
@test "common.sh exports resolved TMPDIR for runtime callers" {
local blocked_tmp="$HOME/common-blocked-tmp"
mkdir -p "$blocked_tmp"
chmod 500 "$blocked_tmp"
result=$(env HOME="$HOME" TMPDIR="$blocked_tmp" bash -c "source '$PROJECT_ROOT/lib/core/common.sh'; printf '%s\n' \"\$TMPDIR\"")
[ "$result" = "$HOME/.cache/mole/tmp" ]
}
@test "get_user_home returns home for valid user" {
current_user="${USER:-$(whoami)}"
result=$(bash -c "source '$PROJECT_ROOT/lib/core/base.sh'; get_user_home '$current_user'")