/*
 * Copyright 2004-2021 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 <crm_resource.h>
#include <crm/lrmd_internal.h>
#include <crm/common/cmdline_internal.h>
#include <crm/common/lists_internal.h>
#include <pacemaker-internal.h>

#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <libgen.h>
#include <time.h>

#include <crm/crm.h>
#include <crm/stonith-ng.h>
#include <crm/common/ipc_controld.h>
#include <crm/cib/internal.h>

#define SUMMARY "crm_resource - perform tasks related to Pacemaker cluster resources"

enum rsc_command {
    cmd_none = 0,           // No command option given (yet)
    cmd_ban,
    cmd_cleanup,
    cmd_clear,
    cmd_colocations,
    cmd_colocations_deep,
    cmd_cts,
    cmd_delete,
    cmd_delete_param,
    cmd_digests,
    cmd_execute_agent,
    cmd_fail,
    cmd_get_param,
    cmd_get_property,
    cmd_list_active_ops,
    cmd_list_agents,
    cmd_list_all_ops,
    cmd_list_alternatives,
    cmd_list_instances,
    cmd_list_providers,
    cmd_list_resources,
    cmd_list_standards,
    cmd_locate,
    cmd_metadata,
    cmd_move,
    cmd_query_raw_xml,
    cmd_query_xml,
    cmd_refresh,
    cmd_restart,
    cmd_set_param,
    cmd_set_property,
    cmd_wait,
    cmd_why,
};

struct {
    enum rsc_command rsc_cmd;     // crm_resource command to perform

    // Infrastructure that given command needs to work
    gboolean require_cib;         // Whether command requires CIB IPC
    int cib_options;              // Options to use with CIB IPC calls
    gboolean require_crmd;        // Whether command requires controller IPC
    gboolean require_dataset;     // Whether command requires populated data set
    gboolean require_resource;    // Whether command requires resource specified
    gboolean require_node;        // Whether command requires node specified
    int find_flags;               // Flags to use when searching for resource

    // Command-line option values
    gchar *rsc_id;                // Value of --resource
    gchar *rsc_type;              // Value of --resource-type
    gboolean force;               // --force was given
    gboolean clear_expired;       // --expired was given
    gboolean recursive;           // --recursive was given
    gboolean promoted_role_only;  // --promoted was given
    gchar *host_uname;            // Value of --node
    gchar *interval_spec;         // Value of --interval
    gchar *move_lifetime;         // Value of --lifetime
    gchar *operation;             // Value of --operation
    const char *attr_set_type;    // Instance, meta, or utilization attribute
    gchar *prop_id;               // --nvpair (attribute XML ID)
    char *prop_name;              // Attribute name
    gchar *prop_set;              // --set-name (attribute block XML ID)
    gchar *prop_value;            // --parameter-value (attribute value)
    int timeout_ms;               // Parsed from --timeout value
    char *agent_spec;             // Standard and/or provider and/or agent
    gchar *xml_file;              // Value of (deprecated) --xml-file
    int check_level;              // Optional value of --validate or --force-check

    // Resource configuration specified via command-line arguments
    gboolean cmdline_config;      // Resource configuration was via arguments
    char *v_agent;                // Value of --agent
    char *v_class;                // Value of --class
    char *v_provider;             // Value of --provider
    GHashTable *cmdline_params;   // Resource parameters specified

    // Positional command-line arguments
    gchar **remainder;            // Positional arguments as given
    GHashTable *override_params;  // Resource parameter values that override config
} options = {
    .attr_set_type = XML_TAG_ATTR_SETS,
    .check_level = -1,
    .cib_options = cib_sync_call,
    .require_cib = TRUE,
    .require_dataset = TRUE,
    .require_resource = TRUE,
};

#if 0
// @COMPAT @TODO enable this at next backward compatibility break
#define SET_COMMAND(cmd) do {                                               \
        if (options.rsc_cmd != cmd_none) {                                  \
            g_set_error(error, PCMK__EXITC_ERROR, CRM_EX_USAGE,             \
                        "Only one command option may be specified");        \
            return FALSE;                                                   \
        }                                                                   \
        options.rsc_cmd = (cmd);                                            \
    } while (0)
#else
#define SET_COMMAND(cmd) do { options.rsc_cmd = (cmd); } while (0)
#endif

gboolean agent_provider_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean attr_set_type_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean class_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean cleanup_refresh_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean delete_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean expired_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean list_agents_cb(const gchar *option_name, const gchar *optarg,
                        gpointer data, GError **error);
gboolean list_providers_cb(const gchar *option_name, const gchar *optarg,
                           gpointer data, GError **error);
gboolean list_standards_cb(const gchar *option_name, const gchar *optarg,
                           gpointer data, GError **error);
gboolean list_alternatives_cb(const gchar *option_name, const gchar *optarg,
                              gpointer data, GError **error);
gboolean metadata_cb(const gchar *option_name, const gchar *optarg,
                     gpointer data, GError **error);
gboolean option_cb(const gchar *option_name, const gchar *optarg,
                   gpointer data, GError **error);
gboolean fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean flag_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean get_param_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean list_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean set_delete_param_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean set_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean timeout_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean validate_or_force_cb(const gchar *option_name, const gchar *optarg,
                              gpointer data, GError **error);
gboolean restart_cb(const gchar *option_name, const gchar *optarg,
                    gpointer data, GError **error);
gboolean digests_cb(const gchar *option_name, const gchar *optarg,
                    gpointer data, GError **error);
gboolean wait_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);
gboolean why_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error);

static crm_exit_t exit_code = CRM_EX_OK;
static pcmk__output_t *out = NULL;
static pcmk__common_args_t *args = NULL;

// Things that should be cleaned up on exit
static GError *error = NULL;
static GMainLoop *mainloop = NULL;
static cib_t *cib_conn = NULL;
static pcmk_ipc_api_t *controld_api = NULL;
static pe_working_set_t *data_set = NULL;

#define MESSAGE_TIMEOUT_S 60

#define INDENT "                                    "

static pcmk__supported_format_t formats[] = {
    PCMK__SUPPORTED_FORMAT_NONE,
    PCMK__SUPPORTED_FORMAT_TEXT,
    PCMK__SUPPORTED_FORMAT_XML,
    { NULL, NULL, NULL }
};

// Clean up and exit
static crm_exit_t
bye(crm_exit_t ec)
{
    pcmk__output_and_clear_error(error, out);

    if (out != NULL) {
        out->finish(out, ec, true, NULL);
        pcmk__output_free(out);
    }

    if (cib_conn != NULL) {
        cib_t *save_cib_conn = cib_conn;

        cib_conn = NULL; // Ensure we can't free this twice
        save_cib_conn->cmds->signoff(save_cib_conn);
        cib_delete(save_cib_conn);
    }
    if (controld_api != NULL) {
        pcmk_ipc_api_t *save_controld_api = controld_api;

        controld_api = NULL; // Ensure we can't free this twice
        pcmk_free_ipc_api(save_controld_api);
    }
    if (mainloop != NULL) {
        g_main_loop_unref(mainloop);
        mainloop = NULL;
    }
    pe_free_working_set(data_set);
    data_set = NULL;
    crm_exit(ec);
    return ec;
}

static void
quit_main_loop(crm_exit_t ec)
{
    exit_code = ec;
    if (mainloop != NULL) {
        GMainLoop *mloop = mainloop;

        mainloop = NULL; // Don't re-enter this block
        pcmk_quit_main_loop(mloop, 10);
        g_main_loop_unref(mloop);
    }
}

static gboolean
resource_ipc_timeout(gpointer data)
{
    // Start with newline because "Waiting for ..." message doesn't have one
    if (error != NULL) {
        g_clear_error(&error);
    }

    g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_TIMEOUT,
                "Aborting because no messages received in %d seconds", MESSAGE_TIMEOUT_S);

    quit_main_loop(CRM_EX_TIMEOUT);
    return FALSE;
}

static void
controller_event_callback(pcmk_ipc_api_t *api, enum pcmk_ipc_event event_type,
                          crm_exit_t status, void *event_data, void *user_data)
{
    switch (event_type) {
        case pcmk_ipc_event_disconnect:
            if (exit_code == CRM_EX_DISCONNECT) { // Unexpected
                crm_info("Connection to controller was terminated");
            }
            quit_main_loop(exit_code);
            break;

        case pcmk_ipc_event_reply:
            if (status != CRM_EX_OK) {
                out->err(out, "Error: bad reply from controller: %s",
                         crm_exit_str(status));
                pcmk_disconnect_ipc(api);
                quit_main_loop(status);
            } else {
                if ((pcmk_controld_api_replies_expected(api) == 0)
                    && mainloop && g_main_loop_is_running(mainloop)) {
                    out->info(out, "... got reply (done)");
                    crm_debug("Got all the replies we expected");
                    pcmk_disconnect_ipc(api);
                    quit_main_loop(CRM_EX_OK);
                } else {
                    out->info(out, "... got reply");
                }
            }
            break;

        default:
            break;
    }
}

static void
start_mainloop(pcmk_ipc_api_t *capi)
{
    unsigned int count = pcmk_controld_api_replies_expected(capi);

    if (count > 0) {
        out->info(out, "Waiting for %d %s from the controller",
                  count, pcmk__plural_alt(count, "reply", "replies"));
        exit_code = CRM_EX_DISCONNECT; // For unexpected disconnects
        mainloop = g_main_loop_new(NULL, FALSE);
        g_timeout_add(MESSAGE_TIMEOUT_S * 1000, resource_ipc_timeout, NULL);
        g_main_loop_run(mainloop);
    }
}

static int
compare_id(gconstpointer a, gconstpointer b)
{
    return strcmp((const char *)a, (const char *)b);
}

static GList *
build_constraint_list(xmlNode *root)
{
    GList *retval = NULL;
    xmlNode *cib_constraints = NULL;
    xmlXPathObjectPtr xpathObj = NULL;
    int ndx = 0;

    cib_constraints = get_object_root(XML_CIB_TAG_CONSTRAINTS, root);
    xpathObj = xpath_search(cib_constraints, "//" XML_CONS_TAG_RSC_LOCATION);

    for (ndx = 0; ndx < numXpathResults(xpathObj); ndx++) {
        xmlNode *match = getXpathResult(xpathObj, ndx);
        retval = g_list_insert_sorted(retval, (gpointer) ID(match), compare_id);
    }

    freeXpathObject(xpathObj);
    return retval;
}

/* short option letters still available: eEJkKXyYZ */

