/*
    Copyright (C) 2015  ABRT Team
    Copyright (C) 2015  RedHat inc.

    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.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "libabrt.h"
#include <json.h>

void dump_docker_info(struct dump_dir *dd, const char *root_dir)
{
    if (!dd_exist(dd, FILENAME_CONTAINER))
        dd_save_text(dd, FILENAME_CONTAINER, "docker");

    json_object *json = NULL;
    g_autofree char *mntnf_path = g_build_filename(dd->dd_dirname ? dd->dd_dirname : "", FILENAME_MOUNTINFO, NULL);
    FILE *mntnf_file = fopen(mntnf_path, "r");
    if (mntnf_file == NULL)
    {
        perror_msg("Could not open mountinfo file");
        return;
    }

    struct mount_point {
        const char *name;
        enum mountinfo_fields {
            MOUNTINFO_ROOT,
            MOUNTINFO_SOURCE,
        } field;
    } mount_points[] = {
        { "/sys/fs/cgroup/memory", MOUNTINFO_ROOT },
        { "/",                     MOUNTINFO_SOURCE },
    };

    char *container_id = NULL;
    g_autofree char *output = NULL;

    /* initialized to 0 because we call libreport_mountinfo_destroy below */
    struct mountinfo mntnf = {0};

    for (size_t i = 0; i < ARRAY_SIZE(mount_points); ++i)
    {
        log_debug("Parsing container ID from mount point '%s'", mount_points[i].name);

        rewind(mntnf_file);

        /* libreport_get_mountinfo_for_mount_point() re-initializes &mntnf */
        libreport_mountinfo_destroy(&mntnf);
        int r = libreport_get_mountinfo_for_mount_point(mntnf_file, &mntnf, mount_points[i].name);

        if (r != 0)
        {
            log_debug("Mount poin not found");
            continue;
        }

        const char *mnt_info = NULL;
        switch(mount_points[i].field)
        {
            case MOUNTINFO_ROOT:
                mnt_info = MOUNTINFO_ROOT(mntnf);
                break;
            case MOUNTINFO_SOURCE:
                mnt_info = MOUNTINFO_MOUNT_SOURCE(mntnf);
                break;
            default:
                error_msg("BUG: forgotten MOUNTINFO field type");
                abort();
        }
        const char *last = strrchr(mnt_info, '/');
        if (last == NULL || strncmp("/docker-", last, strlen("/docker-")) != 0)
        {
            log_debug("Mounted source is not a docker mount source: '%s'", mnt_info);
            continue;
        }

        last = strrchr(last, '-');
        if (last == NULL)
        {
            log_debug("The docker mount point has unknown format");
            continue;
        }

        ++last;

        /* Why we copy only 12 bytes here?
         * Because only the first 12 characters are used by docker as ID of the
         * container. */
        container_id = g_strndup(last, 12);
        if (strlen(container_id) != 12)
        {
            log_debug("Failed to get container ID");
            continue;
        }

        g_autofree char *docker_inspect_cmdline = NULL;
        if (root_dir != NULL)
            docker_inspect_cmdline = g_strdup_printf("chroot %s /bin/sh -c \"docker inspect %s\"", root_dir, container_id);
        else
            docker_inspect_cmdline = g_strdup_printf("docker inspect %s", container_id);

        log_debug("Executing: '%s'", docker_inspect_cmdline);
        output = libreport_run_in_shell_and_save_output(0, docker_inspect_cmdline, "/", NULL);

        if (output == NULL || strcmp(output, "[]\n") == 0)
        {
            log_debug("Unsupported container ID: '%s'", container_id);

            g_free(container_id);
            container_id = NULL;

            output = NULL;

            continue;
        }

        break;
    }
    fclose(mntnf_file);

    if (container_id == NULL)
    {
        error_msg("Could not inspect the container");
        goto dump_docker_info_cleanup;
    }

    dd_save_text(dd, FILENAME_CONTAINER_ID, container_id);
    dd_save_text(dd, FILENAME_DOCKER_INSPECT, output);

    json = json_tokener_parse(output);

    if (json == NULL)
    {
        error_msg("Unable parse response from docker");
        goto dump_docker_info_cleanup;
    }

    json_object *container = json_object_array_get_idx(json, 0);
    if (container == NULL)
    {
        error_msg("docker does not contain array of containers");
        goto dump_docker_info_cleanup;
    }

    json_object *config = NULL;
    if (!json_object_object_get_ex(container, "Config", &config))
    {
        error_msg("container does not have 'Config' member");
        goto dump_docker_info_cleanup;
    }

    json_object *image = NULL;
    if (!json_object_object_get_ex(config, "Image", &image))
    {
        error_msg("Config does not have 'Image' member");
        goto dump_docker_info_cleanup;
    }

    char *name = libreport_strtrimch(g_strdup(json_object_to_json_string(image)), '"');
    dd_save_text(dd, FILENAME_CONTAINER_IMAGE, name);
    free(name);

