#!/usr/bin/env bash
#
# Minimal reproduction: a statement timeout turns a harmless ENOSPC wait into
# a torn binlog event, and the server then keeps writing into the same file.
#
# No debug build, no filesystem images, no KILL. One container, ~3 minutes.
#
#   ./minimal-repro.sh 11.8
#   ./minimal-repro.sh 10.6
#
set -uo pipefail
VER="${1:-11.8}"
PW=secret
N=binlog-repro

cleanup() { docker rm -f "$N" >/dev/null 2>&1 || true; }
trap cleanup EXIT
cleanup

echo "=== MariaDB $VER"
docker run -d --name "$N" \
  -e MARIADB_ROOT_PASSWORD="$PW" \
  --tmpfs /smallfs:size=64m,uid=999,gid=999,mode=0755 \
  mariadb:"$VER" \
  --log-bin=/smallfs/master-bin \
  --log-bin-index=/smallfs/master-bin.index \
  --binlog-format=ROW \
  --sync-binlog=1 \
  --binlog-checksum=CRC32 \
  --max-statement-time=10 \
  --binlog-cache-size=134217728 \
  --binlog-stmt-cache-size=134217728 \
  --max-binlog-cache-size=268435456 \
  --tmpdir=/var/lib/mysql \
  --log-error=/var/lib/mysql/error.log >/dev/null

# binlog_cache_size is deliberately larger than the test transaction so the
# cache never spills to disk. Otherwise the buffering fails first and the
# binlog file write - the one that carries MY_WAIT_IF_FULL - is never reached.

printf 'waiting for server '
for i in $(seq 1 60); do
  docker exec "$N" mariadb -uroot -p"$PW" -e "SELECT 1" >/dev/null 2>&1 && break
  printf '.'; sleep 2
done; echo

q() { docker exec -i "$N" mariadb -uroot -p"$PW" "$@"; }

q -e "INSTALL SONAME 'ha_sequence'" 2>/dev/null
q -e "CREATE DATABASE t;
      CREATE TABLE t.big (id INT AUTO_INCREMENT PRIMARY KEY, pad LONGBLOB) ENGINE=InnoDB;
      INSERT INTO t.big (pad) SELECT REPEAT('s',65536) FROM t.seq_1_to_10;"

echo "=== filling the binlog filesystem, leaving 4 MB"
docker exec "$N" bash -c '
  free=$(df -B1 --output=avail /smallfs | tail -1)
  dd if=/dev/zero of=/smallfs/filler bs=1M count=$(((free-4194304)/1048576)) status=none
  df -h /smallfs'

echo "=== 32 MB transaction; max_statement_time=10 fires while it waits for space"
q -e "SET SESSION binlog_format=ROW;
      BEGIN;
      INSERT INTO t.big (pad) SELECT REPEAT('B',1048576) FROM t.seq_1_to_32;
      COMMIT;" 2>&1 | tail -3

echo "=== error log: the wait, then the abort exactly 10s later"
docker exec "$N" grep -iE "Disk is full|Error writing file" /var/lib/mysql/error.log

echo "=== binlog after the tear"
docker exec "$N" bash -c '
  f=/smallfs/master-bin.000002
  echo "  size on disk     : $(stat -c %s $f)"
  echo "  readable up to   : $(mariadb-binlog --verify-binlog-checksum $f 2>/dev/null \
                               | grep -oE "end_log_pos [0-9]+" | tail -1 | awk "{print \$2}")"
  mariadb-binlog --verify-binlog-checksum $f 2>&1 >/dev/null | head -1'

echo "=== freeing space; no restart, no FLUSH, nothing else"
docker exec "$N" rm -f /smallfs/filler
sleep 3

echo "=== the server writes again, into the SAME damaged file"
q -e "INSERT INTO t.big (pad) SELECT REPEAT('z',1024) FROM t.seq_1_to_5;
      SELECT 'write succeeded' AS probe;" 2>&1 | tail -2
docker exec "$N" bash -c '
  f=/smallfs/master-bin.000002
  echo "  size on disk     : $(stat -c %s $f)   <- grew"
  echo "  readable up to   : $(mariadb-binlog --verify-binlog-checksum $f 2>/dev/null \
                               | grep -oE "end_log_pos [0-9]+" | tail -1 | awk "{print \$2}")   <- did not"'

cat <<'EOF'

The file keeps growing while the readable portion does not. Valid transactions
now sit behind a truncated event, in the same file, with no restart and no
rotation in between. A replica cannot skip past the damage without losing the
transactions that follow it.
EOF