static GOptionEntry query_entries[] = {
    { "list", 'L', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb,
      "List all cluster resources with status",
      NULL },
    { "list-raw", 'l', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb,
      "List IDs of all instantiated resources (individual members\n"
      INDENT "rather than groups etc.)",
      NULL },
    { "list-cts", 'c', G_OPTION_FLAG_HIDDEN|G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb,
      NULL,
      NULL },
    { "list-operations", 'O', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb,
      "List active resource operations, optionally filtered by\n"
      INDENT "--resource and/or --node",
      NULL },
    { "list-all-operations", 'o', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, list_cb,
      "List all resource operations, optionally filtered by\n"
      INDENT "--resource and/or --node",
      NULL },
    { "list-standards", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
      list_standards_cb,
      "List supported standards",
      NULL },
    { "list-ocf-providers", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
      list_providers_cb,
      "List all available OCF providers",
      NULL },
    { "list-agents", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK,
      list_agents_cb,
      "List all agents available for the named standard and/or provider",
      "STD:PROV" },
    { "list-ocf-alternatives", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK,
      list_alternatives_cb,
      "List all available providers for the named OCF agent",
      "AGENT" },
    { "show-metadata", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK,
      metadata_cb,
      "Show the metadata for the named class:provider:agent",
      "SPEC" },
    { "query-xml", 'q', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
      "Show XML configuration of resource (after any template expansion)",
      NULL },
    { "query-xml-raw", 'w', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
      "Show XML configuration of resource (before any template expansion)",
      NULL },
    { "get-parameter", 'g', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, get_param_prop_cb,
      "Display named parameter for resource (use instance attribute\n"
      INDENT "unless --meta or --utilization is specified)",
      "PARAM" },
    { "get-property", 'G', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, get_param_prop_cb,
      "Display named property of resource ('class', 'type', or 'provider') "
      "(requires --resource)",
      "PROPERTY" },
    { "locate", 'W', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
      "Show node(s) currently running resource",
      NULL },
    { "stack", 'A', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
      "Display the (co)location constraints that apply to a resource\n"
      INDENT "and the resources is it colocated with",
      NULL },
    { "constraints", 'a', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
      "Display the (co)location constraints that apply to a resource",
      NULL },
    { "why", 'Y', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, why_cb,
      "Show why resources are not running, optionally filtered by\n"
      INDENT "--resource and/or --node",
      NULL },

    { NULL }
};

static GOptionEntry command_entries[] = {
    { "validate", 0, G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
      validate_or_force_cb,
      "Validate resource configuration by calling agent's validate-all\n"
      INDENT "action. The configuration may be specified either by giving an\n"
      INDENT "existing resource name with -r, or by specifying --class,\n"
      INDENT "--agent, and --provider arguments, along with any number of\n"
      INDENT "--option arguments. An optional LEVEL argument can be given\n"
      INDENT "to control the level of checking performed.",
      "LEVEL" },
    { "cleanup", 'C', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, cleanup_refresh_cb,
      "If resource has any past failures, clear its history and fail\n"
      INDENT "count. Optionally filtered by --resource, --node, --operation\n"
      INDENT "and --interval (otherwise all). --operation and --interval\n"
      INDENT "apply to fail counts, but entire history is always clear, to\n"
      INDENT "allow current state to be rechecked. If the named resource is\n"
      INDENT "part of a group, or one numbered instance of a clone or bundled\n"
      INDENT "resource, the clean-up applies to the whole collective resource\n"
      INDENT "unless --force is given.",
      NULL },
    { "refresh", 'R', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, cleanup_refresh_cb,
      "Delete resource's history (including failures) so its current state\n"
      INDENT "is rechecked. Optionally filtered by --resource and --node\n"
      INDENT "(otherwise all). If the named resource is part of a group, or one\n"
      INDENT "numbered instance of a clone or bundled resource, the refresh\n"
      INDENT "applies to the whole collective resource unless --force is given.",
      NULL },
    { "set-parameter", 'p', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, set_delete_param_cb,
      "Set named parameter for resource (requires -v). Use instance\n"
      INDENT "attribute unless --meta or --utilization is specified.",
      "PARAM" },
    { "delete-parameter", 'd', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, set_delete_param_cb,
      "Delete named parameter for resource. Use instance attribute\n"
      INDENT "unless --meta or --utilization is specified.",
      "PARAM" },
    { "set-property", 'S', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_CALLBACK, set_prop_cb,
      "Set named property of resource ('class', 'type', or 'provider') "
      "(requires -r, -t, -v)",
      "PROPERTY" },

    { NULL }
};

static GOptionEntry location_entries[] = {
    { "move", 'M', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
      "Create a constraint to move resource. If --node is specified,\n"
      INDENT "the constraint will be to move to that node, otherwise it\n"
      INDENT "will be to ban the current node. Unless --force is specified\n"
      INDENT "this will return an error if the resource is already running\n"
      INDENT "on the specified node. If --force is specified, this will\n"
      INDENT "always ban the current node.\n"
      INDENT "Optional: --lifetime, --promoted. NOTE: This may prevent the\n"
      INDENT "resource from running on its previous location until the\n"
      INDENT "implicit constraint expires or is removed with --clear.",
      NULL },
    { "ban", 'B', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
      "Create a constraint to keep resource off a node.\n"
      INDENT "Optional: --node, --lifetime, --promoted.\n"
      INDENT "NOTE: This will prevent the resource from running on the\n"
      INDENT "affected node until the implicit constraint expires or is\n"
      INDENT "removed with --clear. If --node is not specified, it defaults\n"
      INDENT "to the node currently running the resource for primitives\n"
      INDENT "and groups, or the promoted instance of promotable clones with\n"
      INDENT "promoted-max=1 (all other situations result in an error as\n"
      INDENT "there is no sane default).",
      NULL },
    { "clear", 'U', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, flag_cb,
      "Remove all constraints created by the --ban and/or --move\n"
      INDENT "commands. Requires: --resource. Optional: --node, --promoted,\n"
      INDENT "--expired. If --node is not specified, all constraints created\n"
      INDENT "by --ban and --move will be removed for the named resource. If\n"
      INDENT "--node and --force are specified, any constraint created by\n"
      INDENT "--move will be cleared, even if it is not for the specified\n"
      INDENT "node. If --expired is specified, only those constraints whose\n"
      INDENT "lifetimes have expired will be removed.",
      NULL },
    { "expired", 'e', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, expired_cb,
      "Modifies the --clear argument to remove constraints with\n"
      INDENT "expired lifetimes.",
      NULL },
    { "lifetime", 'u', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.move_lifetime,
      "Lifespan (as ISO 8601 duration) of created constraints (with\n"
      INDENT "-B, -M) see https://en.wikipedia.org/wiki/ISO_8601#Durations)",
      "TIMESPEC" },
    { "promoted", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
      &options.promoted_role_only,
      "Limit scope of command to promoted role (with -B, -M, -U). For\n"
      INDENT "-B and -M, previously promoted instances may remain\n"
      INDENT "active in the unpromoted role.",
      NULL },

    // Deprecated since 2.1.0
    { "master", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE,
      &options.promoted_role_only,
      "Deprecated: Use --promoted instead", NULL },

    { NULL }
};

