#!/bin/bash
# Repli GCC 12.4.0 C/C++ pour Linux 6.12.115, dans une copie du rootfs cible.
# Binutils reste celui du rootfs. Aucun binutils 2.42 n'est construit.
# STACKX_BINUTILS_VERSION et STACKX_TOOLCHAIN_DEB_VERSION ne sont pas lus :
# leur correction appartient au paquetage, pas à cette construction.
# Ce script n'écrit pas toolchain.ok tant que les preuves ci-dessous manquent,
# et ce marqueur ne prouve ni la compilation du noyau ni un démarrage.
# shellcheck shell=bash
set -euo pipefail

SCRIPT_LIB=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/common.sh
# shellcheck source=lib/common.sh
source "$SCRIPT_LIB"
STACKX_STEP=build-toolchain
install_exit_trap
load_contract_env
require_runtime_validated_bullseye
require_cmd tar sha256sum find awk grep sed bwrap
umask 022

[[ ${STACKX_TOOLCHAIN_PREFIX} == /opt/stackx-toolchain ]] \
  || die "préfixe de repli inattendu"

GCC_VERSION=12.4.0
GMP_ARCHIVE=gmp-6.2.1.tar.bz2
MPFR_ARCHIVE=mpfr-4.1.0.tar.bz2
MPC_ARCHIVE=mpc-1.2.1.tar.gz
GCC_ARCHIVE=gcc-${GCC_VERSION}.tar.xz

ensure_work_layout
reports=${STACKX_WORK}/reports
FAIL_FILE=${reports}/toolchain.echec
OK_FILE=${reports}/toolchain.ok
workroot=${STACKX_WORK}/rootfs
src_dir=${STACKX_WORK}/toolchain-src
obj_dir=${STACKX_WORK}/toolchain-obj

record_failure() {
  local etape=$1
  local motif=$2
  local binv=${3:-non-mesure}
  keep_first_copy "$FAIL_FILE"
  write_kv "$FAIL_FILE" \
    "kernel=${KERNEL_VERSION}" \
    "dist=${TARGET_DIST}" \
    "abi=${ABI_REV}" \
    "release=${STACKX_RELEASE}" \
    "mode=repli" \
    "decision=repli-possible" \
    "etat=echec" \
    "etape=${etape}" \
    "motif=${motif}" \
    "gcc=${GCC_VERSION}" \
    "binutils=${binv}" \
    "binutils_mode=natif" \
    "prefix=${STACKX_TOOLCHAIN_PREFIX}" \
    "glibc=${STACKX_GLIBC_FLOOR}" \
    "description=echec du GCC ${GCC_VERSION} alternatif, binutils natif non remplacé" \
    "distinct=outils-natifs.echec"
}

measured_binutils() {
  local f value
  for f in "${src_dir}/binutils-natif.txt" "${obj_dir}/binutils-natif.txt"; do
    if [[ -f $f && ! -L $f ]]; then
      value=$(marker_field "$f" version)
      if [[ -n $value ]]; then
        printf '%s\n' "$value"
        return 0
      fi
    fi
  done
  printf '%s\n' "non-mesure"
}

dir_has_payload() {
  local dir=$1
  local name base
  [[ -d $dir && ! -L $dir ]] || return 1
  while IFS= read -r name; do
    [[ -n $name ]] || continue
    base=${name##*/}
    [[ $base == .stackx-attempt ]] && continue
    return 0
  done < <(find "$dir" -mindepth 1 -maxdepth 1 -print)
  return 1
}

refuse_payload() {
  local dir=$1
  local label=$2
  if dir_has_payload "$dir"; then
    if [[ ! -e $FAIL_FILE ]]; then
      record_failure "tentative-conservee" "$label"
    fi
    die "${label} contient déjà une tentative, conservée"
  fi
}

prepare_empty_attempt() {
  local dir=$1
  local label=$2
  if [[ -L $dir ]]; then
    die "${label} est un lien, conservé"
  fi
  if [[ -e $dir ]]; then
    owned_attempt "$dir" || die "${label} existe sans marqueur de cet essai, conservé"
    refuse_payload "$dir" "$label"
  else
    mkdir_under "$EXTERNAL_ROOT" "$dir"
    mark_attempt "$dir" build-toolchain
  fi
}

require_one_inside() {
  local candidate
  for candidate in "$@"; do
    if rootfs_regular "$workroot" "$candidate" >/dev/null; then
      return 0
    fi
  done
  printf 'ERREUR: fichier cible manquant ou hors racine : %s\n' "$*" >&2
  return 1
}

if native_tools_ok; then
  ok "outils natifs suffisants pour ${STACKX_RELEASE}. Aucun compilateur de repli n'est construit."
  exit 0
fi

natfail=${reports}/outils-natifs.echec
if [[ -e ${reports}/outils-natifs.ok ]]; then
  die "outils-natifs.ok est présent mais ne vaut pas natif-possible pour cette révision. Il est conservé."
fi
[[ -f $natfail && ! -L $natfail ]] || die "qualification native absente. qualify-tools.sh doit écrire outils-natifs.echec avant ce repli."
marker_matches "$natfail" || die "outils-natifs.echec vise une autre révision. Il est conservé."

decision=$(marker_field "$natfail" decision)
case $decision in
  rootfs-inapte)
    die "rootfs inapte ($(marker_field "$natfail" minimum_manquant)). Le repli GCC ${GCC_VERSION} est refusé."
    ;;
  repli-possible) ;;
  *)
    die "décision native inattendue : ${decision:-vide}. Aucun compilateur n'est construit."
    ;;
