mirror of
https://github.com/kuhyx/testsAndMisc.git
synced 2026-11-10 20:27:02 +01:00
The split shipped verbatim-proven and green, but measuring it afterwards
showed the tests barely touched it: monitor.sh 44.44%, the policy probes
7.69%, and the health probes never loaded at all. Every _check_* decides
whether phone enforcement is reported as healthy, so an untested probe is
an enforcement failure that reports itself as fine.
The mock stubs adb_root_shell at the function boundary rather than putting
a fake adb on PATH: the probes never invoke adb, they call a function from
adb_common.sh, so a PATH stub would never be consulted. Dispatch is by
pattern, not exact string, because the real commands embed awk programs
with escaped $2, nested quotes and 2>/dev/null redirections.
Two defects the new tests found:
- _mon_escape_json never escaped double quotes. Written as
${escaped//"/\"}, bash reads the bare quote as opening a quoted string,
so the pattern silently degraded to `/\` with an empty replacement. Any
check message containing a quote produced invalid JSON.
- monitor_print_format_warning's multi-line array literal and
collect_snapshot's `{ ... } >file` group held 12 lines that no test
could ever cover: kcov instruments the continuation lines and the
closing brace, but bash only reports a statement at its first line.
Reproduced in an isolated 9-line case before changing anything.
Assertions are mutation-proofed: 18 mutations of the probes' status
decisions, 18 killed. The first run had one survivor -- the repairable
flag was asserted nowhere -- now pinned in both directions.
monitor.sh 104/104 = 100.00% (was 44.44%)
monitor_checks_policy.sh 65/65 = 100.00% (was 7.69%)
monitor_checks_health.sh 79/79 = 100.00% (was 0%)
test_monitor.sh 82 passed, 0 failed (was 9)
Cases live in two sourced files purely to stay under the 250-line cap;
test_monitor.sh remains the single entry point, so one coverage command
still measures all three subjects. Over-cap count unchanged at 22.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011w2Pj4Ek3ZNMQVYcFHgXDY
217 lines
7.3 KiB
Bash
Executable File
217 lines
7.3 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# lib/monitor.sh — Security and health monitoring for the managed phone.
|
|
# Requires: adb_common.sh sourced, ADB_SERIAL set, backup_manifest.sh sourced.
|
|
set -euo pipefail
|
|
|
|
# Directory of this library, used to locate sibling helper scripts (BASH_SOURCE
|
|
# resolves to monitor.sh even when sourced, so the path is stable).
|
|
_MONITOR_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
readonly _MONITOR_LIB_DIR
|
|
|
|
# Constants read here or by both probe libraries. Those read by exactly one of
|
|
# them live in that file instead: a file must not assign a global it never
|
|
# reads (SC2034), and shellcheck runs without -x so each file stands alone.
|
|
readonly _MONITOR_REMOTE_DIR="/data/local/tmp/focus_mode"
|
|
readonly _MONITOR_HOSTS_TARGET="/system/etc/hosts"
|
|
readonly _MONITOR_LAUNCHER_PACKAGE="com.qqlabs.minimalistlauncher"
|
|
readonly _MONITOR_LAUNCHER_ACTIVITY_FILE="/data/local/tmp/focus_mode/minimalist_launcher.activity"
|
|
readonly _MONITOR_HOSTS_CANDIDATES="/system/etc/hosts /etc/hosts /vendor/etc/hosts /system/system/etc/hosts"
|
|
|
|
# Sourced after the constants above, which the probes read. Both libraries call
|
|
# helpers defined below (_mon_check, _trim_output, _safe_adb_root_output); that
|
|
# is fine because nothing here runs until monitor_collect_snapshot is called.
|
|
# shellcheck source=monitor_checks_health.sh
|
|
. "${_MONITOR_LIB_DIR}/monitor_checks_health.sh"
|
|
# shellcheck source=monitor_checks_policy.sh
|
|
. "${_MONITOR_LIB_DIR}/monitor_checks_policy.sh"
|
|
|
|
_mon_escape_json() {
|
|
local escaped="$1"
|
|
|
|
escaped=${escaped//\\/\\\\}
|
|
# The quote must be backslash-escaped in the pattern. Written bare as
|
|
# ${escaped//"/\\"} bash reads the quote as opening a quoted string, so the
|
|
# pattern silently becomes `/\` with an empty replacement and no quote is
|
|
# ever escaped — which emitted invalid JSON for any message containing one.
|
|
escaped=${escaped//\"/\\\"}
|
|
escaped=${escaped//$'\n'/\\n}
|
|
escaped=${escaped//$'\r'/\\r}
|
|
printf '%s' "${escaped}"
|
|
}
|
|
|
|
_mon_check() {
|
|
local check_name="$1"
|
|
local status="$2"
|
|
local source_cmd="$3"
|
|
local message="$4"
|
|
local repairable="${5:-false}"
|
|
|
|
printf '{"check":"%s","status":"%s","source":"%s","message":"%s","repairable":%s}\n' \
|
|
"$(_mon_escape_json "${check_name}")" \
|
|
"$(_mon_escape_json "${status}")" \
|
|
"$(_mon_escape_json "${source_cmd}")" \
|
|
"$(_mon_escape_json "${message}")" \
|
|
"${repairable}"
|
|
}
|
|
|
|
_safe_adb_root_output() {
|
|
adb_root_shell "$@" 2>/dev/null || true
|
|
}
|
|
|
|
_trim_output() {
|
|
printf '%s' "$1" | tr -d '\r' | sed 's/^[[:space:]]*//; s/[[:space:]]*$//'
|
|
}
|
|
|
|
_monitor_read_pidfile() {
|
|
local pidfile="$1"
|
|
local pid=""
|
|
|
|
pid="$(_trim_output "$(_safe_adb_root_output "if [ -f ${pidfile} ]; then cat ${pidfile}; fi")")"
|
|
printf '%s' "${pid}"
|
|
}
|
|
|
|
_monitor_resolve_hosts_target() {
|
|
local candidate=""
|
|
|
|
if adb_root_shell "test -f ${_MONITOR_HOSTS_TARGET}" >/dev/null 2>&1; then
|
|
printf '%s' "${_MONITOR_HOSTS_TARGET}"
|
|
return 0
|
|
fi
|
|
|
|
for candidate in ${_MONITOR_HOSTS_CANDIDATES}; do
|
|
if adb_root_shell "test -f ${candidate}" >/dev/null 2>&1; then
|
|
printf '%s' "${candidate}"
|
|
return 0
|
|
fi
|
|
done
|
|
|
|
printf ''
|
|
}
|
|
|
|
_monitor_pid_matches_script() {
|
|
local pid="$1"
|
|
local script_name="$2"
|
|
|
|
adb_root_shell "kill -0 ${pid} >/dev/null 2>&1" >/dev/null 2>&1 || return 1
|
|
|
|
adb_root_shell "tr '\\0' ' ' </proc/${pid}/cmdline | grep -q ${script_name}" >/dev/null 2>&1 && return 0
|
|
|
|
# Some Android shells hide/normalize cmdline under su; if PID is alive,
|
|
# trust the pidfile check to avoid false negatives.
|
|
return 0
|
|
}
|
|
|
|
monitor_check_format_indicators() {
|
|
local indicator=""
|
|
local description=""
|
|
local command_text=""
|
|
|
|
for indicator in "${FORMAT_INDICATORS[@]}"; do
|
|
description="${indicator%%|*}"
|
|
command_text="${indicator#*|}"
|
|
if ! adb_root_shell "${command_text}" >/dev/null 2>&1; then
|
|
printf '%s\n' "${description}"
|
|
fi
|
|
done
|
|
}
|
|
|
|
monitor_count_missing_format_indicators() {
|
|
local -a missing_indicators=()
|
|
|
|
mapfile -t missing_indicators < <(monitor_check_format_indicators)
|
|
printf '%s\n' "${#missing_indicators[@]}"
|
|
}
|
|
|
|
monitor_is_formatted() {
|
|
local missing_count=""
|
|
|
|
missing_count="$(monitor_count_missing_format_indicators)"
|
|
[[ "${missing_count}" =~ ^[0-9]+$ ]] || _fatal "Format-detection helper returned a non-numeric count: ${missing_count}"
|
|
(( missing_count >= FORMAT_DETECTION_MIN_MISSING ))
|
|
}
|
|
|
|
# Each array element is appended on its own statement rather than listed
|
|
# across a multi-line literal: kcov instruments the continuation lines but
|
|
# bash only ever reports the statement's first line, so a literal spanning N
|
|
# lines is permanently stuck at 1/N covered however thoroughly it is tested.
|
|
monitor_print_format_warning() {
|
|
local -a missing_indicators=("$@")
|
|
local -a box_lines=()
|
|
local indicator=""
|
|
|
|
box_lines+=("")
|
|
box_lines+=("The following expected components were NOT found:")
|
|
|
|
for indicator in "${missing_indicators[@]}"; do
|
|
box_lines+=(" ✗ ${indicator}")
|
|
done
|
|
|
|
box_lines+=("")
|
|
box_lines+=("This strongly suggests the phone was factory-reset or formatted.")
|
|
box_lines+=("")
|
|
box_lines+=("Next step: run the full recovery workflow:")
|
|
box_lines+=(" ./scripts/run_all/run_phone.sh fresh-phone")
|
|
box_lines+=("")
|
|
box_lines+=("Do NOT run 'auto' mode — it will not restore anything.")
|
|
|
|
_box "PHONE APPEARS TO HAVE BEEN WIPED" "${box_lines[@]}"
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
monitor_collect_snapshot() {
|
|
local snapshot_dir="$1"
|
|
local tmp_checks=""
|
|
local report_path="${snapshot_dir}/report.json"
|
|
|
|
mkdir -p "${snapshot_dir}"
|
|
tmp_checks="$(mktemp)"
|
|
|
|
_check_format_indicators "${tmp_checks}"
|
|
_check_battery "${tmp_checks}"
|
|
_check_storage "${tmp_checks}"
|
|
_check_daemon "focus_daemon" "focus_daemon.sh" "${_MONITOR_REMOTE_DIR}/daemon.pid" "${tmp_checks}"
|
|
_check_hosts_daemon "${tmp_checks}"
|
|
_check_daemon "dns_enforcer" "dns_enforcer.sh" "${_MONITOR_REMOTE_DIR}/dns_enforcer.pid" "${tmp_checks}"
|
|
_check_launcher_daemon "${tmp_checks}"
|
|
_check_hosts_integrity "${tmp_checks}"
|
|
_check_dns "${tmp_checks}"
|
|
_check_launcher "${tmp_checks}"
|
|
_check_companion_app "${tmp_checks}"
|
|
_check_boot_persistence "${tmp_checks}"
|
|
|
|
# Redirected per statement rather than as a `{ ... } >file` group: kcov
|
|
# counts the group's closing brace as an instrumented line that bash never
|
|
# reports, which alone held this file below 100%.
|
|
printf '{"timestamp":"%s","device":"%s","checks":[\n' \
|
|
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$(_mon_escape_json "${ADB_SERIAL}")" >"${report_path}"
|
|
paste -sd ',' "${tmp_checks}" >>"${report_path}"
|
|
printf '\n]}\n' >>"${report_path}"
|
|
|
|
cp "${report_path}" "$(dirname "${snapshot_dir}")/latest.json" 2>/dev/null || true
|
|
rm -f "${tmp_checks}"
|
|
}
|
|
|
|
monitor_print_summary() {
|
|
local snapshot_dir="$1"
|
|
local report_path="${snapshot_dir}/report.json"
|
|
|
|
[[ -f "${report_path}" ]] || {
|
|
_warn "No report found at ${report_path}"
|
|
return 0
|
|
}
|
|
|
|
python "${_MONITOR_LIB_DIR}/monitor_report.py" summary "${report_path}"
|
|
}
|
|
|
|
monitor_severity_exit() {
|
|
local snapshot_dir="$1"
|
|
local report_path="${snapshot_dir}/report.json"
|
|
|
|
[[ -f "${report_path}" ]] || return 0
|
|
|
|
python "${_MONITOR_LIB_DIR}/monitor_report.py" severity "${report_path}"
|
|
}
|