static GOptionEntry advanced_entries[] = {
    { "delete", 'D', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, delete_cb,
      "(Advanced) Delete a resource from the CIB. Required: -t",
      NULL },
    { "fail", 'F', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, fail_cb,
      "(Advanced) Tell the cluster this resource has failed",
      NULL },
    { "restart", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, restart_cb,
      "(Advanced) Tell the cluster to restart this resource and\n"
      INDENT "anything that depends on it",
      NULL },
    { "wait", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, wait_cb,
      "(Advanced) Wait until the cluster settles into a stable state",
      NULL },
    { "digests", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, digests_cb,
      "(Advanced) Show parameter hashes that Pacemaker uses to detect\n"
      INDENT "configuration changes (only accurate if there is resource\n"
      INDENT "history on the specified node). Required: --resource, --node.\n"
      INDENT "Optional: any NAME=VALUE parameters will be used to override\n"
      INDENT "the configuration (to see what the hash would be with those\n"
      INDENT "changes).",
      NULL },
    { "force-demote", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
      validate_or_force_cb,
      "(Advanced) Bypass the cluster and demote a resource on the local\n"
      INDENT "node. Unless --force is specified, this will refuse to do so if\n"
      INDENT "the cluster believes the resource is a clone instance already\n"
      INDENT "running on the local node.",
      NULL },
    { "force-stop", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
      validate_or_force_cb,
      "(Advanced) Bypass the cluster and stop a resource on the local node",
      NULL },
    { "force-start", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
      validate_or_force_cb,
      "(Advanced) Bypass the cluster and start a resource on the local\n"
      INDENT "node. Unless --force is specified, this will refuse to do so if\n"
      INDENT "the cluster believes the resource is a clone instance already\n"
      INDENT "running on the local node.",
      NULL },
    { "force-promote", 0, G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK,
      validate_or_force_cb,
      "(Advanced) Bypass the cluster and promote a resource on the local\n"
      INDENT "node. Unless --force is specified, this will refuse to do so if\n"
      INDENT "the cluster believes the resource is a clone instance already\n"
      INDENT "running on the local node.",
      NULL },
    { "force-check", 0, G_OPTION_FLAG_OPTIONAL_ARG, G_OPTION_ARG_CALLBACK,
      validate_or_force_cb,
      "(Advanced) Bypass the cluster and check the state of a resource on\n"
      INDENT "the local node. An optional LEVEL argument can be given\n"
      INDENT "to control the level of checking performed.",
      "LEVEL" },

    { NULL }
};

static GOptionEntry addl_entries[] = {
    { "node", 'N', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.host_uname,
      "Node name",
      "NAME" },
    { "recursive", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.recursive,
      "Follow colocation chains when using --set-parameter",
      NULL },
    { "resource-type", 't', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.rsc_type,
      "Resource XML element (primitive, group, etc.) (with -D)",
      "ELEMENT" },
    { "parameter-value", 'v', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_value,
      "Value to use with -p",
      "PARAM" },
    { "meta", 'm', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb,
      "Use resource meta-attribute instead of instance attribute\n"
      INDENT "(with -p, -g, -d)",
      NULL },
    { "utilization", 'z', G_OPTION_FLAG_NO_ARG, G_OPTION_ARG_CALLBACK, attr_set_type_cb,
      "Use resource utilization attribute instead of instance attribute\n"
      INDENT "(with -p, -g, -d)",
      NULL },
    { "operation", 'n', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.operation,
      "Operation to clear instead of all (with -C -r)",
      "OPERATION" },
    { "interval", 'I', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.interval_spec,
      "Interval of operation to clear (default 0) (with -C -r -n)",
      "N" },
    { "class", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, class_cb,
      "The standard the resource agent conforms to (for example, ocf).\n"
      INDENT "Use with --agent, --provider, --option, and --validate.",
      "CLASS" },
    { "agent", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, agent_provider_cb,
      "The agent to use (for example, IPaddr). Use with --class,\n"
      INDENT "--provider, --option, and --validate.",
      "AGENT" },
    { "provider", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, agent_provider_cb,
      "The vendor that supplies the resource agent (for example,\n"
      INDENT "heartbeat). Use with --class, --agent, --option, and --validate.",
      "PROVIDER" },
    { "option", 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, option_cb,
      "Specify a device configuration parameter as NAME=VALUE (may be\n"
      INDENT "specified multiple times). Use with --validate and without the\n"
      INDENT "-r option.",
      "PARAM" },
    { "set-name", 's', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_set,
      "(Advanced) XML ID of attributes element to use (with -p, -d)",
      "ID" },
    { "nvpair", 'i', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.prop_id,
      "(Advanced) XML ID of nvpair element to use (with -p, -d)",
      "ID" },
    { "timeout", 'T', G_OPTION_FLAG_NONE, G_OPTION_ARG_CALLBACK, timeout_cb,
      "(Advanced) Abort if command does not finish in this time (with\n"
      INDENT "--restart, --wait, --force-*)",
      "N" },
    { "force", 'f', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &options.force,
      "If making CIB changes, do so regardless of quorum. See help for\n"
      INDENT "individual commands for additional behavior.",
      NULL },
    { "xml-file", 'x', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_FILENAME, &options.xml_file,
      NULL,
      "FILE" },
    { "host-uname", 'H', G_OPTION_FLAG_HIDDEN, G_OPTION_ARG_STRING, &options.host_uname,
      NULL,
      "HOST" },

    { NULL }
};

gboolean
agent_provider_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
    options.cmdline_config = TRUE;
    options.require_resource = FALSE;

    if (pcmk__str_eq(option_name, "--provider", pcmk__str_casei)) {
        if (options.v_provider) {
           free(options.v_provider);
        }
        options.v_provider = strdup(optarg);
    } else {
        if (options.v_agent) {
           free(options.v_agent);
        }
        options.v_agent = strdup(optarg);
    }

    return TRUE;
}

gboolean
attr_set_type_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
    if (pcmk__str_any_of(option_name, "-m", "--meta", NULL)) {
        options.attr_set_type = XML_TAG_META_SETS;
    } else if (pcmk__str_any_of(option_name, "-z", "--utilization", NULL)) {
        options.attr_set_type = XML_TAG_UTILIZATION;
    }

    return TRUE;
}

gboolean
class_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
    if (options.v_class != NULL) {
        free(options.v_class);
    }

    options.v_class = strdup(optarg);

    options.cmdline_config = TRUE;
    options.require_resource = FALSE;
    return TRUE;
}

gboolean
cleanup_refresh_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
    if (pcmk__str_any_of(option_name, "-C", "--cleanup", NULL)) {
        SET_COMMAND(cmd_cleanup);
    } else {
        SET_COMMAND(cmd_refresh);
    }

    options.require_resource = FALSE;
    if (getenv("CIB_file") == NULL) {
        options.require_crmd = TRUE;
    }
    options.find_flags = pe_find_renamed|pe_find_anon;
    return TRUE;
}

gboolean
delete_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
    options.require_dataset = FALSE;
    SET_COMMAND(cmd_delete);
    options.find_flags = pe_find_renamed|pe_find_any;
    return TRUE;
}

gboolean
expired_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
    options.clear_expired = TRUE;
    options.require_resource = FALSE;
    return TRUE;
}

static void
get_agent_spec(const gchar *optarg)
{
    options.require_cib = FALSE;
    options.require_dataset = FALSE;
    options.require_resource = FALSE;
    if (options.agent_spec != NULL) {
        free(options.agent_spec);
        options.agent_spec = NULL;
    }
    if (optarg != NULL) {
        options.agent_spec = strdup(optarg);
    }
}

gboolean
list_agents_cb(const gchar *option_name, const gchar *optarg, gpointer data,
               GError **error)
{
    SET_COMMAND(cmd_list_agents);
    get_agent_spec(optarg);
    return TRUE;
}

gboolean
list_providers_cb(const gchar *option_name, const gchar *optarg, gpointer data,
                  GError **error)
{
    SET_COMMAND(cmd_list_providers);
    get_agent_spec(optarg);
    return TRUE;
}

gboolean
list_standards_cb(const gchar *option_name, const gchar *optarg, gpointer data,
                  GError **error)
{
    SET_COMMAND(cmd_list_standards);
    options.require_cib = FALSE;
    options.require_dataset = FALSE;
    options.require_resource = FALSE;
    return TRUE;
}

