Build and install procfd (Rust lsof-like tool) on Debian 11 (Bullseye) ARM64 (RK3588)

Slug: procfd

18862 characters 1889 words
#!/bin/bash set -euo pipefail # install-procfd-rk3588-debian11.sh # # Builds + installs procfd on Debian 11 (Bullseye) ARM64 (RK3588), and patches procfd # to add a CLI flag: # --no-unix-peers # that skips UNIX socket peer endpoint lookup via sock_diag/unix_diag. # # Why: Some vendor kernels (including yours) lack unix_diag/CONFIG_UNIX_DIAG, causing # procfd to fail while collecting UNIX peer endpoints. This flag provides an explicit, # reliable user-controlled fallback. # # Run: execute as a normal user from within ~/ (or any subdirectory under it). Requires sudo. # ---------------------------- # Config # ---------------------------- SRC_DIR_DEFAULT="$HOME/deshaw-procfd" SRC_REPO_URL="https://github.com/deshaw/procfd.git" INSTALL_PATH="/usr/local/bin/procfd" RUSTUP_INIT_URL_AARCH64="https://static.rust-lang.org/rustup/dist/aarch64-unknown-linux-gnu/rustup-init" RUST_2021_MIN_MAJOR=1 RUST_2021_MIN_MINOR=56 SRC_DIR="$SRC_DIR_DEFAULT" DO_CLONE_IF_MISSING="yes" DO_VERIFY="yes" # ---------------------------- # Helpers # ---------------------------- log() { echo "[INFO] $*"; } warn() { echo "[WARN] $*"; } die() { echo "[ERROR] $*" >&2; exit 1; } confirm_yes() { local prompt="$1" local confirmation read -r -p "$prompt (yes/NO): " confirmation [[ "$confirmation" == "yes" ]] || die "Operation cancelled by user." } run_or_die() { local desc="$1"; shift log "$desc" if ! "$@"; then die "Failed: $desc" fi } run_or_warn() { local desc="$1"; shift log "$desc" if ! "$@"; then warn "Non-fatal check failed: $desc" return 0 fi } ensure_in_home_tree() { if [[ "${PWD}" != "${HOME}"* ]]; then die "Please run this script from within your home directory tree (~/...). Current dir: ${PWD}" fi } ensure_not_root() { if [[ "$(id -u)" -eq 0 ]]; then die "Do not run this script as root. Run as a normal user with sudo privileges." fi } ensure_sudo_works() { command -v sudo >/dev/null 2>&1 || die "sudo is required but not found." run_or_die "Validating sudo access (you may be prompted for your password)" sudo -v } ensure_debian11_arm64() { local os_id os_version arch uname_m os_id="$(. /etc/os-release && echo "${ID:-}")" os_version="$(. /etc/os-release && echo "${VERSION_ID:-}")" arch="$(dpkg --print-architecture)" uname_m="$(uname -m)" [[ "$os_id" == "debian" ]] || die "Unsupported OS: expected Debian, got ID=$os_id" [[ "$os_version" == 11* ]] || die "Unsupported Debian version: expected 11.x (Bullseye), got VERSION_ID=$os_version" if [[ "$arch" != "arm64" && "$uname_m" != "aarch64" ]]; then die "Unsupported architecture: expected arm64/aarch64, got dpkg=$arch uname=$uname_m" fi } is_pkg_installed() { local pkg="$1" dpkg -s "$pkg" >/dev/null 2>&1 } apt_install_if_missing() { local pkgs_to_install=() local pkg for pkg in "$@"; do if is_pkg_installed "$pkg"; then log "Package already installed: $pkg" else pkgs_to_install+=("$pkg") fi done if [[ "${#pkgs_to_install[@]}" -gt 0 ]]; then run_or_die "Updating apt package lists" sudo apt-get update run_or_die "Installing packages: ${pkgs_to_install[*]}" sudo apt-get install -y "${pkgs_to_install[@]}" fi } ensure_command() { local cmd="$1" local pkg="$2" if ! command -v "$cmd" >/dev/null 2>&1; then apt_install_if_missing "$pkg" fi command -v "$cmd" >/dev/null 2>&1 || die "Tool '$cmd' still not available after installing '$pkg'." } TEMP_DIR="" cleanup() { if [[ -n "${TEMP_DIR}" && -d "${TEMP_DIR}" ]]; then rm -rf "${TEMP_DIR}" fi } trap cleanup EXIT SIGINT SIGTERM usage() { cat <<'EOF' Usage: ./install-procfd-rk3588-debian11.sh [options] Options: --src-dir PATH Use an existing procfd source directory (default: ~/deshaw-procfd) --no-clone Do not clone if source dir is missing (offline-friendly; will error instead) --skip-verify Skip post-install verification commands -h, --help Show this help EOF } while [[ "${#}" -gt 0 ]]; do case "$1" in --src-dir) shift [[ "${1:-}" ]] || die "--src-dir requires a path" SRC_DIR="$1" ;; --no-clone) DO_CLONE_IF_MISSING="no" ;; --skip-verify) DO_VERIFY="no" ;; -h|--help) usage exit 0 ;; *) die "Unknown option: $1" ;; esac shift done # ---------------------------- # Rust toolchain helpers # ---------------------------- parse_rustc_version() { if ! command -v rustc >/dev/null 2>&1; then return 1 fi local v v="$(rustc --version 2>/dev/null | awk '{print $2}')" [[ -n "$v" ]] || return 1 local major minor patch major="$(echo "$v" | cut -d. -f1)" minor="$(echo "$v" | cut -d. -f2)" patch="$(echo "$v" | cut -d. -f3 | sed 's/[^0-9].*$//')" [[ -n "$major" && -n "$minor" && -n "$patch" ]] || return 1 echo "$major $minor $patch" } rustc_is_new_enough_for_2021() { local major minor patch if ! read -r major minor patch < <(parse_rustc_version); then return 1 fi if (( major > RUST_2021_MIN_MAJOR )); then return 0 fi if (( major < RUST_2021_MIN_MAJOR )); then return 1 fi (( minor >= RUST_2021_MIN_MINOR )) } source_cargo_env_if_present() { if [[ -f "$HOME/.cargo/env" ]]; then # shellcheck disable=SC1090 source "$HOME/.cargo/env" fi export PATH="$HOME/.cargo/bin:/usr/local/bin:$PATH" } install_rust_toolchain_if_needed() { source_cargo_env_if_present if rustc_is_new_enough_for_2021; then log "Found rustc new enough for Rust 2021: $(rustc --version)" return 0 fi if command -v rustc >/dev/null 2>&1; then warn "Found rustc, but it may be too old for Rust 2021: $(rustc --version)" warn "Will install/update Rust via rustup to ensure compatibility." else log "rustc not found; will install Rust via rustup." fi ensure_command curl curl local rustup_init_bin="$TEMP_DIR/rustup-init" run_or_die "Downloading rustup-init (aarch64) to $rustup_init_bin" curl -Lfo "$rustup_init_bin" "$RUSTUP_INIT_URL_AARCH64" [[ -s "$rustup_init_bin" ]] || die "Downloaded rustup-init is empty or missing." run_or_die "Marking rustup-init executable" chmod +x "$rustup_init_bin" confirm_yes "WARNING: This will execute the downloaded Rust installer '$rustup_init_bin' and install/update Rust in your home directory (~/.cargo, ~/.rustup). Continue?" run_or_die "Running rustup-init installer" "$rustup_init_bin" -y --no-modify-path source_cargo_env_if_present command -v rustup >/dev/null 2>&1 || die "rustup not found after installation." run_or_die "Installing stable Rust toolchain" rustup toolchain install stable run_or_die "Setting stable as default toolchain" rustup default stable run_or_die "Printing rustc version" rustc --version rustc_is_new_enough_for_2021 || die "rustc is still not new enough for Rust 2021 after rustup install." } # ---------------------------- # Source tree # ---------------------------- ensure_source_tree() { if [[ -d "$SRC_DIR" && -f "$SRC_DIR/Cargo.toml" ]]; then log "Using existing source directory: $SRC_DIR" return 0 fi if [[ "$DO_CLONE_IF_MISSING" != "yes" ]]; then die "Source directory not found at '$SRC_DIR' and cloning is disabled (--no-clone)." fi ensure_command git git log "Source directory not found. Cloning procfd from upstream into: $SRC_DIR" if [[ -e "$SRC_DIR" && ! -d "$SRC_DIR" ]]; then die "Path exists but is not a directory: $SRC_DIR" fi run_or_die "Cloning repository" git clone "$SRC_REPO_URL" "$SRC_DIR" [[ -f "$SRC_DIR/Cargo.toml" ]] || die "Clone completed but Cargo.toml not found in $SRC_DIR" } # ---------------------------- # Patch: add --no-unix-peers flag and skip peer lookup # ---------------------------- patch_add_no_unix_peers_flag_in_place() { log "Patching source in-place to add --no-unix-peers" [[ -n "$TEMP_DIR" && -d "$TEMP_DIR" ]] || die "Internal error: TEMP_DIR not initialized." ensure_command python3 python3 # Idempotency checks if grep -q "pub no_unix_peers: bool" "$SRC_DIR/src/opts.rs" 2>/dev/null && grep -q "args.no_unix_peers" "$SRC_DIR/src/lib.rs" 2>/dev/null; then log "Patch already present (opts.rs + lib.rs)." return 0 fi # Backups local ts ts="$(date +%Y%m%d-%H%M%S)" run_or_die "Backing up src/opts.rs" cp -a "$SRC_DIR/src/opts.rs" "$SRC_DIR/src/opts.rs.bak.$ts" run_or_die "Backing up src/lib.rs" cp -a "$SRC_DIR/src/lib.rs" "$SRC_DIR/src/lib.rs.bak.$ts" local py="$TEMP_DIR/patch_no_unix_peers.py" cat > "$py" <<'PY' from pathlib import Path import sys root = Path(sys.argv[1]) opts_path = root / "src" / "opts.rs" lib_path = root / "src" / "lib.rs" if not opts_path.exists(): raise SystemExit(f"Missing {opts_path}") if not lib_path.exists(): raise SystemExit(f"Missing {lib_path}") opts = opts_path.read_text(encoding="utf-8") lib = lib_path.read_text(encoding="utf-8") # ---- Patch opts.rs: add no_unix_peers flag ---- if "pub no_unix_peers: bool" not in opts: anchor = "pub pid_only: bool," idx = opts.find(anchor) if idx != -1: line_end = opts.find("\n", idx) if line_end == -1: raise SystemExit("Unexpected opts.rs format (no newline after pid_only).") insert = ( "\n\n #[clap(\n" " long = \"no-unix-peers\",\n" " display_order = 17,\n" " help = \"Skip UNIX socket peer endpoint lookup via sock_diag (useful on kernels without unix_diag)\"\n" " )]\n" " pub no_unix_peers: bool,\n" ) opts = opts[:line_end+1] + insert + opts[line_end+1:] else: # Fallback: insert before the final closing brace of the struct. # This is less precise but avoids failing hard if formatting changes. struct_start = opts.find("pub struct Args") if struct_start == -1: raise SystemExit("Could not find 'pub struct Args' in src/opts.rs") # Find the last occurrence of a standalone '}' after struct start. closing = opts.rfind("}\n") if closing == -1 or closing < struct_start: raise SystemExit("Could not find struct closing brace in src/opts.rs") insert = ( "\n #[clap(\n" " long = \"no-unix-peers\",\n" " display_order = 17,\n" " help = \"Skip UNIX socket peer endpoint lookup via sock_diag (useful on kernels without unix_diag)\"\n" " )]\n" " pub no_unix_peers: bool,\n" ) opts = opts[:closing] + insert + opts[closing:] # ---- Patch lib.rs: guard update_unix_map_with_peer with !args.no_unix_peers ---- needle = "if fd_filter.query_fds() && fd_filter.query_unix_entry(None) {" replacement = "if !args.no_unix_peers && fd_filter.query_fds() && fd_filter.query_unix_entry(None) {" if "args.no_unix_peers" not in lib: if needle not in lib: raise SystemExit("Could not find expected unix peer lookup 'if' line in src/lib.rs to patch.") lib = lib.replace(needle, replacement, 1) opts_path.write_text(opts, encoding="utf-8") lib_path.write_text(lib, encoding="utf-8") PY run_or_die "Applying in-place patch via python3" python3 "$py" "$SRC_DIR" grep -q "pub no_unix_peers: bool" "$SRC_DIR/src/opts.rs" || die "Patch failed: no_unix_peers not found in src/opts.rs" grep -q "args.no_unix_peers" "$SRC_DIR/src/lib.rs" || die "Patch failed: args.no_unix_peers not found in src/lib.rs" log "Patch applied successfully. Backups: src/opts.rs.bak.$ts, src/lib.rs.bak.$ts" } # ---------------------------- # Build + install + verify # ---------------------------- build_procfd() { log "Building procfd (release)" source_cargo_env_if_present command -v cargo >/dev/null 2>&1 || die "cargo not found; Rust toolchain not available." (cd "$SRC_DIR" && run_or_die "Running cargo build --release" cargo build --release) [[ -x "$SRC_DIR/target/release/procfd" ]] || die "Build did not produce expected binary at target/release/procfd" } install_procfd_binary() { local built_bin="$SRC_DIR/target/release/procfd" if [[ -e "$INSTALL_PATH" ]]; then log "Existing install detected: $INSTALL_PATH" run_or_warn "Existing procfd version (best-effort)" "$INSTALL_PATH" --version confirm_yes "WARNING: '$INSTALL_PATH' already exists and will be overwritten. Continue?" fi run_or_die "Installing procfd to $INSTALL_PATH" sudo install -m 0755 "$built_bin" "$INSTALL_PATH" run_or_die "Verifying installed procfd is runnable" "$INSTALL_PATH" --help >/dev/null } detect_base_flags_for_kernel() { # Returns either "" or "--no-unix-peers" based on a real probe run. local errf="$TEMP_DIR/procfd_probe_stderr.txt" : > "$errf" set +e "$INSTALL_PATH" --pid "$$" >/dev/null 2>"$errf" local rc=$? set -e if [[ $rc -eq 0 ]]; then echo "" return 0 fi # If the failure smells like the known unix peer lookup issue, fall back. if grep -qiE "Error collecting socket peer data|diagnostic socket|sock_diag|No such file or directory\s*\(os error 2\)" "$errf"; then warn "Kernel appears to lack UNIX sock_diag support; will use --no-unix-peers by default." echo "--no-unix-peers" return 0 fi # Unknown failure: print stderr and fail. echo "--- procfd probe failed (stderr) ---" >&2 cat "$errf" >&2 echo "-----------------------------------" >&2 die "procfd failed during probe for an unexpected reason." } verify_procfd() { if [[ "$DO_VERIFY" != "yes" ]]; then warn "Skipping verification as requested (--skip-verify)." return 0 fi log "Running procfd verification commands..." local base_flags base_flags="$(detect_base_flags_for_kernel)" local errf="$TEMP_DIR/procfd_verify_stderr.txt" # Basic run : > "$errf" set +e "$INSTALL_PATH" $base_flags --pid "$$" >/dev/null 2>"$errf" local rc=$? set -e if [[ $rc -ne 0 ]]; then echo "--- Basic verification failed (stderr) ---" >&2 cat "$errf" >&2 echo "----------------------------------------" >&2 die "Basic verification failed." fi # JSON output : > "$errf" set +e "$INSTALL_PATH" $base_flags --pid "$$" --json | head -c 200 >/dev/null 2>"$errf" rc=$? set -e if [[ $rc -ne 0 ]]; then echo "--- JSON verification failed (stderr) ---" >&2 cat "$errf" >&2 echo "----------------------------------------" >&2 die "JSON verification failed." fi # PID-only : > "$errf" set +e "$INSTALL_PATH" $base_flags --cmd '/bash/' --pid-only >/dev/null 2>"$errf" rc=$? set -e if [[ $rc -ne 0 ]]; then echo "--- PID-only verification failed (stderr) ---" >&2 cat "$errf" >&2 echo "--------------------------------------------" >&2 die "PID-only verification failed." fi # Best-effort filters run_or_warn "socket-type tcp (no DNS) (best-effort)" bash -c "\"$INSTALL_PATH\" $base_flags --no-dns --socket-type tcp >/dev/null" run_or_warn "socket-domain unix (no DNS) (best-effort)" bash -c "\"$INSTALL_PATH\" $base_flags --no-dns --socket-domain unix >/dev/null" log "Verification complete." log "Recommended run on this device: $INSTALL_PATH $base_flags --pid $$" } # ---------------------------- # Main # ---------------------------- main() { log "Starting procfd install + patch (--no-unix-peers) for Debian 11 ARM64" ensure_not_root ensure_in_home_tree ensure_debian11_arm64 ensure_sudo_works TEMP_DIR="$(mktemp -d)" apt_install_if_missing ca-certificates curl build-essential pkg-config git python3 install_rust_toolchain_if_needed ensure_source_tree patch_add_no_unix_peers_flag_in_place build_procfd install_procfd_binary verify_procfd log "Done." } main "$@"
URL: https://ib.bsb.br/procfd