/*
 * Copyright (C) 2011-2017 Andrew Beekhof <andrew@beekhof.net>
 *
 * This source code is licensed under the GNU Lesser General Public License
 * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
 */


#ifndef _GNU_SOURCE
#  define _GNU_SOURCE
#endif

#include <crm_internal.h>

#include <stdio.h>

#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/attrd.h>

/*!
 * \internal
 * \brief Create a generic attrd operation
 *
 * \param[in] user_name  If not NULL, ACL user to set for operation
 *
 * \return XML of attrd operation
 */
static xmlNode *
create_attrd_op(const char *user_name)
{
    xmlNode *attrd_op = create_xml_node(NULL, __FUNCTION__);

    crm_xml_add(attrd_op, F_TYPE, T_ATTRD);
    crm_xml_add(attrd_op, F_ORIG, (crm_system_name? crm_system_name: "unknown"));
#if ENABLE_ACL
    crm_xml_add(attrd_op, F_ATTRD_USER, user_name);
#endif

    return attrd_op;
}

/*!
 * \internal
 * \brief Send an operation to attrd via IPC
 *
 * \param[in] ipc       Connection to attrd (or NULL to use a local connection)
 * \param[in] attrd_op  XML of attrd operation to send
 *
 * \return pcmk_ok on success, -errno otherwise
 */
static int
send_attrd_op(crm_ipc_t *ipc, xmlNode *attrd_op)
{
    int rc = -ENOTCONN;
    int max = 5;

    static gboolean connected = TRUE;
    static crm_ipc_t *local_ipc = NULL;
    static enum crm_ipc_flags flags = crm_ipc_flags_none;

    if (ipc == NULL && local_ipc == NULL) {
        local_ipc = crm_ipc_new(T_ATTRD, 0);
        flags |= crm_ipc_client_response;
        connected = FALSE;
    }

    if (ipc == NULL) {
        ipc = local_ipc;
    }

    while (max > 0) {
        if (connected == FALSE) {
            crm_info("Connecting to cluster... %d retries remaining", max);
            connected = crm_ipc_connect(ipc);
        }

        if (connected) {
            rc = crm_ipc_send(ipc, attrd_op, flags, 0, NULL);
        } else {
            crm_perror(LOG_INFO, "Connection to cluster attribute manager failed");
        }

        if (ipc != local_ipc) {
            break;

        } else if (rc > 0) {
            break;

        } else if (rc == -EAGAIN || rc == -EALREADY) {
            sleep(5 - max);
            max--;

        } else {
            crm_ipc_close(ipc);
            connected = FALSE;
            sleep(5 - max);
            max--;
        }
    }

    if (rc > 0) {
        rc = pcmk_ok;
    }
    return rc;
}

/*!
 * \brief Send a request to attrd
 *
 * \param[in] ipc      Connection to attrd (or NULL to use a local connection)
 * \param[in] command  A character indicating the type of attrd request:
 *                     U or v: update attribute (or refresh if name is NULL)
 *                     u: update attributes matching regular expression in name
 *                     D: delete attribute (value must be NULL)
 *                     R: refresh
 *                     B: update both attribute and its dampening
 *                     Y: update attribute dampening only
 *                     Q: query attribute
 *                     C: remove peer specified by host
 * \param[in] host     Affect only this host (or NULL for all hosts)
 * \param[in] name     Name of attribute to affect
 * \param[in] value    Attribute value to set
 * \param[in] section  Status or nodes
 * \param[in] set      ID of attribute set to use (or NULL to choose first)
 * \param[in] dampen   Attribute dampening to use with B/Y, and U/v if creating
 * \param[in] user_name ACL user to pass to attrd
 * \param[in] options  Bitmask that may include:
 *                     attrd_opt_remote: host is a Pacemaker Remote node
 *                     attrd_opt_private: attribute is private (not kept in CIB)
 *
 * \return pcmk_ok if request was successfully submitted to attrd, else -errno
 */
