#!/usr/bin/env python3
# -*- coding: utf-8 -*-
# Copyright 2010 The ChromiumOS Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Script to enter the ChromeOS chroot with mounted sources.

This script enters the chroot with mounted sources.
"""


__author__ = "asharif@google.com (Ahmad Sharif)"

import argparse
import getpass
import os
import pwd
import sys

from cros_utils import command_executer
from cros_utils import logger
from cros_utils import misc


class MountPoint(object):
    """Mount point class"""

    def __init__(self, external_dir, mount_dir, owner, options=None):
        self.external_dir = os.path.realpath(external_dir)
        self.mount_dir = os.path.realpath(mount_dir)
        self.owner = owner
        self.options = options

    def CreateAndOwnDir(self, dir_name):
        retv = 0
        if not os.path.exists(dir_name):
            command = "mkdir -p " + dir_name
            command += " || sudo mkdir -p " + dir_name
            retv = command_executer.GetCommandExecuter().RunCommand(command)
        if retv != 0:
            return retv
        pw = pwd.getpwnam(self.owner)
        if os.stat(dir_name).st_uid != pw.pw_uid:
            command = "sudo chown -f " + self.owner + " " + dir_name
            retv = command_executer.GetCommandExecuter().RunCommand(command)
        return retv

    def DoMount(self):
        ce = command_executer.GetCommandExecuter()
        mount_signature = "%s on %s" % (self.external_dir, self.mount_dir)
        command = "mount"
        retv, out, _ = ce.RunCommandWOutput(command)
        if mount_signature not in out:
            retv = self.CreateAndOwnDir(self.mount_dir)
            logger.GetLogger().LogFatalIf(retv, "Cannot create mount_dir!")
            retv = self.CreateAndOwnDir(self.external_dir)
            logger.GetLogger().LogFatalIf(retv, "Cannot create external_dir!")
            retv = self.MountDir()
            logger.GetLogger().LogFatalIf(retv, "Cannot mount!")
            return retv
        else:
            return 0

    def UnMount(self):
        ce = command_executer.GetCommandExecuter()
        return ce.RunCommand("sudo umount %s" % self.mount_dir)

    def MountDir(self):
        command = (
            "sudo mount --bind " + self.external_dir + " " + self.mount_dir
        )
        if self.options == "ro":
            command += " && sudo mount --bind -oremount,ro " + self.mount_dir
        retv = command_executer.GetCommandExecuter().RunCommand(command)
        return retv

    def __str__(self):
        ret = ""
        ret += self.external_dir + "\n"
        ret += self.mount_dir + "\n"
        if self.owner:
            ret += self.owner + "\n"
        if self.options:
            ret += self.options + "\n"
        return ret


def Main(argv, return_output=False):
    """The main function."""

    parser = argparse.ArgumentParser()
    parser.add_argument(
        "-c",
        "--chromeos_root",
        dest="chromeos_root",
        default="../..",
        help="ChromeOS root checkout directory.",
    )
    parser.add_argument(
        "-t",
        "--toolchain_root",
        dest="toolchain_root",
        help="Toolchain root directory.",
    )
    parser.add_argument(
        "-o", "--output", dest="output", help="Toolchain output directory"
    )
    parser.add_argument(
        "--sudo",
        dest="sudo",
        action="store_true",
        default=False,
        help="Run the command with sudo.",
    )
    parser.add_argument(
        "-r",
        "--third_party",
        dest="third_party",
        help="The third_party directory to mount.",
    )
    parser.add_argument(
        "-m",
        "--other_mounts",
        dest="other_mounts",
        help="Other mount points in the form: " "dir:mounted_dir:options",
    )
    parser.add_argument(
        "-s",
        "--mount-scripts-only",
        dest="mount_scripts_only",
        action="store_true",
        default=False,
        help="Mount only the scripts dir, and not the sources.",
    )
    parser.add_argument(
        "passthrough_argv",
        nargs="*",
        help="Command to be executed inside the chroot.",
    )

    options = parser.parse_args(argv)

    chromeos_root = options.chromeos_root

    chromeos_root = os.path.expanduser(chromeos_root)
    if options.toolchain_root:
        options.toolchain_root = os.path.expanduser(options.toolchain_root)

    chromeos_root = os.path.abspath(chromeos_root)

    tc_dirs = []
    if options.toolchain_root is None or options.mount_scripts_only:
        m = "toolchain_root not specified. Will not mount toolchain dirs."
        logger.GetLogger().LogWarning(m)
    else:
        tc_dirs = [
            options.toolchain_root + "/google_vendor_src_branch/gcc",
            options.toolchain_root + "/google_vendor_src_branch/binutils",
        ]

    for tc_dir in tc_dirs:
        if not os.path.exists(tc_dir):
            logger.GetLogger().LogError(
                "toolchain path " + tc_dir + " does not exist!"
            )
            parser.print_help()
            sys.exit(1)

    if not os.path.exists(chromeos_root):
        logger.GetLogger().LogError(
            "chromeos_root " + options.chromeos_root + " does not exist!"
        )
        parser.print_help()
        sys.exit(1)

    if not os.path.exists(chromeos_root + "/src/scripts/build_packages"):
        logger.GetLogger().LogError(
            options.chromeos_root + "/src/scripts/build_packages" " not found!"
        )
        parser.print_help()
        sys.exit(1)

    version_dir = os.path.realpath(
        os.path.expanduser(os.path.dirname(__file__))
    )

    mounted_tc_root = "/usr/local/toolchain_root"
    full_mounted_tc_root = chromeos_root + "/chroot/" + mounted_tc_root
    full_mounted_tc_root = os.path.abspath(full_mounted_tc_root)

    mount_points = []
    for tc_dir in tc_dirs:
        last_dir = misc.GetRoot(tc_dir)[1]
        mount_point = MountPoint(
            tc_dir,
            full_mounted_tc_root + "/" + last_dir,
            getpass.getuser(),
            "ro",
        )
        mount_points.append(mount_point)

    # Add the third_party mount point if it exists
    if options.third_party:
        third_party_dir = options.third_party
        logger.GetLogger().LogFatalIf(
            not os.path.isdir(third_party_dir),
            "--third_party option is not a valid dir.",
        )
    else:
        third_party_dir = os.path.abspath(
            "%s/../../../third_party" % os.path.dirname(__file__)
        )

    if os.path.isdir(third_party_dir):
        mount_point = MountPoint(
            third_party_dir,
            (
                "%s/%s"
                % (full_mounted_tc_root, os.path.basename(third_party_dir))
            ),
            getpass.getuser(),
        )
        mount_points.append(mount_point)

    output = options.output
    if output is None and options.toolchain_root:
        # Mount the output directory at /usr/local/toolchain_root/output
        output = options.toolchain_root + "/output"

    if output:
        mount_points.append(
            MountPoint(
                output, full_mounted_tc_root + "/output", getpass.getuser()
            )
        )

    # Mount the other mount points
    mount_points += CreateMountPointsFromString(
        options.other_mounts, chromeos_root + "/chroot/"
    )

    last_dir = misc.GetRoot(version_dir)[1]

    # Mount the version dir (v14) at /usr/local/toolchain_root/v14
    mount_point = MountPoint(
        version_dir, full_mounted_tc_root + "/" + last_dir, getpass.getuser()
    )
    mount_points.append(mount_point)

    for mount_point in mount_points:
        retv = mount_point.DoMount()
        if retv != 0:
            return retv

    # Finally, create the symlink to build-gcc.
    command = "sudo chown " + getpass.getuser() + " " + full_mounted_tc_root
    retv = command_executer.GetCommandExecuter().RunCommand(command)

    try:
        CreateSymlink(
            last_dir + "/build-gcc", full_mounted_tc_root + "/build-gcc"
        )
        CreateSymlink(
            last_dir + "/build-binutils",
            full_mounted_tc_root + "/build-binutils",
        )
    except Exception as e:
        logger.GetLogger().LogError(str(e))

    # Now call cros_sdk --enter with the rest of the arguments.
    command = "cd %s/src/scripts && cros_sdk --enter" % chromeos_root

    if len(options.passthrough_argv) > 1:
        inner_command = " ".join(options.passthrough_argv[1:])
        inner_command = inner_command.strip()
        if inner_command.startswith("-- "):
            inner_command = inner_command[3:]
        command_file = "tc_enter_chroot.cmd"
        command_file_path = chromeos_root + "/src/scripts/" + command_file
        retv = command_executer.GetCommandExecuter().RunCommand(
            "sudo rm -f " + command_file_path
        )
        if retv != 0:
            return retv
        with open(command_file_path, "w", encoding="utf-8") as f:
            f.write(inner_command)
        logger.GetLogger().LogCmd(inner_command)
        retv = command_executer.GetCommandExecuter().RunCommand(
            "chmod +x " + command_file_path
        )
        if retv != 0:
            return retv

        if options.sudo:
            command += " sudo ./" + command_file
        else:
            command += " ./" + command_file
        retv = command_executer.GetCommandExecuter().RunCommandGeneric(
            command, return_output
        )
        return retv
    else:
        os.chdir("%s/src/scripts" % chromeos_root)
        ce = command_executer.GetCommandExecuter()
        _, out, _ = ce.RunCommandWOutput("which cros_sdk")
        cros_sdk_binary = out.split()[0]
        return os.execv(cros_sdk_binary, ["", "--enter"])


def CreateMountPointsFromString(mount_strings, chroot_dir):
    # String has options in the form dir:mount:options
    mount_points = []
    if not mount_strings:
        return mount_points
    mount_list = mount_strings.split()
    for mount_string in mount_list:
        mount_values = mount_string.split(":")
        external_dir = mount_values[0]
        mount_dir = mount_values[1]
        if len(mount_values) > 2:
            options = mount_values[2]
        else:
            options = None
        mount_point = MountPoint(
            external_dir,
            chroot_dir + "/" + mount_dir,
            getpass.getuser(),
            options,
        )
        mount_points.append(mount_point)
    return mount_points


def CreateSymlink(target, link_name):
    logger.GetLogger().LogFatalIf(
        target.startswith("/"), "Can't create symlink to absolute path!"
    )
    real_from_file = misc.GetRoot(link_name)[0] + "/" + target
    if os.path.realpath(real_from_file) != os.path.realpath(link_name):
        if os.path.exists(link_name):
            command = "rm -rf " + link_name
            command_executer.GetCommandExecuter().RunCommand(command)
        os.symlink(target, link_name)


if __name__ == "__main__":
    retval = Main(sys.argv)
    sys.exit(retval)
