#!/bin/bash
# Regression test for the persist-file "smear" bug.
#
# Before the fix in src/nspersist.c::new_persist():
#   - the persist file was never truncated, so writing a short number
#     over a longer one left trailing bytes (e.g. "42" + "1" -> "12");
#   - mkdir of the new instance dir was unchecked, so a silent failure
#     would still commit the new persist number to disk.
# Either path could leave /var/lsns/<uid>/persist pointing at a
# directory that does not exist, wedging the user until a full restart
# of lsws.  See the customer report on box 006 for the original symptom.
#
# This script drives unmount_ns -a (which is what a graceful reload
# with a config-template change ends up running via ns_init_engine ->
# ns_unpersist_all) against a synthetic uid whose inner umount is
# guaranteed to fail.  That failure used to produce the smear; with
# the fix the persist file must either be removed or must name an
# existing directory.
#
# Must run as root.  Exits 0 on PASS, non-zero on FAIL.

set -u

UNMOUNT_NS="${UNMOUNT_NS:-}"
if [ -z "$UNMOUNT_NS" ]; then
    # Try the usual build locations relative to this script.
    here="$(cd "$(dirname "$0")" && pwd)"
    for cand in \
        "$here/../../../build/support/unmount_ns/unmount_ns" \
        "$here/../../../support/unmount_ns/unmount_ns" \
        "$here/../../../dist/lsns/bin/unmount_ns"; do
        if [ -x "$cand" ]; then UNMOUNT_NS="$cand"; break; fi
    done
fi
if [ -z "$UNMOUNT_NS" ] || [ ! -x "$UNMOUNT_NS" ]; then
    echo "FAIL: unmount_ns binary not found (set UNMOUNT_NS=<path>)" >&2
    exit 2
fi

if [ "$(id -u)" -ne 0 ]; then
    echo "FAIL: must run as root" >&2
    exit 2
fi

# Pick a uid unlikely to collide with anything real on the host.
TUID="${TUID:-77777}"
DIR="/var/lsns/$TUID"

cleanup() {
    rm -rf "$DIR" 2>/dev/null || true
}
trap cleanup EXIT

cleanup
mkdir -p "$DIR"

# Stage: persist file holds "42", dir 42 exists with a regular-file
# "mnt" that umount(2) will refuse (EINVAL: not a mountpoint).  That
# refusal is what causes unpersist_uid to set need_newpersist=1 and
# call new_persist over the still-open persist fd -- the exact path
# that produced the smear pre-fix.
printf '42' > "$DIR/persist"
mkdir "$DIR/42"
: > "$DIR/42/mnt"

echo "=== before unmount_ns ==="
echo "persist contents: [$(cat "$DIR/persist")]"
ls "$DIR"

"$UNMOUNT_NS" -a -v >/dev/null 2>&1 || true

echo "=== after unmount_ns ==="
if [ -f "$DIR/persist" ]; then
    PERSIST_VALUE="$(cat "$DIR/persist")"
    echo "persist contents: [$PERSIST_VALUE]"
else
    PERSIST_VALUE=""
    echo "persist contents: <file removed>"
fi
ls "$DIR" 2>/dev/null || echo "<uid dir removed>"

rc=0

# Check 1: persist file (if present) must name a directory that exists.
if [ -n "$PERSIST_VALUE" ]; then
    if [ ! -d "$DIR/$PERSIST_VALUE" ]; then
        echo "FAIL: persist file claims instance '$PERSIST_VALUE' but $DIR/$PERSIST_VALUE does not exist" >&2
        rc=1
    fi
fi

# Check 2: the specific smear regression -- persist must never contain
# the digits of the old number left behind by a shorter overwrite.
# "12" is what the un-truncated write produced from "42" + "1".
if [ "$PERSIST_VALUE" = "12" ]; then
    echo "FAIL: persist file smeared to '12' (digit of old '42' left over)" >&2
    rc=1
fi

# Check 3: persist value, if present, must be a clean decimal integer
# with no trailing garbage.  Catches any future variant of the same bug.
if [ -n "$PERSIST_VALUE" ]; then
    case "$PERSIST_VALUE" in
        ''|*[!0-9]*)
            echo "FAIL: persist file contains non-numeric content: [$PERSIST_VALUE]" >&2
            rc=1
            ;;
    esac
fi

if [ $rc -eq 0 ]; then
    echo "PASS"
fi
exit $rc
