#!/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 atexit
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


def parse_args():
    global bind_mount, bootstrapcmd, prefix, rootdir, 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)
            Executable("mmdebstrap", "mmdebstrap", fatal=True).check()
            bootstrapcmd = shlex.split(args.bootstrapcmd)
            if args.arch != "*":
                bootstrapcmd.append(f"--arch={args.arch}")
            bootstrapcmd.append(args.release)
            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)
            atexit.register(cleanup_tarball)

    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

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

    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):
    tmp = VirtSubproc.downtmp_mktemp(capabilities, path, None)
    return tmp


def hook_revert():
    hook_cleanup()
    hook_open()


def hook_cleanup():
    global capabilities, rootdir

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

    rootdir = None

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


def cleanup_tarball():
    path = pathlib.Path(tarball)
    path.unlink()
    path.parent.rmdir()


def hook_capabilities():
    return capabilities


parse_args()
VirtSubproc.main()
