#!/usr/bin/python3

import sys
import argparse
import json
import subprocess

ProgName = "crash-forwarder"


def parse_args():
    parser = argparse.ArgumentParser(
        description="coredump forwarder for container",
        formatter_class=argparse.ArgumentDefaultsHelpFormatter)
    parser.add_argument("-p", "--pid",
                        help="PID of dumped process, as seen in the PID namespace in which the process resides")
    parser.add_argument("-P", "--PID",
                        help="PID of dumped process, as seen in the initial PID namespace")
    parser.add_argument("-i", "--tid",
                        help="TID of thread that triggered core dump, as seen in the PID namespace in which the thread resides")
    parser.add_argument("-I", "--TID",
                        help="TID of thread that triggered core dump, as seen in the initial PID namespace")
    parser.add_argument("-c", "--coresize",
                        help="Core file size soft resource limit of crashing process")
    parser.add_argument("-d", "--dump",
                        help="Dump mode—same as value returned by prct")
    parser.add_argument("-e", "--comm",
                        help="The process or thread's comm value, which typically is the same as the executable filename")
    parser.add_argument("-E", "--exec",
                        help="Pathname of executable, with slashes ('/') replaced by exclamation marks ('!')")
    parser.add_argument("-u", "--uid",
                        help="Numeric real UID of dumped process")
    parser.add_argument("-g", "--gid",
                        help="Numeric real GID of dumped process")
    parser.add_argument("-H", "--hostname",
                        help="Hostname")
    parser.add_argument("-s", "--signal",
                        help="Number of signal causing dump")
    parser.add_argument("-t", "--time",
                        help="Time of dump, expressed as seconds since the Epoch")
    return parser.parse_args()


def get_container_execute(info):
    if info == None:
        return []

    type = info["type"]
    cid = info["cid"]

    if type == "docker":
        return ["/usr/bin/docker", "exec", "-i", cid]

    return []


def get_container_info(args):
    if args.PID == None or args.PID == args.pid:
        return None

    cgroup_hierarchy = None
    with open("/proc/" + args.PID + "/cgroup") as f:
        data = f.read().strip()
        for line in data.split():
            if line.startswith("1:name"):
                cgroup_hierarchy = line.split(':')[-1].split('/')

    if cgroup_hierarchy == None or not isinstance(cgroup_hierarchy, list):
        return None

    # cgroup_hierarchy[0] is empty
    if not cgroup_hierarchy[1] in ["docker"]:
        return None

    engine = cgroup_hierarchy[1]
    cid = cgroup_hierarchy[2]
    info = {
        "type": engine,
        "cid": cid,
    }

    return info


def find_core_pattern(info, core_pattern_configs):
    if info != None:
        cid = info["cid"]
        if cid in core_pattern_configs.keys():
            return core_pattern_configs[cid]

    return core_pattern_configs["local"]


def find_default_core_pattern_in_sysctl(default):
    sysctl_list = subprocess.run(["/usr/lib/systemd/systemd-sysctl",
                                  "--cat-config", "--no-pager"],
                                 capture_output=True)
    value = default

    # sysctl output list is assumed to be in the processing order.
    for line in sysctl_list.stdout.decode().splitlines():
        if not line.startswith("kernel.core_pattern"):
            continue

        curr_value = line.split('=', 1)[1]

        if curr_value.find(ProgName) > -1:
            break
        # Among the configuration lists, the above one of the currently executed core_pattern settings is used.
        value = curr_value

    [cmd, args] = value.split(maxsplit=1)

    if not cmd.startswith('|'):
        return [value, "", False]

    return [cmd[1:], args, True]


def convert_args(output_args, input_args):
    out = output_args
    out = out.replace(r"%p", input_args.pid)
    out = out.replace(r"%P", input_args.PID)
    out = out.replace(r"%i", input_args.tid)
    out = out.replace(r"%I", input_args.TID)
    out = out.replace(r"%c", input_args.coresize)
    out = out.replace(r"%d", input_args.dump)
    out = out.replace(r"%e", input_args.comm)
    out = out.replace(r"%E", input_args.exec)
    out = out.replace(r"%u", input_args.uid)
    out = out.replace(r"%g", input_args.gid)
    out = out.replace(r"%h", input_args.hostname)
    out = out.replace(r"%s", input_args.signal)
    out = out.replace(r"%t", input_args.time)
    return out.split()


def get_core_pattern_config():
    config = {}

    # TODO: communicate with docker-launcher
    config_file = "/run/container/.core_patterns"
    try:
        with open(config_file) as f:
            config = json.load(f)
    except IOError:
        print(config_file + " not found")

    if not "local" in config or config["local"][1] == "":
        # defaults for tizen
        config["local"] = find_default_core_pattern_in_sysctl(
            r"/usr/bin/crash-manager -E %E -p %p -u %u -g %g -s %s -t %t -i %i")

    return config


if __name__ == '__main__':
    ProgName = sys.argv[0]
    args = parse_args()
    core_pattern_configs = get_core_pattern_config()
    info = get_container_info(args)
    core_pattern = find_core_pattern(info, core_pattern_configs)
    command = get_container_execute(info) + [core_pattern[0]] + convert_args(
        core_pattern[1], args)

    subprocess.run(command, stdout=subprocess.PIPE,
                   stderr=subprocess.PIPE)
