Skip to content

Instantly share code, notes, and snippets.

@JakeChampion
Created September 19, 2025 13:16
Show Gist options
  • Select an option

  • Save JakeChampion/ee59f85b349b9b996000582eb01ccea4 to your computer and use it in GitHub Desktop.

Select an option

Save JakeChampion/ee59f85b349b9b996000582eb01ccea4 to your computer and use it in GitHub Desktop.
extract erofs file to a folder
#!/usr/bin/env bash
set -euo pipefail
if [[ $# -lt 2 || $# -gt 3 ]]; then
echo "Usage: $0 <image.erofs> <output_dir> [offset_bytes]" >&2
exit 1
fi
IMAGE="$1"
OUTDIR="$2"
OFFSET="${3:-}"
command -v dump.erofs >/dev/null 2>&1 || { echo "dump.erofs not found in PATH" >&2; exit 1; }
mkdir -p "$OUTDIR"
DUMP=(dump.erofs)
[[ -n "$OFFSET" ]] && DUMP+=(--offset="$OFFSET")
abs_child() {
local dir="$1" name="$2"
if [[ "$dir" == "/" ]]; then printf "/%s" "$name"; else printf "%s/%s" "$dir" "$name"; fi
}
parse_ls_table() {
local dir="$1"
"${DUMP[@]}" --path "$dir" --ls "$IMAGE" | awk '
BEGIN { intable=0 }
/^[[:space:]]*NID[[:space:]]+TYPE[[:space:]]+FILENAME[[:space:]]*$/ { intable=1; next }
intable && $0 ~ /^[[:space:]]*[0-9]+[[:space:]]+[0-9]+[[:space:]]+/ {
# Capture first two numeric fields and the rest as filename (may contain spaces)
nid=$1; type=$2;
sub(/^[[:space:]]*[0-9]+[[:space:]]+[0-9]+[[:space:]]+/, "", $0);
fn=$0;
if (fn != "." && fn != "..") {
print nid "\t" type "\t" fn
}
next
}
'
}
probe_path_meta() {
local img_path="$1"
"${DUMP[@]}" --path "$img_path" "$IMAGE" 2>/dev/null || true
}
extract_file() {
local img_path="$1" out_path="$2"
mkdir -p "$(dirname "$out_path")"
if ! "${DUMP[@]}" --path "$img_path" --cat "$IMAGE" > "$out_path"; then
echo "warn: failed to cat $img_path (not a regular file?)" >&2
rm -f "$out_path" || true
fi
}
extract_symlink() {
local img_path="$1" out_path="$2"
local meta target
meta="$(probe_path_meta "$img_path")"
target="$(sed -n 's/.*[[:space:]]target:[[:space:]]\([^[:cntrl:]]*\).*/\1/p' <<<"$meta" | head -n1)"
if [[ -n "$target" ]]; then
mkdir -p "$(dirname "$out_path")"
ln -sf "$target" "$out_path"
return 0
fi
return 1
}
queue=("/")
while ((${#queue[@]})); do
dir="${queue[0]}"; queue=("${queue[@]:1}")
if [[ "$dir" == "/" ]]; then
mkdir -p "$OUTDIR"
else
mkdir -p "$OUTDIR/${dir#/}"
fi
while IFS=$'\t' read -r nid typecode name; do
[[ -z "${name:-}" ]] && continue
child="$(abs_child "$dir" "$name")"
out="$OUTDIR/${child#/}"
case "$typecode" in
2) # directory
mkdir -p "$out"
queue+=("$child")
;;
1) # regular file
extract_file "$child" "$out"
;;
*) # unknown type -> probe
meta="$(probe_path_meta "$child")"
if grep -qi '\<symlink\>' <<<"$meta"; then
extract_symlink "$child" "$out" || echo "warn: could not reconstruct symlink $child" >&2
elif grep -qi '\bdirectory\b' <<<"$meta"; then
mkdir -p "$out"; queue+=("$child")
else
# fall back to trying as a file
extract_file "$child" "$out"
fi
;;
esac
done < <(parse_ls_table "$dir")
done
echo "Done: extracted to $OUTDIR"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment