#!/bin/sh
# Copyright © 2016 Johannes Schauer
# Copyright © 2022 Jochen Sprickerhof
# Copyright © 2022 Simon McVittie
# SPDX-License-Identifier: GPL-2.0-or-later
# shellcheck shell=dash

# A shell script that prepares the environment for autopkgtest-virt-unshare
# by bind-mounting all the important things.

set -eu

rootdir="$1"
shift

bind_mount() {
    local old="$1"
    local new="$2"

    case "$new" in
        (/*)
            ;;
        (*)
            echo "internal error: not an absolute path: $new" >&2
            exit 125
            ;;
    esac
    if test -d "$old"; then
        mkdir -p "$rootdir$new"
        mount -o rbind "$old" "$rootdir$new"
    else
        mkdir -p "$rootdir${new%/*}"
        touch "$rootdir$new"
        chmod -rwx "$rootdir/$new"
        mount -o bind "$old" "$rootdir$new"
    fi
}

while [ "$#" -gt 0 ]; do
    case "$1" in
        (--bind)
            bind_mount "$2" "$3"
            shift 3
            ;;

        (--)
            shift
            break
            ;;

        (*)
            echo "internal error: argument not understood: $1" >&2
            exit 125
            ;;
    esac
done

test -f /etc/resolv.conf && cp /etc/resolv.conf "$rootdir/etc/resolv.conf"

mkdir -p "$rootdir/dev"
for f in null zero full random urandom tty console; do
    touch "$rootdir/dev/$f"
    # The chmod is done such that somebody accidentally using the chroot
    # without the right bind-mounts will not fill up their disk.
    chmod -rwx "$rootdir/dev/$f"
    # silently ignore device files missing outside.
    # we can't print a warning as that breaks the adt protocol
    mount -o bind "/dev/$f" "$rootdir/dev/$f" > /dev/null 2>&1 || true
done
ln -sfT /proc/self/fd "$rootdir/dev/fd"
ln -sfT /proc/self/fd/0 "$rootdir/dev/stdin"
ln -sfT /proc/self/fd/1 "$rootdir/dev/stdout"
ln -sfT /proc/self/fd/2 "$rootdir/dev/stderr"
mkdir -p "$rootdir/dev/pts"
mount -o noexec,nosuid,newinstance,gid=5,mode=620,ptmxmode=666 -t devpts none "$rootdir/dev/pts"
ln -sfT /dev/pts/ptmx "$rootdir/dev/ptmx"
mkdir -p "$rootdir/dev/shm"
mount -t tmpfs tmpfs "$rootdir/dev/shm"
mkdir -p "$rootdir/sys"
mount -o rbind /sys "$rootdir/sys"

# Ideally we would run "$@" directly, but PAM-enabled su from shadow
# through 4.8.1 (provided by login on older Debian/Ubuntu) can mistake
# an adopted orphan's exit status for its command's when running as PID 1.
# Fixed upstream in shadow 4.9 (Debian >= Bookworm, Ubuntu >= Noble),
# see: https://github.com/shadow-maint/shadow/issues/104.
# Workaround: keep a shell as PID 1 to adopt orphans, i.e. replace "$@"
# with: `sh -c '"$@"; exit $?' sh "$@"`.
#
# To avoid lingering process after autopkgtest has been killed:
# setpriv --pdeathsig kills the supervising unshare if its caller exits;
# unshare --kill-child kills the namespace's PID 1 when unshare exits,
# which makes the kernel kill all remaining processes in that PID namespace.
exec env -i PATH=/usr/sbin:/usr/bin:/sbin:/bin SHELL=/bin/sh \
setpriv --pdeathsig SIGKILL \
unshare --root "$rootdir" --ipc --mount --pid --uts --cgroup --fork \
    --kill-child=SIGKILL --mount-proc \
sh -c '"$@"; exit $?' sh "$@"
