Skip to content

Commit 9f269be

Browse files
committed
feat: add debug script to compare actual vs override stats
Add compare_stat_overrides.sh script that displays actual file stats (uid:gid:mode) alongside user.containers.override_stat xattr values. Supports depth limiting (-L) and hidden files (-a) options.
1 parent 4eafc37 commit 9f269be

File tree

1 file changed

+230
-0
lines changed

1 file changed

+230
-0
lines changed
Lines changed: 230 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,230 @@
1+
#!/bin/bash
2+
3+
# Script to compare actual file stats with override stats from xattrs
4+
#
5+
# Usage: ./compare_stat_overrides.sh [-L level] <directory>
6+
#
7+
# Description:
8+
# This script displays the actual file stat (uid:gid:mode) alongside the
9+
# user.containers.override_stat xattr value if present. This allows easy
10+
# comparison between the real file permissions and the virtualized ones.
11+
#
12+
# Options:
13+
# -L level Descend only level directories deep (like tree -L)
14+
# -a Show all files including hidden files
15+
# -h, --help Show help message
16+
#
17+
# Examples:
18+
# ./compare_stat_overrides.sh . # Compare stats in current directory (all depths)
19+
# ./compare_stat_overrides.sh -L 2 /home/user # Compare stats up to 2 levels deep
20+
# ./compare_stat_overrides.sh -L 1 -a ~/ # Compare stats 1 level deep, including hidden files
21+
#
22+
# Notes:
23+
# - Requires 'xattr' command to be available on the system
24+
# - On macOS, xattr is built-in
25+
# - On Linux, you may need to install 'attr' package
26+
27+
# Default values
28+
MAX_DEPTH=""
29+
SHOW_HIDDEN=false
30+
DIR=""
31+
TOTAL_COUNT=0
32+
33+
# Parse command line arguments
34+
while [[ $# -gt 0 ]]; do
35+
case $1 in
36+
-L)
37+
if [[ -z "$2" ]] || ! [[ "$2" =~ ^[0-9]+$ ]]; then
38+
echo "Error: -L requires a numeric level argument"
39+
exit 1
40+
fi
41+
MAX_DEPTH="$2"
42+
shift 2
43+
;;
44+
-a)
45+
SHOW_HIDDEN=true
46+
shift
47+
;;
48+
-h|--help)
49+
echo "compare_stat_overrides.sh - Compare actual file stats with override stats from xattrs"
50+
echo ""
51+
echo "Usage: $0 [-L level] [-a] <directory>"
52+
echo ""
53+
echo "Options:"
54+
echo " -L level Descend only level directories deep"
55+
echo " -a Show all files including hidden files"
56+
echo " -h, --help Show this help message"
57+
echo ""
58+
echo "Examples:"
59+
echo " $0 . # Current directory, all depths"
60+
echo " $0 -L 2 /home/user # Up to 2 levels deep"
61+
echo " $0 -L 1 -a ~/ # 1 level deep, include hidden files"
62+
exit 0
63+
;;
64+
*)
65+
if [[ -z "$DIR" ]]; then
66+
DIR="$1"
67+
else
68+
echo "Error: Too many arguments"
69+
echo "Usage: $0 [-L level] [-a] <directory>"
70+
exit 1
71+
fi
72+
shift
73+
;;
74+
esac
75+
done
76+
77+
# Check if directory argument is provided
78+
if [[ -z "$DIR" ]]; then
79+
echo "Error: No directory specified"
80+
echo "Usage: $0 [-L level] [-a] <directory>"
81+
echo "Try '$0 --help' for more information."
82+
exit 1
83+
fi
84+
85+
# Check if directory exists
86+
if [ ! -d "$DIR" ]; then
87+
echo "Error: Directory '$DIR' does not exist"
88+
exit 1
89+
fi
90+
91+
# Function to print tree-like indentation
92+
print_indent() {
93+
local level=$1
94+
local is_last=$2
95+
local prefix=""
96+
97+
for ((i=0; i<level; i++)); do
98+
if [ $i -eq $((level-1)) ]; then
99+
if [ "$is_last" = "true" ]; then
100+
prefix="${prefix}└── "
101+
else
102+
prefix="${prefix}├── "
103+
fi
104+
else
105+
prefix="${prefix}"
106+
fi
107+
done
108+
109+
echo -n "$prefix"
110+
}
111+
112+
# Function to display stat comparison for a file with tree formatting
113+
show_stat_comparison() {
114+
local file="$1"
115+
local level="$2"
116+
local is_last="$3"
117+
local basename=$(basename "$file")
118+
119+
# Skip hidden files if -a flag not set
120+
if [ "$SHOW_HIDDEN" = false ] && [[ "$basename" == .* ]] && [ "$level" -gt 0 ]; then
121+
return
122+
fi
123+
124+
# Get actual file stat in uid:gid:mode format
125+
local actual_stat=$(stat -c "%u:%g:%04a" "$file" 2>/dev/null)
126+
if [ -d "$file" ] && [ -n "$actual_stat" ]; then
127+
# Prepend file type bits to mode for directories
128+
local mode=$(stat -c "%04a" "$file")
129+
local uid=$(stat -c "%u" "$file")
130+
local gid=$(stat -c "%g" "$file")
131+
actual_stat="${uid}:${gid}:04${mode}"
132+
fi
133+
134+
# Get override stat from xattr
135+
local override_stat=$(xattr -p user.containers.override_stat "$file" 2>/dev/null | tr -d '\n')
136+
137+
# Only show files that have either stat info or override stat
138+
if [ -z "$actual_stat" ] && [ -z "$override_stat" ]; then
139+
return
140+
fi
141+
142+
# Print the file/directory name with tree formatting
143+
if [ $level -gt 0 ]; then
144+
print_indent $level "$is_last"
145+
fi
146+
147+
# Format the output with actual vs override stat
148+
local name_part=""
149+
if [ -d "$file" ]; then
150+
name_part="\033[1;34m${basename}/\033[0m"
151+
else
152+
name_part="$basename"
153+
fi
154+
155+
# Build stat comparison string
156+
local stat_part=""
157+
if [ -n "$actual_stat" ] && [ -n "$override_stat" ]; then
158+
if [ "$actual_stat" = "$override_stat" ]; then
159+
stat_part=" [${actual_stat}]"
160+
else
161+
stat_part=" [\033[33m${actual_stat}\033[0m → \033[32m${override_stat}\033[0m]"
162+
fi
163+
((TOTAL_COUNT++))
164+
elif [ -n "$actual_stat" ]; then
165+
stat_part=" [${actual_stat}]"
166+
elif [ -n "$override_stat" ]; then
167+
stat_part=" [→ \033[32m${override_stat}\033[0m]"
168+
((TOTAL_COUNT++))
169+
fi
170+
171+
echo -e "${name_part}${stat_part}"
172+
}
173+
174+
# Function to recursively process directory
175+
process_directory() {
176+
local dir="$1"
177+
local current_level="$2"
178+
179+
# Check if we've reached max depth
180+
if [ -n "$MAX_DEPTH" ] && [ "$current_level" -ge "$MAX_DEPTH" ]; then
181+
return
182+
fi
183+
184+
# Get list of items in directory
185+
local items=()
186+
if [ "$SHOW_HIDDEN" = true ]; then
187+
while IFS= read -r -d '' item; do
188+
items+=("$item")
189+
done < <(find "$dir" -maxdepth 1 -mindepth 1 -print0 | sort -z)
190+
else
191+
while IFS= read -r -d '' item; do
192+
items+=("$item")
193+
done < <(find "$dir" -maxdepth 1 -mindepth 1 ! -name '.*' -print0 | sort -z)
194+
fi
195+
196+
local total=${#items[@]}
197+
local count=0
198+
199+
for item in "${items[@]}"; do
200+
((count++))
201+
local is_last="false"
202+
if [ $count -eq $total ]; then
203+
is_last="true"
204+
fi
205+
206+
show_stat_comparison "$item" "$current_level" "$is_last"
207+
208+
# Recurse into directories
209+
if [ -d "$item" ]; then
210+
process_directory "$item" $((current_level + 1))
211+
fi
212+
done
213+
}
214+
215+
# Main execution
216+
echo -e "\033[1mComparing file stats with override stats in: $DIR\033[0m"
217+
if [ -n "$MAX_DEPTH" ]; then
218+
echo "Max depth: $MAX_DEPTH"
219+
fi
220+
echo "----------------------------------------"
221+
222+
# Show root directory
223+
show_stat_comparison "$DIR" 0 "false"
224+
225+
# Process subdirectories
226+
process_directory "$DIR" 1
227+
228+
# Summary
229+
echo "----------------------------------------"
230+
echo "Total files with stat overrides: $TOTAL_COUNT"

0 commit comments

Comments
 (0)