#!/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 re
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
from autopkgtest_deps import (
    Executable,
)

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

bind_mount = []
bootstrapcmd = None
prefix = None
rootdir = None
tarball = None
temp_tarball = False


def parse_args():
    global bind_mount, bootstrapcmd, prefix, rootdir, tarball, temp_tarball

    parser = argparse.ArgumentParser()
    parser.add_argument(
        '-a', '--arch', help='Debian name of the architecture', default='*'
    )
    parser.add_argument(
        '-b',
        '--bind',
        action='append',
        nargs=2,
        help='Bind mount a directory from the outside to a mountpoint inside.',
        metavar=('OUTSIDE', 'INSIDE')
    )
    parser.add_argument(
        '-d', '--debug', action='store_true', help='Enable debugging output.'
    )
    parser.add_argument(
        '--bootstrapcmd',
        help='Set alternative chroot bootstrap command (including command line options).',
        default='mmdebstrap --mode=unshare --variant=apt --skip=output/mknod,cleanup/apt/lists '
        '--customize-hook=\'sed "s/^deb /deb-src /" "$1/etc/apt/sources.list" > '
        '"$1/etc/apt/sources.list.d/src.list" && APT_CONFIG=$MMDEBSTRAP_APT_CONFIG apt-get update\'',
    )
    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 re.match(r"^(experimental|rc-buggy|UNRELEASED.*)", args.release):
        args.release = "unstable"
    if match := re.match("^(.*)-(backports|security)$", args.release):
        args.release = match[1]

    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:
            if args.release == "*":
                adtlog.error("No chroot was found and --release was not set.")
                sys.exit(1)
            temp_tarball = True
            Executable('mmdebstrap', 'mmdebstrap', fatal=True).check()
            bootstrapcmd = shlex.split(args.bootstrapcmd)
            if args.arch != '*':
                bootstrapcmd.append(f'--arch={args.arch}')
            bootstrapcmd.append(args.release)

    if args.bind:
        for olddir, newdir in args.bind:
            if not os.path.isdir(olddir):
                adtlog.error(f"{olddir} doesn't exist")
                sys.exit(1)
            if not os.path.isabs(newdir):
                adtlog.error(f"mountpoint {newdir} must be an absolute path inside the chroot")
                sys.exit(1)
        bind_mount = args.bind

    if args.debug:
        adtlog.verbosity = 2

    if args.unpack_dir:
        rootdir = args.unpack_dir

    if args.prefix:
        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.
    global rootdir, tarball

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

    if temp_tarball:
        tarball = os.path.join(tempfile.mkdtemp(), "chroot.tar")
        adtlog.debug(f'Using mmdebstrap to create {tarball=} with {bootstrapcmd=}')
        VirtSubproc.check_exec(bootstrapcmd + [tarball], fail_on_stderr=False)

    srootdir = shlex.quote(rootdir)
    shellcommand = """
    tar --exclude=./dev --directory {rootdir} --extract --file {tarball}
    /usr/sbin/useradd --create-home --no-log-init --prefix {rootdir} unshare
    """.format(rootdir=srootdir, tarball=tarball)
    VirtSubproc.check_exec(['unshare', '--map-auto', '--map-root-user',
                            'sh', '-c', shellcommand])

    argv = [
        'unshare',
        '--map-auto',
        '--map-root-user',
        '--user',
        '--mount',
        '--',
        os.path.join(VirtSubproc.PKGDATADIR, 'lib', 'unshare-helper'),
        rootdir,
    ]

    for olddir, newdir in bind_mount:
        argv.extend(['--bind', olddir, newdir])

    argv.append('--')

    adtlog.debug('auxverb is: %s' % argv)
    VirtSubproc.auxverb = argv


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(True)
    hook_open()


def hook_cleanup(revert=False):
    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')]

    if not revert and temp_tarball:
        path = pathlib.Path(tarball)
        path.unlink()
        path.parent.rmdir()


def hook_capabilities():
    return capabilities


parse_args()
VirtSubproc.main()
