magisk-zapret2/tests/shell/hosts-overlay.sh
loop-uh f16766dd56 Add a Quick Settings tile, and stop looking for files that cannot be there
The app manages a service whose only control was the app itself. The
tile starts and stops it from the shade, shows which state is current,
and refuses the tap while a lifecycle transition, module update or
full rollback owns the module — those serialize against the same lock,
so accepting the tap would queue work the shade cannot show.

Two things the tile cannot borrow. It has no lifecycle owner: the
platform destroys the service when the shade closes, while the
transition it started holds a root shell for as long as the module
needs, so the work is scoped to the process instead and the tile is
redrawn from whatever binding is listening when it ends. And a
long-press has nowhere to go unless the app declares the tile's
settings, which is why one landed on the system App info page instead
of the app.

Deciding what a tap does needed care the first version did not have:
the tap marks itself in flight before it can read the status it acts
on, so asking the ordinary projection at that point answered BUSY
because of that very tap and the tile did nothing at all.

Separately, the boot stage no longer looks for a pre-2.3.0 in-tree
hosts overlay. customize.sh carries that content over while the old
tree still exists, which is the only moment it does — every supported
root manager replaces the tree wholesale on the activating boot. The
search was a branch that never ran in the field, and its absence is
now an invariant the test enforces: boot does not touch the module
tree at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-28 15:20:29 +03:00

165 lines
7.3 KiB
Shell
Executable file