gboolean
list_alternatives_cb(const gchar *option_name, const gchar *optarg,
                     gpointer data, GError **error)
{
    SET_COMMAND(cmd_list_alternatives);
    get_agent_spec(optarg);
    return TRUE;
}

gboolean
metadata_cb(const gchar *option_name, const gchar *optarg, gpointer data,
            GError **error)
{
    SET_COMMAND(cmd_metadata);
    get_agent_spec(optarg);
    return TRUE;
}

gboolean
option_cb(const gchar *option_name, const gchar *optarg, gpointer data,
          GError **error)
{
    char *name = NULL;
    char *value = NULL;

    if (pcmk__scan_nvpair(optarg, &name, &value) != 2) {
        return FALSE;
    }
    if (options.cmdline_params == NULL) {
        options.cmdline_params = pcmk__strkey_table(free, free);
    }
    g_hash_table_replace(options.cmdline_params, name, value);
    return TRUE;
}

gboolean
fail_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
    options.require_crmd = TRUE;
    options.require_node = TRUE;
    SET_COMMAND(cmd_fail);
    return TRUE;
}

gboolean
flag_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
    if (pcmk__str_any_of(option_name, "-U", "--clear", NULL)) {
        options.find_flags = pe_find_renamed|pe_find_anon;
        SET_COMMAND(cmd_clear);
    } else if (pcmk__str_any_of(option_name, "-B", "--ban", NULL)) {
        options.find_flags = pe_find_renamed|pe_find_anon;
        SET_COMMAND(cmd_ban);
    } else if (pcmk__str_any_of(option_name, "-M", "--move", NULL)) {
        options.find_flags = pe_find_renamed|pe_find_anon;
        SET_COMMAND(cmd_move);
    } else if (pcmk__str_any_of(option_name, "-q", "--query-xml", NULL)) {
        options.find_flags = pe_find_renamed|pe_find_any;
        SET_COMMAND(cmd_query_xml);
    } else if (pcmk__str_any_of(option_name, "-w", "--query-xml-raw", NULL)) {
        options.find_flags = pe_find_renamed|pe_find_any;
        SET_COMMAND(cmd_query_raw_xml);
    } else if (pcmk__str_any_of(option_name, "-W", "--locate", NULL)) {
        options.find_flags = pe_find_renamed|pe_find_anon;
        SET_COMMAND(cmd_locate);
    } else if (pcmk__str_any_of(option_name, "-A", "--stack", NULL)) {
        options.find_flags = pe_find_renamed|pe_find_anon;
        SET_COMMAND(cmd_colocations_deep);
    } else {
        options.find_flags = pe_find_renamed|pe_find_anon;
        SET_COMMAND(cmd_colocations);
    }

    return TRUE;
}

gboolean
get_param_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
    if (pcmk__str_any_of(option_name, "-g", "--get-parameter", NULL)) {
        SET_COMMAND(cmd_get_param);
    } else {
        SET_COMMAND(cmd_get_property);
    }

    if (options.prop_name) {
        free(options.prop_name);
    }

    options.prop_name = strdup(optarg);
    options.find_flags = pe_find_renamed|pe_find_any;
    return TRUE;
}

gboolean
list_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
    if (pcmk__str_any_of(option_name, "-c", "--list-cts", NULL)) {
        SET_COMMAND(cmd_cts);
    } else if (pcmk__str_any_of(option_name, "-L", "--list", NULL)) {
        SET_COMMAND(cmd_list_resources);
    } else if (pcmk__str_any_of(option_name, "-l", "--list-raw", NULL)) {
        SET_COMMAND(cmd_list_instances);
    } else if (pcmk__str_any_of(option_name, "-O", "--list-operations", NULL)) {
        SET_COMMAND(cmd_list_active_ops);
    } else {
        SET_COMMAND(cmd_list_all_ops);
    }

    options.require_resource = FALSE;
    return TRUE;
}

gboolean
set_delete_param_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
    if (pcmk__str_any_of(option_name, "-p", "--set-parameter", NULL)) {
        SET_COMMAND(cmd_set_param);
    } else {
        SET_COMMAND(cmd_delete_param);
    }

    if (options.prop_name) {
        free(options.prop_name);
    }

    options.prop_name = strdup(optarg);
    options.find_flags = pe_find_renamed|pe_find_any;
    return TRUE;
}

gboolean
set_prop_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
    options.require_dataset = FALSE;

    if (options.prop_name) {
        free(options.prop_name);
    }

    options.prop_name = strdup(optarg);
    SET_COMMAND(cmd_set_property);
    options.find_flags = pe_find_renamed|pe_find_any;
    return TRUE;
}

gboolean
timeout_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
    options.timeout_ms = crm_get_msec(optarg);
    return TRUE;
}

gboolean
validate_or_force_cb(const gchar *option_name, const gchar *optarg,
                     gpointer data, GError **error)
{
    SET_COMMAND(cmd_execute_agent);
    if (options.operation) {
        g_free(options.operation);
    }
    options.operation = g_strdup(option_name + 2); // skip "--"
    options.find_flags = pe_find_renamed|pe_find_anon;
    if (options.override_params == NULL) {
        options.override_params = pcmk__strkey_table(free, free);
    }

    if (optarg != NULL) {
        if (pcmk__scan_min_int(optarg, &options.check_level, 0) != pcmk_rc_ok) {
            g_set_error(error, G_OPTION_ERROR, CRM_EX_INVALID_PARAM,
                        "Invalid check level setting: %s", optarg);
            return FALSE;
        }
    }

    return TRUE;
}

gboolean
restart_cb(const gchar *option_name, const gchar *optarg, gpointer data,
           GError **error)
{
    SET_COMMAND(cmd_restart);
    options.find_flags = pe_find_renamed|pe_find_anon;
    return TRUE;
}

gboolean
digests_cb(const gchar *option_name, const gchar *optarg, gpointer data,
           GError **error)
{
    SET_COMMAND(cmd_digests);
    options.find_flags = pe_find_renamed|pe_find_anon;
    if (options.override_params == NULL) {
        options.override_params = pcmk__strkey_table(free, free);
    }
    options.require_node = TRUE;
    options.require_dataset = TRUE;
    return TRUE;
}

gboolean
wait_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
    SET_COMMAND(cmd_wait);
    options.require_resource = FALSE;
    options.require_dataset = FALSE;
    return TRUE;
}

gboolean
why_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) {
    options.require_resource = FALSE;
    SET_COMMAND(cmd_why);
    options.find_flags = pe_find_renamed|pe_find_anon;
    return TRUE;
}

static int
ban_or_move(pcmk__output_t *out, pe_resource_t *rsc, const char *move_lifetime)
{
    int rc = pcmk_rc_ok;
    pe_node_t *current = NULL;
    unsigned int nactive = 0;

    CRM_CHECK(rsc != NULL, return EINVAL);

    current = pe__find_active_requires(rsc, &nactive);

    if (nactive == 1) {
        rc = cli_resource_ban(out, options.rsc_id, current->details->uname, move_lifetime, NULL,
                              cib_conn, options.cib_options, options.promoted_role_only);

    } else if (pcmk_is_set(rsc->flags, pe_rsc_promotable)) {
        int count = 0;
        GList *iter = NULL;

        current = NULL;
        for(iter = rsc->children; iter; iter = iter->next) {
            pe_resource_t *child = (pe_resource_t *)iter->data;
            enum rsc_role_e child_role = child->fns->state(child, TRUE);

            if (child_role == RSC_ROLE_PROMOTED) {
                count++;
                current = pe__current_node(child);
            }
        }

        if(count == 1 && current) {
            rc = cli_resource_ban(out, options.rsc_id, current->details->uname, move_lifetime, NULL,
                                  cib_conn, options.cib_options, options.promoted_role_only);

        } else {
            rc = EINVAL;
            g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                        "Resource '%s' not moved: active in %d locations (promoted in %d).\n"
                        "To prevent '%s' from running on a specific location, "
                        "specify a node."
                        "To prevent '%s' from being promoted at a specific "
                        "location, specify a node and the --promoted option.",
                        options.rsc_id, nactive, count, options.rsc_id, options.rsc_id);
        }

    } else {
        rc = EINVAL;
        g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                    "Resource '%s' not moved: active in %d locations.\n"
                    "To prevent '%s' from running on a specific location, "
                    "specify a node.",
                    options.rsc_id, nactive, options.rsc_id);
    }

    return rc;
}

