#!/bin/bash
# xcache_v6.sh — empirical buddy-mediated cross-cache observation.
#
# Methodology:
#   1. Pre-shrink cg-256 to clean baseline.
#   2. Spawn cg-256 holder (msg_msg × N) — adds many new slab pages.
#   3. Snapshot cg-256 inventory (with holder = max).
#   4. Drain holder + shrink cg-256 → empty slabs return to buddy.
#   5. Snapshot cg-256 inventory (post-shrink = min).
#   6. Diff (with-holder ∖ post-shrink) = pages that traveled to buddy.
#   7. Spray rnd-XX-256 via add_key × M.
#   8. Snapshot rnd-XX-256 inventory.
#   9. Intersection: do any "freed cg" pages now appear in rnd-XX-256?
#
# Key trick: shrink via sysfs (echo 1 > /sys/kernel/slab/$cache/shrink)
# is what forces empty slabs back to buddy. Without this, drained slabs
# just sit on partial-list with inuse=0.

set +e
DBG=/sys/kernel/debug/slab_observe

snapshot_all() {
    local cache=$1 out=$2
    echo "$cache" > "$DBG/target"
    sed -nE 's/^pfn=([0-9a-f]+) addr=([0-9a-f]+).*/\2/p' "$DBG/dump_all" | sort -u > "$out"
}

snapshot_all_rnd() {
    local out=$1
    > "$out"
    for i in $(seq -f "%02g" 0 15); do
        snapshot_all "kmalloc-rnd-${i}-256" /tmp/rnd_${i}.tmp
        cat /tmp/rnd_${i}.tmp >> "$out"
    done
    sort -u "$out" -o "$out"
}

shrink() {
    local cache=$1
    echo fuzz | sudo -S sh -c "echo 1 > /sys/kernel/slab/${cache}/shrink" 2>/dev/null
}

cleanup() {
    [ -n "$CG_REAL_PID" ] && kill -USR1 $CG_REAL_PID 2>/dev/null
    [ -n "$RND_REAL_PID" ] && kill -USR1 $RND_REAL_PID 2>/dev/null
    sleep 1
    [ -n "$CG_REAL_PID" ] && kill -9 $CG_REAL_PID 2>/dev/null
    [ -n "$RND_REAL_PID" ] && kill -9 $RND_REAL_PID 2>/dev/null
    ipcs -q | awk '/^0x/{print $2}' | xargs -I{} ipcrm -q {} 2>/dev/null
}
trap cleanup EXIT

# ============================================================
echo "=== xcache_v6: cg-256 → rnd-XX-256 buddy crossover ==="
echo

ipcs -q | awk '/^0x/{print $2}' | xargs -I{} ipcrm -q {} 2>/dev/null

# Phase 0: clean baseline.
echo "[Phase 0] Pre-shrink to clean baseline…"
shrink kmalloc-cg-256
sleep 1
snapshot_all "kmalloc-cg-256" /tmp/cg_baseline.pages
echo "  cg-256 baseline pages (post-shrink): $(wc -l </tmp/cg_baseline.pages)"
shrink kmalloc-rnd-01-256  # rnd-01 absorbs add_key spray
for i in $(seq -f "%02g" 0 15); do
    shrink "kmalloc-rnd-${i}-256"
done
sleep 1
snapshot_all_rnd /tmp/rnd_baseline.pages
echo "  rnd-XX-256 baseline pages (post-shrink): $(wc -l </tmp/rnd_baseline.pages)"

# Phase 1: cg-256 holder. Use 2048 msgs to force many new slab pages.
echo
echo "[Phase 1] Spawn cg-256 holder (msg_msg × 2048)…"
nohup python3 /tmp/cg256_holder.py 2048 > /tmp/cg.out 2>&1 &
disown
sleep 6
cat /tmp/cg.out
CG_REAL_PID=$(awk '/pid=/{gsub(/pid=/,"",$2); print $2}' /tmp/cg.out)

snapshot_all "kmalloc-cg-256" /tmp/cg_with_holder.pages
echo "  cg-256 with-holder pages: $(wc -l </tmp/cg_with_holder.pages)"
comm -23 /tmp/cg_with_holder.pages /tmp/cg_baseline.pages > /tmp/cg_holder_added.pages
echo "  cg-256 NEW pages from holder: $(wc -l </tmp/cg_holder_added.pages)"

# Phase 2: drain + shrink.
echo
echo "[Phase 2] Drain holder + shrink cg-256…"
kill -USR1 $CG_REAL_PID
sleep 3
sync
shrink kmalloc-cg-256
sleep 1
shrink kmalloc-cg-256  # second pass
sleep 1
snapshot_all "kmalloc-cg-256" /tmp/cg_after_drain.pages
echo "  cg-256 after-drain+shrink pages: $(wc -l </tmp/cg_after_drain.pages)"

# Pages freed by drain+shrink = with-holder ∖ after-drain.
comm -23 /tmp/cg_with_holder.pages /tmp/cg_after_drain.pages > /tmp/cg_freed.pages
echo "  cg-256 pages freed (with-holder ∖ after-drain+shrink): $(wc -l </tmp/cg_freed.pages)"
head -10 /tmp/cg_freed.pages

# Phase 3: spray rnd-XX-256.
echo
echo "[Phase 3] Spray rnd-XX-256 via add_key × 4096…"
nohup python3 /tmp/rnd256_sprayer.py 4096 > /tmp/rnd.out 2>&1 &
disown
sleep 5
cat /tmp/rnd.out
RND_REAL_PID=$(awk '/pid=/{gsub(/pid=/,"",$2); print $2}' /tmp/rnd.out)

snapshot_all_rnd /tmp/rnd_after_spray.pages
echo "  rnd-XX-256 after-spray pages: $(wc -l </tmp/rnd_after_spray.pages)"

comm -23 /tmp/rnd_after_spray.pages /tmp/rnd_baseline.pages > /tmp/rnd_added.pages
echo "  rnd-XX-256 NEW pages from spray (vs baseline): $(wc -l </tmp/rnd_added.pages)"
head -10 /tmp/rnd_added.pages

# Cross-cache intersection.
echo
echo "==============================================="
echo "[INTERSECTION] (cg-256 freed) ∩ (rnd-XX-256 added):"
comm -12 /tmp/cg_freed.pages /tmp/rnd_added.pages | tee /tmp/xcache_hit.pages
hits=$(wc -l < /tmp/xcache_hit.pages)
echo "==============================================="
echo "RESULT: hits=$hits  cg_freed=$(wc -l </tmp/cg_freed.pages)  rnd_added=$(wc -l </tmp/rnd_added.pages)"
if [ "$hits" -gt 0 ]; then
    echo "✓ Empirical proof: $hits page(s) crossed cg-256 → rnd-XX-256 via buddy."
    echo "  Hit rate: $(awk -v h=$hits -v c=$(wc -l </tmp/cg_freed.pages) 'BEGIN{printf "%.1f%%\n", h/c*100}')"
else
    echo "✗ No same-page crossings."
fi

echo
echo "[Per-cache rnd contribution]:"
for i in $(seq -f "%02g" 0 15); do
    n_after=$(wc -l < /tmp/rnd_${i}.tmp)
    [ "$n_after" -gt 0 ] && echo "  rnd-${i}-256: $n_after pages"
done