dump_docker_info_cleanup:
    if (json != NULL)
        json_object_put(json);

    libreport_mountinfo_destroy(&mntnf);

    return;
}

void dump_lxc_info(struct dump_dir *dd, const char *lxc_cmd)
{
    if (!dd_exist(dd, FILENAME_CONTAINER))
        dd_save_text(dd, FILENAME_CONTAINER, "lxc");

    g_autofree char *mntnf_path = g_build_filename(dd->dd_dirname ? dd->dd_dirname : "", FILENAME_MOUNTINFO, NULL);
    FILE *mntnf_file = fopen(mntnf_path, "r");
    if (mntnf_file == NULL)
    {
        perror_msg("Could not open mountinfo file");
        return;
    }

    struct mountinfo mntnf;
    int r = libreport_get_mountinfo_for_mount_point(mntnf_file, &mntnf, "/");
    fclose(mntnf_file);

    if (r != 0)
    {
        error_msg("lxc processes must have re-mounted root");
        goto dump_lxc_info_cleanup;
    }

    const char *mnt_root = MOUNTINFO_ROOT(mntnf);
    const char *last_slash = strrchr(mnt_root, '/');
    if (last_slash == NULL || (strcmp("rootfs", last_slash +1) != 0))
    {
        error_msg("Invalid root path '%s'", mnt_root);
        goto dump_lxc_info_cleanup;
    }

    if (last_slash == mnt_root)
    {
        error_msg("root path misses container id: '%s'", mnt_root);
        goto dump_lxc_info_cleanup;
    }

    const char *tmp = strrchr(last_slash - 1, '/');
    if (tmp == NULL)
    {
        error_msg("root path misses first /: '%s'", mnt_root);
        goto dump_lxc_info_cleanup;
    }

    char *container_id = g_strndup(tmp + 1, (last_slash - tmp) - 1);

    dd_save_text(dd, FILENAME_CONTAINER_ID, container_id);
    dd_save_text(dd, FILENAME_CONTAINER_UUID, container_id);

    g_free(container_id);

    /* TODO: make a copy of 'config' */
    /* get mount point for MOUNTINFO_MOUNT_SOURCE(mntnf) + MOUNTINFO_ROOT(mntnf) */

dump_lxc_info_cleanup:
    libreport_mountinfo_destroy(&mntnf);
}

int main(int argc, char **argv)
{
    /* I18n */
    setlocale(LC_ALL, "");
#if ENABLE_NLS
    bindtextdomain(PACKAGE, LOCALEDIR);
    textdomain(PACKAGE);
#endif

    abrt_init(argv);

    const char *dump_dir_name = ".";
    const char *root_dir = NULL;

    /* Can't keep these strings/structs static: _() doesn't support that */
    const char *program_usage_string = _(
        "& [-v] -d DIR\n"
        "\n"
        "Save container metadata"
    );
    enum {
        OPT_v = 1 << 0,
        OPT_d = 1 << 1,
    };
    /* Keep enum above and order of options below in sync! */
    struct options program_options[] = {
        OPT__VERBOSE(&libreport_g_verbose),
        OPT_STRING('d', NULL, &dump_dir_name, "DIR"     , _("Problem directory")),
        OPT_STRING('r', NULL, &root_dir,      "ROOTDIR" , _("Root directory for running container commands")),
        OPT_END()
    };
    /*unsigned opts =*/ libreport_parse_opts(argc, argv, program_options, program_usage_string);

    libreport_export_abrt_envvars(0);

    struct dump_dir *dd = dd_opendir(dump_dir_name, /* for writing */0);
    if (dd == NULL)
        libreport_xfunc_die();

    g_autofree char *container_cmdline = dd_load_text_ext(dd, FILENAME_CONTAINER_CMDLINE, DD_LOAD_TEXT_RETURN_NULL_ON_FAILURE);
    if (container_cmdline == NULL)
        error_msg_and_die("The crash didn't occur in container");

    if (strstr(container_cmdline, "/docker ") != 0 || g_str_has_prefix(container_cmdline, "/usr/libexec/docker"))
        dump_docker_info(dd, root_dir);
    else if (strstr(container_cmdline, "/lxc-") != 0)
        dump_lxc_info(dd, container_cmdline);
    else
        error_msg_and_die("Unsupported container technology");

    dd_close(dd);

    return 0;
}