static void
cleanup(pcmk__output_t *out, pe_resource_t *rsc)
{
    int rc = pcmk_rc_ok;

    if (options.force == FALSE) {
        rsc = uber_parent(rsc);
    }

    crm_debug("Erasing failures of %s (%s requested) on %s",
              rsc->id, options.rsc_id, (options.host_uname? options.host_uname: "all nodes"));
    rc = cli_resource_delete(controld_api, options.host_uname, rsc, options.operation,
                             options.interval_spec, TRUE, data_set, options.force);

    if ((rc == pcmk_rc_ok) && !out->is_quiet(out)) {
        // Show any reasons why resource might stay stopped
        cli_resource_check(out, cib_conn, rsc);
    }

    if (rc == pcmk_rc_ok) {
        start_mainloop(controld_api);
    }
}

static int
clear_constraints(pcmk__output_t *out, xmlNodePtr *cib_xml_copy)
{
    GList *before = NULL;
    GList *after = NULL;
    GList *remaining = NULL;
    GList *ele = NULL;
    pe_node_t *dest = NULL;
    int rc = pcmk_rc_ok;

    if (!out->is_quiet(out)) {
        before = build_constraint_list(data_set->input);
    }

    if (options.clear_expired) {
        rc = cli_resource_clear_all_expired(data_set->input, cib_conn, options.cib_options,
                                            options.rsc_id, options.host_uname,
                                            options.promoted_role_only);

    } else if (options.host_uname) {
        dest = pe_find_node(data_set->nodes, options.host_uname);
        if (dest == NULL) {
            rc = pcmk_rc_node_unknown;
            if (!out->is_quiet(out)) {
                g_list_free(before);
            }
            return rc;
        }
        rc = cli_resource_clear(options.rsc_id, dest->details->uname, NULL,
                                cib_conn, options.cib_options, TRUE, options.force);

    } else {
        rc = cli_resource_clear(options.rsc_id, NULL, data_set->nodes,
                                cib_conn, options.cib_options, TRUE, options.force);
    }

    if (!out->is_quiet(out)) {
        rc = cib_conn->cmds->query(cib_conn, NULL, cib_xml_copy, cib_scope_local | cib_sync_call);
        rc = pcmk_legacy2rc(rc);

        if (rc != pcmk_rc_ok) {
            g_set_error(&error, PCMK__RC_ERROR, rc,
                        "Could not get modified CIB: %s\n", pcmk_strerror(rc));
            g_list_free(before);
            free_xml(*cib_xml_copy);
            *cib_xml_copy = NULL;
            return rc;
        }

        data_set->input = *cib_xml_copy;
        cluster_status(data_set);

        after = build_constraint_list(data_set->input);
        remaining = pcmk__subtract_lists(before, after, (GCompareFunc) strcmp);

        for (ele = remaining; ele != NULL; ele = ele->next) {
            out->info(out, "Removing constraint: %s", (char *) ele->data);
        }

        g_list_free(before);
        g_list_free(after);
        g_list_free(remaining);
    }

    return rc;
}

static int
delete(void)
{
    int rc = pcmk_rc_ok;
    xmlNode *msg_data = NULL;

    if (options.rsc_type == NULL) {
        rc = ENXIO;
        g_set_error(&error, PCMK__RC_ERROR, rc,
                    "You need to specify a resource type with -t");
        return rc;
    }

    msg_data = create_xml_node(NULL, options.rsc_type);
    crm_xml_add(msg_data, XML_ATTR_ID, options.rsc_id);

    rc = cib_conn->cmds->remove(cib_conn, XML_CIB_TAG_RESOURCES, msg_data,
                                options.cib_options);
    rc = pcmk_legacy2rc(rc);
    free_xml(msg_data);
    return rc;
}

static int
list_agents(pcmk__output_t *out, const char *agent_spec)
{
    int rc = pcmk_rc_ok;
    char *provider = strchr(agent_spec, ':');
    lrmd_t *lrmd_conn = lrmd_api_new();
    lrmd_list_t *list = NULL;

    if (provider) {
        *provider++ = 0;
    }

    rc = lrmd_conn->cmds->list_agents(lrmd_conn, &list, agent_spec, provider);

    if (rc > 0) {
        rc = out->message(out, "agents-list", list, agent_spec, provider);
    } else {
        rc = pcmk_rc_error;
    }

    if (rc != pcmk_rc_ok) {
        if (provider == NULL) {
            g_set_error(&error, PCMK__RC_ERROR, rc,
                        "No agents found for standard '%s'", agent_spec);
        } else {
            g_set_error(&error, PCMK__RC_ERROR, rc,
                        "No agents found for standard '%s' and provider '%s'",
                        agent_spec, provider);
        }
    }

    lrmd_api_delete(lrmd_conn);
    return rc;
}

static int
list_providers(pcmk__output_t *out, const char *agent_spec)
{
    int rc;
    const char *text = NULL;
    lrmd_t *lrmd_conn = lrmd_api_new();
    lrmd_list_t *list = NULL;

    switch (options.rsc_cmd) {
        case cmd_list_alternatives:
            rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, agent_spec, &list);

            if (rc > 0) {
                rc = out->message(out, "alternatives-list", list, agent_spec);
            } else {
                rc = pcmk_rc_error;
            }

            text = "OCF providers";
            break;
        case cmd_list_standards:
            rc = lrmd_conn->cmds->list_standards(lrmd_conn, &list);

            if (rc > 0) {
                rc = out->message(out, "standards-list", list);
            } else {
                rc = pcmk_rc_error;
            }

            text = "standards";
            break;
        case cmd_list_providers:
            rc = lrmd_conn->cmds->list_ocf_providers(lrmd_conn, agent_spec, &list);

            if (rc > 0) {
                rc = out->message(out, "providers-list", list, agent_spec);
            } else {
                rc = pcmk_rc_error;
            }

            text = "OCF providers";
            break;
        default:
            g_set_error(&error, PCMK__RC_ERROR, pcmk_rc_error, "Bug");
            lrmd_api_delete(lrmd_conn);
            return pcmk_rc_error;
    }

    if (rc != pcmk_rc_ok) {
        if (agent_spec != NULL) {
            rc = ENXIO;
            g_set_error(&error, PCMK__RC_ERROR, rc,
                        "No %s found for %s", text, agent_spec);

        } else {
            rc = ENXIO;
            g_set_error(&error, PCMK__RC_ERROR, rc,
                        "No %s found", text);
        }
    }

    lrmd_api_delete(lrmd_conn);
    return rc;
}

static int
populate_working_set(xmlNodePtr *cib_xml_copy)
{
    int rc = pcmk_rc_ok;

    if (options.xml_file != NULL) {
        *cib_xml_copy = filename2xml(options.xml_file);
        if (*cib_xml_copy == NULL) {
            rc = pcmk_rc_cib_corrupt;
        }
    } else {
        rc = cib_conn->cmds->query(cib_conn, NULL, cib_xml_copy, cib_scope_local | cib_sync_call);
        rc = pcmk_legacy2rc(rc);
    }

    if (rc == pcmk_rc_ok) {
        data_set = pe_new_working_set();
        if (data_set == NULL) {
            rc = ENOMEM;
        } else {
            pe__set_working_set_flags(data_set,
                                      pe_flag_no_counts|pe_flag_no_compat);
            data_set->priv = out;
            rc = update_working_set_xml(data_set, cib_xml_copy);
        }
    }

    if (rc != pcmk_rc_ok) {
        free_xml(*cib_xml_copy);
        *cib_xml_copy = NULL;
        return rc;
    }

    cluster_status(data_set);
    return pcmk_rc_ok;
}

static int
refresh(pcmk__output_t *out)
{
    int rc = pcmk_rc_ok;
    const char *router_node = options.host_uname;
    int attr_options = pcmk__node_attr_none;

    if (options.host_uname) {
        pe_node_t *node = pe_find_node(data_set->nodes, options.host_uname);

        if (pe__is_guest_or_remote_node(node)) {
            node = pe__current_node(node->details->remote_rsc);
            if (node == NULL) {
                rc = ENXIO;
                g_set_error(&error, PCMK__RC_ERROR, rc,
                            "No cluster connection to Pacemaker Remote node %s detected",
                            options.host_uname);
                return rc;
            }
            router_node = node->details->uname;
            attr_options |= pcmk__node_attr_remote;
        }
    }

    if (controld_api == NULL) {
        out->info(out, "Dry run: skipping clean-up of %s due to CIB_file",
                  options.host_uname? options.host_uname : "all nodes");
        rc = pcmk_rc_ok;
        return rc;
    }

    crm_debug("Re-checking the state of all resources on %s", options.host_uname?options.host_uname:"all nodes");

    rc = pcmk__node_attr_request_clear(NULL, options.host_uname,
                                       NULL, NULL, NULL,
                                       NULL, attr_options);

    if (pcmk_controld_api_reprobe(controld_api, options.host_uname,
                                  router_node) == pcmk_rc_ok) {
        start_mainloop(controld_api);
    }

    return rc;
}