int
attrd_update_delegate(crm_ipc_t *ipc, char command, const char *host,
                      const char *name, const char *value, const char *section,
                      const char *set, const char *dampen,
                      const char *user_name, int options)
{
    int rc = pcmk_ok;
    const char *task = NULL;
    const char *name_as = NULL;
    const char *display_host = (host ? host : "localhost");
    const char *display_command = NULL; /* for commands without name/value */
    xmlNode *update = create_attrd_op(user_name);

    /* remap common aliases */
    if (safe_str_eq(section, "reboot")) {
        section = XML_CIB_TAG_STATUS;

    } else if (safe_str_eq(section, "forever")) {
        section = XML_CIB_TAG_NODES;
    }

    if (name == NULL && command == 'U') {
        command = 'R';
    }

    switch (command) {
        case 'u':
            task = ATTRD_OP_UPDATE;
            name_as = F_ATTRD_REGEX;
            break;
        case 'D':
        case 'U':
        case 'v':
            task = ATTRD_OP_UPDATE;
            name_as = F_ATTRD_ATTRIBUTE;
            break;
        case 'R':
            task = ATTRD_OP_REFRESH;
            display_command = "refresh";
            break;
        case 'B':
            task = ATTRD_OP_UPDATE_BOTH;
            name_as = F_ATTRD_ATTRIBUTE;
            break;
        case 'Y':
            task = ATTRD_OP_UPDATE_DELAY;
            name_as = F_ATTRD_ATTRIBUTE;
            break;
        case 'Q':
            task = ATTRD_OP_QUERY;
            name_as = F_ATTRD_ATTRIBUTE;
            break;
        case 'C':
            task = ATTRD_OP_PEER_REMOVE;
            display_command = "purge";
            break;
    }

    if (name_as != NULL) {
        if (name == NULL) {
            rc = -EINVAL;
            goto done;
        }
        crm_xml_add(update, name_as, name);
    }

    crm_xml_add(update, F_ATTRD_TASK, task);
    crm_xml_add(update, F_ATTRD_VALUE, value);
    crm_xml_add(update, F_ATTRD_DAMPEN, dampen);
    crm_xml_add(update, F_ATTRD_SECTION, section);
    crm_xml_add(update, F_ATTRD_HOST, host);
    crm_xml_add(update, F_ATTRD_SET, set);
    crm_xml_add_int(update, F_ATTRD_IS_REMOTE, is_set(options, attrd_opt_remote));
    crm_xml_add_int(update, F_ATTRD_IS_PRIVATE, is_set(options, attrd_opt_private));

    rc = send_attrd_op(ipc, update);

done:
    free_xml(update);

    if (display_command) {
        crm_debug("Asked attrd to %s %s: %s (%d)",
                  display_command, display_host, pcmk_strerror(rc), rc);
    } else {
        crm_debug("Asked attrd to update %s=%s for %s: %s (%d)",
                  name, value, display_host, pcmk_strerror(rc), rc);
    }
    return rc;
}

/*!
 * \brief Send a request to attrd to clear resource failure
 *
 * \param[in] ipc       Connection to attrd (or NULL to use a local connection)
 * \param[in] host      Affect only this host (or NULL for all hosts)
 * \param[in] name      Name of resource to clear
 * \param[in] user_name ACL user to pass to attrd
 * \param[in] options   attrd_opt_remote if host is a Pacemaker Remote node
 *
 * \return pcmk_ok if request was successfully submitted to attrd, else -errno
 */
int
attrd_clear_delegate(crm_ipc_t *ipc, const char *host, const char *resource,
                     const char *operation, const char *interval,
                     const char *user_name, int options)
{
    int rc = pcmk_ok;
    xmlNode *clear_op = create_attrd_op(user_name);

    crm_xml_add(clear_op, F_ATTRD_TASK, ATTRD_OP_CLEAR_FAILURE);
    crm_xml_add(clear_op, F_ATTRD_HOST, host);
    crm_xml_add(clear_op, F_ATTRD_RESOURCE, resource);
    crm_xml_add(clear_op, F_ATTRD_OPERATION, operation);
    crm_xml_add(clear_op, F_ATTRD_INTERVAL, interval);
    crm_xml_add_int(clear_op, F_ATTRD_IS_REMOTE, is_set(options, attrd_opt_remote));

    rc = send_attrd_op(ipc, clear_op);
    free_xml(clear_op);

    crm_debug("Asked attrd to clear failure of %s (interval %s) for %s on %s: %s (%d)",
              (operation? operation : "all operations"),
              (interval? interval : "0"),
              (resource? resource : "all resources"),
              (host? host : "all nodes"), pcmk_strerror(rc), rc);
    return rc;
}

#define LRM_TARGET_ENV "OCF_RESKEY_" CRM_META "_" XML_LRM_ATTR_TARGET

const char *
attrd_get_target(const char *name)
{
    if(safe_str_eq(name, "auto") || safe_str_eq(name, "localhost")) {
        name = NULL;
    }

    if(name != NULL) {
        return name;

    } else {
        char *target_var = crm_meta_name(XML_RSC_ATTR_TARGET);
        char *phys_var = crm_meta_name(PCMK_ENV_PHYSICAL_HOST);
        const char *target = getenv(target_var);
        const char *host_physical = getenv(phys_var);

        /* It is important we use the names by which the PE knows us */
        if (host_physical && safe_str_eq(target, "host")) {
            name = host_physical;

        } else {
            const char *host_pcmk = getenv(LRM_TARGET_ENV);

            if (host_pcmk) {
                name = host_pcmk;
            }
        }
        free(target_var);
        free(phys_var);
    }

    // TODO? Call get_local_node_name() if name == NULL
    // (currently would require linkage against libcrmcluster)
    return name;
}
