Files
uupl-smart-contract/tools/process-lib.sh
T

165 lines
5.1 KiB
Bash
Executable File

#!/usr/bin/env bash
demo_repository_root() {
cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd -P
}
demo_is_uint() {
[[ ${1:-} =~ ^[0-9]+$ ]]
}
demo_read_one_line() {
local path=$1 destination=$2 value extra
IFS= read -r value <"$path" || return 1
if IFS= read -r extra < <(sed -n '2p' "$path") && [[ -n "$extra" ]]; then return 1; fi
printf -v "$destination" '%s' "$value"
}
demo_start_tick() {
local pid=$1 stat rest
local -a fields
demo_is_uint "$pid" || return 1
[[ -r "/proc/$pid/stat" ]] || return 1
stat=$(<"/proc/$pid/stat")
rest=${stat##*) }
read -r -a fields <<<"$rest"
demo_is_uint "${fields[19]:-}" || return 1
printf '%s\n' "${fields[19]}"
}
demo_process_group() {
local pid=$1 pgid
pgid=$(ps -o pgid= -p "$pid" 2>/dev/null) || return 1
pgid=${pgid//[[:space:]]/}
demo_is_uint "$pgid" || return 1
printf '%s\n' "$pgid"
}
demo_has_sequence() {
local array_name=$1
local -n argv_ref=$array_name
shift
local -a wanted=("$@")
local i j
for ((i = 0; i + ${#wanted[@]} <= ${#argv_ref[@]}; i++)); do
for ((j = 0; j < ${#wanted[@]}; j++)); do
[[ ${argv_ref[i+j]} == "${wanted[j]}" ]] || break
done
((j == ${#wanted[@]})) && return 0
done
return 1
}
demo_command_matches() {
local pid=$1 kind=$2 argument base found=0
local -a arguments=()
[[ -r "/proc/$pid/cmdline" ]] || return 1
mapfile -d '' -t arguments <"/proc/$pid/cmdline"
((${#arguments[@]} > 0)) || return 1
for argument in "${arguments[@]}"; do
base=${argument##*/}
if [[ "$kind" == anvil && "$base" == anvil ]]; then found=1; fi
if [[ "$kind" == vite && ( "$base" == vite || "$base" == vite.js ) ]]; then found=1; fi
done
((found == 1)) || return 1
if [[ "$kind" == anvil ]]; then
demo_has_sequence arguments --host 127.0.0.1 || return 1
demo_has_sequence arguments --port 8545 || return 1
demo_has_sequence arguments --chain-id 31337 || return 1
elif [[ "$kind" == vite ]]; then
demo_has_sequence arguments web --host 127.0.0.1 --port 5173 || return 1
else
return 1
fi
}
demo_remove_record() {
local root=$1 kind=$2 path
for path in "$root/.demo/$kind.pid" "$root/.demo/$kind.start" "$root/.demo/$kind.pgid"; do
if [[ -e "$path" ]]; then
rm -f -- "$path"
printf 'Removed %s\n' "${path#"$root/"}"
fi
done
}
demo_record_process() {
local root=$1 kind=$2 pid=$3 start pgid
[[ "$kind" == anvil || "$kind" == vite ]] || return 1
demo_is_uint "$pid" || return 1
start=$(demo_start_tick "$pid") || return 1
pgid=$(demo_process_group "$pid") || return 1
[[ "$pid" == "$pgid" ]] || return 1
demo_command_matches "$pid" "$kind" || return 1
mkdir -p "$root/.demo"
chmod 0700 "$root/.demo"
printf '%s\n' "$pid" >"$root/.demo/$kind.pid"
printf '%s\n' "$start" >"$root/.demo/$kind.start"
printf '%s\n' "$pgid" >"$root/.demo/$kind.pgid"
chmod 0600 "$root/.demo/$kind.pid" "$root/.demo/$kind.start" "$root/.demo/$kind.pgid"
}
demo_pid_is_running() {
local pid=$1 stat rest state
{ IFS= read -r stat <"/proc/$pid/stat"; } 2>/dev/null || return 1
rest=${stat##*) }
state=${rest%% *}
[[ "$state" != Z ]]
}
demo_stop_recorded() {
local root=$1 kind=$2 pid start pgid actual_start actual_pgid
local pid_path="$root/.demo/$kind.pid"
local start_path="$root/.demo/$kind.start"
local pgid_path="$root/.demo/$kind.pgid"
[[ "$kind" == anvil || "$kind" == vite ]] || return 1
[[ -e "$pid_path" ]] || return 0
if ! demo_read_one_line "$pid_path" pid || [[ ! "$pid" =~ ^[1-9][0-9]*$ ]] || ((10#$pid <= 1)); then
printf 'Refusing invalid %s PID record\n' "$kind" >&2
return 1
fi
if ! demo_pid_is_running "$pid"; then
printf 'Discarding stale %s process record for PID %s\n' "$kind" "$pid"
demo_remove_record "$root" "$kind"
return 0
fi
if ! demo_read_one_line "$start_path" start || ! demo_is_uint "$start"; then
printf 'Refusing incomplete %s start-tick record\n' "$kind" >&2
return 1
fi
if ! demo_read_one_line "$pgid_path" pgid || [[ ! "$pgid" =~ ^[1-9][0-9]*$ ]] || ((10#$pgid <= 1)); then
printf 'Refusing incomplete %s process-group record\n' "$kind" >&2
return 1
fi
actual_start=$(demo_start_tick "$pid") || return 1
actual_pgid=$(demo_process_group "$pid") || return 1
if [[ "$actual_start" != "$start" || "$actual_pgid" != "$pgid" || "$pid" != "$pgid" ]]; then
printf 'Refusing %s PID %s: recorded identity does not match\n' "$kind" "$pid" >&2
return 1
fi
if ! demo_command_matches "$pid" "$kind"; then
printf 'Refusing %s PID %s: command signature does not match\n' "$kind" "$pid" >&2
return 1
fi
printf 'Stopping validated %s process group %s\n' "$kind" "$pgid"
kill -TERM -- "-$pgid"
for _ in {1..50}; do
demo_pid_is_running "$pid" || break
sleep 0.1
done
if demo_pid_is_running "$pid"; then
printf 'Escalating validated %s process group %s to KILL\n' "$kind" "$pgid"
kill -KILL -- "-$pgid"
for _ in {1..20}; do
demo_pid_is_running "$pid" || break
sleep 0.1
done
fi
wait "$pid" 2>/dev/null || true
demo_pid_is_running "$pid" && {
printf 'Validated %s process group %s did not stop\n' "$kind" "$pgid" >&2
return 1
}
demo_remove_record "$root" "$kind"
}