🔒 Repository is read-only – file editing is disabled.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
#!/bin/bash
# =============================================================================
# check-pag-abi.sh – wykrywa pakiety .pag, które NIE ZADZIAŁAJĄ na PaganLinux
# -----------------------------------------------------------------------------
# PO CO: pakiety budowane w chrootcie Debiana (VPS: builder-chroot.sh, suite
# trixie) linkują się z Debianowymi bibliotekami. Jeśli Debian ma INNĄ wersję
# zależności niż PaganLinux, w pakiecie zostają odwołania do symboli, których w
# naszym systemie nie ma → program/plugin pada na starcie z „undefined symbol”.
# Dwa realne przypadki z naszego repo:
# * libgsf-1.so.114 → U xmlNanoHTTPClose (Debian ma, libxml2 2.16 nie)
# * libavcodec.so.63 → U rsvg_handle_render_document (Debian ma librsvg 2.46+,
# my mamy 2.40.21)
#
# JAK DZIAŁA: buduje indeks WSZYSTKICH symboli eksportowanych przez biblioteki
# docelowego rootfsu i sprawdza, czy każdy nierozwiązany symbol z pakietu jest
# w tym indeksie (albo w samym pakiecie). To wyłapuje przypadki „na pewno
# zepsute”; nie wykryje zmiany semantyki symbolu o tej samej nazwie.
#
# Użycie:
# # 1) TRYB ROZJAZDU (precyzyjny – do bramki w build-repo.sh): porównuje symbole
# # z ŚRODOWISKA BUDOWY z symbolem docelowego systemu.
# ./check-pag-abi.sh --build-rootfs /mnt/pagan-build --rootfs /mnt/pagan --strict *.pag
#
# # 2) TRYB CELU (bez --build-rootfs): pokazuje, jakich zależności brakuje w
# # obrazie (to NORMALNE dla pakietów repo – instalator dociąga je z repo).
# ./check-pag-abi.sh --rootfs /mnt/pagan --dir repo-out
#
# Kod wyjścia: 1 = problem (w trybie rozjazdu zawsze; w trybie celu tylko z --strict).
# =============================================================================
set -uo pipefail
ROOTFS="${PAGAN_ROOT:-/mnt/pagan}"
BUILD_ROOTFS=""
STRICT=0
QUIET=0
VERBOSE=0
PKGS=()
SCAN_DIR=""
usage() { sed -n '2,40p' "$0"; exit "${1:-0}"; }
while [[ $# -gt 0 ]]; do
case "$1" in
--rootfs) ROOTFS="$2"; shift 2 ;;
--build-rootfs) BUILD_ROOTFS="$2"; shift 2 ;;
--dir) SCAN_DIR="$2"; shift 2 ;;
--strict) STRICT=1; shift ;;
--quiet|-q) QUIET=1; shift ;;
--verbose|-v) VERBOSE=1; shift ;;
-h|--help) usage 0 ;;
-*) echo "Nieznana opcja: $1" >&2; usage 2 ;;
*) PKGS+=("$1"); shift ;;
esac
done
[[ -d "$ROOTFS/usr/lib" ]] || { echo "❌ Brak rootfsu PaganLinux: $ROOTFS/usr/lib" >&2; exit 2; }
command -v nm >/dev/null || { echo "❌ Brak nm (binutils) na hoście" >&2; exit 2; }
msg() { [[ $QUIET -eq 1 ]] || printf '%s\n' "$*"; }
die() { printf '\033[1;31m✗ %s\033[0m\n' "$*" >&2; exit 2; }
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
# ── 1) Indeks symboli eksportowanych przez docelowy system ──────────────────
# Bierzemy wszystkie .so* z rootfsu (to one mogą rozwiązać symbole pakietu).
# Indeks można zcache-ować (env PAGAN_ABI_INDEX=/ścieżka) – dla 1800 pakietów
# liczenie go za każdym razem byłoby marnotrawstwem.
if [[ -n "${PAGAN_ABI_INDEX:-}" && -s "${PAGAN_ABI_INDEX}" ]]; then
cp -f "$PAGAN_ABI_INDEX" "$TMP/target.syms"
msg "▸ Indeks symboli z cache: $PAGAN_ABI_INDEX ($(wc -l < "$TMP/target.syms") symboli)"
else
msg "▸ Buduję indeks symboli z $ROOTFS (biblioteki .so)…"
: > "$TMP/target.syms"
# BEZ -maxdepth: moduły Xorg potrafią leżeć głęboko (np.
# /usr/lib/xorg/modules/libglamoregl.so eksportuje glamor_init, glamor_create_pixmap…).
# Przy -maxdepth 2 te pliki nie trafiały do indeksu, więc sterowniki Xorg
# (amdgpu/intel) były fałszywie zgłaszane jako „rozjazd ABI” – ich symbole
# glamor_* są w tym module, a nie w samym sterowniku.
while IFS= read -r -d '' lib; do
nm -D --defined-only --format=posix "$lib" 2>/dev/null | awk '{print $1}'
done < <(find "$ROOTFS/usr/lib" "$ROOTFS/lib" -name '*.so*' -type f -print0 2>/dev/null) \
| sed 's/@.*$//' | sort -u > "$TMP/target.syms"
msg " symboli w systemie docelowym: $(wc -l < "$TMP/target.syms")"
if [[ -n "${PAGAN_ABI_INDEX:-}" ]]; then
mkdir -p "$(dirname "$PAGAN_ABI_INDEX")" 2>/dev/null || true
cp -f "$TMP/target.syms" "$PAGAN_ABI_INDEX" 2>/dev/null || true
msg " indeks zapisany w cache: $PAGAN_ABI_INDEX"
fi
fi
# Symbole, które wolno mieć nierozwiązane (są opcjonalne / dostarczane przez
# mechanizmy runtime): słabe odwołania i kilka znanych wstawek linkera.
cat > "$TMP/ignore.syms" <<'EOF'
__gmon_start__
__cxa_finalize
_ITM_deregisterTMCloneTable
_ITM_registerTMCloneTable
_ITM_addTMCloneTable
_Jv_RegisterClasses
EOF
# ── 1b) Indeks symboli ze ŚRODOWISKA BUDOWY (tryb rozjazdu) ────────────────
# To jest sedno odpowiedzi na „czy repo będzie spójne”: jeśli symbol jest
# dostępny w środowisku, w którym pakiet powstawał, a NIE MA go w systemie
# docelowym, to pakiet został zbudowany pod inne biblioteki (np. Debianowy
# chroot na VPS) i padnie u użytkownika z „undefined symbol”.
if [[ -n "$BUILD_ROOTFS" ]]; then
[[ -d "$BUILD_ROOTFS/usr/lib" || -d "$BUILD_ROOTFS/lib" ]] || \
die "Brak środowiska budowy: $BUILD_ROOTFS"
msg "▸ Buduję indeks symboli ze ŚRODOWISKA BUDOWY: $BUILD_ROOTFS…"
: > "$TMP/build.syms"
# Tu również bez -maxdepth (ten sam powód co w indeksie docelowym wyżej).
while IFS= read -r -d '' lib; do
nm -D --defined-only --format=posix "$lib" 2>/dev/null | awk '{print $1}'
done < <(find "$BUILD_ROOTFS/usr/lib" "$BUILD_ROOTFS/lib" -name '*.so*' -type f -print0 2>/dev/null \
) | sed 's/@.*$//' | sort -u > "$TMP/build.syms"
msg " symboli w środowisku budowy: $(wc -l < "$TMP/build.syms")"
# Mapa „który soname dostarcza dany symbol” (środowisko budowy) + soname'y
# obecne w systemie docelowym. Pozwala odróżnić DWIE sytuacje, które wcześniej
# wyglądały identycznie (i dawały fałszywe „ROZJAZD ABI”):
# * DRIFT ABI – biblioteka o TYM SAMYM soname JEST w targetcie, ale bez
# potrzebnego symbolu (np. zbudowane pod nowsze Debianowe libxml2/librsvg)
# → naprawdę zepsuty pakiet,
# * BRAK ZALEŻNOŚCI – takiej biblioteki nie ma w targetcie WCALE, więc jest
# to zwykła zależność z repo (np. cmake → librhash.so.1, libuv.so.1,
# libjsoncpp.so.27). To NIE rozjazd ABI, ale MUSI trafić do `depends:`.
msg "▸ Buduję mapę dostawców symboli (symbol → soname)…"
: > "$TMP/build.prov"
while IFS= read -r -d '' lib; do
_sn="$(readelf -d "$lib" 2>/dev/null | sed -n 's/.*SONAME.*\[\(.*\)\].*/\1/p' | head -n1)"
[[ -n "$_sn" ]] || _sn="$(basename "$lib")"
nm -D --defined-only --format=posix "$lib" 2>/dev/null | awk -v s="$_sn" 'NF{print $1 "\t" s}'
done < <(find "$BUILD_ROOTFS/usr/lib" "$BUILD_ROOTFS/lib" -name '*.so*' -type f -print0 2>/dev/null) \
| sed 's/@.*$//' | sort -u > "$TMP/build.prov"
: > "$TMP/target.sonames"
while IFS= read -r -d '' lib; do
_sn="$(readelf -d "$lib" 2>/dev/null | sed -n 's/.*SONAME.*\[\(.*\)\].*/\1/p' | head -n1)"
[[ -n "$_sn" ]] && printf '%s\n' "$_sn"
basename "$lib"
done < <(find "$ROOTFS/usr/lib" "$ROOTFS/lib" -name '*.so*' -type f -print0 2>/dev/null) \
| sort -u > "$TMP/target.sonames"
fi
# Katalogi wtyczek: te pliki .so są dlopen()owane przez proces-gospodarza
# (Xorg, pipewire, GIO, gdk-pixbuf…) i legalnie rozwiązują symbole z BINARKI
# gospodarza, której nie ma w żadnym .so – w trybie celu dawałyoby to szum.
PLUGIN_SKIP='/(xorg/modules|pipewire-0\.3|spa-0\.2|gio/modules|gdk-pixbuf-2\.0|dri|vulkan|gstreamer-1\.0|xfce4/panel/plugins|xfce4/settings-manager|tumbler-1/plugins|NetworkManager)/'
# Drzewa ŹRÓDEŁ w pakiecie (np. kernel: /usr/src/kernel/...) zawierają narzędzia
# BUILD-TIME (objtool) linkowane z libbfd/libopcodes z binutils. Są używane
# dopiero przy budowie modułów (gdy binutils i tak musi być obecny), a NIE w
# runtime obrazu – więc nie są rozjazdem ABI. Pomijamy je w obu trybach.
SKIP_ALWAYS='/(usr/src)/'
if [[ -n "$SCAN_DIR" ]]; then
for f in "$SCAN_DIR"/*.pag; do
[[ -f "$f" ]] && PKGS+=("$f")
done
fi
[[ ${#PKGS[@]} -gt 0 ]] || die "Nie podano żadnego pakietu .pag (ani --dir)"
# ── 2) Sprawdzenie każdego pakietu ──────────────────────────────────────────
_broken=0
_warned=0
_depwarn=0
_checked=0
: > "$TMP/dep_all.txt"
for pag in "${PKGS[@]}"; do
[[ -f "$pag" ]] || { msg " ! pomijam (brak pliku): $pag"; continue; }
# Rozpakuj: .pag = tar.xz( metadata.json + data.tar.xz + hooks/ )
rm -rf "$TMP/x"; mkdir -p "$TMP/x"
if ! tar -xJf "$pag" -C "$TMP/x" data.tar.xz 2>/dev/null; then
msg " ! $pag: nie mogę odczytać data.tar.xz – pomijam"
continue
fi
mkdir -p "$TMP/x/data"
tar -xJf "$TMP/x/data.tar.xz" -C "$TMP/x/data" 2>/dev/null || {
msg " ! $pag: data.tar.xz uszkodzony – pomijam"; continue; }
# Zbiór symboli dostarczanych przez sam pakiet (biblioteki w pakiecie)
: > "$TMP/pkg.syms"
while IFS= read -r -d '' elf; do
nm -D --defined-only --format=posix "$elf" 2>/dev/null | awk '{print $1}'
done < <(find "$TMP/x/data" -name '*.so*' -type f -print0 2>/dev/null) \
| sed 's/@.*$//' | sort -u > "$TMP/pkg.syms"
_pkg_missing="$TMP/missing.txt"
: > "$_pkg_missing"
# Soname'y bibliotek spoza obrazu (zależności runtime) – liczone od nowa dla
# każdego pakietu.
: > "$TMP/dep_sonames.txt"
# Każdy plik ELF: nierozwiązane symbole (BEZ słabych – te są opcjonalne)
while IFS= read -r -d '' f; do
# tylko prawdziwe ELF-y (pierwsze 4 bajty: \x7fELF)
[[ "$(head -c4 "$f" 2>/dev/null | od -An -c | tr -d ' \n')" == *177ELF* ]] || continue
# ZAWSZE pomijaj drzewa źródeł (kernel /usr/src/kernel/tools/objtool itd.).
[[ "$f" =~ $SKIP_ALWAYS ]] && continue
# W trybie CELU pomijamy wtyczki (rozwiązują symbole z procesu-gospodarza).
if [[ -z "$BUILD_ROOTFS" ]] && [[ "$f" =~ $PLUGIN_SKIP ]]; then
continue
fi
nm -D --undefined-only "$f" 2>/dev/null \
| awk '$1 ~ /^[Uu]$/ {print $2} $2 ~ /^U$/ {print $3}' \
| sed 's/@.*$//' | sort -u > "$TMP/u.syms"
[[ -s "$TMP/u.syms" ]] || continue
# odrzuć: własne symbole pakietu + listę wyjątków
comm -23 "$TMP/u.syms" <(sort -u "$TMP/pkg.syms" "$TMP/ignore.syms") > "$TMP/u2.syms"
if [[ -n "$BUILD_ROOTFS" ]]; then
# TRYB ROZJAZDU: kandydaci to symbole, które środowisko budowy MA,
# a system docelowy NIE MA. Potem rozdzielamy drift od zależności.
comm -12 "$TMP/u2.syms" "$TMP/build.syms" | comm -23 - "$TMP/target.syms" > "$TMP/cand.syms"
awk -F'\t' -v tfile="$TMP/target.sonames" '
BEGIN { while ((getline l < tfile) > 0) inT[l] = 1 }
NR == FNR { prov[$1] = prov[$1] (prov[$1] ? "," : "") $2; next }
{
if (!($1 in prov)) { print $1 "\tDRIFT\t?"; next }
n = split(prov[$1], a, ","); hit = 0; miss = ""
for (i = 1; i <= n; i++) {
if (a[i] in inT) hit = 1
else miss = miss (miss ? "," : "") a[i]
}
if (hit) print $1 "\tDRIFT\t" miss
else print $1 "\tDEP\t" miss
}' "$TMP/build.prov" "$TMP/cand.syms" > "$TMP/classified.txt"
awk -F'\t' '$2 == "DRIFT" { print $1 }' "$TMP/classified.txt" > "$TMP/u3.syms"
awk -F'\t' '$2 == "DEP" && $3 != "" { n = split($3, a, ","); for (i = 1; i <= n; i++) print a[i] }' \
"$TMP/classified.txt" >> "$TMP/dep_sonames.txt"
else
# TRYB CELU: wszystko, czego nie ma w obrazie (informacyjnie).
comm -23 "$TMP/u2.syms" "$TMP/target.syms" > "$TMP/u3.syms"
fi
while IFS= read -r sym; do
[[ -n "$sym" ]] && printf '%s\t%s\n' "${f#"$TMP/x/data"}" "$sym" >> "$_pkg_missing"
done < "$TMP/u3.syms"
done < <(find "$TMP/x/data" -type f -print0 2>/dev/null)
_checked=$((_checked + 1))
_has_drift=0; [[ -s "$_pkg_missing" ]] && _has_drift=1
_has_dep=0; [[ -s "$TMP/dep_sonames.txt" ]] && _has_dep=1
if [[ $_has_drift -eq 1 ]]; then
if [[ -n "$BUILD_ROOTFS" ]]; then
_broken=$((_broken + 1))
printf '\033[1;31m✗ %s\033[0m – ROZJAZD ABI: %s symboli jest w środowisku budowy, a nie ma w %s:\n' \
"$(basename "$pag")" "$(wc -l < "$_pkg_missing")" "$ROOTFS"
else
_warned=$((_warned + 1))
printf '\033[1;33m· %s\033[0m – %s symboli spoza obrazu (pakiet potrzebuje zależności z repo):\n' \
"$(basename "$pag")" "$(wc -l < "$_pkg_missing")"
fi
head -10 "$_pkg_missing" | while IFS=$'\t' read -r p s; do
printf ' %-52s %s\n' "$s" "$p"
done
if [[ "$(wc -l < "$_pkg_missing")" -gt 10 ]]; then
printf ' … i %s dalszych\n' "$(( $(wc -l < "$_pkg_missing") - 10 ))"
if [[ $VERBOSE -eq 1 ]]; then
tail -n +11 "$_pkg_missing" | while IFS=$'\t' read -r p s; do
printf ' %-52s %s\n' "$s" "$p"
done
fi
fi
if [[ -n "$BUILD_ROOTFS" ]]; then
printf ' ↳ symbol jest w środowisku budowy, a brak go w bibliotece o TYM SAMYM\n'
printf ' sonamie w %s – pakiet zbudowano pod inne biblioteki\n' "$ROOTFS"
printf ' (np. chroot Debiana na VPS). Przebuduj natywnie:\n'
printf ' ./build-repo.sh --rootfs %s %s\n' "$BUILD_ROOTFS" \
"$(basename "$pag" | sed 's/-[0-9][^-]*-[0-9]*\.pag$//')"
fi
fi
# Brakujące zależności runtime: bibliotek, których w obrazie nie ma WCALE.
# To nie rozjazd ABI (dociągnie je `pag` z repo), ale muszą być w `depends:`
# receptury – inaczej użytkownik dostanie „error while loading shared
# libraries”. Tak wyszło przy cmake: brakowało librhash/libuv/libjsoncpp.
if [[ $_has_dep -eq 1 ]]; then
_depwarn=$((_depwarn + 1))
sort -u "$TMP/dep_sonames.txt" >> "$TMP/dep_all.txt"
printf '\033[1;33m· %s\033[0m – %s bibliotek spoza obrazu (zależności runtime – dopisz do depends:):\n' \
"$(basename "$pag")" "$(sort -u "$TMP/dep_sonames.txt" | wc -l)"
printf ' %s\n' "$(sort -u "$TMP/dep_sonames.txt" | tr '\n' ' ')"
fi
if [[ $_has_drift -eq 0 && $_has_dep -eq 0 ]]; then
msg " ✓ $(basename "$pag") – bez zarzutów"
fi
done
echo
if [[ -n "$BUILD_ROOTFS" && $_broken -gt 0 ]]; then
printf '\033[1;31m✗ %d z %d pakietów zbudowano pod inne biblioteki niż %s – NIE publikuj ich\033[0m\n' \
"$_broken" "$_checked" "$ROOTFS"
exit 1
fi
if [[ -n "$BUILD_ROOTFS" ]]; then
if [[ $_depwarn -gt 0 ]]; then
printf '\033[1;33m! %d pakiet(ów) używa bibliotek spoza obrazu: %s\033[0m\n' \
"$_depwarn" "$(sort -u "$TMP/dep_all.txt" 2>/dev/null | tr '\n' ' ')"
printf ' To normalne dla repo (`pag` dociąga je z repo), ale każda z nich musi być w `depends:` receptury.\n'
if [[ $STRICT -eq 1 ]]; then
printf '\033[1;31m✗ --strict: brakujące zależności traktowane jak błąd\033[0m\n'
exit 1
fi
fi
printf '\033[1;32m✓ %d pakietów sprawdzonych – bez rozjazdu ABI (środowisko budowy == %s)\033[0m\n' \
"$_checked" "$ROOTFS"
else
printf '\033[1;32m✓ %d pakietów sprawdzonych (%d z zależnościami spoza ISO – to normalne dla repo)\033[0m\n' \
"$_checked" "$_warned"
fi
if [[ $STRICT -eq 1 && $_warned -gt 0 ]]; then
exit 1
fi
exit 0