From 744ecec4ba82eb6a8e5b3b3add9e7a250e7ec968 Mon Sep 17 00:00:00 2001 From: tw93 Date: Sun, 15 Feb 2026 07:52:43 +0800 Subject: [PATCH] fix(clean): use sudo-safe cleanup for Xcode documentation cache --- lib/clean/dev.sh | 43 ++++++++++++++++++++- lib/core/app_protection.sh | 11 +++++- tests/dev_extended.bats | 12 +++--- tests/test_diagnostic_reports_standalone.sh | 9 +++++ tests/uninstall.bats | 35 +++++++++++++++++ 5 files changed, 102 insertions(+), 8 deletions(-) diff --git a/lib/clean/dev.sh b/lib/clean/dev.sh index 8cf1b39..6506ed1 100644 --- a/lib/clean/dev.sh +++ b/lib/clean/dev.sh @@ -216,9 +216,50 @@ clean_xcode_documentation_cache() { ((idx++)) done - if [[ ${#stale_entries[@]} -gt 0 ]]; then + if [[ ${#stale_entries[@]} -eq 0 ]]; then + return 0 + fi + + if [[ "${DRY_RUN:-false}" == "true" ]]; then safe_clean "${stale_entries[@]}" "Xcode documentation cache (old indexes)" note_activity + return 0 + fi + + if ! has_sudo_session; then + if ! ensure_sudo_session "Cleaning Xcode documentation cache requires admin access"; then + echo -e " ${GRAY}${ICON_WARNING}${NC} Xcode documentation cache cleanup skipped (sudo denied)" + note_activity + return 0 + fi + fi + + local removed_count=0 + local skipped_count=0 + local stale_entry + for stale_entry in "${stale_entries[@]}"; do + if should_protect_path "$stale_entry" || is_path_whitelisted "$stale_entry"; then + skipped_count=$((skipped_count + 1)) + continue + fi + if safe_sudo_remove "$stale_entry"; then + removed_count=$((removed_count + 1)) + fi + done + + if [[ $removed_count -gt 0 ]]; then + echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode documentation cache · removed ${removed_count} old indexes" + if [[ $skipped_count -gt 0 ]]; then + echo -e " ${GRAY}${ICON_WARNING}${NC} Xcode documentation cache · skipped ${skipped_count} protected items" + fi + note_activity + elif [[ $skipped_count -gt 0 ]]; then + echo -e " ${GREEN}${ICON_SUCCESS}${NC} Xcode documentation cache · nothing to clean" + echo -e " ${GRAY}${ICON_WARNING}${NC} Xcode documentation cache · skipped ${skipped_count} protected items" + note_activity + else + echo -e " ${GRAY}${ICON_WARNING}${NC} Xcode documentation cache · no items removed" + note_activity fi } diff --git a/lib/core/app_protection.sh b/lib/core/app_protection.sh index 9577539..1180152 100755 --- a/lib/core/app_protection.sh +++ b/lib/core/app_protection.sh @@ -1182,13 +1182,20 @@ get_diagnostic_report_paths_for_app() { [[ -z "$f" ]] && continue local base base=$(basename "$f" 2> /dev/null) - [[ "$base" != "$prefix"* ]] && continue + case "$base" in + "$prefix".* | "$prefix"_* | "$prefix"-*) ;; + *) continue ;; + esac case "$base" in *.ips | *.crash | *.spin) ;; *) continue ;; esac printf '%s\n' "$f" - done < <(find "$dir_abs" -maxdepth 1 -type f -name "${prefix}*" -print0 2> /dev/null || true) + done < <( + find "$dir_abs" -maxdepth 1 -type f \ + \( -name "${prefix}.*" -o -name "${prefix}_*" -o -name "${prefix}-*" \) \ + -print0 2> /dev/null || true + ) return 0 } diff --git a/tests/dev_extended.bats b/tests/dev_extended.bats index 0ecee83..cbc4ee5 100644 --- a/tests/dev_extended.bats +++ b/tests/dev_extended.bats @@ -149,10 +149,12 @@ set -euo pipefail source "$PROJECT_ROOT/lib/core/common.sh" source "$PROJECT_ROOT/lib/clean/dev.sh" note_activity() { :; } -safe_clean() { - local description="${*: -1}" +has_sudo_session() { return 0; } +is_path_whitelisted() { return 1; } +should_protect_path() { return 1; } +safe_sudo_remove() { local target="$1" - echo "CLEAN:$target:$description" + echo "CLEAN:$target:Xcode documentation cache (old indexes)" } clean_xcode_documentation_cache EOF @@ -174,13 +176,13 @@ source "$PROJECT_ROOT/lib/core/common.sh" source "$PROJECT_ROOT/lib/clean/dev.sh" note_activity() { :; } pgrep() { return 0; } -safe_clean() { echo "UNEXPECTED_SAFE_CLEAN"; } +safe_sudo_remove() { echo "UNEXPECTED_SAFE_SUDO_REMOVE"; } clean_xcode_documentation_cache EOF [ "$status" -eq 0 ] [[ "$output" == *"skipping documentation cache cleanup"* ]] - [[ "$output" != *"UNEXPECTED_SAFE_CLEAN"* ]] + [[ "$output" != *"UNEXPECTED_SAFE_SUDO_REMOVE"* ]] } @test "check_rust_toolchains reports multiple toolchains" { diff --git a/tests/test_diagnostic_reports_standalone.sh b/tests/test_diagnostic_reports_standalone.sh index 4aad835..fc23935 100644 --- a/tests/test_diagnostic_reports_standalone.sh +++ b/tests/test_diagnostic_reports_standalone.sh @@ -77,6 +77,8 @@ touch "$TMP_DIAG/MyApp_2025-02-10-120000_host.ips" touch "$TMP_DIAG/MyApp.crash" touch "$TMP_DIAG/MyApp_2025-02-10-120001_host.spin" touch "$TMP_DIAG/OtherApp_2025-02-10.ips" +touch "$TMP_DIAG/MyAppPro_2025-02-10-120002_host.ips" +touch "$TMP_DIAG/MyAppPro.crash" touch "$TMP_DIAG/MyApp_log.txt" out=$(get_diagnostic_report_paths_for_app "$TMP_APP" "My App" "$TMP_DIAG" 2> /dev/null || true) @@ -92,6 +94,13 @@ else echo " OK does not return OtherApp" ((PASSED++)) fi +if [[ "$out" == *"MyAppPro"* ]]; then + echo " FAIL should not return MyAppPro (prefix collision)" + ((FAILED++)) +else + echo " OK does not return MyAppPro" + ((PASSED++)) +fi if [[ "$out" == *"MyApp_log.txt"* ]]; then echo " FAIL should not return non-diagnostic extension" ((FAILED++)) diff --git a/tests/uninstall.bats b/tests/uninstall.bats index f1edade..3c98927 100644 --- a/tests/uninstall.bats +++ b/tests/uninstall.bats @@ -60,6 +60,41 @@ EOF [[ "$result" == *"LaunchAgents/com.example.TestApp.plist"* ]] } +@test "get_diagnostic_report_paths_for_app avoids executable prefix collisions" { + run env HOME="$HOME" PROJECT_ROOT="$PROJECT_ROOT" bash --noprofile --norc << 'EOF' +set -euo pipefail +source "$PROJECT_ROOT/lib/core/common.sh" + +diag_dir="$HOME/Library/Logs/DiagnosticReports" +app_dir="$HOME/Applications/Foo.app" +mkdir -p "$diag_dir" "$app_dir/Contents" + +cat > "$app_dir/Contents/Info.plist" << 'PLIST' + + + + + CFBundleExecutable + Foo + + +PLIST + +touch "$diag_dir/Foo.crash" +touch "$diag_dir/Foo_2026-01-01-120000_host.ips" +touch "$diag_dir/Foobar.crash" +touch "$diag_dir/Foobar_2026-01-01-120001_host.ips" + +result=$(get_diagnostic_report_paths_for_app "$app_dir" "Foo" "$diag_dir") +[[ "$result" == *"Foo.crash"* ]] || exit 1 +[[ "$result" == *"Foo_2026-01-01-120000_host.ips"* ]] || exit 1 +[[ "$result" != *"Foobar.crash"* ]] || exit 1 +[[ "$result" != *"Foobar_2026-01-01-120001_host.ips"* ]] || exit 1 +EOF + + [ "$status" -eq 0 ] +} + @test "calculate_total_size returns aggregate kilobytes" { mkdir -p "$HOME/sized" dd if=/dev/zero of="$HOME/sized/file1" bs=1024 count=1 > /dev/null 2>&1