#!/bin/sh # Copyright 2019 Johannes 'josch' Schauer # # Permission is hereby granted, free of charge, to any person obtaining a copy # of this software and associated documentation files (the "Software"), to deal # in the Software without restriction, including without limitation the rights # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell # copies of the Software, and to permit persons to whom the Software is # furnished to do so, subject to the following conditions: # # The above copyright notice and this permission notice shall be included in all # copies or substantial portions of the Software. # This script tests, whether fakeroot can be used to create foreign # architecture chroots. This is to find bugs like #973405 early. set -exu if [ "$(dpkg --print-architecture)" != "amd64" ]; then # creating the bootable qemu machine is only supported for amd64 echo "W: Architecture isn't amd64, skipping test." >&2 exit 77 fi IGNORE_MA_SAME_SKEWS=yes TMPDIR=$(mktemp --directory --tmpdir fakeroot_foreign.XXXXXXXXXX) # current directory is the unpacked sources SRCDIR="$(pwd)" cd "$TMPDIR" # We would like to run mmdebstrap without superuser privileges but we cannot # use fakechroot mode because of #944929, proot mode produces wrong permissions # and unshare mode only works if kernel.unprivileged_userns_clone is set to 1 if [ "$(cat /proc/sys/kernel/unprivileged_userns_clone)" = "1" ]; then MODE="unshare" # the temporary directory might not have read permissions for the # unshared user chmod a+rx "$TMPDIR" else MODE="root" fi if [ ! -e "$TMPDIR/id_rsa" ]; then ssh-keygen -q -t rsa -f "$TMPDIR/id_rsa" -N "" fi cat << SCRIPT > "$TMPDIR/customize.sh" #!/bin/sh set -exu rootfs="\$1" # setup various files in /etc echo host > "\$rootfs/etc/hostname" echo "127.0.0.1 localhost host" > "\$rootfs/etc/hosts" echo "/dev/vda1 / auto errors=remount-ro 0 1" > "\$rootfs/etc/fstab" cat /etc/resolv.conf > "\$rootfs/etc/resolv.conf" # give a trivial password to the root user for easy debugging in case something fails echo root:abcdef | chroot "\$rootfs" /usr/sbin/chpasswd # extlinux config to boot from /dev/vda1 with predictable network interface # naming and a serial console for logging cat << END > "\$rootfs/extlinux.conf" default linux timeout 0 label linux kernel /vmlinuz append initrd=/initrd.img root=/dev/vda1 net.ifnames=0 console=ttyS0 END # network interface config # we can use eth0 because we boot with net.ifnames=0 for predictable interface # names cat << END > "\$rootfs/etc/network/interfaces" auto lo iface lo inet loopback auto eth0 iface eth0 inet dhcp END # copy in the public key mkdir "\$rootfs/root/.ssh" cp "$TMPDIR/id_rsa.pub" "\$rootfs/root/.ssh/authorized_keys" chroot "\$rootfs" chown 0:0 /root/.ssh/authorized_keys chroot "\$rootfs" sh -c "apt-get install --yes --no-install-recommends /debpkg/*.deb" chroot "\$rootfs" sh -c "rm /debpkg/*.deb && rmdir /debpkg" SCRIPT chmod +x "$TMPDIR/customize.sh" include=python3-apt,mmdebstrap,time,arch-test,debootstrap,qemu-user,qemu-user-static,binfmt-support,curl,openssh-server,systemd-sysv,linux-image-amd64,ifupdown,netbase,isc-dhcp-client,udev,policykit-1,fakechroot,dpkg-dev # apt sources.list names must not contain spaces, so we can use a string to # store the list srclist="" for f in /etc/apt/sources.list /etc/apt/sources.list.d/*.list /etc/apt/sources.list.d/*.sources; do if [ ! -e "$f" ]; then continue fi bn=$(basename "$f") # remove all file:// entries as those cannot be accessed from within # the chroot and would break an apt-get update grep -v "file://" "$f" > "$TMPDIR/$bn" || continue srclist="$srclist $bn" done test -n "$srclist" mkdir debpkg env --chdir=debpkg apt-get download libfakeroot fakeroot mmdebstrap --mode=$MODE \ --variant=apt \ --include=$include \ --customize-hook="copy-in debpkg /" \ --customize-hook="$TMPDIR/customize.sh" \ dummy "$TMPDIR/debian.tar" $srclist for f in $srclist; do rm "$TMPDIR/$f" done rm debpkg/*.deb rmdir debpkg # use guestfish to prepare the host system # # - create a single 2G partition and unpack the rootfs tarball into it # - put a syslinux MBR into the first 440 bytes of the drive # - install extlinux and make partition bootable # # useful stuff to debug any errors: # LIBGUESTFS_BACKEND_SETTINGS=force_tcg # libguestfs-test-tool || true # export LIBGUESTFS_DEBUG=1 LIBGUESTFS_TRACE=1 guestfish -N "$TMPDIR/debian.img"=disk:4G -- \ part-disk /dev/sda mbr : \ mkfs ext2 /dev/sda1 : \ mount /dev/sda1 / : \ tar-in "$TMPDIR/debian.tar" / : \ upload /usr/lib/SYSLINUX/mbr.bin /mbr.bin : \ copy-file-to-device /mbr.bin /dev/sda size:440 : \ rm /mbr.bin : \ extlinux / : \ sync : \ umount / : \ part-set-bootable /dev/sda 1 true : \ shutdown # start the host system # prefer using kvm but fall back to tcg if not available # avoid entropy starvation by feeding the crypt system with random bits from /dev/urandom # the default memory size of 128 MiB is not enough for Debian, so we go with 1G # use a virtio network card instead of emulating a real network device # we don't need any graphics # this also multiplexes the console and the monitor to stdio # creates a multiplexed stdio backend connected to the serial port and the qemu # monitor # redirect tcp connections on port 10022 localhost to the host system port 22 # redirect all output to a file # run in the background qemu-system-x86_64 \ -M accel=kvm:tcg \ -no-user-config \ -object rng-random,filename=/dev/urandom,id=rng0 -device virtio-rng-pci,rng=rng0 \ -m 1G \ -net nic,model=virtio \ -nographic \ -serial mon:stdio \ -net user,hostfwd=tcp:127.0.0.1:10022-:22 \ -drive file="$TMPDIR/debian.img",format=raw,if=virtio \ > "$TMPDIR/qemu.log" &1 & # store the pid QEMUPID=$! onerror() { cat --show-nonprinting $TMPDIR/qemu.log # attempt poweroff $ssh -o ConnectTimeout=$TIMEOUT root@localhost systemctl poweroff # give a few seconds for poweroff sleep 10 kill $QEMUPID || true } # show the log and kill qemu in case the script exits first trap onerror EXIT # the default ssh command does not store known hosts and even ignores host keys # it identifies itself with the rsa key generated above # pseudo terminal allocation is disabled or otherwise, programs executed via # ssh might wait for input on stdin of the ssh process ssh="ssh -oUserKnownHostsFile=/dev/null -oStrictHostKeyChecking=no -i "$TMPDIR/id_rsa" -T -p 10022" # we use sleepenh to make sure that we wait the right number of seconds # independent on how long the command took beforehand TIMESTAMP=$(sleepenh 0 || [ $? -eq 1 ]) # the timeout in seconds TIMEOUT=5 # the maximum number of tries NUM_TRIES=20 i=0 while true; do rv=0 $ssh -o ConnectTimeout=$TIMEOUT root@localhost echo success || rv=1 # with an exit code of zero, the ssh connection was successful # and we break out of the loop [ $rv -eq 0 ] && break # if the command before took less than $TIMEOUT seconds, wait the remaining time TIMESTAMP=$(sleepenh $TIMESTAMP $TIMEOUT || [ $? -eq 1 ]); # increment the counter and break out of the loop if we tried # too often i=$((i+1)) if [ $i -ge $NUM_TRIES ]; then break fi done # if all tries were exhausted, the process failed if [ $i -eq $NUM_TRIES ]; then echo "timeout reached: unable to connect to qemu via ssh" exit 1 fi $ssh root@localhost env IGNORE_MA_SAME_SKEWS="$IGNORE_MA_SAME_SKEWS" sh << 'END' set -exu curl --output Release http://deb.debian.org/debian/dists/unstable/Release curl --output Release.gpg http://deb.debian.org/debian/dists/unstable/Release.gpg gpgv --quiet --keyring=/usr/share/keyrings/debian-archive-keyring.gpg Release.gpg Release failed_arches="" for arch in $(sed -ne 's/^Architectures: \(.*\)/\1/p' Release); do if [ "$arch" = amd64 ]; then # only test this for foreign architectures continue fi dpkg --add-architecture "$arch" apt-get update d_h_m=$(dpkg-architecture --host-arch="$arch" -qDEB_HOST_MULTIARCH) ret=0 apt-get --yes install --no-install-recommends \ libfakeroot:amd64 libfakeroot:"$arch" \ libfakechroot:amd64 libfakechroot:"$arch" \ || ret=$? if [ "$ret" -ne 0 ]; then echo "failed installing fakeroot/fakechroot on $arch" >&2 if [ "$IGNORE_MA_SAME_SKEWS" = yes ]; then echo "ignoring M-A version skew" >&2 apt-get remove --purge --yes --allow-remove-essential $(dpkg-query -f '${binary:Package}\n' --show '*:'"$arch") dpkg --remove-architecture "$arch" continue else exit 1 fi fi ret=0 # we could also run debootstrap first with --foreign and then with --second-stage # but on salsaci and debci there is no support for kvm acceleration so # the second stage would be doubly emulated, taking way too much time #fakechroot fakeroot -s fakeroot.environ /usr/sbin/debootstrap --arch="$arch" --foreign --variant=fakechroot sid debian-sid #timeout 10m fakechroot fakeroot -i fakeroot.environ sh -c 'env QEMU_LD_PREFIX="$(pwd)/debian-sid" LD_LIBRARY_PATH="$LD_LIBRARY_PATH:/usr/lib/'"$d_h_m"'/fakechroot:/usr/lib/'"$d_h_m"'/libfakeroot" PATH=/usr/sbin:/usr/bin:/sbin:/bin /usr/sbin/chroot debian-sid /debootstrap/debootstrap --second-stage' || ret=$? #timeout 20m mmdebstrap --arch="$arch" --variant=apt --mode=fakechroot sid debian-sid # for the quickest possible test of foreign architecture chroot, we # set up the most minimal possible chroot using busybox #timeout 20m mmdebstrap \ # --arch="$arch" \ # --variant=custom \ # --mode=fakechroot \ # --include=base-files,base-passwd,busybox,debianutils,dpkg,libc-bin,mawk,tar \ # --setup-hook='echo root:x:0:0:root:/root:/bin/sh > "$1/etc/passwd"' \ # --setup-hook='printf "root:x:0:\nmail:x:8:\nutmp:x:43:\n" > "$1/etc/group"' \ # --extract-hook='chroot "$1" /bin/busybox --install -s' \ # sid debian-sid || ret=$? timeout 20m time mmdebstrap \ --arch="$arch" \ --variant=custom \ --mode=fakechroot \ --include=base-files,base-passwd,coreutils,dash,diffutils,dpkg,grep,libc-bin,sed,tar \ sid debian-sid || ret=$? if [ "$ret" -ne 0 ]; then failed_arches="$failed_arches $arch" fi rm -rf debian-sid fakeroot.environ # juliank: instead of --allow-remove-essential, a future apt version # will add support for '--allow-remove=?architecture(foreign)' # https://salsa.debian.org/apt-team/apt/-/merge_requests/135 apt-get remove --purge --yes --allow-remove-essential $(dpkg-query -f '${binary:Package}\n' --show '*:'"$arch") dpkg --remove-architecture "$arch" done echo "failed architectures: $failed_arches" test -z "$failed_arches" END # shut the system off trap - EXIT $ssh root@localhost systemctl poweroff || true wait $QEMUPID # cleanup for f in debian.tar id_rsa id_rsa.pub \ qemu.log debian.img customize.sh; do rm "$TMPDIR/$f" done rmdir "$TMPDIR"