#!/bin/sh # Materialise a self-contained resolvconf tree under a temporary root. # # resolvconf hardcodes /run/resolvconf, /etc/resolvconf and /lib/resolvconf in # every one of its scripts and offers no override, so the only way to drive the # whole interface without touching the machine's real state -- and the only way # to run the identical scenario natively at build time, where /run/resolvconf is # neither writable nor ours -- is to rewrite those prefixes into a temporary # root. This script is that rewrite. It is run on BOTH sides, from the same file, # so the target and the oracle cannot diverge in how the tree was built. # # Usage: resolvconf-temproot.sh ROOT RESOLVCONF LIST_RECORDS NORMALIZE \ # INTERFACE_ORDER UPDATE_LIBC [POSIX_SH] # # POSIX_SH, when non-empty, replaces the "#!/bin/sh" shebang of the two sh # scripts. The oracle uses it to run under an ash closest to the target's # busybox rather than under whatever the build host happens to point /bin/sh at; # the target passes nothing and keeps its shipped interpreter. set -e fail() { echo "resolvconf-temproot: $*" >&2 ; exit 1 ; } [ $# -ge 6 ] || fail "usage: ROOT RESOLVCONF LIST_RECORDS NORMALIZE INTERFACE_ORDER UPDATE_LIBC [POSIX_SH]" R=$1 ; RCONF=$2 ; LRECS=$3 ; NORM=$4 ; IFORDER=$5 ; ULIBC=$6 ; PSH=${7:-} # The rewrite is a sed s|| with $R on the right-hand side, so a root carrying the # delimiter or a backslash would produce a tree that is wrong rather than a # script that fails. Refuse it here instead. case "$R" in /*) ;; *) fail "ROOT must be absolute: $R" ;; esac case "$R" in *[\|\&\\\"\'\ ]*) fail "ROOT must not contain | & \\ quotes or spaces: $R" ;; esac case "$R" in */) fail "ROOT must not end in a slash: $R" ;; esac for f in "$RCONF" "$LRECS" "$NORM" "$IFORDER" "$ULIBC" ; do [ -r "$f" ] || fail "not readable: $f" done mkdir -p "$R/sbin" "$R/lib/resolvconf" \ "$R/etc/resolvconf/update.d" "$R/etc/resolvconf/resolv.conf.d" \ "$R/etc/default" "$R/run/resolvconf/interface" \ "$R/usr/share/doc/resolvconf" # Every absolute prefix resolvconf reaches out to. /etc/default/resolvconf and # /usr/share/doc/resolvconf are here for the same reason as the other three and # are the two that are easy to miss: update.d/libc sources the first if it is # readable and `resolvconf --version` cats the second, so a tree that left them # alone would read the BUILD HOST's copies on the oracle side and silently take a # different branch from the target. # bin/resolvconf line 132 is `exec run-parts ... /etc/resolvconf/update.d`, and it # resolves run-parts through the PATH the script sets for itself on line 33. The # directory argument is redirected into the temproot below; the program is not, so # on a host with no run-parts in /sbin:/bin:/usr/sbin:/usr/bin the exec fails. The # native sysroot's bindir is appended to that line rather than any tool being # installed on the host. Empty expands to nothing -- never a trailing colon, which # would put the current directory on the PATH of every scenario. RC_SCRIPT_PATH="/sbin:/bin:/usr/sbin:/usr/bin${RC_NATIVE_BINDIR:+:${RC_NATIVE_BINDIR}}" rewrite() { sed -e "s|^PATH=/sbin:/bin:/usr/sbin:/usr/bin\$|PATH=$RC_SCRIPT_PATH|" \ -e "s|/run/resolvconf|$R/run/resolvconf|g" \ -e "s|/lib/resolvconf|$R/lib/resolvconf|g" \ -e "s|/etc/default/resolvconf|$R/etc/default/resolvconf|g" \ -e "s|/etc/resolvconf|$R/etc/resolvconf|g" \ -e "s|/usr/share/doc/resolvconf|$R/usr/share/doc/resolvconf|g" \ -e "s|^ETC=/etc\$|ETC=$R/etc|" \ "$1" } # A prefix the rewrite missed does not fail -- it reaches the REAL /run/resolvconf # and /etc/resolvconf. On the build host that is somebody else's file or a # permission error; on target, running as root, it is a silent write to the # machine's live resolver state and a scenario that no longer proves anything # about isolation. So assert the absence directly: map each rewritten prefix to a # sentinel and then look for any bare survivor. assert_rewritten() { _f=$1 _bare=$(sed -e "s|$R/run/resolvconf|@RUN@|g" \ -e "s|$R/lib/resolvconf|@LIB@|g" \ -e "s|$R/etc/default/resolvconf|@DEF@|g" \ -e "s|$R/etc/resolvconf|@ETCRC@|g" \ -e "s|$R/usr/share/doc/resolvconf|@DOC@|g" \ -e "s|$R/etc|@ETC@|g" \ "$_f" \ | grep -c -F -e '/run/resolvconf' \ -e '/lib/resolvconf' \ -e '/etc/resolvconf' \ -e '/etc/default/resolvconf' \ -e '/usr/share/doc/resolvconf' || :) [ "$_bare" = 0 ] || fail "$_f still carries $_bare unrewritten resolvconf path(s) -- it would read or write the real root" } # The shebang swap is the oracle's only asymmetry and it is deliberate: see the # header. Applied before the path rewrite so neither can see the other's output. set_shebang() { if [ -n "$PSH" ] ; then sed -e "1s|^#!/bin/sh.*\$|#!$PSH|" "$1" else cat "$1" fi } set_shebang "$RCONF" | rewrite - > "$R/sbin/resolvconf" chmod 0755 "$R/sbin/resolvconf" assert_rewritten "$R/sbin/resolvconf" # list-records is #!/bin/bash and stays bash on both sides: it needs nullglob and # extglob, which is the whole of the interface-priority mechanism, and no ash has # them. Only its RUN_DIR and interface-order path move. rewrite "$LRECS" > "$R/lib/resolvconf/list-records" chmod 0755 "$R/lib/resolvconf/list-records" assert_rewritten "$R/lib/resolvconf/list-records" # normalize-resolvconf is #!/bin/sed -f and names no path at all, so it is copied # rather than rewritten -- and copying it is the assertion that it names none. cp "$NORM" "$R/lib/resolvconf/normalize-resolvconf" chmod 0755 "$R/lib/resolvconf/normalize-resolvconf" assert_rewritten "$R/lib/resolvconf/normalize-resolvconf" cp "$IFORDER" "$R/etc/resolvconf/interface-order" chmod 0644 "$R/etc/resolvconf/interface-order" set_shebang "$ULIBC" | rewrite - > "$R/etc/resolvconf/update.d/libc" chmod 0755 "$R/etc/resolvconf/update.d/libc" assert_rewritten "$R/etc/resolvconf/update.d/libc" # ETC=/etc is the one assignment the prefix rules above cannot reach, and it is # the one that decides where the generated resolv.conf symlink is looked for. grep -q "^ETC=$R/etc\$" "$R/etc/resolvconf/update.d/libc" || \ fail "update.d/libc still has ETC=/etc -- the symlink check would read the real /etc" exit 0