esac
if [[ -L $OK_FILE ]]; then
  die "toolchain.ok est un lien, conservé"
fi
if [[ -e $OK_FILE ]]; then
  if marker_matches "$OK_FILE" \
    && [[ $(marker_field "$OK_FILE" mode) == repli ]] \
    && [[ $(marker_field "$OK_FILE" decision) == repli-possible ]] \
    && [[ $(marker_field "$OK_FILE" binutils_mode) == natif ]] \
    && [[ $(marker_field "$OK_FILE" bootstrap) == yes ]] \
    && [[ $(marker_field "$OK_FILE" gcc) == "$GCC_VERSION" ]] \
    && [[ $(marker_field "$OK_FILE" prefix) == "$STACKX_TOOLCHAIN_PREFIX" ]]; then
    [[ ${STACKX_ALLOW_TOOLCHAIN_FALLBACK:-} == 1 ]] \
      || die "toolchain.ok existe déjà, mais STACKX_ALLOW_TOOLCHAIN_FALLBACK=1 doit rester posé pour reconnaître le repli. Aucune reconstruction."
    ok "repli GCC ${GCC_VERSION} déjà construit avec binutils natif $(marker_field "$OK_FILE" binutils). Aucune reconstruction. Ce marqueur ne prouve ni la compilation du noyau ni un démarrage."
    exit 0
  fi
  die "toolchain.ok existe et ne correspond pas à ce repli. Il est conservé."
fi
[[ ${STACKX_ALLOW_TOOLCHAIN_FALLBACK:-} == 1 ]] \
  || die "repli possible, mais STACKX_ALLOW_TOOLCHAIN_FALLBACK=1 est absent. Aucun GCC n'est construit."
if [[ -n ${STACKX_GCC_DISABLE_BOOTSTRAP:-} ]]; then
  die "le repli exige le bootstrap. STACKX_GCC_DISABLE_BOOTSTRAP est refusé."
fi

: "${TOOLCHAIN_DIR:?TOOLCHAIN_DIR est obligatoire}"
: "${TOOLCHAIN_SHA256:?TOOLCHAIN_SHA256 est obligatoire}"
assert_abs_path "$TOOLCHAIN_DIR" TOOLCHAIN_DIR
assert_abs_path "$TOOLCHAIN_SHA256" TOOLCHAIN_SHA256
[[ -d $TOOLCHAIN_DIR && ! -L $TOOLCHAIN_DIR ]] || die "TOOLCHAIN_DIR absent"
[[ -f $TOOLCHAIN_SHA256 && ! -L $TOOLCHAIN_SHA256 ]] || die "fichier SHA256 absent"

