diff --git a/libexec/bklibcvenv b/libexec/bklibcvenv new file mode 100755 index 00000000..5e8618fc --- /dev/null +++ b/libexec/bklibcvenv @@ -0,0 +1,131 @@ +#!/usr/bin/env -S pkgx +nixos.org/patchelf bash +# bklibcvenv — hermetic libc bundler for relocatable libc bottles. +# +# Mirror of libexec/bkpyvenv. Recipe-side helper that makes a libc +# bottle (glibc, eventually musl etc.) ship its bin/* + sbin/* as +# POSIX-sh wrappers that route through the bundled ld.so by relative +# path, so the bottle is fully relocatable. +# +# Usage: +# bklibcvenv seal +# +# Where: +# install root (e.g. {{prefix}}) +# subdir of /lib/ containing libc.so.6 + ld.so +# (e.g. "glibc-2.43"). The script derives: +# LIBC_NAME = libdir-name up to the first '-' +# (so "glibc-2.43" → "glibc") +# LIBC_NAME is used as the libexec subdir prefix +# (libexec/-bin/, libexec/-sbin/) +# so multiple libc bottles can coexist without colliding. +# +# Effect: for each ELF in /{bin,sbin}/* that has a PT_INTERP, +# - move it to /libexec/-/ +# - replace // with a POSIX sh wrapper that: +# * resolves its own bindir (handles invocation by path or PATH) +# * climbs to prefix +# * invokes /lib// --library-path … +# /libexec/-/ "$@" +# +# LDSO is auto-detected from `uname -m`. Override via LDSO env var +# if needed (cross-arch host, exotic loader name, etc.). +# +# Refs: pkgxdev/brewkit#344 (RFC), pkgxdev/pantry@5354c73f (the +# inline-in-glibc-recipe origin of this pattern by @jhheider). + +set -eo pipefail + +if [ $# -lt 1 ]; then + echo "bklibcvenv: missing subcommand" >&2 + echo "usage: $0 seal " >&2 + exit 64 +fi + +CMD=$1 +shift + +# Optional debug tracing — opt-in via env to avoid log noise. +[ -n "${BKLIBCVENV_DEBUG:-}" ] && set -x + +# sed-replacement-safe escape. Backslashes, ampersands, and the +# `|` delimiter need to be quoted in the replacement-half of an +# `s|…|…|g`. Without escaping a value like `foo&bar` would be +# replaced by sed's match-back-reference; `foo\1bar` similarly +# breaks. In practice our values are tightly controlled (LDSO, +# LIBDIR_NAME etc. come from arch/version, never user input), so +# this is defense in depth. +sed_escape() { + printf '%s\n' "$1" | sed -e 's/[\\&|]/\\&/g' +} + +case "$CMD" in + seal) + if [ $# -lt 2 ]; then + echo "bklibcvenv seal: missing args" >&2 + echo "usage: $0 seal " >&2 + exit 64 + fi + + PREFIX=$1 + LIBDIR_NAME=$2 + + # Auto-detect ld.so name from arch. Override-able via LDSO env. + if [ -z "${LDSO:-}" ]; then + case "$(uname -m)" in + x86_64) LDSO=ld-linux-x86-64.so.2 ;; + aarch64|arm64) LDSO=ld-linux-aarch64.so.1 ;; + armv7*|armhf) LDSO=ld-linux-armhf.so.3 ;; + i686|i386) LDSO=ld-linux.so.2 ;; + *) echo "bklibcvenv: unsupported arch $(uname -m); set LDSO env" >&2; exit 1 ;; + esac + fi + + # libc name from libdir's first dash-separated component. + LIBC_NAME=${LIBDIR_NAME%%-*} + + # Locate the wrapper template (share/brewkit/libcvenv-wrapper.sh). + SELF_DIR=$(CDPATH= cd "$(dirname "$0")" && pwd) + TEMPLATE="$SELF_DIR/../share/brewkit/libcvenv-wrapper.sh" + if [ ! -f "$TEMPLATE" ]; then + echo "bklibcvenv: wrapper template not found at $TEMPLATE" >&2 + exit 2 + fi + + # Escape sed-replacement values once up-front. + LDSO_ESC=$(sed_escape "$LDSO") + LIBDIR_ESC=$(sed_escape "$LIBDIR_NAME") + LIBC_ESC=$(sed_escape "$LIBC_NAME") + + for dir in bin sbin; do + [ -d "$PREFIX/$dir" ] || continue + mkdir -p "$PREFIX/libexec/$LIBC_NAME-$dir" + + for f in "$PREFIX/$dir"/*; do + [ -f "$f" ] && [ ! -L "$f" ] || continue + # Skip non-ELF (shell scripts, etc.) — patchelf exits non-zero + # when the file isn't an ELF with a PT_INTERP. + patchelf --print-interpreter "$f" >/dev/null 2>&1 || continue + + name=$(basename "$f") + mv "$f" "$PREFIX/libexec/$LIBC_NAME-$dir/" + + DIR_ESC=$(sed_escape "$dir") + sed \ + -e "s|@LDSO@|$LDSO_ESC|g" \ + -e "s|@LIBDIR@|$LIBDIR_ESC|g" \ + -e "s|@LIBC_NAME@|$LIBC_ESC|g" \ + -e "s|@DIR@|$DIR_ESC|g" \ + "$TEMPLATE" > "$f" + chmod 755 "$f" + + echo "wrapped $f" + done + done + ;; + + *) + echo "bklibcvenv: unknown subcommand '$CMD'" >&2 + echo "usage: $0 seal " >&2 + exit 64 + ;; +esac diff --git a/share/brewkit/libcvenv-wrapper.sh b/share/brewkit/libcvenv-wrapper.sh new file mode 100755 index 00000000..61ae95b3 --- /dev/null +++ b/share/brewkit/libcvenv-wrapper.sh @@ -0,0 +1,20 @@ +#!/bin/sh +# Template for bklibcvenv-generated wrappers. Pure POSIX sh — no +# `--` end-of-options markers (POSIX doesn't specify them for `cd` +# or `command -v`; some old /bin/sh implementations reject them). +# +# Replaced by `bklibcvenv seal`: +# @LDSO@ e.g. ld-linux-x86-64.so.2 +# @LIBDIR@ e.g. glibc-2.43 (subdir of $prefix/lib/) +# @LIBC_NAME@ e.g. glibc (prefix for libexec/-/) +# @DIR@ e.g. bin (or sbin) + +case "$0" in + */*) bindir=${0%/*} ;; + *) bindir=$(command -v "$0"); bindir=${bindir%/*} ;; +esac + +prefix=$(CDPATH= cd "$bindir/.." && pwd) +libdir="$prefix/lib/@LIBDIR@" + +exec "$libdir/@LDSO@" --library-path "$libdir" "$prefix/libexec/@LIBC_NAME@-@DIR@/$(basename "$0")" "$@"