diff --git a/coverage.sh b/coverage.sh index 8df1c51..5046629 100755 --- a/coverage.sh +++ b/coverage.sh @@ -96,6 +96,13 @@ if [ ! -e shared/proxysolver ] || [ proxysolver -nt shared/proxysolver ]; then cp -a /usr/lib/apt/solvers/mmdebstrap-dump-solution shared/proxysolver fi fi +if [ ! -e shared/ldconfig.fakechroot ] || [ ldconfig.fakechroot -nt shared/ldconfig.fakechroot ]; then + if [ -e ./ldconfig.fakechroot ]; then + cp -a ldconfig.fakechroot shared + else + cp -a /usr/libexec/mmdebstrap/ldconfig.fakechroot shared/ldconfig.fakechroot + fi +fi mkdir -p shared/hooks if [ ! -e shared/hooks/setup00-merged-usr.sh ] || [ hooks/setup00-merged-usr.sh -nt shared/hooks/setup00-merged-usr.sh ]; then if [ -e hooks/setup00-merged-usr.sh ]; then @@ -754,8 +761,10 @@ fi runuser -u user -- $CMD --mode=unshare --variant=$variant $DEFAULT_DIST /tmp/debian-chroot-unshare.$format $mirror cmp /tmp/debian-chroot-root.$format /tmp/debian-chroot-unshare.$format rm /tmp/debian-chroot-unshare.$format -case $variant in essential|apt|minbase) - # /etc/ld.so.cache differs with some variants +case $variant in essential|apt|minbase|buildd) + # variants important and standard differ because permissions drwxr-sr-x + # and extended attributes of ./var/log/journal/ cannot be preserved + # in fakechroot mode runuser -u user -- $CMD --mode=fakechroot --variant=$variant $DEFAULT_DIST /tmp/debian-chroot-fakechroot.$format $mirror cmp /tmp/debian-chroot-root.$format /tmp/debian-chroot-fakechroot.$format rm /tmp/debian-chroot-fakechroot.$format diff --git a/ldconfig.fakechroot b/ldconfig.fakechroot new file mode 100755 index 0000000..509263b --- /dev/null +++ b/ldconfig.fakechroot @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +# +# This script is in the public domain +# +# Author: Johannes Schauer Marin Rodrigues +# +# This is command substitution for ldconfig under fakechroot: +# +# export FAKECHROOT_CMD_SUBST=/sbin/ldconfig=/path/to/ldconfig.fakechroot +# +# Statically linked binaries cannot work with fakechroot and thus have to be +# replaced by either /bin/true or a more clever solution like this one. The +# ldconfig command supports the -r option which allows passing a chroot +# directory for ldconfig to work in. This can be used to run ldconfig without +# fakechroot but still let it create /etc/ld.so.cache inside the chroot. +# +# Since absolute symlinks are broken without fakechroot to translate them, +# we read /etc/ld.so.conf and turn all absolute symlink shared libraries into +# relative ones. At program exit, the original state is restored. + + +import os +import sys +import subprocess +import atexit +import glob +from pathlib import Path + +symlinks = [] + + +def restore_symlinks(): + for (link, target, atime, mtime) in symlinks: + link.unlink() + link.symlink_to(target) + os.utime(link, times=None, ns=(atime, mtime), follow_symlinks=False) + + +atexit.register(restore_symlinks) + + +def get_libdirs(chroot, configs): + res = [] + for conf in configs: + for line in (Path(conf)).read_text().splitlines(): + line = line.strip() + if not line: + continue + if line.startswith("#"): + continue + if line.startswith("include "): + assert line.startswith("include /") + res.extend( + get_libdirs(chroot, chroot.glob(line.removeprefix("include /"))) + ) + continue + assert line.startswith("/"), line + line = line.lstrip("/") + if not (chroot / Path(line)).is_dir(): + continue + for f in (chroot / Path(line)).iterdir(): + if not f.is_symlink(): + continue + linktarget = f.readlink() + # make sure that the linktarget is an absolute path inside the + # chroot + if not str(linktarget).startswith("/"): + continue + if chroot not in linktarget.parents: + continue + # store original link so that we can restore it later + symlinks.append( + (f, linktarget, f.lstat().st_atime_ns, f.lstat().st_mtime_ns) + ) + # replace absolute symlink by relative link + relative = os.path.relpath(linktarget, f.parent) + f.unlink() + f.symlink_to(relative) + return res + + +def main(): + if "FAKECHROOT_BASE_ORIG" not in os.environ: + print("FAKECHROOT_BASE_ORIG is not set", file=sys.stderr) + print( + "must be executed under fakechroot using FAKECHROOT_CMD_SUBST", + file=sys.stderr, + ) + sys.exit(1) + + chroot = Path(os.environ["FAKECHROOT_BASE_ORIG"]) + + if not (chroot / "sbin" / "ldconfig").exists(): + sys.exit(0) + + (chroot / "var" / "cache" / "ldconfig").mkdir( + mode=0o700, parents=True, exist_ok=True + ) + + for d in get_libdirs(chroot, [chroot / "etc" / "ld.so.conf"]): + make_relative(d) + + # we add any additional arguments before "-r" such that any other "-r" + # option will be overwritten by the one we set + subprocess.check_call( + [chroot / "sbin" / "ldconfig"] + sys.argv[1:] + ["-r", chroot] + ) + + +if __name__ == "__main__": + main() diff --git a/mmdebstrap b/mmdebstrap index 2fa4f48..86d9ac3 100755 --- a/mmdebstrap +++ b/mmdebstrap @@ -2660,28 +2660,15 @@ sub run_prepare { # /etc/fakechroot/debootstrap.env and # /etc/fakechroot/chroot.env { - # we add "$@" before "-r" such that any other "-r" options will be - # overwritten by the one we set - my $escapedroot = shellescape $options->{root}; - my ($fh, $filename) = tempfile( - "mmdebstrap.ldconfig.XXXXXXXXXXXX", - UNLINK => 1, - TMPDIR => 1 - ); - print $fh <