mirror of
https://github.com/tw93/Mole.git
synced 2026-02-04 12:41:46 +00:00
core: improve file removal diagnostics
This commit is contained in:
@@ -10,6 +10,11 @@ if [[ -n "${MOLE_FILE_OPS_LOADED:-}" ]]; then
|
|||||||
fi
|
fi
|
||||||
readonly MOLE_FILE_OPS_LOADED=1
|
readonly MOLE_FILE_OPS_LOADED=1
|
||||||
|
|
||||||
|
# Error codes for removal operations
|
||||||
|
readonly MOLE_ERR_SIP_PROTECTED=10
|
||||||
|
readonly MOLE_ERR_AUTH_FAILED=11
|
||||||
|
readonly MOLE_ERR_READONLY_FS=12
|
||||||
|
|
||||||
# Ensure dependencies are loaded
|
# Ensure dependencies are loaded
|
||||||
_MOLE_CORE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
_MOLE_CORE_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
if [[ -z "${MOLE_BASE_LOADED:-}" ]]; then
|
if [[ -z "${MOLE_BASE_LOADED:-}" ]]; then
|
||||||
@@ -25,6 +30,35 @@ if [[ -z "${MOLE_TIMEOUT_LOADED:-}" ]]; then
|
|||||||
source "$_MOLE_CORE_DIR/timeout.sh"
|
source "$_MOLE_CORE_DIR/timeout.sh"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Utility Functions
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
# Format duration in seconds to human readable string (e.g., "5 days", "2 months")
|
||||||
|
format_duration_human() {
|
||||||
|
local seconds="${1:-0}"
|
||||||
|
[[ ! "$seconds" =~ ^[0-9]+$ ]] && seconds=0
|
||||||
|
|
||||||
|
local days=$((seconds / 86400))
|
||||||
|
|
||||||
|
if [[ $days -eq 0 ]]; then
|
||||||
|
echo "today"
|
||||||
|
elif [[ $days -eq 1 ]]; then
|
||||||
|
echo "1 day"
|
||||||
|
elif [[ $days -lt 7 ]]; then
|
||||||
|
echo "${days} days"
|
||||||
|
elif [[ $days -lt 30 ]]; then
|
||||||
|
local weeks=$((days / 7))
|
||||||
|
[[ $weeks -eq 1 ]] && echo "1 week" || echo "${weeks} weeks"
|
||||||
|
elif [[ $days -lt 365 ]]; then
|
||||||
|
local months=$((days / 30))
|
||||||
|
[[ $months -eq 1 ]] && echo "1 month" || echo "${months} months"
|
||||||
|
else
|
||||||
|
local years=$((days / 365))
|
||||||
|
[[ $years -eq 1 ]] && echo "1 year" || echo "${years} years"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Path Validation
|
# Path Validation
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -235,28 +269,54 @@ safe_remove() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Safe symlink removal (for pre-validated symlinks only)
|
||||||
|
safe_remove_symlink() {
|
||||||
|
local path="$1"
|
||||||
|
local use_sudo="${2:-false}"
|
||||||
|
|
||||||
|
if [[ ! -L "$path" ]]; then
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then
|
||||||
|
debug_log "[DRY RUN] Would remove symlink: $path"
|
||||||
|
return 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
local rm_exit=0
|
||||||
|
if [[ "$use_sudo" == "true" ]]; then
|
||||||
|
sudo rm "$path" 2> /dev/null || rm_exit=$?
|
||||||
|
else
|
||||||
|
rm "$path" 2> /dev/null || rm_exit=$?
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [[ $rm_exit -eq 0 ]]; then
|
||||||
|
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "REMOVED" "$path" "symlink"
|
||||||
|
return 0
|
||||||
|
else
|
||||||
|
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "FAILED" "$path" "symlink removal failed"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
# Safe sudo removal with symlink protection
|
# Safe sudo removal with symlink protection
|
||||||
safe_sudo_remove() {
|
safe_sudo_remove() {
|
||||||
local path="$1"
|
local path="$1"
|
||||||
|
|
||||||
# Validate path
|
|
||||||
if ! validate_path_for_deletion "$path"; then
|
if ! validate_path_for_deletion "$path"; then
|
||||||
log_error "Path validation failed for sudo remove: $path"
|
log_error "Path validation failed for sudo remove: $path"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Check if path exists
|
|
||||||
if [[ ! -e "$path" ]]; then
|
if [[ ! -e "$path" ]]; then
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Additional check: reject symlinks for sudo operations
|
|
||||||
if [[ -L "$path" ]]; then
|
if [[ -L "$path" ]]; then
|
||||||
log_error "Refusing to sudo remove symlink: $path"
|
log_error "Refusing to sudo remove symlink: $path"
|
||||||
return 1
|
return 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Dry-run mode: log but don't delete
|
|
||||||
if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then
|
if [[ "${MOLE_DRY_RUN:-0}" == "1" ]]; then
|
||||||
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
if [[ "${MO_DEBUG:-}" == "1" ]]; then
|
||||||
local file_type="file"
|
local file_type="file"
|
||||||
@@ -278,21 +338,21 @@ safe_sudo_remove() {
|
|||||||
local now
|
local now
|
||||||
now=$(date +%s 2> /dev/null || echo "0")
|
now=$(date +%s 2> /dev/null || echo "0")
|
||||||
if [[ "$mod_time" -gt 0 && "$now" -gt 0 ]]; then
|
if [[ "$mod_time" -gt 0 && "$now" -gt 0 ]]; then
|
||||||
file_age=$(((now - mod_time) / 86400))
|
local age_seconds=$((now - mod_time))
|
||||||
|
file_age=$(format_duration_human "$age_seconds")
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
debug_file_action "[DRY RUN] Would remove, sudo" "$path" "$file_size" "$file_age"
|
log_info "[DRY-RUN] Would sudo remove: $file_type $path"
|
||||||
|
[[ -n "$file_size" ]] && log_info " Size: $file_size"
|
||||||
|
[[ -n "$file_age" ]] && log_info " Age: $file_age"
|
||||||
else
|
else
|
||||||
debug_log "[DRY RUN] Would remove, sudo: $path"
|
log_info "[DRY-RUN] Would sudo remove: $path"
|
||||||
fi
|
fi
|
||||||
return 0
|
return 0
|
||||||
fi
|
fi
|
||||||
|
|
||||||
debug_log "Removing, sudo: $path"
|
|
||||||
|
|
||||||
# Calculate size before deletion for logging
|
|
||||||
local size_kb=0
|
local size_kb=0
|
||||||
local size_human=""
|
local size_human=""
|
||||||
if oplog_enabled; then
|
if oplog_enabled; then
|
||||||
@@ -304,15 +364,35 @@ safe_sudo_remove() {
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Perform the deletion
|
local output
|
||||||
if sudo rm -rf "$path" 2> /dev/null; then # SAFE: safe_sudo_remove implementation
|
local ret
|
||||||
|
output=$(sudo rm -rf "$path" 2>&1)
|
||||||
|
ret=$?
|
||||||
|
|
||||||
|
if [[ $ret -eq 0 ]]; then
|
||||||
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "REMOVED" "$path" "$size_human"
|
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "REMOVED" "$path" "$size_human"
|
||||||
return 0
|
return 0
|
||||||
else
|
fi
|
||||||
|
|
||||||
|
case "$output" in
|
||||||
|
*"Operation not permitted"*)
|
||||||
|
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "FAILED" "$path" "sip/mdm protected"
|
||||||
|
return "$MOLE_ERR_SIP_PROTECTED"
|
||||||
|
;;
|
||||||
|
*"Read-only file system"*)
|
||||||
|
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "FAILED" "$path" "readonly filesystem"
|
||||||
|
return "$MOLE_ERR_READONLY_FS"
|
||||||
|
;;
|
||||||
|
*"Sorry, try again"* | *"incorrect password"*)
|
||||||
|
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "FAILED" "$path" "auth failed"
|
||||||
|
return "$MOLE_ERR_AUTH_FAILED"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
log_error "Failed to remove, sudo: $path"
|
log_error "Failed to remove, sudo: $path"
|
||||||
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "FAILED" "$path" "sudo error"
|
log_operation "${MOLE_CURRENT_COMMAND:-clean}" "FAILED" "$path" "sudo error"
|
||||||
return 1
|
return 1
|
||||||
fi
|
;;
|
||||||
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -414,16 +494,13 @@ get_path_size_kb() {
|
|||||||
echo "0"
|
echo "0"
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
# Direct execution without timeout overhead - critical for performance in loops
|
|
||||||
# Use || echo 0 to ensure failure in du (e.g. permission error) doesn't exit script under set -e
|
|
||||||
# Pipefail would normally cause the pipeline to fail if du fails, but || handle catches it.
|
|
||||||
local size
|
local size
|
||||||
size=$(command du -skP "$path" 2> /dev/null | awk 'NR==1 {print $1; exit}' || true)
|
size=$(command du -skP "$path" 2> /dev/null | awk 'NR==1 {print $1; exit}' || true)
|
||||||
|
|
||||||
# Ensure size is a valid number (fix for non-numeric du output)
|
|
||||||
if [[ "$size" =~ ^[0-9]+$ ]]; then
|
if [[ "$size" =~ ^[0-9]+$ ]]; then
|
||||||
echo "$size"
|
echo "$size"
|
||||||
else
|
else
|
||||||
|
[[ "${MO_DEBUG:-}" == "1" ]] && debug_log "get_path_size_kb: Failed to get size for $path (returned: $size)"
|
||||||
echo "0"
|
echo "0"
|
||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
@@ -443,3 +520,40 @@ calculate_total_size() {
|
|||||||
|
|
||||||
echo "$total_kb"
|
echo "$total_kb"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
diagnose_removal_failure() {
|
||||||
|
local exit_code="$1"
|
||||||
|
local app_name="${2:-application}"
|
||||||
|
|
||||||
|
local reason=""
|
||||||
|
local suggestion=""
|
||||||
|
local touchid_file="/etc/pam.d/sudo"
|
||||||
|
|
||||||
|
case "$exit_code" in
|
||||||
|
"$MOLE_ERR_SIP_PROTECTED")
|
||||||
|
reason="protected by macOS (SIP/MDM)"
|
||||||
|
;;
|
||||||
|
"$MOLE_ERR_AUTH_FAILED")
|
||||||
|
reason="authentication failed"
|
||||||
|
if [[ -f "$touchid_file" ]] && grep -q "pam_tid.so" "$touchid_file" 2> /dev/null; then
|
||||||
|
suggestion="Check your password or restart Terminal"
|
||||||
|
else
|
||||||
|
suggestion="Try 'mole touchid' to enable fingerprint auth"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
"$MOLE_ERR_READONLY_FS")
|
||||||
|
reason="filesystem is read-only"
|
||||||
|
suggestion="Check if disk needs repair"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
reason="permission denied"
|
||||||
|
if [[ -f "$touchid_file" ]] && grep -q "pam_tid.so" "$touchid_file" 2> /dev/null; then
|
||||||
|
suggestion="Try running again or check file ownership"
|
||||||
|
else
|
||||||
|
suggestion="Try 'mole touchid' or check with 'ls -l'"
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
echo "$reason|$suggestion"
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user