static void
refresh_resource(pcmk__output_t *out, pe_resource_t *rsc)
{
    int rc = pcmk_rc_ok;

    if (options.force == FALSE) {
        rsc = uber_parent(rsc);
    }

    crm_debug("Re-checking the state of %s (%s requested) on %s",
              rsc->id, options.rsc_id, (options.host_uname? options.host_uname: "all nodes"));
    rc = cli_resource_delete(controld_api, options.host_uname, rsc, NULL,
                             0, FALSE, data_set, options.force);

    if ((rc == pcmk_rc_ok) && !out->is_quiet(out)) {
        // Show any reasons why resource might stay stopped
        cli_resource_check(out, cib_conn, rsc);
    }

    if (rc == pcmk_rc_ok) {
        start_mainloop(controld_api);
    }
}

static int
set_property(void)
{
    int rc = pcmk_rc_ok;
    xmlNode *msg_data = NULL;

    if (pcmk__str_empty(options.rsc_type)) {
        g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                    "Must specify -t with resource type");
        rc = ENXIO;
        return rc;

    } else if (pcmk__str_empty(options.prop_value)) {
        g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                    "Must supply -v with new value");
        rc = ENXIO;
        return rc;
    }

    CRM_LOG_ASSERT(options.prop_name != NULL);

    msg_data = create_xml_node(NULL, options.rsc_type);
    crm_xml_add(msg_data, XML_ATTR_ID, options.rsc_id);
    crm_xml_add(msg_data, options.prop_name, options.prop_value);

    rc = cib_conn->cmds->modify(cib_conn, XML_CIB_TAG_RESOURCES, msg_data,
                                options.cib_options);
    rc = pcmk_legacy2rc(rc);
    free_xml(msg_data);

    return rc;
}

static int
show_metadata(pcmk__output_t *out, const char *agent_spec)
{
    int rc = pcmk_rc_ok;
    char *standard = NULL;
    char *provider = NULL;
    char *type = NULL;
    char *metadata = NULL;
    lrmd_t *lrmd_conn = lrmd_api_new();

    rc = crm_parse_agent_spec(agent_spec, &standard, &provider, &type);
    rc = pcmk_legacy2rc(rc);

    if (rc == pcmk_rc_ok) {
        rc = lrmd_conn->cmds->get_metadata(lrmd_conn, standard,
                                           provider, type,
                                           &metadata, 0);
        rc = pcmk_legacy2rc(rc);

        if (metadata) {
            out->output_xml(out, "metadata", metadata);
        } else {
            /* We were given a validly formatted spec, but it doesn't necessarily
             * match up with anything that exists.  Use ENXIO as the return code
             * here because that maps to an exit code of CRM_EX_NOSUCH, which
             * probably is the most common reason to get here.
             */
            rc = ENXIO;
            g_set_error(&error, PCMK__RC_ERROR, rc,
                        "Metadata query for %s failed: %s",
                        agent_spec, pcmk_rc_str(rc));
        }
    } else {
        rc = ENXIO;
        g_set_error(&error, PCMK__RC_ERROR, rc,
                    "'%s' is not a valid agent specification", agent_spec);
    }

    lrmd_api_delete(lrmd_conn);
    return rc;
}

static void
validate_cmdline_config(void)
{
    // Cannot use both --resource and command-line resource configuration
    if (options.rsc_id != NULL) {
        g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                    "--resource cannot be used with --class, --agent, and --provider");

    // Not all commands support command-line resource configuration
    } else if (options.rsc_cmd != cmd_execute_agent) {
        g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                    "--class, --agent, and --provider can only be used with "
                    "--validate and --force-*");

    // Not all of --class, --agent, and --provider need to be given.  Not all
    // classes support the concept of a provider.  Check that what we were given
    // is valid.
    } else if (pcmk__str_eq(options.v_class, "stonith", pcmk__str_none)) {
        if (options.v_provider != NULL) {
            g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                        "stonith does not support providers");

        } else if (stonith_agent_exists(options.v_agent, 0) == FALSE) {
            g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                        "%s is not a known stonith agent", options.v_agent ? options.v_agent : "");
        }

    } else if (resources_agent_exists(options.v_class, options.v_provider, options.v_agent) == FALSE) {
        g_set_error(&error, PCMK__EXITC_ERROR, CRM_EX_USAGE,
                    "%s:%s:%s is not a known resource",
                    options.v_class ? options.v_class : "",
                    options.v_provider ? options.v_provider : "",
                    options.v_agent ? options.v_agent : "");
    }

    if (error != NULL) {
        return;
    }

    if (options.cmdline_params == NULL) {
        options.cmdline_params = pcmk__strkey_table(free, free);
    }
    options.require_resource = FALSE;
    options.require_dataset = FALSE;
    options.require_cib = FALSE;
}

static GOptionContext *
build_arg_context(pcmk__common_args_t *args, GOptionGroup **group) {
    GOptionContext *context = NULL;

    GOptionEntry extra_prog_entries[] = {
        { "quiet", 'Q', G_OPTION_FLAG_NONE, G_OPTION_ARG_NONE, &(args->quiet),
          "Be less descriptive in output.",
          NULL },
        { "resource", 'r', G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING, &options.rsc_id,
          "Resource ID",
          "ID" },
        { G_OPTION_REMAINING, 0, G_OPTION_FLAG_NONE, G_OPTION_ARG_STRING_ARRAY, &options.remainder,
          NULL,
          NULL },

        { NULL }
    };

    const char *description = "Examples:\n\n"
                              "List the available OCF agents:\n\n"
                              "\t# crm_resource --list-agents ocf\n\n"
                              "List the available OCF agents from the linux-ha project:\n\n"
                              "\t# crm_resource --list-agents ocf:heartbeat\n\n"
                              "Move 'myResource' to a specific node:\n\n"
                              "\t# crm_resource --resource myResource --move --node altNode\n\n"
                              "Allow (but not force) 'myResource' to move back to its original "
                              "location:\n\n"
                              "\t# crm_resource --resource myResource --clear\n\n"
                              "Stop 'myResource' (and anything that depends on it):\n\n"
                              "\t# crm_resource --resource myResource --set-parameter target-role "
                              "--meta --parameter-value Stopped\n\n"
                              "Tell the cluster not to manage 'myResource' (the cluster will not "
                              "attempt to start or stop the\n"
                              "resource under any circumstances; useful when performing maintenance "
                              "tasks on a resource):\n\n"
                              "\t# crm_resource --resource myResource --set-parameter is-managed "
                              "--meta --parameter-value false\n\n"
                              "Erase the operation history of 'myResource' on 'aNode' (the cluster "
                              "will 'forget' the existing\n"
                              "resource state, including any errors, and attempt to recover the"
                              "resource; useful when a resource\n"
                              "had failed permanently and has been repaired by an administrator):\n\n"
                              "\t# crm_resource --resource myResource --cleanup --node aNode\n\n";

    context = pcmk__build_arg_context(args, "text (default), xml", group, NULL);
    g_option_context_set_description(context, description);

    /* Add the -Q option, which cannot be part of the globally supported options
     * because some tools use that flag for something else.
     */
    pcmk__add_main_args(context, extra_prog_entries);

    pcmk__add_arg_group(context, "queries", "Queries:",
                        "Show query help", query_entries);
    pcmk__add_arg_group(context, "commands", "Commands:",
                        "Show command help", command_entries);
    pcmk__add_arg_group(context, "locations", "Locations:",
                        "Show location help", location_entries);
    pcmk__add_arg_group(context, "advanced", "Advanced:",
                        "Show advanced option help", advanced_entries);
    pcmk__add_arg_group(context, "additional", "Additional Options:",
                        "Show additional options", addl_entries);
    return context;
}

