pub const REMOTE_PROCESS_IDENTITY_SCRIPT: &str = "# Linux process-group identity shared by launch, timeout cleanup, and recovery.\n# A PID is not durable authority: bind it to the worker boot and leader start.\nset -f\n\nrch_valid_pgid() {\n case \"$1\" in \'\'|*[!0-9]*|0*|1) return 1;; esac\n [ \"${#1}\" -le 10 ] && [ \"$1\" -le 2147483647 ]\n}\n\nrch_valid_boot() {\n case \"$1\" in *[!0-9a-f-]*) return 1;; esac\n case \"$1\" in ????????-????-????-????-????????????) return 0;; esac\n return 1\n}\n\n# /proc can belong to an ancestor PID namespace while kill uses ours. Never\n# interpret that foreign view as ownership, or as proof that a group is empty.\n# NSpid lists IDs from procfs\'s namespace through our active namespace. Require\n# exactly our own ID: equality with just the first ID can occur by coincidence.\n# Use builtin read so /proc/self describes this shell, not a spawned reader.\n# Missing namespace evidence (including older kernels) fails closed.\n# Inside ( ... ) $$ still names the invoking shell while /proc/self is this\n# subshell (the watchdog\'s timer is one), so compare with this process\'s own\n# ID: a command exec\'d from a substitution reports it as its parent. Use an\n# absolute path: launch environments may carry a minimal PATH.\nrch_check_procfs_namespace() {\n rch_self_pid=$(exec /bin/sh -c \'echo \"$PPID\"\') || return 1\n while IFS= read -r rch_status_line; do\n case \"$rch_status_line\" in\n NSpid:*)\n set -- ${rch_status_line#NSpid:}\n [ \"$#\" -eq 1 ] && [ \"$1\" = \"$rch_self_pid\" ]\n return $?;;\n esac\n done < /proc/self/status 2>/dev/null\n return 1\n}\n\n# Read a member of the bound group, including comm with spaces or parentheses.\n# A member may be a non-leader TID: /proc/<tid>/stat has the same birth/group\n# fields. This preserves escalation when a process leader is already a zombie\n# but another thread of that process is still running.\n# Function positional arguments do not replace the launcher\'s command arguments.\nrch_read_process() {\n rch_valid_pgid \"$1\" && rch_check_procfs_namespace || return 1\n rch_proc_pid=$1\n rch_observed_boot=$(cat /proc/sys/kernel/random/boot_id 2>/dev/null) || return 1\n rch_valid_boot \"$rch_observed_boot\" || return 1\n rch_stat=$(cat \"/proc/$rch_proc_pid/stat\" 2>/dev/null) || return 1\n case \"$rch_stat\" in \"$rch_proc_pid (\"*\") \"*) ;; *) return 1;; esac\n rch_stat=${rch_stat##*) }\n set -- $rch_stat\n [ \"$#\" -ge 20 ] && [ \"$3\" = \"$rch_pgid\" ] || return 1\n # Keep the same conservative state grammar as process_identity.rs. An\n # unknown future state is not authority to signal a process group.\n case \"$1\" in R|S|D|T|t|K|W|P|I) ;; *) return 1;; esac\n shift 19\n rch_observed_start=$1\n case \"$rch_observed_start\" in \'\'|*[!0-9]*|0*) return 1;; esac\n [ \"${#rch_observed_start}\" -le 20 ]\n}\n\nrch_read_leader() {\n rch_read_process \"$rch_pgid\"\n}\n\nrch_remote_leader_matches() {\n rch_read_leader || return 1\n [ \"$rch_observed_boot\" = \"$rch_boot\" ] && [ \"$rch_observed_start\" = \"$rch_start\" ]\n}\n\n# Publish before starting any workload. A complete record appears in one rename.\n# The caller must already be the session/process-group leader created by setsid.\nrch_remote_record() {\n rch_pgid=$$\n rch_valid_pgid \"$rch_pgid\" && rch_read_leader || return 1\n rch_boot=$rch_observed_boot\n rch_start=$rch_observed_start\n # Refuse before publishing execution authority or spawning the payload.\n # Scope placement moves this leader, not the workload into a separate tree.\n rch_apply_configured_worker_policy || return 125\n rch_record_tmp=\"$1.$rch_pgid\"\n (umask 077; set -C; printf \'RCH_REMOTE_PROCESS_V1:%s:%s:%s:%s\\n\' \\\n \"$2\" \"$rch_boot\" \"$rch_pgid\" \"$rch_start\" > \"$rch_record_tmp\") || return 1\n mv -f \"$rch_record_tmp\" \"$1\"\n}\n\nrch_read_record() {\n case \"$1\" in /*) ;; *) return 42;; esac\n [ -f \"$1\" ] && [ ! -L \"$1\" ] && [ -r \"$1\" ] || return 42\n rch_record=$(cat \"$1\" 2>/dev/null) || return 42\n rch_identified=0\n case \"$rch_record\" in\n RCH_REMOTE_PROCESS_V1:*)\n rch_fields=${rch_record#RCH_REMOTE_PROCESS_V1:}\n rch_record_build=${rch_fields%%:*}\n [ \"$rch_record_build\" = \"$2\" ] || return 43\n rch_fields=${rch_fields#*:}\n rch_boot=${rch_fields%%:*}\n rch_fields=${rch_fields#*:}\n rch_pgid=${rch_fields%%:*}\n rch_start=${rch_fields#*:}\n rch_valid_boot \"$rch_boot\" || return 42\n case \"$rch_start\" in \'\'|*[!0-9]*|0*) return 42;; esac\n [ \"${#rch_start}\" -le 20 ] || return 42\n [ \"$rch_record\" = \"RCH_REMOTE_PROCESS_V1:$2:$rch_boot:$rch_pgid:$rch_start\" ] || return 42\n # Command substitution strips trailing newlines (and some shells\n # discard NULs). Require the original bytes to be one complete line.\n rch_record_bytes=$(LC_ALL=C wc -c < \"$1\") || return 42\n rch_record_lines=$(LC_ALL=C wc -l < \"$1\") || return 42\n [ \"$rch_record_bytes\" -eq \"$((${#rch_record} + 1))\" ] && \\\n [ \"$rch_record_lines\" -eq 1 ] || return 42\n rch_identified=1\n ;;\n *) rch_pgid=$rch_record;;\n esac\n rch_valid_pgid \"$rch_pgid\" || return 42\n}\n\n# 0: no executing members; 1: live members; 2: observation unavailable.\n# kill -0 cannot distinguish ESRCH from EPERM, or live processes from zombies.\n# A zombie process leader does not prove its other threads have exited. Enumerate\n# LWPs, retaining their own IDs for live escalation witnesses. A terminal-only\n# snapshot must account for every reported thread; unsupported/incomplete thread\n# enumeration is unknown, never a fallback to a process-only empty-group claim.\nrch_group_state() {\n rch_check_procfs_namespace || return 2\n rch_task_snapshot=$(LC_ALL=C ps -e -L -o pid= -o lwp= -o pgid= -o stat= -o nlwp=) || return 2\n rch_snapshot=$(printf \'%s\\n\' \"$rch_task_snapshot\" | LC_ALL=C awk -v group=\"$rch_pgid\" -v observer=\"$$\" \'\n NF != 5 || $1 !~ /^[1-9][0-9]*$/ || $2 !~ /^[1-9][0-9]*$/ || $3 !~ /^[0-9]+$/ || $4 !~ /^[RSDTtKWPIXZx][A-Za-z0-9+<>-]*$/ || $5 !~ /^[1-9][0-9]*$/ { bad = 1; next }\n seen_task[$2]++ { bad = 1 }\n $2 == observer { seen = 1; if ($3 == group) bad = 1 }\n $3 == group {\n members[$1]++\n if ($1 in expected && expected[$1] != $5) uncertain = 1\n expected[$1] = $5\n if ($4 !~ /^[ZXx]/) live = 1\n }\n { print $2, $3, $4 }\n END {\n if (bad || !seen) exit 2\n # Positive liveness needs no complete census. Requiring a stable\n # thread count here would prevent cancelling a busy compiler.\n if (live) exit 1\n for (pid in members) if (members[pid] != expected[pid]) uncertain = 1\n if (uncertain) exit 2\n exit 0\n }\n \')\n return $?\n}\n\n# Capture live members while the recorded leader still authenticates the group.\n# TERM can kill that leader before a child that ignores TERM. A surviving\n# member\'s birth identity lets THIS cancellation finish escalation without\n# treating a historical PGID as authority. These witnesses are never persisted.\nrch_capture_group_witnesses() {\n rch_witnesses=\n rch_witness_identity=\n [ \"$rch_identified\" -eq 1 ] && rch_remote_leader_matches || return 43\n # rch_group_state normalized the validated LWP census to ID/group/state.\n # Retain live TIDs, not just process leaders, with the same birth fencing.\n rch_member_pids=$(printf \'%s\\n\' \"$rch_snapshot\" | LC_ALL=C awk -v group=\"$rch_pgid\" \'\n $2 == group && $3 !~ /^[ZXx]/ { print $1 }\n \') || return 44\n for rch_member_pid in $rch_member_pids; do\n if rch_read_process \"$rch_member_pid\" && [ \"$rch_observed_boot\" = \"$rch_boot\" ]; then\n rch_witnesses=\"$rch_witnesses $rch_member_pid:$rch_observed_start\"\n fi\n done\n # Do not transfer authority across a leader exit/reuse during collection.\n rch_remote_leader_matches || { rch_witnesses=; return 43; }\n rch_witness_identity=\"$rch_boot:$rch_pgid:$rch_start\"\n}\n\nrch_remote_witness_matches() {\n [ \"${rch_witness_identity:-}\" = \"$rch_boot:$rch_pgid:$rch_start\" ] || return 1\n for rch_witness in $rch_witnesses; do\n rch_witness_pid=${rch_witness%%:*}\n rch_witness_start=${rch_witness#*:}\n # rch_read_process also checks current group membership and liveness.\n if rch_read_process \"$rch_witness_pid\" &&\n [ \"$rch_observed_boot\" = \"$rch_boot\" ] &&\n [ \"$rch_observed_start\" = \"$rch_witness_start\" ]; then\n return 0\n fi\n done\n return 1\n}\n\n# Return 43 for unknown/reused identity. A pre-existing orphan is never adopted;\n# only an authenticated TERM in this call can authorize witnessed escalation.\nrch_signal_group() {\n [ \"$rch_identified\" -eq 1 ] || return 43\n if ! rch_remote_leader_matches; then\n [ \"$1\" = KILL ] && [ \"${rch_term_sent:-0}\" -eq 1 ] &&\n rch_remote_witness_matches || return 43\n fi\n # Revalidate immediately before EACH signal. dash requires no `--` here.\n kill -\"$1\" -\"$rch_pgid\" 2>/dev/null || :\n}\n\nrch_remote_cancel() {\n # No authority may leak between calls, even when parsing the next record fails.\n rch_witnesses=\n rch_witness_identity=\n rch_term_sent=0\n rch_read_record \"$1\" \"$2\" || return $?\n rch_mode=$3\n rch_group_state\n case $? in 0) return 0;; 1) ;; *) return 44;; esac\n if [ \"$rch_mode\" = term ]; then\n rch_capture_group_witnesses || return $?\n rch_signal_group TERM || return $?\n rch_term_sent=1\n sleep 1 || return 44\n rch_group_state\n case $? in 0) return 0;; 1) ;; *) return 44;; esac\n fi\n rch_signal_group KILL || return $?\n rch_attempt=0\n while [ \"$rch_attempt\" -lt 20 ]; do\n rch_group_state\n case $? in 0) return 0;; 1) ;; *) return 44;; esac\n rch_attempt=$((rch_attempt + 1))\n sleep 0.1 || return 44\n done\n return 45\n}\n\n# Optional receiver-side execution policy for tracked Linux build payloads.\n# These functions travel with the launch protocol; no worker launcher is needed.\n# Sync, pre-launch package bootstrap, and untracked execution are NOT confined\n# here. This is not by itself admission for a dual-role worker (GH #104).\n#\n# Configure ONE of /etc/rch/worker-execution.conf (root owned) or\n# $HOME/.config/rch/worker-execution.conf (owned by the SSH user or root):\n# version=1\n# nice=10\n# systemd_slice=jobs.slice\n# systemd_manager=user\n# All specified settings are required. Missing policy preserves legacy behavior.\n# Unknown/duplicate keys, an unreadable file, or failed enforcement refuse setup.\n# No policy contents are ever evaluated as shell code.\n\nrch_policy_refuse() {\n printf \'RCH_WORKER_POLICY_REFUSED:%s\\n\' \"$1\" >&2\n return 125\n}\n\nrch_policy_valid_nice() {\n case \"$1\" in 0|[1-9]|1[0-9]|-[1-9]|-1[0-9]|-20) return 0;; esac\n return 1\n}\n\nrch_policy_valid_slice() {\n case \"$1\" in *.slice) rch_policy_slice_base=${1%.slice};; *) return 1;; esac\n [ -n \"$rch_policy_slice_base\" ] && [ \"${#1}\" -le 200 ] || return 1\n case \"$rch_policy_slice_base\" in\n -*|*-|*--*|*[!a-zA-Z0-9_-]*) return 1;;\n esac\n}\n\nrch_policy_select_file() {\n rch_policy_file=\n rch_policy_uid=$(id -u) || { rch_policy_refuse uid; return 125; }\n rch_policy_system=/etc/rch/worker-execution.conf\n rch_policy_user=\n case \"${HOME:-}\" in /*) rch_policy_user=\"$HOME/.config/rch/worker-execution.conf\";; esac\n # An inaccessible containing directory is not evidence of absent policy.\n for rch_policy_dir in /etc/rch \"${HOME:-/}/.config\" \"${HOME:-/}/.config/rch\"; do\n if [ -d \"$rch_policy_dir\" ] && [ ! -x \"$rch_policy_dir\" ]; then\n rch_policy_refuse unreadable_directory; return 125\n fi\n done\n if [ -e \"$rch_policy_system\" ] || [ -L \"$rch_policy_system\" ]; then\n rch_policy_file=$rch_policy_system\n rch_policy_owner=0\n fi\n if [ -n \"$rch_policy_user\" ] && { [ -e \"$rch_policy_user\" ] || [ -L \"$rch_policy_user\" ]; }; then\n [ -z \"$rch_policy_file\" ] || { rch_policy_refuse multiple_policies; return 125; }\n rch_policy_file=$rch_policy_user\n rch_policy_owner=$rch_policy_uid\n fi\n}\n\n# Parse the entire bounded, ASCII data file before changing process attributes.\n# owner is the expected uid; root-owned user policy is also accepted.\nrch_policy_read_file() {\n rch_policy_nice=\n rch_policy_slice=\n rch_policy_manager=user\n rch_policy_seen=\':\'\n [ -f \"$1\" ] && [ ! -L \"$1\" ] && [ -r \"$1\" ] || {\n rch_policy_refuse unreadable_file; return 125;\n }\n rch_policy_stat=$(LC_ALL=C stat -c \'%u:%a:%s:%h\' -- \"$1\") || {\n rch_policy_refuse file_metadata; return 125;\n }\n rch_policy_file_uid=${rch_policy_stat%%:*}\n rch_policy_stat=${rch_policy_stat#*:}\n rch_policy_mode=${rch_policy_stat%%:*}\n rch_policy_stat=${rch_policy_stat#*:}\n rch_policy_size=${rch_policy_stat%%:*}\n rch_policy_links=${rch_policy_stat#*:}\n case \"$rch_policy_mode\" in [0-7][0-7][0-7]) ;; *) rch_policy_refuse file_mode; return 125;; esac\n [ \"$rch_policy_file_uid\" = \"$2\" ] || [ \"$rch_policy_file_uid\" = 0 ] || {\n rch_policy_refuse file_owner; return 125;\n }\n [ \"$((0$rch_policy_mode & 022))\" -eq 0 ] && [ \"$rch_policy_links\" = 1 ] || {\n rch_policy_refuse writable_or_linked_file; return 125;\n }\n [ \"$rch_policy_size\" -le 8192 ] || { rch_policy_refuse file_size; return 125; }\n # Shell read can discard NULs. Validate bytes before parsing, not afterwards.\n LC_ALL=C tr -cd \'\\11\\12\\40-\\176\' < \"$1\" | cmp -s - \"$1\" || {\n rch_policy_refuse file_encoding; return 125;\n }\n while IFS= read -r rch_policy_line || [ -n \"$rch_policy_line\" ]; do\n case \"$rch_policy_line\" in \'\'|\'#\'*) continue;; esac\n case \"$rch_policy_line\" in *=*) ;; *) rch_policy_refuse syntax; return 125;; esac\n rch_policy_key=${rch_policy_line%%=*}\n rch_policy_value=${rch_policy_line#*=}\n case \"$rch_policy_seen\" in *\":$rch_policy_key:\"*) rch_policy_refuse duplicate_key; return 125;; esac\n rch_policy_seen=\"$rch_policy_seen$rch_policy_key:\"\n case \"$rch_policy_key\" in\n version) [ \"$rch_policy_value\" = 1 ] || { rch_policy_refuse version; return 125; };;\n nice)\n rch_policy_valid_nice \"$rch_policy_value\" || { rch_policy_refuse nice; return 125; }\n rch_policy_nice=$rch_policy_value;;\n systemd_slice)\n rch_policy_valid_slice \"$rch_policy_value\" || { rch_policy_refuse slice; return 125; }\n rch_policy_slice=$rch_policy_value;;\n systemd_manager)\n case \"$rch_policy_value\" in user|system) ;; *) rch_policy_refuse manager; return 125;; esac\n rch_policy_manager=$rch_policy_value;;\n *) rch_policy_refuse unknown_key; return 125;;\n esac\n done < \"$1\"\n case \"$rch_policy_seen\" in *:version:*) ;; *) rch_policy_refuse missing_version; return 125;; esac\n [ -n \"$rch_policy_nice$rch_policy_slice\" ] || { rch_policy_refuse empty_policy; return 125; }\n case \"$rch_policy_seen\" in\n *:systemd_manager:*)\n [ -n \"$rch_policy_slice\" ] || { rch_policy_refuse manager_without_slice; return 125; };;\n esac\n}\n\nrch_policy_systemctl() {\n timeout 5s systemctl \"--$rch_policy_manager\" --no-pager --no-ask-password \"$@\"\n}\n\n# Read the launcher\'s actual unified cgroup. A successful D-Bus job submission\n# is not evidence that systemd moved it; cgroup v1/ambiguous evidence is refused.\nrch_policy_current_cgroup() (\n rch_policy_cgroup_count=0\n rch_policy_cgroup_value=\n while IFS= read -r rch_policy_cgroup_line; do\n case \"$rch_policy_cgroup_line\" in\n 0::/*)\n rch_policy_cgroup_count=$((rch_policy_cgroup_count + 1))\n rch_policy_cgroup_value=${rch_policy_cgroup_line#0::};;\n esac\n done < \"/proc/$$/cgroup\"\n [ \"$rch_policy_cgroup_count\" -eq 1 ] || exit 125\n printf \'%s\\n\' \"$rch_policy_cgroup_value\"\n)\n\nrch_policy_enter_scope() {\n command -v busctl >/dev/null 2>&1 && command -v systemctl >/dev/null 2>&1 &&\n command -v timeout >/dev/null 2>&1 || { rch_policy_refuse systemd_tools; return 125; }\n # An operator must start/configure the selected slice. Do not synthesize a\n # missing slice and accidentally run without its intended resource limits.\n [ \"$(rch_policy_systemctl show --value --property=ActiveState -- \"$rch_policy_slice\")\" = active ] || {\n rch_policy_refuse slice_not_active; return 125;\n }\n rch_policy_slice_cgroup=$(rch_policy_systemctl show --value --property=ControlGroup -- \"$rch_policy_slice\") || {\n rch_policy_refuse slice_cgroup; return 125;\n }\n case \"$rch_policy_slice_cgroup\" in /*) ;; *) rch_policy_refuse slice_cgroup; return 125;; esac\n [ \"$rch_policy_slice_cgroup\" != / ] || { rch_policy_refuse root_slice; return 125; }\n rch_policy_scope=\"rch-$rch_boot-$rch_pgid-$rch_start.scope\"\n # Attach the EXISTING setsid leader, rather than starting an independent\n # transient service. All subsequently spawned work keeps the same PGID and\n # stdio, so the existing watchdog, cancellation and recovery remain owners.\n busctl \"--$rch_policy_manager\" --timeout=5s --allow-interactive-authorization=no \\\n call org.freedesktop.systemd1 /org/freedesktop/systemd1 \\\n org.freedesktop.systemd1.Manager StartTransientUnit \'ssa(sv)a(sa(sv))\' \\\n \"$rch_policy_scope\" fail 3 PIDs au 1 \"$rch_pgid\" \\\n Slice s \"$rch_policy_slice\" Description s \'RCH tracked build\' 0 >/dev/null || {\n rch_policy_refuse scope_creation; return 125;\n }\n rch_policy_expected_cgroup=\"$rch_policy_slice_cgroup/$rch_policy_scope\"\n rch_policy_poll=0\n while [ \"$(rch_policy_current_cgroup)\" != \"$rch_policy_expected_cgroup\" ]; do\n rch_policy_poll=$((rch_policy_poll + 1))\n [ \"$rch_policy_poll\" -lt 50 ] || { rch_policy_refuse scope_not_enforced; return 125; }\n sleep 0.1 || { rch_policy_refuse scope_wait; return 125; }\n done\n [ \"$(rch_policy_systemctl show --value --property=ActiveState -- \"$rch_policy_scope\")\" = active ] &&\n [ \"$(rch_policy_systemctl show --value --property=Slice -- \"$rch_policy_scope\")\" = \"$rch_policy_slice\" ] &&\n [ \"$(rch_policy_systemctl show --value --property=ControlGroup -- \"$rch_policy_scope\")\" = \"$rch_policy_expected_cgroup\" ] || {\n rch_policy_refuse scope_evidence; return 125;\n }\n}\n\nrch_policy_enforce() {\n [ \"$(uname -s)\" = Linux ] || { rch_policy_refuse platform; return 125; }\n rch_remote_leader_matches || { rch_policy_refuse process_identity; return 125; }\n if [ -n \"$rch_policy_nice\" ]; then\n # util-linux -n becomes relative with POSIXLY_CORRECT. Clear that flag\n # only in the helper process; never change the build\'s environment.\n (unset POSIXLY_CORRECT; renice -n \"$rch_policy_nice\" -p \"$rch_pgid\") >/dev/null || {\n rch_policy_refuse priority_change; return 125;\n }\n rch_policy_observed_nice=$(LC_ALL=C ps -o ni= -p \"$rch_pgid\") || {\n rch_policy_refuse priority_observation; return 125;\n }\n # ps pads the numeric field; splitting is safe after the numeric check.\n case \"$rch_policy_observed_nice\" in *[!\\ 0-9-]*) rch_policy_refuse priority_observation; return 125;; esac\n set -- $rch_policy_observed_nice\n [ \"$#\" -eq 1 ] && [ \"$1\" = \"$rch_policy_nice\" ] || {\n rch_policy_refuse priority_not_enforced; return 125;\n }\n fi\n rch_policy_scope=\n if [ -n \"$rch_policy_slice\" ]; then\n rch_policy_enter_scope || return 125\n fi\n rch_remote_leader_matches || { rch_policy_refuse process_identity; return 125; }\n printf \'RCH_WORKER_EXECUTION_POLICY:nice=%s,slice=%s,manager=%s,scope=%s\\n\' \\\n \"${rch_policy_nice:-inherit}\" \"${rch_policy_slice:-none}\" \\\n \"$rch_policy_manager\" \"${rch_policy_scope:-none}\" >&2\n}\n\nrch_apply_configured_worker_policy() {\n rch_policy_select_file || return 125\n [ -n \"$rch_policy_file\" ] || return 0\n rch_policy_read_file \"$rch_policy_file\" \"$rch_policy_owner\" || return 125\n rch_policy_enforce\n}\n";Expand description
Shared Linux launch and cancellation protocol; callers select their receipt.