#!/usr/bin/python3.13
# Copyright 2013-2014 Gentoo Foundation
# Distributed under the terms of the GNU General Public License v2

import argparse
import os
import shlex
import stat
import sys
import subprocess
import traceback

import portage
from portage.util.movefile import _copyxattr
from portage.exception import OperationNotSupported

# Change back to original cwd _after_ all imports (bug #469338).
os.chdir(os.environ["__PORTAGE_HELPER_CWD"])


def parse_args(args):
    """
    Parse the command line arguments using optparse for python 2.6 compatibility
    Args:
      args: a list of the white space delimited command line
    Returns:
      tuple of the Namespace of parsed options, and a list of order parameters
    """
    parser = argparse.ArgumentParser(add_help=False)

    parser.add_argument("-b", action="store_true", dest="shortopt_b")
    parser.add_argument("--backup", action="store", dest="backup")
    parser.add_argument("-c", action="store_true", dest="shortopt_c")
    parser.add_argument("--compare", "-C", action="store_true", dest="compare")
    parser.add_argument("--directory", "-d", action="store_true", dest="directory")
    parser.add_argument("-D", action="store_true", dest="shortopt_D")
    parser.add_argument("--owner", "-o", action="store", dest="owner")
    parser.add_argument("--group", "-g", action="store", dest="group")
    parser.add_argument("--mode", "-m", action="store", dest="mode")
    parser.add_argument(
        "--preserve-timestamps", "-p", action="store_true", dest="preserve_timestamps"
    )
    parser.add_argument("--strip", "-s", action="store_true", dest="strip")
    parser.add_argument("--strip-program", action="store", dest="strip_program")
    parser.add_argument("--suffix", "-S", action="store", dest="suffix")
    parser.add_argument(
        "--target-directory", "-t", action="store", dest="target_directory"
    )
    parser.add_argument(
        "--no-target-directory", "-T", action="store_true", dest="no_target_directory"
    )
    parser.add_argument("--verbose", "-v", action="store_true", dest="verbose")
    parser.add_argument("--help", action="store_true", dest="help")
    parser.add_argument("--version", action="store_true", dest="version")

    # Use parse_known_args for maximum compatibility with
    # getopt handling of non-option file arguments. Note
    # that parser.add_argument("files", nargs='+') would
    # be subtly incompatible because it requires that all
    # of the file arguments be grouped sequentially. Also
    # note that we have to explicitly call add_argument
    # for known options in order for argparse to correctly
    # separate option arguments from file arguments in all
    # cases (it also allows for optparse compatibility).
    (opts, args) = parser.parse_known_args(args)

    files = []
    i = 0
    while i < len(args):
        if args[i] == "--":
            i += 1
            break
        if not args[i].startswith("-"):
            files.append(args[i])
        i += 1

    while i < len(args):
        files.append(args[i])
        i += 1

    return (opts, files)


def copy_xattrs(opts, files):
    """
    Copy the extended attributes using portage.util.movefile._copyxattr
    Args:
      opts:  Namespace of the parsed command line otions
      files: list of ordered command line parameters which should be files/directories
    Returns:
      system exit code
    """
    if opts.directory or not files:
        return os.EX_OK

    if opts.target_directory is None:
        source, target = files[:-1], files[-1]
        target_is_directory = os.path.isdir(target)
    else:
        source, target = files, opts.target_directory
        target_is_directory = True

    exclude = os.environ.get("PORTAGE_XATTR_EXCLUDE", "")

    try:
        if target_is_directory:
            for s in source:
                abs_path = os.path.join(target, os.path.basename(s))
                _copyxattr(s, abs_path, exclude=exclude)
        else:
            _copyxattr(source[0], target, exclude=exclude)
        return os.EX_OK

    except OperationNotSupported:
        traceback.print_exc()
        return os.EX_OSERR


def Which(filename, path=None, exclude=None):
    """
    Find the absolute path of 'filename' in a given search 'path'
    Args:
      filename: basename of the file
      path: colon delimited search path
      exclude: path of file to exclude
    """
    if path is None:
        path = os.environ.get("PATH", "")

    if exclude is not None:
        st = os.stat(exclude)
        exclude = (st.st_ino, st.st_dev)

    for p in path.split(":"):
        p = os.path.join(p, filename)
        if os.access(p, os.X_OK):
            try:
                st = os.stat(p)
            except OSError:
                # file disappeared?
                pass
            else:
                if stat.S_ISREG(st.st_mode) and (
                    exclude is None or exclude != (st.st_ino, st.st_dev)
                ):
                    return p

    return None


def main(args):
    opts, files = parse_args(args)
    install_binary = Which("install", exclude=os.environ["__PORTAGE_HELPER_PATH"])
    if install_binary is None:
        sys.stderr.write("install: command not found\n")
        return 127

    cmdline = [install_binary]
    cmdline += args

    # We can't trust that the filesystem encoding (locale dependent)
    # correctly matches the arguments, so use surrogateescape to
    # pass through the original argv bytes for Python 3.
    fs_encoding = sys.getfilesystemencoding()
    cmdline = [x.encode(fs_encoding, "surrogateescape") for x in cmdline]
    files = [x.encode(fs_encoding, "surrogateescape") for x in files]
    if opts.target_directory is not None:
        opts.target_directory = opts.target_directory.encode(
            fs_encoding, "surrogateescape"
        )

    returncode = subprocess.call(cmdline)
    if returncode == os.EX_OK:
        returncode = copy_xattrs(opts, files)
        if returncode != os.EX_OK:
            portage.util.writemsg(
                "!!! install: copy_xattrs failed with the "
                f"following arguments: {shlex.join(args)}\n",
                noiselevel=-1,
            )
    return returncode


if __name__ == "__main__":
    sys.exit(main(sys.argv[1:]))
