/*
 * 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 Lesser General Public License
 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
 */

#include <crm_internal.h>

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

#include <glib.h>
#include <libxml/tree.h>

#include <crm/msg_xml.h>
#include <crm/common/xml_internal.h>

/*!
 * \brief Create a Pacemaker request (for IPC or cluster layer)
 *
 * \param[in] task          What to set as the request's task
 * \param[in] msg_data      What to add as the request's data contents
 * \param[in] host_to       What to set as the request's destination host
 * \param[in] sys_to        What to set as the request's destination system
 * \param[in] sys_from      If not NULL, set as request's origin system
 * \param[in] uuid_from     If not NULL, use in request's origin system
 * \param[in] origin        Name of function that called this one
 *
 * \return XML of new request
 *
 * \note One of sys_from or uuid_from must be non-NULL
 * \note This function should not be called directly, but via the
 *       create_request() wrapper.
 * \note The caller is responsible for freeing the result using free_xml().
 */
xmlNode *
create_request_adv(const char *task, xmlNode * msg_data,
                   const char *host_to, const char *sys_to,
                   const char *sys_from, const char *uuid_from,
                   const char *origin)
{
    static uint ref_counter = 0;

    char *true_from = NULL;
    xmlNode *request = NULL;
    char *reference = crm_strdup_printf("%s-%s-%lld-%u",
                                        (task? task : "_empty_"),
                                        (sys_from? sys_from : "_empty_"),
                                        (long long) time(NULL), ref_counter++);

    if (uuid_from != NULL) {
        true_from = crm_strdup_printf("%s_%s", uuid_from,
                                      (sys_from? sys_from : "none"));
    } else if (sys_from != NULL) {
        true_from = strdup(sys_from);
    } else {
        crm_err("Cannot create IPC request: No originating system specified");
    }

    // host_from will get set for us if necessary by the controller when routed
    request = create_xml_node(NULL, __func__);
    crm_xml_add(request, F_CRM_ORIGIN, origin);
    crm_xml_add(request, F_TYPE, T_CRM);
    crm_xml_add(request, F_CRM_VERSION, CRM_FEATURE_SET);
    crm_xml_add(request, F_CRM_MSG_TYPE, XML_ATTR_REQUEST);
    crm_xml_add(request, F_CRM_REFERENCE, reference);
    crm_xml_add(request, F_CRM_TASK, task);
    crm_xml_add(request, F_CRM_SYS_TO, sys_to);
    crm_xml_add(request, F_CRM_SYS_FROM, true_from);

    /* HOSTTO will be ignored if it is to the DC anyway. */
    if (host_to != NULL && strlen(host_to) > 0) {
        crm_xml_add(request, F_CRM_HOST_TO, host_to);
    }

    if (msg_data != NULL) {
        add_message_xml(request, F_CRM_DATA, msg_data);
    }
    free(reference);
    free(true_from);

    return request;
}

/*!
 * \brief Create a Pacemaker reply (for IPC or cluster layer)
 *
 * \param[in] original_request   XML of request this is a reply to
 * \param[in] xml_response_data  XML to copy as data section of reply
 * \param[in] origin             Name of function that called this one
 *
 * \return XML of new reply
 *
 * \note This function should not be called directly, but via the
 *       create_reply() wrapper.
 * \note The caller is responsible for freeing the result using free_xml().
 */
xmlNode *
create_reply_adv(xmlNode *original_request, xmlNode *xml_response_data,
                 const char *origin)
{
    xmlNode *reply = NULL;

    const char *host_from = crm_element_value(original_request, F_CRM_HOST_FROM);
    const char *sys_from = crm_element_value(original_request, F_CRM_SYS_FROM);
    const char *sys_to = crm_element_value(original_request, F_CRM_SYS_TO);
    const char *type = crm_element_value(original_request, F_CRM_MSG_TYPE);
    const char *operation = crm_element_value(original_request, F_CRM_TASK);
    const char *crm_msg_reference = crm_element_value(original_request, F_CRM_REFERENCE);

    if (type == NULL) {
        crm_err("Cannot create new_message, no message type in original message");
        CRM_ASSERT(type != NULL);
        return NULL;
#if 0
    } else if (strcasecmp(XML_ATTR_REQUEST, type) != 0) {
        crm_err("Cannot create new_message, original message was not a request");
        return NULL;
#endif
    }
    reply = create_xml_node(NULL, __func__);
    if (reply == NULL) {
        crm_err("Cannot create new_message, malloc failed");
        return NULL;
    }

    crm_xml_add(reply, F_CRM_ORIGIN, origin);
    crm_xml_add(reply, F_TYPE, T_CRM);
    crm_xml_add(reply, F_CRM_VERSION, CRM_FEATURE_SET);
    crm_xml_add(reply, F_CRM_MSG_TYPE, XML_ATTR_RESPONSE);
    crm_xml_add(reply, F_CRM_REFERENCE, crm_msg_reference);
    crm_xml_add(reply, F_CRM_TASK, operation);

    /* since this is a reply, we reverse the from and to */
    crm_xml_add(reply, F_CRM_SYS_TO, sys_from);
    crm_xml_add(reply, F_CRM_SYS_FROM, sys_to);

    /* HOSTTO will be ignored if it is to the DC anyway. */
    if (host_from != NULL && strlen(host_from) > 0) {
        crm_xml_add(reply, F_CRM_HOST_TO, host_from);
    }

    if (xml_response_data != NULL) {
        add_message_xml(reply, F_CRM_DATA, xml_response_data);
    }

    return reply;
}

xmlNode *
get_message_xml(xmlNode *msg, const char *field)
{
    xmlNode *tmp = first_named_child(msg, field);

    return pcmk__xml_first_child(tmp);
}

gboolean
add_message_xml(xmlNode *msg, const char *field, xmlNode *xml)
{
    xmlNode *holder = create_xml_node(msg, field);

    add_node_copy(holder, xml);
    return TRUE;
}

/*!
 * \brief Get name to be used as identifier for cluster messages
 *
 * \param[in] name  Actual system name to check
 *
 * \return Non-NULL cluster message identifier corresponding to name
 *
 * \note The Pacemaker daemons were renamed in version 2.0.0, but the old names
 *       must continue to be used as the identifier for cluster messages, so
 *       that mixed-version clusters are possible during a rolling upgrade.
 */
const char *
pcmk__message_name(const char *name)
{
    if (name == NULL) {
        return "unknown";

    } else if (!strcmp(name, "pacemaker-attrd")) {
        return "attrd";

    } else if (!strcmp(name, "pacemaker-based")) {
        return CRM_SYSTEM_CIB;

    } else if (!strcmp(name, "pacemaker-controld")) {
        return CRM_SYSTEM_CRMD;

    } else if (!strcmp(name, "pacemaker-execd")) {
        return CRM_SYSTEM_LRMD;

    } else if (!strcmp(name, "pacemaker-fenced")) {
        return "stonith-ng";

    } else if (!strcmp(name, "pacemaker-schedulerd")) {
        return CRM_SYSTEM_PENGINE;

    } else {
        return name;
    }
}
