#!/usr/bin/env python
# Copyright 2018-2026 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

import locale
import os
import sys

if (
    sys.getfilesystemencoding().lower() != "utf-8"
    or locale.getpreferredencoding(False).lower() != "utf-8"
):
    os.environ["PYTHONUTF8"] = "1"
    os.execv(sys.executable, [sys.executable] + sys.argv)


import errno
import fcntl
import functools
import platform
import signal
import subprocess
import termios

from pathlib import Path

FORWARD_SIGNALS = (
    signal.SIGINT,
    signal.SIGTERM,
    signal.SIGHUP,
    signal.SIGTSTP,
    signal.SIGCONT,
)


def forward_signal(signum, frame):
    # Temporarily disable the handler in order to prevent it from
    # being called recursively, since the signal will also be sent
    # to the current process.
    handler = signal.signal(signum, signal.SIG_DFL)
    os.kill(0, signum)
    signal.signal(signum, handler)


def preexec_fn(uid, gid, groups, umask):
    if gid is not None:
        os.setgid(gid)
    if groups is not None:
        os.setgroups(groups)
    if uid is not None:
        os.setuid(uid)
    if umask is not None:
        os.umask(umask)

    # CPython >= 3 subprocess.Popen handles this internally.
    if platform.python_implementation() != "CPython":
        for signum in (
            signal.SIGHUP,
            signal.SIGINT,
            signal.SIGPIPE,
            signal.SIGQUIT,
            signal.SIGTERM,
        ):
            signal.signal(signum, signal.SIG_DFL)


def main(argv):
    if len(argv) < 8:
        return f"Usage: {argv[0]} <uid> <gid> <groups> <umask> <pass_fds> <binary> <argv0> [arg]..."

    # The current process is init (pid 1) in a child pid namespace.
    uid, gid, groups, umask, pass_fds, binary, args = (
        argv[1],
        argv[2],
        argv[3],
        argv[4],
        tuple(int(fd) for fd in argv[5].split(",")),
        argv[6],
        argv[7:],
    )
    uid = int(uid) if uid else None
    gid = int(gid) if gid else None
    groups = tuple(int(group) for group in groups.split(",")) if groups else None
    umask = int(umask) if umask else None

    popen_kwargs = {
        "preexec_fn": functools.partial(preexec_fn, uid, gid, groups, umask),
        "pass_fds": pass_fds,
    }

    # Obtain the current nice value, which will be potentially be
    # used as the newly created session's autogroup nice value.
    nice_value = os.nice(0)

    # Isolate parent process from process group SIGSTOP (bug 675870)
    os.setsid()

    # Set the previously obtained autogroup nice value again,
    # since we created a new session with os.setsid() above.
    try:
        Path("/proc/self/autogroup").write_text(str(nice_value))
    except OSError as e:
        # The process is likely not allowed to set the autogroup
        # value (Linux employs a rate limiting for unprivileged
        # changes to the autogroup value) or autogroups are not
        # enabled. Nothing we can do here, so we simply carry on.
        pass

    if sys.stdout.isatty():
        try:
            fcntl.ioctl(sys.stdout, termios.TIOCSCTTY, 0)
        except OSError as e:
            if e.errno == errno.EPERM:
                # This means that stdout refers to the controlling terminal
                # of the parent process, and in this case we do not want to
                # steal it.
                pass
            else:
                raise

    for signum in FORWARD_SIGNALS:
        signal.signal(signum, forward_signal)

    proc = subprocess.Popen(args, executable=binary, **popen_kwargs)
    main_child_pid = proc.pid

    # wait for child processes
    while True:
        try:
            pid, status = os.wait()
        except OSError as e:
            if e.errno == errno.EINTR:
                continue
            raise
        if pid == main_child_pid:
            # Suppress warning messages like this:
            # ResourceWarning: subprocess 1234 is still running
            proc.returncode = 0

            if os.WIFEXITED(status):
                return os.WEXITSTATUS(status)
            elif os.WIFSIGNALED(status):
                signal.signal(os.WTERMSIG(status), signal.SIG_DFL)
                os.kill(os.getpid(), os.WTERMSIG(status))
            # go to the unreachable place
            break

    # this should never be reached
    return 127


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