/*
 * Copyright 2004-2020 the Pacemaker project contributors
 *
 * The version control history for this file may have further details.
 *
 * This source code is licensed under the GNU General Public License version 2
 * or later (GPLv2+) WITHOUT ANY WARRANTY.
 */

#include <crm_internal.h>

#include <sys/param.h>

#include <crm/crm.h>

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>

#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>

#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crm/common/mainloop.h>
#include <crm/cib/internal.h>

#include <crm/common/ipc.h>
#include <crm/pengine/status.h>

#include <crm/cib.h>

#ifdef HAVE_GETOPT_H
#  include <getopt.h>
#endif

static int max_failures = 30;

static gboolean log_diffs = FALSE;
static gboolean log_updates = FALSE;

static GMainLoop *mainloop = NULL;
void usage(const char *cmd, crm_exit_t exit_status);
void cib_connection_destroy(gpointer user_data);

void cibmon_shutdown(int nsig);
void cibmon_diff(const char *event, xmlNode * msg);

static cib_t *cib = NULL;
static xmlNode *cib_copy = NULL;

#define OPTARGS	"V?m:du"

int
main(int argc, char **argv)
{
    int argerr = 0;
    int flag;
    int attempts = 0;
    int rc = pcmk_ok;

#ifdef HAVE_GETOPT_H
    int option_index = 0;

    static struct option long_options[] = {
        /* Top-level Options */
        {"verbose", 0, 0, 'V'},
        {"help", 0, 0, '?'},
        {"log-diffs", 0, 0, 'd'},
        {"log-updates", 0, 0, 'u'},
        {"max-conn-fail", 1, 0, 'm'},
        {0, 0, 0, 0}
    };
#endif

    crm_log_cli_init("cibmon");

    crm_signal_handler(SIGTERM, cibmon_shutdown);

    while (1) {
#ifdef HAVE_GETOPT_H
        flag = getopt_long(argc, argv, OPTARGS, long_options, &option_index);
#else
        flag = getopt(argc, argv, OPTARGS);
#endif
        if (flag == -1)
            break;

        switch (flag) {
            case 'V':
                crm_bump_log_level(argc, argv);
                break;
            case '?':
                usage(crm_system_name, CRM_EX_OK);
                break;
            case 'd':
                log_diffs = TRUE;
                break;
            case 'u':
                log_updates = TRUE;
                break;
            case 'm':
                max_failures = crm_parse_int(optarg, "30");
                break;
            default:
                printf("Argument code 0%o (%c)" " is not (?yet?) supported\n", flag, flag);
                ++argerr;
                break;
        }
    }

    if (optind < argc) {
        printf("non-option ARGV-elements: ");
        while (optind < argc)
            printf("%s ", argv[optind++]);
        printf("\n");
    }

    if (optind > argc) {
        ++argerr;
    }

    if (argerr) {
        usage(crm_system_name, CRM_EX_USAGE);
    }

    cib = cib_new();

    do {
        sleep(1);
        rc = cib->cmds->signon(cib, crm_system_name, cib_query);

    } while (rc == -ENOTCONN && attempts++ < max_failures);

    if (rc != pcmk_ok) {
        crm_err("Signon to CIB failed: %s", pcmk_strerror(rc));
        goto fail;
    }

    crm_debug("Setting dnotify");
    rc = cib->cmds->set_connection_dnotify(cib, cib_connection_destroy);
    if (rc != pcmk_ok) {
        crm_err("Failed to set dnotify callback: %s", pcmk_strerror(rc));
        goto fail;
    }

    crm_debug("Setting diff callback");
    rc = cib->cmds->add_notify_callback(cib, T_CIB_DIFF_NOTIFY, cibmon_diff);
    if (rc != pcmk_ok) {
        crm_err("Failed to set diff callback: %s", pcmk_strerror(rc));
        goto fail;
    }

    mainloop = g_main_loop_new(NULL, FALSE);
    crm_info("Starting mainloop");
    g_main_loop_run(mainloop);
    crm_trace("%s exiting normally", crm_system_name);
    fflush(stderr);
    return CRM_EX_OK;

fail:
    crm_err("Setup failed, could not monitor CIB actions");
    return CRM_EX_ERROR;
}

void
usage(const char *cmd, crm_exit_t exit_status)
{
    FILE *stream;

    stream = (exit_status == CRM_EX_OK)? stdout : stderr;
    fflush(stream);

    crm_exit(exit_status);
}

void
cib_connection_destroy(gpointer user_data)
{
    cib_t *conn = user_data;

    crm_err("Connection to the CIB terminated... exiting");
    conn->cmds->signoff(conn);  /* Ensure IPC is cleaned up */
    g_main_loop_quit(mainloop);
    return;
}

void
cibmon_diff(const char *event, xmlNode * msg)
{
    int rc = -1;
    const char *op = NULL;
    unsigned int log_level = LOG_INFO;

    xmlNode *diff = NULL;
    xmlNode *cib_last = NULL;
    xmlNode *update = get_message_xml(msg, F_CIB_UPDATE);

    if (msg == NULL) {
        crm_err("NULL update");
        return;
    }

    crm_element_value_int(msg, F_CIB_RC, &rc);
    op = crm_element_value(msg, F_CIB_OPERATION);
    diff = get_message_xml(msg, F_CIB_UPDATE_RESULT);

    if (rc < pcmk_ok) {
        log_level = LOG_WARNING;
        do_crm_log(log_level, "[%s] %s ABORTED: %s", event, op, pcmk_strerror(rc));
        return;
    }

    if (log_diffs) {
        xml_log_patchset(log_level, op, diff);
    }

    if (log_updates && update != NULL) {
        crm_log_xml_trace(update, "raw_update");
    }

    if (cib_copy != NULL) {
        cib_last = cib_copy;
        cib_copy = NULL;
        rc = cib_process_diff(op, cib_force_diff, NULL, NULL, diff, cib_last, &cib_copy, NULL);

        if (rc != pcmk_ok) {
            crm_debug("Update didn't apply, requesting full copy: %s", pcmk_strerror(rc));
            free_xml(cib_copy);
            cib_copy = NULL;
        }
    }

    if (cib_copy == NULL) {
        rc = cib->cmds->query(cib, NULL, &cib_copy, cib_scope_local | cib_sync_call);
    }

    if(rc == -EACCES) {
        crm_exit(CRM_EX_INSUFFICIENT_PRIV);
    }

    free_xml(cib_last);
}

void
cibmon_shutdown(int nsig)
{
    crm_exit(CRM_EX_OK);
}