verify_archive() {
  local base=$1
  local file=${TOOLCHAIN_DIR}/${base}
  local expected got
  [[ -f $file && ! -L $file ]] || {
    record_failure "sha256" "archive-absente"
    die "archive manquante : ${base}"
  }
  expected=$(awk -v b="$base" '
    $1 ~ /^[0-9a-fA-F]{64}$/ {
      name = $2
      sub(/^\*/, "", name)
      if (name == b) { print tolower($1); exit }
    }
  ' "$TOOLCHAIN_SHA256")
  [[ $expected =~ ^[0-9a-f]{64}$ ]] || {
    record_failure "sha256" "somme-absente"
    die "SHA256 absent pour ${base}"
  }
  got=$(sha256_file "$file")
  [[ $got == "$expected" ]] || {
    record_failure "sha256" "somme-incorrecte"
    die "SHA256 incorrect pour ${base}"
  }
  if ! tar -tf "$file" >/dev/null; then
    record_failure "sha256" "archive-illisible"
    die "archive illisible par le tar de l'hôte, aucune installation : ${base}"
  fi
}

verify_archive "$GCC_ARCHIVE"
verify_archive "$GMP_ARCHIVE"
verify_archive "$MPFR_ARCHIVE"
verify_archive "$MPC_ARCHIVE"

marker_ok "${reports}/rootfs.ok"
root_glibc=$(marker_field "${reports}/rootfs.ok" glibc)
[[ $root_glibc == "$STACKX_GLIBC_FLOOR" ]] || die "plancher glibc incohérent avec le rootfs qualifié"
ldso_rel=$(marker_field "${reports}/rootfs.ok" ldso)
ldso_rel=${ldso_rel//]/}
ldso_rel=${ldso_rel//[/}
ldso_rel=${ldso_rel//[[:space:]]/}
[[ $ldso_rel == /* ]] || die "ldso du marqueur illisible"
qual_binutils=$(marker_field "$natfail" binutils_version)
qual_binutils=${qual_binutils//[[:space:]]/}
[[ $qual_binutils =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]] || die "version binutils de la qualification illisible"

qualified=$(marker_field "${reports}/rootfs.ok" rootfs)
[[ -n $qualified ]] || die "rootfs du marqueur vide"
qualified=$(canon_existing_dir "$qualified" "rootfs qualifié")

verify_copy() {
  local inside
  local -a required=(
    /etc/debian_version
    /bin/sh
    /bin/bash
    /usr/bin/make
    /usr/bin/gcc
    /usr/bin/g++
    /usr/bin/as
    /usr/bin/ld
    /usr/bin/ar
    /usr/bin/nm
    /usr/bin/objcopy
    /usr/bin/objdump
    /usr/bin/readelf
    /usr/bin/strip
    /usr/bin/ranlib
    /usr/bin/find
    /usr/bin/grep
    /usr/bin/head
    /usr/include/zlib.h
    /usr/include/stdio.h
    "$ldso_rel"
  )
  for inside in "${required[@]}"; do
    if ! rootfs_regular "$workroot" "$inside" >/dev/null; then
      printf 'ERREUR: fichier cible manquant ou hors racine : %s\n' "$inside" >&2
      return 1
    fi
  done
  require_one_inside /usr/bin/tar /bin/tar || return 1
  require_one_inside /usr/bin/awk /usr/bin/gawk /bin/awk || return 1
  require_one_inside /bin/sed /usr/bin/sed || return 1
  require_one_inside /bin/readlink /usr/bin/readlink || return 1
  require_one_inside /usr/bin/cksum /bin/cksum || return 1
  require_one_inside /usr/bin/cmp /bin/cmp || return 1
  require_one_inside \
    /usr/lib/x86_64-linux-gnu/libz.so \
    /lib/x86_64-linux-gnu/libz.so \
    /usr/lib/libz.so || return 1
  if [[ ! -d ${workroot}/mnt || -L ${workroot}/mnt ]]; then
    printf 'ERREUR: la copie n'\''a pas de /mnt réel\n' >&2
    return 1
  fi
  assert_reserved_mountpoints "$workroot" || return 1
}

if [[ -L $workroot ]]; then
  die "copie de rootfs est un lien, conservée"
fi
if [[ -e $workroot ]]; then
  owned_attempt "$workroot" || die "copie de rootfs déjà présente sans marqueur de cet essai, conservée"
  if ! verify_copy; then
    record_failure "copie-rootfs" "verification"
    die "copie de rootfs incomplète, conservée"
  fi
else
  mkdir_under "$EXTERNAL_ROOT" "$workroot"
  if ! tar -C "$qualified" -cf - . | tar -C "$workroot" --no-same-owner -xf -; then
    record_failure "copie-rootfs" "extraction-interrompue"
    die "copie du rootfs interrompue, arbre conservé"
  fi
  if ! verify_copy; then
    record_failure "copie-rootfs" "verification"
    die "copie du rootfs incomplète, arbre conservé"
  fi
  mark_attempt "$workroot" build-toolchain
fi

declare -A tool_sha=()
declare -A tool_rel=()
snapshot_tools() {
  local inside rel
  for inside in /usr/bin/gcc /usr/bin/g++ /usr/bin/as /usr/bin/ld /usr/bin/ar; do
    rel=$(rootfs_regular "$workroot" "$inside")
    tool_rel[$inside]=$rel
    tool_sha[$inside]=$(sha256_file "${workroot}${rel}")
  done
}
confirm_tools_unchanged() {
  local inside rel now
  for inside in "${!tool_sha[@]}"; do
    rel=$(rootfs_regular "$workroot" "$inside")
    if [[ $rel != "${tool_rel[$inside]}" ]]; then
      record_failure "rootfs-modifie" "chemin" "$(measured_binutils)"
      die "outil cible déplacé, copie conservée"
    fi
    now=$(sha256_file "${workroot}${rel}")
    if [[ $now != "${tool_sha[$inside]}" ]]; then
      record_failure "rootfs-modifie" "contenu" "$(measured_binutils)"
      die "outil cible modifié, copie conservée"
    fi
  done
}
snapshot_tools

prefix_host=${workroot}${STACKX_TOOLCHAIN_PREFIX}
if [[ -L $prefix_host ]]; then
  die "préfixe de toolchain est un lien, conservé"
fi
if [[ -e $prefix_host ]]; then
  refuse_payload "$prefix_host" "prefixe"
else
  mkdir_under "$workroot" "$prefix_host"
fi

prepare_empty_attempt "$src_dir" "sources-toolchain"
prepare_empty_attempt "$obj_dir" "objets-toolchain"

extract_archive() {
  local base=$1
  local expected_dir=$2
  if ! tar -C "$src_dir" --no-same-owner -xf "${TOOLCHAIN_DIR}/${base}"; then
    record_failure "extraction" "$base"
    die "extraction interrompue, arbre conservé : ${base}"
  fi
  [[ -d ${src_dir}/${expected_dir} && ! -L ${src_dir}/${expected_dir} ]] || {
    record_failure "extraction" "racine-${expected_dir}"
    die "racine d'archive inattendue : ${base}"
  }
}

extract_archive "$GCC_ARCHIVE" "gcc-${GCC_VERSION}"
extract_archive "$GMP_ARCHIVE" "gmp-6.2.1"
extract_archive "$MPFR_ARCHIVE" "mpfr-4.1.0"
extract_archive "$MPC_ARCHIVE" "mpc-1.2.1"
mv -- "${src_dir}/gmp-6.2.1" "${src_dir}/gcc-${GCC_VERSION}/gmp" \
  || { record_failure "extraction" "gmp"; die "déplacement GMP interrompu, arbre conservé"; }
mv -- "${src_dir}/mpfr-4.1.0" "${src_dir}/gcc-${GCC_VERSION}/mpfr" \
  || { record_failure "extraction" "mpfr"; die "déplacement MPFR interrompu, arbre conservé"; }
mv -- "${src_dir}/mpc-1.2.1" "${src_dir}/gcc-${GCC_VERSION}/mpc" \
  || { record_failure "extraction" "mpc"; die "déplacement MPC interrompu, arbre conservé"; }

inner=${reports}/toolchain-inside.sh
cat > "$inner" <<'INNER_SCRIPT'
#!/bin/bash
# Construction dans la copie : / est le rootfs, en lecture seule sauf
# /opt/stackx-toolchain et les répertoires de travail montés par l'appelant.
# MAKEINFO=true ne recompile aucun objet. Les archives officielles contiennent
# déjà les fichiers info ; le rootfs n'a pas texinfo, et rien n'est installé
# pour l'obtenir. La commande true remplace seulement makeinfo.
set -euo pipefail
umask 022
export LC_ALL=C
export PATH=/usr/sbin:/usr/bin:/sbin:/bin
# bwrap conserve l'environnement de l'hôte. On retire les drapeaux hérités,
# notamment -march=native, avant de poser des valeurs explicites.
unset LD_LIBRARY_PATH CPATH C_INCLUDE_PATH CPLUS_INCLUDE_PATH LIBRARY_PATH \
  PKG_CONFIG_PATH COMPILER_PATH CPPFLAGS \
  CFLAGS CXXFLAGS LDFLAGS \
  CFLAGS_FOR_BUILD CXXFLAGS_FOR_BUILD LDFLAGS_FOR_BUILD \
  CFLAGS_FOR_TARGET CXXFLAGS_FOR_TARGET LDFLAGS_FOR_TARGET \
  BOOT_CFLAGS BOOT_CXXFLAGS BOOT_LDFLAGS \
  STAGE1_CFLAGS STAGE1_CXXFLAGS STAGE1_LDFLAGS \
  CC_FOR_BUILD CXX_FOR_BUILD || true
export MAKEINFO=true
export CC=/usr/bin/gcc
export CXX=/usr/bin/g++
export CFLAGS=-O2
export CXXFLAGS=-O2
export BOOT_CFLAGS='-O2 -g'
runtime_rpath='-Wl,-rpath,/opt/stackx-toolchain/lib64 -Wl,-rpath,/opt/stackx-toolchain/lib'
export LDFLAGS="$runtime_rpath"
export LDFLAGS_FOR_TARGET="$runtime_rpath"
export BOOT_LDFLAGS="$runtime_rpath"

jobs=$1
ldso=$2
qual=$3
[[ $jobs =~ ^[1-9][0-9]*$ ]] || exit 1
[[ $ldso == /* ]] || exit 1
[[ $qual =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]] || exit 1
[[ -x /usr/bin/gcc && -x /usr/bin/g++ && -x /usr/bin/make ]] || exit 1
[[ -d /work/src/gcc-12.4.0/gmp && -d /work/src/gcc-12.4.0/mpfr && -d /work/src/gcc-12.4.0/mpc ]] || exit 1

tool_version() {
  local line tok found=
  if ! "$@" > /tmp/tool.ver 2>&1; then
    printf 'outil natif illisible\n' >&2
    exit 1
  fi
  line=$(head -n 1 /tmp/tool.ver | tr -d '\r')
  for tok in $line; do
    if [[ $tok =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
      found=$tok
    elif [[ -z $found && $tok =~ ^[0-9]+\.[0-9]+$ ]]; then
      found=$tok
    fi
  done
  printf '%s\n' "$found"
}

ld_ver=$(tool_version /usr/bin/ld -v)
as_ver=$(tool_version /usr/bin/as --version)
[[ -n $ld_ver && $ld_ver == "$as_ver" ]] || {
  printf 'as et ld natifs divergent\n' >&2
  exit 1
}
case $ld_ver in
  "$qual"|"$qual".*) ;;
  *)
    printf 'binutils natif incompatible avec la qualification\n' >&2
    exit 1
    ;;
esac
printf 'version=%s\n' "$ld_ver" > /work/src/binutils-natif.txt
command -v cksum >/dev/null 2>&1 || {
  printf 'cksum absent du rootfs\n' >&2
  exit 1
}

cd /work/obj
/work/src/gcc-12.4.0/configure \
  --prefix=/opt/stackx-toolchain \
  --libdir=/opt/stackx-toolchain/lib \
  --libexecdir=/opt/stackx-toolchain/libexec \
  --enable-languages=c,c++ \
  --enable-bootstrap \
  --disable-multilib \
  --disable-libsanitizer \
  --disable-libgomp \
  --disable-libquadmath \
  --disable-libvtv \
  --disable-libitm \
  --disable-libssp \
  --without-isl \
  --with-system-zlib \
  --with-arch=x86-64 \
  --with-tune=generic \
  --with-as=/usr/bin/as \
  --with-ld=/usr/bin/ld \
  --with-gnu-as \
  --with-gnu-ld \
  --with-boot-ldflags='-Wl,-rpath,/opt/stackx-toolchain/lib64 -Wl,-rpath,/opt/stackx-toolchain/lib'
if ! grep -E -q '^stage3-bubble::?' /work/obj/Makefile \
  && ! grep -F -q 'Comparing stages 2 and 3' /work/obj/Makefile; then
  printf 'bootstrap non configure\n' >&2
  exit 1
fi
# Les bibliothèques désactivées limitent le compilateur au C/C++ utile au noyau.
# Ce n'est pas un contournement d'erreur : -Werror n'est pas désactivé.

/usr/bin/make -j"$jobs" MAKEINFO=true > /work/obj/build.log 2>&1
/usr/bin/make install MAKEINFO=true >> /work/obj/build.log 2>&1
if ! grep -F -q 'Comparing stages 2 and 3' /work/obj/build.log \
  && ! grep -F -q 'Comparing stage2 and stage3' /work/obj/build.log; then
  printf 'comparaison de bootstrap absente\n' >&2
  exit 1
fi
if grep -F -q 'Bootstrap comparison failure' /work/obj/build.log; then
  printf 'comparaison de bootstrap en echec\n' >&2
  exit 1
fi

install_wrapper() {
  local tool=$1
  local dest=/opt/stackx-toolchain/bin/$tool
  [[ -x /usr/bin/$tool ]] || exit 1
  if [[ -e $dest || -L $dest ]]; then
    printf 'wrapper non ecrit, le nom existe deja\n' >&2
    exit 1
  fi
  printf '%s\n' '#!/bin/sh' "exec /usr/bin/${tool} \"\$@\"" > "$dest"
  chmod 755 "$dest"
  [[ ! -L $dest && -x $dest ]] || exit 1
}
wrapper_line() {
  local bin=$1
  "$bin" --version > /tmp/wrap.ver 2>&1 || true
  head -n 1 /tmp/wrap.ver | tr -d '\r'
}
for tool in as ld ar nm objcopy objdump readelf strip ranlib; do
  install_wrapper "$tool"
  left=$(wrapper_line /usr/bin/"$tool")
  right=$(wrapper_line /opt/stackx-toolchain/bin/"$tool")
  [[ -n $left && $left == "$right" ]] || {
    printf 'wrapper different de l outil natif\n' >&2
    exit 1
  }
done

unset LD_LIBRARY_PATH || true
gcc=/opt/stackx-toolchain/bin/gcc
gxx=/opt/stackx-toolchain/bin/g++
for bin in "$gcc" "$gxx"; do
  real=$(readlink -f -- "$bin")
  case $real in
    /opt/stackx-toolchain/*) ;;
    *) exit 1 ;;
  esac
  [[ -f $real && -x $real ]] || exit 1
done
"$gcc" -v > /tmp/gcc-v.txt 2>&1
full=$("$gcc" -dumpfullversion 2>/dev/null || true)
[[ -n $full ]] || full=$("$gcc" -dumpversion)
[[ $full == 12.4.0 ]] || exit 1
machine=$("$gcc" -dumpmachine)
[[ $machine == x86_64-* ]] || exit 1
grep -F -q -- '--with-arch=x86-64' /tmp/gcc-v.txt
grep -F -q -- '--with-tune=generic' /tmp/gcc-v.txt
grep -F -q -- '--with-as=/usr/bin/as' /tmp/gcc-v.txt
grep -F -q -- '--with-ld=/usr/bin/ld' /tmp/gcc-v.txt
case $("$gcc" -print-prog-name=as) in
  /usr/bin/as|as) ;;
  *) exit 1 ;;
esac
case $("$gcc" -print-prog-name=ld) in
  /usr/bin/ld|ld) ;;
  *) exit 1 ;;
esac
printf 'int main(void){return 0;}\n' > /tmp/empty.c
"$gcc" -### -O2 -fno-pie -no-pie /tmp/empty.c > /tmp/gcc-hash.txt 2>&1
# Les commandes as/ld sont contrôlées sur la liaison réelle du test C plus bas.
# La ligne « Configured with » de -### ne prouve pas leur exécution.
if grep -E -q -- '-march=native|-mtune=native' /tmp/gcc-v.txt /tmp/gcc-hash.txt; then
  printf 'architecture native refusee\n' >&2
  exit 1
fi

cc1=$("$gcc" -print-prog-name=cc1)
cc1plus=$("$gxx" -print-prog-name=cc1plus)
case $cc1 in
  /opt/stackx-toolchain/*) ;;
  *) exit 1 ;;
esac
case $cc1plus in
  /opt/stackx-toolchain/*) ;;
  *) exit 1 ;;
esac
[[ -x $cc1 && -x $cc1plus ]] || exit 1
"$cc1" -quiet -o /tmp/cc1-out.s /tmp/empty.c
[[ -s /tmp/cc1-out.s ]] || exit 1
printf 'int main(){return 0;}\n' > /tmp/empty.cpp
"$cc1plus" -quiet -o /tmp/cc1plus-out.s /tmp/empty.cpp
[[ -s /tmp/cc1plus-out.s ]] || exit 1

assert_libs() {
  local bin=$1
  local ldd_out soname path
  ldd_out=$("$ldso" --list "$bin" 2>/tmp/ldd.err) || {
    printf 'chargeur en echec\n' >&2
    exit 1
  }
  while IFS= read -r soname; do
    [[ -n $soname ]] || continue
    path=$(printf '%s\n' "$ldd_out" | awk -v s="$soname" '
      index($0, s " => ") {
        line = $0
        sub(/^.*=>[ \t]*/, "", line)
        split(line, a, " ")
        print a[1]
        exit
      }
    ')
    [[ -n $path ]] || {
      printf 'dependance non resolue\n' >&2
      exit 1
    }
    [[ $path == /* ]] || exit 1
    # Résolution dans le rootfs courant, sans suivre un lien depuis l'hôte.
    path=$(readlink -f -- "$path") || exit 1
    [[ -f $path ]] || exit 1
    case $soname in
      libc.so.6|libm.so.6|libdl.so.2|libpthread.so.0|librt.so.1|libz.so.1|ld-linux-x86-64.so.2)
        case $path in
          /lib/*|/lib64/*|/usr/*) ;;
          *) exit 1 ;;
        esac
        ;;
      *)
        case $path in
          /opt/stackx-toolchain/*) ;;
          *) exit 1 ;;
        esac
        ;;
    esac
  done < <(readelf -d "$bin" | awk '/NEEDED/ { gsub(/[\[\]]/, "", $NF); print $NF }')
}

elf_type() {
  readelf -h "$1" > /tmp/elf.type 2>/dev/null || return 1
  awk '/Type:/ { print $2; exit }' /tmp/elf.type
}
elf_interp_ok() {
  local bin=$1
  local interp got exp
  readelf -l "$bin" > /tmp/elf.interp 2>/dev/null || exit 1
  interp=$(awk '
    /interpreter:/ {
      line = $0
      sub(/^.*interpreter:[ \t]*/, "", line)
      sub(/].*$/, "", line)
      gsub(/[ \t]/, "", line)
      print line
      exit
    }
  ' /tmp/elf.interp)
  [[ -n $interp ]] || exit 1
  got=$(readlink -f -- "$interp")
  exp=$(readlink -f -- "$ldso")
  [[ -n $got && $got == "$exp" ]] || exit 1
}

printf '#include <stdio.h>\nint main(void){printf("stackx-c-ok\\n");return 0;}\n' > /tmp/cprog.c
"$gcc" -v -Wl,-v -O2 -fno-pie -no-pie -fno-stack-protector -o /tmp/cprog /tmp/cprog.c \
  > /work/obj/c-link.txt 2>&1
# -Wl,-v transmet la verbosité à collect2 et affiche le ld réellement lancé.
# Seul le premier champ d'une commande compte, jamais les options configure.
for tool in as ld; do
  if ! awk -v expected="/usr/bin/$tool" '
    $1 == expected { seen = 1 }
    END { exit !seen }
  ' /work/obj/c-link.txt; then
    printf 'commande native %s absente de la liaison C, voir c-link.txt\n' "$tool" >&2
    exit 1
  fi
done
c_type=$(elf_type /tmp/cprog) || exit 1
[[ $c_type == EXEC ]] || exit 1
elf_interp_ok /tmp/cprog
c_out=$(/tmp/cprog) || exit 1
[[ $c_out == stackx-c-ok ]] || exit 1
assert_libs /tmp/cprog

printf '#include <iostream>\nint main(){std::cout<<"stackx-cxx-ok\\n";return 0;}\n' > /tmp/cxxprog.cpp
"$gxx" -O2 -fno-pie -no-pie \
  -Wl,-rpath,/opt/stackx-toolchain/lib64 \
  -Wl,-rpath,/opt/stackx-toolchain/lib \
  -o /tmp/cxxprog /tmp/cxxprog.cpp
cxx_type=$(elf_type /tmp/cxxprog) || exit 1
[[ $cxx_type == EXEC ]] || exit 1
elf_interp_ok /tmp/cxxprog
cxx_out=$(/tmp/cxxprog) || exit 1
[[ $cxx_out == stackx-cxx-ok ]] || exit 1
assert_libs /tmp/cxxprog
assert_libs "$gcc"
assert_libs "$gxx"
assert_libs "$cc1"
assert_libs "$cc1plus"
cxx_std=$(readelf -d /tmp/cxxprog | awk '/NEEDED/ { gsub(/[\[\]]/, "", $NF); if ($NF == "libstdc++.so.6") found = 1 } END { print found + 0 }')
[[ $cxx_std == 1 ]] || {
  printf 'libstdc++ absente du programme C++\n' >&2
  exit 1
}

cat > /tmp/sls.c <<'END'
int plus(int x) { return x + 1; }
int main(void) { return plus(41) == 42 ? 0 : 1; }
END
"$gcc" -S -O2 -fno-pie -fno-stack-protector -mharden-sls=all -Werror -o /tmp/sls.s /tmp/sls.c
awk '
  $1 == "ret" || $1 == "retq" { seen = 1; n = 0; next }
  seen { n++ }
  seen && $1 == "int3" { ok = 1; exit }
  seen && n > 3 { exit 1 }
  END { exit ok ? 0 : 1 }
' /tmp/sls.s
"$gcc" -O2 -fno-pie -no-pie -fno-stack-protector -mharden-sls=all -Werror -o /tmp/sls /tmp/sls.c
sls_type=$(elf_type /tmp/sls) || exit 1
[[ $sls_type == EXEC ]] || exit 1
/tmp/sls

cat > /tmp/z.c <<'END'
extern void use(void *);
void foo(void) {
  char buf[32];
  use(buf);
}
END
"$gcc" -S -O2 -fno-pie -fno-stack-protector -ftrivial-auto-var-init=uninitialized -Werror -o /tmp/z-off.s /tmp/z.c
"$gcc" -S -O2 -fno-pie -fno-stack-protector -ftrivial-auto-var-init=zero -Werror -o /tmp/z-on.s /tmp/z.c
z_off=$(cksum < /tmp/z-off.s)
z_on=$(cksum < /tmp/z-on.s)
if [[ $z_off == "$z_on" ]]; then
  printf 'initialisation de pile sans effet\n' >&2
  exit 1
fi
grep -E -q 'pxor|xorl|stos|[$]0' /tmp/z-on.s
if grep -E -q 'pxor|rep[[:space:]]+stos' /tmp/z-off.s; then
  printf 'assembleur de controle deja initialise\n' >&2
  exit 1
fi
printf 'int main(void){return 0;}\n' > /tmp/zrun.c
"$gcc" -O2 -fno-pie -no-pie -ftrivial-auto-var-init=zero -Werror -o /tmp/zrun /tmp/zrun.c
z_type=$(elf_type /tmp/zrun) || exit 1
[[ $z_type == EXEC ]] || exit 1
/tmp/zrun

"$gcc" -c -O2 -fno-pie \
  -mindirect-branch=thunk-extern \
  -mindirect-branch-register \
  -mfunction-return=thunk-extern \
  -mharden-sls=all \
  -ftrivial-auto-var-init=zero \
  -fstack-protector-strong \
  -m64 -mcmodel=kernel -mno-red-zone -mno-sse -ffreestanding \
  -o /tmp/k.o /tmp/empty.c
"$gcc" -c -m16 -march=i386 -ffreestanding -fno-stack-protector -fno-pic -o /tmp/m16.o /tmp/empty.c

cat > /work/obj/preuve.txt.tmp <<EOF
gcc_full=${full}
gcc_machine=${machine}
binutils=${ld_ver}
binutils_ok=oui
as_prog=/usr/bin/as
ld_prog=/usr/bin/ld
bootstrap_compare=oui
cc1=ok
cc1plus=ok
c_run=ok
cxx_run=ok
c_type=EXEC
cxx_type=EXEC
sls=ok
stackzero=ok
m16=ok
kernel_flags=ok
march_native=absent
prefix_libs=oui
wrappers=ok
makeinfo=true
non_pie=oui
EOF
mv -- /work/obj/preuve.txt.tmp /work/obj/preuve.txt
INNER_SCRIPT
chmod 755 "$inner"

keep_first_copy "${reports}/toolchain-build.log"
budget_acquire "$MAX_JOBS"
STACKX_BWRAP_EXTRA=(
  --bind "$src_dir" /work/src
  --bind "$obj_dir" /work/obj
  --ro-bind "$inner" /work/inside.sh
  --bind "$prefix_host" /opt/stackx-toolchain
)
set +e
bwrap_run ro "$workroot" /bin/bash /work/inside.sh "$MAX_JOBS" "$ldso_rel" "$qual_binutils" \
  > "${reports}/toolchain-build.log" 2>&1
build_status=$?
set -e
STACKX_BWRAP_EXTRA=()
if [[ $build_status -ne 0 ]]; then
  record_failure "construction" "bwrap-${build_status}" "$(measured_binutils)"
  budget_release
  die "construction GCC ${GCC_VERSION} interrompue, tentative et journal conservés"
fi

confirm_tools_unchanged
proof=${obj_dir}/preuve.txt
[[ -f $proof && ! -L $proof ]] || {
  record_failure "preuve" "fichier-absent" "$(measured_binutils)"
  budget_release
  die "preuves absentes, tentative conservée"
}
if grep -E -q '/media/|/home/|EXTERNAL_ROOT|SOURCE_ROOT' "$proof"; then
  record_failure "preuve" "chemin-prive" "$(measured_binutils)"
  budget_release
  die "preuve refusée"
fi

require_proof() {
  local key=$1
  local expected=$2
  local got
  got=$(marker_field "$proof" "$key")
  if [[ $got != "$expected" ]]; then
    record_failure "preuve" "$key" "$(measured_binutils)"
    budget_release
    die "preuve refusée : ${key}"
  fi
}

require_proof gcc_full "$GCC_VERSION"
require_proof binutils_ok oui
require_proof as_prog /usr/bin/as
require_proof ld_prog /usr/bin/ld
require_proof bootstrap_compare oui
require_proof cc1 ok
require_proof cc1plus ok
require_proof c_run ok
require_proof cxx_run ok
require_proof c_type EXEC
require_proof cxx_type EXEC
require_proof sls ok
require_proof stackzero ok
require_proof m16 ok
require_proof kernel_flags ok
require_proof march_native absent
require_proof prefix_libs oui
require_proof wrappers ok
require_proof makeinfo true
require_proof non_pie oui
measured=$(marker_field "$proof" binutils)
[[ $measured =~ ^[0-9]+\.[0-9]+(\.[0-9]+)?$ ]] || {
  record_failure "preuve" "binutils" "$(measured_binutils)"
  budget_release
  die "version binutils mesurée illisible"
}
[[ $measured == "$qual_binutils" || $measured == "$qual_binutils".* ]] || {
  record_failure "preuve" "binutils-qualification" "$measured"
  budget_release
  die "binutils mesuré différent de la qualification native"
}
machine=$(marker_field "$proof" gcc_machine)
[[ $machine == x86_64-* ]] || {
  record_failure "preuve" "machine" "$measured"
  budget_release
  die "cible compilateur inattendue"
}
cp -- "$proof" "${reports}/toolchain-preuve.txt"

set +e
bash "${STACKX_DRAFT}/scripts/check-elf-glibc.sh" \
  --sysroot "$workroot" \
  --root "$prefix_host" \
  --ldso "$ldso_rel" \
  --floor "$STACKX_GLIBC_FLOOR" \
  --report "${reports}/toolchain-elf.txt" \
  --expect gcc \
  --allow-prefix "$STACKX_TOOLCHAIN_PREFIX"
elf_status=$?
set -e
if [[ $elf_status -ne 0 ]]; then
  record_failure "elf" "controle-refuse" "$measured"
  budget_release
  die "contrôle ELF/glibc refusé, rapport conservé, toolchain.ok non écrit"
fi

description="GCC ${GCC_VERSION} alternatif, binutils natif ${measured}"
tmp_ok=${reports}/toolchain.ok.tmp
write_kv "$tmp_ok" \
  "kernel=${KERNEL_VERSION}" \
  "dist=${TARGET_DIST}" \
  "abi=${ABI_REV}" \
  "release=${STACKX_RELEASE}" \
  "mode=repli" \
  "decision=repli-possible" \
  "bootstrap=yes" \
  "gcc=${GCC_VERSION}" \
  "binutils=${measured}" \
  "binutils_mode=natif" \
  "prefix=${STACKX_TOOLCHAIN_PREFIX}" \
  "glibc=${STACKX_GLIBC_FLOOR}" \
  "sha256=conforme" \
  "makeinfo=true-documentation-prerendu" \
  "description=${description}"
if grep -E -q '/media/|/home/|EXTERNAL_ROOT|SOURCE_ROOT' "$tmp_ok"; then
  rm -f -- "$tmp_ok"
  record_failure "marqueur" "contenu-refuse" "$measured"
  budget_release
  die "marqueur de repli refusé"
fi
mv -- "$tmp_ok" "$OK_FILE"
budget_release
ok "repli GCC ${GCC_VERSION} construit, binutils natif ${measured}, bootstrap actif. toolchain.ok ne prouve ni la compilation du noyau ni un démarrage."