int
main(int argc, char **argv)
{
    xmlNode *cib_xml_copy = NULL;
    pe_resource_t *rsc = NULL;
    pe_node_t *node = NULL;
    int rc = pcmk_rc_ok;

    GOptionGroup *output_group = NULL;
    gchar **processed_args = NULL;
    GOptionContext *context = NULL;

    /*
     * Parse command line arguments
     */

    args = pcmk__new_common_args(SUMMARY);
    processed_args = pcmk__cmdline_preproc(argv, "GHINSTdginpstuvx");
    context = build_arg_context(args, &output_group);

    pcmk__register_formats(output_group, formats);
    if (!g_option_context_parse_strv(context, &processed_args, &error)) {
        exit_code = CRM_EX_USAGE;
        goto done;
    }

    pcmk__cli_init_logging("crm_resource", args->verbosity);

    rc = pcmk__output_new(&out, args->output_ty, args->output_dest, argv);
    if (rc != pcmk_rc_ok) {
        exit_code = CRM_EX_ERROR;
        g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Error creating output format %s: %s",
                    args->output_ty, pcmk_rc_str(rc));
        goto done;
    }

    pe__register_messages(out);
    crm_resource_register_messages(out);
    lrmd__register_messages(out);
    pcmk__register_lib_messages(out);

    out->quiet = args->quiet;

    crm_log_args(argc, argv);

    /*
     * Validate option combinations
     */

    // If the user didn't explicitly specify a command, list resources
    if (options.rsc_cmd == cmd_none) {
        options.rsc_cmd = cmd_list_resources;
        options.require_resource = FALSE;
    }

    // --expired without --clear/-U doesn't make sense
    if (options.clear_expired && (options.rsc_cmd != cmd_clear)) {
        exit_code = CRM_EX_USAGE;
        g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "--expired requires --clear or -U");
        goto done;
    }

    if ((options.remainder != NULL) && (options.override_params != NULL)) {
        // Commands that use positional arguments will create override_params
        for (gchar **s = options.remainder; *s; s++) {
            char *name = calloc(1, strlen(*s));
            char *value = calloc(1, strlen(*s));
            int rc = sscanf(*s, "%[^=]=%s", name, value);

            if (rc == 2) {
                g_hash_table_replace(options.override_params, name, value);

            } else {
                exit_code = CRM_EX_USAGE;
                g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                            "Error parsing '%s' as a name=value pair",
                            argv[optind]);
                free(value);
                free(name);
                goto done;
            }
        }

    } else if (options.remainder != NULL) {
        gchar **strv = NULL;
        gchar *msg = NULL;
        int i = 1;
        int len = 0;

        for (gchar **s = options.remainder; *s; s++) {
            len++;
        }

        CRM_ASSERT(len > 0);

        strv = calloc(len, sizeof(char *));
        strv[0] = strdup("non-option ARGV-elements:");

        for (gchar **s = options.remainder; *s; s++) {
            strv[i] = crm_strdup_printf("[%d of %d] %s\n", i, len, *s);
            i++;
        }

        exit_code = CRM_EX_USAGE;
        msg = g_strjoinv("", strv);
        g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "%s", msg);
        g_free(msg);

        for(i = 0; i < len; i++) {
            free(strv[i]);
        }
        free(strv);

        goto done;
    }

    if (pcmk__str_eq(args->output_ty, "xml", pcmk__str_none)) {
        /* Kind of a hack to display XML lists using a real tag instead of <list>.  This just
         * saves from having to write custom messages to build the lists around all these things
         */
        switch (options.rsc_cmd) {
            case cmd_execute_agent:
            case cmd_list_resources:
            case cmd_query_xml:
            case cmd_query_raw_xml:
            case cmd_list_active_ops:
            case cmd_list_all_ops:
            case cmd_colocations:
            case cmd_colocations_deep:
                pcmk__force_args(context, &error, "%s --xml-simple-list --xml-substitute", g_get_prgname());
                break;

            default:
                pcmk__force_args(context, &error, "%s --xml-substitute", g_get_prgname());
                break;
        }
    } else if (pcmk__str_eq(args->output_ty, "text", pcmk__str_null_matches)) {
        if (options.rsc_cmd == cmd_colocations || options.rsc_cmd == cmd_colocations_deep ||
            options.rsc_cmd == cmd_list_resources) {
            pcmk__force_args(context, &error, "%s --text-fancy", g_get_prgname());
        }
    }

    if (args->version) {
        out->version(out, false);
        goto done;
    }

    if (options.cmdline_config) {
        /* A resource configuration was given on the command line. Sanity-check
         * the values and set error if they don't make sense.
         */
        validate_cmdline_config();
        if (error != NULL) {
            exit_code = CRM_EX_USAGE;
            goto done;
        }

    } else if (options.cmdline_params != NULL) {
        // @COMPAT @TODO error out here when we can break backward compatibility
        g_hash_table_destroy(options.cmdline_params);
        options.cmdline_params = NULL;
    }

    if (options.require_resource && (options.rsc_id == NULL)) {
        rc = ENXIO;
        exit_code = CRM_EX_USAGE;
        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                    "Must supply a resource id with -r");
        goto done;
    }
    if (options.require_node && (options.host_uname == NULL)) {
        rc = ENXIO;
        exit_code = CRM_EX_USAGE;
        g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                    "Must supply a node name with -N");
        goto done;
    }

    /*
     * Set up necessary connections
     */

    if (options.force) {
        crm_debug("Forcing...");
        cib__set_call_options(options.cib_options, crm_system_name,
                              cib_quorum_override);
    }

    if (options.find_flags && options.rsc_id) {
        options.require_dataset = TRUE;
    }

    // Establish a connection to the CIB if needed
    if (options.require_cib) {
        cib_conn = cib_new();
        if ((cib_conn == NULL) || (cib_conn->cmds == NULL)) {
            exit_code = CRM_EX_DISCONNECT;
            g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                        "Could not create CIB connection");
            goto done;
        }
        rc = cib_conn->cmds->signon(cib_conn, crm_system_name, cib_command);
        rc = pcmk_legacy2rc(rc);
        if (rc != pcmk_rc_ok) {
            exit_code = pcmk_rc2exitc(rc);
            g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                        "Could not connect to the CIB: %s", pcmk_rc_str(rc));
            goto done;
        }
    }

    /* Populate working set from XML file if specified or CIB query otherwise */
    if (options.require_dataset) {
        rc = populate_working_set(&cib_xml_copy);
        if (rc != pcmk_rc_ok) {
            exit_code = pcmk_rc2exitc(rc);
            goto done;
        }
    }

    // If command requires that resource exist if specified, find it
    if (options.find_flags && options.rsc_id) {
        rsc = pe_find_resource_with_flags(data_set->resources, options.rsc_id,
                                          options.find_flags);
        if (rsc == NULL) {
            exit_code = CRM_EX_NOSUCH;
            g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                        "Resource '%s' not found", options.rsc_id);
            goto done;
        }
    }

    // If user supplied a node name, check whether it exists
    if ((options.host_uname != NULL) && (data_set != NULL)) {
        node = pe_find_node(data_set->nodes, options.host_uname);
    }

    // Establish a connection to the controller if needed
    if (options.require_crmd) {
        rc = pcmk_new_ipc_api(&controld_api, pcmk_ipc_controld);
        if (rc != pcmk_rc_ok) {
            exit_code = pcmk_rc2exitc(rc);
            g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                        "Error connecting to the controller: %s", pcmk_rc_str(rc));
            goto done;
        }
        pcmk_register_ipc_callback(controld_api, controller_event_callback,
                                   NULL);
        rc = pcmk_connect_ipc(controld_api, pcmk_ipc_dispatch_main);
        if (rc != pcmk_rc_ok) {
            exit_code = pcmk_rc2exitc(rc);
            g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                        "Error connecting to the controller: %s", pcmk_rc_str(rc));
            goto done;
        }
    }

    /*
     * Handle requested command
     */

    switch (options.rsc_cmd) {
        case cmd_list_resources: {
            GList *all = NULL;
            all = g_list_prepend(all, strdup("*"));
            rc = out->message(out, "resource-list", data_set,
                              pe_print_rsconly | pe_print_pending,
                              FALSE, TRUE, FALSE, TRUE, all, all, FALSE);
            g_list_free_full(all, free);

            if (rc == pcmk_rc_no_output) {
                rc = ENXIO;
            }
            break;
        }

        case cmd_list_instances:
            rc = out->message(out, "resource-names-list", data_set->resources);

            if (rc != pcmk_rc_ok) {
                rc = ENXIO;
            }

            break;

        case cmd_list_standards:
        case cmd_list_providers:
        case cmd_list_alternatives:
            rc = list_providers(out, options.agent_spec);
            break;

        case cmd_list_agents:
            rc = list_agents(out, options.agent_spec);
            break;

        case cmd_metadata:
            rc = show_metadata(out, options.agent_spec);
            break;

        case cmd_restart:
            /* We don't pass data_set because rsc needs to stay valid for the
             * entire lifetime of cli_resource_restart(), but it will reset and
             * update the working set multiple times, so it needs to use its own
             * copy.
             */
            rc = cli_resource_restart(out, rsc, options.host_uname,
                                      options.move_lifetime, options.timeout_ms,
                                      cib_conn, options.cib_options,
                                      options.promoted_role_only,
                                      options.force);
            break;

        case cmd_wait:
            rc = wait_till_stable(out, options.timeout_ms, cib_conn);
            break;

        case cmd_execute_agent:
            if (options.cmdline_config) {
                exit_code = cli_resource_execute_from_params(out, NULL,
                    options.v_class, options.v_provider, options.v_agent,
                    options.operation, options.cmdline_params,
                    options.override_params, options.timeout_ms,
                    args->verbosity, options.force, options.check_level);
            } else {
                exit_code = cli_resource_execute(rsc, options.rsc_id,
                    options.operation, options.override_params,
                    options.timeout_ms, cib_conn, data_set,
                    args->verbosity, options.force, options.check_level);
            }
            goto done;

        case cmd_digests:
            node = pe_find_node(data_set->nodes, options.host_uname);
            if (node == NULL) {
                rc = pcmk_rc_node_unknown;
            } else {
                rc = pcmk__resource_digests(out, rsc, node,
                                            options.override_params, data_set);
            }
            break;

        case cmd_colocations:
            rc = out->message(out, "stacks-constraints", rsc, data_set, false);
            break;

        case cmd_colocations_deep:
            rc = out->message(out, "stacks-constraints", rsc, data_set, true);
            break;

        case cmd_cts:
            rc = pcmk_rc_ok;

            for (GList *lpc = data_set->resources; lpc != NULL;
                 lpc = lpc->next) {

                rsc = (pe_resource_t *) lpc->data;
                cli_resource_print_cts(out, rsc);
            }

            cli_resource_print_cts_constraints(data_set);
            break;

        case cmd_fail:
            rc = cli_resource_fail(controld_api, options.host_uname,
                                   options.rsc_id, data_set);
            if (rc == pcmk_rc_ok) {
                start_mainloop(controld_api);
            }
            break;

        case cmd_list_active_ops:
            rc = cli_resource_print_operations(options.rsc_id,
                                               options.host_uname, TRUE,
                                               data_set);
            break;

        case cmd_list_all_ops:
            rc = cli_resource_print_operations(options.rsc_id,
                                               options.host_uname, FALSE,
                                               data_set);
            break;

        case cmd_locate: {
            GList *nodes = cli_resource_search(rsc, options.rsc_id, data_set);
            rc = out->message(out, "resource-search-list", nodes, options.rsc_id);
            g_list_free_full(nodes, free);
            break;
        }

        case cmd_query_xml:
            rc = cli_resource_print(rsc, data_set, TRUE);
            break;

        case cmd_query_raw_xml:
            rc = cli_resource_print(rsc, data_set, FALSE);
            break;

        case cmd_why:
            if ((options.host_uname != NULL) && (node == NULL)) {
                rc = pcmk_rc_node_unknown;
            } else {
                rc = out->message(out, "resource-reasons-list", cib_conn,
                                  data_set->resources, rsc, node);
            }
            break;

        case cmd_clear:
            rc = clear_constraints(out, &cib_xml_copy);
            break;

        case cmd_move:
            if (options.host_uname == NULL) {
                rc = ban_or_move(out, rsc, options.move_lifetime);
            } else {
                rc = cli_resource_move(rsc, options.rsc_id, options.host_uname,
                                       options.move_lifetime, cib_conn,
                                       options.cib_options, data_set,
                                       options.promoted_role_only,
                                       options.force);
            }

            if (rc == EINVAL) {
                exit_code = CRM_EX_USAGE;
                goto done;
            }

            break;

        case cmd_ban:
            if (options.host_uname == NULL) {
                rc = ban_or_move(out, rsc, options.move_lifetime);
            } else if (node == NULL) {
                rc = pcmk_rc_node_unknown;
            } else {
                rc = cli_resource_ban(out, options.rsc_id, node->details->uname,
                                      options.move_lifetime, NULL, cib_conn,
                                      options.cib_options,
                                      options.promoted_role_only);
            }

            if (rc == EINVAL) {
                exit_code = CRM_EX_USAGE;
                goto done;
            }

            break;

        case cmd_get_property:
            rc = out->message(out, "property-list", rsc, options.prop_name);
            if (rc == pcmk_rc_no_output) {
                rc = ENXIO;
            }

            break;

        case cmd_set_property:
            rc = set_property();
            break;

        case cmd_get_param: {
            unsigned int count = 0;
            GHashTable *params = NULL;
            pe_node_t *current = pe__find_active_on(rsc, &count, NULL);
            bool free_params = true;

            if (count > 1) {
                out->err(out, "%s is active on more than one node,"
                         " returning the default value for %s", rsc->id, crm_str(options.prop_name));
                current = NULL;
            }

            crm_debug("Looking up %s in %s", options.prop_name, rsc->id);

            if (pcmk__str_eq(options.attr_set_type, XML_TAG_ATTR_SETS, pcmk__str_casei)) {
                params = pe_rsc_params(rsc, current, data_set);
                free_params = false;

            } else if (pcmk__str_eq(options.attr_set_type, XML_TAG_META_SETS, pcmk__str_casei)) {
                params = pcmk__strkey_table(free, free);
                get_meta_attributes(params, rsc, current, data_set);

            } else {
                params = pcmk__strkey_table(free, free);
                pe__unpack_dataset_nvpairs(rsc->xml, XML_TAG_UTILIZATION, NULL, params,
                                           NULL, FALSE, data_set);
            }

            rc = out->message(out, "attribute-list", rsc, options.prop_name, params);
            if (free_params) {
                g_hash_table_destroy(params);
            }
            break;
        }

        case cmd_set_param:
            if (pcmk__str_empty(options.prop_value)) {
                exit_code = CRM_EX_USAGE;
                g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                            "You need to supply a value with the -v option");
                goto done;
            }

            /* coverity[var_deref_model] False positive */
            rc = cli_resource_update_attribute(rsc, options.rsc_id,
                                               options.prop_set,
                                               options.attr_set_type,
                                               options.prop_id,
                                               options.prop_name,
                                               options.prop_value,
                                               options.recursive, cib_conn,
                                               options.cib_options, data_set,
                                               options.force);
            break;

        case cmd_delete_param:
            /* coverity[var_deref_model] False positive */
            rc = cli_resource_delete_attribute(rsc, options.rsc_id,
                                               options.prop_set,
                                               options.attr_set_type,
                                               options.prop_id,
                                               options.prop_name, cib_conn,
                                               options.cib_options, data_set,
                                               options.force);
            break;

        case cmd_cleanup:
            if (rsc == NULL) {
                rc = cli_cleanup_all(controld_api, options.host_uname,
                                     options.operation, options.interval_spec,
                                     data_set);
                if (rc == pcmk_rc_ok) {
                    start_mainloop(controld_api);
                }
            } else {
                cleanup(out, rsc);
            }
            break;

        case cmd_refresh:
            if (rsc == NULL) {
                rc = refresh(out);
            } else {
                refresh_resource(out, rsc);
            }
            break;

        case cmd_delete:
            rc = delete();
            break;

        default:
            exit_code = CRM_EX_USAGE;
            g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                        "Unimplemented command: %d", (int) options.rsc_cmd);
            goto done;
    }

    /* Convert rc into an exit code. */
    if (rc != pcmk_rc_ok && rc != pcmk_rc_no_output) {
        if (rc == pcmk_rc_no_quorum) {
            g_prefix_error(&error, "To ignore quorum, use the force option.\n");
        }

        exit_code = pcmk_rc2exitc(rc);
    }

    /*
     * Clean up and exit
     */