#!/bin/sh
set -eu
ROOT=$(CDPATH= cd -- "$(dirname "$0")/../.." && pwd)
SCRIPT="$ROOT/zapret2/scripts/hosts-overlay.sh"
POST_FS_DATA="$ROOT/post-fs-data.sh"
fail() { echo "FAIL: hosts-overlay: $*" >&2; exit 1; }
assert_contains() { grep -Fq -- "$2" "$1" || fail "missing fragment in $1: $2"; }
[ -f "$SCRIPT" ] && [ ! -L "$SCRIPT" ] && [ -x "$SCRIPT" ] ||
fail "hosts-overlay.sh is not a regular executable"
[ -f "$POST_FS_DATA" ] && [ ! -L "$POST_FS_DATA" ] && [ -x "$POST_FS_DATA" ] ||
fail "post-fs-data.sh is not a regular executable"
[ "$(sed -n '1p' "$SCRIPT")" = '#!/system/bin/sh' ] || fail "hosts-overlay shebang"
[ "$(sed -n '1p' "$POST_FS_DATA")" = '#!/system/bin/sh' ] || fail "post-fs-data shebang"
# The boot entry point is the only thing that may publish at boot, and it must
# never fail the stage it blocks.
assert_contains "$POST_FS_DATA" 'hosts-overlay.sh'
assert_contains "$POST_FS_DATA" '--boot'
assert_contains "$POST_FS_DATA" '|| exit 0'
# Everything below publishes root-owned files and asks the script to accept
# them, which is exactly what it refuses to do for anything else. Without uid 0
# the scenarios cannot be set up at all, so say so rather than assert nothing.
if [ "$(id -u)" != 0 ]; then
echo "hosts-overlay: OK (static checks only; scenarios need uid 0)"
exit 0
fi
if [ -n "${Z2_TEST_WORKDIR:-}" ]; then
TMP="$Z2_TEST_WORKDIR/hosts-overlay"
mkdir -p "$TMP"
else
TMP=$(mktemp -d "${Z2_TEST_TMP:-${TMPDIR:-/tmp}}/zapret2-hosts.XXXXXX")
fi
# A failed assertion between mount and umount must not leave the fixture
# mounted, whichever branch created the workdir.
cleanup_fixture() {
cleanup_attempt=0
while [ "$cleanup_attempt" -lt 8 ] && umount "$TMP/system-hosts" 2>/dev/null; do
cleanup_attempt=$((cleanup_attempt + 1))
done
[ -n "${Z2_TEST_WORKDIR:-}" ] || rm -rf "$TMP" 2>/dev/null || :
}
trap cleanup_fixture EXIT HUP INT TERM
SYSTEM_HOSTS="$TMP/system-hosts"
OVERLAY_DIR="$TMP/zapret2-hosts"
MODULES_DIR="$TMP/modules"
mkdir -p "$MODULES_DIR/zapret2"
run_overlay() {
HOSTS_OVERLAY_DIR="$OVERLAY_DIR" SYSTEM_HOSTS="$SYSTEM_HOSTS" \
MODULES_DIR="$MODULES_DIR" sh "$SCRIPT" "$@"
}
machine_field() {
printf '%s\n' "$2" | awk -F'\t' -v key="$1" '$1 == key { print $2 }'
}
reset_fixture() {
# A scenario that published a mount owns the system path until it is
# released; removing the fixture underneath it would fail on a busy inode.
reset_attempt=0
while [ "$reset_attempt" -lt 8 ] && umount "$SYSTEM_HOSTS" 2>/dev/null; do
reset_attempt=$((reset_attempt + 1))
done
rm -rf "$OVERLAY_DIR" "$MODULES_DIR" "$SYSTEM_HOSTS"
mkdir -p "$MODULES_DIR/zapret2"
printf '127.0.0.1\tlocalhost\n' > "$SYSTEM_HOSTS"
}
# --- nothing published: a boot must stay a no-op that still captures the base ---
reset_fixture
run_overlay --boot || fail "boot failed with nothing published"
[ -f "$OVERLAY_DIR/system-hosts.base" ] || fail "boot did not capture the system baseline"
cmp -s "$OVERLAY_DIR/system-hosts.base" "$SYSTEM_HOSTS" ||
fail "captured baseline does not match the system hosts file"
state=$(machine_field Z2_HOSTS_STATE "$(run_overlay --inspect-machine)")
[ "$state" = absent ] || fail "expected absent with nothing published, got $state"
# --- boot never writes into the module's own tree ---
# The installer carries a pre-2.3.0 in-tree overlay across while the old tree
# still exists, and every supported root manager replaces that tree wholesale
# on the activating boot. So this stage has no business reaching into it, and a
# file that is somehow there is left for the manager that owns it.
reset_fixture
mkdir -p "$MODULES_DIR/zapret2/system/etc"
printf '127.0.0.1\tlocalhost\n0.0.0.0\tlegacy.test\n' > "$MODULES_DIR/zapret2/system/etc/hosts"
tree_before=$(find "$MODULES_DIR/zapret2" | sort)
run_overlay --boot >/dev/null 2>&1 || :
[ "$(find "$MODULES_DIR/zapret2" | sort)" = "$tree_before" ] ||
fail "boot mutated the module tree"
grep -Fq 'legacy.test' "$MODULES_DIR/zapret2/system/etc/hosts" ||
fail "boot rewrote a file inside the module tree"
[ ! -e "$OVERLAY_DIR/hosts" ] ||
fail "boot published content it found in the module tree"
# --- another enabled module owning the same path is a conflict, not a race ---
reset_fixture
printf '127.0.0.1\tlocalhost\n0.0.0.0\tblocked.test\n' > "$OVERLAY_DIR-staging" 2>/dev/null || :
mkdir -p "$OVERLAY_DIR"
printf '127.0.0.1\tlocalhost\n0.0.0.0\tblocked.test\n' > "$OVERLAY_DIR/hosts"
chmod 0644 "$OVERLAY_DIR/hosts"
mkdir -p "$MODULES_DIR/bindhosts/system/etc"
: > "$MODULES_DIR/bindhosts/system/etc/hosts"
output=$(run_overlay --inspect-machine)
[ "$(machine_field Z2_HOSTS_STATE "$output")" = conflict ] ||
fail "a foreign hosts module was not reported as a conflict"
[ "$(machine_field Z2_HOSTS_CONFLICT "$output")" = bindhosts ] ||
fail "the conflicting module was not named"
# --- a disabled foreign module owns nothing ---
: > "$MODULES_DIR/bindhosts/disable"
output=$(run_overlay --inspect-machine)
[ "$(machine_field Z2_HOSTS_STATE "$output")" = published ] ||
fail "a disabled foreign module still counted as a conflict"
rm -f "$MODULES_DIR/bindhosts/disable"
# --- clear removes the publication ---
rm -rf "$MODULES_DIR/bindhosts"
run_overlay --clear >/dev/null || fail "clear failed"
[ ! -e "$OVERLAY_DIR/hosts" ] || fail "clear left the published file behind"
# --- an unsafe publication is never mounted ---
reset_fixture
mkdir -p "$OVERLAY_DIR"
ln -s /etc/passwd "$OVERLAY_DIR/hosts"
if run_overlay --boot >/dev/null 2>&1; then fail "a symlinked publication was accepted"; fi
[ "$(machine_field Z2_HOSTS_STATE "$(run_overlay --inspect-machine)")" = absent ] ||
fail "a symlinked publication was not rejected by inspection"
rm -f "$OVERLAY_DIR/hosts"
# --- the mount itself, where the kernel allows one ---
reset_fixture
mkdir -p "$OVERLAY_DIR"
printf '127.0.0.1\tlocalhost\n0.0.0.0\tmounted.test\n' > "$OVERLAY_DIR/hosts"
chmod 0644 "$OVERLAY_DIR/hosts"
if [ "$(id -u)" = 0 ] && mount -o bind "$SYSTEM_HOSTS" "$SYSTEM_HOSTS" 2>/dev/null; then
umount "$SYSTEM_HOSTS" 2>/dev/null || umount -l "$SYSTEM_HOSTS" 2>/dev/null || :
run_overlay --boot || fail "boot did not publish the mount"
grep -Fq 'mounted.test' "$SYSTEM_HOSTS" || fail "the published file is not visible at the system path"
[ "$(machine_field Z2_HOSTS_STATE "$(run_overlay --inspect-machine)")" = mounted ] ||
fail "a live mount was not reported as mounted"
# An atomic rename leaves the mount on the replaced inode; --apply is what
# makes the edit live without a reboot.
printf '127.0.0.1\tlocalhost\n0.0.0.0\tsecond.test\n' > "$OVERLAY_DIR/hosts.tmp"
chmod 0644 "$OVERLAY_DIR/hosts.tmp"
mv -f "$OVERLAY_DIR/hosts.tmp" "$OVERLAY_DIR/hosts"
grep -Fq 'mounted.test' "$SYSTEM_HOSTS" ||
fail "the stale mount unexpectedly followed the rename"
run_overlay --apply >/dev/null || fail "apply failed"
grep -Fq 'second.test' "$SYSTEM_HOSTS" || fail "apply did not republish the edit"
run_overlay --clear >/dev/null || fail "clear failed while mounted"
grep -Fq 'localhost' "$SYSTEM_HOSTS" || fail "clear did not restore the system path"
if grep -Fq 'second.test' "$SYSTEM_HOSTS"; then fail "clear left the publication mounted"; fi
else
echo "hosts-overlay: skipping mount assertions (no bind-mount capability)"
fi
echo "hosts-overlay: OK"