#!/usr/bin/python3
#
# autopkgtest-virt-unshare is part of autopkgtest
# autopkgtest is a tool for testing Debian binary packages
#
# autopkgtest is Copyright (C) 2006-2007 Canonical Ltd.
# autopkgtest-virt-unshare is Copyright (C) 2016 Johannes Schauer
# autopkgtest-virt-unshare is Copyright (C) 2022 Jochen Sprickerhof
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# See the file CREDITS for a full list of credits information (often
# installed as /usr/share/doc/autopkgtest/CREDITS).

import argparse
import os
import pathlib
import shlex
import sys
import tempfile

sys.path.insert(0, '/usr/share/autopkgtest/lib')
sys.path.insert(0, os.path.join(os.path.dirname(os.path.dirname(
    os.path.abspath(__file__))), 'lib'))

import VirtSubproc
import adtlog

capabilities = [
    'revert',
    'revert-full-system',
    'root-on-testbed',
    'suggested-normal-user=unshare',
]

tarball = None
rootdir = None


def parse_args():
    global tarball, rootdir

    parser = argparse.ArgumentParser()
    parser.add_argument(
        '-a', '--arch', help='Debian name of the architecture', default='*'
    )
    parser.add_argument(
        '-d', '--debug', action='store_true', help='Enable debugging output.'
    )
    parser.add_argument(
        '-p',
        '--prefix',
        help='Prefix for the temporary unpack directory (passed to mkdtemp).',
    )
    parser.add_argument(
        '-r',
        '--release',
        help='The distribution to use Mnemonic: the r is the first letter in "release".',
        default='*',
    )
    parser.add_argument('-t', '--tarball', help='Path to rootfs tarball.')
    parser.add_argument('-u', '--unpack-dir', help='Temporary unpack directory.')

    args = parser.parse_args()

    if args.tarball:
        tarball = args.tarball
    else:
        xdg_cache_home = pathlib.Path('~/.cache/sbuild').expanduser()
        if 'XDG_CACHE_HOME' in os.environ:
            xdg_cache_home = pathlib.Path(os.environ['XDG_CACHE_HOME']).joinpath('/sbuild')

        chroots = xdg_cache_home.glob(f'{args.release}-{args.arch}.t*')

        try:
            tarball = str(next(chroots))
            adtlog.debug(f'Using {tarball=}')
        except StopIteration:
            adtlog.error(f'Unable to find chroot in {xdg_cache_home}')

    if args.debug:
        adtlog.verbosity = 2

    if args.unpack_dir:
        rootdir = args.unpack_dir
        os.makedirs(rootdir)
    else:
        rootdir = tempfile.mkdtemp(prefix=args.prefix)


def hook_open():
    # Unpack the tarball into the new directory.
    # Make sure not to extract any character special files because we cannot
    # mknod.
    shellcommand = """
    tar --exclude=./dev --directory {rootdir} --extract --file {tarball}
    /usr/sbin/useradd --create-home --root {rootdir} unshare
    """.format(rootdir=shlex.quote(rootdir), tarball=tarball)
    VirtSubproc.check_exec(['unshare', '--map-auto', '--map-root-user',
                            'sh', '-c', shellcommand])

    # A shell script that prepares the environment by bind-mounting all the
    # important things.
    # The chmod is done such that somebody accidentally using the chroot
    # without the right bind-mounts will not fill up their disk.
    shellcommand = """
    mkdir -p {rootdir}/dev
    for f in null zero full random urandom tty console ptmx; do
        touch {rootdir}/dev/$f
        chmod -rwx {rootdir}/dev/$f
        mount -o bind /dev/$f {rootdir}/dev/$f
    done
    ln -sfT /proc/self/fd {rootdir}/dev/fd
    mkdir -p {rootdir}/dev/pts
    mount -o bind /dev/pts {rootdir}/dev/pts
    mkdir -p {rootdir}/dev/shm
    mount -t tmpfs tmpfs {rootdir}/dev/shm
    mkdir -p {rootdir}/sys
    mount -o rbind /sys {rootdir}/sys
    exec env -i PATH=/usr/sbin:/usr/bin:/sbin:/bin SHELL=/bin/sh \
            unshare --root {rootdir} --ipc --mount --pid --uts --cgroup --fork --mount-proc "$@"
    """.format(rootdir=shlex.quote(rootdir))
    VirtSubproc.auxverb = ['unshare', '--map-auto', '--map-root-user', '--user',
                           '--mount', 'sh', '-c', shellcommand, '--']


def hook_downtmp(path):
    global capabilities

    tmp = VirtSubproc.downtmp_mktemp(path)

    capabilities.append(f'downtmp-host={rootdir}/{tmp}')

    return tmp


def hook_revert():
    hook_cleanup()
    os.makedirs(rootdir)
    hook_open()


def hook_cleanup():
    global capabilities

    VirtSubproc.check_exec(['unshare', '--map-auto', '--map-root-user', 'rm',
                            '-rf', rootdir])

    capabilities = [c for c in capabilities if not c.startswith('downtmp-host')]


def hook_capabilities():
    return capabilities


parse_args()
VirtSubproc.main()