done:
    /* When we get here, exit_code has been set one of two ways - either at one of
     * the spots where there's a "goto done" (which itself could have happened either
     * directly or by calling pcmk_rc2exitc), or just up above after any of the break
     * statements.
     *
     * Thus, we can use just exit_code here to decide what to do.
     */
    if (exit_code != CRM_EX_OK && exit_code != CRM_EX_USAGE) {
        if (error != NULL) {
            char *msg = crm_strdup_printf("%s\nError performing operation: %s",
                                          error->message, crm_exit_str(exit_code));
            g_clear_error(&error);
            g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "%s", msg);
            free(msg);
        } else {
            g_set_error(&error, PCMK__EXITC_ERROR, exit_code,
                        "Error performing operation: %s", crm_exit_str(exit_code));
        }
    }

    g_free(options.host_uname);
    g_free(options.interval_spec);
    g_free(options.move_lifetime);
    g_free(options.operation);
    g_free(options.prop_id);
    free(options.prop_name);
    g_free(options.prop_set);
    g_free(options.prop_value);
    g_free(options.rsc_id);
    g_free(options.rsc_type);
    free(options.agent_spec);
    free(options.v_agent);
    free(options.v_class);
    free(options.v_provider);
    g_free(options.xml_file);
    g_strfreev(options.remainder);

    if (options.override_params != NULL) {
        g_hash_table_destroy(options.override_params);
    }

    /* options.cmdline_params does not need to be destroyed here.  See the
     * comments in cli_resource_execute_from_params.
     */

    g_strfreev(processed_args);
    g_option_context_free(context);

    return bye(exit_code);
}
