Page MenuHomeClusterLabs Projects

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/attrd/legacy.c b/attrd/legacy.c
index 238b07c2a1..592fb0d0c1 100644
--- a/attrd/legacy.c
+++ b/attrd/legacy.c
@@ -1,910 +1,910 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/msg_xml.h>
#include <crm/common/ipc.h>
#include <crm/common/ipcs.h>
#include <crm/cluster/internal.h>
#include <crm/common/mainloop.h>
#include <crm/common/xml.h>
#include <crm/attrd.h>
#define OPTARGS "hV"
#if SUPPORT_HEARTBEAT
ll_cluster_t *attrd_cluster_conn;
#endif
GMainLoop *mainloop = NULL;
char *attrd_uname = NULL;
char *attrd_uuid = NULL;
gboolean need_shutdown = FALSE;
GHashTable *attr_hash = NULL;
cib_t *cib_conn = NULL;
typedef struct attr_hash_entry_s {
char *uuid;
char *id;
char *set;
char *section;
char *value;
char *stored_value;
int timeout;
char *dampen;
guint timer_id;
char *user;
} attr_hash_entry_t;
void attrd_local_callback(xmlNode * msg);
gboolean attrd_timer_callback(void *user_data);
gboolean attrd_trigger_update(attr_hash_entry_t * hash_entry);
void attrd_perform_update(attr_hash_entry_t * hash_entry);
static void
free_hash_entry(gpointer data)
{
attr_hash_entry_t *entry = data;
if (entry == NULL) {
return;
}
free(entry->id);
free(entry->set);
free(entry->dampen);
free(entry->section);
free(entry->uuid);
free(entry->value);
free(entry->stored_value);
free(entry->user);
free(entry);
}
static int32_t
attrd_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid)
{
crm_trace("Connection %p", c);
if (need_shutdown) {
crm_info("Ignoring new client [%d] during shutdown", crm_ipcs_client_pid(c));
return -EPERM;
}
if (crm_client_new(c, uid, gid) == NULL) {
return -EIO;
}
return 0;
}
static void
attrd_ipc_created(qb_ipcs_connection_t * c)
{
crm_trace("Connection %p", c);
}
/* Exit code means? */
static int32_t
attrd_ipc_dispatch(qb_ipcs_connection_t * c, void *data, size_t size)
{
uint32_t id = 0;
uint32_t flags = 0;
crm_client_t *client = crm_client_get(c);
xmlNode *msg = crm_ipcs_recv(client, data, size, &id, &flags);
crm_ipcs_send_ack(client, id, flags, "ack", __FUNCTION__, __LINE__);
if (msg == NULL) {
crm_debug("No msg from %d (%p)", crm_ipcs_client_pid(c), c);
return 0;
}
#if ENABLE_ACL
CRM_ASSERT(client->user != NULL);
crm_acl_get_set_user(msg, F_ATTRD_USER, client->user);
#endif
crm_trace("Processing msg from %d (%p)", crm_ipcs_client_pid(c), c);
crm_log_xml_trace(msg, __FUNCTION__);
attrd_local_callback(msg);
free_xml(msg);
return 0;
}
/* Error code means? */
static int32_t
attrd_ipc_closed(qb_ipcs_connection_t * c)
{
crm_client_t *client = crm_client_get(c);
if (client == NULL) {
return 0;
}
crm_trace("Connection %p", c);
crm_client_destroy(client);
return 0;
}
static void
attrd_ipc_destroy(qb_ipcs_connection_t * c)
{
crm_trace("Connection %p", c);
attrd_ipc_closed(c);
}
struct qb_ipcs_service_handlers ipc_callbacks = {
.connection_accept = attrd_ipc_accept,
.connection_created = attrd_ipc_created,
.msg_process = attrd_ipc_dispatch,
.connection_closed = attrd_ipc_closed,
.connection_destroyed = attrd_ipc_destroy
};
static void
attrd_shutdown(int nsig)
{
need_shutdown = TRUE;
crm_info("Exiting");
if (mainloop != NULL && g_main_is_running(mainloop)) {
g_main_quit(mainloop);
} else {
crm_exit(pcmk_ok);
}
}
static void
usage(const char *cmd, int exit_status)
{
FILE *stream;
stream = exit_status ? stderr : stdout;
fprintf(stream, "usage: %s [-srkh] [-c configure file]\n", cmd);
/* fprintf(stream, "\t-d\tsets debug level\n"); */
/* fprintf(stream, "\t-s\tgets daemon status\n"); */
/* fprintf(stream, "\t-r\trestarts daemon\n"); */
/* fprintf(stream, "\t-k\tstops daemon\n"); */
/* fprintf(stream, "\t-h\thelp message\n"); */
fflush(stream);
crm_exit(exit_status);
}
static void
stop_attrd_timer(attr_hash_entry_t * hash_entry)
{
if (hash_entry != NULL && hash_entry->timer_id != 0) {
crm_trace("Stopping %s timer", hash_entry->id);
g_source_remove(hash_entry->timer_id);
hash_entry->timer_id = 0;
}
}
static void
log_hash_entry(int level, attr_hash_entry_t * entry, const char *text)
{
do_crm_log(level, "%s: Set: %s, Name: %s, Value: %s, Timeout: %s",
text, entry->section, entry->id, entry->value, entry->dampen);
}
static attr_hash_entry_t *
find_hash_entry(xmlNode * msg)
{
const char *value = NULL;
const char *attr = crm_element_value(msg, F_ATTRD_ATTRIBUTE);
attr_hash_entry_t *hash_entry = NULL;
if (attr == NULL) {
crm_info("Ignoring message with no attribute name");
return NULL;
}
hash_entry = g_hash_table_lookup(attr_hash, attr);
if (hash_entry == NULL) {
/* create one and add it */
crm_info("Creating hash entry for %s", attr);
hash_entry = calloc(1, sizeof(attr_hash_entry_t));
hash_entry->id = strdup(attr);
g_hash_table_insert(attr_hash, hash_entry->id, hash_entry);
hash_entry = g_hash_table_lookup(attr_hash, attr);
CRM_CHECK(hash_entry != NULL, return NULL);
}
value = crm_element_value(msg, F_ATTRD_SET);
if (value != NULL) {
free(hash_entry->set);
hash_entry->set = strdup(value);
crm_debug("\t%s->set: %s", attr, value);
}
value = crm_element_value(msg, F_ATTRD_SECTION);
if (value == NULL) {
value = XML_CIB_TAG_STATUS;
}
free(hash_entry->section);
hash_entry->section = strdup(value);
crm_trace("\t%s->section: %s", attr, value);
value = crm_element_value(msg, F_ATTRD_DAMPEN);
if (value != NULL) {
free(hash_entry->dampen);
hash_entry->dampen = strdup(value);
hash_entry->timeout = crm_get_msec(value);
crm_trace("\t%s->timeout: %s", attr, value);
}
#if ENABLE_ACL
free(hash_entry->user);
hash_entry->user = NULL;
value = crm_element_value(msg, F_ATTRD_USER);
if (value != NULL) {
hash_entry->user = strdup(value);
crm_trace("\t%s->user: %s", attr, value);
}
#endif
log_hash_entry(LOG_DEBUG_2, hash_entry, "Found (and updated) entry:");
return hash_entry;
}
#if SUPPORT_HEARTBEAT
static void
attrd_ha_connection_destroy(gpointer user_data)
{
crm_trace("Invoked");
if (need_shutdown) {
/* we signed out, so this is expected */
crm_info("Heartbeat disconnection complete");
return;
}
crm_crit("Lost connection to heartbeat service!");
if (mainloop != NULL && g_main_is_running(mainloop)) {
g_main_quit(mainloop);
return;
}
crm_exit(pcmk_ok);
}
static void
attrd_ha_callback(HA_Message * msg, void *private_data)
{
attr_hash_entry_t *hash_entry = NULL;
xmlNode *xml = convert_ha_message(NULL, msg, __FUNCTION__);
const char *from = crm_element_value(xml, F_ORIG);
const char *op = crm_element_value(xml, F_ATTRD_TASK);
const char *host = crm_element_value(xml, F_ATTRD_HOST);
const char *ignore = crm_element_value(xml, F_ATTRD_IGNORE_LOCALLY);
if (host != NULL && safe_str_eq(host, attrd_uname)) {
crm_info("Update relayed from %s", from);
attrd_local_callback(xml);
} else if (ignore == NULL || safe_str_neq(from, attrd_uname)) {
crm_info("%s message from %s", op, from);
hash_entry = find_hash_entry(xml);
stop_attrd_timer(hash_entry);
attrd_perform_update(hash_entry);
}
free_xml(xml);
}
#endif
#if SUPPORT_COROSYNC
static void
attrd_cs_dispatch(cpg_handle_t handle,
const struct cpg_name *groupName,
uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len)
{
uint32_t kind = 0;
xmlNode *xml = NULL;
const char *from = NULL;
char *data = pcmk_message_common_cs(handle, nodeid, pid, msg, &kind, &from);
if(data == NULL) {
return;
}
if (kind == crm_class_cluster) {
xml = string2xml(data);
if (xml == NULL) {
crm_err("Bad message received: '%.120s'", data);
}
}
if (xml != NULL) {
attr_hash_entry_t *hash_entry = NULL;
const char *op = crm_element_value(xml, F_ATTRD_TASK);
const char *host = crm_element_value(xml, F_ATTRD_HOST);
const char *ignore = crm_element_value(xml, F_ATTRD_IGNORE_LOCALLY);
/* crm_xml_add_int(xml, F_SEQ, wrapper->id); */
crm_xml_add(xml, F_ORIG, from);
if (host != NULL && safe_str_eq(host, attrd_uname)) {
crm_notice("Update relayed from %s", from);
attrd_local_callback(xml);
} else if (ignore == NULL || safe_str_neq(from, attrd_uname)) {
crm_trace("%s message from %s", op, from);
hash_entry = find_hash_entry(xml);
stop_attrd_timer(hash_entry);
attrd_perform_update(hash_entry);
}
free_xml(xml);
}
free(data);
}
static void
attrd_cs_destroy(gpointer unused)
{
if (need_shutdown) {
/* we signed out, so this is expected */
crm_info("Corosync disconnection complete");
return;
}
crm_crit("Lost connection to Corosync service!");
if (mainloop != NULL && g_main_is_running(mainloop)) {
g_main_quit(mainloop);
return;
}
crm_exit(EINVAL);
}
#endif
static void
attrd_cib_connection_destroy(gpointer user_data)
{
cib_t *conn = user_data;
conn->cmds->signoff(conn); /* Ensure IPC is cleaned up */
if (need_shutdown) {
crm_info("Connection to the CIB terminated...");
} else {
/* eventually this will trigger a reconnect, not a shutdown */
crm_err("Connection to the CIB terminated...");
crm_exit(ENOTCONN);
}
return;
}
static void
update_for_hash_entry(gpointer key, gpointer value, gpointer user_data)
{
attr_hash_entry_t *entry = value;
if (entry->value != NULL || entry->stored_value != NULL) {
attrd_timer_callback(value);
}
}
static void
local_update_for_hash_entry(gpointer key, gpointer value, gpointer user_data)
{
attr_hash_entry_t *entry = value;
if (entry->timer_id == 0) {
crm_trace("Performing local-only update after replace for %s", entry->id);
attrd_perform_update(entry);
/* } else {
* just let the timer expire and attrd_timer_callback() will do the right thing
*/
}
}
static void
do_cib_replaced(const char *event, xmlNode * msg)
{
crm_info("Updating all attributes after %s event", event);
g_hash_table_foreach(attr_hash, local_update_for_hash_entry, NULL);
}
static gboolean
cib_connect(void *user_data)
{
static int attempts = 1;
static int max_retry = 20;
gboolean was_err = FALSE;
static cib_t *local_conn = NULL;
if (local_conn == NULL) {
local_conn = cib_new();
}
if (was_err == FALSE) {
int rc = -ENOTCONN;
if (attempts < max_retry) {
crm_debug("CIB signon attempt %d", attempts);
rc = local_conn->cmds->signon(local_conn, T_ATTRD, cib_command);
}
if (rc != pcmk_ok && attempts > max_retry) {
crm_err("Signon to CIB failed: %s", pcmk_strerror(rc));
was_err = TRUE;
} else if (rc != pcmk_ok) {
attempts++;
return TRUE;
}
}
crm_info("Connected to the CIB after %d signon attempts", attempts);
if (was_err == FALSE) {
int rc = local_conn->cmds->set_connection_dnotify(local_conn, attrd_cib_connection_destroy);
if (rc != pcmk_ok) {
crm_err("Could not set dnotify callback");
was_err = TRUE;
}
}
if (was_err == FALSE) {
if (pcmk_ok !=
local_conn->cmds->add_notify_callback(local_conn, T_CIB_REPLACE_NOTIFY,
do_cib_replaced)) {
crm_err("Could not set CIB notification callback");
was_err = TRUE;
}
}
if (was_err) {
crm_err("Aborting startup");
crm_exit(DAEMON_RESPAWN_STOP);
}
cib_conn = local_conn;
crm_info("Sending full refresh now that we're connected to the cib");
g_hash_table_foreach(attr_hash, local_update_for_hash_entry, NULL);
return FALSE;
}
int
main(int argc, char **argv)
{
int flag = 0;
int argerr = 0;
crm_cluster_t cluster;
gboolean was_err = FALSE;
qb_ipcs_connection_t *c = NULL;
qb_ipcs_service_t *ipcs = NULL;
crm_log_init(T_ATTRD, LOG_NOTICE, TRUE, FALSE, argc, argv, FALSE);
mainloop_add_signal(SIGTERM, attrd_shutdown);
while ((flag = getopt(argc, argv, OPTARGS)) != EOF) {
switch (flag) {
case 'V':
crm_bump_log_level(argc, argv);
break;
case 'h': /* Help message */
usage(T_ATTRD, EX_OK);
break;
default:
++argerr;
break;
}
}
if (optind > argc) {
++argerr;
}
if (argerr) {
usage(T_ATTRD, EX_USAGE);
}
attr_hash = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, free_hash_entry);
crm_info("Starting up");
if (was_err == FALSE) {
#if SUPPORT_COROSYNC
if (is_openais_cluster()) {
cluster.destroy = attrd_cs_destroy;
cluster.cpg.cpg_deliver_fn = attrd_cs_dispatch;
cluster.cpg.cpg_confchg_fn = pcmk_cpg_membership;
}
#endif
#if SUPPORT_HEARTBEAT
if (is_heartbeat_cluster()) {
cluster.hb_conn = NULL;
cluster.hb_dispatch = attrd_ha_callback;
cluster.destroy = attrd_ha_connection_destroy;
}
#endif
if (FALSE == crm_cluster_connect(&cluster)) {
crm_err("HA Signon failed");
was_err = TRUE;
}
attrd_uname = cluster.uname;
attrd_uuid = cluster.uuid;
#if SUPPORT_HEARTBEAT
attrd_cluster_conn = cluster.hb_conn;
#endif
}
crm_info("Cluster connection active");
if (was_err == FALSE) {
attrd_ipc_server_init(&ipcs, &ipc_callbacks);
}
crm_info("Accepting attribute updates");
mainloop = g_main_new(FALSE);
if (0 == g_timeout_add_full(G_PRIORITY_LOW + 1, 5000, cib_connect, NULL, NULL)) {
crm_info("Adding timer failed");
was_err = TRUE;
}
if (was_err) {
crm_err("Aborting startup");
return 100;
}
crm_notice("Starting mainloop...");
g_main_run(mainloop);
crm_notice("Exiting...");
#if SUPPORT_HEARTBEAT
if (is_heartbeat_cluster()) {
attrd_cluster_conn->llc_ops->signoff(attrd_cluster_conn, TRUE);
attrd_cluster_conn->llc_ops->delete(attrd_cluster_conn);
}
#endif
c = qb_ipcs_connection_first_get(ipcs);
while (c != NULL) {
qb_ipcs_connection_t *last = c;
c = qb_ipcs_connection_next_get(ipcs, last);
/* There really shouldn't be anyone connected at this point */
crm_notice("Disconnecting client %p, pid=%d...", last, crm_ipcs_client_pid(last));
qb_ipcs_disconnect(last);
qb_ipcs_connection_unref(last);
}
qb_ipcs_destroy(ipcs);
if (cib_conn) {
cib_conn->cmds->signoff(cib_conn);
cib_delete(cib_conn);
}
g_hash_table_destroy(attr_hash);
free(attrd_uuid);
return crm_exit(pcmk_ok);
}
struct attrd_callback_s {
char *attr;
char *value;
};
-/*
+/*!
* \internal
* \brief Free an attrd callback structure
*/
static void
free_attrd_callback(void *user_data)
{
struct attrd_callback_s *data = user_data;
free(data->attr);
free(data->value);
free(data);
}
static void
attrd_cib_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
{
attr_hash_entry_t *hash_entry = NULL;
struct attrd_callback_s *data = user_data;
if (data->value == NULL && rc == -ENXIO) {
rc = pcmk_ok;
} else if (call_id < 0) {
crm_warn("Update %s=%s failed: %s", data->attr, data->value, pcmk_strerror(call_id));
return;
}
switch (rc) {
case pcmk_ok:
crm_debug("Update %d for %s=%s passed", call_id, data->attr, data->value);
hash_entry = g_hash_table_lookup(attr_hash, data->attr);
if (hash_entry) {
free(hash_entry->stored_value);
hash_entry->stored_value = NULL;
if (data->value != NULL) {
hash_entry->stored_value = strdup(data->value);
}
}
break;
case -pcmk_err_diff_failed: /* When an attr changes while the CIB is syncing */
case -ETIME: /* When an attr changes while there is a DC election */
case -ENXIO: /* When an attr changes while the CIB is syncing a
* newer config from a node that just came up
*/
crm_warn("Update %d for %s=%s failed: %s",
call_id, data->attr, data->value, pcmk_strerror(rc));
break;
default:
crm_err("Update %d for %s=%s failed: %s",
call_id, data->attr, data->value, pcmk_strerror(rc));
}
}
void
attrd_perform_update(attr_hash_entry_t * hash_entry)
{
int rc = pcmk_ok;
struct attrd_callback_s *data = NULL;
const char *user_name = NULL;
if (hash_entry == NULL) {
return;
} else if (cib_conn == NULL) {
crm_info("Delaying operation %s=%s: cib not connected", hash_entry->id,
crm_str(hash_entry->value));
return;
}
#if ENABLE_ACL
if (hash_entry->user) {
user_name = hash_entry->user;
crm_trace("Performing request from user '%s'", hash_entry->user);
}
#endif
if (hash_entry->value == NULL) {
/* delete the attr */
rc = delete_attr_delegate(cib_conn, cib_none, hash_entry->section, attrd_uuid, NULL,
hash_entry->set, hash_entry->uuid, hash_entry->id, NULL, FALSE,
user_name);
if (rc >= 0 && hash_entry->stored_value) {
crm_notice("Sent delete %d: node=%s, attr=%s, id=%s, set=%s, section=%s",
rc, attrd_uuid, hash_entry->id,
hash_entry->uuid ? hash_entry->uuid : "<n/a>", hash_entry->set,
hash_entry->section);
} else if (rc < 0 && rc != -ENXIO) {
crm_notice
("Delete operation failed: node=%s, attr=%s, id=%s, set=%s, section=%s: %s (%d)",
attrd_uuid, hash_entry->id, hash_entry->uuid ? hash_entry->uuid : "<n/a>",
hash_entry->set, hash_entry->section, pcmk_strerror(rc), rc);
} else {
crm_trace("Sent delete %d: node=%s, attr=%s, id=%s, set=%s, section=%s",
rc, attrd_uuid, hash_entry->id,
hash_entry->uuid ? hash_entry->uuid : "<n/a>", hash_entry->set,
hash_entry->section);
}
} else {
/* send update */
rc = update_attr_delegate(cib_conn, cib_none, hash_entry->section,
attrd_uuid, NULL, hash_entry->set, hash_entry->uuid,
hash_entry->id, hash_entry->value, FALSE, user_name, NULL);
if (rc < 0) {
crm_notice("Sent update %s=%s failed: %s", hash_entry->id, hash_entry->value,
pcmk_strerror(rc));
}
if (safe_str_neq(hash_entry->value, hash_entry->stored_value) || rc < 0) {
crm_notice("Sent update %d: %s=%s", rc, hash_entry->id, hash_entry->value);
} else {
crm_trace("Sent update %d: %s=%s", rc, hash_entry->id, hash_entry->value);
}
}
data = calloc(1, sizeof(struct attrd_callback_s));
data->attr = strdup(hash_entry->id);
if (hash_entry->value != NULL) {
data->value = strdup(hash_entry->value);
}
cib_conn->cmds->register_callback_full(cib_conn, rc, 120, FALSE, data,
"attrd_cib_callback",
attrd_cib_callback,
free_attrd_callback);
return;
}
void
attrd_local_callback(xmlNode * msg)
{
static int plus_plus_len = 5;
attr_hash_entry_t *hash_entry = NULL;
const char *from = crm_element_value(msg, F_ORIG);
const char *op = crm_element_value(msg, F_ATTRD_TASK);
const char *attr = crm_element_value(msg, F_ATTRD_ATTRIBUTE);
const char *value = crm_element_value(msg, F_ATTRD_VALUE);
const char *host = crm_element_value(msg, F_ATTRD_HOST);
if (safe_str_eq(op, ATTRD_OP_REFRESH)) {
crm_notice("Sending full refresh (origin=%s)", from);
g_hash_table_foreach(attr_hash, update_for_hash_entry, NULL);
return;
} else if (safe_str_eq(op, ATTRD_OP_PEER_REMOVE)) {
/* The legacy code didn't understand this command - swallow silently */
return;
}
if (host != NULL && safe_str_neq(host, attrd_uname)) {
send_cluster_message(crm_get_peer(0, host), crm_msg_attrd, msg, FALSE);
return;
}
crm_debug("%s message from %s: %s=%s", op, from, attr, crm_str(value));
hash_entry = find_hash_entry(msg);
if (hash_entry == NULL) {
return;
}
if (hash_entry->uuid == NULL) {
const char *key = crm_element_value(msg, F_ATTRD_KEY);
if (key) {
hash_entry->uuid = strdup(key);
}
}
crm_debug("Supplied: %s, Current: %s, Stored: %s",
value, hash_entry->value, hash_entry->stored_value);
if (safe_str_eq(value, hash_entry->value)
&& safe_str_eq(value, hash_entry->stored_value)) {
crm_trace("Ignoring non-change");
return;
} else if (value) {
int offset = 1;
int int_value = 0;
int value_len = strlen(value);
if (value_len < (plus_plus_len + 2)
|| value[plus_plus_len] != '+'
|| (value[plus_plus_len + 1] != '+' && value[plus_plus_len + 1] != '=')) {
goto set_unexpanded;
}
int_value = char2score(hash_entry->value);
if (value[plus_plus_len + 1] != '+') {
const char *offset_s = value + (plus_plus_len + 2);
offset = char2score(offset_s);
}
int_value += offset;
if (int_value > INFINITY) {
int_value = INFINITY;
}
crm_info("Expanded %s=%s to %d", attr, value, int_value);
crm_xml_add_int(msg, F_ATTRD_VALUE, int_value);
value = crm_element_value(msg, F_ATTRD_VALUE);
}
set_unexpanded:
if (safe_str_eq(value, hash_entry->value) && hash_entry->timer_id) {
/* We're already waiting to set this value */
return;
}
free(hash_entry->value);
hash_entry->value = NULL;
if (value != NULL) {
hash_entry->value = strdup(value);
crm_debug("New value of %s is %s", attr, value);
}
stop_attrd_timer(hash_entry);
if (hash_entry->timeout > 0) {
hash_entry->timer_id = g_timeout_add(hash_entry->timeout, attrd_timer_callback, hash_entry);
} else {
attrd_trigger_update(hash_entry);
}
return;
}
gboolean
attrd_timer_callback(void *user_data)
{
stop_attrd_timer(user_data);
attrd_trigger_update(user_data);
return TRUE; /* Always return true, removed cleanly by stop_attrd_timer() */
}
gboolean
attrd_trigger_update(attr_hash_entry_t * hash_entry)
{
xmlNode *msg = NULL;
/* send HA message to everyone */
crm_notice("Sending flush op to all hosts for: %s (%s)",
hash_entry->id, crm_str(hash_entry->value));
log_hash_entry(LOG_DEBUG_2, hash_entry, "Sending flush op to all hosts for:");
msg = create_xml_node(NULL, __FUNCTION__);
crm_xml_add(msg, F_TYPE, T_ATTRD);
crm_xml_add(msg, F_ORIG, attrd_uname);
crm_xml_add(msg, F_ATTRD_TASK, "flush");
crm_xml_add(msg, F_ATTRD_ATTRIBUTE, hash_entry->id);
crm_xml_add(msg, F_ATTRD_SET, hash_entry->set);
crm_xml_add(msg, F_ATTRD_SECTION, hash_entry->section);
crm_xml_add(msg, F_ATTRD_DAMPEN, hash_entry->dampen);
crm_xml_add(msg, F_ATTRD_VALUE, hash_entry->value);
#if ENABLE_ACL
if (hash_entry->user) {
crm_xml_add(msg, F_ATTRD_USER, hash_entry->user);
}
#endif
if (hash_entry->timeout <= 0) {
crm_xml_add(msg, F_ATTRD_IGNORE_LOCALLY, hash_entry->value);
attrd_perform_update(hash_entry);
}
send_cluster_message(NULL, crm_msg_attrd, msg, FALSE);
free_xml(msg);
return TRUE;
}
diff --git a/cib/callbacks.c b/cib/callbacks.c
index b0490b3bb8..3d13635eb1 100644
--- a/cib/callbacks.c
+++ b/cib/callbacks.c
@@ -1,1745 +1,1745 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <crm_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 <crm/crm.h>
#include <crm/cib.h>
#include <crm/msg_xml.h>
#include <crm/cluster/internal.h>
#include <crm/common/xml.h>
#include <cibio.h>
#include <callbacks.h>
#include <cibmessages.h>
#include <notify.h>
#include "common.h"
static unsigned long cib_local_bcast_num = 0;
typedef struct cib_local_notify_s {
xmlNode *notify_src;
char *client_id;
gboolean from_peer;
gboolean sync_reply;
} cib_local_notify_t;
int next_client_id = 0;
#if SUPPORT_PLUGIN
gboolean legacy_mode = TRUE;
#else
gboolean legacy_mode = FALSE;
#endif
qb_ipcs_service_t *ipcs_ro = NULL;
qb_ipcs_service_t *ipcs_rw = NULL;
qb_ipcs_service_t *ipcs_shm = NULL;
gint cib_GCompareFunc(gconstpointer a, gconstpointer b);
gboolean can_write(int flags);
void send_cib_replace(const xmlNode * sync_request, const char *host);
void cib_process_request(xmlNode * request, gboolean privileged, gboolean force_synchronous,
gboolean from_peer, crm_client_t * cib_client);
int cib_process_command(xmlNode * request, xmlNode ** reply,
xmlNode ** cib_diff, gboolean privileged);
gboolean cib_common_callback(qb_ipcs_connection_t * c, void *data, size_t size,
gboolean privileged);
#if !SUPPORT_PLUGIN
static gboolean cib_read_legacy_mode(void)
{
static gboolean init = TRUE;
static gboolean legacy = FALSE;
if(init) {
init = FALSE;
legacy = daemon_option_enabled("cib", "legacy");
if(legacy) {
crm_notice("Enabled legacy mode");
}
}
return legacy;
}
#endif
gboolean cib_legacy_mode(void)
{
#if SUPPORT_PLUGIN
return TRUE;
#else
if(cib_read_legacy_mode()) {
return TRUE;
}
return legacy_mode;
#endif
}
static int32_t
cib_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid)
{
crm_trace("Connection %p", c);
if (cib_shutdown_flag) {
crm_info("Ignoring new client [%d] during shutdown", crm_ipcs_client_pid(c));
return -EPERM;
}
if (crm_client_new(c, uid, gid) == NULL) {
return -EIO;
}
return 0;
}
static void
cib_ipc_created(qb_ipcs_connection_t * c)
{
crm_trace("Connection %p", c);
}
static int32_t
cib_ipc_dispatch_rw(qb_ipcs_connection_t * c, void *data, size_t size)
{
crm_client_t *client = crm_client_get(c);
crm_trace("%p message from %s", c, client->id);
return cib_common_callback(c, data, size, TRUE);
}
static int32_t
cib_ipc_dispatch_ro(qb_ipcs_connection_t * c, void *data, size_t size)
{
crm_client_t *client = crm_client_get(c);
crm_trace("%p message from %s", c, client->id);
return cib_common_callback(c, data, size, FALSE);
}
/* Error code means? */
static int32_t
cib_ipc_closed(qb_ipcs_connection_t * c)
{
crm_client_t *client = crm_client_get(c);
if (client == NULL) {
return 0;
}
crm_trace("Connection %p", c);
crm_client_destroy(client);
return 0;
}
static void
cib_ipc_destroy(qb_ipcs_connection_t * c)
{
crm_trace("Connection %p", c);
cib_ipc_closed(c);
if (cib_shutdown_flag) {
cib_shutdown(0);
}
}
struct qb_ipcs_service_handlers ipc_ro_callbacks = {
.connection_accept = cib_ipc_accept,
.connection_created = cib_ipc_created,
.msg_process = cib_ipc_dispatch_ro,
.connection_closed = cib_ipc_closed,
.connection_destroyed = cib_ipc_destroy
};
struct qb_ipcs_service_handlers ipc_rw_callbacks = {
.connection_accept = cib_ipc_accept,
.connection_created = cib_ipc_created,
.msg_process = cib_ipc_dispatch_rw,
.connection_closed = cib_ipc_closed,
.connection_destroyed = cib_ipc_destroy
};
void
cib_common_callback_worker(uint32_t id, uint32_t flags, xmlNode * op_request,
crm_client_t * cib_client, gboolean privileged)
{
const char *op = crm_element_value(op_request, F_CIB_OPERATION);
if (crm_str_eq(op, CRM_OP_REGISTER, TRUE)) {
if (flags & crm_ipc_client_response) {
xmlNode *ack = create_xml_node(NULL, __FUNCTION__);
crm_xml_add(ack, F_CIB_OPERATION, CRM_OP_REGISTER);
crm_xml_add(ack, F_CIB_CLIENTID, cib_client->id);
crm_ipcs_send(cib_client, id, ack, flags);
cib_client->request_id = 0;
free_xml(ack);
}
return;
} else if (crm_str_eq(op, T_CIB_NOTIFY, TRUE)) {
/* Update the notify filters for this client */
int on_off = 0;
long long bit = 0;
const char *type = crm_element_value(op_request, F_CIB_NOTIFY_TYPE);
crm_element_value_int(op_request, F_CIB_NOTIFY_ACTIVATE, &on_off);
crm_debug("Setting %s callbacks for %s (%s): %s",
type, cib_client->name, cib_client->id, on_off ? "on" : "off");
if (safe_str_eq(type, T_CIB_POST_NOTIFY)) {
bit = cib_notify_post;
} else if (safe_str_eq(type, T_CIB_PRE_NOTIFY)) {
bit = cib_notify_pre;
} else if (safe_str_eq(type, T_CIB_UPDATE_CONFIRM)) {
bit = cib_notify_confirm;
} else if (safe_str_eq(type, T_CIB_DIFF_NOTIFY)) {
bit = cib_notify_diff;
} else if (safe_str_eq(type, T_CIB_REPLACE_NOTIFY)) {
bit = cib_notify_replace;
}
if (on_off) {
set_bit(cib_client->options, bit);
} else {
clear_bit(cib_client->options, bit);
}
if (flags & crm_ipc_client_response) {
/* TODO - include rc */
crm_ipcs_send_ack(cib_client, id, flags, "ack", __FUNCTION__, __LINE__);
}
return;
}
cib_process_request(op_request, FALSE, privileged, FALSE, cib_client);
}
int32_t
cib_common_callback(qb_ipcs_connection_t * c, void *data, size_t size, gboolean privileged)
{
uint32_t id = 0;
uint32_t flags = 0;
int call_options = 0;
crm_client_t *cib_client = crm_client_get(c);
xmlNode *op_request = crm_ipcs_recv(cib_client, data, size, &id, &flags);
if (op_request) {
crm_element_value_int(op_request, F_CIB_CALLOPTS, &call_options);
}
if (op_request == NULL) {
crm_trace("Invalid message from %p", c);
crm_ipcs_send_ack(cib_client, id, flags, "nack", __FUNCTION__, __LINE__);
return 0;
} else if(cib_client == NULL) {
crm_trace("Invalid client %p", c);
return 0;
}
if (is_set(call_options, cib_sync_call)) {
CRM_LOG_ASSERT(flags & crm_ipc_client_response);
CRM_LOG_ASSERT(cib_client->request_id == 0); /* This means the client has two synchronous events in-flight */
cib_client->request_id = id; /* Reply only to the last one */
}
if (cib_client->name == NULL) {
const char *value = crm_element_value(op_request, F_CIB_CLIENTNAME);
if (value == NULL) {
cib_client->name = crm_itoa(cib_client->pid);
} else {
cib_client->name = strdup(value);
}
}
crm_xml_add(op_request, F_CIB_CLIENTID, cib_client->id);
crm_xml_add(op_request, F_CIB_CLIENTNAME, cib_client->name);
#if ENABLE_ACL
CRM_LOG_ASSERT(cib_client->user != NULL);
crm_acl_get_set_user(op_request, F_CIB_USER, cib_client->user);
#endif
crm_log_xml_trace(op_request, "Client[inbound]");
cib_common_callback_worker(id, flags, op_request, cib_client, privileged);
free_xml(op_request);
return 0;
}
static uint64_t ping_seq = 0;
static char *ping_digest = NULL;
static bool ping_modified_since = FALSE;
int sync_our_cib(xmlNode * request, gboolean all);
static gboolean
cib_digester_cb(gpointer data)
{
if (cib_is_master) {
char buffer[32];
xmlNode *ping = create_xml_node(NULL, "ping");
ping_seq++;
free(ping_digest);
ping_digest = NULL;
ping_modified_since = FALSE;
snprintf(buffer, 32, U64T, ping_seq);
crm_trace("Requesting peer digests (%s)", buffer);
crm_xml_add(ping, F_TYPE, "cib");
crm_xml_add(ping, F_CIB_OPERATION, CRM_OP_PING);
crm_xml_add(ping, F_CIB_PING_ID, buffer);
crm_xml_add(ping, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
send_cluster_message(NULL, crm_msg_cib, ping, TRUE);
free_xml(ping);
}
return FALSE;
}
static void
process_ping_reply(xmlNode *reply)
{
uint64_t seq = 0;
const char *host = crm_element_value(reply, F_ORIG);
xmlNode *pong = get_message_xml(reply, F_CIB_CALLDATA);
const char *seq_s = crm_element_value(pong, F_CIB_PING_ID);
const char *digest = crm_element_value(pong, XML_ATTR_DIGEST);
if (seq_s) {
seq = crm_int_helper(seq_s, NULL);
}
if(digest == NULL) {
crm_trace("Ignoring ping reply %s from %s with no digest", seq_s, host);
} else if(seq != ping_seq) {
crm_trace("Ignoring out of sequence ping reply %s from %s", seq_s, host);
} else if(ping_modified_since) {
crm_trace("Ignoring ping reply %s from %s: cib updated since", seq_s, host);
} else {
const char *version = crm_element_value(pong, XML_ATTR_CRM_VERSION);
if(ping_digest == NULL) {
crm_trace("Calculating new digest");
ping_digest = calculate_xml_versioned_digest(the_cib, FALSE, TRUE, version);
}
crm_trace("Processing ping reply %s from %s (%s)", seq_s, host, digest);
if(safe_str_eq(ping_digest, digest) == FALSE) {
xmlNode *remote_cib = get_message_xml(pong, F_CIB_CALLDATA);
crm_notice("Local CIB %s.%s.%s.%s differs from %s: %s.%s.%s.%s %p",
crm_element_value(the_cib, XML_ATTR_GENERATION_ADMIN),
crm_element_value(the_cib, XML_ATTR_GENERATION),
crm_element_value(the_cib, XML_ATTR_NUMUPDATES),
ping_digest, host,
remote_cib?crm_element_value(remote_cib, XML_ATTR_GENERATION_ADMIN):"_",
remote_cib?crm_element_value(remote_cib, XML_ATTR_GENERATION):"_",
remote_cib?crm_element_value(remote_cib, XML_ATTR_NUMUPDATES):"_",
digest, remote_cib);
if(remote_cib && remote_cib->children) {
/* Additional debug */
xml_calculate_changes(the_cib, remote_cib);
xml_log_changes(LOG_INFO, __FUNCTION__, remote_cib);
crm_trace("End of differences");
}
free_xml(remote_cib);
sync_our_cib(reply, FALSE);
}
}
}
static void
do_local_notify(xmlNode * notify_src, const char *client_id,
gboolean sync_reply, gboolean from_peer)
{
/* send callback to originating child */
crm_client_t *client_obj = NULL;
int local_rc = pcmk_ok;
int call_id = 0;
crm_element_value_int(notify_src, F_CIB_CALLID, &call_id);
if (client_id != NULL) {
client_obj = crm_client_get_by_id(client_id);
}
if (client_obj == NULL) {
local_rc = -ECONNRESET;
crm_trace("No client to sent response %d to, F_CIB_CLIENTID not set.", call_id);
} else {
int rid = 0;
if (sync_reply) {
if (client_obj->ipcs) {
CRM_LOG_ASSERT(client_obj->request_id);
rid = client_obj->request_id;
client_obj->request_id = 0;
crm_trace("Sending response %d to %s %s",
rid, client_obj->name,
from_peer ? "(originator of delegated request)" : "");
} else {
crm_trace("Sending response [call %d] to %s %s",
call_id, client_obj->name, from_peer ? "(originator of delegated request)" : "");
}
} else {
crm_trace("Sending event %d to %s %s",
call_id, client_obj->name, from_peer ? "(originator of delegated request)" : "");
}
switch (client_obj->kind) {
case CRM_CLIENT_IPC:
if (crm_ipcs_send(client_obj, rid, notify_src, sync_reply?crm_ipc_flags_none:crm_ipc_server_event) < 0) {
local_rc = -ENOMSG;
}
break;
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
#endif
case CRM_CLIENT_TCP:
crm_remote_send(client_obj->remote, notify_src);
break;
default:
crm_err("Unknown transport %d for %s", client_obj->kind, client_obj->name);
}
}
if (local_rc != pcmk_ok && client_obj != NULL) {
crm_warn("%sSync reply to %s failed: %s",
sync_reply ? "" : "A-",
client_obj ? client_obj->name : "<unknown>", pcmk_strerror(local_rc));
}
}
static void
local_notify_destroy_callback(gpointer data)
{
cib_local_notify_t *notify = data;
free_xml(notify->notify_src);
free(notify->client_id);
free(notify);
}
static void
check_local_notify(int bcast_id)
{
cib_local_notify_t *notify = NULL;
if (!local_notify_queue) {
return;
}
notify = g_hash_table_lookup(local_notify_queue, GINT_TO_POINTER(bcast_id));
if (notify) {
do_local_notify(notify->notify_src, notify->client_id, notify->sync_reply,
notify->from_peer);
g_hash_table_remove(local_notify_queue, GINT_TO_POINTER(bcast_id));
}
}
static void
queue_local_notify(xmlNode * notify_src, const char *client_id, gboolean sync_reply,
gboolean from_peer)
{
cib_local_notify_t *notify = calloc(1, sizeof(cib_local_notify_t));
notify->notify_src = notify_src;
notify->client_id = strdup(client_id);
notify->sync_reply = sync_reply;
notify->from_peer = from_peer;
if (!local_notify_queue) {
local_notify_queue = g_hash_table_new_full(g_direct_hash,
g_direct_equal, NULL,
local_notify_destroy_callback);
}
g_hash_table_insert(local_notify_queue, GINT_TO_POINTER(cib_local_bcast_num), notify);
}
static void
parse_local_options_v1(crm_client_t * cib_client, int call_type, int call_options, const char *host,
const char *op, gboolean * local_notify, gboolean * needs_reply,
gboolean * process, gboolean * needs_forward)
{
if (cib_op_modifies(call_type)
&& !(call_options & cib_inhibit_bcast)) {
/* we need to send an update anyway */
*needs_reply = TRUE;
} else {
*needs_reply = FALSE;
}
if (host == NULL && (call_options & cib_scope_local)) {
crm_trace("Processing locally scoped %s op from %s", op, cib_client->name);
*local_notify = TRUE;
} else if (host == NULL && cib_is_master) {
crm_trace("Processing master %s op locally from %s", op, cib_client->name);
*local_notify = TRUE;
} else if (safe_str_eq(host, cib_our_uname)) {
crm_trace("Processing locally addressed %s op from %s", op, cib_client->name);
*local_notify = TRUE;
} else if (stand_alone) {
*needs_forward = FALSE;
*local_notify = TRUE;
*process = TRUE;
} else {
crm_trace("%s op from %s needs to be forwarded to %s",
op, cib_client->name, host ? host : "the master instance");
*needs_forward = TRUE;
*process = FALSE;
}
}
static void
parse_local_options_v2(crm_client_t * cib_client, int call_type, int call_options, const char *host,
const char *op, gboolean * local_notify, gboolean * needs_reply,
gboolean * process, gboolean * needs_forward)
{
if (cib_op_modifies(call_type)) {
if(safe_str_eq(op, CIB_OP_MASTER) || safe_str_eq(op, CIB_OP_SLAVE)) {
/* Always handle these locally */
*process = TRUE;
*needs_reply = FALSE;
*local_notify = TRUE;
*needs_forward = FALSE;
return;
} else {
/* Redirect all other updates via CPG */
*needs_reply = TRUE;
*needs_forward = TRUE;
*process = FALSE;
crm_trace("%s op from %s needs to be forwarded to %s",
op, cib_client->name, host ? host : "the master instance");
return;
}
}
*process = TRUE;
*needs_reply = FALSE;
*local_notify = TRUE;
*needs_forward = FALSE;
if (stand_alone) {
crm_trace("Processing %s op from %s (stand-alone)", op, cib_client->name);
} else if (host == NULL) {
crm_trace("Processing unaddressed %s op from %s", op, cib_client->name);
} else if (safe_str_eq(host, cib_our_uname)) {
crm_trace("Processing locally addressed %s op from %s", op, cib_client->name);
} else {
crm_trace("%s op from %s needs to be forwarded to %s", op, cib_client->name, host);
*needs_forward = TRUE;
*process = FALSE;
}
}
static void
parse_local_options(crm_client_t * cib_client, int call_type, int call_options, const char *host,
const char *op, gboolean * local_notify, gboolean * needs_reply,
gboolean * process, gboolean * needs_forward)
{
if(cib_legacy_mode()) {
parse_local_options_v1(cib_client, call_type, call_options, host,
op, local_notify, needs_reply, process, needs_forward);
} else {
parse_local_options_v2(cib_client, call_type, call_options, host,
op, local_notify, needs_reply, process, needs_forward);
}
}
static gboolean
parse_peer_options_v1(int call_type, xmlNode * request,
gboolean * local_notify, gboolean * needs_reply, gboolean * process,
gboolean * needs_forward)
{
const char *op = NULL;
const char *host = NULL;
const char *delegated = NULL;
const char *originator = crm_element_value(request, F_ORIG);
const char *reply_to = crm_element_value(request, F_CIB_ISREPLY);
const char *update = crm_element_value(request, F_CIB_GLOBAL_UPDATE);
gboolean is_reply = safe_str_eq(reply_to, cib_our_uname);
if (crm_is_true(update)) {
*needs_reply = FALSE;
if (is_reply) {
*local_notify = TRUE;
crm_trace("Processing global/peer update from %s"
" that originated from us", originator);
} else {
crm_trace("Processing global/peer update from %s", originator);
}
return TRUE;
}
crm_trace("Processing %s request sent by %s", op, originator);
op = crm_element_value(request, F_CIB_OPERATION);
if (safe_str_eq(op, "cib_shutdown_req")) {
/* Always process these */
*local_notify = FALSE;
if (reply_to == NULL || is_reply) {
*process = TRUE;
}
if (is_reply) {
*needs_reply = FALSE;
}
return *process;
}
if (is_reply && safe_str_eq(op, CRM_OP_PING)) {
process_ping_reply(request);
return FALSE;
}
if (is_reply) {
crm_trace("Forward reply sent from %s to local clients", originator);
*process = FALSE;
*needs_reply = FALSE;
*local_notify = TRUE;
return TRUE;
}
host = crm_element_value(request, F_CIB_HOST);
if (host != NULL && safe_str_eq(host, cib_our_uname)) {
crm_trace("Processing %s request sent to us from %s", op, originator);
return TRUE;
} else if(is_reply == FALSE && safe_str_eq(op, CRM_OP_PING)) {
crm_trace("Processing %s request sent to %s by %s", op, host?host:"everyone", originator);
*needs_reply = TRUE;
return TRUE;
} else if (host == NULL && cib_is_master == TRUE) {
crm_trace("Processing %s request sent to master instance from %s", op, originator);
return TRUE;
}
delegated = crm_element_value(request, F_CIB_DELEGATED);
if (delegated != NULL) {
crm_trace("Ignoring msg for master instance");
} else if (host != NULL) {
/* this is for a specific instance and we're not it */
crm_trace("Ignoring msg for instance on %s", crm_str(host));
} else if (reply_to == NULL && cib_is_master == FALSE) {
/* this is for the master instance and we're not it */
crm_trace("Ignoring reply to %s", crm_str(reply_to));
} else if (safe_str_eq(op, "cib_shutdown_req")) {
if (reply_to != NULL) {
crm_debug("Processing %s from %s", op, host);
*needs_reply = FALSE;
} else {
crm_debug("Processing %s reply from %s", op, host);
}
return TRUE;
} else {
crm_err("Nothing for us to do?");
crm_log_xml_err(request, "Peer[inbound]");
}
return FALSE;
}
static gboolean
parse_peer_options_v2(int call_type, xmlNode * request,
gboolean * local_notify, gboolean * needs_reply, gboolean * process,
gboolean * needs_forward)
{
const char *host = NULL;
const char *delegated = crm_element_value(request, F_CIB_DELEGATED);
const char *op = crm_element_value(request, F_CIB_OPERATION);
const char *originator = crm_element_value(request, F_ORIG);
const char *reply_to = crm_element_value(request, F_CIB_ISREPLY);
const char *update = crm_element_value(request, F_CIB_GLOBAL_UPDATE);
gboolean is_reply = safe_str_eq(reply_to, cib_our_uname);
if(safe_str_eq(op, CIB_OP_REPLACE)) {
/* sync_our_cib() sets F_CIB_ISREPLY */
if (reply_to) {
delegated = reply_to;
}
goto skip_is_reply;
} else if(safe_str_eq(op, CIB_OP_SYNC)) {
} else if (is_reply && safe_str_eq(op, CRM_OP_PING)) {
process_ping_reply(request);
return FALSE;
} else if (safe_str_eq(op, CIB_OP_UPGRADE)) {
/* Only the DC (node with the oldest software) should process
* this operation if F_CIB_SCHEMA_MAX is unset
*
* If the DC is happy it will then send out another
* CIB_OP_UPGRADE which will tell all nodes to do the actual
* upgrade.
*
* Except this time F_CIB_SCHEMA_MAX will be set which puts a
* limit on how far newer nodes will go
*/
const char *max = crm_element_value(request, F_CIB_SCHEMA_MAX);
crm_trace("Parsing %s operation%s for %s with max=%s",
op, is_reply?" reply":"", cib_is_master?"master":"slave", max);
if(max == NULL && cib_is_master) {
/* We are the DC, check if this upgrade is allowed */
goto skip_is_reply;
} else if(max) {
/* Ok, go ahead and upgrade to 'max' */
goto skip_is_reply;
} else {
return FALSE; /* Ignore */
}
} else if (crm_is_true(update)) {
crm_info("Detected legacy %s global update from %s", op, originator);
send_sync_request(NULL);
legacy_mode = TRUE;
return FALSE;
} else if (is_reply && cib_op_modifies(call_type)) {
crm_trace("Ignoring legacy %s reply sent from %s to local clients", op, originator);
return FALSE;
} else if (safe_str_eq(op, "cib_shutdown_req")) {
/* Legacy handling */
crm_debug("Legacy handling of %s message from %s", op, originator);
*local_notify = FALSE;
if (reply_to == NULL) {
*process = TRUE;
}
return *process;
}
if(is_reply) {
crm_trace("Handling %s reply sent from %s to local clients", op, originator);
*process = FALSE;
*needs_reply = FALSE;
*local_notify = TRUE;
return TRUE;
}
skip_is_reply:
*process = TRUE;
*needs_reply = FALSE;
if(safe_str_eq(delegated, cib_our_uname)) {
*local_notify = TRUE;
} else {
*local_notify = FALSE;
}
host = crm_element_value(request, F_CIB_HOST);
if (host != NULL && safe_str_eq(host, cib_our_uname)) {
crm_trace("Processing %s request sent to us from %s", op, originator);
*needs_reply = TRUE;
return TRUE;
} else if (host != NULL) {
/* this is for a specific instance and we're not it */
crm_trace("Ignoring %s operation for instance on %s", op, crm_str(host));
return FALSE;
} else if(is_reply == FALSE && safe_str_eq(op, CRM_OP_PING)) {
*needs_reply = TRUE;
}
crm_trace("Processing %s request sent to everyone by %s/%s on %s %s", op,
crm_element_value(request, F_CIB_CLIENTNAME),
crm_element_value(request, F_CIB_CALLID),
originator, (*local_notify)?"(notify)":"");
return TRUE;
}
static gboolean
parse_peer_options(int call_type, xmlNode * request,
gboolean * local_notify, gboolean * needs_reply, gboolean * process,
gboolean * needs_forward)
{
/* TODO: What happens when an update comes in after node A
* requests the CIB from node B, but before it gets the reply (and
* sends out the replace operation)
*/
if(cib_legacy_mode()) {
return parse_peer_options_v1(
call_type, request, local_notify, needs_reply, process, needs_forward);
} else {
return parse_peer_options_v2(
call_type, request, local_notify, needs_reply, process, needs_forward);
}
}
static void
forward_request(xmlNode * request, crm_client_t * cib_client, int call_options)
{
const char *op = crm_element_value(request, F_CIB_OPERATION);
const char *host = crm_element_value(request, F_CIB_HOST);
crm_xml_add(request, F_CIB_DELEGATED, cib_our_uname);
if (host != NULL) {
crm_trace("Forwarding %s op to %s", op, host);
send_cluster_message(crm_get_peer(0, host), crm_msg_cib, request, FALSE);
} else {
crm_trace("Forwarding %s op to master instance", op);
send_cluster_message(NULL, crm_msg_cib, request, FALSE);
}
/* Return the request to its original state */
xml_remove_prop(request, F_CIB_DELEGATED);
if (call_options & cib_discard_reply) {
crm_trace("Client not interested in reply");
}
}
static gboolean
send_peer_reply(xmlNode * msg, xmlNode * result_diff, const char *originator, gboolean broadcast)
{
CRM_ASSERT(msg != NULL);
if (broadcast) {
/* this (successful) call modified the CIB _and_ the
* change needs to be broadcast...
* send via HA to other nodes
*/
int diff_add_updates = 0;
int diff_add_epoch = 0;
int diff_add_admin_epoch = 0;
int diff_del_updates = 0;
int diff_del_epoch = 0;
int diff_del_admin_epoch = 0;
const char *digest = NULL;
int format = 1;
CRM_LOG_ASSERT(result_diff != NULL);
digest = crm_element_value(result_diff, XML_ATTR_DIGEST);
crm_element_value_int(result_diff, "format", &format);
cib_diff_version_details(result_diff,
&diff_add_admin_epoch, &diff_add_epoch, &diff_add_updates,
&diff_del_admin_epoch, &diff_del_epoch, &diff_del_updates);
crm_trace("Sending update diff %d.%d.%d -> %d.%d.%d %s",
diff_del_admin_epoch, diff_del_epoch, diff_del_updates,
diff_add_admin_epoch, diff_add_epoch, diff_add_updates, digest);
crm_xml_add(msg, F_CIB_ISREPLY, originator);
crm_xml_add(msg, F_CIB_GLOBAL_UPDATE, XML_BOOLEAN_TRUE);
crm_xml_add(msg, F_CIB_OPERATION, CIB_OP_APPLY_DIFF);
if (format == 1) {
CRM_ASSERT(digest != NULL);
}
add_message_xml(msg, F_CIB_UPDATE_DIFF, result_diff);
crm_log_xml_explicit(msg, "copy");
return send_cluster_message(NULL, crm_msg_cib, msg, TRUE);
} else if (originator != NULL) {
/* send reply via HA to originating node */
crm_trace("Sending request result to %s only", originator);
crm_xml_add(msg, F_CIB_ISREPLY, originator);
return send_cluster_message(crm_get_peer(0, originator), crm_msg_cib, msg, FALSE);
}
return FALSE;
}
void
cib_process_request(xmlNode * request, gboolean force_synchronous, gboolean privileged,
gboolean unused, crm_client_t * cib_client)
{
int call_type = 0;
int call_options = 0;
gboolean process = TRUE;
gboolean is_update = TRUE;
gboolean from_peer = TRUE;
gboolean needs_reply = TRUE;
gboolean local_notify = FALSE;
gboolean needs_forward = FALSE;
gboolean global_update = crm_is_true(crm_element_value(request, F_CIB_GLOBAL_UPDATE));
xmlNode *op_reply = NULL;
xmlNode *result_diff = NULL;
int rc = pcmk_ok;
const char *op = crm_element_value(request, F_CIB_OPERATION);
const char *originator = crm_element_value(request, F_ORIG);
const char *host = crm_element_value(request, F_CIB_HOST);
const char *target = NULL;
const char *call_id = crm_element_value(request, F_CIB_CALLID);
const char *client_id = crm_element_value(request, F_CIB_CLIENTID);
const char *client_name = crm_element_value(request, F_CIB_CLIENTNAME);
const char *reply_to = crm_element_value(request, F_CIB_ISREPLY);
if (cib_client) {
from_peer = FALSE;
}
cib_num_ops++;
if (cib_num_ops == 0) {
cib_num_fail = 0;
cib_num_local = 0;
cib_num_updates = 0;
crm_info("Stats wrapped around");
}
crm_element_value_int(request, F_CIB_CALLOPTS, &call_options);
if (force_synchronous) {
call_options |= cib_sync_call;
}
if (host != NULL && strlen(host) == 0) {
host = NULL;
}
if (host) {
target = host;
} else if (call_options & cib_scope_local) {
target = "local host";
} else {
target = "master";
}
if (from_peer) {
crm_trace("Processing peer %s operation from %s/%s on %s intended for %s (reply=%s)",
op, client_name, call_id, originator, target, reply_to);
} else {
crm_xml_add(request, F_ORIG, cib_our_uname);
crm_trace("Processing local %s operation from %s/%s intended for %s", op, client_name, call_id, target);
}
rc = cib_get_operation_id(op, &call_type);
if (rc != pcmk_ok) {
/* TODO: construct error reply? */
crm_err("Pre-processing of command failed: %s", pcmk_strerror(rc));
return;
}
if (from_peer == FALSE) {
parse_local_options(cib_client, call_type, call_options, host, op,
&local_notify, &needs_reply, &process, &needs_forward);
} else if (parse_peer_options(call_type, request, &local_notify,
&needs_reply, &process, &needs_forward) == FALSE) {
return;
}
is_update = cib_op_modifies(call_type);
if (is_update) {
cib_num_updates++;
}
if (call_options & cib_discard_reply) {
needs_reply = is_update;
local_notify = FALSE;
}
if (needs_forward) {
const char *host = crm_element_value(request, F_CIB_HOST);
const char *section = crm_element_value(request, F_CIB_SECTION);
int log_level = LOG_INFO;
if (safe_str_eq(op, CRM_OP_NOOP)) {
log_level = LOG_DEBUG;
}
do_crm_log(log_level,
"Forwarding %s operation for section %s to %s (origin=%s/%s/%s)",
op,
section ? section : "'all'",
host ? host : cib_legacy_mode() ? "master" : "all",
originator ? originator : "local",
client_name, call_id);
forward_request(request, cib_client, call_options);
return;
}
if (cib_status != pcmk_ok) {
const char *call = crm_element_value(request, F_CIB_CALLID);
rc = cib_status;
crm_err("Operation ignored, cluster configuration is invalid."
" Please repair and restart: %s", pcmk_strerror(cib_status));
op_reply = create_xml_node(NULL, "cib-reply");
crm_xml_add(op_reply, F_TYPE, T_CIB);
crm_xml_add(op_reply, F_CIB_OPERATION, op);
crm_xml_add(op_reply, F_CIB_CALLID, call);
crm_xml_add(op_reply, F_CIB_CLIENTID, client_id);
crm_xml_add_int(op_reply, F_CIB_CALLOPTS, call_options);
crm_xml_add_int(op_reply, F_CIB_RC, rc);
crm_trace("Attaching reply output");
add_message_xml(op_reply, F_CIB_CALLDATA, the_cib);
crm_log_xml_explicit(op_reply, "cib:reply");
} else if (process) {
time_t finished = 0;
int now = time(NULL);
int level = LOG_INFO;
const char *section = crm_element_value(request, F_CIB_SECTION);
cib_num_local++;
rc = cib_process_command(request, &op_reply, &result_diff, privileged);
if (is_update == FALSE) {
level = LOG_TRACE;
} else if (global_update) {
switch (rc) {
case pcmk_ok:
level = LOG_INFO;
break;
case -pcmk_err_old_data:
case -pcmk_err_diff_resync:
case -pcmk_err_diff_failed:
level = LOG_TRACE;
break;
default:
level = LOG_ERR;
}
} else if (rc != pcmk_ok && is_update) {
cib_num_fail++;
level = LOG_WARNING;
}
do_crm_log(level,
"Completed %s operation for section %s: %s (rc=%d, origin=%s/%s/%s, version=%s.%s.%s)",
op, section ? section : "'all'", pcmk_strerror(rc), rc,
originator ? originator : "local", client_name, call_id,
the_cib ? crm_element_value(the_cib, XML_ATTR_GENERATION_ADMIN) : "0",
the_cib ? crm_element_value(the_cib, XML_ATTR_GENERATION) : "0",
the_cib ? crm_element_value(the_cib, XML_ATTR_NUMUPDATES) : "0");
finished = time(NULL);
if (finished - now > 3) {
crm_trace("%s operation took %lds to complete", op, (long)(finished - now));
crm_write_blackbox(0, NULL);
}
if (op_reply == NULL && (needs_reply || local_notify)) {
crm_err("Unexpected NULL reply to message");
crm_log_xml_err(request, "null reply");
needs_reply = FALSE;
local_notify = FALSE;
}
}
/* from now on we are the server */
if(is_update && cib_legacy_mode() == FALSE) {
crm_trace("Completed pre-sync update from %s/%s/%s%s",
originator ? originator : "local", client_name, call_id,
local_notify?" with local notification":"");
} else if (needs_reply == FALSE || stand_alone) {
/* nothing more to do...
* this was a non-originating slave update
*/
crm_trace("Completed slave update");
} else if (cib_legacy_mode() &&
rc == pcmk_ok && result_diff != NULL && !(call_options & cib_inhibit_bcast)) {
gboolean broadcast = FALSE;
cib_local_bcast_num++;
crm_xml_add_int(request, F_CIB_LOCAL_NOTIFY_ID, cib_local_bcast_num);
broadcast = send_peer_reply(request, result_diff, originator, TRUE);
if (broadcast && client_id && local_notify && op_reply) {
/* If we have been asked to sync the reply,
* and a bcast msg has gone out, we queue the local notify
* until we know the bcast message has been received */
local_notify = FALSE;
crm_trace("Queuing local %ssync notification for %s",
(call_options & cib_sync_call) ? "" : "a-", client_id);
queue_local_notify(op_reply, client_id, (call_options & cib_sync_call), from_peer);
op_reply = NULL; /* the reply is queued, so don't free here */
}
} else if (call_options & cib_discard_reply) {
crm_trace("Caller isn't interested in reply");
} else if (from_peer) {
if (is_update == FALSE || result_diff == NULL) {
crm_trace("Request not broadcast: R/O call");
} else if (call_options & cib_inhibit_bcast) {
crm_trace("Request not broadcast: inhibited");
} else if (rc != pcmk_ok) {
crm_trace("Request not broadcast: call failed: %s", pcmk_strerror(rc));
} else {
crm_trace("Directing reply to %s", originator);
}
send_peer_reply(op_reply, result_diff, originator, FALSE);
}
if (local_notify && client_id) {
crm_trace("Performing local %ssync notification for %s",
(call_options & cib_sync_call) ? "" : "a-", client_id);
if (process == FALSE) {
do_local_notify(request, client_id, call_options & cib_sync_call, from_peer);
} else {
do_local_notify(op_reply, client_id, call_options & cib_sync_call, from_peer);
}
}
free_xml(op_reply);
free_xml(result_diff);
return;
}
int
cib_process_command(xmlNode * request, xmlNode ** reply, xmlNode ** cib_diff, gboolean privileged)
{
xmlNode *input = NULL;
xmlNode *output = NULL;
xmlNode *result_cib = NULL;
xmlNode *current_cib = NULL;
int call_type = 0;
int call_options = 0;
const char *op = NULL;
const char *section = NULL;
const char *call_id = crm_element_value(request, F_CIB_CALLID);
int rc = pcmk_ok;
int rc2 = pcmk_ok;
gboolean send_r_notify = FALSE;
gboolean global_update = FALSE;
gboolean config_changed = FALSE;
gboolean manage_counters = TRUE;
static mainloop_timer_t *digest_timer = NULL;
CRM_ASSERT(cib_status == pcmk_ok);
if(digest_timer == NULL) {
digest_timer = mainloop_timer_add("digester", 5000, FALSE, cib_digester_cb, NULL);
}
*reply = NULL;
*cib_diff = NULL;
current_cib = the_cib;
/* Start processing the request... */
op = crm_element_value(request, F_CIB_OPERATION);
crm_element_value_int(request, F_CIB_CALLOPTS, &call_options);
rc = cib_get_operation_id(op, &call_type);
if (rc == pcmk_ok && privileged == FALSE) {
rc = cib_op_can_run(call_type, call_options, privileged, global_update);
}
rc2 = cib_op_prepare(call_type, request, &input, &section);
if (rc == pcmk_ok) {
rc = rc2;
}
if (rc != pcmk_ok) {
crm_trace("Call setup failed: %s", pcmk_strerror(rc));
goto done;
} else if (cib_op_modifies(call_type) == FALSE) {
rc = cib_perform_op(op, call_options, cib_op_func(call_type), TRUE,
section, request, input, FALSE, &config_changed,
current_cib, &result_cib, NULL, &output);
CRM_CHECK(result_cib == NULL, free_xml(result_cib));
goto done;
}
/* Handle a valid write action */
global_update = crm_is_true(crm_element_value(request, F_CIB_GLOBAL_UPDATE));
if (global_update) {
/* legacy code */
manage_counters = FALSE;
call_options |= cib_force_diff;
crm_trace("Global update detected");
CRM_CHECK(call_type == 3 || call_type == 4, crm_err("Call type: %d", call_type);
crm_log_xml_err(request, "bad op"));
}
if (rc == pcmk_ok) {
ping_modified_since = TRUE;
if (call_options & cib_inhibit_bcast) {
/* skip */
crm_trace("Skipping update: inhibit broadcast");
manage_counters = FALSE;
}
if (is_not_set(call_options, cib_dryrun) && safe_str_eq(section, XML_CIB_TAG_STATUS)) {
/* Copying large CIBs accounts for a huge percentage of our CIB usage */
call_options |= cib_zero_copy;
} else {
clear_bit(call_options, cib_zero_copy);
}
/* result_cib must not be modified after cib_perform_op() returns */
rc = cib_perform_op(op, call_options, cib_op_func(call_type), FALSE,
section, request, input, manage_counters, &config_changed,
current_cib, &result_cib, cib_diff, &output);
if (manage_counters == FALSE) {
int format = 1;
/* Legacy code
* If the diff is NULL at this point, it's because nothing changed
*/
if (*cib_diff) {
crm_element_value_int(*cib_diff, "format", &format);
}
if (format == 1) {
config_changed = cib_config_changed(NULL, NULL, cib_diff);
}
}
/* Always write to disk for replace ops,
* this also negates the need to detect ordering changes
*/
if (crm_str_eq(CIB_OP_REPLACE, op, TRUE)) {
config_changed = TRUE;
}
}
if (rc == pcmk_ok && is_not_set(call_options, cib_dryrun)) {
crm_trace("Activating %d %s %s", is_set(call_options, cib_zero_copy),
crm_element_value(current_cib, XML_ATTR_NUMUPDATES),
crm_element_value(result_cib, XML_ATTR_NUMUPDATES));
if(is_not_set(call_options, cib_zero_copy)) {
rc = activateCibXml(result_cib, config_changed, op);
crm_trace("Activated %d %s", rc, crm_element_value(current_cib, XML_ATTR_NUMUPDATES));
}
if (rc == pcmk_ok && cib_internal_config_changed(*cib_diff)) {
cib_read_config(config_hash, result_cib);
}
if (crm_str_eq(CIB_OP_REPLACE, op, TRUE)) {
if (section == NULL) {
send_r_notify = TRUE;
} else if (safe_str_eq(section, XML_TAG_CIB)) {
send_r_notify = TRUE;
} else if (safe_str_eq(section, XML_CIB_TAG_NODES)) {
send_r_notify = TRUE;
} else if (safe_str_eq(section, XML_CIB_TAG_STATUS)) {
send_r_notify = TRUE;
}
} else if (crm_str_eq(CIB_OP_ERASE, op, TRUE)) {
send_r_notify = TRUE;
}
mainloop_timer_stop(digest_timer);
mainloop_timer_start(digest_timer);
} else if (rc == -pcmk_err_schema_validation) {
CRM_ASSERT(is_not_set(call_options, cib_zero_copy));
if (output != NULL) {
crm_log_xml_info(output, "cib:output");
free_xml(output);
}
output = result_cib;
} else {
crm_trace("Not activating %d %d %s", rc, is_set(call_options, cib_dryrun), crm_element_value(result_cib, XML_ATTR_NUMUPDATES));
if(is_not_set(call_options, cib_zero_copy)) {
free_xml(result_cib);
}
}
if ((call_options & (cib_inhibit_notify|cib_dryrun)) == 0) {
const char *client = crm_element_value(request, F_CIB_CLIENTNAME);
crm_trace("Sending notifications %d", is_set(call_options, cib_dryrun));
cib_diff_notify(call_options, client, call_id, op, input, rc, *cib_diff);
}
if (send_r_notify) {
const char *origin = crm_element_value(request, F_ORIG);
cib_replace_notify(origin, the_cib, rc, *cib_diff);
}
xml_log_patchset(LOG_TRACE, "cib:diff", *cib_diff);
done:
if ((call_options & cib_discard_reply) == 0) {
const char *caller = crm_element_value(request, F_CIB_CLIENTID);
*reply = create_xml_node(NULL, "cib-reply");
crm_xml_add(*reply, F_TYPE, T_CIB);
crm_xml_add(*reply, F_CIB_OPERATION, op);
crm_xml_add(*reply, F_CIB_CALLID, call_id);
crm_xml_add(*reply, F_CIB_CLIENTID, caller);
crm_xml_add_int(*reply, F_CIB_CALLOPTS, call_options);
crm_xml_add_int(*reply, F_CIB_RC, rc);
if (output != NULL) {
crm_trace("Attaching reply output");
add_message_xml(*reply, F_CIB_CALLDATA, output);
}
crm_log_xml_explicit(*reply, "cib:reply");
}
crm_trace("cleanup");
if (cib_op_modifies(call_type) == FALSE && output != current_cib) {
free_xml(output);
output = NULL;
}
if (call_type >= 0) {
cib_op_cleanup(call_type, call_options, &input, &output);
}
crm_trace("done");
return rc;
}
gint
cib_GCompareFunc(gconstpointer a, gconstpointer b)
{
const xmlNode *a_msg = a;
const xmlNode *b_msg = b;
int msg_a_id = 0;
int msg_b_id = 0;
const char *value = NULL;
value = crm_element_value_const(a_msg, F_CIB_CALLID);
msg_a_id = crm_parse_int(value, NULL);
value = crm_element_value_const(b_msg, F_CIB_CALLID);
msg_b_id = crm_parse_int(value, NULL);
if (msg_a_id == msg_b_id) {
return 0;
} else if (msg_a_id < msg_b_id) {
return -1;
}
return 1;
}
#if SUPPORT_HEARTBEAT
void
cib_ha_peer_callback(HA_Message * msg, void *private_data)
{
xmlNode *xml = convert_ha_message(NULL, msg, __FUNCTION__);
cib_peer_callback(xml, private_data);
free_xml(xml);
}
#endif
void
cib_peer_callback(xmlNode * msg, void *private_data)
{
const char *reason = NULL;
const char *originator = crm_element_value(msg, F_ORIG);
if (cib_legacy_mode() && (originator == NULL || crm_str_eq(originator, cib_our_uname, TRUE))) {
/* message is from ourselves */
int bcast_id = 0;
if (!(crm_element_value_int(msg, F_CIB_LOCAL_NOTIFY_ID, &bcast_id))) {
check_local_notify(bcast_id);
}
return;
} else if (crm_peer_cache == NULL) {
reason = "membership not established";
goto bail;
}
if (crm_element_value(msg, F_CIB_CLIENTNAME) == NULL) {
crm_xml_add(msg, F_CIB_CLIENTNAME, originator);
}
/* crm_log_xml_trace("Peer[inbound]", msg); */
cib_process_request(msg, FALSE, TRUE, TRUE, NULL);
return;
bail:
if (reason) {
const char *seq = crm_element_value(msg, F_SEQ);
const char *op = crm_element_value(msg, F_CIB_OPERATION);
crm_warn("Discarding %s message (%s) from %s: %s", op, seq, originator, reason);
}
}
#if SUPPORT_HEARTBEAT
extern oc_ev_t *cib_ev_token;
static void *ccm_library = NULL;
int (*ccm_api_callback_done) (void *cookie) = NULL;
int (*ccm_api_handle_event) (const oc_ev_t * token) = NULL;
void
cib_client_status_callback(const char *node, const char *client, const char *status, void *private)
{
/* Heartbeat only */
crm_node_t *peer = NULL;
if (safe_str_eq(client, CRM_SYSTEM_CIB)) {
peer = crm_get_peer(0, node);
if (safe_str_neq(peer->state, CRM_NODE_MEMBER)) {
crm_warn("This peer is not a ccm member (yet). "
"Status ignored: Client %s/%s announced status [%s]",
node, client, status);
return;
}
crm_info("Status update: Client %s/%s now has status [%s]", node, client, status);
if (safe_str_eq(status, JOINSTATUS)) {
status = ONLINESTATUS;
} else if (safe_str_eq(status, LEAVESTATUS)) {
status = OFFLINESTATUS;
}
crm_update_peer_proc(__FUNCTION__, peer, crm_proc_cib, status);
}
return;
}
int
cib_ccm_dispatch(gpointer user_data)
{
int rc = 0;
oc_ev_t *ccm_token = (oc_ev_t *) user_data;
crm_trace("received callback");
if (ccm_api_handle_event == NULL) {
ccm_api_handle_event =
find_library_function(&ccm_library, CCM_LIBRARY, "oc_ev_handle_event", 1);
}
rc = (*ccm_api_handle_event) (ccm_token);
if (0 == rc) {
return 0;
}
crm_err("CCM connection appears to have failed: rc=%d.", rc);
/* eventually it might be nice to recover and reconnect... but until then... */
crm_err("Exiting to recover from CCM connection failure");
return crm_exit(ENOTCONN);
}
int current_instance = 0;
void
cib_ccm_msg_callback(oc_ed_t event, void *cookie, size_t size, const void *data)
{
gboolean update_id = FALSE;
const oc_ev_membership_t *membership = data;
CRM_ASSERT(membership != NULL);
crm_info("Processing CCM event=%s (id=%d)", ccm_event_name(event), membership->m_instance);
if (current_instance > membership->m_instance) {
crm_err("Membership instance ID went backwards! %d->%d",
current_instance, membership->m_instance);
CRM_ASSERT(current_instance <= membership->m_instance);
}
switch (event) {
case OC_EV_MS_NEW_MEMBERSHIP:
case OC_EV_MS_INVALID:
update_id = TRUE;
break;
case OC_EV_MS_PRIMARY_RESTORED:
update_id = TRUE;
break;
case OC_EV_MS_NOT_PRIMARY:
crm_trace("Ignoring transitional CCM event: %s", ccm_event_name(event));
break;
case OC_EV_MS_EVICTED:
crm_err("Evicted from CCM: %s", ccm_event_name(event));
break;
default:
crm_err("Unknown CCM event: %d", event);
}
if (update_id) {
unsigned int lpc = 0;
CRM_CHECK(membership != NULL, return);
current_instance = membership->m_instance;
for (lpc = 0; lpc < membership->m_n_out; lpc++) {
crm_update_ccm_node(membership, lpc + membership->m_out_idx, CRM_NODE_LOST,
current_instance);
}
for (lpc = 0; lpc < membership->m_n_member; lpc++) {
crm_update_ccm_node(membership, lpc + membership->m_memb_idx, CRM_NODE_MEMBER,
current_instance);
}
heartbeat_cluster->llc_ops->client_status(heartbeat_cluster, NULL, crm_system_name, 0);
}
if (ccm_api_callback_done == NULL) {
ccm_api_callback_done =
find_library_function(&ccm_library, CCM_LIBRARY, "oc_ev_callback_done", 1);
}
(*ccm_api_callback_done) (cookie);
return;
}
#endif
gboolean
can_write(int flags)
{
return TRUE;
}
static gboolean
cib_force_exit(gpointer data)
{
crm_notice("Forcing exit!");
terminate_cib(__FUNCTION__, -1);
return FALSE;
}
static void
disconnect_remote_client(gpointer key, gpointer value, gpointer user_data)
{
crm_client_t *a_client = value;
crm_err("Disconnecting %s... Not implemented", crm_str(a_client->name));
}
void
cib_shutdown(int nsig)
{
struct qb_ipcs_stats srv_stats;
if (cib_shutdown_flag == FALSE) {
int disconnects = 0;
qb_ipcs_connection_t *c = NULL;
cib_shutdown_flag = TRUE;
c = qb_ipcs_connection_first_get(ipcs_rw);
while (c != NULL) {
qb_ipcs_connection_t *last = c;
c = qb_ipcs_connection_next_get(ipcs_rw, last);
crm_debug("Disconnecting r/w client %p...", last);
qb_ipcs_disconnect(last);
qb_ipcs_connection_unref(last);
disconnects++;
}
c = qb_ipcs_connection_first_get(ipcs_ro);
while (c != NULL) {
qb_ipcs_connection_t *last = c;
c = qb_ipcs_connection_next_get(ipcs_ro, last);
crm_debug("Disconnecting r/o client %p...", last);
qb_ipcs_disconnect(last);
qb_ipcs_connection_unref(last);
disconnects++;
}
c = qb_ipcs_connection_first_get(ipcs_shm);
while (c != NULL) {
qb_ipcs_connection_t *last = c;
c = qb_ipcs_connection_next_get(ipcs_shm, last);
crm_debug("Disconnecting non-blocking r/w client %p...", last);
qb_ipcs_disconnect(last);
qb_ipcs_connection_unref(last);
disconnects++;
}
disconnects += crm_hash_table_size(client_connections);
crm_debug("Disconnecting %d remote clients", crm_hash_table_size(client_connections));
g_hash_table_foreach(client_connections, disconnect_remote_client, NULL);
crm_info("Disconnected %d clients", disconnects);
}
qb_ipcs_stats_get(ipcs_rw, &srv_stats, QB_FALSE);
if (crm_hash_table_size(client_connections) == 0) {
crm_info("All clients disconnected (%d)", srv_stats.active_connections);
initiate_exit();
} else {
crm_info("Waiting on %d clients to disconnect (%d)",
crm_hash_table_size(client_connections), srv_stats.active_connections);
}
}
void
initiate_exit(void)
{
int active = 0;
xmlNode *leaving = NULL;
active = crm_active_peers();
if (active < 2) {
terminate_cib(__FUNCTION__, 0);
return;
}
crm_info("Sending disconnect notification to %d peers...", active);
leaving = create_xml_node(NULL, "exit-notification");
crm_xml_add(leaving, F_TYPE, "cib");
crm_xml_add(leaving, F_CIB_OPERATION, "cib_shutdown_req");
send_cluster_message(NULL, crm_msg_cib, leaving, TRUE);
free_xml(leaving);
g_timeout_add(crm_get_msec("5s"), cib_force_exit, NULL);
}
extern int remote_fd;
extern int remote_tls_fd;
-/*
+/*!
* \internal
* \brief Close remote sockets, free the global CIB and quit
*
* \param[in] caller Name of calling function (for log message)
* \param[in] fast If 1, skip disconnect; if -1, also exit error
*/
void
terminate_cib(const char *caller, int fast)
{
crm_info("%s: Exiting%s...", caller,
(fast < 0)? " fast" : mainloop ? " from mainloop" : "");
if (remote_fd > 0) {
close(remote_fd);
remote_fd = 0;
}
if (remote_tls_fd > 0) {
close(remote_tls_fd);
remote_tls_fd = 0;
}
uninitializeCib();
if (fast < 0) {
/* Quit fast on error */
cib_ipc_servers_destroy(ipcs_ro, ipcs_rw, ipcs_shm);
crm_exit(EINVAL);
} else if ((mainloop != NULL) && g_main_is_running(mainloop)) {
/* Quit via returning from the main loop. If fast == 1, we skip the
* disconnect here, and it will be done when the main loop returns
* (this allows the peer status callback to avoid messing with the
* peer caches).
*/
if (fast == 0) {
crm_cluster_disconnect(&crm_cluster);
}
g_main_quit(mainloop);
} else {
/* Quit via clean exit. Even the peer status callback can disconnect
* here, because we're not returning control to the caller. */
crm_cluster_disconnect(&crm_cluster);
cib_ipc_servers_destroy(ipcs_ro, ipcs_rw, ipcs_shm);
crm_exit(pcmk_ok);
}
}
diff --git a/crmd/lrm.c b/crmd/lrm.c
index 2988974ad3..c987e499e7 100644
--- a/crmd/lrm.c
+++ b/crmd/lrm.c
@@ -1,2466 +1,2466 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <crm/crm.h>
#include <crm/services.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <crmd.h>
#include <crmd_fsa.h>
#include <crmd_messages.h>
#include <crmd_callbacks.h>
#include <crmd_lrm.h>
#define START_DELAY_THRESHOLD 5 * 60 * 1000
#define MAX_LRM_REG_FAILS 30
struct delete_event_s {
int rc;
const char *rsc;
lrm_state_t *lrm_state;
};
static gboolean is_rsc_active(lrm_state_t * lrm_state, const char *rsc_id);
static gboolean build_active_RAs(lrm_state_t * lrm_state, xmlNode * rsc_list);
static gboolean stop_recurring_actions(gpointer key, gpointer value, gpointer user_data);
static int delete_rsc_status(lrm_state_t * lrm_state, const char *rsc_id, int call_options,
const char *user_name);
static lrmd_event_data_t *construct_op(lrm_state_t * lrm_state, xmlNode * rsc_op,
const char *rsc_id, const char *operation);
static void do_lrm_rsc_op(lrm_state_t * lrm_state, lrmd_rsc_info_t * rsc, const char *operation,
xmlNode * msg, xmlNode * request);
void send_direct_ack(const char *to_host, const char *to_sys,
lrmd_rsc_info_t * rsc, lrmd_event_data_t * op, const char *rsc_id);
static gboolean lrm_state_verify_stopped(lrm_state_t * lrm_state, enum crmd_fsa_state cur_state,
int log_level);
static int do_update_resource(const char *node_name, lrmd_rsc_info_t * rsc, lrmd_event_data_t * op);
static void
lrm_connection_destroy(void)
{
if (is_set(fsa_input_register, R_LRM_CONNECTED)) {
crm_crit("LRM Connection failed");
register_fsa_input(C_FSA_INTERNAL, I_ERROR, NULL);
clear_bit(fsa_input_register, R_LRM_CONNECTED);
} else {
crm_info("LRM Connection disconnected");
}
}
static char *
make_stop_id(const char *rsc, int call_id)
{
char *op_id = NULL;
op_id = calloc(1, strlen(rsc) + 34);
if (op_id != NULL) {
snprintf(op_id, strlen(rsc) + 34, "%s:%d", rsc, call_id);
}
return op_id;
}
static void
copy_instance_keys(gpointer key, gpointer value, gpointer user_data)
{
if (strstr(key, CRM_META "_") == NULL) {
g_hash_table_replace(user_data, strdup((const char *)key), strdup((const char *)value));
}
}
static void
copy_meta_keys(gpointer key, gpointer value, gpointer user_data)
{
if (strstr(key, CRM_META "_") != NULL) {
g_hash_table_replace(user_data, strdup((const char *)key), strdup((const char *)value));
}
}
-/*
+/*!
* \internal
* \brief Remove a recurring operation from a resource's history
*
* \param[in,out] history Resource history to modify
* \param[in] op Operation to remove
*
* \return TRUE if the operation was found and removed, FALSE otherwise
*/
static gboolean
history_remove_recurring_op(rsc_history_t *history, const lrmd_event_data_t *op)
{
GList *iter;
for (iter = history->recurring_op_list; iter != NULL; iter = iter->next) {
lrmd_event_data_t *existing = iter->data;
if ((op->interval == existing->interval)
&& crm_str_eq(op->rsc_id, existing->rsc_id, TRUE)
&& safe_str_eq(op->op_type, existing->op_type)) {
history->recurring_op_list = g_list_delete_link(history->recurring_op_list, iter);
lrmd_free_event(existing);
return TRUE;
}
}
return FALSE;
}
-/*
+/*!
* \internal
* \brief Free all recurring operations in resource history
*
* \param[in,out] history Resource history to modify
*/
static void
history_free_recurring_ops(rsc_history_t *history)
{
GList *iter;
for (iter = history->recurring_op_list; iter != NULL; iter = iter->next) {
lrmd_free_event(iter->data);
}
g_list_free(history->recurring_op_list);
history->recurring_op_list = NULL;
}
-/*
+/*!
* \internal
* \brief Free resource history
*
* \param[in,out] history Resource history to free
*/
void
history_free(gpointer data)
{
rsc_history_t *history = (rsc_history_t*)data;
if (history->stop_params) {
g_hash_table_destroy(history->stop_params);
}
/* Don't need to free history->rsc.id because it's set to history->id */
free(history->rsc.type);
free(history->rsc.class);
free(history->rsc.provider);
lrmd_free_event(history->failed);
lrmd_free_event(history->last);
free(history->id);
history_free_recurring_ops(history);
free(history);
}
static void
update_history_cache(lrm_state_t * lrm_state, lrmd_rsc_info_t * rsc, lrmd_event_data_t * op)
{
int target_rc = 0;
rsc_history_t *entry = NULL;
if (op->rsc_deleted) {
crm_debug("Purged history for '%s' after %s", op->rsc_id, op->op_type);
delete_rsc_status(lrm_state, op->rsc_id, cib_quorum_override, NULL);
return;
}
if (safe_str_eq(op->op_type, RSC_NOTIFY)) {
return;
}
crm_debug("Updating history for '%s' with %s op", op->rsc_id, op->op_type);
entry = g_hash_table_lookup(lrm_state->resource_history, op->rsc_id);
if (entry == NULL && rsc) {
entry = calloc(1, sizeof(rsc_history_t));
entry->id = strdup(op->rsc_id);
g_hash_table_insert(lrm_state->resource_history, entry->id, entry);
entry->rsc.id = entry->id;
entry->rsc.type = strdup(rsc->type);
entry->rsc.class = strdup(rsc->class);
if (rsc->provider) {
entry->rsc.provider = strdup(rsc->provider);
} else {
entry->rsc.provider = NULL;
}
} else if (entry == NULL) {
crm_info("Resource %s no longer exists, not updating cache", op->rsc_id);
return;
}
entry->last_callid = op->call_id;
target_rc = rsc_op_expected_rc(op);
if (op->op_status == PCMK_LRM_OP_CANCELLED) {
if (op->interval > 0) {
crm_trace("Removing cancelled recurring op: %s_%s_%d", op->rsc_id, op->op_type,
op->interval);
history_remove_recurring_op(entry, op);
return;
} else {
crm_trace("Skipping %s_%s_%d rc=%d, status=%d", op->rsc_id, op->op_type, op->interval,
op->rc, op->op_status);
}
} else if (did_rsc_op_fail(op, target_rc)) {
/* We must store failed monitors here
* - otherwise the block below will cause them to be forgetten them when a stop happens
*/
if (entry->failed) {
lrmd_free_event(entry->failed);
}
entry->failed = lrmd_copy_event(op);
} else if (op->interval == 0) {
if (entry->last) {
lrmd_free_event(entry->last);
}
entry->last = lrmd_copy_event(op);
if (op->params &&
(safe_str_eq(CRMD_ACTION_START, op->op_type) ||
safe_str_eq("reload", op->op_type) ||
safe_str_eq(CRMD_ACTION_STATUS, op->op_type))) {
if (entry->stop_params) {
g_hash_table_destroy(entry->stop_params);
}
entry->stop_params = g_hash_table_new_full(crm_str_hash,
g_str_equal, g_hash_destroy_str,
g_hash_destroy_str);
g_hash_table_foreach(op->params, copy_instance_keys, entry->stop_params);
}
}
if (op->interval > 0) {
/* Ensure there are no duplicates */
history_remove_recurring_op(entry, op);
crm_trace("Adding recurring op: %s_%s_%d", op->rsc_id, op->op_type, op->interval);
entry->recurring_op_list = g_list_prepend(entry->recurring_op_list, lrmd_copy_event(op));
} else if (entry->recurring_op_list && safe_str_eq(op->op_type, RSC_STATUS) == FALSE) {
crm_trace("Dropping %d recurring ops because of: %s_%s_%d",
g_list_length(entry->recurring_op_list), op->rsc_id,
op->op_type, op->interval);
history_free_recurring_ops(entry);
}
}
-/*
+/*!
* \internal
* \brief Send a direct OK ack for a resource task
*
* \param[in] lrm_state LRM connection
* \param[in] input Input message being ack'ed
* \param[in] rsc_id ID of affected resource
* \param[in] rsc Affected resource (if available)
* \param[in] task Operation task being ack'ed
* \param[in] ack_host Name of host to send ack to
* \param[in] ack_sys IPC system name to ack
*/
static void
send_task_ok_ack(lrm_state_t *lrm_state, ha_msg_input_t *input,
const char *rsc_id, lrmd_rsc_info_t *rsc, const char *task,
const char *ack_host, const char *ack_sys)
{
lrmd_event_data_t *op = construct_op(lrm_state, input->xml, rsc_id, task);
CRM_ASSERT(op != NULL);
op->rc = PCMK_OCF_OK;
op->op_status = PCMK_LRM_OP_DONE;
send_direct_ack(ack_host, ack_sys, rsc, op, rsc_id);
lrmd_free_event(op);
}
void
lrm_op_callback(lrmd_event_data_t * op)
{
const char *nodename = NULL;
lrm_state_t *lrm_state = NULL;
CRM_CHECK(op != NULL, return);
/* determine the node name for this connection. */
nodename = op->remote_nodename ? op->remote_nodename : fsa_our_uname;
if (op->type == lrmd_event_disconnect && (safe_str_eq(nodename, fsa_our_uname))) {
/* if this is the local lrmd ipc connection, set the right bits in the
* crmd when the connection goes down */
lrm_connection_destroy();
return;
} else if (op->type != lrmd_event_exec_complete) {
/* we only need to process execution results */
return;
}
lrm_state = lrm_state_find(nodename);
CRM_ASSERT(lrm_state != NULL);
process_lrm_event(lrm_state, op, NULL);
}
/* A_LRM_CONNECT */
void
do_lrm_control(long long action,
enum crmd_fsa_cause cause,
enum crmd_fsa_state cur_state,
enum crmd_fsa_input current_input, fsa_data_t * msg_data)
{
/* This only pertains to local lrmd connections. Remote connections are handled as
* resources within the pengine. Connecting and disconnecting from remote lrmd instances
* handled differently than the local. */
lrm_state_t *lrm_state = NULL;
if(fsa_our_uname == NULL) {
return; /* Nothing to do */
}
lrm_state = lrm_state_find_or_create(fsa_our_uname);
if (lrm_state == NULL) {
register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
return;
}
if (action & A_LRM_DISCONNECT) {
if (lrm_state_verify_stopped(lrm_state, cur_state, LOG_INFO) == FALSE) {
if (action == A_LRM_DISCONNECT) {
crmd_fsa_stall(FALSE);
return;
}
}
clear_bit(fsa_input_register, R_LRM_CONNECTED);
crm_info("Disconnecting from the LRM");
lrm_state_disconnect(lrm_state);
lrm_state_reset_tables(lrm_state);
crm_notice("Disconnected from the LRM");
}
if (action & A_LRM_CONNECT) {
int ret = pcmk_ok;
crm_debug("Connecting to the LRM");
ret = lrm_state_ipc_connect(lrm_state);
if (ret != pcmk_ok) {
if (lrm_state->num_lrm_register_fails < MAX_LRM_REG_FAILS) {
crm_warn("Failed to sign on to the LRM %d"
" (%d max) times", lrm_state->num_lrm_register_fails, MAX_LRM_REG_FAILS);
crm_timer_start(wait_timer);
crmd_fsa_stall(FALSE);
return;
}
}
if (ret != pcmk_ok) {
crm_err("Failed to sign on to the LRM %d" " (max) times",
lrm_state->num_lrm_register_fails);
register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
return;
}
set_bit(fsa_input_register, R_LRM_CONNECTED);
crm_info("LRM connection established");
}
if (action & ~(A_LRM_CONNECT | A_LRM_DISCONNECT)) {
crm_err("Unexpected action %s in %s", fsa_action2string(action), __FUNCTION__);
}
}
static gboolean
lrm_state_verify_stopped(lrm_state_t * lrm_state, enum crmd_fsa_state cur_state, int log_level)
{
int counter = 0;
gboolean rc = TRUE;
const char *when = "lrm disconnect";
GHashTableIter gIter;
const char *key = NULL;
rsc_history_t *entry = NULL;
struct recurring_op_s *pending = NULL;
crm_debug("Checking for active resources before exit");
if (cur_state == S_TERMINATE) {
log_level = LOG_ERR;
when = "shutdown";
} else if (is_set(fsa_input_register, R_SHUTDOWN)) {
when = "shutdown... waiting";
}
if (lrm_state->pending_ops && lrm_state_is_connected(lrm_state) == TRUE) {
guint removed = g_hash_table_foreach_remove(
lrm_state->pending_ops, stop_recurring_actions, lrm_state);
guint nremaining = g_hash_table_size(lrm_state->pending_ops);
if (removed || nremaining) {
crm_notice("Stopped %u recurring operations at %s (%u operations remaining)",
removed, when, nremaining);
}
}
if (lrm_state->pending_ops) {
g_hash_table_iter_init(&gIter, lrm_state->pending_ops);
while (g_hash_table_iter_next(&gIter, NULL, (void **)&pending)) {
/* Ignore recurring actions in the shutdown calculations */
if (pending->interval == 0) {
counter++;
}
}
}
if (counter > 0) {
do_crm_log(log_level, "%d pending LRM operations at %s", counter, when);
if (cur_state == S_TERMINATE || !is_set(fsa_input_register, R_SENT_RSC_STOP)) {
g_hash_table_iter_init(&gIter, lrm_state->pending_ops);
while (g_hash_table_iter_next(&gIter, (gpointer*)&key, (gpointer*)&pending)) {
do_crm_log(log_level, "Pending action: %s (%s)", key, pending->op_key);
}
} else {
rc = FALSE;
}
return rc;
}
if (lrm_state->resource_history == NULL) {
return rc;
}
if (cur_state == S_TERMINATE || is_set(fsa_input_register, R_SHUTDOWN)) {
/* At this point we're not waiting, we're just shutting down */
when = "shutdown";
}
counter = 0;
g_hash_table_iter_init(&gIter, lrm_state->resource_history);
while (g_hash_table_iter_next(&gIter, NULL, (gpointer*)&entry)) {
if (is_rsc_active(lrm_state, entry->id) == FALSE) {
continue;
}
counter++;
crm_trace("Found %s active", entry->id);
if (lrm_state->pending_ops) {
GHashTableIter hIter;
g_hash_table_iter_init(&hIter, lrm_state->pending_ops);
while (g_hash_table_iter_next(&hIter, (gpointer*)&key, (gpointer*)&pending)) {
if (crm_str_eq(entry->id, pending->rsc_id, TRUE)) {
crm_notice("%sction %s (%s) incomplete at %s",
pending->interval == 0 ? "A" : "Recurring a",
key, pending->op_key, when);
}
}
}
}
if (counter) {
crm_err("%d resources were active at %s.", counter, when);
}
return rc;
}
GHashTable *metadata_hash = NULL;
static char *
get_rsc_metadata(const char *type, const char *rclass, const char *provider, bool force)
{
int rc = pcmk_ok;
int len = 0;
char *key = NULL;
char *metadata = NULL;
/* Always use a local connection for this operation */
lrm_state_t *lrm_state = lrm_state_find(fsa_our_uname);
CRM_CHECK(type != NULL, return NULL);
CRM_CHECK(rclass != NULL, return NULL);
CRM_CHECK(lrm_state != NULL, return NULL);
if (provider == NULL) {
provider = "heartbeat";
}
if (metadata_hash == NULL) {
metadata_hash = g_hash_table_new_full(crm_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str);
}
len = strlen(type) + strlen(rclass) + strlen(provider) + 4;
key = malloc(len);
if(key == NULL) {
return NULL;
}
snprintf(key, len, "%s::%s:%s", rclass, provider, type);
if(force == FALSE) {
metadata = g_hash_table_lookup(metadata_hash, key);
if (metadata) {
crm_trace("Retrieved cached metadata for %s", key);
}
}
if(metadata == NULL) {
rc = lrm_state_get_metadata(lrm_state, rclass, provider, type, &metadata, 0);
if(rc == pcmk_ok) {
crm_trace("Retrieved live metadata for %s", key);
CRM_LOG_ASSERT(metadata != NULL);
g_hash_table_insert(metadata_hash, key, metadata);
key = NULL;
} else {
crm_trace("No metadata found for %s: %s" CRM_XS " rc=%d",
key, pcmk_strerror(rc), rc);
CRM_CHECK(metadata == NULL, metadata = NULL);
}
}
free(key);
return metadata;
}
static char *
build_parameter_list(lrmd_event_data_t *op, xmlNode *metadata, xmlNode *result,
const char *criteria, bool target, bool invert_for_xml)
{
int len = 0;
int max = 0;
char *list = NULL;
xmlNode *param = NULL;
xmlNode *params = NULL;
const char *secure_terms[] = {
"password",
"passwd",
"user",
};
if(safe_str_eq("private", criteria)) {
/* It will take time for the agents to be updated
* Check for some common terms
*/
max = DIMOF(secure_terms);
}
params = find_xml_node(metadata, "parameters", TRUE);
for (param = __xml_first_child(params); param != NULL; param = __xml_next(param)) {
if (crm_str_eq((const char *)param->name, "parameter", TRUE)) {
bool accept = FALSE;
const char *name = crm_element_value(param, "name");
const char *value = crm_element_value(param, criteria);
if(max && value) {
/* Turn off the compatibility logic once an agent has been updated to know about 'private' */
max = 0;
}
if (name == NULL) {
crm_err("Invalid parameter in %s metadata", op->rsc_id);
} else if(target == crm_is_true(value)) {
accept = TRUE;
} else if(max) {
int lpc = 0;
bool found = FALSE;
for(lpc = 0; found == FALSE && lpc < max; lpc++) {
if(safe_str_eq(secure_terms[lpc], name)) {
found = TRUE;
}
}
if(found == target) {
accept = TRUE;
}
}
if(accept) {
int start = len;
crm_trace("Attr %s is %s%s", name, target?"":"not ", criteria);
len += strlen(name) + 2;
list = realloc_safe(list, len + 1);
sprintf(list + start, " %s ", name);
} else {
crm_trace("Rejecting %s for %s", name, criteria);
}
if(invert_for_xml) {
crm_trace("Inverting %s match for %s xml", name, criteria);
accept = !accept;
}
if(result && accept) {
value = g_hash_table_lookup(op->params, name);
if(value != NULL) {
char *summary = crm_versioned_param_summary(op->versioned_params, name);
if (summary) {
crm_trace("Adding attr %s=%s to the xml result", name, summary);
crm_xml_add(result, name, summary);
free(summary);
} else {
crm_trace("Adding attr %s=%s to the xml result", name, value);
crm_xml_add(result, name, value);
}
}
}
}
}
return list;
}
static bool
resource_supports_action(xmlNode *metadata, const char *name)
{
const char *value = NULL;
xmlNode *action = NULL;
xmlNode *actions = NULL;
actions = find_xml_node(metadata, "actions", TRUE);
for (action = __xml_first_child(actions); action != NULL; action = __xml_next(action)) {
if (crm_str_eq((const char *)action->name, "action", TRUE)) {
value = crm_element_value(action, "name");
if (safe_str_eq(name, value)) {
return TRUE;
}
}
}
return FALSE;
}
static void
append_restart_list(lrmd_event_data_t *op, xmlNode *metadata, xmlNode * update, const char *version)
{
char *list = NULL;
char *digest = NULL;
xmlNode *restart = NULL;
CRM_LOG_ASSERT(op->params != NULL);
if (op->interval > 0) {
/* monitors are not reloadable */
return;
}
if(resource_supports_action(metadata, "reload")) {
restart = create_xml_node(NULL, XML_TAG_PARAMS);
/* Any parameters with unique="1" should be added into the "op-force-restart" list. */
list = build_parameter_list(op, metadata, restart, "unique", TRUE, FALSE);
} else {
/* Resource does not support reloads */
return;
}
digest = calculate_operation_digest(restart, version);
/* Add "op-force-restart" and "op-restart-digest" to indicate the resource supports reload,
* no matter if it actually supports any parameters with unique="1"). */
crm_xml_add(update, XML_LRM_ATTR_OP_RESTART, list? list: "");
crm_xml_add(update, XML_LRM_ATTR_RESTART_DIGEST, digest);
crm_trace("%s: %s, %s", op->rsc_id, digest, list);
crm_log_xml_trace(restart, "restart digest source");
free_xml(restart);
free(digest);
free(list);
}
static void
append_secure_list(lrmd_event_data_t *op, xmlNode *metadata, xmlNode * update, const char *version)
{
char *list = NULL;
char *digest = NULL;
xmlNode *secure = NULL;
CRM_LOG_ASSERT(op->params != NULL);
/*
* To keep XML_LRM_ATTR_OP_SECURE short, we want it to contain the
* secure parameters but XML_LRM_ATTR_SECURE_DIGEST to be based on
* the insecure ones
*/
secure = create_xml_node(NULL, XML_TAG_PARAMS);
list = build_parameter_list(op, metadata, secure, "private", TRUE, TRUE);
if (list != NULL) {
digest = calculate_operation_digest(secure, version);
crm_xml_add(update, XML_LRM_ATTR_OP_SECURE, list);
crm_xml_add(update, XML_LRM_ATTR_SECURE_DIGEST, digest);
crm_trace("%s: %s, %s", op->rsc_id, digest, list);
crm_log_xml_trace(secure, "secure digest source");
} else {
crm_trace("%s: no secure parameters", op->rsc_id);
}
free_xml(secure);
free(digest);
free(list);
}
static gboolean
build_operation_update(xmlNode * parent, lrmd_rsc_info_t * rsc, lrmd_event_data_t * op,
const char *src)
{
int target_rc = 0;
xmlNode *xml_op = NULL;
xmlNode *metadata = NULL;
const char *m_string = NULL;
const char *caller_version = NULL;
if (op == NULL) {
return FALSE;
}
target_rc = rsc_op_expected_rc(op);
/* there is a small risk in formerly mixed clusters that it will
* be sub-optimal.
*
* however with our upgrade policy, the update we send should
* still be completely supported anyway
*/
caller_version = g_hash_table_lookup(op->params, XML_ATTR_CRM_VERSION);
CRM_LOG_ASSERT(caller_version != NULL);
if(caller_version == NULL) {
caller_version = CRM_FEATURE_SET;
}
crm_trace("Building %s operation update with originator version: %s", op->rsc_id, caller_version);
xml_op = create_operation_update(parent, op, caller_version, target_rc, fsa_our_uname, src, LOG_DEBUG);
if (xml_op == NULL) {
return TRUE;
}
if (rsc == NULL || op->params == NULL || crm_str_eq(CRMD_ACTION_STOP, op->op_type, TRUE)) {
/* Stopped resources don't need the digest logic */
crm_trace("No digests needed for %s %p %p %s", op->rsc_id, op->params, rsc, op->op_type);
return TRUE;
}
m_string = get_rsc_metadata(rsc->type, rsc->class, rsc->provider, safe_str_eq(op->op_type, RSC_START));
if(m_string == NULL) {
crm_err("No metadata for %s::%s:%s", rsc->class, rsc->provider, rsc->type);
return TRUE;
}
metadata = string2xml(m_string);
if(metadata == NULL) {
crm_err("Metadata for %s::%s:%s is not valid XML", rsc->class, rsc->provider, rsc->type);
return TRUE;
}
crm_trace("Including additional digests for %s::%s:%s", rsc->class, rsc->provider, rsc->type);
append_restart_list(op, metadata, xml_op, caller_version);
append_secure_list(op, metadata, xml_op, caller_version);
free_xml(metadata);
return TRUE;
}
static gboolean
is_rsc_active(lrm_state_t * lrm_state, const char *rsc_id)
{
rsc_history_t *entry = NULL;
entry = g_hash_table_lookup(lrm_state->resource_history, rsc_id);
if (entry == NULL || entry->last == NULL) {
return FALSE;
}
crm_trace("Processing %s: %s.%d=%d",
rsc_id, entry->last->op_type, entry->last->interval, entry->last->rc);
if (entry->last->rc == PCMK_OCF_OK && safe_str_eq(entry->last->op_type, CRMD_ACTION_STOP)) {
return FALSE;
} else if (entry->last->rc == PCMK_OCF_OK
&& safe_str_eq(entry->last->op_type, CRMD_ACTION_MIGRATE)) {
/* a stricter check is too complex...
* leave that to the PE
*/
return FALSE;
} else if (entry->last->rc == PCMK_OCF_NOT_RUNNING) {
return FALSE;
} else if (entry->last->interval == 0 && entry->last->rc == PCMK_OCF_NOT_CONFIGURED) {
/* Badly configured resources can't be reliably stopped */
return FALSE;
}
return TRUE;
}
static gboolean
build_active_RAs(lrm_state_t * lrm_state, xmlNode * rsc_list)
{
GHashTableIter iter;
rsc_history_t *entry = NULL;
g_hash_table_iter_init(&iter, lrm_state->resource_history);
while (g_hash_table_iter_next(&iter, NULL, (void **)&entry)) {
GList *gIter = NULL;
xmlNode *xml_rsc = create_xml_node(rsc_list, XML_LRM_TAG_RESOURCE);
crm_xml_add(xml_rsc, XML_ATTR_ID, entry->id);
crm_xml_add(xml_rsc, XML_ATTR_TYPE, entry->rsc.type);
crm_xml_add(xml_rsc, XML_AGENT_ATTR_CLASS, entry->rsc.class);
crm_xml_add(xml_rsc, XML_AGENT_ATTR_PROVIDER, entry->rsc.provider);
if (entry->last && entry->last->params) {
const char *container = g_hash_table_lookup(entry->last->params, CRM_META"_"XML_RSC_ATTR_CONTAINER);
if (container) {
crm_trace("Resource %s is a part of container resource %s", entry->id, container);
crm_xml_add(xml_rsc, XML_RSC_ATTR_CONTAINER, container);
}
}
build_operation_update(xml_rsc, &(entry->rsc), entry->failed, __FUNCTION__);
build_operation_update(xml_rsc, &(entry->rsc), entry->last, __FUNCTION__);
for (gIter = entry->recurring_op_list; gIter != NULL; gIter = gIter->next) {
build_operation_update(xml_rsc, &(entry->rsc), gIter->data, __FUNCTION__);
}
}
return FALSE;
}
static xmlNode *
do_lrm_query_internal(lrm_state_t *lrm_state, int update_flags)
{
xmlNode *xml_state = NULL;
xmlNode *xml_data = NULL;
xmlNode *rsc_list = NULL;
crm_node_t *peer = NULL;
peer = crm_get_peer_full(0, lrm_state->node_name, CRM_GET_PEER_ANY);
CRM_CHECK(peer != NULL, return NULL);
xml_state = do_update_node_cib(peer, update_flags, NULL, __FUNCTION__);
xml_data = create_xml_node(xml_state, XML_CIB_TAG_LRM);
crm_xml_add(xml_data, XML_ATTR_ID, peer->uuid);
rsc_list = create_xml_node(xml_data, XML_LRM_TAG_RESOURCES);
/* Build a list of active (not always running) resources */
build_active_RAs(lrm_state, rsc_list);
crm_log_xml_trace(xml_state, "Current state of the LRM");
return xml_state;
}
xmlNode *
do_lrm_query(gboolean is_replace, const char *node_name)
{
lrm_state_t *lrm_state = lrm_state_find(node_name);
xmlNode *xml_state;
if (!lrm_state) {
crm_err("Could not query lrm state for lrmd node %s", node_name);
return NULL;
}
xml_state = do_lrm_query_internal(lrm_state,
node_update_cluster|node_update_peer);
/* In case this function is called to generate a join confirmation to
* send to the DC, force the current and expected join state to member.
* This isn't necessary for newer DCs but is backward compatible.
*/
crm_xml_add(xml_state, XML_NODE_JOIN_STATE, CRMD_JOINSTATE_MEMBER);
crm_xml_add(xml_state, XML_NODE_EXPECTED, CRMD_JOINSTATE_MEMBER);
return xml_state;
}
static void
notify_deleted(lrm_state_t * lrm_state, ha_msg_input_t * input, const char *rsc_id, int rc)
{
lrmd_event_data_t *op = NULL;
const char *from_sys = crm_element_value(input->msg, F_CRM_SYS_FROM);
const char *from_host = crm_element_value(input->msg, F_CRM_HOST_FROM);
crm_info("Notifying %s on %s that %s was%s deleted",
from_sys, from_host, rsc_id, rc == pcmk_ok ? "" : " not");
op = construct_op(lrm_state, input->xml, rsc_id, CRMD_ACTION_DELETE);
CRM_ASSERT(op != NULL);
if (rc == pcmk_ok) {
op->op_status = PCMK_LRM_OP_DONE;
op->rc = PCMK_OCF_OK;
} else {
op->op_status = PCMK_LRM_OP_ERROR;
op->rc = PCMK_OCF_UNKNOWN_ERROR;
}
send_direct_ack(from_host, from_sys, NULL, op, rsc_id);
lrmd_free_event(op);
if (safe_str_neq(from_sys, CRM_SYSTEM_TENGINE)) {
/* this isn't expected - trigger a new transition */
time_t now = time(NULL);
char *now_s = crm_itoa(now);
crm_debug("Triggering a refresh after %s deleted %s from the LRM", from_sys, rsc_id);
update_attr_delegate(fsa_cib_conn, cib_none, XML_CIB_TAG_CRMCONFIG, NULL, NULL, NULL, NULL,
"last-lrm-refresh", now_s, FALSE, NULL, NULL);
free(now_s);
}
}
static gboolean
lrm_remove_deleted_rsc(gpointer key, gpointer value, gpointer user_data)
{
struct delete_event_s *event = user_data;
struct pending_deletion_op_s *op = value;
if (crm_str_eq(event->rsc, op->rsc, TRUE)) {
notify_deleted(event->lrm_state, op->input, event->rsc, event->rc);
return TRUE;
}
return FALSE;
}
static gboolean
lrm_remove_deleted_op(gpointer key, gpointer value, gpointer user_data)
{
const char *rsc = user_data;
struct recurring_op_s *pending = value;
if (crm_str_eq(rsc, pending->rsc_id, TRUE)) {
crm_info("Removing op %s:%d for deleted resource %s",
pending->op_key, pending->call_id, rsc);
return TRUE;
}
return FALSE;
}
/*
* Remove the rsc from the CIB
*
* Avoids refreshing the entire LRM section of this host
*/
#define rsc_template "//"XML_CIB_TAG_STATE"[@uname='%s']//"XML_LRM_TAG_RESOURCE"[@id='%s']"
static int
delete_rsc_status(lrm_state_t * lrm_state, const char *rsc_id, int call_options,
const char *user_name)
{
char *rsc_xpath = NULL;
int max = 0;
int rc = pcmk_ok;
CRM_CHECK(rsc_id != NULL, return -ENXIO);
max = strlen(rsc_template) + strlen(rsc_id) + strlen(lrm_state->node_name) + 1;
rsc_xpath = calloc(1, max);
snprintf(rsc_xpath, max, rsc_template, lrm_state->node_name, rsc_id);
rc = cib_internal_op(fsa_cib_conn, CIB_OP_DELETE, NULL, rsc_xpath,
NULL, NULL, call_options | cib_xpath, user_name);
free(rsc_xpath);
return rc;
}
static void
delete_rsc_entry(lrm_state_t * lrm_state, ha_msg_input_t * input, const char *rsc_id,
GHashTableIter * rsc_gIter, int rc, const char *user_name)
{
struct delete_event_s event;
CRM_CHECK(rsc_id != NULL, return);
if (rc == pcmk_ok) {
char *rsc_id_copy = strdup(rsc_id);
if (rsc_gIter)
g_hash_table_iter_remove(rsc_gIter);
else
g_hash_table_remove(lrm_state->resource_history, rsc_id_copy);
crm_debug("sync: Sending delete op for %s", rsc_id_copy);
delete_rsc_status(lrm_state, rsc_id_copy, cib_quorum_override, user_name);
g_hash_table_foreach_remove(lrm_state->pending_ops, lrm_remove_deleted_op, rsc_id_copy);
free(rsc_id_copy);
}
if (input) {
notify_deleted(lrm_state, input, rsc_id, rc);
}
event.rc = rc;
event.rsc = rsc_id;
event.lrm_state = lrm_state;
g_hash_table_foreach_remove(lrm_state->deletion_ops, lrm_remove_deleted_rsc, &event);
}
/*
* Remove the op from the CIB
*
* Avoids refreshing the entire LRM section of this host
*/
#define op_template "//"XML_CIB_TAG_STATE"[@uname='%s']//"XML_LRM_TAG_RESOURCE"[@id='%s']/"XML_LRM_TAG_RSC_OP"[@id='%s']"
#define op_call_template "//"XML_CIB_TAG_STATE"[@uname='%s']//"XML_LRM_TAG_RESOURCE"[@id='%s']/"XML_LRM_TAG_RSC_OP"[@id='%s' and @"XML_LRM_ATTR_CALLID"='%d']"
static void
delete_op_entry(lrm_state_t * lrm_state, lrmd_event_data_t * op, const char *rsc_id,
const char *key, int call_id)
{
xmlNode *xml_top = NULL;
if (op != NULL) {
xml_top = create_xml_node(NULL, XML_LRM_TAG_RSC_OP);
crm_xml_add_int(xml_top, XML_LRM_ATTR_CALLID, op->call_id);
crm_xml_add(xml_top, XML_ATTR_TRANSITION_KEY, op->user_data);
if (op->interval > 0) {
char *op_id = generate_op_key(op->rsc_id, op->op_type, op->interval);
/* Avoid deleting last_failure too (if it was a result of this recurring op failing) */
crm_xml_add(xml_top, XML_ATTR_ID, op_id);
free(op_id);
}
crm_debug("async: Sending delete op for %s_%s_%d (call=%d)",
op->rsc_id, op->op_type, op->interval, op->call_id);
fsa_cib_conn->cmds->delete(fsa_cib_conn, XML_CIB_TAG_STATUS, xml_top, cib_quorum_override);
} else if (rsc_id != NULL && key != NULL) {
int max = 0;
char *op_xpath = NULL;
if (call_id > 0) {
max =
strlen(op_call_template) + strlen(rsc_id) + strlen(lrm_state->node_name) +
strlen(key) + 10;
op_xpath = calloc(1, max);
snprintf(op_xpath, max, op_call_template, lrm_state->node_name, rsc_id, key, call_id);
} else {
max =
strlen(op_template) + strlen(rsc_id) + strlen(lrm_state->node_name) + strlen(key) +
1;
op_xpath = calloc(1, max);
snprintf(op_xpath, max, op_template, lrm_state->node_name, rsc_id, key);
}
crm_debug("sync: Sending delete op for %s (call=%d)", rsc_id, call_id);
fsa_cib_conn->cmds->delete(fsa_cib_conn, op_xpath, NULL, cib_quorum_override | cib_xpath);
free(op_xpath);
} else {
crm_err("Not enough information to delete op entry: rsc=%p key=%p", rsc_id, key);
return;
}
crm_log_xml_trace(xml_top, "op:cancel");
free_xml(xml_top);
}
void
lrm_clear_last_failure(const char *rsc_id, const char *node_name)
{
char *attr = NULL;
GHashTableIter iter;
GList *lrm_state_list = lrm_state_get_list();
GList *state_entry;
rsc_history_t *entry = NULL;
attr = generate_op_key(rsc_id, "last_failure", 0);
/* This clears last failure for every lrm state that has this rsc.*/
for (state_entry = lrm_state_list; state_entry != NULL; state_entry = state_entry->next) {
lrm_state_t *lrm_state = state_entry->data;
if (node_name != NULL) {
if (strcmp(node_name, lrm_state->node_name) != 0) {
/* filter by node_name if node_name is present */
continue;
}
}
delete_op_entry(lrm_state, NULL, rsc_id, attr, 0);
if (!lrm_state->resource_history) {
continue;
}
g_hash_table_iter_init(&iter, lrm_state->resource_history);
while (g_hash_table_iter_next(&iter, NULL, (void **)&entry)) {
if (crm_str_eq(rsc_id, entry->id, TRUE)) {
lrmd_free_event(entry->failed);
entry->failed = NULL;
}
}
}
free(attr);
g_list_free(lrm_state_list);
}
/* Returns: gboolean - cancellation is in progress */
static gboolean
cancel_op(lrm_state_t * lrm_state, const char *rsc_id, const char *key, int op, gboolean remove)
{
int rc = pcmk_ok;
char *local_key = NULL;
struct recurring_op_s *pending = NULL;
CRM_CHECK(op != 0, return FALSE);
CRM_CHECK(rsc_id != NULL, return FALSE);
if (key == NULL) {
local_key = make_stop_id(rsc_id, op);
key = local_key;
}
pending = g_hash_table_lookup(lrm_state->pending_ops, key);
if (pending) {
if (remove && pending->remove == FALSE) {
pending->remove = TRUE;
crm_debug("Scheduling %s for removal", key);
}
if (pending->cancelled) {
crm_debug("Operation %s already cancelled", key);
free(local_key);
return FALSE;
}
pending->cancelled = TRUE;
} else {
crm_info("No pending op found for %s", key);
free(local_key);
return FALSE;
}
crm_debug("Cancelling op %d for %s (%s)", op, rsc_id, key);
rc = lrm_state_cancel(lrm_state, pending->rsc_id, pending->op_type, pending->interval);
if (rc == pcmk_ok) {
crm_debug("Op %d for %s (%s): cancelled", op, rsc_id, key);
free(local_key);
return TRUE;
}
crm_debug("Op %d for %s (%s): Nothing to cancel", op, rsc_id, key);
/* The caller needs to make sure the entry is
* removed from the pending_ops list
*
* Usually by returning TRUE inside the worker function
* supplied to g_hash_table_foreach_remove()
*
* Not removing the entry from pending_ops will block
* the node from shutting down
*/
free(local_key);
return FALSE;
}
struct cancel_data {
gboolean done;
gboolean remove;
const char *key;
lrmd_rsc_info_t *rsc;
lrm_state_t *lrm_state;
};
static gboolean
cancel_action_by_key(gpointer key, gpointer value, gpointer user_data)
{
gboolean remove = FALSE;
struct cancel_data *data = user_data;
struct recurring_op_s *op = (struct recurring_op_s *)value;
if (crm_str_eq(op->op_key, data->key, TRUE)) {
data->done = TRUE;
remove = !cancel_op(data->lrm_state, data->rsc->id, key, op->call_id, data->remove);
}
return remove;
}
static gboolean
cancel_op_key(lrm_state_t * lrm_state, lrmd_rsc_info_t * rsc, const char *key, gboolean remove)
{
guint removed = 0;
struct cancel_data data;
CRM_CHECK(rsc != NULL, return FALSE);
CRM_CHECK(key != NULL, return FALSE);
data.key = key;
data.rsc = rsc;
data.done = FALSE;
data.remove = remove;
data.lrm_state = lrm_state;
removed = g_hash_table_foreach_remove(lrm_state->pending_ops, cancel_action_by_key, &data);
crm_trace("Removed %u op cache entries, new size: %u",
removed, g_hash_table_size(lrm_state->pending_ops));
return data.done;
}
static lrmd_rsc_info_t *
get_lrm_resource(lrm_state_t * lrm_state, xmlNode * resource, xmlNode * op_msg, gboolean do_create)
{
lrmd_rsc_info_t *rsc = NULL;
const char *id = ID(resource);
const char *type = crm_element_value(resource, XML_ATTR_TYPE);
const char *class = crm_element_value(resource, XML_AGENT_ATTR_CLASS);
const char *provider = crm_element_value(resource, XML_AGENT_ATTR_PROVIDER);
const char *long_id = crm_element_value(resource, XML_ATTR_ID_LONG);
crm_trace("Retrieving %s from the LRM.", id);
CRM_CHECK(id != NULL, return NULL);
rsc = lrm_state_get_rsc_info(lrm_state, id, 0);
if (!rsc && long_id) {
rsc = lrm_state_get_rsc_info(lrm_state, long_id, 0);
}
if (!rsc && do_create) {
CRM_CHECK(class != NULL, return NULL);
CRM_CHECK(type != NULL, return NULL);
crm_trace("Adding rsc %s before operation", id);
lrm_state_register_rsc(lrm_state, id, class, provider, type, lrmd_opt_drop_recurring);
rsc = lrm_state_get_rsc_info(lrm_state, id, 0);
if (!rsc) {
fsa_data_t *msg_data = NULL;
crm_err("Could not add resource %s to LRM %s", id, lrm_state->node_name);
/* only register this as a internal error if this involves the local
* lrmd. Otherwise we're likely dealing with an unresponsive remote-node
* which is not a FSA failure. */
if (lrm_state_is_local(lrm_state) == TRUE) {
register_fsa_error(C_FSA_INTERNAL, I_FAIL, NULL);
}
}
}
return rsc;
}
static void
delete_resource(lrm_state_t * lrm_state,
const char *id,
lrmd_rsc_info_t * rsc,
GHashTableIter * gIter,
const char *sys,
const char *host,
const char *user,
ha_msg_input_t * request,
gboolean unregister)
{
int rc = pcmk_ok;
crm_info("Removing resource %s for %s (%s) on %s", id, sys, user ? user : "internal", host);
if (rsc && unregister) {
rc = lrm_state_unregister_rsc(lrm_state, id, 0);
}
if (rc == pcmk_ok) {
crm_trace("Resource '%s' deleted", id);
} else if (rc == -EINPROGRESS) {
crm_info("Deletion of resource '%s' pending", id);
if (request) {
struct pending_deletion_op_s *op = NULL;
char *ref = crm_element_value_copy(request->msg, XML_ATTR_REFERENCE);
op = calloc(1, sizeof(struct pending_deletion_op_s));
op->rsc = strdup(rsc->id);
op->input = copy_ha_msg_input(request);
g_hash_table_insert(lrm_state->deletion_ops, ref, op);
}
return;
} else {
crm_warn("Deletion of resource '%s' for %s (%s) on %s failed: %d",
id, sys, user ? user : "internal", host, rc);
}
delete_rsc_entry(lrm_state, request, id, gIter, rc, user);
}
static int
get_fake_call_id(lrm_state_t *lrm_state, const char *rsc_id)
{
int call_id = 999999999;
rsc_history_t *entry = NULL;
if(lrm_state) {
entry = g_hash_table_lookup(lrm_state->resource_history, rsc_id);
}
/* Make sure the call id is greater than the last successful operation,
* otherwise the failure will not result in a possible recovery of the resource
* as it could appear the failure occurred before the successful start */
if (entry) {
call_id = entry->last_callid + 1;
}
if (call_id < 0) {
call_id = 1;
}
return call_id;
}
static void
force_reprobe(lrm_state_t *lrm_state, const char *from_sys, const char *from_host, const char *user_name, gboolean is_remote_node)
{
GHashTableIter gIter;
rsc_history_t *entry = NULL;
crm_info("clearing resource history on node %s", lrm_state->node_name);
g_hash_table_iter_init(&gIter, lrm_state->resource_history);
while (g_hash_table_iter_next(&gIter, NULL, (void **)&entry)) {
/* only unregister the resource during a reprobe if it is not a remote connection
* resource. otherwise unregistering the connection will terminate remote-node
* membership */
gboolean unregister = TRUE;
if (is_remote_lrmd_ra(NULL, NULL, entry->id)) {
lrm_state_t *remote_lrm_state = lrm_state_find(entry->id);
if (remote_lrm_state) {
/* when forcing a reprobe, make sure to clear remote node before
* clearing the remote node's connection resource */
force_reprobe(remote_lrm_state, from_sys, from_host, user_name, TRUE);
}
unregister = FALSE;
}
delete_resource(lrm_state, entry->id, &entry->rsc, &gIter, from_sys, from_host,
user_name, NULL, unregister);
}
/* Now delete the copy in the CIB */
erase_status_tag(lrm_state->node_name, XML_CIB_TAG_LRM, cib_scope_local);
/* And finally, _delete_ the value in attrd
* Setting it to FALSE results in the PE sending us back here again
*/
update_attrd(lrm_state->node_name, CRM_OP_PROBED, NULL, user_name, is_remote_node);
}
static void
synthesize_lrmd_failure(lrm_state_t *lrm_state, xmlNode *action, int rc)
{
lrmd_event_data_t *op = NULL;
const char *operation = crm_element_value(action, XML_LRM_ATTR_TASK);
const char *target_node = crm_element_value(action, XML_LRM_ATTR_TARGET);
xmlNode *xml_rsc = find_xml_node(action, XML_CIB_TAG_RESOURCE, TRUE);
if(xml_rsc == NULL) {
/* Do something else? driect_ack? */
crm_info("Skipping %s=%d on %s (%p): no resource",
crm_element_value(action, XML_LRM_ATTR_TASK_KEY), rc, target_node, lrm_state);
return;
} else if(operation == NULL) {
/* This probably came from crm_resource -C, nothing to do */
crm_info("Skipping %s=%d on %s (%p): no operation",
crm_element_value(action, XML_ATTR_TRANSITION_KEY), rc, target_node, lrm_state);
return;
}
op = construct_op(lrm_state, action, ID(xml_rsc), operation);
CRM_ASSERT(op != NULL);
op->call_id = get_fake_call_id(lrm_state, op->rsc_id);
if(safe_str_eq(operation, RSC_NOTIFY)) {
/* Notifications can't fail yet */
op->op_status = PCMK_LRM_OP_DONE;
op->rc = PCMK_OCF_OK;
} else {
op->op_status = PCMK_LRM_OP_ERROR;
op->rc = rc;
}
op->t_run = time(NULL);
op->t_rcchange = op->t_run;
crm_info("Faking result %d for %s_%s_%d on %s (%p)", op->rc, op->rsc_id, op->op_type, op->interval, target_node, lrm_state);
if(lrm_state) {
process_lrm_event(lrm_state, op, NULL);
} else {
lrmd_rsc_info_t rsc;
rsc.id = strdup(op->rsc_id);
rsc.type = crm_element_value_copy(xml_rsc, XML_ATTR_TYPE);
rsc.class = crm_element_value_copy(xml_rsc, XML_AGENT_ATTR_CLASS);
rsc.provider = crm_element_value_copy(xml_rsc, XML_AGENT_ATTR_PROVIDER);
do_update_resource(target_node, &rsc, op);
free(rsc.id);
free(rsc.type);
free(rsc.class);
free(rsc.provider);
}
lrmd_free_event(op);
}
/* A_LRM_INVOKE */
void
do_lrm_invoke(long long action,
enum crmd_fsa_cause cause,
enum crmd_fsa_state cur_state,
enum crmd_fsa_input current_input, fsa_data_t * msg_data)
{
gboolean create_rsc = TRUE;
lrm_state_t *lrm_state = NULL;
const char *crm_op = NULL;
const char *from_sys = NULL;
const char *from_host = NULL;
const char *operation = NULL;
ha_msg_input_t *input = fsa_typed_data(fsa_dt_ha_msg);
const char *user_name = NULL;
const char *target_node = NULL;
gboolean is_remote_node = FALSE;
gboolean crm_rsc_delete = FALSE;
if (input->xml != NULL) {
/* Remote node operations are routed here to their remote connections */
target_node = crm_element_value(input->xml, XML_LRM_ATTR_TARGET);
}
if (target_node == NULL) {
target_node = fsa_our_uname;
} else if (safe_str_neq(target_node, fsa_our_uname)) {
is_remote_node = TRUE;
}
lrm_state = lrm_state_find(target_node);
if (lrm_state == NULL && is_remote_node) {
crm_err("no lrmd connection for remote node %s found on cluster node %s. Can not process request.",
target_node, fsa_our_uname);
/* The action must be recorded here and in the CIB as failed */
synthesize_lrmd_failure(NULL, input->xml, PCMK_OCF_CONNECTION_DIED);
return;
}
CRM_ASSERT(lrm_state != NULL);
#if ENABLE_ACL
user_name = crm_acl_get_set_user(input->msg, F_CRM_USER, NULL);
crm_trace("LRM command from user '%s'", user_name);
#endif
crm_op = crm_element_value(input->msg, F_CRM_TASK);
from_sys = crm_element_value(input->msg, F_CRM_SYS_FROM);
if (safe_str_neq(from_sys, CRM_SYSTEM_TENGINE)) {
from_host = crm_element_value(input->msg, F_CRM_HOST_FROM);
}
crm_trace("LRM command from: %s", from_sys);
if (safe_str_eq(crm_op, CRM_OP_LRM_DELETE)) {
/* remember this delete op came from crm_resource */
crm_rsc_delete = TRUE;
operation = CRMD_ACTION_DELETE;
} else if (safe_str_eq(crm_op, CRM_OP_LRM_REFRESH)) {
operation = CRM_OP_LRM_REFRESH;
} else if (safe_str_eq(crm_op, CRM_OP_LRM_FAIL)) {
lrmd_event_data_t *op = NULL;
lrmd_rsc_info_t *rsc = NULL;
xmlNode *xml_rsc = find_xml_node(input->xml, XML_CIB_TAG_RESOURCE, TRUE);
CRM_CHECK(xml_rsc != NULL, return);
/* The lrmd can not fail a resource, it does not understand the
* concept of success or failure in relation to a resource, it simply
* executes operations and reports the results. We determine what a failure is.
* Because of this, if we want to fail a resource we have to fake what we
* understand a failure to look like.
*
* To do this we create a fake lrmd operation event for the resource
* we want to fail. We then pass that event to the lrmd client callback
* so it will be processed as if it actually came from the lrmd. */
op = construct_op(lrm_state, input->xml, ID(xml_rsc), "asyncmon");
CRM_ASSERT(op != NULL);
free((char *)op->user_data);
op->user_data = NULL;
op->call_id = get_fake_call_id(lrm_state, op->rsc_id);
op->interval = 0;
op->op_status = PCMK_LRM_OP_DONE;
op->rc = PCMK_OCF_UNKNOWN_ERROR;
op->t_run = time(NULL);
op->t_rcchange = op->t_run;
#if ENABLE_ACL
if (user_name && is_privileged(user_name) == FALSE) {
crm_err("%s does not have permission to fail %s", user_name, ID(xml_rsc));
send_direct_ack(from_host, from_sys, NULL, op, ID(xml_rsc));
lrmd_free_event(op);
return;
}
#endif
rsc = get_lrm_resource(lrm_state, xml_rsc, input->xml, create_rsc);
if (rsc) {
crm_info("Failing resource %s...", rsc->id);
process_lrm_event(lrm_state, op, NULL);
op->op_status = PCMK_LRM_OP_DONE;
op->rc = PCMK_OCF_OK;
lrmd_free_rsc_info(rsc);
} else {
crm_info("Cannot find/create resource in order to fail it...");
crm_log_xml_warn(input->msg, "bad input");
}
send_direct_ack(from_host, from_sys, NULL, op, ID(xml_rsc));
lrmd_free_event(op);
return;
} else if (input->xml != NULL) {
operation = crm_element_value(input->xml, XML_LRM_ATTR_TASK);
}
if (safe_str_eq(crm_op, CRM_OP_LRM_REFRESH)) {
int rc = pcmk_ok;
xmlNode *fragment = do_lrm_query_internal(lrm_state, node_update_all);
fsa_cib_update(XML_CIB_TAG_STATUS, fragment, cib_quorum_override, rc, user_name);
crm_info("Forced a local LRM refresh: call=%d", rc);
if(strcmp(CRM_SYSTEM_CRMD, from_sys) != 0) {
xmlNode *reply = create_request(
CRM_OP_INVOKE_LRM, fragment,
from_host, from_sys, CRM_SYSTEM_LRMD, fsa_our_uuid);
crm_debug("ACK'ing refresh from %s (%s)", from_sys, from_host);
if (relay_message(reply, TRUE) == FALSE) {
crm_log_xml_err(reply, "Unable to route reply");
}
free_xml(reply);
}
free_xml(fragment);
} else if (safe_str_eq(crm_op, CRM_OP_LRM_QUERY)) {
xmlNode *data = do_lrm_query_internal(lrm_state, node_update_all);
xmlNode *reply = create_reply(input->msg, data);
if (relay_message(reply, TRUE) == FALSE) {
crm_err("Unable to route reply");
crm_log_xml_err(reply, "reply");
}
free_xml(reply);
free_xml(data);
} else if (safe_str_eq(operation, CRM_OP_PROBED)) {
update_attrd(lrm_state->node_name, CRM_OP_PROBED, XML_BOOLEAN_TRUE, user_name, is_remote_node);
} else if (safe_str_eq(operation, CRM_OP_REPROBE) || safe_str_eq(crm_op, CRM_OP_REPROBE)) {
crm_notice("Forcing the status of all resources to be redetected");
force_reprobe(lrm_state, from_sys, from_host, user_name, is_remote_node);
if(strcmp(CRM_SYSTEM_TENGINE, from_sys) != 0
&& strcmp(CRM_SYSTEM_TENGINE, from_sys) != 0) {
xmlNode *reply = create_request(
CRM_OP_INVOKE_LRM, NULL,
from_host, from_sys, CRM_SYSTEM_LRMD, fsa_our_uuid);
crm_debug("ACK'ing re-probe from %s (%s)", from_sys, from_host);
if (relay_message(reply, TRUE) == FALSE) {
crm_log_xml_err(reply, "Unable to route reply");
}
free_xml(reply);
}
} else if (operation != NULL) {
lrmd_rsc_info_t *rsc = NULL;
xmlNode *params = NULL;
xmlNode *xml_rsc = find_xml_node(input->xml, XML_CIB_TAG_RESOURCE, TRUE);
CRM_CHECK(xml_rsc != NULL, return);
params = find_xml_node(input->xml, XML_TAG_ATTRS, TRUE);
if (safe_str_eq(operation, CRMD_ACTION_DELETE)) {
create_rsc = FALSE;
}
if(lrm_state_is_connected(lrm_state) == FALSE) {
synthesize_lrmd_failure(lrm_state, input->xml, PCMK_OCF_CONNECTION_DIED);
return;
}
rsc = get_lrm_resource(lrm_state, xml_rsc, input->xml, create_rsc);
if (rsc == NULL && create_rsc) {
crm_err("Invalid resource definition for %s", ID(xml_rsc));
crm_log_xml_warn(input->msg, "bad input");
/* if the operation couldn't complete because we can't register
* the resource, return a generic error */
synthesize_lrmd_failure(lrm_state, input->xml, PCMK_OCF_NOT_CONFIGURED);
} else if (rsc == NULL) {
crm_notice("Not creating resource for a %s event: %s", operation, ID(input->xml));
delete_rsc_entry(lrm_state, input, ID(xml_rsc), NULL, pcmk_ok, user_name);
/* Deleting something that does not exist is a success */
send_task_ok_ack(lrm_state, input, ID(xml_rsc), NULL, operation,
from_host, from_sys);
} else if (safe_str_eq(operation, CRMD_ACTION_CANCEL)) {
char *op_key = NULL;
char *meta_key = NULL;
int call = 0;
const char *call_id = NULL;
const char *op_task = NULL;
const char *op_interval = NULL;
gboolean in_progress = FALSE;
CRM_CHECK(params != NULL, crm_log_xml_warn(input->xml, "Bad command");
lrmd_free_rsc_info(rsc); return);
meta_key = crm_meta_name(XML_LRM_ATTR_INTERVAL);
op_interval = crm_element_value(params, meta_key);
free(meta_key);
meta_key = crm_meta_name(XML_LRM_ATTR_TASK);
op_task = crm_element_value(params, meta_key);
free(meta_key);
meta_key = crm_meta_name(XML_LRM_ATTR_CALLID);
call_id = crm_element_value(params, meta_key);
free(meta_key);
CRM_CHECK(op_task != NULL, crm_log_xml_warn(input->xml, "Bad command");
lrmd_free_rsc_info(rsc); return);
CRM_CHECK(op_interval != NULL, crm_log_xml_warn(input->xml, "Bad command");
lrmd_free_rsc_info(rsc); return);
op_key = generate_op_key(rsc->id, op_task, crm_parse_int(op_interval, "0"));
crm_debug("PE requested op %s (call=%s) be cancelled",
op_key, call_id ? call_id : "NA");
call = crm_parse_int(call_id, "0");
if (call == 0) {
/* the normal case when the PE cancels a recurring op */
in_progress = cancel_op_key(lrm_state, rsc, op_key, TRUE);
} else {
/* the normal case when the PE cancels an orphan op */
in_progress = cancel_op(lrm_state, rsc->id, NULL, call, TRUE);
}
/* Acknowledge the cancellation operation if it's for a remote connection resource */
if (in_progress == FALSE || is_remote_lrmd_ra(NULL, NULL, rsc->id)) {
char *op_id = make_stop_id(rsc->id, call);
if (is_remote_lrmd_ra(NULL, NULL, rsc->id) == FALSE) {
crm_info("Nothing known about operation %d for %s", call, op_key);
}
delete_op_entry(lrm_state, NULL, rsc->id, op_key, call);
send_task_ok_ack(lrm_state, input, rsc->id, rsc, op_task,
from_host, from_sys);
/* needed at least for cancellation of a remote operation */
g_hash_table_remove(lrm_state->pending_ops, op_id);
free(op_id);
} else {
/* No ack is needed since abcdaa8, but peers with older versions
* in a rolling upgrade need one. We didn't bump the feature set
* at that commit, so we can only compare against the previous
* CRM version (3.0.8). If any peers have feature set 3.0.9 but
* not abcdaa8, they will time out waiting for the ack (no
* released versions of Pacemaker are affected).
*/
const char *peer_version = crm_element_value(params, XML_ATTR_CRM_VERSION);
if (compare_version(peer_version, "3.0.8") <= 0) {
crm_info("Sending compatibility ack for %s cancellation to %s (CRM version %s)",
op_key, from_host, peer_version);
send_task_ok_ack(lrm_state, input, rsc->id, rsc, op_task,
from_host, from_sys);
}
}
free(op_key);
} else if (safe_str_eq(operation, CRMD_ACTION_DELETE)) {
gboolean unregister = TRUE;
#if ENABLE_ACL
int cib_rc = delete_rsc_status(lrm_state, rsc->id, cib_dryrun | cib_sync_call, user_name);
if (cib_rc != pcmk_ok) {
lrmd_event_data_t *op = NULL;
crm_err
("Attempted deletion of resource status '%s' from CIB for %s (user=%s) on %s failed: (rc=%d) %s",
rsc->id, from_sys, user_name ? user_name : "unknown", from_host, cib_rc,
pcmk_strerror(cib_rc));
op = construct_op(lrm_state, input->xml, rsc->id, operation);
op->op_status = PCMK_LRM_OP_ERROR;
if (cib_rc == -EACCES) {
op->rc = PCMK_OCF_INSUFFICIENT_PRIV;
} else {
op->rc = PCMK_OCF_UNKNOWN_ERROR;
}
send_direct_ack(from_host, from_sys, NULL, op, rsc->id);
lrmd_free_event(op);
lrmd_free_rsc_info(rsc);
return;
}
#endif
if (crm_rsc_delete == TRUE && is_remote_lrmd_ra(NULL, NULL, rsc->id)) {
unregister = FALSE;
}
delete_resource(lrm_state, rsc->id, rsc, NULL, from_sys, from_host, user_name, input, unregister);
} else {
do_lrm_rsc_op(lrm_state, rsc, operation, input->xml, input->msg);
}
lrmd_free_rsc_info(rsc);
} else {
crm_err("Operation was neither a lrm_query, nor a rsc op. %s", crm_str(crm_op));
register_fsa_error(C_FSA_INTERNAL, I_ERROR, NULL);
}
}
static lrmd_event_data_t *
construct_op(lrm_state_t * lrm_state, xmlNode * rsc_op, const char *rsc_id, const char *operation)
{
lrmd_event_data_t *op = NULL;
const char *op_delay = NULL;
const char *op_timeout = NULL;
const char *op_interval = NULL;
GHashTable *params = NULL;
xmlNode *versioned_params = NULL;
const char *transition = NULL;
CRM_ASSERT(rsc_id != NULL);
op = calloc(1, sizeof(lrmd_event_data_t));
op->type = lrmd_event_exec_complete;
op->op_type = strdup(operation);
op->op_status = PCMK_LRM_OP_PENDING;
op->rc = -1;
op->rsc_id = strdup(rsc_id);
op->interval = 0;
op->timeout = 0;
op->start_delay = 0;
if (rsc_op == NULL) {
CRM_LOG_ASSERT(safe_str_eq(CRMD_ACTION_STOP, operation));
op->user_data = NULL;
/* the stop_all_resources() case
* by definition there is no DC (or they'd be shutting
* us down).
* So we should put our version here.
*/
op->params = g_hash_table_new_full(crm_str_hash, g_str_equal,
g_hash_destroy_str, g_hash_destroy_str);
g_hash_table_insert(op->params, strdup(XML_ATTR_CRM_VERSION), strdup(CRM_FEATURE_SET));
crm_trace("Constructed %s op for %s", operation, rsc_id);
return op;
}
params = xml2list(rsc_op);
g_hash_table_remove(params, CRM_META "_op_target_rc");
if (!is_remote_lrmd_ra(NULL, NULL, rsc_id)) {
xmlNode *ptr = first_named_child(rsc_op, XML_TAG_VER_ATTRS);
if (ptr) {
versioned_params = copy_xml(ptr);
}
}
op_delay = crm_meta_value(params, XML_OP_ATTR_START_DELAY);
op_timeout = crm_meta_value(params, XML_ATTR_TIMEOUT);
op_interval = crm_meta_value(params, XML_LRM_ATTR_INTERVAL);
op->interval = crm_parse_int(op_interval, "0");
op->timeout = crm_parse_int(op_timeout, "0");
op->start_delay = crm_parse_int(op_delay, "0");
if (safe_str_neq(operation, RSC_STOP)) {
op->params = params;
op->versioned_params = versioned_params;
} else {
rsc_history_t *entry = g_hash_table_lookup(lrm_state->resource_history, rsc_id);
/* If we do not have stop parameters cached, use
* whatever we are given */
if (!entry || !entry->stop_params) {
op->params = params;
op->versioned_params = versioned_params;
} else {
/* Copy the cached parameter list so that we stop the resource
* with the old attributes, not the new ones */
op->params = g_hash_table_new_full(crm_str_hash, g_str_equal,
g_hash_destroy_str, g_hash_destroy_str);
g_hash_table_foreach(params, copy_meta_keys, op->params);
g_hash_table_foreach(entry->stop_params, copy_instance_keys, op->params);
g_hash_table_destroy(params);
params = NULL;
op->versioned_params = NULL;
free_xml(versioned_params);
}
}
if (op->versioned_params) {
char *versioned_params_text = dump_xml_unformatted(op->versioned_params);
if (versioned_params_text) {
g_hash_table_insert(op->params, strdup("#" XML_TAG_VER_ATTRS), versioned_params_text);
}
}
/* sanity */
if (op->interval < 0) {
op->interval = 0;
}
if (op->timeout <= 0) {
op->timeout = op->interval;
}
if (op->start_delay < 0) {
op->start_delay = 0;
}
transition = crm_element_value(rsc_op, XML_ATTR_TRANSITION_KEY);
CRM_CHECK(transition != NULL, return op);
op->user_data = strdup(transition);
if (op->interval != 0) {
if (safe_str_eq(operation, CRMD_ACTION_START)
|| safe_str_eq(operation, CRMD_ACTION_STOP)) {
crm_err("Start and Stop actions cannot have an interval: %d", op->interval);
op->interval = 0;
}
}
crm_trace("Constructed %s op for %s: interval=%d", operation, rsc_id, op->interval);
return op;
}
void
send_direct_ack(const char *to_host, const char *to_sys,
lrmd_rsc_info_t * rsc, lrmd_event_data_t * op, const char *rsc_id)
{
xmlNode *reply = NULL;
xmlNode *update, *iter;
crm_node_t *peer = NULL;
CRM_CHECK(op != NULL, return);
if (op->rsc_id == NULL) {
CRM_ASSERT(rsc_id != NULL);
op->rsc_id = strdup(rsc_id);
}
if (to_sys == NULL) {
to_sys = CRM_SYSTEM_TENGINE;
}
peer = crm_get_peer(0, fsa_our_uname);
update = do_update_node_cib(peer, node_update_none, NULL, __FUNCTION__);
iter = create_xml_node(update, XML_CIB_TAG_LRM);
crm_xml_add(iter, XML_ATTR_ID, fsa_our_uuid);
iter = create_xml_node(iter, XML_LRM_TAG_RESOURCES);
iter = create_xml_node(iter, XML_LRM_TAG_RESOURCE);
crm_xml_add(iter, XML_ATTR_ID, op->rsc_id);
build_operation_update(iter, rsc, op, __FUNCTION__);
reply = create_request(CRM_OP_INVOKE_LRM, update, to_host, to_sys, CRM_SYSTEM_LRMD, NULL);
crm_log_xml_trace(update, "ACK Update");
crm_debug("ACK'ing resource op %s_%s_%d from %s: %s",
op->rsc_id, op->op_type, op->interval, op->user_data,
crm_element_value(reply, XML_ATTR_REFERENCE));
if (relay_message(reply, TRUE) == FALSE) {
crm_log_xml_err(reply, "Unable to route reply");
}
free_xml(update);
free_xml(reply);
}
gboolean
verify_stopped(enum crmd_fsa_state cur_state, int log_level)
{
gboolean res = TRUE;
GList *lrm_state_list = lrm_state_get_list();
GList *state_entry;
for (state_entry = lrm_state_list; state_entry != NULL; state_entry = state_entry->next) {
lrm_state_t *lrm_state = state_entry->data;
if (!lrm_state_verify_stopped(lrm_state, cur_state, log_level)) {
/* keep iterating through all even when false is returned */
res = FALSE;
}
}
set_bit(fsa_input_register, R_SENT_RSC_STOP);
g_list_free(lrm_state_list); lrm_state_list = NULL;
return res;
}
struct stop_recurring_action_s {
lrmd_rsc_info_t *rsc;
lrm_state_t *lrm_state;
};
static gboolean
stop_recurring_action_by_rsc(gpointer key, gpointer value, gpointer user_data)
{
gboolean remove = FALSE;
struct stop_recurring_action_s *event = user_data;
struct recurring_op_s *op = (struct recurring_op_s *)value;
if (op->interval != 0 && crm_str_eq(op->rsc_id, event->rsc->id, TRUE)) {
crm_debug("Cancelling op %d for %s (%s)", op->call_id, op->rsc_id, (char*)key);
remove = !cancel_op(event->lrm_state, event->rsc->id, key, op->call_id, FALSE);
}
return remove;
}
static gboolean
stop_recurring_actions(gpointer key, gpointer value, gpointer user_data)
{
gboolean remove = FALSE;
lrm_state_t *lrm_state = user_data;
struct recurring_op_s *op = (struct recurring_op_s *)value;
if (op->interval != 0) {
crm_info("Cancelling op %d for %s (%s)", op->call_id, op->rsc_id, key);
remove = !cancel_op(lrm_state, op->rsc_id, key, op->call_id, FALSE);
}
return remove;
}
static void
do_lrm_rsc_op(lrm_state_t * lrm_state, lrmd_rsc_info_t * rsc, const char *operation, xmlNode * msg,
xmlNode * request)
{
int call_id = 0;
char *op_id = NULL;
lrmd_event_data_t *op = NULL;
lrmd_key_value_t *params = NULL;
fsa_data_t *msg_data = NULL;
const char *transition = NULL;
gboolean stop_recurring = FALSE;
CRM_CHECK(rsc != NULL, return);
CRM_CHECK(operation != NULL, return);
if (msg != NULL) {
transition = crm_element_value(msg, XML_ATTR_TRANSITION_KEY);
if (transition == NULL) {
crm_log_xml_err(msg, "Missing transition number");
}
}
op = construct_op(lrm_state, msg, rsc->id, operation);
CRM_CHECK(op != NULL, return);
if (is_remote_lrmd_ra(NULL, NULL, rsc->id)
&& op->interval == 0
&& strcmp(operation, CRMD_ACTION_MIGRATE) == 0) {
/* pcmk remote connections are a special use case.
* We never ever want to stop monitoring a connection resource until
* the entire migration has completed. If the connection is ever unexpected
* severed, even during a migration, this is an event we must detect.*/
stop_recurring = FALSE;
} else if (op->interval == 0
&& strcmp(operation, CRMD_ACTION_STATUS) != 0
&& strcmp(operation, CRMD_ACTION_NOTIFY) != 0) {
/* stop any previous monitor operations before changing the resource state */
stop_recurring = TRUE;
}
if (stop_recurring == TRUE) {
guint removed = 0;
struct stop_recurring_action_s data;
data.rsc = rsc;
data.lrm_state = lrm_state;
removed = g_hash_table_foreach_remove(
lrm_state->pending_ops, stop_recurring_action_by_rsc, &data);
if (removed) {
crm_debug("Stopped %u recurring operations in preparation for %s_%s_%d",
removed, rsc->id, operation, op->interval);
}
}
/* now do the op */
crm_info("Performing key=%s op=%s_%s_%d", transition, rsc->id, operation, op->interval);
if (fsa_state != S_NOT_DC && fsa_state != S_POLICY_ENGINE && fsa_state != S_TRANSITION_ENGINE) {
if (safe_str_neq(operation, "fail")
&& safe_str_neq(operation, CRMD_ACTION_STOP)) {
crm_info("Discarding attempt to perform action %s on %s in state %s",
operation, rsc->id, fsa_state2string(fsa_state));
op->rc = CRM_DIRECT_NACK_RC;
op->op_status = PCMK_LRM_OP_ERROR;
send_direct_ack(NULL, NULL, rsc, op, rsc->id);
lrmd_free_event(op);
free(op_id);
return;
}
}
op_id = generate_op_key(rsc->id, op->op_type, op->interval);
if (op->interval > 0) {
/* cancel it so we can then restart it without conflict */
cancel_op_key(lrm_state, rsc, op_id, FALSE);
}
if (op->params) {
char *key = NULL;
char *value = NULL;
GHashTableIter iter;
g_hash_table_iter_init(&iter, op->params);
while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value)) {
params = lrmd_key_value_add(params, key, value);
}
}
call_id = lrm_state_exec(lrm_state,
rsc->id,
op->op_type,
op->user_data, op->interval, op->timeout, op->start_delay, params);
if (call_id <= 0 && lrm_state_is_local(lrm_state)) {
crm_err("Operation %s on %s failed: %d", operation, rsc->id, call_id);
register_fsa_error(C_FSA_INTERNAL, I_FAIL, NULL);
} else if (call_id <= 0) {
crm_err("Operation %s on resource %s failed to execute on remote node %s: %d", operation, rsc->id, lrm_state->node_name, call_id);
op->call_id = get_fake_call_id(lrm_state, rsc->id);
op->op_status = PCMK_LRM_OP_DONE;
op->rc = PCMK_OCF_UNKNOWN_ERROR;
op->t_run = time(NULL);
op->t_rcchange = op->t_run;
process_lrm_event(lrm_state, op, NULL);
} else {
/* record all operations so we can wait
* for them to complete during shutdown
*/
char *call_id_s = make_stop_id(rsc->id, call_id);
struct recurring_op_s *pending = NULL;
pending = calloc(1, sizeof(struct recurring_op_s));
crm_trace("Recording pending op: %d - %s %s", call_id, op_id, call_id_s);
pending->call_id = call_id;
pending->interval = op->interval;
pending->op_type = strdup(operation);
pending->op_key = strdup(op_id);
pending->rsc_id = strdup(rsc->id);
pending->start_time = time(NULL);
pending->user_data = strdup(op->user_data);
g_hash_table_replace(lrm_state->pending_ops, call_id_s, pending);
if (op->interval > 0 && op->start_delay > START_DELAY_THRESHOLD) {
char *uuid = NULL;
int dummy = 0, target_rc = 0;
crm_info("Faking confirmation of %s: execution postponed for over 5 minutes", op_id);
decode_transition_key(op->user_data, &uuid, &dummy, &dummy, &target_rc);
free(uuid);
op->rc = target_rc;
op->op_status = PCMK_LRM_OP_DONE;
send_direct_ack(NULL, NULL, rsc, op, rsc->id);
}
pending->params = op->params;
op->params = NULL;
}
free(op_id);
lrmd_free_event(op);
return;
}
int last_resource_update = 0;
static void
cib_rsc_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
{
switch (rc) {
case pcmk_ok:
case -pcmk_err_diff_failed:
case -pcmk_err_diff_resync:
crm_trace("Resource update %d complete: rc=%d", call_id, rc);
break;
default:
crm_warn("Resource update %d failed: (rc=%d) %s", call_id, rc, pcmk_strerror(rc));
}
if (call_id == last_resource_update) {
last_resource_update = 0;
trigger_fsa(fsa_source);
}
}
static int
do_update_resource(const char *node_name, lrmd_rsc_info_t * rsc, lrmd_event_data_t * op)
{
/*
<status>
<nodes_status id=uname>
<lrm>
<lrm_resources>
<lrm_resource id=...>
</...>
*/
int rc = pcmk_ok;
xmlNode *update, *iter = NULL;
int call_opt = crmd_cib_smart_opt();
const char *uuid = NULL;
CRM_CHECK(op != NULL, return 0);
iter = create_xml_node(iter, XML_CIB_TAG_STATUS);
update = iter;
iter = create_xml_node(iter, XML_CIB_TAG_STATE);
if (safe_str_eq(node_name, fsa_our_uname)) {
uuid = fsa_our_uuid;
} else {
/* remote nodes uuid and uname are equal */
uuid = node_name;
crm_xml_add(iter, XML_NODE_IS_REMOTE, "true");
}
CRM_LOG_ASSERT(uuid != NULL);
if(uuid == NULL) {
rc = -EINVAL;
goto done;
}
crm_xml_add(iter, XML_ATTR_UUID, uuid);
crm_xml_add(iter, XML_ATTR_UNAME, node_name);
crm_xml_add(iter, XML_ATTR_ORIGIN, __FUNCTION__);
iter = create_xml_node(iter, XML_CIB_TAG_LRM);
crm_xml_add(iter, XML_ATTR_ID, uuid);
iter = create_xml_node(iter, XML_LRM_TAG_RESOURCES);
iter = create_xml_node(iter, XML_LRM_TAG_RESOURCE);
crm_xml_add(iter, XML_ATTR_ID, op->rsc_id);
build_operation_update(iter, rsc, op, __FUNCTION__);
if (rsc) {
const char *container = NULL;
crm_xml_add(iter, XML_ATTR_TYPE, rsc->type);
crm_xml_add(iter, XML_AGENT_ATTR_CLASS, rsc->class);
crm_xml_add(iter, XML_AGENT_ATTR_PROVIDER, rsc->provider);
if (op->params) {
container = g_hash_table_lookup(op->params, CRM_META"_"XML_RSC_ATTR_CONTAINER);
}
if (container) {
crm_trace("Resource %s is a part of container resource %s", op->rsc_id, container);
crm_xml_add(iter, XML_RSC_ATTR_CONTAINER, container);
}
} else {
crm_warn("Resource %s no longer exists in the lrmd", op->rsc_id);
send_direct_ack(NULL, NULL, rsc, op, op->rsc_id);
goto cleanup;
}
crm_log_xml_trace(update, __FUNCTION__);
/* make it an asynchronous call and be done with it
*
* Best case:
* the resource state will be discovered during
* the next signup or election.
*
* Bad case:
* we are shutting down and there is no DC at the time,
* but then why were we shutting down then anyway?
* (probably because of an internal error)
*
* Worst case:
* we get shot for having resources "running" when the really weren't
*
* the alternative however means blocking here for too long, which
* isn't acceptable
*/
fsa_cib_update(XML_CIB_TAG_STATUS, update, call_opt, rc, NULL);
if (rc > 0) {
last_resource_update = rc;
}
done:
/* the return code is a call number, not an error code */
crm_trace("Sent resource state update message: %d for %s=%d on %s", rc,
op->op_type, op->interval, op->rsc_id);
fsa_register_cib_callback(rc, FALSE, NULL, cib_rsc_callback);
cleanup:
free_xml(update);
return rc;
}
void
do_lrm_event(long long action,
enum crmd_fsa_cause cause,
enum crmd_fsa_state cur_state, enum crmd_fsa_input cur_input, fsa_data_t * msg_data)
{
CRM_CHECK(FALSE, return);
}
gboolean
process_lrm_event(lrm_state_t * lrm_state, lrmd_event_data_t * op, struct recurring_op_s *pending)
{
char *op_id = NULL;
char *op_key = NULL;
int update_id = 0;
gboolean remove = FALSE;
gboolean removed = FALSE;
lrmd_rsc_info_t *rsc = NULL;
CRM_CHECK(op != NULL, return FALSE);
CRM_CHECK(op->rsc_id != NULL, return FALSE);
op_id = make_stop_id(op->rsc_id, op->call_id);
op_key = generate_op_key(op->rsc_id, op->op_type, op->interval);
rsc = lrm_state_get_rsc_info(lrm_state, op->rsc_id, 0);
if(pending == NULL) {
remove = TRUE;
pending = g_hash_table_lookup(lrm_state->pending_ops, op_id);
}
if (op->op_status == PCMK_LRM_OP_ERROR) {
switch(op->rc) {
case PCMK_OCF_NOT_RUNNING:
case PCMK_OCF_RUNNING_MASTER:
case PCMK_OCF_DEGRADED:
case PCMK_OCF_DEGRADED_MASTER:
/* Leave it up to the TE/PE to decide if this is an error */
op->op_status = PCMK_LRM_OP_DONE;
break;
default:
/* Nothing to do */
break;
}
}
if (op->op_status != PCMK_LRM_OP_CANCELLED) {
if (safe_str_eq(op->op_type, RSC_NOTIFY)) {
/* Keep notify ops out of the CIB */
send_direct_ack(NULL, NULL, NULL, op, op->rsc_id);
} else {
update_id = do_update_resource(lrm_state->node_name, rsc, op);
}
} else if (op->interval == 0) {
/* This will occur when "crm resource cleanup" is called while actions are in-flight */
crm_err("Op %s (call=%d): Cancelled", op_key, op->call_id);
send_direct_ack(NULL, NULL, NULL, op, op->rsc_id);
} else if (pending == NULL) {
/* We don't need to do anything for cancelled ops
* that are not in our pending op list. There are no
* transition actions waiting on these operations. */
} else if (op->user_data == NULL) {
/* At this point we have a pending entry, but no transition
* key present in the user_data field. report this */
crm_err("Op %s (call=%d): No user data", op_key, op->call_id);
} else if (pending->remove) {
/* The tengine canceled this op, we have been waiting for the cancel to finish. */
delete_op_entry(lrm_state, op, op->rsc_id, op_key, op->call_id);
} else if (pending && op->rsc_deleted) {
/* The tengine initiated this op, but it was cancelled outside of the
* tengine's control during a resource cleanup/re-probe request. The tengine
* must be alerted that this operation completed, otherwise the tengine
* will continue waiting for this update to occur until it is timed out.
* We don't want this update going to the cib though, so use a direct ack. */
crm_trace("Op %s (call=%d): cancelled due to rsc deletion", op_key, op->call_id);
send_direct_ack(NULL, NULL, NULL, op, op->rsc_id);
} else {
/* Before a stop is called, no need to direct ack */
crm_trace("Op %s (call=%d): no delete event required", op_key, op->call_id);
}
if(remove == FALSE) {
/* The caller will do this afterwards, but keep the logging consistent */
removed = TRUE;
} else if ((op->interval == 0) && g_hash_table_remove(lrm_state->pending_ops, op_id)) {
removed = TRUE;
crm_trace("Op %s (call=%d, stop-id=%s, remaining=%u): Confirmed",
op_key, op->call_id, op_id, g_hash_table_size(lrm_state->pending_ops));
} else if(op->interval != 0 && op->op_status == PCMK_LRM_OP_CANCELLED) {
removed = TRUE;
g_hash_table_remove(lrm_state->pending_ops, op_id);
}
switch (op->op_status) {
case PCMK_LRM_OP_CANCELLED:
crm_info("Result of %s operation for %s on %s: %s "
CRM_XS " call=%d key=%s confirmed=%s",
crm_action_str(op->op_type, op->interval),
op->rsc_id, lrm_state->node_name,
services_lrm_status_str(op->op_status),
op->call_id, op_key, (removed? "true" : "false"));
break;
case PCMK_LRM_OP_DONE:
do_crm_log(op->interval?LOG_INFO:LOG_NOTICE,
"Result of %s operation for %s on %s: %d (%s) "
CRM_XS " call=%d key=%s confirmed=%s cib-update=%d",
crm_action_str(op->op_type, op->interval),
op->rsc_id, lrm_state->node_name,
op->rc, services_ocf_exitcode_str(op->rc),
op->call_id, op_key, (removed? "true" : "false"),
update_id);
break;
case PCMK_LRM_OP_TIMEOUT:
crm_err("Result of %s operation for %s on %s: %s "
CRM_XS " call=%d key=%s timeout=%dms",
crm_action_str(op->op_type, op->interval),
op->rsc_id, lrm_state->node_name,
services_lrm_status_str(op->op_status),
op->call_id, op_key, op->timeout);
break;
default:
crm_err("Result of %s operation for %s on %s: %s "
CRM_XS " call=%d key=%s confirmed=%s status=%d cib-update=%d",
crm_action_str(op->op_type, op->interval),
op->rsc_id, lrm_state->node_name,
services_lrm_status_str(op->op_status), op->call_id, op_key,
(removed? "true" : "false"), op->op_status, update_id);
}
if (op->output) {
char *prefix =
crm_strdup_printf("%s-%s_%s_%d:%d", lrm_state->node_name, op->rsc_id, op->op_type, op->interval, op->call_id);
if (op->rc) {
crm_log_output(LOG_NOTICE, prefix, op->output);
} else {
crm_log_output(LOG_DEBUG, prefix, op->output);
}
free(prefix);
}
crmd_notify_resource_op(lrm_state->node_name, op);
if (op->rsc_deleted) {
crm_info("Deletion of resource '%s' complete after %s", op->rsc_id, op_key);
delete_rsc_entry(lrm_state, NULL, op->rsc_id, NULL, pcmk_ok, NULL);
}
/* If a shutdown was escalated while operations were pending,
* then the FSA will be stalled right now... allow it to continue
*/
mainloop_set_trigger(fsa_source);
update_history_cache(lrm_state, rsc, op);
lrmd_free_rsc_info(rsc);
free(op_key);
free(op_id);
return TRUE;
}
diff --git a/crmd/throttle.c b/crmd/throttle.c
index 9a49fbdf52..ae371c6041 100644
--- a/crmd/throttle.c
+++ b/crmd/throttle.c
@@ -1,676 +1,676 @@
/*
* Copyright (C) 2013 Andrew Beekhof <andrew@beekhof.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <crm_internal.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <ctype.h>
#include <dirent.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/cluster.h>
#include <crmd_fsa.h>
#include <throttle.h>
enum throttle_state_e
{
throttle_extreme = 0x1000,
throttle_high = 0x0100,
throttle_med = 0x0010,
throttle_low = 0x0001,
throttle_none = 0x0000,
};
struct throttle_record_s
{
int max;
enum throttle_state_e mode;
char *node;
};
int throttle_job_max = 0;
float throttle_load_target = 0.0;
#define THROTTLE_FACTOR_LOW 1.2
#define THROTTLE_FACTOR_MEDIUM 1.6
#define THROTTLE_FACTOR_HIGH 2.0
GHashTable *throttle_records = NULL;
mainloop_timer_t *throttle_timer = NULL;
int throttle_num_cores(void)
{
static int cores = 0;
char buffer[256];
FILE *stream = NULL;
const char *cpufile = "/proc/cpuinfo";
if(cores) {
return cores;
}
stream = fopen(cpufile, "r");
if(stream == NULL) {
int rc = errno;
crm_warn("Couldn't read %s, assuming a single processor: %s (%d)", cpufile, pcmk_strerror(rc), rc);
return 1;
}
while (fgets(buffer, sizeof(buffer), stream)) {
if(strstr(buffer, "processor") == buffer) {
cores++;
}
}
fclose(stream);
if(cores == 0) {
crm_warn("No processors found in %s, assuming 1", cpufile);
return 1;
}
return cores;
}
-/*
+/*!
* \internal
* \brief Return name of /proc file containing the CIB deamon's load statistics
*
* \return Newly allocated memory with file name on success, NULL otherwise
*
* \note It is the caller's responsibility to free the return value.
* This will return NULL if the daemon is being run via valgrind.
* This should be called only on Linux systems.
*/
static char *find_cib_loadfile(void)
{
int pid = crm_procfs_pid_of("cib");
return pid? crm_strdup_printf("/proc/%d/stat", pid) : NULL;
}
static bool throttle_cib_load(float *load)
{
/*
/proc/[pid]/stat
Status information about the process. This is used by ps(1). It is defined in /usr/src/linux/fs/proc/array.c.
The fields, in order, with their proper scanf(3) format specifiers, are:
pid %d (1) The process ID.
comm %s (2) The filename of the executable, in parentheses. This is visible whether or not the executable is swapped out.
state %c (3) One character from the string "RSDZTW" where R is running, S is sleeping in an interruptible wait, D is waiting in uninterruptible disk sleep, Z is zombie, T is traced or stopped (on a signal), and W is paging.
ppid %d (4) The PID of the parent.
pgrp %d (5) The process group ID of the process.
session %d (6) The session ID of the process.
tty_nr %d (7) The controlling terminal of the process. (The minor device number is contained in the combination of bits 31 to 20 and 7 to 0; the major device number is in bits 15 to 8.)
tpgid %d (8) The ID of the foreground process group of the controlling terminal of the process.
flags %u (%lu before Linux 2.6.22)
(9) The kernel flags word of the process. For bit meanings, see the PF_* defines in the Linux kernel source file include/linux/sched.h. Details depend on the kernel version.
minflt %lu (10) The number of minor faults the process has made which have not required loading a memory page from disk.
cminflt %lu (11) The number of minor faults that the process's waited-for children have made.
majflt %lu (12) The number of major faults the process has made which have required loading a memory page from disk.
cmajflt %lu (13) The number of major faults that the process's waited-for children have made.
utime %lu (14) Amount of time that this process has been scheduled in user mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)). This includes guest time, guest_time (time spent running a virtual CPU, see below), so that applications that are not aware of the guest time field do not lose that time from their calculations.
stime %lu (15) Amount of time that this process has been scheduled in kernel mode, measured in clock ticks (divide by sysconf(_SC_CLK_TCK)).
*/
static char *loadfile = NULL;
static time_t last_call = 0;
static long ticks_per_s = 0;
static unsigned long last_utime, last_stime;
char buffer[64*1024];
FILE *stream = NULL;
time_t now = time(NULL);
if(load == NULL) {
return FALSE;
} else {
*load = 0.0;
}
if(loadfile == NULL) {
last_call = 0;
last_utime = 0;
last_stime = 0;
loadfile = find_cib_loadfile();
if (loadfile == NULL) {
crm_warn("Couldn't find CIB load file");
return FALSE;
}
ticks_per_s = sysconf(_SC_CLK_TCK);
crm_trace("Found %s", loadfile);
}
stream = fopen(loadfile, "r");
if(stream == NULL) {
int rc = errno;
crm_warn("Couldn't read %s: %s (%d)", loadfile, pcmk_strerror(rc), rc);
free(loadfile); loadfile = NULL;
return FALSE;
}
if(fgets(buffer, sizeof(buffer), stream)) {
char *comm = calloc(1, 256);
char state = 0;
int rc = 0, pid = 0, ppid = 0, pgrp = 0, session = 0, tty_nr = 0, tpgid = 0;
unsigned long flags = 0, minflt = 0, cminflt = 0, majflt = 0, cmajflt = 0, utime = 0, stime = 0;
rc = sscanf(buffer, "%d %[^ ] %c %d %d %d %d %d %lu %lu %lu %lu %lu %lu %lu",
&pid, comm, &state,
&ppid, &pgrp, &session, &tty_nr, &tpgid,
&flags, &minflt, &cminflt, &majflt, &cmajflt, &utime, &stime);
free(comm);
if(rc != 15) {
crm_err("Only %d of 15 fields found in %s", rc, loadfile);
fclose(stream);
return FALSE;
} else if(last_call > 0
&& last_call < now
&& last_utime <= utime
&& last_stime <= stime) {
time_t elapsed = now - last_call;
unsigned long delta_utime = utime - last_utime;
unsigned long delta_stime = stime - last_stime;
*load = (delta_utime + delta_stime); /* Cast to a float before division */
*load /= ticks_per_s;
*load /= elapsed;
crm_debug("cib load: %f (%lu ticks in %lds)", *load, delta_utime + delta_stime, (long)elapsed);
} else {
crm_debug("Init %lu + %lu ticks at %ld (%lu tps)", utime, stime, (long)now, ticks_per_s);
}
last_call = now;
last_utime = utime;
last_stime = stime;
fclose(stream);
return TRUE;
}
fclose(stream);
return FALSE;
}
static bool throttle_load_avg(float *load)
{
char buffer[256];
FILE *stream = NULL;
const char *loadfile = "/proc/loadavg";
if(load == NULL) {
return FALSE;
}
stream = fopen(loadfile, "r");
if(stream == NULL) {
int rc = errno;
crm_warn("Couldn't read %s: %s (%d)", loadfile, pcmk_strerror(rc), rc);
return FALSE;
}
if(fgets(buffer, sizeof(buffer), stream)) {
char *nl = strstr(buffer, "\n");
/* Grab the 1-minute average, ignore the rest */
*load = strtof(buffer, NULL);
if(nl) { nl[0] = 0; }
crm_debug("Current load is %f (full: %s)", *load, buffer);
fclose(stream);
return TRUE;
}
fclose(stream);
return FALSE;
}
static bool throttle_io_load(float *load, unsigned int *blocked)
{
char buffer[64*1024];
FILE *stream = NULL;
const char *loadfile = "/proc/stat";
if(load == NULL) {
return FALSE;
}
stream = fopen(loadfile, "r");
if(stream == NULL) {
int rc = errno;
crm_warn("Couldn't read %s: %s (%d)", loadfile, pcmk_strerror(rc), rc);
return FALSE;
}
if(fgets(buffer, sizeof(buffer), stream)) {
/* Borrowed from procps-ng's sysinfo.c */
char *b = NULL;
unsigned long long cpu_use = 0;
unsigned long long cpu_nic = 0;
unsigned long long cpu_sys = 0;
unsigned long long cpu_idl = 0;
unsigned long long cpu_iow = 0; /* not separated out until the 2.5.41 kernel */
unsigned long long cpu_xxx = 0; /* not separated out until the 2.6.0-test4 kernel */
unsigned long long cpu_yyy = 0; /* not separated out until the 2.6.0-test4 kernel */
unsigned long long cpu_zzz = 0; /* not separated out until the 2.6.11 kernel */
long long divo2 = 0;
long long duse = 0;
long long dsys = 0;
long long didl =0;
long long diow =0;
long long dstl = 0;
long long Div = 0;
b = strstr(buffer, "cpu ");
if(b) sscanf(b, "cpu %Lu %Lu %Lu %Lu %Lu %Lu %Lu %Lu",
&cpu_use, &cpu_nic, &cpu_sys, &cpu_idl, &cpu_iow, &cpu_xxx, &cpu_yyy, &cpu_zzz);
if(blocked) {
b = strstr(buffer, "procs_blocked ");
if(b) sscanf(b, "procs_blocked %u", blocked);
}
duse = cpu_use + cpu_nic;
dsys = cpu_sys + cpu_xxx + cpu_yyy;
didl = cpu_idl;
diow = cpu_iow;
dstl = cpu_zzz;
Div = duse + dsys + didl + diow + dstl;
if (!Div) Div = 1, didl = 1;
divo2 = Div / 2UL;
/* vmstat output:
*
* procs -----------memory---------- ---swap-- -----io---- -system-- ----cpu----
* r b swpd free buff cache si so bi bo in cs us sy id wa
* 1 0 5537800 958592 204180 1737740 1 1 12 15 0 0 2 1 97 0
*
* The last four columns are calculated as:
*
* (unsigned)( (100*duse + divo2) / Div ),
* (unsigned)( (100*dsys + divo2) / Div ),
* (unsigned)( (100*didl + divo2) / Div ),
* (unsigned)( (100*diow + divo2) / Div )
*
*/
*load = (diow + divo2) / Div;
crm_debug("Current IO load is %f", *load);
fclose(stream);
return TRUE;
}
fclose(stream);
return FALSE;
}
static enum throttle_state_e
throttle_handle_load(float load, const char *desc, int cores)
{
float adjusted_load = load;
if(cores <= 0) {
/* No fudging of the supplied load value */
} else if(cores == 1) {
/* On a single core machine, a load of 1.0 is already too high */
adjusted_load = load * THROTTLE_FACTOR_MEDIUM;
} else {
/* Normalize the load to be per-core */
adjusted_load = load / cores;
}
if(adjusted_load > THROTTLE_FACTOR_HIGH * throttle_load_target) {
crm_notice("High %s detected: %f", desc, load);
return throttle_high;
} else if(adjusted_load > THROTTLE_FACTOR_MEDIUM * throttle_load_target) {
crm_info("Moderate %s detected: %f", desc, load);
return throttle_med;
} else if(adjusted_load > THROTTLE_FACTOR_LOW * throttle_load_target) {
crm_debug("Noticeable %s detected: %f", desc, load);
return throttle_low;
}
crm_trace("Negligable %s detected: %f", desc, adjusted_load);
return throttle_none;
}
static enum throttle_state_e
throttle_mode(void)
{
int cores;
float load;
unsigned int blocked = 0;
enum throttle_state_e mode = throttle_none;
#if defined(ON_BSD) || defined(ON_SOLARIS)
return throttle_none;
#endif
cores = throttle_num_cores();
if(throttle_cib_load(&load)) {
float cib_max_cpu = 0.95;
const char *desc = "CIB load";
/* The CIB is a single threaded task and thus cannot consume
* more than 100% of a CPU (and 1/cores of the overall system
* load).
*
* On a many cored system, the CIB might therefor be maxed out
* (causing operations to fail or appear to fail) even though
* the overall system load is still reasonable.
*
* Therefor the 'normal' thresholds can not apply here and we
* need a special case.
*/
if(cores == 1) {
cib_max_cpu = 0.4;
}
if(throttle_load_target > 0.0 && throttle_load_target < cib_max_cpu) {
cib_max_cpu = throttle_load_target;
}
if(load > 1.5 * cib_max_cpu) {
/* Can only happen on machines with a low number of cores */
crm_notice("Extreme %s detected: %f", desc, load);
mode |= throttle_extreme;
} else if(load > cib_max_cpu) {
crm_notice("High %s detected: %f", desc, load);
mode |= throttle_high;
} else if(load > cib_max_cpu * 0.9) {
crm_info("Moderate %s detected: %f", desc, load);
mode |= throttle_med;
} else if(load > cib_max_cpu * 0.8) {
crm_debug("Noticeable %s detected: %f", desc, load);
mode |= throttle_low;
} else {
crm_trace("Negligable %s detected: %f", desc, load);
}
}
if(throttle_load_target <= 0) {
/* If we ever make this a valid value, the cluster will at least behave as expected */
return mode;
}
if(throttle_load_avg(&load)) {
mode |= throttle_handle_load(load, "CPU load", cores);
}
if(throttle_io_load(&load, &blocked)) {
mode |= throttle_handle_load(load, "IO load", 0);
mode |= throttle_handle_load(blocked, "blocked IO ratio", cores);
}
if(mode & throttle_extreme) {
return throttle_extreme;
} else if(mode & throttle_high) {
return throttle_high;
} else if(mode & throttle_med) {
return throttle_med;
} else if(mode & throttle_low) {
return throttle_low;
}
return throttle_none;
}
static void
throttle_send_command(enum throttle_state_e mode)
{
xmlNode *xml = NULL;
static enum throttle_state_e last = -1;
if(mode != last) {
crm_info("New throttle mode: %.4x (was %.4x)", mode, last);
last = mode;
xml = create_request(CRM_OP_THROTTLE, NULL, NULL, CRM_SYSTEM_CRMD, CRM_SYSTEM_CRMD, NULL);
crm_xml_add_int(xml, F_CRM_THROTTLE_MODE, mode);
crm_xml_add_int(xml, F_CRM_THROTTLE_MAX, throttle_job_max);
send_cluster_message(NULL, crm_msg_crmd, xml, TRUE);
free_xml(xml);
}
}
static gboolean
throttle_timer_cb(gpointer data)
{
static bool send_updates = FALSE;
enum throttle_state_e now = throttle_none;
if(send_updates) {
now = throttle_mode();
throttle_send_command(now);
} else if(compare_version(fsa_our_dc_version, "3.0.8") < 0) {
/* Optimize for the true case */
crm_trace("DC version %s doesn't support throttling", fsa_our_dc_version);
} else {
send_updates = TRUE;
now = throttle_mode();
throttle_send_command(now);
}
return TRUE;
}
static void
throttle_record_free(gpointer p)
{
struct throttle_record_s *r = p;
free(r->node);
free(r);
}
void
throttle_update_job_max(const char *preference)
{
int max = 0;
throttle_job_max = 2 * throttle_num_cores();
if(preference) {
/* Global preference from the CIB */
max = crm_int_helper(preference, NULL);
if(max > 0) {
throttle_job_max = max;
}
}
preference = getenv("LRMD_MAX_CHILDREN");
if(preference) {
/* Legacy env variable */
max = crm_int_helper(preference, NULL);
if(max > 0) {
throttle_job_max = max;
}
}
preference = getenv("PCMK_node_action_limit");
if(preference) {
/* Per-node override */
max = crm_int_helper(preference, NULL);
if(max > 0) {
throttle_job_max = max;
}
}
}
void
throttle_init(void)
{
if(throttle_records == NULL) {
throttle_records = g_hash_table_new_full(
crm_str_hash, g_str_equal, NULL, throttle_record_free);
throttle_timer = mainloop_timer_add("throttle", 30 * 1000, TRUE, throttle_timer_cb, NULL);
}
throttle_update_job_max(NULL);
mainloop_timer_start(throttle_timer);
}
void
throttle_fini(void)
{
mainloop_timer_del(throttle_timer); throttle_timer = NULL;
g_hash_table_destroy(throttle_records); throttle_records = NULL;
}
int
throttle_get_total_job_limit(int l)
{
/* Cluster-wide limit */
GHashTableIter iter;
int limit = l;
int peers = crm_active_peers();
struct throttle_record_s *r = NULL;
g_hash_table_iter_init(&iter, throttle_records);
while (g_hash_table_iter_next(&iter, NULL, (gpointer *) &r)) {
switch(r->mode) {
case throttle_extreme:
if(limit == 0 || limit > peers/4) {
limit = QB_MAX(1, peers/4);
}
break;
case throttle_high:
if(limit == 0 || limit > peers/2) {
limit = QB_MAX(1, peers/2);
}
break;
default:
break;
}
}
if(limit == l) {
/* crm_trace("No change to batch-limit=%d", limit); */
} else if(l == 0) {
crm_trace("Using batch-limit=%d", limit);
} else {
crm_trace("Using batch-limit=%d instead of %d", limit, l);
}
return limit;
}
int
throttle_get_job_limit(const char *node)
{
int jobs = 1;
struct throttle_record_s *r = NULL;
r = g_hash_table_lookup(throttle_records, node);
if(r == NULL) {
r = calloc(1, sizeof(struct throttle_record_s));
r->node = strdup(node);
r->mode = throttle_low;
r->max = throttle_job_max;
crm_trace("Defaulting to local values for unknown node %s", node);
g_hash_table_insert(throttle_records, r->node, r);
}
switch(r->mode) {
case throttle_extreme:
case throttle_high:
jobs = 1; /* At least one job must always be allowed */
break;
case throttle_med:
jobs = QB_MAX(1, r->max / 4);
break;
case throttle_low:
jobs = QB_MAX(1, r->max / 2);
break;
case throttle_none:
jobs = QB_MAX(1, r->max);
break;
default:
crm_err("Unknown throttle mode %.4x on %s", r->mode, node);
break;
}
return jobs;
}
void
throttle_update(xmlNode *xml)
{
int max = 0;
enum throttle_state_e mode = 0;
struct throttle_record_s *r = NULL;
const char *from = crm_element_value(xml, F_CRM_HOST_FROM);
crm_element_value_int(xml, F_CRM_THROTTLE_MODE, (int*)&mode);
crm_element_value_int(xml, F_CRM_THROTTLE_MAX, &max);
r = g_hash_table_lookup(throttle_records, from);
if(r == NULL) {
r = calloc(1, sizeof(struct throttle_record_s));
r->node = strdup(from);
g_hash_table_insert(throttle_records, r->node, r);
}
r->max = max;
r->mode = mode;
crm_debug("Host %s supports a maximum of %d jobs and throttle mode %.4x. New job limit is %d",
from, max, mode, throttle_get_job_limit(from));
}
diff --git a/fencing/commands.c b/fencing/commands.c
index bc6f9c00ed..00bf3208d0 100644
--- a/fencing/commands.c
+++ b/fencing/commands.c
@@ -1,2599 +1,2599 @@
/*
* Copyright (C) 2009 Andrew Beekhof <andrew@beekhof.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/ipc.h>
#include <crm/common/ipcs.h>
#include <crm/cluster/internal.h>
#include <crm/common/mainloop.h>
#include <crm/stonith-ng.h>
#include <crm/fencing/internal.h>
#include <crm/common/xml.h>
#if SUPPORT_CIBSECRETS
# include <crm/common/cib_secrets.h>
#endif
#include <internal.h>
GHashTable *device_list = NULL;
GHashTable *topology = NULL;
GList *cmd_list = NULL;
struct device_search_s {
/* target of fence action */
char *host;
/* requested fence action */
char *action;
/* timeout to use if a device is queried dynamically for possible targets */
int per_device_timeout;
/* number of registered fencing devices at time of request */
int replies_needed;
/* number of device replies received so far */
int replies_received;
/* whether the target is eligible to perform requested action (or off) */
bool allow_suicide;
/* private data to pass to search callback function */
void *user_data;
/* function to call when all replies have been received */
void (*callback) (GList * devices, void *user_data);
/* devices capable of performing requested action (or off if remapping) */
GListPtr capable;
};
static gboolean stonith_device_dispatch(gpointer user_data);
static void st_child_done(GPid pid, int rc, const char *output, gpointer user_data);
static void stonith_send_reply(xmlNode * reply, int call_options, const char *remote_peer,
const char *client_id);
static void search_devices_record_result(struct device_search_s *search, const char *device,
gboolean can_fence);
typedef struct async_command_s {
int id;
int pid;
int fd_stdout;
int options;
int default_timeout; /* seconds */
int timeout; /* seconds */
int start_delay; /* milliseconds */
int delay_id;
char *op;
char *origin;
char *client;
char *client_name;
char *remote_op_id;
char *victim;
uint32_t victim_nodeid;
char *action;
char *device;
char *mode;
GListPtr device_list;
GListPtr device_next;
void *internal_user_data;
void (*done_cb) (GPid pid, int rc, const char *output, gpointer user_data);
guint timer_sigterm;
guint timer_sigkill;
/*! If the operation timed out, this is the last signal
* we sent to the process to get it to terminate */
int last_timeout_signo;
stonith_device_t *active_on;
} async_command_t;
static xmlNode *stonith_construct_async_reply(async_command_t * cmd, const char *output,
xmlNode * data, int rc);
static gboolean
is_action_required(const char *action, stonith_device_t *device)
{
return device && device->automatic_unfencing && safe_str_eq(action, "on");
}
static int
get_action_delay_max(stonith_device_t * device, const char * action)
{
const char *value = NULL;
int delay_max_ms = 0;
if (safe_str_neq(action, "off") && safe_str_neq(action, "reboot")) {
return 0;
}
value = g_hash_table_lookup(device->params, STONITH_ATTR_DELAY_MAX);
if (value) {
delay_max_ms = crm_get_msec(value);
}
return delay_max_ms;
}
/*!
* \internal
* \brief Override STONITH timeout with pcmk_*_timeout if available
*
* \param[in] device STONITH device to use
* \param[in] action STONITH action name
* \param[in] default_timeout Timeout to use if device does not have
* a pcmk_*_timeout parameter for action
*
* \return Value of pcmk_(action)_timeout if available, otherwise default_timeout
* \note For consistency, it would be nice if reboot/off/on timeouts could be
* set the same way as start/stop/monitor timeouts, i.e. with an
* <operation> entry in the fencing resource configuration. However that
* is insufficient because fencing devices may be registered directly via
* the STONITH register_device() API instead of going through the CIB
* (e.g. stonith_admin uses it for its -R option, and the LRMD uses it to
* ensure a device is registered when a command is issued). As device
* properties, pcmk_*_timeout parameters can be grabbed by stonithd when
* the device is registered, whether by CIB change or API call.
*/
static int
get_action_timeout(stonith_device_t * device, const char *action, int default_timeout)
{
if (action && device && device->params) {
char buffer[64] = { 0, };
const char *value = NULL;
/* If "reboot" was requested but the device does not support it,
* we will remap to "off", so check timeout for "off" instead
*/
if (safe_str_eq(action, "reboot")
&& is_not_set(device->flags, st_device_supports_reboot)) {
crm_trace("%s doesn't support reboot, using timeout for off instead",
device->id);
action = "off";
}
/* If the device config specified an action-specific timeout, use it */
snprintf(buffer, sizeof(buffer) - 1, "pcmk_%s_timeout", action);
value = g_hash_table_lookup(device->params, buffer);
if (value) {
return atoi(value);
}
}
return default_timeout;
}
static void
free_async_command(async_command_t * cmd)
{
if (!cmd) {
return;
}
if (cmd->delay_id) {
g_source_remove(cmd->delay_id);
}
cmd_list = g_list_remove(cmd_list, cmd);
g_list_free_full(cmd->device_list, free);
free(cmd->device);
free(cmd->action);
free(cmd->victim);
free(cmd->remote_op_id);
free(cmd->client);
free(cmd->client_name);
free(cmd->origin);
free(cmd->mode);
free(cmd->op);
free(cmd);
}
static async_command_t *
create_async_command(xmlNode * msg)
{
async_command_t *cmd = NULL;
xmlNode *op = get_xpath_object("//@" F_STONITH_ACTION, msg, LOG_ERR);
const char *action = crm_element_value(op, F_STONITH_ACTION);
CRM_CHECK(action != NULL, crm_log_xml_warn(msg, "NoAction"); return NULL);
crm_log_xml_trace(msg, "Command");
cmd = calloc(1, sizeof(async_command_t));
crm_element_value_int(msg, F_STONITH_CALLID, &(cmd->id));
crm_element_value_int(msg, F_STONITH_CALLOPTS, &(cmd->options));
crm_element_value_int(msg, F_STONITH_TIMEOUT, &(cmd->default_timeout));
cmd->timeout = cmd->default_timeout;
cmd->origin = crm_element_value_copy(msg, F_ORIG);
cmd->remote_op_id = crm_element_value_copy(msg, F_STONITH_REMOTE_OP_ID);
cmd->client = crm_element_value_copy(msg, F_STONITH_CLIENTID);
cmd->client_name = crm_element_value_copy(msg, F_STONITH_CLIENTNAME);
cmd->op = crm_element_value_copy(msg, F_STONITH_OPERATION);
cmd->action = strdup(action);
cmd->victim = crm_element_value_copy(op, F_STONITH_TARGET);
cmd->mode = crm_element_value_copy(op, F_STONITH_MODE);
cmd->device = crm_element_value_copy(op, F_STONITH_DEVICE);
CRM_CHECK(cmd->op != NULL, crm_log_xml_warn(msg, "NoOp"); free_async_command(cmd); return NULL);
CRM_CHECK(cmd->client != NULL, crm_log_xml_warn(msg, "NoClient"));
cmd->done_cb = st_child_done;
cmd_list = g_list_append(cmd_list, cmd);
return cmd;
}
static int
get_action_limit(stonith_device_t * device)
{
const char *value = NULL;
int action_limit = 1;
value = g_hash_table_lookup(device->params, STONITH_ATTR_ACTION_LIMIT);
if (value) {
action_limit = crm_parse_int(value, "1");
if (action_limit == 0) {
/* pcmk_action_limit should not be 0. Enforce it to be 1. */
action_limit = 1;
}
}
return action_limit;
}
static int
get_active_cmds(stonith_device_t * device)
{
int counter = 0;
GListPtr gIter = NULL;
GListPtr gIterNext = NULL;
CRM_CHECK(device != NULL, return 0);
for (gIter = cmd_list; gIter != NULL; gIter = gIterNext) {
async_command_t *cmd = gIter->data;
gIterNext = gIter->next;
if (cmd->active_on == device) {
counter++;
}
}
return counter;
}
static gboolean
stonith_device_execute(stonith_device_t * device)
{
int exec_rc = 0;
const char *action_str = NULL;
async_command_t *cmd = NULL;
stonith_action_t *action = NULL;
int active_cmds = 0;
int action_limit = 0;
CRM_CHECK(device != NULL, return FALSE);
active_cmds = get_active_cmds(device);
action_limit = get_action_limit(device);
if (action_limit > -1 && active_cmds >= action_limit) {
crm_trace("%s is over its action limit of %d (%u active action%s)",
device->id, action_limit, active_cmds, active_cmds > 1 ? "s" : "");
return TRUE;
}
if (device->pending_ops) {
GList *first = device->pending_ops;
cmd = first->data;
if (cmd && cmd->delay_id) {
crm_trace
("Operation %s%s%s on %s was asked to run too early, waiting for start_delay timeout of %dms",
cmd->action, cmd->victim ? " for node " : "", cmd->victim ? cmd->victim : "",
device->id, cmd->start_delay);
return TRUE;
}
device->pending_ops = g_list_remove_link(device->pending_ops, first);
g_list_free_1(first);
}
if (cmd == NULL) {
crm_trace("Nothing further to do for %s", device->id);
return TRUE;
}
if(safe_str_eq(device->agent, STONITH_WATCHDOG_AGENT)) {
if(safe_str_eq(cmd->action, "reboot")) {
pcmk_panic(__FUNCTION__);
return TRUE;
} else if(safe_str_eq(cmd->action, "off")) {
pcmk_panic(__FUNCTION__);
return TRUE;
} else {
crm_info("Faking success for %s watchdog operation", cmd->action);
cmd->done_cb(0, 0, NULL, cmd);
return TRUE;
}
}
#if SUPPORT_CIBSECRETS
if (replace_secret_params(device->id, device->params) < 0) {
/* replacing secrets failed! */
if (safe_str_eq(cmd->action,"stop")) {
/* don't fail on stop! */
crm_info("proceeding with the stop operation for %s", device->id);
} else {
crm_err("failed to get secrets for %s, "
"considering resource not configured", device->id);
exec_rc = PCMK_OCF_NOT_CONFIGURED;
cmd->done_cb(0, exec_rc, NULL, cmd);
return TRUE;
}
}
#endif
action_str = cmd->action;
if (safe_str_eq(cmd->action, "reboot") && is_not_set(device->flags, st_device_supports_reboot)) {
crm_warn("Agent '%s' does not advertise support for 'reboot', performing 'off' action instead", device->agent);
action_str = "off";
}
action = stonith_action_create(device->agent,
action_str,
cmd->victim,
cmd->victim_nodeid,
cmd->timeout, device->params, device->aliases);
/* for async exec, exec_rc is pid if positive and error code if negative/zero */
exec_rc = stonith_action_execute_async(action, (void *)cmd, cmd->done_cb);
if (exec_rc > 0) {
crm_debug("Operation %s%s%s on %s now running with pid=%d, timeout=%ds",
cmd->action, cmd->victim ? " for node " : "", cmd->victim ? cmd->victim : "",
device->id, exec_rc, cmd->timeout);
cmd->active_on = device;
} else {
crm_warn("Operation %s%s%s on %s failed: %s (%d)",
cmd->action, cmd->victim ? " for node " : "", cmd->victim ? cmd->victim : "",
device->id, pcmk_strerror(exec_rc), exec_rc);
cmd->done_cb(0, exec_rc, NULL, cmd);
}
return TRUE;
}
static gboolean
stonith_device_dispatch(gpointer user_data)
{
return stonith_device_execute(user_data);
}
static gboolean
start_delay_helper(gpointer data)
{
async_command_t *cmd = data;
stonith_device_t *device = NULL;
cmd->delay_id = 0;
device = cmd->device ? g_hash_table_lookup(device_list, cmd->device) : NULL;
if (device) {
mainloop_set_trigger(device->work);
}
return FALSE;
}
static void
schedule_stonith_command(async_command_t * cmd, stonith_device_t * device)
{
int delay_max = 0;
CRM_CHECK(cmd != NULL, return);
CRM_CHECK(device != NULL, return);
if (cmd->device) {
free(cmd->device);
}
if (device->include_nodeid && cmd->victim) {
crm_node_t *node = crm_get_peer(0, cmd->victim);
cmd->victim_nodeid = node->id;
}
cmd->device = strdup(device->id);
cmd->timeout = get_action_timeout(device, cmd->action, cmd->default_timeout);
if (cmd->remote_op_id) {
crm_debug("Scheduling %s on %s for remote peer %s with op id (%s) (timeout=%ds)",
cmd->action, device->id, cmd->origin, cmd->remote_op_id, cmd->timeout);
} else {
crm_debug("Scheduling %s on %s for %s (timeout=%ds)",
cmd->action, device->id, cmd->client, cmd->timeout);
}
device->pending_ops = g_list_append(device->pending_ops, cmd);
mainloop_set_trigger(device->work);
delay_max = get_action_delay_max(device, cmd->action);
if (delay_max > 0) {
cmd->start_delay = rand() % delay_max;
crm_notice("Delaying %s on %s for %lldms (timeout=%ds)",
cmd->action, device->id, cmd->start_delay, cmd->timeout);
cmd->delay_id = g_timeout_add(cmd->start_delay, start_delay_helper, cmd);
}
}
void
free_device(gpointer data)
{
GListPtr gIter = NULL;
stonith_device_t *device = data;
g_hash_table_destroy(device->params);
g_hash_table_destroy(device->aliases);
for (gIter = device->pending_ops; gIter != NULL; gIter = gIter->next) {
async_command_t *cmd = gIter->data;
crm_warn("Removal of device '%s' purged operation %s", device->id, cmd->action);
cmd->done_cb(0, -ENODEV, NULL, cmd);
free_async_command(cmd);
}
g_list_free(device->pending_ops);
g_list_free_full(device->targets, free);
mainloop_destroy_trigger(device->work);
free_xml(device->agent_metadata);
free(device->namespace);
free(device->on_target_actions);
free(device->agent);
free(device->id);
free(device);
}
static GHashTable *
build_port_aliases(const char *hostmap, GListPtr * targets)
{
char *name = NULL;
int last = 0, lpc = 0, max = 0, added = 0;
GHashTable *aliases =
g_hash_table_new_full(crm_strcase_hash, crm_strcase_equal, g_hash_destroy_str, g_hash_destroy_str);
if (hostmap == NULL) {
return aliases;
}
max = strlen(hostmap);
for (; lpc <= max; lpc++) {
switch (hostmap[lpc]) {
/* Assignment chars */
case '=':
case ':':
if (lpc > last) {
free(name);
name = calloc(1, 1 + lpc - last);
memcpy(name, hostmap + last, lpc - last);
}
last = lpc + 1;
break;
/* Delimeter chars */
/* case ',': Potentially used to specify multiple ports */
case 0:
case ';':
case ' ':
case '\t':
if (name) {
char *value = NULL;
value = calloc(1, 1 + lpc - last);
memcpy(value, hostmap + last, lpc - last);
crm_debug("Adding alias '%s'='%s'", name, value);
g_hash_table_replace(aliases, name, value);
if (targets) {
*targets = g_list_append(*targets, strdup(value));
}
value = NULL;
name = NULL;
added++;
} else if (lpc > last) {
crm_debug("Parse error at offset %d near '%s'", lpc - last, hostmap + last);
}
last = lpc + 1;
break;
}
if (hostmap[lpc] == 0) {
break;
}
}
if (added == 0) {
crm_info("No host mappings detected in '%s'", hostmap);
}
free(name);
return aliases;
}
static void
parse_host_line(const char *line, int max, GListPtr * output)
{
int lpc = 0;
int last = 0;
if (max <= 0) {
return;
}
/* Check for any complaints about additional parameters that the device doesn't understand */
if (strstr(line, "invalid") || strstr(line, "variable")) {
crm_debug("Skipping: %s", line);
return;
}
crm_trace("Processing %d bytes: [%s]", max, line);
/* Skip initial whitespace */
for (lpc = 0; lpc <= max && isspace(line[lpc]); lpc++) {
last = lpc + 1;
}
/* Now the actual content */
for (lpc = 0; lpc <= max; lpc++) {
gboolean a_space = isspace(line[lpc]);
if (a_space && lpc < max && isspace(line[lpc + 1])) {
/* fast-forward to the end of the spaces */
} else if (a_space || line[lpc] == ',' || line[lpc] == ';' || line[lpc] == 0) {
int rc = 1;
char *entry = NULL;
if (lpc != last) {
entry = calloc(1, 1 + lpc - last);
rc = sscanf(line + last, "%[a-zA-Z0-9_-.]", entry);
}
if (entry == NULL) {
/* Skip */
} else if (rc != 1) {
crm_warn("Could not parse (%d %d): %s", last, lpc, line + last);
} else if (safe_str_neq(entry, "on") && safe_str_neq(entry, "off")) {
crm_trace("Adding '%s'", entry);
*output = g_list_append(*output, entry);
entry = NULL;
}
free(entry);
last = lpc + 1;
}
}
}
static GListPtr
parse_host_list(const char *hosts)
{
int lpc = 0;
int max = 0;
int last = 0;
GListPtr output = NULL;
if (hosts == NULL) {
return output;
}
max = strlen(hosts);
for (lpc = 0; lpc <= max; lpc++) {
if (hosts[lpc] == '\n' || hosts[lpc] == 0) {
char *line = NULL;
int len = lpc - last;
if(len > 1) {
line = malloc(1 + len);
}
if(line) {
snprintf(line, 1 + len, "%s", hosts + last);
line[len] = 0; /* Because it might be '\n' */
parse_host_line(line, len, &output);
free(line);
}
last = lpc + 1;
}
}
crm_trace("Parsed %d entries from '%s'", g_list_length(output), hosts);
return output;
}
GHashTable *metadata_cache = NULL;
static xmlNode *
get_agent_metadata(const char *agent)
{
xmlNode *xml = NULL;
char *buffer = NULL;
if(metadata_cache == NULL) {
metadata_cache = g_hash_table_new_full(
crm_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str);
}
buffer = g_hash_table_lookup(metadata_cache, agent);
if(safe_str_eq(agent, STONITH_WATCHDOG_AGENT)) {
return NULL;
} else if(buffer == NULL) {
stonith_t *st = stonith_api_new();
int rc = st->cmds->metadata(st, st_opt_sync_call, agent, NULL, &buffer, 10);
stonith_api_delete(st);
if (rc || !buffer) {
crm_err("Could not retrieve metadata for fencing agent %s", agent);
return NULL;
}
g_hash_table_replace(metadata_cache, strdup(agent), buffer);
}
xml = string2xml(buffer);
return xml;
}
static gboolean
is_nodeid_required(xmlNode * xml)
{
xmlXPathObjectPtr xpath = NULL;
if (stand_alone) {
return FALSE;
}
if (!xml) {
return FALSE;
}
xpath = xpath_search(xml, "//parameter[@name='nodeid']");
if (numXpathResults(xpath) <= 0) {
freeXpathObject(xpath);
return FALSE;
}
freeXpathObject(xpath);
return TRUE;
}
static char *
add_action(char *actions, const char *action)
{
static size_t len = 256;
int offset = 0;
if (actions == NULL) {
actions = calloc(1, len);
} else {
offset = strlen(actions);
}
if (offset > 0) {
offset += snprintf(actions+offset, len-offset, " ");
}
offset += snprintf(actions+offset, len-offset, "%s", action);
return actions;
}
static void
read_action_metadata(stonith_device_t *device)
{
xmlXPathObjectPtr xpath = NULL;
int max = 0;
int lpc = 0;
if (device->agent_metadata == NULL) {
return;
}
xpath = xpath_search(device->agent_metadata, "//action");
max = numXpathResults(xpath);
if (max <= 0) {
freeXpathObject(xpath);
return;
}
for (lpc = 0; lpc < max; lpc++) {
const char *on_target = NULL;
const char *action = NULL;
xmlNode *match = getXpathResult(xpath, lpc);
CRM_LOG_ASSERT(match != NULL);
if(match == NULL) { continue; };
on_target = crm_element_value(match, "on_target");
action = crm_element_value(match, "name");
if(safe_str_eq(action, "list")) {
set_bit(device->flags, st_device_supports_list);
} else if(safe_str_eq(action, "status")) {
set_bit(device->flags, st_device_supports_status);
} else if(safe_str_eq(action, "reboot")) {
set_bit(device->flags, st_device_supports_reboot);
} else if (safe_str_eq(action, "on")) {
/* "automatic" means the cluster will unfence node when it joins */
const char *automatic = crm_element_value(match, "automatic");
/* "required" is a deprecated synonym for "automatic" */
const char *required = crm_element_value(match, "required");
if (crm_is_true(automatic) || crm_is_true(required)) {
device->automatic_unfencing = TRUE;
}
}
if (action && crm_is_true(on_target)) {
device->on_target_actions = add_action(device->on_target_actions, action);
}
}
freeXpathObject(xpath);
}
static stonith_device_t *
build_device_from_xml(xmlNode * msg)
{
const char *value = NULL;
xmlNode *dev = get_xpath_object("//" F_STONITH_DEVICE, msg, LOG_ERR);
stonith_device_t *device = NULL;
device = calloc(1, sizeof(stonith_device_t));
device->id = crm_element_value_copy(dev, XML_ATTR_ID);
device->agent = crm_element_value_copy(dev, "agent");
device->namespace = crm_element_value_copy(dev, "namespace");
device->params = xml2list(dev);
value = g_hash_table_lookup(device->params, STONITH_ATTR_HOSTLIST);
if (value) {
device->targets = parse_host_list(value);
}
value = g_hash_table_lookup(device->params, STONITH_ATTR_HOSTMAP);
device->aliases = build_port_aliases(value, &(device->targets));
device->agent_metadata = get_agent_metadata(device->agent);
read_action_metadata(device);
value = g_hash_table_lookup(device->params, "nodeid");
if (!value) {
device->include_nodeid = is_nodeid_required(device->agent_metadata);
}
value = crm_element_value(dev, "rsc_provides");
if (safe_str_eq(value, "unfencing")) {
device->automatic_unfencing = TRUE;
}
if (is_action_required("on", device)) {
crm_info("The fencing device '%s' requires unfencing", device->id);
}
if (device->on_target_actions) {
crm_info("The fencing device '%s' requires actions (%s) to be executed on the target node",
device->id, device->on_target_actions);
}
device->work = mainloop_add_trigger(G_PRIORITY_HIGH, stonith_device_dispatch, device);
/* TODO: Hook up priority */
return device;
}
static const char *
target_list_type(stonith_device_t * dev)
{
const char *check_type = NULL;
check_type = g_hash_table_lookup(dev->params, STONITH_ATTR_HOSTCHECK);
if (check_type == NULL) {
if (g_hash_table_lookup(dev->params, STONITH_ATTR_HOSTLIST)) {
check_type = "static-list";
} else if (g_hash_table_lookup(dev->params, STONITH_ATTR_HOSTMAP)) {
check_type = "static-list";
} else if(is_set(dev->flags, st_device_supports_list)){
check_type = "dynamic-list";
} else if(is_set(dev->flags, st_device_supports_status)){
check_type = "status";
} else {
check_type = "none";
}
}
return check_type;
}
void
schedule_internal_command(const char *origin,
stonith_device_t * device,
const char *action,
const char *victim,
int timeout,
void *internal_user_data,
void (*done_cb) (GPid pid, int rc, const char *output,
gpointer user_data))
{
async_command_t *cmd = NULL;
cmd = calloc(1, sizeof(async_command_t));
cmd->id = -1;
cmd->default_timeout = timeout ? timeout : 60;
cmd->timeout = cmd->default_timeout;
cmd->action = strdup(action);
cmd->victim = victim ? strdup(victim) : NULL;
cmd->device = strdup(device->id);
cmd->origin = strdup(origin);
cmd->client = strdup(crm_system_name);
cmd->client_name = strdup(crm_system_name);
cmd->internal_user_data = internal_user_data;
cmd->done_cb = done_cb; /* cmd, not internal_user_data, is passed to 'done_cb' as the userdata */
schedule_stonith_command(cmd, device);
}
gboolean
string_in_list(GListPtr list, const char *item)
{
int lpc = 0;
int max = g_list_length(list);
for (lpc = 0; lpc < max; lpc++) {
const char *value = g_list_nth_data(list, lpc);
if (safe_str_eq(item, value)) {
return TRUE;
} else {
crm_trace("%d: '%s' != '%s'", lpc, item, value);
}
}
return FALSE;
}
static void
status_search_cb(GPid pid, int rc, const char *output, gpointer user_data)
{
async_command_t *cmd = user_data;
struct device_search_s *search = cmd->internal_user_data;
stonith_device_t *dev = cmd->device ? g_hash_table_lookup(device_list, cmd->device) : NULL;
gboolean can = FALSE;
free_async_command(cmd);
if (!dev) {
search_devices_record_result(search, NULL, FALSE);
return;
}
mainloop_set_trigger(dev->work);
if (rc == 1 /* unknown */ ) {
crm_trace("Host %s is not known by %s", search->host, dev->id);
} else if (rc == 0 /* active */ || rc == 2 /* inactive */ ) {
crm_trace("Host %s is known by %s", search->host, dev->id);
can = TRUE;
} else {
crm_notice("Unknown result when testing if %s can fence %s: rc=%d", dev->id, search->host,
rc);
}
search_devices_record_result(search, dev->id, can);
}
static void
dynamic_list_search_cb(GPid pid, int rc, const char *output, gpointer user_data)
{
async_command_t *cmd = user_data;
struct device_search_s *search = cmd->internal_user_data;
stonith_device_t *dev = cmd->device ? g_hash_table_lookup(device_list, cmd->device) : NULL;
gboolean can_fence = FALSE;
free_async_command(cmd);
/* Host/alias must be in the list output to be eligible to be fenced
*
* Will cause problems if down'd nodes aren't listed or (for virtual nodes)
* if the guest is still listed despite being moved to another machine
*/
if (!dev) {
search_devices_record_result(search, NULL, FALSE);
return;
}
mainloop_set_trigger(dev->work);
/* If we successfully got the targets earlier, don't disable. */
if (rc != 0 && !dev->targets) {
crm_notice("Disabling port list queries for %s (%d): %s", dev->id, rc, output);
/* Fall back to status */
g_hash_table_replace(dev->params, strdup(STONITH_ATTR_HOSTCHECK), strdup("status"));
g_list_free_full(dev->targets, free);
dev->targets = NULL;
} else if (!rc) {
crm_info("Refreshing port list for %s", dev->id);
g_list_free_full(dev->targets, free);
dev->targets = parse_host_list(output);
dev->targets_age = time(NULL);
}
if (dev->targets) {
const char *alias = g_hash_table_lookup(dev->aliases, search->host);
if (!alias) {
alias = search->host;
}
if (string_in_list(dev->targets, alias)) {
can_fence = TRUE;
}
}
search_devices_record_result(search, dev->id, can_fence);
}
/*!
* \internal
* \brief Checks to see if an identical device already exists in the device_list
*/
static stonith_device_t *
device_has_duplicate(stonith_device_t * device)
{
char *key = NULL;
char *value = NULL;
GHashTableIter gIter;
stonith_device_t *dup = g_hash_table_lookup(device_list, device->id);
if (!dup) {
crm_trace("No match for %s", device->id);
return NULL;
} else if (safe_str_neq(dup->agent, device->agent)) {
crm_trace("Different agent: %s != %s", dup->agent, device->agent);
return NULL;
}
/* Use calculate_operation_digest() here? */
g_hash_table_iter_init(&gIter, device->params);
while (g_hash_table_iter_next(&gIter, (void **)&key, (void **)&value)) {
if(strstr(key, "CRM_meta") == key) {
continue;
} else if(strcmp(key, "crm_feature_set") == 0) {
continue;
} else {
char *other_value = g_hash_table_lookup(dup->params, key);
if (!other_value || safe_str_neq(other_value, value)) {
crm_trace("Different value for %s: %s != %s", key, other_value, value);
return NULL;
}
}
}
crm_trace("Match");
return dup;
}
int
stonith_device_register(xmlNode * msg, const char **desc, gboolean from_cib)
{
stonith_device_t *dup = NULL;
stonith_device_t *device = build_device_from_xml(msg);
dup = device_has_duplicate(device);
if (dup) {
crm_debug("Device '%s' already existed in device list (%d active devices)", device->id,
g_hash_table_size(device_list));
free_device(device);
device = dup;
} else {
stonith_device_t *old = g_hash_table_lookup(device_list, device->id);
if (from_cib && old && old->api_registered) {
/* If the cib is writing over an entry that is shared with a stonith client,
* copy any pending ops that currently exist on the old entry to the new one.
* Otherwise the pending ops will be reported as failures
*/
crm_info("Overwriting an existing entry for %s from the cib", device->id);
device->pending_ops = old->pending_ops;
device->api_registered = TRUE;
old->pending_ops = NULL;
if (device->pending_ops) {
mainloop_set_trigger(device->work);
}
}
g_hash_table_replace(device_list, device->id, device);
crm_notice("Added '%s' to the device list (%d active devices)", device->id,
g_hash_table_size(device_list));
}
if (desc) {
*desc = device->id;
}
if (from_cib) {
device->cib_registered = TRUE;
} else {
device->api_registered = TRUE;
}
return pcmk_ok;
}
int
stonith_device_remove(const char *id, gboolean from_cib)
{
stonith_device_t *device = g_hash_table_lookup(device_list, id);
if (!device) {
crm_info("Device '%s' not found (%d active devices)", id, g_hash_table_size(device_list));
return pcmk_ok;
}
if (from_cib) {
device->cib_registered = FALSE;
} else {
device->verified = FALSE;
device->api_registered = FALSE;
}
if (!device->cib_registered && !device->api_registered) {
g_hash_table_remove(device_list, id);
crm_info("Removed '%s' from the device list (%d active devices)",
id, g_hash_table_size(device_list));
}
return pcmk_ok;
}
/*!
* \internal
* \brief Return the number of stonith levels registered for a node
*
* \param[in] tp Node's topology table entry
*
* \return Number of non-NULL levels in topology entry
* \note This function is used only for log messages.
*/
static int
count_active_levels(stonith_topology_t * tp)
{
int lpc = 0;
int count = 0;
for (lpc = 0; lpc < ST_LEVEL_MAX; lpc++) {
if (tp->levels[lpc] != NULL) {
count++;
}
}
return count;
}
void
free_topology_entry(gpointer data)
{
stonith_topology_t *tp = data;
int lpc = 0;
for (lpc = 0; lpc < ST_LEVEL_MAX; lpc++) {
if (tp->levels[lpc] != NULL) {
g_list_free_full(tp->levels[lpc], free);
}
}
free(tp->target);
free(tp->target_value);
free(tp->target_pattern);
free(tp->target_attribute);
free(tp);
}
char *stonith_level_key(xmlNode *level, int mode)
{
if(mode == -1) {
mode = stonith_level_kind(level);
}
switch(mode) {
case 0:
return crm_element_value_copy(level, XML_ATTR_STONITH_TARGET);
case 1:
return crm_element_value_copy(level, XML_ATTR_STONITH_TARGET_PATTERN);
case 2:
{
const char *name = crm_element_value(level, XML_ATTR_STONITH_TARGET_ATTRIBUTE);
const char *value = crm_element_value(level, XML_ATTR_STONITH_TARGET_VALUE);
if(name && value) {
return crm_strdup_printf("%s=%s", name, value);
}
}
default:
return crm_strdup_printf("Unknown-%d-%s", mode, ID(level));
}
}
int stonith_level_kind(xmlNode * level)
{
int mode = 0;
const char *target = crm_element_value(level, XML_ATTR_STONITH_TARGET);
if(target == NULL) {
mode++;
target = crm_element_value(level, XML_ATTR_STONITH_TARGET_PATTERN);
}
if(stand_alone == FALSE && target == NULL) {
mode++;
if(crm_element_value(level, XML_ATTR_STONITH_TARGET_ATTRIBUTE) == NULL) {
mode++;
} else if(crm_element_value(level, XML_ATTR_STONITH_TARGET_VALUE) == NULL) {
mode++;
}
}
return mode;
}
static stonith_key_value_t *
parse_device_list(const char *devices)
{
int lpc = 0;
int max = 0;
int last = 0;
stonith_key_value_t *output = NULL;
if (devices == NULL) {
return output;
}
max = strlen(devices);
for (lpc = 0; lpc <= max; lpc++) {
if (devices[lpc] == ',' || devices[lpc] == 0) {
char *line = NULL;
line = calloc(1, 2 + lpc - last);
snprintf(line, 1 + lpc - last, "%s", devices + last);
output = stonith_key_value_add(output, NULL, line);
free(line);
last = lpc + 1;
}
}
return output;
}
/*!
* \internal
* \brief Register a STONITH level for a target
*
* Given an XML request specifying the target name, level index, and device IDs
* for the level, this will create an entry for the target in the global topology
* table if one does not already exist, then append the specified device IDs to
* the entry's device list for the specified level.
*
* \param[in] msg XML request for STONITH level registration
* \param[out] desc If not NULL, will be set to string representation ("TARGET[LEVEL]")
*
* \return pcmk_ok on success, -EINVAL if XML does not specify valid level index
*/
int
stonith_level_register(xmlNode *msg, char **desc)
{
int id = 0;
xmlNode *level;
int mode;
char *target;
stonith_topology_t *tp;
stonith_key_value_t *dIter = NULL;
stonith_key_value_t *devices = NULL;
/* Allow the XML here to point to the level tag directly, or wrapped in
* another tag. If directly, don't search by xpath, because it might give
* multiple hits (e.g. if the XML is the CIB).
*/
if (safe_str_eq(TYPE(msg), XML_TAG_FENCING_LEVEL)) {
level = msg;
} else {
level = get_xpath_object("//" XML_TAG_FENCING_LEVEL, msg, LOG_ERR);
}
CRM_CHECK(level != NULL, return -EINVAL);
mode = stonith_level_kind(level);
target = stonith_level_key(level, mode);
crm_element_value_int(level, XML_ATTR_STONITH_INDEX, &id);
if (desc) {
*desc = crm_strdup_printf("%s[%d]", target, id);
}
/* Sanity-check arguments */
if (mode >= 3 || (id <= 0) || (id >= ST_LEVEL_MAX)) {
crm_trace("Could not add %s[%d] (%d) to the topology (%d active entries)", target, id, mode, g_hash_table_size(topology));
free(target);
crm_log_xml_err(level, "Bad topology");
return -EINVAL;
}
/* Find or create topology table entry */
tp = g_hash_table_lookup(topology, target);
if (tp == NULL) {
tp = calloc(1, sizeof(stonith_topology_t));
tp->kind = mode;
tp->target = target;
tp->target_value = crm_element_value_copy(level, XML_ATTR_STONITH_TARGET_VALUE);
tp->target_pattern = crm_element_value_copy(level, XML_ATTR_STONITH_TARGET_PATTERN);
tp->target_attribute = crm_element_value_copy(level, XML_ATTR_STONITH_TARGET_ATTRIBUTE);
g_hash_table_replace(topology, tp->target, tp);
crm_trace("Added %s (%d) to the topology (%d active entries)",
target, mode, g_hash_table_size(topology));
} else {
free(target);
}
if (tp->levels[id] != NULL) {
crm_info("Adding to the existing %s[%d] topology entry",
tp->target, id);
}
devices = parse_device_list(crm_element_value(level, XML_ATTR_STONITH_DEVICES));
for (dIter = devices; dIter; dIter = dIter->next) {
const char *device = dIter->value;
crm_trace("Adding device '%s' for %s[%d]", device, tp->target, id);
tp->levels[id] = g_list_append(tp->levels[id], strdup(device));
}
stonith_key_value_freeall(devices, 1, 1);
crm_info("Target %s has %d active fencing levels",
tp->target, count_active_levels(tp));
return pcmk_ok;
}
int
stonith_level_remove(xmlNode *msg, char **desc)
{
int id = 0;
stonith_topology_t *tp;
char *target;
/* Unlike additions, removal requests should always have one level tag */
xmlNode *level = get_xpath_object("//" XML_TAG_FENCING_LEVEL, msg, LOG_ERR);
CRM_CHECK(level != NULL, return -EINVAL);
target = stonith_level_key(level, -1);
crm_element_value_int(level, XML_ATTR_STONITH_INDEX, &id);
if (desc) {
*desc = crm_strdup_printf("%s[%d]", target, id);
}
/* Sanity-check arguments */
if (id >= ST_LEVEL_MAX) {
free(target);
return -EINVAL;
}
tp = g_hash_table_lookup(topology, target);
if (tp == NULL) {
crm_info("Topology for %s not found (%d active entries)",
target, g_hash_table_size(topology));
} else if (id == 0 && g_hash_table_remove(topology, target)) {
crm_info("Removed all %s related entries from the topology (%d active entries)",
target, g_hash_table_size(topology));
} else if (id > 0 && tp->levels[id] != NULL) {
g_list_free_full(tp->levels[id], free);
tp->levels[id] = NULL;
crm_info("Removed level '%d' from topology for %s (%d active levels remaining)",
id, target, count_active_levels(tp));
}
free(target);
return pcmk_ok;
}
static int
stonith_device_action(xmlNode * msg, char **output)
{
int rc = pcmk_ok;
xmlNode *dev = get_xpath_object("//" F_STONITH_DEVICE, msg, LOG_ERR);
const char *id = crm_element_value(dev, F_STONITH_DEVICE);
async_command_t *cmd = NULL;
stonith_device_t *device = NULL;
if (id) {
crm_trace("Looking for '%s'", id);
device = g_hash_table_lookup(device_list, id);
}
if (device && device->api_registered == FALSE) {
rc = -ENODEV;
} else if (device) {
cmd = create_async_command(msg);
if (cmd == NULL) {
return -EPROTO;
}
schedule_stonith_command(cmd, device);
rc = -EINPROGRESS;
} else {
crm_info("Device %s not found", id ? id : "<none>");
rc = -ENODEV;
}
return rc;
}
static void
search_devices_record_result(struct device_search_s *search, const char *device, gboolean can_fence)
{
search->replies_received++;
if (can_fence && device) {
search->capable = g_list_append(search->capable, strdup(device));
}
if (search->replies_needed == search->replies_received) {
crm_debug("Finished Search. %d devices can perform action (%s) on node %s",
g_list_length(search->capable),
search->action ? search->action : "<unknown>",
search->host ? search->host : "<anyone>");
search->callback(search->capable, search->user_data);
free(search->host);
free(search->action);
free(search);
}
}
-/*
+/*!
* \internal
* \brief Check whether the local host is allowed to execute a fencing action
*
* \param[in] device Fence device to check
* \param[in] action Fence action to check
* \param[in] target Hostname of fence target
* \param[in] allow_suicide Whether self-fencing is allowed for this operation
*
* \return TRUE if local host is allowed to execute action, FALSE otherwise
*/
static gboolean
localhost_is_eligible(const stonith_device_t *device, const char *action,
const char *target, gboolean allow_suicide)
{
gboolean localhost_is_target = safe_str_eq(target, stonith_our_uname);
if (device && action && device->on_target_actions
&& strstr(device->on_target_actions, action)) {
if (!localhost_is_target) {
crm_trace("%s operation with %s can only be executed for localhost not %s",
action, device->id, target);
return FALSE;
}
} else if (localhost_is_target && !allow_suicide) {
crm_trace("%s operation does not support self-fencing", action);
return FALSE;
}
return TRUE;
}
static void
can_fence_host_with_device(stonith_device_t * dev, struct device_search_s *search)
{
gboolean can = FALSE;
const char *check_type = NULL;
const char *host = search->host;
const char *alias = NULL;
CRM_LOG_ASSERT(dev != NULL);
if (dev == NULL) {
goto search_report_results;
} else if (host == NULL) {
can = TRUE;
goto search_report_results;
}
/* Short-circuit query if this host is not allowed to perform the action */
if (safe_str_eq(search->action, "reboot")) {
/* A "reboot" *might* get remapped to "off" then "on", so short-circuit
* only if all three are disallowed. If only one or two are disallowed,
* we'll report that with the results. We never allow suicide for
* remapped "on" operations because the host is off at that point.
*/
if (!localhost_is_eligible(dev, "reboot", host, search->allow_suicide)
&& !localhost_is_eligible(dev, "off", host, search->allow_suicide)
&& !localhost_is_eligible(dev, "on", host, FALSE)) {
goto search_report_results;
}
} else if (!localhost_is_eligible(dev, search->action, host,
search->allow_suicide)) {
goto search_report_results;
}
alias = g_hash_table_lookup(dev->aliases, host);
if (alias == NULL) {
alias = host;
}
check_type = target_list_type(dev);
if (safe_str_eq(check_type, "none")) {
can = TRUE;
} else if (safe_str_eq(check_type, "static-list")) {
/* Presence in the hostmap is sufficient
* Only use if all hosts on which the device can be active can always fence all listed hosts
*/
if (string_in_list(dev->targets, host)) {
can = TRUE;
} else if (g_hash_table_lookup(dev->params, STONITH_ATTR_HOSTMAP)
&& g_hash_table_lookup(dev->aliases, host)) {
can = TRUE;
}
} else if (safe_str_eq(check_type, "dynamic-list")) {
time_t now = time(NULL);
if (dev->targets == NULL || dev->targets_age + 60 < now) {
crm_trace("Running %s command to see if %s can fence %s (%s)",
check_type, dev?dev->id:"N/A", search->host, search->action);
schedule_internal_command(__FUNCTION__, dev, "list", NULL,
search->per_device_timeout, search, dynamic_list_search_cb);
/* we'll respond to this search request async in the cb */
return;
}
if (string_in_list(dev->targets, alias)) {
can = TRUE;
}
} else if (safe_str_eq(check_type, "status")) {
crm_trace("Running %s command to see if %s can fence %s (%s)",
check_type, dev?dev->id:"N/A", search->host, search->action);
schedule_internal_command(__FUNCTION__, dev, "status", search->host,
search->per_device_timeout, search, status_search_cb);
/* we'll respond to this search request async in the cb */
return;
} else {
crm_err("Unknown check type: %s", check_type);
}
if (safe_str_eq(host, alias)) {
crm_notice("%s can%s fence (%s) %s: %s", dev->id, can ? "" : " not", search->action, host, check_type);
} else {
crm_notice("%s can%s fence (%s) %s (aka. '%s'): %s", dev->id, can ? "" : " not", search->action, host, alias,
check_type);
}
search_report_results:
search_devices_record_result(search, dev ? dev->id : NULL, can);
}
static void
search_devices(gpointer key, gpointer value, gpointer user_data)
{
stonith_device_t *dev = value;
struct device_search_s *search = user_data;
can_fence_host_with_device(dev, search);
}
#define DEFAULT_QUERY_TIMEOUT 20
static void
get_capable_devices(const char *host, const char *action, int timeout, bool suicide, void *user_data,
void (*callback) (GList * devices, void *user_data))
{
struct device_search_s *search;
int per_device_timeout = DEFAULT_QUERY_TIMEOUT;
int devices_needing_async_query = 0;
char *key = NULL;
const char *check_type = NULL;
GHashTableIter gIter;
stonith_device_t *device = NULL;
if (!g_hash_table_size(device_list)) {
callback(NULL, user_data);
return;
}
search = calloc(1, sizeof(struct device_search_s));
if (!search) {
callback(NULL, user_data);
return;
}
g_hash_table_iter_init(&gIter, device_list);
while (g_hash_table_iter_next(&gIter, (void **)&key, (void **)&device)) {
check_type = target_list_type(device);
if (safe_str_eq(check_type, "status") || safe_str_eq(check_type, "dynamic-list")) {
devices_needing_async_query++;
}
}
/* If we have devices that require an async event in order to know what
* nodes they can fence, we have to give the events a timeout. The total
* query timeout is divided among those events. */
if (devices_needing_async_query) {
per_device_timeout = timeout / devices_needing_async_query;
if (!per_device_timeout) {
crm_err("STONITH timeout %ds is too low; using %ds, but consider raising to at least %ds",
timeout, DEFAULT_QUERY_TIMEOUT,
DEFAULT_QUERY_TIMEOUT * devices_needing_async_query);
per_device_timeout = DEFAULT_QUERY_TIMEOUT;
} else if (per_device_timeout < DEFAULT_QUERY_TIMEOUT) {
crm_notice("STONITH timeout %ds is low for the current configuration;"
" consider raising to at least %ds",
timeout, DEFAULT_QUERY_TIMEOUT * devices_needing_async_query);
}
}
search->host = host ? strdup(host) : NULL;
search->action = action ? strdup(action) : NULL;
search->per_device_timeout = per_device_timeout;
/* We are guaranteed this many replies. Even if a device gets
* unregistered some how during the async search, we will get
* the correct number of replies. */
search->replies_needed = g_hash_table_size(device_list);
search->allow_suicide = suicide;
search->callback = callback;
search->user_data = user_data;
/* kick off the search */
crm_debug("Searching through %d devices to see what is capable of action (%s) for target %s",
search->replies_needed,
search->action ? search->action : "<unknown>",
search->host ? search->host : "<anyone>");
g_hash_table_foreach(device_list, search_devices, search);
}
struct st_query_data {
xmlNode *reply;
char *remote_peer;
char *client_id;
char *target;
char *action;
int call_options;
};
-/*
+/*!
* \internal
* \brief Add action-specific attributes to query reply XML
*
* \param[in,out] xml XML to add attributes to
* \param[in] action Fence action
* \param[in] device Fence device
*/
static void
add_action_specific_attributes(xmlNode *xml, const char *action,
stonith_device_t *device)
{
int action_specific_timeout;
int delay_max;
CRM_CHECK(xml && action && device, return);
if (is_action_required(action, device)) {
crm_trace("Action %s is required on %s", action, device->id);
crm_xml_add_int(xml, F_STONITH_DEVICE_REQUIRED, 1);
}
action_specific_timeout = get_action_timeout(device, action, 0);
if (action_specific_timeout) {
crm_trace("Action %s has timeout %dms on %s",
action, action_specific_timeout, device->id);
crm_xml_add_int(xml, F_STONITH_ACTION_TIMEOUT, action_specific_timeout);
}
delay_max = get_action_delay_max(device, action);
if (delay_max > 0) {
crm_trace("Action %s has maximum random delay %dms on %s",
action, delay_max, device->id);
crm_xml_add_int(xml, F_STONITH_DELAY_MAX, delay_max / 1000);
}
}
-/*
+/*!
* \internal
* \brief Add "disallowed" attribute to query reply XML if appropriate
*
* \param[in,out] xml XML to add attribute to
* \param[in] action Fence action
* \param[in] device Fence device
* \param[in] target Fence target
* \param[in] allow_suicide Whether self-fencing is allowed
*/
static void
add_disallowed(xmlNode *xml, const char *action, stonith_device_t *device,
const char *target, gboolean allow_suicide)
{
if (!localhost_is_eligible(device, action, target, allow_suicide)) {
crm_trace("Action %s on %s is disallowed for local host",
action, device->id);
crm_xml_add(xml, F_STONITH_ACTION_DISALLOWED, XML_BOOLEAN_TRUE);
}
}
-/*
+/*!
* \internal
* \brief Add child element with action-specific values to query reply XML
*
* \param[in,out] xml XML to add attribute to
* \param[in] action Fence action
* \param[in] device Fence device
* \param[in] target Fence target
* \param[in] allow_suicide Whether self-fencing is allowed
*/
static void
add_action_reply(xmlNode *xml, const char *action, stonith_device_t *device,
const char *target, gboolean allow_suicide)
{
xmlNode *child = create_xml_node(xml, F_STONITH_ACTION);
crm_xml_add(child, XML_ATTR_ID, action);
add_action_specific_attributes(child, action, device);
add_disallowed(child, action, device, target, allow_suicide);
}
static void
stonith_query_capable_device_cb(GList * devices, void *user_data)
{
struct st_query_data *query = user_data;
int available_devices = 0;
xmlNode *dev = NULL;
xmlNode *list = NULL;
GListPtr lpc = NULL;
/* Pack the results into XML */
list = create_xml_node(NULL, __FUNCTION__);
crm_xml_add(list, F_STONITH_TARGET, query->target);
for (lpc = devices; lpc != NULL; lpc = lpc->next) {
stonith_device_t *device = g_hash_table_lookup(device_list, lpc->data);
const char *action = query->action;
if (!device) {
/* It is possible the device got unregistered while
* determining who can fence the target */
continue;
}
available_devices++;
dev = create_xml_node(list, F_STONITH_DEVICE);
crm_xml_add(dev, XML_ATTR_ID, device->id);
crm_xml_add(dev, "namespace", device->namespace);
crm_xml_add(dev, "agent", device->agent);
crm_xml_add_int(dev, F_STONITH_DEVICE_VERIFIED, device->verified);
/* If the originating stonithd wants to reboot the node, and we have a
* capable device that doesn't support "reboot", remap to "off" instead.
*/
if (is_not_set(device->flags, st_device_supports_reboot)
&& safe_str_eq(query->action, "reboot")) {
crm_trace("%s doesn't support reboot, using values for off instead",
device->id);
action = "off";
}
/* Add action-specific values if available */
add_action_specific_attributes(dev, action, device);
if (safe_str_eq(query->action, "reboot")) {
/* A "reboot" *might* get remapped to "off" then "on", so after
* sending the "reboot"-specific values in the main element, we add
* sub-elements for "off" and "on" values.
*
* We short-circuited earlier if "reboot", "off" and "on" are all
* disallowed for the local host. However if only one or two are
* disallowed, we send back the results and mark which ones are
* disallowed. If "reboot" is disallowed, this might cause problems
* with older stonithd versions, which won't check for it. Older
* versions will ignore "off" and "on", so they are not a problem.
*/
add_disallowed(dev, action, device, query->target,
is_set(query->call_options, st_opt_allow_suicide));
add_action_reply(dev, "off", device, query->target,
is_set(query->call_options, st_opt_allow_suicide));
add_action_reply(dev, "on", device, query->target, FALSE);
}
/* A query without a target wants device parameters */
if (query->target == NULL) {
xmlNode *attrs = create_xml_node(dev, XML_TAG_ATTRS);
g_hash_table_foreach(device->params, hash2field, attrs);
}
}
crm_xml_add_int(list, F_STONITH_AVAILABLE_DEVICES, available_devices);
if (query->target) {
crm_debug("Found %d matching devices for '%s'", available_devices, query->target);
} else {
crm_debug("%d devices installed", available_devices);
}
if (list != NULL) {
crm_log_xml_trace(list, "Add query results");
add_message_xml(query->reply, F_STONITH_CALLDATA, list);
}
stonith_send_reply(query->reply, query->call_options, query->remote_peer, query->client_id);
free_xml(query->reply);
free(query->remote_peer);
free(query->client_id);
free(query->target);
free(query->action);
free(query);
free_xml(list);
g_list_free_full(devices, free);
}
static void
stonith_query(xmlNode * msg, const char *remote_peer, const char *client_id, int call_options)
{
struct st_query_data *query = NULL;
const char *action = NULL;
const char *target = NULL;
int timeout = 0;
xmlNode *dev = get_xpath_object("//@" F_STONITH_ACTION, msg, LOG_DEBUG_3);
crm_element_value_int(msg, F_STONITH_TIMEOUT, &timeout);
if (dev) {
const char *device = crm_element_value(dev, F_STONITH_DEVICE);
target = crm_element_value(dev, F_STONITH_TARGET);
action = crm_element_value(dev, F_STONITH_ACTION);
if (device && safe_str_eq(device, "manual_ack")) {
/* No query or reply necessary */
return;
}
}
crm_log_xml_debug(msg, "Query");
query = calloc(1, sizeof(struct st_query_data));
query->reply = stonith_construct_reply(msg, NULL, NULL, pcmk_ok);
query->remote_peer = remote_peer ? strdup(remote_peer) : NULL;
query->client_id = client_id ? strdup(client_id) : NULL;
query->target = target ? strdup(target) : NULL;
query->action = action ? strdup(action) : NULL;
query->call_options = call_options;
get_capable_devices(target, action, timeout,
is_set(call_options, st_opt_allow_suicide),
query, stonith_query_capable_device_cb);
}
#define ST_LOG_OUTPUT_MAX 512
static void
log_operation(async_command_t * cmd, int rc, int pid, const char *next, const char *output)
{
if (rc == 0) {
next = NULL;
}
if (cmd->victim != NULL) {
do_crm_log(rc == 0 ? LOG_NOTICE : LOG_ERR,
"Operation '%s' [%d] (call %d from %s) for host '%s' with device '%s' returned: %d (%s)%s%s",
cmd->action, pid, cmd->id, cmd->client_name, cmd->victim, cmd->device, rc,
pcmk_strerror(rc), next ? ". Trying: " : "", next ? next : "");
} else {
do_crm_log_unlikely(rc == 0 ? LOG_DEBUG : LOG_NOTICE,
"Operation '%s' [%d] for device '%s' returned: %d (%s)%s%s",
cmd->action, pid, cmd->device, rc, pcmk_strerror(rc),
next ? ". Trying: " : "", next ? next : "");
}
if (output) {
/* Logging the whole string confuses syslog when the string is xml */
char *prefix = crm_strdup_printf("%s:%d", cmd->device, pid);
crm_log_output(rc == 0 ? LOG_DEBUG : LOG_WARNING, prefix, output);
free(prefix);
}
}
static void
stonith_send_async_reply(async_command_t * cmd, const char *output, int rc, GPid pid)
{
xmlNode *reply = NULL;
gboolean bcast = FALSE;
reply = stonith_construct_async_reply(cmd, output, NULL, rc);
if (safe_str_eq(cmd->action, "metadata")) {
/* Too verbose to log */
crm_trace("Metadata query for %s", cmd->device);
output = NULL;
} else if (crm_str_eq(cmd->action, "monitor", TRUE) ||
crm_str_eq(cmd->action, "list", TRUE) || crm_str_eq(cmd->action, "status", TRUE)) {
crm_trace("Never broadcast %s replies", cmd->action);
} else if (!stand_alone && safe_str_eq(cmd->origin, cmd->victim) && safe_str_neq(cmd->action, "on")) {
crm_trace("Broadcast %s reply for %s", cmd->action, cmd->victim);
crm_xml_add(reply, F_SUBTYPE, "broadcast");
bcast = TRUE;
}
log_operation(cmd, rc, pid, NULL, output);
crm_log_xml_trace(reply, "Reply");
if (bcast) {
crm_xml_add(reply, F_STONITH_OPERATION, T_STONITH_NOTIFY);
send_cluster_message(NULL, crm_msg_stonith_ng, reply, FALSE);
} else if (cmd->origin) {
crm_trace("Directed reply to %s", cmd->origin);
send_cluster_message(crm_get_peer(0, cmd->origin), crm_msg_stonith_ng, reply, FALSE);
} else {
crm_trace("Directed local %ssync reply to %s",
(cmd->options & st_opt_sync_call) ? "" : "a-", cmd->client_name);
do_local_reply(reply, cmd->client, cmd->options & st_opt_sync_call, FALSE);
}
if (stand_alone) {
/* Do notification with a clean data object */
xmlNode *notify_data = create_xml_node(NULL, T_STONITH_NOTIFY_FENCE);
crm_xml_add_int(notify_data, F_STONITH_RC, rc);
crm_xml_add(notify_data, F_STONITH_TARGET, cmd->victim);
crm_xml_add(notify_data, F_STONITH_OPERATION, cmd->op);
crm_xml_add(notify_data, F_STONITH_DELEGATE, "localhost");
crm_xml_add(notify_data, F_STONITH_DEVICE, cmd->device);
crm_xml_add(notify_data, F_STONITH_REMOTE_OP_ID, cmd->remote_op_id);
crm_xml_add(notify_data, F_STONITH_ORIGIN, cmd->client);
do_stonith_notify(0, T_STONITH_NOTIFY_FENCE, rc, notify_data);
}
free_xml(reply);
}
void
unfence_cb(GPid pid, int rc, const char *output, gpointer user_data)
{
async_command_t * cmd = user_data;
stonith_device_t *dev = g_hash_table_lookup(device_list, cmd->device);
log_operation(cmd, rc, pid, NULL, output);
cmd->active_on = NULL;
if(dev) {
mainloop_set_trigger(dev->work);
} else {
crm_trace("Device %s does not exist", cmd->device);
}
if(rc != 0) {
crm_exit(DAEMON_RESPAWN_STOP);
}
}
static void
cancel_stonith_command(async_command_t * cmd)
{
stonith_device_t *device;
CRM_CHECK(cmd != NULL, return);
if (!cmd->device) {
return;
}
device = g_hash_table_lookup(device_list, cmd->device);
if (device) {
crm_trace("Cancel scheduled %s on %s", cmd->action, device->id);
device->pending_ops = g_list_remove(device->pending_ops, cmd);
}
}
static void
st_child_done(GPid pid, int rc, const char *output, gpointer user_data)
{
stonith_device_t *device = NULL;
stonith_device_t *next_device = NULL;
async_command_t *cmd = user_data;
GListPtr gIter = NULL;
GListPtr gIterNext = NULL;
CRM_CHECK(cmd != NULL, return);
cmd->active_on = NULL;
/* The device is ready to do something else now */
device = g_hash_table_lookup(device_list, cmd->device);
if (device) {
if (rc == pcmk_ok &&
(safe_str_eq(cmd->action, "list") ||
safe_str_eq(cmd->action, "monitor") || safe_str_eq(cmd->action, "status"))) {
device->verified = TRUE;
}
mainloop_set_trigger(device->work);
}
crm_debug("Operation '%s' on '%s' completed with rc=%d (%d remaining)",
cmd->action, cmd->device, rc, g_list_length(cmd->device_next));
if (rc == 0) {
GListPtr iter;
/* see if there are any required devices left to execute for this op */
for (iter = cmd->device_next; iter != NULL; iter = iter->next) {
next_device = g_hash_table_lookup(device_list, iter->data);
if (next_device != NULL && is_action_required(cmd->action, next_device)) {
cmd->device_next = iter->next;
break;
}
next_device = NULL;
}
} else if (rc != 0 && cmd->device_next && (is_action_required(cmd->action, device) == FALSE)) {
/* if this device didn't work out, see if there are any others we can try.
* if the failed device was 'required', we can't pick another device. */
next_device = g_hash_table_lookup(device_list, cmd->device_next->data);
cmd->device_next = cmd->device_next->next;
}
/* this operation requires more fencing, hooray! */
if (next_device) {
log_operation(cmd, rc, pid, cmd->device, output);
schedule_stonith_command(cmd, next_device);
/* Prevent cmd from being freed */
cmd = NULL;
goto done;
}
stonith_send_async_reply(cmd, output, rc, pid);
if (rc != 0) {
goto done;
}
/* Check to see if any operations are scheduled to do the exact
* same thing that just completed. If so, rather than
* performing the same fencing operation twice, return the result
* of this operation for all pending commands it matches. */
for (gIter = cmd_list; gIter != NULL; gIter = gIterNext) {
async_command_t *cmd_other = gIter->data;
gIterNext = gIter->next;
if (cmd == cmd_other) {
continue;
}
/* A pending scheduled command matches the command that just finished if.
* 1. The client connections are different.
* 2. The node victim is the same.
* 3. The fencing action is the same.
* 4. The device scheduled to execute the action is the same.
*/
if (safe_str_eq(cmd->client, cmd_other->client) ||
safe_str_neq(cmd->victim, cmd_other->victim) ||
safe_str_neq(cmd->action, cmd_other->action) ||
safe_str_neq(cmd->device, cmd_other->device)) {
continue;
}
/* Duplicate merging will do the right thing for either type of remapped
* reboot. If the executing stonithd remapped an unsupported reboot to
* off, then cmd->action will be reboot and will be merged with any
* other reboot requests. If the originating stonithd remapped a
* topology reboot to off then on, we will get here once with
* cmd->action "off" and once with "on", and they will be merged
* separately with similar requests.
*/
crm_notice
("Merging stonith action %s for node %s originating from client %s with identical stonith request from client %s",
cmd_other->action, cmd_other->victim, cmd_other->client_name, cmd->client_name);
cmd_list = g_list_remove_link(cmd_list, gIter);
stonith_send_async_reply(cmd_other, output, rc, pid);
cancel_stonith_command(cmd_other);
free_async_command(cmd_other);
g_list_free_1(gIter);
}
done:
free_async_command(cmd);
}
static gint
sort_device_priority(gconstpointer a, gconstpointer b)
{
const stonith_device_t *dev_a = a;
const stonith_device_t *dev_b = b;
if (dev_a->priority > dev_b->priority) {
return -1;
} else if (dev_a->priority < dev_b->priority) {
return 1;
}
return 0;
}
static void
stonith_fence_get_devices_cb(GList * devices, void *user_data)
{
async_command_t *cmd = user_data;
stonith_device_t *device = NULL;
crm_info("Found %d matching devices for '%s'", g_list_length(devices), cmd->victim);
if (g_list_length(devices) > 0) {
/* Order based on priority */
devices = g_list_sort(devices, sort_device_priority);
device = g_hash_table_lookup(device_list, devices->data);
if (device) {
cmd->device_list = devices;
cmd->device_next = devices->next;
devices = NULL; /* list owned by cmd now */
}
}
/* we have a device, schedule it for fencing. */
if (device) {
schedule_stonith_command(cmd, device);
/* in progress */
return;
}
/* no device found! */
stonith_send_async_reply(cmd, NULL, -ENODEV, 0);
free_async_command(cmd);
g_list_free_full(devices, free);
}
static int
stonith_fence(xmlNode * msg)
{
const char *device_id = NULL;
stonith_device_t *device = NULL;
async_command_t *cmd = create_async_command(msg);
xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, msg, LOG_ERR);
if (cmd == NULL) {
return -EPROTO;
}
device_id = crm_element_value(dev, F_STONITH_DEVICE);
if (device_id) {
device = g_hash_table_lookup(device_list, device_id);
if (device == NULL) {
crm_err("Requested device '%s' is not available", device_id);
return -ENODEV;
}
schedule_stonith_command(cmd, device);
} else {
const char *host = crm_element_value(dev, F_STONITH_TARGET);
char *nodename = NULL;
if (cmd->options & st_opt_cs_nodeid) {
int nodeid = crm_atoi(host, NULL);
nodename = stonith_get_peer_name(nodeid);
if (nodename) {
host = nodename;
}
}
/* If we get to here, then self-fencing is implicitly allowed */
get_capable_devices(host, cmd->action, cmd->default_timeout,
TRUE, cmd, stonith_fence_get_devices_cb);
free(nodename);
}
return -EINPROGRESS;
}
xmlNode *
stonith_construct_reply(xmlNode * request, const char *output, xmlNode * data, int rc)
{
int lpc = 0;
xmlNode *reply = NULL;
const char *name = NULL;
const char *value = NULL;
const char *names[] = {
F_STONITH_OPERATION,
F_STONITH_CALLID,
F_STONITH_CLIENTID,
F_STONITH_CLIENTNAME,
F_STONITH_REMOTE_OP_ID,
F_STONITH_CALLOPTS
};
crm_trace("Creating a basic reply");
reply = create_xml_node(NULL, T_STONITH_REPLY);
crm_xml_add(reply, "st_origin", __FUNCTION__);
crm_xml_add(reply, F_TYPE, T_STONITH_NG);
crm_xml_add(reply, "st_output", output);
crm_xml_add_int(reply, F_STONITH_RC, rc);
CRM_CHECK(request != NULL, crm_warn("Can't create a sane reply"); return reply);
for (lpc = 0; lpc < DIMOF(names); lpc++) {
name = names[lpc];
value = crm_element_value(request, name);
crm_xml_add(reply, name, value);
}
if (data != NULL) {
crm_trace("Attaching reply output");
add_message_xml(reply, F_STONITH_CALLDATA, data);
}
return reply;
}
static xmlNode *
stonith_construct_async_reply(async_command_t * cmd, const char *output, xmlNode * data, int rc)
{
xmlNode *reply = NULL;
crm_trace("Creating a basic reply");
reply = create_xml_node(NULL, T_STONITH_REPLY);
crm_xml_add(reply, "st_origin", __FUNCTION__);
crm_xml_add(reply, F_TYPE, T_STONITH_NG);
crm_xml_add(reply, F_STONITH_OPERATION, cmd->op);
crm_xml_add(reply, F_STONITH_DEVICE, cmd->device);
crm_xml_add(reply, F_STONITH_REMOTE_OP_ID, cmd->remote_op_id);
crm_xml_add(reply, F_STONITH_CLIENTID, cmd->client);
crm_xml_add(reply, F_STONITH_CLIENTNAME, cmd->client_name);
crm_xml_add(reply, F_STONITH_TARGET, cmd->victim);
crm_xml_add(reply, F_STONITH_ACTION, cmd->op);
crm_xml_add(reply, F_STONITH_ORIGIN, cmd->origin);
crm_xml_add_int(reply, F_STONITH_CALLID, cmd->id);
crm_xml_add_int(reply, F_STONITH_CALLOPTS, cmd->options);
crm_xml_add_int(reply, F_STONITH_RC, rc);
crm_xml_add(reply, "st_output", output);
if (data != NULL) {
crm_info("Attaching reply output");
add_message_xml(reply, F_STONITH_CALLDATA, data);
}
return reply;
}
bool fencing_peer_active(crm_node_t *peer)
{
if (peer == NULL) {
return FALSE;
} else if (peer->uname == NULL) {
return FALSE;
} else if (is_set(peer->processes, crm_get_cluster_proc())) {
return TRUE;
}
return FALSE;
}
/*!
* \internal
* \brief Determine if we need to use an alternate node to
* fence the target. If so return that node's uname
*
* \retval NULL, no alternate host
* \retval uname, uname of alternate host to use
*/
static const char *
check_alternate_host(const char *target)
{
const char *alternate_host = NULL;
crm_trace("Checking if we (%s) can fence %s", stonith_our_uname, target);
if (find_topology_for_host(target) && safe_str_eq(target, stonith_our_uname)) {
GHashTableIter gIter;
crm_node_t *entry = NULL;
g_hash_table_iter_init(&gIter, crm_peer_cache);
while (g_hash_table_iter_next(&gIter, NULL, (void **)&entry)) {
crm_trace("Checking for %s.%d != %s", entry->uname, entry->id, target);
if (fencing_peer_active(entry)
&& safe_str_neq(entry->uname, target)) {
alternate_host = entry->uname;
break;
}
}
if (alternate_host == NULL) {
crm_err("No alternate host available to handle complex self fencing request");
g_hash_table_iter_init(&gIter, crm_peer_cache);
while (g_hash_table_iter_next(&gIter, NULL, (void **)&entry)) {
crm_notice("Peer[%d] %s", entry->id, entry->uname);
}
}
}
return alternate_host;
}
static void
stonith_send_reply(xmlNode * reply, int call_options, const char *remote_peer,
const char *client_id)
{
if (remote_peer) {
send_cluster_message(crm_get_peer(0, remote_peer), crm_msg_stonith_ng, reply, FALSE);
} else {
do_local_reply(reply, client_id, is_set(call_options, st_opt_sync_call), remote_peer != NULL);
}
}
static int
handle_request(crm_client_t * client, uint32_t id, uint32_t flags, xmlNode * request,
const char *remote_peer)
{
int call_options = 0;
int rc = -EOPNOTSUPP;
xmlNode *data = NULL;
xmlNode *reply = NULL;
char *output = NULL;
const char *op = crm_element_value(request, F_STONITH_OPERATION);
const char *client_id = crm_element_value(request, F_STONITH_CLIENTID);
crm_element_value_int(request, F_STONITH_CALLOPTS, &call_options);
if (is_set(call_options, st_opt_sync_call)) {
CRM_ASSERT(client == NULL || client->request_id == id);
}
if (crm_str_eq(op, CRM_OP_REGISTER, TRUE)) {
xmlNode *reply = create_xml_node(NULL, "reply");
CRM_ASSERT(client);
crm_xml_add(reply, F_STONITH_OPERATION, CRM_OP_REGISTER);
crm_xml_add(reply, F_STONITH_CLIENTID, client->id);
crm_ipcs_send(client, id, reply, flags);
client->request_id = 0;
free_xml(reply);
return 0;
} else if (crm_str_eq(op, STONITH_OP_EXEC, TRUE)) {
rc = stonith_device_action(request, &output);
} else if (crm_str_eq(op, STONITH_OP_TIMEOUT_UPDATE, TRUE)) {
const char *call_id = crm_element_value(request, F_STONITH_CALLID);
const char *client_id = crm_element_value(request, F_STONITH_CLIENTID);
int op_timeout = 0;
crm_element_value_int(request, F_STONITH_TIMEOUT, &op_timeout);
do_stonith_async_timeout_update(client_id, call_id, op_timeout);
return 0;
} else if (crm_str_eq(op, STONITH_OP_QUERY, TRUE)) {
if (remote_peer) {
create_remote_stonith_op(client_id, request, TRUE); /* Record it for the future notification */
}
stonith_query(request, remote_peer, client_id, call_options);
return 0;
} else if (crm_str_eq(op, T_STONITH_NOTIFY, TRUE)) {
const char *flag_name = NULL;
CRM_ASSERT(client);
flag_name = crm_element_value(request, F_STONITH_NOTIFY_ACTIVATE);
if (flag_name) {
crm_debug("Setting %s callbacks for %s (%s): ON", flag_name, client->name, client->id);
client->options |= get_stonith_flag(flag_name);
}
flag_name = crm_element_value(request, F_STONITH_NOTIFY_DEACTIVATE);
if (flag_name) {
crm_debug("Setting %s callbacks for %s (%s): off", flag_name, client->name, client->id);
client->options |= get_stonith_flag(flag_name);
}
if (flags & crm_ipc_client_response) {
crm_ipcs_send_ack(client, id, flags, "ack", __FUNCTION__, __LINE__);
}
return 0;
} else if (crm_str_eq(op, STONITH_OP_RELAY, TRUE)) {
xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, request, LOG_TRACE);
crm_notice("Peer %s has received a forwarded fencing request from %s to fence (%s) peer %s",
stonith_our_uname,
client ? client->name : remote_peer,
crm_element_value(dev, F_STONITH_ACTION),
crm_element_value(dev, F_STONITH_TARGET));
if (initiate_remote_stonith_op(NULL, request, FALSE) != NULL) {
rc = -EINPROGRESS;
}
} else if (crm_str_eq(op, STONITH_OP_FENCE, TRUE)) {
if (remote_peer || stand_alone) {
rc = stonith_fence(request);
} else if (call_options & st_opt_manual_ack) {
remote_fencing_op_t *rop = NULL;
xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, request, LOG_TRACE);
const char *target = crm_element_value(dev, F_STONITH_TARGET);
crm_notice("Received manual confirmation that %s is fenced", target);
rop = initiate_remote_stonith_op(client, request, TRUE);
rc = stonith_manual_ack(request, rop);
} else {
const char *alternate_host = NULL;
xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, request, LOG_TRACE);
const char *target = crm_element_value(dev, F_STONITH_TARGET);
const char *action = crm_element_value(dev, F_STONITH_ACTION);
const char *device = crm_element_value(dev, F_STONITH_DEVICE);
if (client) {
int tolerance = 0;
crm_notice("Client %s.%.8s wants to fence (%s) '%s' with device '%s'",
client->name, client->id, action, target, device ? device : "(any)");
crm_element_value_int(dev, F_STONITH_TOLERANCE, &tolerance);
if (stonith_check_fence_tolerance(tolerance, target, action)) {
rc = 0;
goto done;
}
} else {
crm_notice("Peer %s wants to fence (%s) '%s' with device '%s'",
remote_peer, action, target, device ? device : "(any)");
}
alternate_host = check_alternate_host(target);
if (alternate_host && client) {
const char *client_id = NULL;
crm_notice("Forwarding complex self fencing request to peer %s", alternate_host);
if (client->id) {
client_id = client->id;
} else {
client_id = crm_element_value(request, F_STONITH_CLIENTID);
}
/* Create a record of it, otherwise call_id will be 0 if we need to notify of failures */
create_remote_stonith_op(client_id, request, FALSE);
crm_xml_add(request, F_STONITH_OPERATION, STONITH_OP_RELAY);
crm_xml_add(request, F_STONITH_CLIENTID, client->id);
send_cluster_message(crm_get_peer(0, alternate_host), crm_msg_stonith_ng, request,
FALSE);
rc = -EINPROGRESS;
} else if (initiate_remote_stonith_op(client, request, FALSE) != NULL) {
rc = -EINPROGRESS;
}
}
} else if (crm_str_eq(op, STONITH_OP_FENCE_HISTORY, TRUE)) {
rc = stonith_fence_history(request, &data);
} else if (crm_str_eq(op, STONITH_OP_DEVICE_ADD, TRUE)) {
const char *id = NULL;
rc = stonith_device_register(request, &id, FALSE);
do_stonith_notify_device(call_options, op, rc, id);
} else if (crm_str_eq(op, STONITH_OP_DEVICE_DEL, TRUE)) {
xmlNode *dev = get_xpath_object("//" F_STONITH_DEVICE, request, LOG_ERR);
const char *id = crm_element_value(dev, XML_ATTR_ID);
rc = stonith_device_remove(id, FALSE);
do_stonith_notify_device(call_options, op, rc, id);
} else if (crm_str_eq(op, STONITH_OP_LEVEL_ADD, TRUE)) {
char *id = NULL;
rc = stonith_level_register(request, &id);
do_stonith_notify_level(call_options, op, rc, id);
free(id);
} else if (crm_str_eq(op, STONITH_OP_LEVEL_DEL, TRUE)) {
char *id = NULL;
rc = stonith_level_remove(request, &id);
do_stonith_notify_level(call_options, op, rc, id);
} else if (crm_str_eq(op, STONITH_OP_CONFIRM, TRUE)) {
async_command_t *cmd = create_async_command(request);
xmlNode *reply = stonith_construct_async_reply(cmd, NULL, NULL, 0);
crm_xml_add(reply, F_STONITH_OPERATION, T_STONITH_NOTIFY);
crm_notice("Broadcasting manual fencing confirmation for node %s", cmd->victim);
send_cluster_message(NULL, crm_msg_stonith_ng, reply, FALSE);
free_async_command(cmd);
free_xml(reply);
} else if(safe_str_eq(op, CRM_OP_RM_NODE_CACHE)) {
int id = 0;
const char *name = NULL;
crm_element_value_int(request, XML_ATTR_ID, &id);
name = crm_element_value(request, XML_ATTR_UNAME);
reap_crm_member(id, name);
return pcmk_ok;
} else {
crm_err("Unknown %s from %s", op, client ? client->name : remote_peer);
crm_log_xml_warn(request, "UnknownOp");
}
done:
/* Always reply unless the request is in process still.
* If in progress, a reply will happen async after the request
* processing is finished */
if (rc != -EINPROGRESS) {
crm_trace("Reply handling: %p %u %u %d %d %s", client, client?client->request_id:0,
id, is_set(call_options, st_opt_sync_call), call_options,
crm_element_value(request, F_STONITH_CALLOPTS));
if (is_set(call_options, st_opt_sync_call)) {
CRM_ASSERT(client == NULL || client->request_id == id);
}
reply = stonith_construct_reply(request, output, data, rc);
stonith_send_reply(reply, call_options, remote_peer, client_id);
}
free(output);
free_xml(data);
free_xml(reply);
return rc;
}
static void
handle_reply(crm_client_t * client, xmlNode * request, const char *remote_peer)
{
const char *op = crm_element_value(request, F_STONITH_OPERATION);
if (crm_str_eq(op, STONITH_OP_QUERY, TRUE)) {
process_remote_stonith_query(request);
} else if (crm_str_eq(op, T_STONITH_NOTIFY, TRUE)) {
process_remote_stonith_exec(request);
} else if (crm_str_eq(op, STONITH_OP_FENCE, TRUE)) {
/* Reply to a complex fencing op */
process_remote_stonith_exec(request);
} else {
crm_err("Unknown %s reply from %s", op, client ? client->name : remote_peer);
crm_log_xml_warn(request, "UnknownOp");
}
}
void
stonith_command(crm_client_t * client, uint32_t id, uint32_t flags, xmlNode * request,
const char *remote_peer)
{
int call_options = 0;
int rc = 0;
gboolean is_reply = FALSE;
/* Copy op for reporting. The original might get freed by handle_reply()
* before we use it in crm_debug():
* handle_reply()
* |- process_remote_stonith_exec()
* |-- remote_op_done()
* |--- handle_local_reply_and_notify()
* |---- crm_xml_add(...F_STONITH_OPERATION...)
* |--- free_xml(op->request)
*/
char *op = crm_element_value_copy(request, F_STONITH_OPERATION);
if (get_xpath_object("//" T_STONITH_REPLY, request, LOG_DEBUG_3)) {
is_reply = TRUE;
}
crm_element_value_int(request, F_STONITH_CALLOPTS, &call_options);
crm_debug("Processing %s%s %u from %s (%16x)", op, is_reply ? " reply" : "",
id, client ? client->name : remote_peer, call_options);
if (is_set(call_options, st_opt_sync_call)) {
CRM_ASSERT(client == NULL || client->request_id == id);
}
if (is_reply) {
handle_reply(client, request, remote_peer);
} else {
rc = handle_request(client, id, flags, request, remote_peer);
}
crm_debug("Processed %s%s from %s: %s (%d)", op,
is_reply ? " reply" : "", client ? client->name : remote_peer,
rc > 0 ? "" : pcmk_strerror(rc), rc);
free(op);
}
diff --git a/fencing/main.c b/fencing/main.c
index 8d30f6f547..95a1408192 100644
--- a/fencing/main.c
+++ b/fencing/main.c
@@ -1,1546 +1,1546 @@
/*
* Copyright (C) 2009 Andrew Beekhof <andrew@beekhof.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/ipc.h>
#include <crm/common/ipcs.h>
#include <crm/cluster/internal.h>
#include <crm/stonith-ng.h>
#include <crm/fencing/internal.h>
#include <crm/common/xml.h>
#include <crm/common/mainloop.h>
#include <crm/cib/internal.h>
#include <crm/pengine/status.h>
#include <allocate.h>
#include <internal.h>
#include <standalone_config.h>
char *stonith_our_uname = NULL;
char *stonith_our_uuid = NULL;
long stonith_watchdog_timeout_ms = 0;
GMainLoop *mainloop = NULL;
gboolean stand_alone = FALSE;
gboolean no_cib_connect = FALSE;
gboolean stonith_shutdown_flag = FALSE;
qb_ipcs_service_t *ipcs = NULL;
xmlNode *local_cib = NULL;
GHashTable *known_peer_names = NULL;
static cib_t *cib_api = NULL;
static void *cib_library = NULL;
static void stonith_shutdown(int nsig);
static void stonith_cleanup(void);
static int32_t
st_ipc_accept(qb_ipcs_connection_t * c, uid_t uid, gid_t gid)
{
if (stonith_shutdown_flag) {
crm_info("Ignoring new client [%d] during shutdown", crm_ipcs_client_pid(c));
return -EPERM;
}
if (crm_client_new(c, uid, gid) == NULL) {
return -EIO;
}
return 0;
}
static void
st_ipc_created(qb_ipcs_connection_t * c)
{
crm_trace("Connection created for %p", c);
}
/* Exit code means? */
static int32_t
st_ipc_dispatch(qb_ipcs_connection_t * qbc, void *data, size_t size)
{
uint32_t id = 0;
uint32_t flags = 0;
int call_options = 0;
xmlNode *request = NULL;
crm_client_t *c = crm_client_get(qbc);
const char *op = NULL;
if (c == NULL) {
crm_info("Invalid client: %p", qbc);
return 0;
}
request = crm_ipcs_recv(c, data, size, &id, &flags);
if (request == NULL) {
crm_ipcs_send_ack(c, id, flags, "nack", __FUNCTION__, __LINE__);
return 0;
}
op = crm_element_value(request, F_CRM_TASK);
if(safe_str_eq(op, CRM_OP_RM_NODE_CACHE)) {
crm_xml_add(request, F_TYPE, T_STONITH_NG);
crm_xml_add(request, F_STONITH_OPERATION, op);
crm_xml_add(request, F_STONITH_CLIENTID, c->id);
crm_xml_add(request, F_STONITH_CLIENTNAME, crm_client_name(c));
crm_xml_add(request, F_STONITH_CLIENTNODE, stonith_our_uname);
send_cluster_message(NULL, crm_msg_stonith_ng, request, FALSE);
free_xml(request);
return 0;
}
if (c->name == NULL) {
const char *value = crm_element_value(request, F_STONITH_CLIENTNAME);
if (value == NULL) {
value = "unknown";
}
c->name = crm_strdup_printf("%s.%u", value, c->pid);
}
crm_element_value_int(request, F_STONITH_CALLOPTS, &call_options);
crm_trace("Flags %u/%u for command %u from %s", flags, call_options, id, crm_client_name(c));
if (is_set(call_options, st_opt_sync_call)) {
CRM_ASSERT(flags & crm_ipc_client_response);
CRM_LOG_ASSERT(c->request_id == 0); /* This means the client has two synchronous events in-flight */
c->request_id = id; /* Reply only to the last one */
}
crm_xml_add(request, F_STONITH_CLIENTID, c->id);
crm_xml_add(request, F_STONITH_CLIENTNAME, crm_client_name(c));
crm_xml_add(request, F_STONITH_CLIENTNODE, stonith_our_uname);
crm_log_xml_trace(request, "Client[inbound]");
stonith_command(c, id, flags, request, NULL);
free_xml(request);
return 0;
}
/* Error code means? */
static int32_t
st_ipc_closed(qb_ipcs_connection_t * c)
{
crm_client_t *client = crm_client_get(c);
if (client == NULL) {
return 0;
}
crm_trace("Connection %p closed", c);
crm_client_destroy(client);
/* 0 means: yes, go ahead and destroy the connection */
return 0;
}
static void
st_ipc_destroy(qb_ipcs_connection_t * c)
{
crm_trace("Connection %p destroyed", c);
st_ipc_closed(c);
}
static void
stonith_peer_callback(xmlNode * msg, void *private_data)
{
const char *remote_peer = crm_element_value(msg, F_ORIG);
const char *op = crm_element_value(msg, F_STONITH_OPERATION);
if (crm_str_eq(op, "poke", TRUE)) {
return;
}
crm_log_xml_trace(msg, "Peer[inbound]");
stonith_command(NULL, 0, 0, msg, remote_peer);
}
#if SUPPORT_HEARTBEAT
static void
stonith_peer_hb_callback(HA_Message * msg, void *private_data)
{
xmlNode *xml = convert_ha_message(NULL, msg, __FUNCTION__);
stonith_peer_callback(xml, private_data);
free_xml(xml);
}
static void
stonith_peer_hb_destroy(gpointer user_data)
{
if (stonith_shutdown_flag) {
crm_info("Heartbeat disconnection complete... exiting");
} else {
crm_err("Heartbeat connection lost! Exiting.");
}
stonith_shutdown(0);
}
#endif
#if SUPPORT_COROSYNC
static void
stonith_peer_ais_callback(cpg_handle_t handle,
const struct cpg_name *groupName,
uint32_t nodeid, uint32_t pid, void *msg, size_t msg_len)
{
uint32_t kind = 0;
xmlNode *xml = NULL;
const char *from = NULL;
char *data = pcmk_message_common_cs(handle, nodeid, pid, msg, &kind, &from);
if(data == NULL) {
return;
}
if (kind == crm_class_cluster) {
xml = string2xml(data);
if (xml == NULL) {
crm_err("Invalid XML: '%.120s'", data);
free(data);
return;
}
crm_xml_add(xml, F_ORIG, from);
/* crm_xml_add_int(xml, F_SEQ, wrapper->id); */
stonith_peer_callback(xml, NULL);
}
free_xml(xml);
free(data);
return;
}
static void
stonith_peer_cs_destroy(gpointer user_data)
{
crm_err("Corosync connection terminated");
stonith_shutdown(0);
}
#endif
void
do_local_reply(xmlNode * notify_src, const char *client_id, gboolean sync_reply, gboolean from_peer)
{
/* send callback to originating child */
crm_client_t *client_obj = NULL;
int local_rc = pcmk_ok;
crm_trace("Sending response");
client_obj = crm_client_get_by_id(client_id);
crm_trace("Sending callback to request originator");
if (client_obj == NULL) {
local_rc = -1;
crm_trace("No client to sent the response to. F_STONITH_CLIENTID not set.");
} else {
int rid = 0;
if (sync_reply) {
CRM_LOG_ASSERT(client_obj->request_id);
rid = client_obj->request_id;
client_obj->request_id = 0;
crm_trace("Sending response %d to %s %s",
rid, client_obj->name, from_peer ? "(originator of delegated request)" : "");
} else {
crm_trace("Sending an event to %s %s",
client_obj->name, from_peer ? "(originator of delegated request)" : "");
}
local_rc = crm_ipcs_send(client_obj, rid, notify_src, sync_reply?crm_ipc_flags_none:crm_ipc_server_event);
}
if (local_rc < pcmk_ok && client_obj != NULL) {
crm_warn("%sSync reply to %s failed: %s",
sync_reply ? "" : "A-",
client_obj ? client_obj->name : "<unknown>", pcmk_strerror(local_rc));
}
}
long long
get_stonith_flag(const char *name)
{
if (safe_str_eq(name, T_STONITH_NOTIFY_FENCE)) {
return 0x01;
} else if (safe_str_eq(name, STONITH_OP_DEVICE_ADD)) {
return 0x04;
} else if (safe_str_eq(name, STONITH_OP_DEVICE_DEL)) {
return 0x10;
}
return 0;
}
static void
stonith_notify_client(gpointer key, gpointer value, gpointer user_data)
{
xmlNode *update_msg = user_data;
crm_client_t *client = value;
const char *type = NULL;
CRM_CHECK(client != NULL, return);
CRM_CHECK(update_msg != NULL, return);
type = crm_element_value(update_msg, F_SUBTYPE);
CRM_CHECK(type != NULL, crm_log_xml_err(update_msg, "notify"); return);
if (client->ipcs == NULL) {
crm_trace("Skipping client with NULL channel");
return;
}
if (client->options & get_stonith_flag(type)) {
int rc = crm_ipcs_send(client, 0, update_msg, crm_ipc_server_event | crm_ipc_server_error);
if (rc <= 0) {
crm_warn("%s notification of client %s.%.6s failed: %s (%d)",
type, crm_client_name(client), client->id, pcmk_strerror(rc), rc);
} else {
crm_trace("Sent %s notification to client %s.%.6s", type, crm_client_name(client),
client->id);
}
}
}
void
do_stonith_async_timeout_update(const char *client_id, const char *call_id, int timeout)
{
crm_client_t *client = NULL;
xmlNode *notify_data = NULL;
if (!timeout || !call_id || !client_id) {
return;
}
client = crm_client_get_by_id(client_id);
if (!client) {
return;
}
notify_data = create_xml_node(NULL, T_STONITH_TIMEOUT_VALUE);
crm_xml_add(notify_data, F_TYPE, T_STONITH_TIMEOUT_VALUE);
crm_xml_add(notify_data, F_STONITH_CALLID, call_id);
crm_xml_add_int(notify_data, F_STONITH_TIMEOUT, timeout);
crm_trace("timeout update is %d for client %s and call id %s", timeout, client_id, call_id);
if (client) {
crm_ipcs_send(client, 0, notify_data, crm_ipc_server_event);
}
free_xml(notify_data);
}
void
do_stonith_notify(int options, const char *type, int result, xmlNode * data)
{
/* TODO: Standardize the contents of data */
xmlNode *update_msg = create_xml_node(NULL, "notify");
CRM_CHECK(type != NULL,;);
crm_xml_add(update_msg, F_TYPE, T_STONITH_NOTIFY);
crm_xml_add(update_msg, F_SUBTYPE, type);
crm_xml_add(update_msg, F_STONITH_OPERATION, type);
crm_xml_add_int(update_msg, F_STONITH_RC, result);
if (data != NULL) {
add_message_xml(update_msg, F_STONITH_CALLDATA, data);
}
crm_trace("Notifying clients");
g_hash_table_foreach(client_connections, stonith_notify_client, update_msg);
free_xml(update_msg);
crm_trace("Notify complete");
}
static void
do_stonith_notify_config(int options, const char *op, int rc,
const char *desc, int active)
{
xmlNode *notify_data = create_xml_node(NULL, op);
CRM_CHECK(notify_data != NULL, return);
crm_xml_add(notify_data, F_STONITH_DEVICE, desc);
crm_xml_add_int(notify_data, F_STONITH_ACTIVE, active);
do_stonith_notify(options, op, rc, notify_data);
free_xml(notify_data);
}
void
do_stonith_notify_device(int options, const char *op, int rc, const char *desc)
{
do_stonith_notify_config(options, op, rc, desc, g_hash_table_size(device_list));
}
void
do_stonith_notify_level(int options, const char *op, int rc, const char *desc)
{
do_stonith_notify_config(options, op, rc, desc, g_hash_table_size(topology));
}
static void
topology_remove_helper(const char *node, int level)
{
int rc;
char *desc = NULL;
xmlNode *data = create_xml_node(NULL, XML_TAG_FENCING_LEVEL);
crm_xml_add(data, F_STONITH_ORIGIN, __FUNCTION__);
crm_xml_add_int(data, XML_ATTR_STONITH_INDEX, level);
crm_xml_add(data, XML_ATTR_STONITH_TARGET, node);
rc = stonith_level_remove(data, &desc);
do_stonith_notify_level(0, STONITH_OP_LEVEL_DEL, rc, desc);
free_xml(data);
free(desc);
}
static void
remove_cib_device(xmlXPathObjectPtr xpathObj)
{
int max = numXpathResults(xpathObj), lpc = 0;
for (lpc = 0; lpc < max; lpc++) {
const char *rsc_id = NULL;
const char *standard = NULL;
xmlNode *match = getXpathResult(xpathObj, lpc);
CRM_LOG_ASSERT(match != NULL);
if(match != NULL) {
standard = crm_element_value(match, XML_AGENT_ATTR_CLASS);
}
if (safe_str_neq(standard, "stonith")) {
continue;
}
rsc_id = crm_element_value(match, XML_ATTR_ID);
stonith_device_remove(rsc_id, TRUE);
}
}
static void
handle_topology_change(xmlNode *match, bool remove)
{
int rc;
char *desc = NULL;
CRM_CHECK(match != NULL, return);
crm_trace("Updating %s", ID(match));
if(remove) {
int index = 0;
char *key = stonith_level_key(match, -1);
crm_element_value_int(match, XML_ATTR_STONITH_INDEX, &index);
topology_remove_helper(key, index);
free(key);
}
rc = stonith_level_register(match, &desc);
do_stonith_notify_level(0, STONITH_OP_LEVEL_ADD, rc, desc);
free(desc);
}
static void
remove_fencing_topology(xmlXPathObjectPtr xpathObj)
{
int max = numXpathResults(xpathObj), lpc = 0;
for (lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpathObj, lpc);
CRM_LOG_ASSERT(match != NULL);
if (match && crm_element_value(match, XML_DIFF_MARKER)) {
/* Deletion */
int index = 0;
char *target = stonith_level_key(match, -1);
crm_element_value_int(match, XML_ATTR_STONITH_INDEX, &index);
if (target == NULL) {
crm_err("Invalid fencing target in element %s", ID(match));
} else if (index <= 0) {
crm_err("Invalid level for %s in element %s", target, ID(match));
} else {
topology_remove_helper(target, index);
}
/* } else { Deal with modifications during the 'addition' stage */
}
}
}
static void
register_fencing_topology(xmlXPathObjectPtr xpathObj)
{
int max = numXpathResults(xpathObj), lpc = 0;
for (lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpathObj, lpc);
handle_topology_change(match, TRUE);
}
}
/* Fencing
<diff crm_feature_set="3.0.6">
<diff-removed>
<fencing-topology>
<fencing-level id="f-p1.1" target="pcmk-1" index="1" devices="poison-pill" __crm_diff_marker__="removed:top"/>
<fencing-level id="f-p1.2" target="pcmk-1" index="2" devices="power" __crm_diff_marker__="removed:top"/>
<fencing-level devices="disk,network" id="f-p2.1"/>
</fencing-topology>
</diff-removed>
<diff-added>
<fencing-topology>
<fencing-level id="f-p.1" target="pcmk-1" index="1" devices="poison-pill" __crm_diff_marker__="added:top"/>
<fencing-level id="f-p2.1" target="pcmk-2" index="1" devices="disk,something"/>
<fencing-level id="f-p3.1" target="pcmk-2" index="2" devices="power" __crm_diff_marker__="added:top"/>
</fencing-topology>
</diff-added>
</diff>
*/
static void
fencing_topology_init()
{
xmlXPathObjectPtr xpathObj = NULL;
const char *xpath = "//" XML_TAG_FENCING_LEVEL;
crm_trace("Full topology refresh");
if(topology) {
g_hash_table_destroy(topology);
topology = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, free_topology_entry);
}
/* Grab everything */
xpathObj = xpath_search(local_cib, xpath);
register_fencing_topology(xpathObj);
freeXpathObject(xpathObj);
}
#define rsc_name(x) x->clone_name?x->clone_name:x->id
/*!
* \internal
* \brief Check whether our uname is in a resource's allowed node list
*
* \param[in] rsc Resource to check
*
* \return Pointer to node object if found, NULL otherwise
*/
static node_t *
our_node_allowed_for(resource_t *rsc)
{
GHashTableIter iter;
node_t *node = NULL;
if (rsc && stonith_our_uname) {
g_hash_table_iter_init(&iter, rsc->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
if (node && strcmp(node->details->uname, stonith_our_uname) == 0) {
break;
}
node = NULL;
}
}
return node;
}
/*!
* \internal
* \brief If a resource or any of its children are STONITH devices, update their
* definitions given a cluster working set.
*
* \param[in] rsc Resource to check
* \param[in] data_set Cluster working set with device information
*/
static void cib_device_update(resource_t *rsc, pe_working_set_t *data_set)
{
node_t *node = NULL;
const char *value = NULL;
const char *rclass = NULL;
node_t *parent = NULL;
gboolean remove = TRUE;
/* If this is a complex resource, check children rather than this resource itself.
* TODO: Mark each installed device and remove if untouched when this process finishes.
*/
if(rsc->children) {
GListPtr gIter = NULL;
for (gIter = rsc->children; gIter != NULL; gIter = gIter->next) {
cib_device_update(gIter->data, data_set);
if(rsc->variant == pe_clone || rsc->variant == pe_master) {
crm_trace("Only processing one copy of the clone %s", rsc->id);
break;
}
}
return;
}
/* We only care about STONITH resources. */
rclass = crm_element_value(rsc->xml, XML_AGENT_ATTR_CLASS);
if(safe_str_neq(rclass, "stonith")) {
return;
}
/* If this STONITH resource is disabled, just remove it. */
value = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE);
if (safe_str_eq(value, RSC_STOPPED)) {
crm_info("Device %s has been disabled", rsc->id);
goto update_done;
}
/* Check whether our node is allowed for this resource (and its parent if in a group) */
node = our_node_allowed_for(rsc);
if (rsc->parent && (rsc->parent->variant == pe_group)) {
parent = our_node_allowed_for(rsc->parent);
}
if(node == NULL) {
/* Our node is disallowed, so remove the device */
GHashTableIter iter;
crm_info("Device %s has been disabled on %s: unknown", rsc->id, stonith_our_uname);
g_hash_table_iter_init(&iter, rsc->allowed_nodes);
while (g_hash_table_iter_next(&iter, NULL, (void **)&node)) {
crm_trace("Available: %s = %d", node->details->uname, node->weight);
}
goto update_done;
} else if(node->weight < 0 || (parent && parent->weight < 0)) {
/* Our node (or its group) is disallowed by score, so remove the device */
char *score = score2char((node->weight < 0) ? node->weight : parent->weight);
crm_info("Device %s has been disabled on %s: score=%s", rsc->id, stonith_our_uname, score);
free(score);
goto update_done;
} else {
/* Our node is allowed, so update the device information */
xmlNode *data;
GHashTableIter gIter;
stonith_key_value_t *params = NULL;
const char *name = NULL;
const char *agent = crm_element_value(rsc->xml, XML_EXPR_ATTR_TYPE);
const char *provider = crm_element_value(rsc->xml, XML_AGENT_ATTR_PROVIDER);
const char *rsc_provides = NULL;
crm_debug("Device %s is allowed on %s: score=%d", rsc->id, stonith_our_uname, node->weight);
get_rsc_attributes(rsc->parameters, rsc, node, data_set);
get_meta_attributes(rsc->meta, rsc, node, data_set);
rsc_provides = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_PROVIDES);
g_hash_table_iter_init(&gIter, rsc->parameters);
while (g_hash_table_iter_next(&gIter, (gpointer *) & name, (gpointer *) & value)) {
if (!name || !value) {
continue;
}
params = stonith_key_value_add(params, name, value);
crm_trace(" %s=%s", name, value);
}
remove = FALSE;
data = create_device_registration_xml(rsc_name(rsc), provider, agent, params, rsc_provides);
stonith_device_register(data, NULL, TRUE);
stonith_key_value_freeall(params, 1, 1);
free_xml(data);
}
update_done:
if(remove && g_hash_table_lookup(device_list, rsc_name(rsc))) {
stonith_device_remove(rsc_name(rsc), TRUE);
}
}
extern xmlNode *do_calculations(pe_working_set_t * data_set, xmlNode * xml_input, crm_time_t * now);
extern node_t *create_node(const char *id, const char *uname, const char *type, const char *score, pe_working_set_t * data_set);
/*!
* \internal
* \brief Update all STONITH device definitions based on current CIB
*/
static void
cib_devices_update(void)
{
GListPtr gIter = NULL;
pe_working_set_t data_set;
crm_info("Updating devices to version %s.%s.%s",
crm_element_value(local_cib, XML_ATTR_GENERATION_ADMIN),
crm_element_value(local_cib, XML_ATTR_GENERATION),
crm_element_value(local_cib, XML_ATTR_NUMUPDATES));
set_working_set_defaults(&data_set);
data_set.input = local_cib;
data_set.now = crm_time_new(NULL);
data_set.flags |= pe_flag_quick_location;
data_set.localhost = stonith_our_uname;
cluster_status(&data_set);
do_calculations(&data_set, NULL, NULL);
for (gIter = data_set.resources; gIter != NULL; gIter = gIter->next) {
cib_device_update(gIter->data, &data_set);
}
data_set.input = NULL; /* Wasn't a copy */
cleanup_alloc_calculations(&data_set);
}
static void
update_cib_stonith_devices_v2(const char *event, xmlNode * msg)
{
xmlNode *change = NULL;
char *reason = NULL;
bool needs_update = FALSE;
xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
for (change = __xml_first_child(patchset); change != NULL; change = __xml_next(change)) {
const char *op = crm_element_value(change, XML_DIFF_OP);
const char *xpath = crm_element_value(change, XML_DIFF_PATH);
const char *shortpath = NULL;
if(op == NULL || strcmp(op, "move") == 0) {
continue;
} else if(safe_str_eq(op, "delete") && strstr(xpath, XML_CIB_TAG_RESOURCE)) {
const char *rsc_id = NULL;
char *search = NULL;
char *mutable = NULL;
if (strstr(xpath, XML_TAG_ATTR_SETS)) {
needs_update = TRUE;
break;
}
mutable = strdup(xpath);
rsc_id = strstr(mutable, "primitive[@id=\'");
if (rsc_id != NULL) {
rsc_id += strlen("primitive[@id=\'");
search = strchr(rsc_id, '\'');
}
if (search != NULL) {
*search = 0;
stonith_device_remove(rsc_id, TRUE);
} else {
crm_warn("Ignoring malformed CIB update (resource deletion)");
}
free(mutable);
} else if(strstr(xpath, "/"XML_CIB_TAG_RESOURCES)) {
shortpath = strrchr(xpath, '/'); CRM_ASSERT(shortpath);
reason = crm_strdup_printf("%s %s", op, shortpath+1);
needs_update = TRUE;
break;
} else if(strstr(xpath, XML_CONS_TAG_RSC_LOCATION)) {
shortpath = strrchr(xpath, '/'); CRM_ASSERT(shortpath);
reason = crm_strdup_printf("%s %s", op, shortpath+1);
needs_update = TRUE;
break;
}
}
if(needs_update) {
crm_info("Updating device list from the cib: %s", reason);
cib_devices_update();
}
free(reason);
}
static void
update_cib_stonith_devices_v1(const char *event, xmlNode * msg)
{
const char *reason = "none";
gboolean needs_update = FALSE;
xmlXPathObjectPtr xpath_obj = NULL;
/* process new constraints */
xpath_obj = xpath_search(msg, "//" F_CIB_UPDATE_RESULT "//" XML_CONS_TAG_RSC_LOCATION);
if (numXpathResults(xpath_obj) > 0) {
int max = numXpathResults(xpath_obj), lpc = 0;
/* Safest and simplest to always recompute */
needs_update = TRUE;
reason = "new location constraint";
for (lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpath_obj, lpc);
crm_log_xml_trace(match, "new constraint");
}
}
freeXpathObject(xpath_obj);
/* process deletions */
xpath_obj = xpath_search(msg, "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_REMOVED "//" XML_CIB_TAG_RESOURCE);
if (numXpathResults(xpath_obj) > 0) {
remove_cib_device(xpath_obj);
}
freeXpathObject(xpath_obj);
/* process additions */
xpath_obj = xpath_search(msg, "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED "//" XML_CIB_TAG_RESOURCE);
if (numXpathResults(xpath_obj) > 0) {
int max = numXpathResults(xpath_obj), lpc = 0;
for (lpc = 0; lpc < max; lpc++) {
const char *rsc_id = NULL;
const char *standard = NULL;
xmlNode *match = getXpathResult(xpath_obj, lpc);
rsc_id = crm_element_value(match, XML_ATTR_ID);
standard = crm_element_value(match, XML_AGENT_ATTR_CLASS);
if (safe_str_neq(standard, "stonith")) {
continue;
}
crm_trace("Fencing resource %s was added or modified", rsc_id);
reason = "new resource";
needs_update = TRUE;
}
}
freeXpathObject(xpath_obj);
if(needs_update) {
crm_info("Updating device list from the cib: %s", reason);
cib_devices_update();
}
}
static void
update_cib_stonith_devices(const char *event, xmlNode * msg)
{
int format = 1;
xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
CRM_ASSERT(patchset);
crm_element_value_int(patchset, "format", &format);
switch(format) {
case 1:
update_cib_stonith_devices_v1(event, msg);
break;
case 2:
update_cib_stonith_devices_v2(event, msg);
break;
default:
crm_warn("Unknown patch format: %d", format);
}
}
/* Needs to hold node name + attribute name + attribute value + 75 */
#define XPATH_MAX 512
-/*
+/*!
* \internal
* \brief Check whether a node has a specific attribute name/value
*
* \param[in] node Name of node to check
* \param[in] name Name of an attribute to look for
* \param[in] value The value the named attribute needs to be set to in order to be considered a match
*
* \return TRUE if the locally cached CIB has the specified node attribute
*/
gboolean
node_has_attr(const char *node, const char *name, const char *value)
{
char xpath[XPATH_MAX];
xmlNode *match;
int n;
CRM_CHECK(local_cib != NULL, return FALSE);
/* Search for the node's attributes in the CIB. While the schema allows
* multiple sets of instance attributes, and allows instance attributes to
* use id-ref to reference values elsewhere, that is intended for resources,
* so we ignore that here.
*/
n = snprintf(xpath, XPATH_MAX, "//" XML_CIB_TAG_NODES
"/" XML_CIB_TAG_NODE "[@uname='%s']/" XML_TAG_ATTR_SETS
"/" XML_CIB_TAG_NVPAIR "[@name='%s' and @value='%s']",
node, name, value);
match = get_xpath_object(xpath, local_cib, LOG_TRACE);
CRM_CHECK(n < XPATH_MAX, return FALSE);
return (match != NULL);
}
static void
update_fencing_topology(const char *event, xmlNode * msg)
{
int format = 1;
const char *xpath;
xmlXPathObjectPtr xpathObj = NULL;
xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
CRM_ASSERT(patchset);
crm_element_value_int(patchset, "format", &format);
if(format == 1) {
/* Process deletions (only) */
xpath = "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_REMOVED "//" XML_TAG_FENCING_LEVEL;
xpathObj = xpath_search(msg, xpath);
remove_fencing_topology(xpathObj);
freeXpathObject(xpathObj);
/* Process additions and changes */
xpath = "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED "//" XML_TAG_FENCING_LEVEL;
xpathObj = xpath_search(msg, xpath);
register_fencing_topology(xpathObj);
freeXpathObject(xpathObj);
} else if(format == 2) {
xmlNode *change = NULL;
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
xml_patch_versions(patchset, add, del);
for (change = __xml_first_child(patchset); change != NULL; change = __xml_next(change)) {
const char *op = crm_element_value(change, XML_DIFF_OP);
const char *xpath = crm_element_value(change, XML_DIFF_PATH);
if(op == NULL) {
continue;
} else if(strstr(xpath, "/" XML_TAG_FENCING_LEVEL) != NULL) {
/* Change to a specific entry */
crm_trace("Handling %s operation %d.%d.%d for %s", op, add[0], add[1], add[2], xpath);
if(strcmp(op, "move") == 0) {
continue;
} else if(strcmp(op, "create") == 0) {
handle_topology_change(change->children, FALSE);
} else if(strcmp(op, "modify") == 0) {
xmlNode *match = first_named_child(change, XML_DIFF_RESULT);
if(match) {
handle_topology_change(match->children, TRUE);
}
} else if(strcmp(op, "delete") == 0) {
/* Nuclear option, all we have is the path and an id... not enough to remove a specific entry */
crm_info("Re-initializing fencing topology after %s operation %d.%d.%d for %s",
op, add[0], add[1], add[2], xpath);
fencing_topology_init();
return;
}
} else if (strstr(xpath, "/" XML_TAG_FENCING_TOPOLOGY) != NULL) {
/* Change to the topology in general */
crm_info("Re-initializing fencing topology after top-level %s operation %d.%d.%d for %s",
op, add[0], add[1], add[2], xpath);
fencing_topology_init();
return;
} else if (strstr(xpath, "/" XML_CIB_TAG_CONFIGURATION)) {
/* Changes to the whole config section, possibly including the topology as a whild */
if(first_named_child(change, XML_TAG_FENCING_TOPOLOGY) == NULL) {
crm_trace("Nothing for us in %s operation %d.%d.%d for %s.",
op, add[0], add[1], add[2], xpath);
} else if(strcmp(op, "delete") == 0 || strcmp(op, "create") == 0) {
crm_info("Re-initializing fencing topology after top-level %s operation %d.%d.%d for %s.",
op, add[0], add[1], add[2], xpath);
fencing_topology_init();
return;
}
} else {
crm_trace("Nothing for us in %s operation %d.%d.%d for %s",
op, add[0], add[1], add[2], xpath);
}
}
} else {
crm_warn("Unknown patch format: %d", format);
}
}
static bool have_cib_devices = FALSE;
static void
update_cib_cache_cb(const char *event, xmlNode * msg)
{
int rc = pcmk_ok;
xmlNode *stonith_enabled_xml = NULL;
xmlNode *stonith_watchdog_xml = NULL;
const char *stonith_enabled_s = NULL;
static gboolean stonith_enabled_saved = TRUE;
if(!have_cib_devices) {
crm_trace("Skipping updates until we get a full dump");
return;
} else if(msg == NULL) {
crm_trace("Missing %s update", event);
return;
}
/* Maintain a local copy of the CIB so that we have full access
* to device definitions, location constraints, and node attributes
*/
if (local_cib != NULL) {
int rc = pcmk_ok;
xmlNode *patchset = NULL;
crm_element_value_int(msg, F_CIB_RC, &rc);
if (rc != pcmk_ok) {
return;
}
patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT);
xml_log_patchset(LOG_TRACE, "Config update", patchset);
rc = xml_apply_patchset(local_cib, patchset, TRUE);
switch (rc) {
case pcmk_ok:
case -pcmk_err_old_data:
break;
case -pcmk_err_diff_resync:
case -pcmk_err_diff_failed:
crm_notice("[%s] Patch aborted: %s (%d)", event, pcmk_strerror(rc), rc);
free_xml(local_cib);
local_cib = NULL;
break;
default:
crm_warn("[%s] ABORTED: %s (%d)", event, pcmk_strerror(rc), rc);
free_xml(local_cib);
local_cib = NULL;
}
}
if (local_cib == NULL) {
crm_trace("Re-requesting the full cib");
rc = cib_api->cmds->query(cib_api, NULL, &local_cib, cib_scope_local | cib_sync_call);
if(rc != pcmk_ok) {
crm_err("Couldn't retrieve the CIB: %s (%d)", pcmk_strerror(rc), rc);
return;
}
CRM_ASSERT(local_cib != NULL);
stonith_enabled_saved = FALSE; /* Trigger a full refresh below */
}
stonith_enabled_xml = get_xpath_object("//nvpair[@name='stonith-enabled']", local_cib, LOG_TRACE);
if (stonith_enabled_xml) {
stonith_enabled_s = crm_element_value(stonith_enabled_xml, XML_NVPAIR_ATTR_VALUE);
}
if (stonith_enabled_s == NULL || crm_is_true(stonith_enabled_s)) {
long timeout_ms = 0;
const char *value = NULL;
stonith_watchdog_xml = get_xpath_object("//nvpair[@name='stonith-watchdog-timeout']", local_cib, LOG_TRACE);
if (stonith_watchdog_xml) {
value = crm_element_value(stonith_watchdog_xml, XML_NVPAIR_ATTR_VALUE);
}
if(value) {
timeout_ms = crm_get_msec(value);
}
if(timeout_ms != stonith_watchdog_timeout_ms) {
crm_notice("New watchdog timeout %lds (was %lds)", timeout_ms/1000, stonith_watchdog_timeout_ms/1000);
stonith_watchdog_timeout_ms = timeout_ms;
}
} else {
stonith_watchdog_timeout_ms = 0;
}
if (stonith_enabled_s && crm_is_true(stonith_enabled_s) == FALSE) {
crm_trace("Ignoring cib updates while stonith is disabled");
stonith_enabled_saved = FALSE;
return;
} else if (stonith_enabled_saved == FALSE) {
crm_info("Updating stonith device and topology lists now that stonith is enabled");
stonith_enabled_saved = TRUE;
fencing_topology_init();
cib_devices_update();
} else {
update_fencing_topology(event, msg);
update_cib_stonith_devices(event, msg);
}
}
static void
init_cib_cache_cb(xmlNode * msg, int call_id, int rc, xmlNode * output, void *user_data)
{
crm_info("Updating device list from the cib: init");
have_cib_devices = TRUE;
local_cib = copy_xml(output);
fencing_topology_init();
cib_devices_update();
}
static void
stonith_shutdown(int nsig)
{
stonith_shutdown_flag = TRUE;
crm_info("Terminating with %d clients", crm_hash_table_size(client_connections));
if (mainloop != NULL && g_main_is_running(mainloop)) {
g_main_quit(mainloop);
} else {
stonith_cleanup();
crm_exit(pcmk_ok);
}
}
static void
cib_connection_destroy(gpointer user_data)
{
if (stonith_shutdown_flag) {
crm_info("Connection to the CIB closed.");
return;
} else {
crm_notice("Connection to the CIB terminated. Shutting down.");
}
if (cib_api) {
cib_api->cmds->signoff(cib_api);
}
stonith_shutdown(0);
}
static void
stonith_cleanup(void)
{
if (cib_api) {
cib_api->cmds->signoff(cib_api);
}
if (ipcs) {
qb_ipcs_destroy(ipcs);
}
g_hash_table_destroy(known_peer_names);
known_peer_names = NULL;
crm_peer_destroy();
crm_client_cleanup();
free(stonith_our_uname);
free_xml(local_cib);
}
/* *INDENT-OFF* */
static struct crm_option long_options[] = {
{"stand-alone", 0, 0, 's'},
{"stand-alone-w-cpg", 0, 0, 'c'},
{"logfile", 1, 0, 'l'},
{"verbose", 0, 0, 'V'},
{"version", 0, 0, '$'},
{"help", 0, 0, '?'},
{0, 0, 0, 0}
};
/* *INDENT-ON* */
static void
setup_cib(void)
{
int rc, retries = 0;
static cib_t *(*cib_new_fn) (void) = NULL;
if (cib_new_fn == NULL) {
cib_new_fn = find_library_function(&cib_library, CIB_LIBRARY, "cib_new", TRUE);
}
if (cib_new_fn != NULL) {
cib_api = (*cib_new_fn) ();
}
if (cib_api == NULL) {
crm_err("No connection to the CIB");
return;
}
do {
sleep(retries);
rc = cib_api->cmds->signon(cib_api, CRM_SYSTEM_STONITHD, cib_command);
} while (rc == -ENOTCONN && ++retries < 5);
if (rc != pcmk_ok) {
crm_err("Could not connect to the CIB service: %s (%d)", pcmk_strerror(rc), rc);
} else if (pcmk_ok !=
cib_api->cmds->add_notify_callback(cib_api, T_CIB_DIFF_NOTIFY, update_cib_cache_cb)) {
crm_err("Could not set CIB notification callback");
} else {
rc = cib_api->cmds->query(cib_api, NULL, NULL, cib_scope_local);
cib_api->cmds->register_callback(cib_api, rc, 120, FALSE, NULL, "init_cib_cache_cb",
init_cib_cache_cb);
cib_api->cmds->set_connection_dnotify(cib_api, cib_connection_destroy);
crm_info("Watching for stonith topology changes");
}
}
struct qb_ipcs_service_handlers ipc_callbacks = {
.connection_accept = st_ipc_accept,
.connection_created = st_ipc_created,
.msg_process = st_ipc_dispatch,
.connection_closed = st_ipc_closed,
.connection_destroyed = st_ipc_destroy
};
/*!
* \internal
* \brief Callback for peer status changes
*
* \param[in] type What changed
* \param[in] node What peer had the change
* \param[in] data Previous value of what changed
*/
static void
st_peer_update_callback(enum crm_status_type type, crm_node_t * node, const void *data)
{
if ((type != crm_status_processes) && !is_set(node->flags, crm_remote_node)) {
xmlNode *query = NULL;
if (node->id && node->uname) {
g_hash_table_insert(known_peer_names, GUINT_TO_POINTER(node->id), strdup(node->uname));
}
/*
* This is a hack until we can send to a nodeid and/or we fix node name lookups
* These messages are ignored in stonith_peer_callback()
*/
query = create_xml_node(NULL, "stonith_command");
crm_xml_add(query, F_XML_TAGNAME, "stonith_command");
crm_xml_add(query, F_TYPE, T_STONITH_NG);
crm_xml_add(query, F_STONITH_OPERATION, "poke");
crm_debug("Broadcasting our uname because of node %u", node->id);
send_cluster_message(NULL, crm_msg_stonith_ng, query, FALSE);
free_xml(query);
}
}
int
main(int argc, char **argv)
{
int flag;
int rc = 0;
int lpc = 0;
int argerr = 0;
int option_index = 0;
crm_cluster_t cluster;
const char *actions[] = { "reboot", "off", "list", "monitor", "status" };
crm_log_preinit("stonith-ng", argc, argv);
crm_set_options(NULL, "mode [options]", long_options,
"Provides a summary of cluster's current state."
"\n\nOutputs varying levels of detail in a number of different formats.\n");
while (1) {
flag = crm_get_option(argc, argv, &option_index);
if (flag == -1) {
break;
}
switch (flag) {
case 'V':
crm_bump_log_level(argc, argv);
break;
case 'l':
crm_add_logfile(optarg);
break;
case 's':
stand_alone = TRUE;
break;
case 'c':
stand_alone = FALSE;
no_cib_connect = TRUE;
break;
case '$':
case '?':
crm_help(flag, EX_OK);
break;
default:
++argerr;
break;
}
}
if (argc - optind == 1 && safe_str_eq("metadata", argv[optind])) {
printf("<?xml version=\"1.0\"?><!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n");
printf("<resource-agent name=\"stonithd\">\n");
printf(" <version>1.0</version>\n");
printf
(" <longdesc lang=\"en\">This is a fake resource that details the instance attributes handled by stonithd.</longdesc>\n");
printf(" <shortdesc lang=\"en\">Options available for all stonith resources</shortdesc>\n");
printf(" <parameters>\n");
printf(" <parameter name=\"priority\" unique=\"0\">\n");
printf
(" <shortdesc lang=\"en\">The priority of the stonith resource. Devices are tried in order of highest priority to lowest.</shortdesc>\n");
printf(" <content type=\"integer\" default=\"0\"/>\n");
printf(" </parameter>\n");
printf(" <parameter name=\"%s\" unique=\"0\">\n", STONITH_ATTR_HOSTARG);
printf
(" <shortdesc lang=\"en\">Advanced use only: An alternate parameter to supply instead of 'port'</shortdesc>\n");
printf
(" <longdesc lang=\"en\">Some devices do not support the standard 'port' parameter or may provide additional ones.\n"
"Use this to specify an alternate, device-specific, parameter that should indicate the machine to be fenced.\n"
"A value of 'none' can be used to tell the cluster not to supply any additional parameters.\n"
" </longdesc>\n");
printf(" <content type=\"string\" default=\"port\"/>\n");
printf(" </parameter>\n");
printf(" <parameter name=\"%s\" unique=\"0\">\n", STONITH_ATTR_HOSTMAP);
printf
(" <shortdesc lang=\"en\">A mapping of host names to ports numbers for devices that do not support host names.</shortdesc>\n");
printf
(" <longdesc lang=\"en\">Eg. node1:1;node2:2,3 would tell the cluster to use port 1 for node1 and ports 2 and 3 for node2</longdesc>\n");
printf(" <content type=\"string\" default=\"\"/>\n");
printf(" </parameter>\n");
printf(" <parameter name=\"%s\" unique=\"0\">\n", STONITH_ATTR_HOSTLIST);
printf
(" <shortdesc lang=\"en\">A list of machines controlled by this device (Optional unless %s=static-list).</shortdesc>\n",
STONITH_ATTR_HOSTCHECK);
printf(" <content type=\"string\" default=\"\"/>\n");
printf(" </parameter>\n");
printf(" <parameter name=\"%s\" unique=\"0\">\n", STONITH_ATTR_HOSTCHECK);
printf
(" <shortdesc lang=\"en\">How to determine which machines are controlled by the device.</shortdesc>\n");
printf
(" <longdesc lang=\"en\">Allowed values: dynamic-list (query the device), static-list (check the %s attribute), none (assume every device can fence every machine)</longdesc>\n",
STONITH_ATTR_HOSTLIST);
printf(" <content type=\"string\" default=\"dynamic-list\"/>\n");
printf(" </parameter>\n");
printf(" <parameter name=\"%s\" unique=\"0\">\n", STONITH_ATTR_DELAY_MAX);
printf
(" <shortdesc lang=\"en\">Enable random delay for stonith actions and specify the maximum of random delay</shortdesc>\n");
printf
(" <longdesc lang=\"en\">This prevents double fencing when using slow devices such as sbd.\n"
"Use this to enable random delay for stonith actions and specify the maximum of random delay.</longdesc>\n");
printf(" <content type=\"time\" default=\"0s\"/>\n");
printf(" </parameter>\n");
printf(" <parameter name=\"%s\" unique=\"0\">\n", STONITH_ATTR_ACTION_LIMIT);
printf
(" <shortdesc lang=\"en\">The maximum number of actions can be performed in parallel on this device</shortdesc>\n");
printf
(" <longdesc lang=\"en\">Pengine property concurrent-fencing=true needs to be configured first.\n"
"Then use this to specify the maximum number of actions can be performed in parallel on this device. -1 is unlimited.</longdesc>\n");
printf(" <content type=\"integer\" default=\"1\"/>\n");
printf(" </parameter>\n");
for (lpc = 0; lpc < DIMOF(actions); lpc++) {
printf(" <parameter name=\"pcmk_%s_action\" unique=\"0\">\n", actions[lpc]);
printf
(" <shortdesc lang=\"en\">Advanced use only: An alternate command to run instead of '%s'</shortdesc>\n",
actions[lpc]);
printf
(" <longdesc lang=\"en\">Some devices do not support the standard commands or may provide additional ones.\n"
"Use this to specify an alternate, device-specific, command that implements the '%s' action.</longdesc>\n",
actions[lpc]);
printf(" <content type=\"string\" default=\"%s\"/>\n", actions[lpc]);
printf(" </parameter>\n");
printf(" <parameter name=\"pcmk_%s_timeout\" unique=\"0\">\n", actions[lpc]);
printf
(" <shortdesc lang=\"en\">Advanced use only: Specify an alternate timeout to use for %s actions instead of stonith-timeout</shortdesc>\n",
actions[lpc]);
printf
(" <longdesc lang=\"en\">Some devices need much more/less time to complete than normal.\n"
"Use this to specify an alternate, device-specific, timeout for '%s' actions.</longdesc>\n",
actions[lpc]);
printf(" <content type=\"time\" default=\"60s\"/>\n");
printf(" </parameter>\n");
printf(" <parameter name=\"pcmk_%s_retries\" unique=\"0\">\n", actions[lpc]);
printf
(" <shortdesc lang=\"en\">Advanced use only: The maximum number of times to retry the '%s' command within the timeout period</shortdesc>\n",
actions[lpc]);
printf(" <longdesc lang=\"en\">Some devices do not support multiple connections."
" Operations may 'fail' if the device is busy with another task so Pacemaker will automatically retry the operation, if there is time remaining."
" Use this option to alter the number of times Pacemaker retries '%s' actions before giving up."
"</longdesc>\n", actions[lpc]);
printf(" <content type=\"integer\" default=\"2\"/>\n");
printf(" </parameter>\n");
}
printf(" </parameters>\n");
printf("</resource-agent>\n");
return 0;
}
if (optind != argc) {
++argerr;
}
if (argerr) {
crm_help('?', EX_USAGE);
}
crm_log_init("stonith-ng", LOG_INFO, TRUE, FALSE, argc, argv, FALSE);
mainloop_add_signal(SIGTERM, stonith_shutdown);
crm_peer_init();
known_peer_names = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, free);
if (stand_alone == FALSE) {
#if SUPPORT_HEARTBEAT
cluster.hb_conn = NULL;
cluster.hb_dispatch = stonith_peer_hb_callback;
cluster.destroy = stonith_peer_hb_destroy;
#endif
if (is_openais_cluster()) {
#if SUPPORT_COROSYNC
cluster.destroy = stonith_peer_cs_destroy;
cluster.cpg.cpg_deliver_fn = stonith_peer_ais_callback;
cluster.cpg.cpg_confchg_fn = pcmk_cpg_membership;
#endif
}
crm_set_status_callback(&st_peer_update_callback);
if (crm_cluster_connect(&cluster) == FALSE) {
crm_crit("Cannot sign in to the cluster... terminating");
crm_exit(DAEMON_RESPAWN_STOP);
}
stonith_our_uname = cluster.uname;
stonith_our_uuid = cluster.uuid;
#if SUPPORT_HEARTBEAT
if (is_heartbeat_cluster()) {
/* crm_cluster_connect() registered us for crm_system_name, which
* usually is the only F_TYPE used by the respective sub system.
* Stonith needs to register two additional F_TYPE callbacks,
* because it can :-/ */
if (HA_OK !=
cluster.hb_conn->llc_ops->set_msg_callback(cluster.hb_conn, T_STONITH_NOTIFY,
cluster.hb_dispatch, cluster.hb_conn)) {
crm_crit("Cannot set msg callback %s: %s", T_STONITH_NOTIFY, cluster.hb_conn->llc_ops->errmsg(cluster.hb_conn));
crm_exit(DAEMON_RESPAWN_STOP);
}
if (HA_OK !=
cluster.hb_conn->llc_ops->set_msg_callback(cluster.hb_conn, T_STONITH_TIMEOUT_VALUE,
cluster.hb_dispatch, cluster.hb_conn)) {
crm_crit("Cannot set msg callback %s: %s", T_STONITH_TIMEOUT_VALUE, cluster.hb_conn->llc_ops->errmsg(cluster.hb_conn));
crm_exit(DAEMON_RESPAWN_STOP);
}
}
#endif
if (no_cib_connect == FALSE) {
setup_cib();
}
} else {
stonith_our_uname = strdup("localhost");
}
device_list = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, free_device);
topology = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, free_topology_entry);
if(stonith_watchdog_timeout_ms > 0) {
xmlNode *xml;
stonith_key_value_t *params = NULL;
params = stonith_key_value_add(params, STONITH_ATTR_HOSTLIST, stonith_our_uname);
xml = create_device_registration_xml("watchdog", "internal", STONITH_WATCHDOG_AGENT, params, NULL);
stonith_device_register(xml, NULL, FALSE);
stonith_key_value_freeall(params, 1, 1);
free_xml(xml);
}
stonith_ipc_server_init(&ipcs, &ipc_callbacks);
#if SUPPORT_STONITH_CONFIG
if (((stand_alone == TRUE)) && !(standalone_cfg_read_file(STONITH_NG_CONF_FILE))) {
standalone_cfg_commit();
}
#endif
/* Create the mainloop and run it... */
mainloop = g_main_new(FALSE);
crm_info("Starting %s mainloop", crm_system_name);
g_main_run(mainloop);
stonith_cleanup();
#if SUPPORT_HEARTBEAT
if (cluster.hb_conn) {
cluster.hb_conn->llc_ops->delete(cluster.hb_conn);
}
#endif
crm_info("Done");
return crm_exit(rc);
}
diff --git a/fencing/remote.c b/fencing/remote.c
index 6096d27799..c8c324ed6e 100644
--- a/fencing/remote.c
+++ b/fencing/remote.c
@@ -1,2109 +1,2109 @@
/*
* Copyright (C) 2009 Andrew Beekhof <andrew@beekhof.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/utsname.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <regex.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/ipc.h>
#include <crm/common/ipcs.h>
#include <crm/cluster/internal.h>
#include <crm/stonith-ng.h>
#include <crm/fencing/internal.h>
#include <crm/common/xml.h>
#include <crm/common/util.h>
#include <internal.h>
#define TIMEOUT_MULTIPLY_FACTOR 1.2
/* When one stonithd queries its peers for devices able to handle a fencing
* request, each peer will reply with a list of such devices available to it.
* Each reply will be parsed into a st_query_result_t, with each device's
* information kept in a device_properties_t.
*/
typedef struct device_properties_s {
/* Whether access to this device has been verified */
gboolean verified;
/* The remaining members are indexed by the operation's "phase" */
/* Whether this device has been executed in each phase */
gboolean executed[st_phase_max];
/* Whether this device is disallowed from executing in each phase */
gboolean disallowed[st_phase_max];
/* Action-specific timeout for each phase */
int custom_action_timeout[st_phase_max];
/* Action-specific maximum random delay for each phase */
int delay_max[st_phase_max];
} device_properties_t;
typedef struct st_query_result_s {
/* Name of peer that sent this result */
char *host;
/* Only try peers for non-topology based operations once */
gboolean tried;
/* Number of entries in the devices table */
int ndevices;
/* Devices available to this host that are capable of fencing the target */
GHashTable *devices;
} st_query_result_t;
GHashTable *remote_op_list = NULL;
void call_remote_stonith(remote_fencing_op_t * op, st_query_result_t * peer);
static void remote_op_done(remote_fencing_op_t * op, xmlNode * data, int rc, int dup);
extern xmlNode *stonith_create_op(int call_id, const char *token, const char *op, xmlNode * data,
int call_options);
static void report_timeout_period(remote_fencing_op_t * op, int op_timeout);
static int get_op_total_timeout(const remote_fencing_op_t *op,
const st_query_result_t *chosen_peer);
static gint
sort_strings(gconstpointer a, gconstpointer b)
{
return strcmp(a, b);
}
static void
free_remote_query(gpointer data)
{
if (data) {
st_query_result_t *query = data;
crm_trace("Free'ing query result from %s", query->host);
g_hash_table_destroy(query->devices);
free(query->host);
free(query);
}
}
struct peer_count_data {
const remote_fencing_op_t *op;
gboolean verified_only;
int count;
};
/*!
* \internal
* \brief Increment a counter if a device has not been executed yet
*
* \param[in] key Device ID (ignored)
* \param[in] value Device properties
* \param[in] user_data Peer count data
*/
static void
count_peer_device(gpointer key, gpointer value, gpointer user_data)
{
device_properties_t *props = (device_properties_t*)value;
struct peer_count_data *data = user_data;
if (!props->executed[data->op->phase]
&& (!data->verified_only || props->verified)) {
++(data->count);
}
}
/*!
* \internal
* \brief Check the number of available devices in a peer's query results
*
* \param[in] op Operation that results are for
* \param[in] peer Peer to count
* \param[in] verified_only Whether to count only verified devices
*
* \return Number of devices available to peer that were not already executed
*/
static int
count_peer_devices(const remote_fencing_op_t *op, const st_query_result_t *peer,
gboolean verified_only)
{
struct peer_count_data data;
data.op = op;
data.verified_only = verified_only;
data.count = 0;
if (peer) {
g_hash_table_foreach(peer->devices, count_peer_device, &data);
}
return data.count;
}
/*!
* \internal
* \brief Search for a device in a query result
*
* \param[in] op Operation that result is for
* \param[in] peer Query result for a peer
* \param[in] device Device ID to search for
*
* \return Device properties if found, NULL otherwise
*/
static device_properties_t *
find_peer_device(const remote_fencing_op_t *op, const st_query_result_t *peer,
const char *device)
{
device_properties_t *props = g_hash_table_lookup(peer->devices, device);
return (props && !props->executed[op->phase]
&& !props->disallowed[op->phase])? props : NULL;
}
/*!
* \internal
* \brief Find a device in a peer's device list and mark it as executed
*
* \param[in] op Operation that peer result is for
* \param[in,out] peer Peer with results to search
* \param[in] device ID of device to mark as done
* \param[in] verified_devices_only Only consider verified devices
*
* \return TRUE if device was found and marked, FALSE otherwise
*/
static gboolean
grab_peer_device(const remote_fencing_op_t *op, st_query_result_t *peer,
const char *device, gboolean verified_devices_only)
{
device_properties_t *props = find_peer_device(op, peer, device);
if ((props == NULL) || (verified_devices_only && !props->verified)) {
return FALSE;
}
crm_trace("Removing %s from %s (%d remaining)",
device, peer->host, count_peer_devices(op, peer, FALSE));
props->executed[op->phase] = TRUE;
return TRUE;
}
static void
clear_remote_op_timers(remote_fencing_op_t * op)
{
if (op->query_timer) {
g_source_remove(op->query_timer);
op->query_timer = 0;
}
if (op->op_timer_total) {
g_source_remove(op->op_timer_total);
op->op_timer_total = 0;
}
if (op->op_timer_one) {
g_source_remove(op->op_timer_one);
op->op_timer_one = 0;
}
}
static void
free_remote_op(gpointer data)
{
remote_fencing_op_t *op = data;
crm_trace("Free'ing op %s for %s", op->id, op->target);
crm_log_xml_debug(op->request, "Destroying");
clear_remote_op_timers(op);
free(op->id);
free(op->action);
free(op->target);
free(op->client_id);
free(op->client_name);
free(op->originator);
if (op->query_results) {
g_list_free_full(op->query_results, free_remote_query);
}
if (op->request) {
free_xml(op->request);
op->request = NULL;
}
if (op->devices_list) {
g_list_free_full(op->devices_list, free);
op->devices_list = NULL;
}
g_list_free_full(op->automatic_list, free);
free(op);
}
-/*
+/*!
* \internal
* \brief Return an operation's originally requested action (before any remap)
*
* \param[in] op Operation to check
*
* \return Operation's original action
*/
static const char *
op_requested_action(const remote_fencing_op_t *op)
{
return ((op->phase > st_phase_requested)? "reboot" : op->action);
}
-/*
+/*!
* \internal
* \brief Remap a "reboot" operation to the "off" phase
*
* \param[in,out] op Operation to remap
*/
static void
op_phase_off(remote_fencing_op_t *op)
{
crm_info("Remapping multiple-device reboot of %s (%s) to off",
op->target, op->id);
op->phase = st_phase_off;
/* Happily, "off" and "on" are shorter than "reboot", so we can reuse the
* memory allocation at each phase.
*/
strcpy(op->action, "off");
}
/*!
* \internal
* \brief Advance a remapped reboot operation to the "on" phase
*
* \param[in,out] op Operation to remap
*/
static void
op_phase_on(remote_fencing_op_t *op)
{
GListPtr iter = NULL;
crm_info("Remapped off of %s complete, remapping to on for %s.%.8s",
op->target, op->client_name, op->id);
op->phase = st_phase_on;
strcpy(op->action, "on");
/* Skip devices with automatic unfencing, because the cluster will handle it
* when the node rejoins.
*/
for (iter = op->automatic_list; iter != NULL; iter = iter->next) {
GListPtr match = g_list_find_custom(op->devices_list, iter->data,
sort_strings);
if (match) {
op->devices_list = g_list_remove(op->devices_list, match->data);
}
}
g_list_free_full(op->automatic_list, free);
op->automatic_list = NULL;
/* Rewind device list pointer */
op->devices = op->devices_list;
}
/*!
* \internal
* \brief Reset a remapped reboot operation
*
* \param[in,out] op Operation to reset
*/
static void
undo_op_remap(remote_fencing_op_t *op)
{
if (op->phase > 0) {
crm_info("Undoing remap of reboot of %s for %s.%.8s",
op->target, op->client_name, op->id);
op->phase = st_phase_requested;
strcpy(op->action, "reboot");
}
}
static xmlNode *
create_op_done_notify(remote_fencing_op_t * op, int rc)
{
xmlNode *notify_data = create_xml_node(NULL, T_STONITH_NOTIFY_FENCE);
crm_xml_add_int(notify_data, "state", op->state);
crm_xml_add_int(notify_data, F_STONITH_RC, rc);
crm_xml_add(notify_data, F_STONITH_TARGET, op->target);
crm_xml_add(notify_data, F_STONITH_ACTION, op->action);
crm_xml_add(notify_data, F_STONITH_DELEGATE, op->delegate);
crm_xml_add(notify_data, F_STONITH_REMOTE_OP_ID, op->id);
crm_xml_add(notify_data, F_STONITH_ORIGIN, op->originator);
crm_xml_add(notify_data, F_STONITH_CLIENTID, op->client_id);
crm_xml_add(notify_data, F_STONITH_CLIENTNAME, op->client_name);
return notify_data;
}
static void
bcast_result_to_peers(remote_fencing_op_t * op, int rc)
{
static int count = 0;
xmlNode *bcast = create_xml_node(NULL, T_STONITH_REPLY);
xmlNode *notify_data = create_op_done_notify(op, rc);
count++;
crm_trace("Broadcasting result to peers");
crm_xml_add(bcast, F_TYPE, T_STONITH_NOTIFY);
crm_xml_add(bcast, F_SUBTYPE, "broadcast");
crm_xml_add(bcast, F_STONITH_OPERATION, T_STONITH_NOTIFY);
crm_xml_add_int(bcast, "count", count);
add_message_xml(bcast, F_STONITH_CALLDATA, notify_data);
send_cluster_message(NULL, crm_msg_stonith_ng, bcast, FALSE);
free_xml(notify_data);
free_xml(bcast);
return;
}
static void
handle_local_reply_and_notify(remote_fencing_op_t * op, xmlNode * data, int rc)
{
xmlNode *notify_data = NULL;
xmlNode *reply = NULL;
if (op->notify_sent == TRUE) {
/* nothing to do */
return;
}
/* Do notification with a clean data object */
notify_data = create_op_done_notify(op, rc);
crm_xml_add_int(data, "state", op->state);
crm_xml_add(data, F_STONITH_TARGET, op->target);
crm_xml_add(data, F_STONITH_OPERATION, op->action);
reply = stonith_construct_reply(op->request, NULL, data, rc);
crm_xml_add(reply, F_STONITH_DELEGATE, op->delegate);
/* Send fencing OP reply to local client that initiated fencing */
do_local_reply(reply, op->client_id, op->call_options & st_opt_sync_call, FALSE);
/* bcast to all local clients that the fencing operation happend */
do_stonith_notify(0, T_STONITH_NOTIFY_FENCE, rc, notify_data);
/* mark this op as having notify's already sent */
op->notify_sent = TRUE;
free_xml(reply);
free_xml(notify_data);
}
static void
handle_duplicates(remote_fencing_op_t * op, xmlNode * data, int rc)
{
GListPtr iter = NULL;
for (iter = op->duplicates; iter != NULL; iter = iter->next) {
remote_fencing_op_t *other = iter->data;
if (other->state == st_duplicate) {
/* Ie. it hasn't timed out already */
other->state = op->state;
crm_debug("Peforming duplicate notification for %s@%s.%.8s = %s", other->client_name,
other->originator, other->id, pcmk_strerror(rc));
remote_op_done(other, data, rc, TRUE);
} else {
crm_err("Skipping duplicate notification for %s@%s - %d", other->client_name,
other->originator, other->state);
}
}
}
/*!
* \internal
* \brief Finalize a remote operation.
*
* \description This function has two code paths.
*
* Path 1. This node is the owner of the operation and needs
* to notify the cpg group via a broadcast as to the operation's
* results.
*
* Path 2. The cpg broadcast is received. All nodes notify their local
* stonith clients the operation results.
*
* So, The owner of the operation first notifies the cluster of the result,
* and once that cpg notify is received back it notifies all the local clients.
*
* Nodes that are passive watchers of the operation will receive the
* broadcast and only need to notify their local clients the operation finished.
*
* \param op, The fencing operation to finalize
* \param data, The xml msg reply (if present) of the last delegated fencing
* operation.
* \param dup, Is this operation a duplicate, if so treat it a little differently
* making sure the broadcast is not sent out.
*/
static void
remote_op_done(remote_fencing_op_t * op, xmlNode * data, int rc, int dup)
{
int level = LOG_ERR;
const char *subt = NULL;
xmlNode *local_data = NULL;
op->completed = time(NULL);
clear_remote_op_timers(op);
undo_op_remap(op);
if (op->notify_sent == TRUE) {
crm_err("Already sent notifications for '%s of %s by %s' (for=%s@%s.%.8s, state=%d): %s",
op->action, op->target, op->delegate ? op->delegate : "<no-one>",
op->client_name, op->originator, op->id, op->state, pcmk_strerror(rc));
goto remote_op_done_cleanup;
}
if (!op->delegate && data && rc != -ENODEV && rc != -EHOSTUNREACH) {
xmlNode *ndata = get_xpath_object("//@" F_STONITH_DELEGATE, data, LOG_TRACE);
if(ndata) {
op->delegate = crm_element_value_copy(ndata, F_STONITH_DELEGATE);
} else {
op->delegate = crm_element_value_copy(data, F_ORIG);
}
}
if (data == NULL) {
data = create_xml_node(NULL, "remote-op");
local_data = data;
}
/* Tell everyone the operation is done, we will continue
* with doing the local notifications once we receive
* the broadcast back. */
subt = crm_element_value(data, F_SUBTYPE);
if (dup == FALSE && safe_str_neq(subt, "broadcast")) {
/* Defer notification until the bcast message arrives */
bcast_result_to_peers(op, rc);
goto remote_op_done_cleanup;
}
if (rc == pcmk_ok || dup) {
level = LOG_NOTICE;
} else if (safe_str_neq(op->originator, stonith_our_uname)) {
level = LOG_NOTICE;
}
do_crm_log(level,
"Operation %s of %s by %s for %s@%s.%.8s: %s",
op->action, op->target, op->delegate ? op->delegate : "<no-one>",
op->client_name, op->originator, op->id, pcmk_strerror(rc));
handle_local_reply_and_notify(op, data, rc);
if (dup == FALSE) {
handle_duplicates(op, data, rc);
}
/* Free non-essential parts of the record
* Keep the record around so we can query the history
*/
if (op->query_results) {
g_list_free_full(op->query_results, free_remote_query);
op->query_results = NULL;
}
if (op->request) {
free_xml(op->request);
op->request = NULL;
}
remote_op_done_cleanup:
free_xml(local_data);
}
static gboolean
remote_op_watchdog_done(gpointer userdata)
{
remote_fencing_op_t *op = userdata;
op->op_timer_one = 0;
crm_notice("Self-fencing (%s) by %s for %s.%8s assumed complete",
op->action, op->target, op->client_name, op->id);
op->state = st_done;
remote_op_done(op, NULL, pcmk_ok, FALSE);
return FALSE;
}
static gboolean
remote_op_timeout_one(gpointer userdata)
{
remote_fencing_op_t *op = userdata;
op->op_timer_one = 0;
crm_notice("Peer's fencing (%s) of %s for %s timed out" CRM_XS "id=%s",
op->action, op->target, op->client_name, op->id);
call_remote_stonith(op, NULL);
return FALSE;
}
static gboolean
remote_op_timeout(gpointer userdata)
{
remote_fencing_op_t *op = userdata;
op->op_timer_total = 0;
if (op->state == st_done) {
crm_debug("Action %s (%s) for %s (%s) already completed",
op->action, op->id, op->target, op->client_name);
return FALSE;
}
crm_debug("Action %s (%s) for %s (%s) timed out",
op->action, op->id, op->target, op->client_name);
if (op->phase == st_phase_on) {
/* A remapped reboot operation timed out in the "on" phase, but the
* "off" phase completed successfully, so quit trying any further
* devices, and return success.
*/
remote_op_done(op, NULL, pcmk_ok, FALSE);
return FALSE;
}
op->state = st_failed;
remote_op_done(op, NULL, -ETIME, FALSE);
return FALSE;
}
static gboolean
remote_op_query_timeout(gpointer data)
{
remote_fencing_op_t *op = data;
op->query_timer = 0;
if (op->state == st_done) {
crm_debug("Operation %s for %s already completed", op->id, op->target);
} else if (op->state == st_exec) {
crm_debug("Operation %s for %s already in progress", op->id, op->target);
} else if (op->query_results) {
crm_debug("Query %s for %s complete: %d", op->id, op->target, op->state);
call_remote_stonith(op, NULL);
} else {
crm_debug("Query %s for %s timed out: %d", op->id, op->target, op->state);
if (op->op_timer_total) {
g_source_remove(op->op_timer_total);
op->op_timer_total = 0;
}
remote_op_timeout(op);
}
return FALSE;
}
static gboolean
topology_is_empty(stonith_topology_t *tp)
{
int i;
if (tp == NULL) {
return TRUE;
}
for (i = 0; i < ST_LEVEL_MAX; i++) {
if (tp->levels[i] != NULL) {
return FALSE;
}
}
return TRUE;
}
-/*
+/*!
* \internal
* \brief Add a device to an operation's automatic unfencing list
*
* \param[in,out] op Operation to modify
* \param[in] device Device ID to add
*/
static void
add_required_device(remote_fencing_op_t *op, const char *device)
{
GListPtr match = g_list_find_custom(op->automatic_list, device,
sort_strings);
if (!match) {
op->automatic_list = g_list_prepend(op->automatic_list, strdup(device));
}
}
-/*
+/*!
* \internal
* \brief Remove a device from the automatic unfencing list
*
* \param[in,out] op Operation to modify
* \param[in] device Device ID to remove
*/
static void
remove_required_device(remote_fencing_op_t *op, const char *device)
{
GListPtr match = g_list_find_custom(op->automatic_list, device,
sort_strings);
if (match) {
op->automatic_list = g_list_remove(op->automatic_list, match->data);
}
}
/* deep copy the device list */
static void
set_op_device_list(remote_fencing_op_t * op, GListPtr devices)
{
GListPtr lpc = NULL;
if (op->devices_list) {
g_list_free_full(op->devices_list, free);
op->devices_list = NULL;
}
for (lpc = devices; lpc != NULL; lpc = lpc->next) {
op->devices_list = g_list_append(op->devices_list, strdup(lpc->data));
}
op->devices = op->devices_list;
}
-/*
+/*!
* \internal
* \brief Check whether a node matches a topology target
*
* \param[in] tp Topology table entry to check
* \param[in] node Name of node to check
*
* \return TRUE if node matches topology target
*/
static gboolean
topology_matches(const stonith_topology_t *tp, const char *node)
{
regex_t r_patt;
CRM_CHECK(node && tp && tp->target, return FALSE);
switch(tp->kind) {
case 2:
/* This level targets by attribute, so tp->target is a NAME=VALUE pair
* of a permanent attribute applied to targeted nodes. The test below
* relies on the locally cached copy of the CIB, so if fencing needs to
* be done before the initial CIB is received or after a malformed CIB
* is received, then the topology will be unable to be used.
*/
if (node_has_attr(node, tp->target_attribute, tp->target_value)) {
crm_notice("Matched %s with %s by attribute", node, tp->target);
return TRUE;
}
break;
case 1:
/* This level targets by name, so tp->target is a regular expression
* matching names of nodes to be targeted.
*/
if (regcomp(&r_patt, tp->target_pattern, REG_EXTENDED)) {
crm_info("Bad regex '%s' for fencing level", tp->target);
} else {
int status = regexec(&r_patt, node, 0, NULL, 0);
regfree(&r_patt);
if (status == 0) {
crm_notice("Matched %s with %s by name", node, tp->target);
return TRUE;
}
}
break;
case 0:
crm_trace("Testing %s against %s", node, tp->target);
return safe_str_eq(tp->target, node);
}
crm_trace("No match for %s with %s", node, tp->target);
return FALSE;
}
stonith_topology_t *
find_topology_for_host(const char *host)
{
GHashTableIter tIter;
stonith_topology_t *tp = g_hash_table_lookup(topology, host);
if(tp != NULL) {
crm_trace("Found %s for %s in %d entries", tp->target, host, g_hash_table_size(topology));
return tp;
}
g_hash_table_iter_init(&tIter, topology);
while (g_hash_table_iter_next(&tIter, NULL, (gpointer *) & tp)) {
if (topology_matches(tp, host)) {
crm_trace("Found %s for %s in %d entries", tp->target, host, g_hash_table_size(topology));
return tp;
}
}
crm_trace("No matches for %s in %d topology entries", host, g_hash_table_size(topology));
return NULL;
}
/*!
* \internal
* \brief Set fencing operation's device list to target's next topology level
*
* \param[in,out] op Remote fencing operation to modify
*
* \return pcmk_ok if successful, target was not specified (i.e. queries) or
* target has no topology, or -EINVAL if no more topology levels to try
*/
static int
stonith_topology_next(remote_fencing_op_t * op)
{
stonith_topology_t *tp = NULL;
if (op->target) {
/* Queries don't have a target set */
tp = find_topology_for_host(op->target);
}
if (topology_is_empty(tp)) {
return pcmk_ok;
}
set_bit(op->call_options, st_opt_topology);
/* This is a new level, so undo any remapping left over from previous */
undo_op_remap(op);
do {
op->level++;
} while (op->level < ST_LEVEL_MAX && tp->levels[op->level] == NULL);
if (op->level < ST_LEVEL_MAX) {
crm_trace("Attempting fencing level %d for %s (%d devices) - %s@%s.%.8s",
op->level, op->target, g_list_length(tp->levels[op->level]),
op->client_name, op->originator, op->id);
set_op_device_list(op, tp->levels[op->level]);
if (g_list_next(op->devices_list) && safe_str_eq(op->action, "reboot")) {
/* A reboot has been requested for a topology level with multiple
* devices. Instead of rebooting the devices sequentially, we will
* turn them all off, then turn them all on again. (Think about
* switched power outlets for redundant power supplies.)
*/
op_phase_off(op);
}
return pcmk_ok;
}
crm_notice("All fencing options to fence %s for %s@%s.%.8s failed",
op->target, op->client_name, op->originator, op->id);
return -EINVAL;
}
/*!
* \brief Check to see if this operation is a duplicate of another in flight
* operation. If so merge this operation into the inflight operation, and mark
* it as a duplicate.
*/
static void
merge_duplicates(remote_fencing_op_t * op)
{
GHashTableIter iter;
remote_fencing_op_t *other = NULL;
time_t now = time(NULL);
g_hash_table_iter_init(&iter, remote_op_list);
while (g_hash_table_iter_next(&iter, NULL, (void **)&other)) {
crm_node_t *peer = NULL;
const char *other_action = op_requested_action(other);
if (other->state > st_exec) {
/* Must be in-progress */
continue;
} else if (safe_str_neq(op->target, other->target)) {
/* Must be for the same node */
continue;
} else if (safe_str_neq(op->action, other_action)) {
crm_trace("Must be for the same action: %s vs. %s",
op->action, other_action);
continue;
} else if (safe_str_eq(op->client_name, other->client_name)) {
crm_trace("Must be for different clients: %s", op->client_name);
continue;
} else if (safe_str_eq(other->target, other->originator)) {
crm_trace("Can't be a suicide operation: %s", other->target);
continue;
}
peer = crm_get_peer(0, other->originator);
if(fencing_peer_active(peer) == FALSE) {
crm_notice("Failing stonith action %s for node %s originating from %s@%s.%.8s: Originator is dead",
other->action, other->target, other->client_name, other->originator, other->id);
other->state = st_failed;
continue;
} else if(other->total_timeout > 0 && now > (other->total_timeout + other->created)) {
crm_info("Stonith action %s for node %s originating from %s@%s.%.8s is too old: %d vs. %d + %d",
other->action, other->target, other->client_name, other->originator, other->id,
now, other->created, other->total_timeout);
continue;
}
/* There is another in-flight request to fence the same host
* Piggyback on that instead. If it fails, so do we.
*/
other->duplicates = g_list_append(other->duplicates, op);
if (other->total_timeout == 0) {
crm_trace("Making a best-guess as to the timeout used");
other->total_timeout = op->total_timeout =
TIMEOUT_MULTIPLY_FACTOR * get_op_total_timeout(op, NULL);
}
crm_notice
("Merging stonith action %s for node %s originating from client %s.%.8s with identical request from %s@%s.%.8s (%ds)",
op->action, op->target, op->client_name, op->id, other->client_name, other->originator,
other->id, other->total_timeout);
report_timeout_period(op, other->total_timeout);
op->state = st_duplicate;
}
}
static uint32_t fencing_active_peers(void)
{
uint32_t count = 0;
crm_node_t *entry;
GHashTableIter gIter;
g_hash_table_iter_init(&gIter, crm_peer_cache);
while (g_hash_table_iter_next(&gIter, NULL, (void **)&entry)) {
if(fencing_peer_active(entry)) {
count++;
}
}
return count;
}
int
stonith_manual_ack(xmlNode * msg, remote_fencing_op_t * op)
{
xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, msg, LOG_ERR);
op->state = st_done;
op->completed = time(NULL);
op->delegate = strdup("a human");
crm_notice("Injecting manual confirmation that %s is safely off/down",
crm_element_value(dev, F_STONITH_TARGET));
remote_op_done(op, msg, pcmk_ok, FALSE);
/* Replies are sent via done_cb->stonith_send_async_reply()->do_local_reply() */
return -EINPROGRESS;
}
char *
stonith_get_peer_name(unsigned int nodeid)
{
crm_node_t *node = crm_find_peer(nodeid, NULL);
char *nodename = NULL;
if (node && node->uname) {
return strdup(node->uname);
} else if ((nodename = get_node_name(nodeid))) {
return nodename;
} else {
const char *last_known_name = g_hash_table_lookup(known_peer_names, GUINT_TO_POINTER(nodeid));
if (last_known_name) {
crm_debug("Use the last known name %s for nodeid %u", last_known_name, nodeid);
return strdup(last_known_name);
}
}
return NULL;
}
/*!
* \internal
* \brief Create a new remote stonith op
* \param client, he local stonith client id that initaited the operation
* \param request, The request from the client that started the operation
* \param peer, Is this operation owned by another stonith peer? Operations
* owned by other peers are stored on all the stonith nodes, but only the
* owner executes the operation. All the nodes get the results to the operation
* once the owner finishes executing it.
*/
void *
create_remote_stonith_op(const char *client, xmlNode * request, gboolean peer)
{
remote_fencing_op_t *op = NULL;
xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, request, LOG_TRACE);
int call_options = 0;
if (remote_op_list == NULL) {
remote_op_list = g_hash_table_new_full(crm_str_hash, g_str_equal, NULL, free_remote_op);
}
/* If this operation is owned by another node, check to make
* sure we haven't already created this operation. */
if (peer && dev) {
const char *op_id = crm_element_value(dev, F_STONITH_REMOTE_OP_ID);
CRM_CHECK(op_id != NULL, return NULL);
op = g_hash_table_lookup(remote_op_list, op_id);
if (op) {
crm_debug("%s already exists", op_id);
return op;
}
}
op = calloc(1, sizeof(remote_fencing_op_t));
crm_element_value_int(request, F_STONITH_TIMEOUT, &(op->base_timeout));
if (peer && dev) {
op->id = crm_element_value_copy(dev, F_STONITH_REMOTE_OP_ID);
} else {
op->id = crm_generate_uuid();
}
g_hash_table_replace(remote_op_list, op->id, op);
CRM_LOG_ASSERT(g_hash_table_lookup(remote_op_list, op->id) != NULL);
crm_trace("Created %s", op->id);
op->state = st_query;
op->replies_expected = fencing_active_peers();
op->action = crm_element_value_copy(dev, F_STONITH_ACTION);
op->originator = crm_element_value_copy(dev, F_STONITH_ORIGIN);
op->delegate = crm_element_value_copy(dev, F_STONITH_DELEGATE); /* May not be set */
op->created = time(NULL);
if (op->originator == NULL) {
/* Local or relayed request */
op->originator = strdup(stonith_our_uname);
}
CRM_LOG_ASSERT(client != NULL);
if (client) {
op->client_id = strdup(client);
}
op->client_name = crm_element_value_copy(request, F_STONITH_CLIENTNAME);
op->target = crm_element_value_copy(dev, F_STONITH_TARGET);
op->request = copy_xml(request); /* TODO: Figure out how to avoid this */
crm_element_value_int(request, F_STONITH_CALLOPTS, &call_options);
op->call_options = call_options;
crm_element_value_int(request, F_STONITH_CALLID, &(op->client_callid));
crm_trace("%s new stonith op: %s - %s of %s for %s",
(peer
&& dev) ? "Recorded" : "Generated", op->id, op->action, op->target, op->client_name);
if (op->call_options & st_opt_cs_nodeid) {
int nodeid = crm_atoi(op->target, NULL);
char *nodename = stonith_get_peer_name(nodeid);
/* Ensure the conversion only happens once */
op->call_options &= ~st_opt_cs_nodeid;
if (nodename) {
free(op->target);
op->target = nodename;
} else {
crm_warn("Could not expand nodeid '%s' into a host name", op->target);
}
}
/* check to see if this is a duplicate operation of another in-flight operation */
merge_duplicates(op);
return op;
}
remote_fencing_op_t *
initiate_remote_stonith_op(crm_client_t * client, xmlNode * request, gboolean manual_ack)
{
int query_timeout = 0;
xmlNode *query = NULL;
const char *client_id = NULL;
remote_fencing_op_t *op = NULL;
if (client) {
client_id = client->id;
} else {
client_id = crm_element_value(request, F_STONITH_CLIENTID);
}
CRM_LOG_ASSERT(client_id != NULL);
op = create_remote_stonith_op(client_id, request, FALSE);
op->owner = TRUE;
if (manual_ack) {
crm_notice("Initiating manual confirmation for %s: %s",
op->target, op->id);
return op;
}
CRM_CHECK(op->action, return NULL);
if (stonith_topology_next(op) != pcmk_ok) {
op->state = st_failed;
}
switch (op->state) {
case st_failed:
crm_warn("Could not request peer fencing (%s) of %s "
CRM_XS " id=%s", op->action, op->target, op->id);
remote_op_done(op, NULL, -EINVAL, FALSE);
return op;
case st_duplicate:
crm_info("Requesting peer fencing (%s) of %s (duplicate) "
CRM_XS " id=%s", op->action, op->target, op->id);
return op;
default:
crm_notice("Requesting peer fencing (%s) of %s "
CRM_XS " id=%s state=%d",
op->action, op->target, op->id, op->state);
}
query = stonith_create_op(op->client_callid, op->id, STONITH_OP_QUERY,
NULL, op->call_options);
crm_xml_add(query, F_STONITH_REMOTE_OP_ID, op->id);
crm_xml_add(query, F_STONITH_TARGET, op->target);
crm_xml_add(query, F_STONITH_ACTION, op_requested_action(op));
crm_xml_add(query, F_STONITH_ORIGIN, op->originator);
crm_xml_add(query, F_STONITH_CLIENTID, op->client_id);
crm_xml_add(query, F_STONITH_CLIENTNAME, op->client_name);
crm_xml_add_int(query, F_STONITH_TIMEOUT, op->base_timeout);
send_cluster_message(NULL, crm_msg_stonith_ng, query, FALSE);
free_xml(query);
query_timeout = op->base_timeout * TIMEOUT_MULTIPLY_FACTOR;
op->query_timer = g_timeout_add((1000 * query_timeout), remote_op_query_timeout, op);
return op;
}
enum find_best_peer_options {
/*! Skip checking the target peer for capable fencing devices */
FIND_PEER_SKIP_TARGET = 0x0001,
/*! Only check the target peer for capable fencing devices */
FIND_PEER_TARGET_ONLY = 0x0002,
/*! Skip peers and devices that are not verified */
FIND_PEER_VERIFIED_ONLY = 0x0004,
};
static st_query_result_t *
find_best_peer(const char *device, remote_fencing_op_t * op, enum find_best_peer_options options)
{
GListPtr iter = NULL;
gboolean verified_devices_only = (options & FIND_PEER_VERIFIED_ONLY) ? TRUE : FALSE;
if (!device && is_set(op->call_options, st_opt_topology)) {
return NULL;
}
for (iter = op->query_results; iter != NULL; iter = iter->next) {
st_query_result_t *peer = iter->data;
crm_trace("Testing result from %s for %s with %d devices: %d %x",
peer->host, op->target, peer->ndevices, peer->tried, options);
if ((options & FIND_PEER_SKIP_TARGET) && safe_str_eq(peer->host, op->target)) {
continue;
}
if ((options & FIND_PEER_TARGET_ONLY) && safe_str_neq(peer->host, op->target)) {
continue;
}
if (is_set(op->call_options, st_opt_topology)) {
if (grab_peer_device(op, peer, device, verified_devices_only)) {
return peer;
}
} else if ((peer->tried == FALSE)
&& count_peer_devices(op, peer, verified_devices_only)) {
/* No topology: Use the current best peer */
crm_trace("Simple fencing");
return peer;
}
}
return NULL;
}
static st_query_result_t *
stonith_choose_peer(remote_fencing_op_t * op)
{
const char *device = NULL;
st_query_result_t *peer = NULL;
uint32_t active = fencing_active_peers();
do {
if (op->devices) {
device = op->devices->data;
crm_trace("Checking for someone to fence (%s) %s with %s",
op->action, op->target, device);
} else {
crm_trace("Checking for someone to fence (%s) %s",
op->action, op->target);
}
/* Best choice is a peer other than the target with verified access */
peer = find_best_peer(device, op, FIND_PEER_SKIP_TARGET|FIND_PEER_VERIFIED_ONLY);
if (peer) {
crm_trace("Found verified peer %s for %s", peer->host, device?device:"<any>");
return peer;
}
if(op->query_timer != 0 && op->replies < QB_MIN(op->replies_expected, active)) {
crm_trace("Waiting before looking for unverified devices to fence %s", op->target);
return NULL;
}
/* If no other peer has verified access, next best is unverified access */
peer = find_best_peer(device, op, FIND_PEER_SKIP_TARGET);
if (peer) {
crm_trace("Found best unverified peer %s", peer->host);
return peer;
}
/* If no other peer can do it, last option is self-fencing
* (which is never allowed for the "on" phase of a remapped reboot)
*/
if (op->phase != st_phase_on) {
peer = find_best_peer(device, op, FIND_PEER_TARGET_ONLY);
if (peer) {
crm_trace("%s will fence itself", peer->host);
return peer;
}
}
/* Try the next fencing level if there is one (unless we're in the "on"
* phase of a remapped "reboot", because we ignore errors in that case)
*/
} while ((op->phase != st_phase_on)
&& is_set(op->call_options, st_opt_topology)
&& stonith_topology_next(op) == pcmk_ok);
crm_notice("Couldn't find anyone to fence (%s) %s with %s",
op->action, op->target, (device? device : "any device"));
return NULL;
}
static int
get_device_timeout(const remote_fencing_op_t *op, const st_query_result_t *peer,
const char *device)
{
device_properties_t *props;
if (!peer || !device) {
return op->base_timeout;
}
props = g_hash_table_lookup(peer->devices, device);
if (!props) {
return op->base_timeout;
}
return (props->custom_action_timeout[op->phase]?
props->custom_action_timeout[op->phase] : op->base_timeout)
+ props->delay_max[op->phase];
}
struct timeout_data {
const remote_fencing_op_t *op;
const st_query_result_t *peer;
int total_timeout;
};
/*!
* \internal
* \brief Add timeout to a total if device has not been executed yet
*
* \param[in] key GHashTable key (device ID)
* \param[in] value GHashTable value (device properties)
* \param[in] user_data Timeout data
*/
static void
add_device_timeout(gpointer key, gpointer value, gpointer user_data)
{
const char *device_id = key;
device_properties_t *props = value;
struct timeout_data *timeout = user_data;
if (!props->executed[timeout->op->phase]
&& !props->disallowed[timeout->op->phase]) {
timeout->total_timeout += get_device_timeout(timeout->op,
timeout->peer, device_id);
}
}
static int
get_peer_timeout(const remote_fencing_op_t *op, const st_query_result_t *peer)
{
struct timeout_data timeout;
timeout.op = op;
timeout.peer = peer;
timeout.total_timeout = 0;
g_hash_table_foreach(peer->devices, add_device_timeout, &timeout);
return (timeout.total_timeout? timeout.total_timeout : op->base_timeout);
}
static int
get_op_total_timeout(const remote_fencing_op_t *op,
const st_query_result_t *chosen_peer)
{
int total_timeout = 0;
stonith_topology_t *tp = find_topology_for_host(op->target);
if (is_set(op->call_options, st_opt_topology) && tp) {
int i;
GListPtr device_list = NULL;
GListPtr iter = NULL;
/* Yep, this looks scary, nested loops all over the place.
* Here is what is going on.
* Loop1: Iterate through fencing levels.
* Loop2: If a fencing level has devices, loop through each device
* Loop3: For each device in a fencing level, see what peer owns it
* and what that peer has reported the timeout is for the device.
*/
for (i = 0; i < ST_LEVEL_MAX; i++) {
if (!tp->levels[i]) {
continue;
}
for (device_list = tp->levels[i]; device_list; device_list = device_list->next) {
for (iter = op->query_results; iter != NULL; iter = iter->next) {
const st_query_result_t *peer = iter->data;
if (find_peer_device(op, peer, device_list->data)) {
total_timeout += get_device_timeout(op, peer,
device_list->data);
break;
}
} /* End Loop3: match device with peer that owns device, find device's timeout period */
} /* End Loop2: iterate through devices at a specific level */
} /*End Loop1: iterate through fencing levels */
} else if (chosen_peer) {
total_timeout = get_peer_timeout(op, chosen_peer);
} else {
total_timeout = op->base_timeout;
}
return total_timeout ? total_timeout : op->base_timeout;
}
static void
report_timeout_period(remote_fencing_op_t * op, int op_timeout)
{
GListPtr iter = NULL;
xmlNode *update = NULL;
const char *client_node = NULL;
const char *client_id = NULL;
const char *call_id = NULL;
if (op->call_options & st_opt_sync_call) {
/* There is no reason to report the timeout for a synchronous call. It
* is impossible to use the reported timeout to do anything when the client
* is blocking for the response. This update is only important for
* async calls that require a callback to report the results in. */
return;
} else if (!op->request) {
return;
}
crm_trace("Reporting timeout for %s.%.8s", op->client_name, op->id);
client_node = crm_element_value(op->request, F_STONITH_CLIENTNODE);
call_id = crm_element_value(op->request, F_STONITH_CALLID);
client_id = crm_element_value(op->request, F_STONITH_CLIENTID);
if (!client_node || !call_id || !client_id) {
return;
}
if (safe_str_eq(client_node, stonith_our_uname)) {
/* The client is connected to this node, send the update direclty to them */
do_stonith_async_timeout_update(client_id, call_id, op_timeout);
return;
}
/* The client is connected to another node, relay this update to them */
update = stonith_create_op(op->client_callid, op->id, STONITH_OP_TIMEOUT_UPDATE, NULL, 0);
crm_xml_add(update, F_STONITH_REMOTE_OP_ID, op->id);
crm_xml_add(update, F_STONITH_CLIENTID, client_id);
crm_xml_add(update, F_STONITH_CALLID, call_id);
crm_xml_add_int(update, F_STONITH_TIMEOUT, op_timeout);
send_cluster_message(crm_get_peer(0, client_node), crm_msg_stonith_ng, update, FALSE);
free_xml(update);
for (iter = op->duplicates; iter != NULL; iter = iter->next) {
remote_fencing_op_t *dup = iter->data;
crm_trace("Reporting timeout for duplicate %s.%.8s", dup->client_name, dup->id);
report_timeout_period(iter->data, op_timeout);
}
}
-/*
+/*!
* \internal
* \brief Advance an operation to the next device in its topology
*
* \param[in,out] op Operation to advance
* \param[in] device ID of device just completed
* \param[in] msg XML reply that contained device result (if available)
* \param[in] rc Return code of device's execution
*/
static void
advance_op_topology(remote_fencing_op_t *op, const char *device, xmlNode *msg,
int rc)
{
/* Advance to the next device at this topology level, if any */
if (op->devices) {
op->devices = op->devices->next;
}
/* Handle automatic unfencing if an "on" action was requested */
if ((op->phase == st_phase_requested) && safe_str_eq(op->action, "on")) {
/* If the device we just executed was required, it's not anymore */
remove_required_device(op, device);
/* If there are no more devices at this topology level, run through any
* remaining devices with automatic unfencing
*/
if (op->devices == NULL) {
op->devices = op->automatic_list;
}
}
if ((op->devices == NULL) && (op->phase == st_phase_off)) {
/* We're done with this level and with required devices, but we had
* remapped "reboot" to "off", so start over with "on". If any devices
* need to be turned back on, op->devices will be non-NULL after this.
*/
op_phase_on(op);
}
if (op->devices) {
/* Necessary devices remain, so execute the next one */
crm_trace("Next for %s on behalf of %s@%s (rc was %d)",
op->target, op->originator, op->client_name, rc);
call_remote_stonith(op, NULL);
} else {
/* We're done with all devices and phases, so finalize operation */
crm_trace("Marking complex fencing op for %s as complete", op->target);
op->state = st_done;
remote_op_done(op, msg, rc, FALSE);
}
}
void
call_remote_stonith(remote_fencing_op_t * op, st_query_result_t * peer)
{
const char *device = NULL;
int timeout = op->base_timeout;
crm_trace("State for %s.%.8s: %s %d", op->target, op->client_name, op->id, op->state);
if (peer == NULL && !is_set(op->call_options, st_opt_topology)) {
peer = stonith_choose_peer(op);
}
if (!op->op_timer_total) {
int total_timeout = get_op_total_timeout(op, peer);
op->total_timeout = TIMEOUT_MULTIPLY_FACTOR * total_timeout;
op->op_timer_total = g_timeout_add(1000 * op->total_timeout, remote_op_timeout, op);
report_timeout_period(op, op->total_timeout);
crm_info("Total timeout set to %d for peer's fencing of %s for %s"
CRM_XS "id=%s",
total_timeout, op->target, op->client_name, op->id);
}
if (is_set(op->call_options, st_opt_topology) && op->devices) {
/* Ignore any peer preference, they might not have the device we need */
/* When using topology, stonith_choose_peer() removes the device from
* further consideration, so be sure to calculate timeout beforehand */
peer = stonith_choose_peer(op);
device = op->devices->data;
timeout = get_device_timeout(op, peer, device);
}
if (peer) {
int timeout_one = 0;
xmlNode *remote_op = stonith_create_op(op->client_callid, op->id, STONITH_OP_FENCE, NULL, 0);
crm_xml_add(remote_op, F_STONITH_REMOTE_OP_ID, op->id);
crm_xml_add(remote_op, F_STONITH_TARGET, op->target);
crm_xml_add(remote_op, F_STONITH_ACTION, op->action);
crm_xml_add(remote_op, F_STONITH_ORIGIN, op->originator);
crm_xml_add(remote_op, F_STONITH_CLIENTID, op->client_id);
crm_xml_add(remote_op, F_STONITH_CLIENTNAME, op->client_name);
crm_xml_add_int(remote_op, F_STONITH_TIMEOUT, timeout);
crm_xml_add_int(remote_op, F_STONITH_CALLOPTS, op->call_options);
if (device) {
timeout_one = TIMEOUT_MULTIPLY_FACTOR *
get_device_timeout(op, peer, device);
crm_info("Requesting that '%s' perform op '%s %s' with '%s' for %s (%ds)", peer->host,
op->target, op->action, device, op->client_name, timeout_one);
crm_xml_add(remote_op, F_STONITH_DEVICE, device);
crm_xml_add(remote_op, F_STONITH_MODE, "slave");
} else {
timeout_one = TIMEOUT_MULTIPLY_FACTOR * get_peer_timeout(op, peer);
crm_info("Requesting that '%s' perform op '%s %s' for %s (%ds, %ds)",
peer->host, op->target, op->action, op->client_name, timeout_one, stonith_watchdog_timeout_ms);
crm_xml_add(remote_op, F_STONITH_MODE, "smart");
}
op->state = st_exec;
if (op->op_timer_one) {
g_source_remove(op->op_timer_one);
}
if(stonith_watchdog_timeout_ms > 0 && device && safe_str_eq(device, "watchdog")) {
crm_notice("Waiting %ds for %s to self-fence (%s) for %s.%.8s (%p)",
stonith_watchdog_timeout_ms/1000, op->target,
op->action, op->client_name, op->id, device);
op->op_timer_one = g_timeout_add(stonith_watchdog_timeout_ms, remote_op_watchdog_done, op);
/* TODO check devices to verify watchdog will be in use */
} else if(stonith_watchdog_timeout_ms > 0
&& safe_str_eq(peer->host, op->target)
&& safe_str_neq(op->action, "on")) {
crm_notice("Waiting %ds for %s to self-fence (%s) for %s.%.8s (%p)",
stonith_watchdog_timeout_ms/1000, op->target,
op->action, op->client_name, op->id, device);
op->op_timer_one = g_timeout_add(stonith_watchdog_timeout_ms, remote_op_watchdog_done, op);
} else {
op->op_timer_one = g_timeout_add((1000 * timeout_one), remote_op_timeout_one, op);
}
send_cluster_message(crm_get_peer(0, peer->host), crm_msg_stonith_ng, remote_op, FALSE);
peer->tried = TRUE;
free_xml(remote_op);
return;
} else if (op->phase == st_phase_on) {
/* A remapped "on" cannot be executed, but the node was already
* turned off successfully, so ignore the error and continue.
*/
crm_warn("Ignoring %s 'on' failure (no capable peers) for %s after successful 'off'",
device, op->target);
advance_op_topology(op, device, NULL, pcmk_ok);
return;
} else if (op->owner == FALSE) {
crm_err("Fencing (%s) of %s for %s is not ours to control",
op->action, op->target, op->client_name);
} else if (op->query_timer == 0) {
/* We've exhausted all available peers */
crm_info("No remaining peers capable of fencing (%s) %s for %s (%d)",
op->target, op->action, op->client_name, op->state);
CRM_LOG_ASSERT(op->state < st_done);
remote_op_timeout(op);
} else if(op->replies >= op->replies_expected || op->replies >= fencing_active_peers()) {
int rc = -EHOSTUNREACH;
/* if the operation never left the query state,
* but we have all the expected replies, then no devices
* are available to execute the fencing operation. */
if(stonith_watchdog_timeout_ms && (device == NULL || safe_str_eq(device, "watchdog"))) {
crm_notice("Waiting %ds for %s to self-fence (%s) for %s.%.8s (%p)",
stonith_watchdog_timeout_ms/1000, op->target,
op->action, op->client_name, op->id, device);
op->op_timer_one = g_timeout_add(stonith_watchdog_timeout_ms, remote_op_watchdog_done, op);
return;
}
if (op->state == st_query) {
crm_info("None of the %d peers have devices capable of fencing (%s) %s for %s (%d)",
op->replies, op->action, op->target, op->client_name,
op->state);
rc = -ENODEV;
} else {
crm_info("None of the %d peers are capable of fencing (%s) %s for %s (%d)",
op->replies, op->action, op->target, op->client_name,
op->state);
}
op->state = st_failed;
remote_op_done(op, NULL, rc, FALSE);
} else if (device) {
crm_info("Waiting for additional peers capable of fencing (%s) %s with %s for %s.%.8s",
op->action, op->target, device, op->client_name, op->id);
} else {
crm_info("Waiting for additional peers capable of fencing (%s) %s for %s%.8s",
op->action, op->target, op->client_name, op->id);
}
}
/*!
* \internal
* \brief Comparison function for sorting query results
*
* \param[in] a GList item to compare
* \param[in] b GList item to compare
*
* \return Per the glib documentation, "a negative integer if the first value
* comes before the second, 0 if they are equal, or a positive integer
* if the first value comes after the second."
*/
static gint
sort_peers(gconstpointer a, gconstpointer b)
{
const st_query_result_t *peer_a = a;
const st_query_result_t *peer_b = b;
return (peer_b->ndevices - peer_a->ndevices);
}
/*!
* \internal
* \brief Determine if all the devices in the topology are found or not
*/
static gboolean
all_topology_devices_found(remote_fencing_op_t * op)
{
GListPtr device = NULL;
GListPtr iter = NULL;
device_properties_t *match = NULL;
stonith_topology_t *tp = NULL;
gboolean skip_target = FALSE;
int i;
tp = find_topology_for_host(op->target);
if (!tp) {
return FALSE;
}
if (safe_str_eq(op->action, "off") || safe_str_eq(op->action, "reboot")) {
/* Don't count the devices on the target node if we are killing
* the target node. */
skip_target = TRUE;
}
for (i = 0; i < ST_LEVEL_MAX; i++) {
for (device = tp->levels[i]; device; device = device->next) {
match = NULL;
for (iter = op->query_results; iter && !match; iter = iter->next) {
st_query_result_t *peer = iter->data;
if (skip_target && safe_str_eq(peer->host, op->target)) {
continue;
}
match = find_peer_device(op, peer, device->data);
}
if (!match) {
return FALSE;
}
}
}
return TRUE;
}
-/*
+/*!
* \internal
* \brief Parse action-specific device properties from XML
*
* \param[in] msg XML element containing the properties
* \param[in] peer Name of peer that sent XML (for logs)
* \param[in] device Device ID (for logs)
* \param[in] action Action the properties relate to (for logs)
* \param[in] phase Phase the properties relate to
* \param[in,out] props Device properties to update
*/
static void
parse_action_specific(xmlNode *xml, const char *peer, const char *device,
const char *action, remote_fencing_op_t *op,
enum st_remap_phase phase, device_properties_t *props)
{
props->custom_action_timeout[phase] = 0;
crm_element_value_int(xml, F_STONITH_ACTION_TIMEOUT,
&props->custom_action_timeout[phase]);
if (props->custom_action_timeout[phase]) {
crm_trace("Peer %s with device %s returned %s action timeout %d",
peer, device, action, props->custom_action_timeout[phase]);
}
props->delay_max[phase] = 0;
crm_element_value_int(xml, F_STONITH_DELAY_MAX, &props->delay_max[phase]);
if (props->delay_max[phase]) {
crm_trace("Peer %s with device %s returned maximum of random delay %d for %s",
peer, device, props->delay_max[phase], action);
}
/* Handle devices with automatic unfencing */
if (safe_str_eq(action, "on")) {
int required = 0;
crm_element_value_int(xml, F_STONITH_DEVICE_REQUIRED, &required);
if (required) {
crm_trace("Peer %s requires device %s to execute for action %s",
peer, device, action);
add_required_device(op, device);
}
}
/* If a reboot is remapped to off+on, it's possible that a node is allowed
* to perform one action but not another.
*/
if (crm_is_true(crm_element_value(xml, F_STONITH_ACTION_DISALLOWED))) {
props->disallowed[phase] = TRUE;
crm_trace("Peer %s is disallowed from executing %s for device %s",
peer, action, device);
}
}
-/*
+/*!
* \internal
* \brief Parse one device's properties from peer's XML query reply
*
* \param[in] xml XML node containing device properties
* \param[in,out] op Operation that query and reply relate to
* \param[in,out] result Peer's results
* \param[in] device ID of device being parsed
*/
static void
add_device_properties(xmlNode *xml, remote_fencing_op_t *op,
st_query_result_t *result, const char *device)
{
xmlNode *child;
int verified = 0;
device_properties_t *props = calloc(1, sizeof(device_properties_t));
/* Add a new entry to this result's devices list */
CRM_ASSERT(props != NULL);
g_hash_table_insert(result->devices, strdup(device), props);
/* Peers with verified (monitored) access will be preferred */
crm_element_value_int(xml, F_STONITH_DEVICE_VERIFIED, &verified);
if (verified) {
crm_trace("Peer %s has confirmed a verified device %s",
result->host, device);
props->verified = TRUE;
}
/* Parse action-specific device properties */
parse_action_specific(xml, result->host, device, op_requested_action(op),
op, st_phase_requested, props);
for (child = __xml_first_child(xml); child != NULL; child = __xml_next(child)) {
/* Replies for "reboot" operations will include the action-specific
* values for "off" and "on" in child elements, just in case the reboot
* winds up getting remapped.
*/
if (safe_str_eq(ID(child), "off")) {
parse_action_specific(child, result->host, device, "off",
op, st_phase_off, props);
} else if (safe_str_eq(ID(child), "on")) {
parse_action_specific(child, result->host, device, "on",
op, st_phase_on, props);
}
}
}
-/*
+/*!
* \internal
* \brief Parse a peer's XML query reply and add it to operation's results
*
* \param[in,out] op Operation that query and reply relate to
* \param[in] host Name of peer that sent this reply
* \param[in] ndevices Number of devices expected in reply
* \param[in] xml XML node containing device list
*
* \return Newly allocated result structure with parsed reply
*/
static st_query_result_t *
add_result(remote_fencing_op_t *op, const char *host, int ndevices, xmlNode *xml)
{
st_query_result_t *result = calloc(1, sizeof(st_query_result_t));
xmlNode *child;
CRM_CHECK(result != NULL, return NULL);
result->host = strdup(host);
result->devices = g_hash_table_new_full(crm_str_hash, g_str_equal, free, free);
/* Each child element describes one capable device available to the peer */
for (child = __xml_first_child(xml); child != NULL; child = __xml_next(child)) {
const char *device = ID(child);
if (device) {
add_device_properties(child, op, result, device);
}
}
result->ndevices = g_hash_table_size(result->devices);
CRM_CHECK(ndevices == result->ndevices,
crm_err("Query claimed to have %d devices but %d found",
ndevices, result->ndevices));
op->query_results = g_list_insert_sorted(op->query_results, result, sort_peers);
return result;
}
-/*
+/*!
* \internal
* \brief Handle a peer's reply to our fencing query
*
* Parse a query result from XML and store it in the remote operation
* table, and when enough replies have been received, issue a fencing request.
*
* \param[in] msg XML reply received
*
* \return pcmk_ok on success, -errno on error
*
* \note See initiate_remote_stonith_op() for how the XML query was initially
* formed, and stonith_query() for how the peer formed its XML reply.
*/
int
process_remote_stonith_query(xmlNode * msg)
{
int ndevices = 0;
gboolean host_is_target = FALSE;
gboolean have_all_replies = FALSE;
const char *id = NULL;
const char *host = NULL;
remote_fencing_op_t *op = NULL;
st_query_result_t *result = NULL;
uint32_t replies_expected;
xmlNode *dev = get_xpath_object("//@" F_STONITH_REMOTE_OP_ID, msg, LOG_ERR);
CRM_CHECK(dev != NULL, return -EPROTO);
id = crm_element_value(dev, F_STONITH_REMOTE_OP_ID);
CRM_CHECK(id != NULL, return -EPROTO);
dev = get_xpath_object("//@" F_STONITH_AVAILABLE_DEVICES, msg, LOG_ERR);
CRM_CHECK(dev != NULL, return -EPROTO);
crm_element_value_int(dev, F_STONITH_AVAILABLE_DEVICES, &ndevices);
op = g_hash_table_lookup(remote_op_list, id);
if (op == NULL) {
crm_debug("Received query reply for unknown or expired operation %s",
id);
return -EOPNOTSUPP;
}
replies_expected = QB_MIN(op->replies_expected, fencing_active_peers());
if ((++op->replies >= replies_expected) && (op->state == st_query)) {
have_all_replies = TRUE;
}
host = crm_element_value(msg, F_ORIG);
host_is_target = safe_str_eq(host, op->target);
crm_info("Query result %d of %d from %s for %s/%s (%d devices) %s",
op->replies, replies_expected, host,
op->target, op->action, ndevices, id);
if (ndevices > 0) {
result = add_result(op, host, ndevices, dev);
}
if (is_set(op->call_options, st_opt_topology)) {
/* If we start the fencing before all the topology results are in,
* it is possible fencing levels will be skipped because of the missing
* query results. */
if (op->state == st_query && all_topology_devices_found(op)) {
/* All the query results are in for the topology, start the fencing ops. */
crm_trace("All topology devices found");
call_remote_stonith(op, result);
} else if (have_all_replies) {
crm_info("All topology query replies have arrived, continuing (%d expected/%d received) ",
replies_expected, op->replies);
call_remote_stonith(op, NULL);
}
} else if (op->state == st_query) {
int nverified = count_peer_devices(op, result, TRUE);
/* We have a result for a non-topology fencing op that looks promising,
* go ahead and start fencing before query timeout */
if (result && (host_is_target == FALSE) && nverified) {
/* we have a verified device living on a peer that is not the target */
crm_trace("Found %d verified devices", nverified);
call_remote_stonith(op, result);
} else if (have_all_replies) {
crm_info("All query replies have arrived, continuing (%d expected/%d received) ",
replies_expected, op->replies);
call_remote_stonith(op, NULL);
} else {
crm_trace("Waiting for more peer results before launching fencing operation");
}
} else if (result && (op->state == st_done)) {
crm_info("Discarding query result from %s (%d devices): Operation is in state %d",
result->host, result->ndevices, op->state);
}
return pcmk_ok;
}
-/*
+/*!
* \internal
* \brief Handle a peer's reply to a fencing request
*
* Parse a fencing reply from XML, and either finalize the operation
* or attempt another device as appropriate.
*
* \param[in] msg XML reply received
*
* \return pcmk_ok on success, -errno on error
*/
int
process_remote_stonith_exec(xmlNode * msg)
{
int rc = 0;
const char *id = NULL;
const char *device = NULL;
remote_fencing_op_t *op = NULL;
xmlNode *dev = get_xpath_object("//@" F_STONITH_REMOTE_OP_ID, msg, LOG_ERR);
CRM_CHECK(dev != NULL, return -EPROTO);
id = crm_element_value(dev, F_STONITH_REMOTE_OP_ID);
CRM_CHECK(id != NULL, return -EPROTO);
dev = get_xpath_object("//@" F_STONITH_RC, msg, LOG_ERR);
CRM_CHECK(dev != NULL, return -EPROTO);
crm_element_value_int(dev, F_STONITH_RC, &rc);
device = crm_element_value(dev, F_STONITH_DEVICE);
if (remote_op_list) {
op = g_hash_table_lookup(remote_op_list, id);
}
if (op == NULL && rc == pcmk_ok) {
/* Record successful fencing operations */
const char *client_id = crm_element_value(dev, F_STONITH_CLIENTID);
op = create_remote_stonith_op(client_id, dev, TRUE);
}
if (op == NULL) {
/* Could be for an event that began before we started */
/* TODO: Record the op for later querying */
crm_info("Received peer result of unknown or expired operation %s", id);
return -EOPNOTSUPP;
}
if (op->devices && device && safe_str_neq(op->devices->data, device)) {
crm_err("Received outdated reply for device %s (instead of %s) to "
"fence (%s) %s. Operation already timed out at peer level.",
device, op->devices->data, op->action, op->target);
return rc;
}
if (safe_str_eq(crm_element_value(msg, F_SUBTYPE), "broadcast")) {
crm_debug("Marking call to %s for %s on behalf of %s@%s.%.8s: %s (%d)",
op->action, op->target, op->client_name, op->id, op->originator,
pcmk_strerror(rc), rc);
if (rc == pcmk_ok) {
op->state = st_done;
} else {
op->state = st_failed;
}
remote_op_done(op, msg, rc, FALSE);
return pcmk_ok;
} else if (safe_str_neq(op->originator, stonith_our_uname)) {
/* If this isn't a remote level broadcast, and we are not the
* originator of the operation, we should not be receiving this msg. */
crm_err
("%s received non-broadcast fencing result for operation it does not own (device %s targeting %s)",
stonith_our_uname, device, op->target);
return rc;
}
if (is_set(op->call_options, st_opt_topology)) {
const char *device = crm_element_value(msg, F_STONITH_DEVICE);
crm_notice("Call to %s for '%s %s' on behalf of %s@%s: %s (%d)",
device, op->target, op->action, op->client_name, op->originator,
pcmk_strerror(rc), rc);
/* We own the op, and it is complete. broadcast the result to all nodes
* and notify our local clients. */
if (op->state == st_done) {
remote_op_done(op, msg, rc, FALSE);
return rc;
}
if ((op->phase == 2) && (rc != pcmk_ok)) {
/* A remapped "on" failed, but the node was already turned off
* successfully, so ignore the error and continue.
*/
crm_warn("Ignoring %s 'on' failure (exit code %d) for %s after successful 'off'",
device, rc, op->target);
rc = pcmk_ok;
}
if (rc == pcmk_ok) {
/* An operation completed successfully. Try another device if
* necessary, otherwise mark the operation as done. */
advance_op_topology(op, device, msg, rc);
return rc;
} else {
/* This device failed, time to try another topology level. If no other
* levels are available, mark this operation as failed and report results. */
if (stonith_topology_next(op) != pcmk_ok) {
op->state = st_failed;
remote_op_done(op, msg, rc, FALSE);
return rc;
}
}
} else if (rc == pcmk_ok && op->devices == NULL) {
crm_trace("All done for %s", op->target);
op->state = st_done;
remote_op_done(op, msg, rc, FALSE);
return rc;
} else if (rc == -ETIME && op->devices == NULL) {
/* If the operation timed out don't bother retrying other peers. */
op->state = st_failed;
remote_op_done(op, msg, rc, FALSE);
return rc;
} else {
/* fall-through and attempt other fencing action using another peer */
}
/* Retry on failure */
crm_trace("Next for %s on behalf of %s@%s (rc was %d)", op->target, op->originator,
op->client_name, rc);
call_remote_stonith(op, NULL);
return rc;
}
int
stonith_fence_history(xmlNode * msg, xmlNode ** output)
{
int rc = 0;
const char *target = NULL;
xmlNode *dev = get_xpath_object("//@" F_STONITH_TARGET, msg, LOG_TRACE);
char *nodename = NULL;
if (dev) {
int options = 0;
target = crm_element_value(dev, F_STONITH_TARGET);
crm_element_value_int(msg, F_STONITH_CALLOPTS, &options);
if (target && (options & st_opt_cs_nodeid)) {
int nodeid = crm_atoi(target, NULL);
nodename = stonith_get_peer_name(nodeid);
if (nodename) {
target = nodename;
}
}
}
crm_trace("Looking for operations on %s in %p", target, remote_op_list);
*output = create_xml_node(NULL, F_STONITH_HISTORY_LIST);
if (remote_op_list) {
GHashTableIter iter;
remote_fencing_op_t *op = NULL;
g_hash_table_iter_init(&iter, remote_op_list);
while (g_hash_table_iter_next(&iter, NULL, (void **)&op)) {
xmlNode *entry = NULL;
if (target && strcmp(op->target, target) != 0) {
continue;
}
rc = 0;
crm_trace("Attaching op %s", op->id);
entry = create_xml_node(*output, STONITH_OP_EXEC);
crm_xml_add(entry, F_STONITH_TARGET, op->target);
crm_xml_add(entry, F_STONITH_ACTION, op->action);
crm_xml_add(entry, F_STONITH_ORIGIN, op->originator);
crm_xml_add(entry, F_STONITH_DELEGATE, op->delegate);
crm_xml_add(entry, F_STONITH_CLIENTNAME, op->client_name);
crm_xml_add_int(entry, F_STONITH_DATE, op->completed);
crm_xml_add_int(entry, F_STONITH_STATE, op->state);
}
}
free(nodename);
return rc;
}
gboolean
stonith_check_fence_tolerance(int tolerance, const char *target, const char *action)
{
GHashTableIter iter;
time_t now = time(NULL);
remote_fencing_op_t *rop = NULL;
crm_trace("tolerance=%d, remote_op_list=%p", tolerance, remote_op_list);
if (tolerance <= 0 || !remote_op_list || target == NULL || action == NULL) {
return FALSE;
}
g_hash_table_iter_init(&iter, remote_op_list);
while (g_hash_table_iter_next(&iter, NULL, (void **)&rop)) {
if (strcmp(rop->target, target) != 0) {
continue;
} else if (rop->state != st_done) {
continue;
/* We don't have to worry about remapped reboots here
* because if state is done, any remapping has been undone
*/
} else if (strcmp(rop->action, action) != 0) {
continue;
} else if ((rop->completed + tolerance) < now) {
continue;
}
crm_notice("Target %s was fenced (%s) less than %ds ago by %s on behalf of %s",
target, action, tolerance, rop->delegate, rop->originator);
return TRUE;
}
return FALSE;
}
diff --git a/lib/cib/cib_client.c b/lib/cib/cib_client.c
index f7a19b8317..315be016e7 100644
--- a/lib/cib/cib_client.c
+++ b/lib/cib/cib_client.c
@@ -1,653 +1,653 @@
/*
* Copyright (c) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <crm_internal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <pwd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <glib.h>
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
GHashTable *cib_op_callback_table = NULL;
int cib_client_set_op_callback(cib_t * cib, void (*callback) (const xmlNode * msg, int call_id,
int rc, xmlNode * output));
int cib_client_add_notify_callback(cib_t * cib, const char *event,
void (*callback) (const char *event, xmlNode * msg));
int cib_client_del_notify_callback(cib_t * cib, const char *event,
void (*callback) (const char *event, xmlNode * msg));
gint ciblib_GCompareFunc(gconstpointer a, gconstpointer b);
#define op_common(cib) do { \
if(cib == NULL) { \
return -EINVAL; \
} else if(cib->delegate_fn == NULL) { \
return -EPROTONOSUPPORT; \
} \
} while(0)
static int
cib_client_noop(cib_t * cib, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CRM_OP_NOOP, NULL, NULL, NULL, NULL, call_options, NULL);
}
static int
cib_client_ping(cib_t * cib, xmlNode ** output_data, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CRM_OP_PING, NULL, NULL, NULL, output_data, call_options, NULL);
}
static int
cib_client_query(cib_t * cib, const char *section, xmlNode ** output_data, int call_options)
{
return cib->cmds->query_from(cib, NULL, section, output_data, call_options);
}
static int
cib_client_query_from(cib_t * cib, const char *host, const char *section,
xmlNode ** output_data, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_QUERY, host, section, NULL, output_data, call_options, NULL);
}
static int
cib_client_is_master(cib_t * cib)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_ISMASTER, NULL, NULL, NULL, NULL,
cib_scope_local | cib_sync_call, NULL);
}
static int
cib_client_set_slave(cib_t * cib, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_SLAVE, NULL, NULL, NULL, NULL, call_options, NULL);
}
static int
cib_client_set_slave_all(cib_t * cib, int call_options)
{
return -EPROTONOSUPPORT;
}
static int
cib_client_set_master(cib_t * cib, int call_options)
{
op_common(cib);
crm_trace("Adding cib_scope_local to options");
return cib_internal_op(cib, CIB_OP_MASTER, NULL, NULL, NULL, NULL,
call_options | cib_scope_local, NULL);
}
static int
cib_client_bump_epoch(cib_t * cib, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_BUMP, NULL, NULL, NULL, NULL, call_options, NULL);
}
static int
cib_client_upgrade(cib_t * cib, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_UPGRADE, NULL, NULL, NULL, NULL, call_options, NULL);
}
static int
cib_client_sync(cib_t * cib, const char *section, int call_options)
{
return cib->cmds->sync_from(cib, NULL, section, call_options);
}
static int
cib_client_sync_from(cib_t * cib, const char *host, const char *section, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_SYNC, host, section, NULL, NULL, call_options, NULL);
}
static int
cib_client_create(cib_t * cib, const char *section, xmlNode * data, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_CREATE, NULL, section, data, NULL, call_options, NULL);
}
static int
cib_client_modify(cib_t * cib, const char *section, xmlNode * data, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_MODIFY, NULL, section, data, NULL, call_options, NULL);
}
static int
cib_client_update(cib_t * cib, const char *section, xmlNode * data, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_MODIFY, NULL, section, data, NULL, call_options, NULL);
}
static int
cib_client_replace(cib_t * cib, const char *section, xmlNode * data, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_REPLACE, NULL, section, data, NULL, call_options, NULL);
}
static int
cib_client_delete(cib_t * cib, const char *section, xmlNode * data, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_DELETE, NULL, section, data, NULL, call_options, NULL);
}
static int
cib_client_delete_absolute(cib_t * cib, const char *section, xmlNode * data, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_DELETE_ALT, NULL, section, data, NULL, call_options, NULL);
}
static int
cib_client_erase(cib_t * cib, xmlNode ** output_data, int call_options)
{
op_common(cib);
return cib_internal_op(cib, CIB_OP_ERASE, NULL, NULL, NULL, output_data, call_options, NULL);
}
static void
cib_destroy_op_callback(gpointer data)
{
cib_callback_client_t *blob = data;
if (blob->timer && blob->timer->ref > 0) {
g_source_remove(blob->timer->ref);
}
free(blob->timer);
if (blob->user_data && blob->free_func) {
blob->free_func(blob->user_data);
}
free(blob);
}
char *
get_shadow_file(const char *suffix)
{
char *cib_home = NULL;
char *fullname = NULL;
char *name = crm_concat("shadow", suffix, '.');
const char *dir = getenv("CIB_shadow_dir");
if (dir == NULL) {
uid_t uid = geteuid();
struct passwd *pwent = getpwuid(uid);
const char *user = NULL;
if (pwent) {
user = pwent->pw_name;
} else {
crm_perror(LOG_ERR, "Cannot get password entry for uid: %d", uid);
user = getenv("USER");
}
if (safe_str_eq(user, "root") || safe_str_eq(user, CRM_DAEMON_USER)) {
dir = CRM_CONFIG_DIR;
} else {
const char *home = NULL;
if ((home = getenv("HOME")) == NULL) {
if (pwent) {
home = pwent->pw_dir;
}
}
if ((dir = getenv("TMPDIR")) == NULL) {
dir = "/tmp";
}
if (home && home[0] == '/') {
int rc = 0;
cib_home = crm_concat(home, ".cib", '/');
rc = mkdir(cib_home, 0700);
if (rc < 0 && errno != EEXIST) {
crm_perror(LOG_ERR, "Couldn't create user-specific shadow directory: %s",
cib_home);
errno = 0;
} else {
dir = cib_home;
}
}
}
}
fullname = crm_concat(dir, name, '/');
free(cib_home);
free(name);
return fullname;
}
cib_t *
cib_shadow_new(const char *shadow)
{
cib_t *new_cib = NULL;
char *shadow_file = NULL;
CRM_CHECK(shadow != NULL, return NULL);
shadow_file = get_shadow_file(shadow);
new_cib = cib_file_new(shadow_file);
free(shadow_file);
return new_cib;
}
cib_t *
cib_new_no_shadow(void)
{
unsetenv("CIB_shadow");
return cib_new();
}
cib_t *
cib_new(void)
{
const char *value = getenv("CIB_shadow");
if (value && value[0] != 0) {
return cib_shadow_new(value);
}
value = getenv("CIB_file");
if (value) {
return cib_file_new(value);
}
value = getenv("CIB_port");
if (value) {
gboolean encrypted = TRUE;
int port = crm_parse_int(value, NULL);
const char *server = getenv("CIB_server");
const char *user = getenv("CIB_user");
const char *pass = getenv("CIB_passwd");
value = getenv("CIB_encrypted");
if (value && crm_is_true(value) == FALSE) {
crm_info("Disabling TLS");
encrypted = FALSE;
}
if (user == NULL) {
user = CRM_DAEMON_USER;
crm_info("Defaulting to user: %s", user);
}
if (server == NULL) {
server = "localhost";
crm_info("Defaulting to localhost");
}
return cib_remote_new(server, user, pass, port, encrypted);
}
return cib_native_new();
}
-/*
+/*!
* \internal
* \brief Create a generic CIB connection instance
*
* \return Newly allocated and initialized cib_t instance
*
* \note This is called by each variant's cib_*_new() function before setting
* variant-specific values.
*/
cib_t *
cib_new_variant(void)
{
cib_t *new_cib = NULL;
new_cib = calloc(1, sizeof(cib_t));
if (cib_op_callback_table != NULL) {
g_hash_table_destroy(cib_op_callback_table);
cib_op_callback_table = NULL;
}
if (cib_op_callback_table == NULL) {
cib_op_callback_table = g_hash_table_new_full(g_direct_hash, g_direct_equal,
NULL, cib_destroy_op_callback);
}
new_cib->call_id = 1;
new_cib->variant = cib_undefined;
new_cib->type = cib_no_connection;
new_cib->state = cib_disconnected;
new_cib->op_callback = NULL;
new_cib->variant_opaque = NULL;
new_cib->notify_list = NULL;
/* the rest will get filled in by the variant constructor */
new_cib->cmds = calloc(1, sizeof(cib_api_operations_t));
new_cib->cmds->set_op_callback = cib_client_set_op_callback;
new_cib->cmds->add_notify_callback = cib_client_add_notify_callback;
new_cib->cmds->del_notify_callback = cib_client_del_notify_callback;
new_cib->cmds->register_callback = cib_client_register_callback;
new_cib->cmds->register_callback_full = cib_client_register_callback_full;
new_cib->cmds->noop = cib_client_noop;
new_cib->cmds->ping = cib_client_ping;
new_cib->cmds->query = cib_client_query;
new_cib->cmds->sync = cib_client_sync;
new_cib->cmds->query_from = cib_client_query_from;
new_cib->cmds->sync_from = cib_client_sync_from;
new_cib->cmds->is_master = cib_client_is_master;
new_cib->cmds->set_master = cib_client_set_master;
new_cib->cmds->set_slave = cib_client_set_slave;
new_cib->cmds->set_slave_all = cib_client_set_slave_all;
new_cib->cmds->upgrade = cib_client_upgrade;
new_cib->cmds->bump_epoch = cib_client_bump_epoch;
new_cib->cmds->create = cib_client_create;
new_cib->cmds->modify = cib_client_modify;
new_cib->cmds->update = cib_client_update;
new_cib->cmds->replace = cib_client_replace;
new_cib->cmds->delete = cib_client_delete;
new_cib->cmds->erase = cib_client_erase;
new_cib->cmds->delete_absolute = cib_client_delete_absolute;
return new_cib;
}
void
cib_delete(cib_t * cib)
{
GList *list = NULL;
if(cib) {
list = cib->notify_list;
}
while (list != NULL) {
cib_notify_client_t *client = g_list_nth_data(list, 0);
list = g_list_remove(list, client);
free(client);
}
if(cib_op_callback_table) {
g_hash_table_destroy(cib_op_callback_table);
cib_op_callback_table = NULL;
}
if(cib) {
cib->cmds->free(cib);
}
}
int
cib_client_set_op_callback(cib_t * cib, void (*callback) (const xmlNode * msg, int call_id,
int rc, xmlNode * output))
{
if (callback == NULL) {
crm_info("Un-Setting operation callback");
} else {
crm_trace("Setting operation callback");
}
cib->op_callback = callback;
return pcmk_ok;
}
int
cib_client_add_notify_callback(cib_t * cib, const char *event,
void (*callback) (const char *event, xmlNode * msg))
{
GList *list_item = NULL;
cib_notify_client_t *new_client = NULL;
if (cib->variant != cib_native && cib->variant != cib_remote) {
return -EPROTONOSUPPORT;
}
crm_trace("Adding callback for %s events (%d)", event, g_list_length(cib->notify_list));
new_client = calloc(1, sizeof(cib_notify_client_t));
new_client->event = event;
new_client->callback = callback;
list_item = g_list_find_custom(cib->notify_list, new_client, ciblib_GCompareFunc);
if (list_item != NULL) {
crm_warn("Callback already present");
free(new_client);
return -ENOTUNIQ;
} else {
cib->notify_list = g_list_append(cib->notify_list, new_client);
cib->cmds->register_notification(cib, event, 1);
crm_trace("Callback added (%d)", g_list_length(cib->notify_list));
}
return pcmk_ok;
}
int
cib_client_del_notify_callback(cib_t * cib, const char *event,
void (*callback) (const char *event, xmlNode * msg))
{
GList *list_item = NULL;
cib_notify_client_t *new_client = NULL;
if (cib->variant != cib_native && cib->variant != cib_remote) {
return -EPROTONOSUPPORT;
}
crm_debug("Removing callback for %s events", event);
new_client = calloc(1, sizeof(cib_notify_client_t));
new_client->event = event;
new_client->callback = callback;
list_item = g_list_find_custom(cib->notify_list, new_client, ciblib_GCompareFunc);
cib->cmds->register_notification(cib, event, 0);
if (list_item != NULL) {
cib_notify_client_t *list_client = list_item->data;
cib->notify_list = g_list_remove(cib->notify_list, list_client);
free(list_client);
crm_trace("Removed callback");
} else {
crm_trace("Callback not present");
}
free(new_client);
return pcmk_ok;
}
gint
ciblib_GCompareFunc(gconstpointer a, gconstpointer b)
{
int rc = 0;
const cib_notify_client_t *a_client = a;
const cib_notify_client_t *b_client = b;
CRM_CHECK(a_client->event != NULL && b_client->event != NULL, return 0);
rc = strcmp(a_client->event, b_client->event);
if (rc == 0) {
if (a_client->callback == b_client->callback) {
return 0;
} else if (((long)a_client->callback) < ((long)b_client->callback)) {
crm_trace("callbacks for %s are not equal: %p < %p",
a_client->event, a_client->callback, b_client->callback);
return -1;
}
crm_trace("callbacks for %s are not equal: %p > %p",
a_client->event, a_client->callback, b_client->callback);
return 1;
}
return rc;
}
static gboolean
cib_async_timeout_handler(gpointer data)
{
struct timer_rec_s *timer = data;
crm_debug("Async call %d timed out after %ds", timer->call_id, timer->timeout);
cib_native_callback(timer->cib, NULL, timer->call_id, -ETIME);
/* Always return TRUE, never remove the handler
* We do that in remove_cib_op_callback()
*/
return TRUE;
}
gboolean
cib_client_register_callback(cib_t * cib, int call_id, int timeout, gboolean only_success,
void *user_data, const char *callback_name,
void (*callback) (xmlNode *, int, int, xmlNode *, void *))
{
return cib_client_register_callback_full(cib, call_id, timeout,
only_success, user_data,
callback_name, callback, NULL);
}
gboolean
cib_client_register_callback_full(cib_t *cib, int call_id, int timeout,
gboolean only_success, void *user_data,
const char *callback_name,
void (*callback)(xmlNode *, int, int,
xmlNode *, void *),
void (*free_func)(void *))
{
cib_callback_client_t *blob = NULL;
if (call_id < 0) {
if (only_success == FALSE) {
callback(NULL, call_id, call_id, NULL, user_data);
} else {
crm_warn("CIB call failed: %s", pcmk_strerror(call_id));
}
if (user_data && free_func) {
free_func(user_data);
}
return FALSE;
}
blob = calloc(1, sizeof(cib_callback_client_t));
blob->id = callback_name;
blob->only_success = only_success;
blob->user_data = user_data;
blob->callback = callback;
blob->free_func = free_func;
if (timeout > 0) {
struct timer_rec_s *async_timer = NULL;
async_timer = calloc(1, sizeof(struct timer_rec_s));
blob->timer = async_timer;
async_timer->cib = cib;
async_timer->call_id = call_id;
async_timer->timeout = timeout * 1000;
async_timer->ref =
g_timeout_add(async_timer->timeout, cib_async_timeout_handler, async_timer);
}
crm_trace("Adding callback %s for call %d", callback_name, call_id);
g_hash_table_insert(cib_op_callback_table, GINT_TO_POINTER(call_id), blob);
return TRUE;
}
void
remove_cib_op_callback(int call_id, gboolean all_callbacks)
{
if (all_callbacks) {
if (cib_op_callback_table != NULL) {
g_hash_table_destroy(cib_op_callback_table);
}
cib_op_callback_table = g_hash_table_new_full(g_direct_hash, g_direct_equal,
NULL, cib_destroy_op_callback);
} else {
g_hash_table_remove(cib_op_callback_table, GINT_TO_POINTER(call_id));
}
}
int
num_cib_op_callbacks(void)
{
if (cib_op_callback_table == NULL) {
return 0;
}
return g_hash_table_size(cib_op_callback_table);
}
static void
cib_dump_pending_op(gpointer key, gpointer value, gpointer user_data)
{
int call = GPOINTER_TO_INT(key);
cib_callback_client_t *blob = value;
crm_debug("Call %d (%s): pending", call, crm_str(blob->id));
}
void
cib_dump_pending_callbacks(void)
{
if (cib_op_callback_table == NULL) {
return;
}
return g_hash_table_foreach(cib_op_callback_table, cib_dump_pending_op, NULL);
}
diff --git a/lib/common/digest.c b/lib/common/digest.c
index e12905eb76..b5769e7540 100644
--- a/lib/common/digest.c
+++ b/lib/common/digest.c
@@ -1,308 +1,309 @@
/*
* Copyright (C) 2015 Andrew Beekhof <andrew@beekhof.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <crm_internal.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#define BEST_EFFORT_STATUS 0
/*!
* \brief Dump XML in a format used with v1 digests
*
* \param[in] an_xml_node Root of XML to dump
*
* \return Newly allocated buffer containing dumped XML
*/
static char *
dump_xml_for_digest(xmlNode * an_xml_node)
{
char *buffer = NULL;
int offset = 0, max = 0;
/* for compatibility with the old result which is used for v1 digests */
crm_buffer_add_char(&buffer, &offset, &max, ' ');
crm_xml_dump(an_xml_node, 0, &buffer, &offset, &max, 0);
crm_buffer_add_char(&buffer, &offset, &max, '\n');
return buffer;
}
/*!
* \brief Calculate and return v1 digest of XML tree
*
* \param[in] input Root of XML to digest
* \param[in] sort Whether to sort the XML before calculating digest
* \param[in] ignored Not used
*
* \return Newly allocated string containing digest
* \note Example return value: "c048eae664dba840e1d2060f00299e9d"
*/
static char *
calculate_xml_digest_v1(xmlNode * input, gboolean sort, gboolean ignored)
{
char *digest = NULL;
char *buffer = NULL;
xmlNode *copy = NULL;
if (sort) {
crm_trace("Sorting xml...");
copy = sorted_xml(input, NULL, TRUE);
crm_trace("Done");
input = copy;
}
buffer = dump_xml_for_digest(input);
CRM_CHECK(buffer != NULL && strlen(buffer) > 0, free_xml(copy);
free(buffer);
return NULL);
digest = crm_md5sum(buffer);
crm_log_xml_trace(input, "digest:source");
free(buffer);
free_xml(copy);
return digest;
}
/*!
* \brief Calculate and return v2 digest of XML tree
*
* \param[in] source Root of XML to digest
* \param[in] do_filter Whether to filter certain XML attributes
*
* \return Newly allocated string containing digest
*/
static char *
calculate_xml_digest_v2(xmlNode * source, gboolean do_filter)
{
char *digest = NULL;
char *buffer = NULL;
int offset, max;
static struct qb_log_callsite *digest_cs = NULL;
crm_trace("Begin digest %s", do_filter?"filtered":"");
if (do_filter && BEST_EFFORT_STATUS) {
/* Exclude the status calculation from the digest
*
* This doesn't mean it won't be sync'd, we just won't be paranoid
* about it being an _exact_ copy
*
* We don't need it to be exact, since we throw it away and regenerate
* from our peers whenever a new DC is elected anyway
*
* Importantly, this reduces the amount of XML to copy+export as
* well as the amount of data for MD5 needs to operate on
*/
} else {
crm_xml_dump(source, do_filter ? xml_log_option_filtered : 0, &buffer, &offset, &max, 0);
}
CRM_ASSERT(buffer != NULL);
digest = crm_md5sum(buffer);
if (digest_cs == NULL) {
digest_cs = qb_log_callsite_get(__func__, __FILE__, "cib-digest", LOG_TRACE, __LINE__,
crm_trace_nonlog);
}
if (digest_cs && digest_cs->targets) {
char *trace_file = crm_concat("/tmp/digest", digest, '-');
crm_trace("Saving %s.%s.%s to %s",
crm_element_value(source, XML_ATTR_GENERATION_ADMIN),
crm_element_value(source, XML_ATTR_GENERATION),
crm_element_value(source, XML_ATTR_NUMUPDATES), trace_file);
save_xml_to_file(source, "digest input", trace_file);
free(trace_file);
}
free(buffer);
crm_trace("End digest");
return digest;
}
/*!
* \brief Calculate and return digest of XML tree, suitable for storing on disk
*
* \param[in] input Root of XML to digest
*
* \return Newly allocated string containing digest
*/
char *
calculate_on_disk_digest(xmlNode * input)
{
/* Always use the v1 format for on-disk digests
* a) it's a compatibility nightmare
* b) we only use this once at startup, all other
* invocations are in a separate child process
*/
return calculate_xml_digest_v1(input, FALSE, FALSE);
}
/*!
* \brief Calculate and return digest of XML operation
*
* \param[in] input Root of XML to digest
* \param[in] version Not used
*
* \return Newly allocated string containing digest
*/
char *
calculate_operation_digest(xmlNode *input, const char *version)
{
/* We still need the sorting for operation digests */
return calculate_xml_digest_v1(input, TRUE, FALSE);
}
/*!
* \brief Calculate and return digest of XML tree
*
* \param[in] input Root of XML to digest
* \param[in] sort Whether to sort XML before calculating digest
* \param[in] do_filter Whether to filter certain XML attributes
* \param[in] version CRM feature set version (used to select v1/v2 digest)
*
* \return Newly allocated string containing digest
*/
char *
calculate_xml_versioned_digest(xmlNode * input, gboolean sort, gboolean do_filter,
const char *version)
{
/*
* The sorting associated with v1 digest creation accounted for 23% of
* the CIB's CPU usage on the server. v2 drops this.
*
* The filtering accounts for an additional 2.5% and we may want to
* remove it in future.
*
* v2 also uses the xmlBuffer contents directly to avoid additional copying
*/
if (version == NULL || compare_version("3.0.5", version) > 0) {
crm_trace("Using v1 digest algorithm for %s", crm_str(version));
return calculate_xml_digest_v1(input, sort, do_filter);
}
crm_trace("Using v2 digest algorithm for %s", crm_str(version));
return calculate_xml_digest_v2(input, do_filter);
}
/*!
+ * \internal
* \brief Return whether calculated digest of XML tree matches expected digest
*
* \param[in] input Root of XML to digest
* \param[in] expected Expected digest in on-disk format
*
* \return TRUE if digests match, FALSE otherwise or on error
*/
gboolean
crm_digest_verify(xmlNode *input, const char *expected)
{
char *calculated = NULL;
gboolean passed;
if (input != NULL) {
calculated = calculate_on_disk_digest(input);
if (calculated == NULL) {
crm_perror(LOG_ERR, "Could not calculate digest for comparison");
return FALSE;
}
}
passed = safe_str_eq(expected, calculated);
if (passed) {
crm_trace("Digest comparison passed: %s", calculated);
} else {
crm_err("Digest comparison failed: expected %s, calculated %s",
expected, calculated);
}
free(calculated);
return passed;
}
char*
crm_versioned_param_summary(xmlNode *versioned_params, const char *name)
{
xmlNode *attrs = NULL;
xmlNode *attr = NULL;
char *summary = NULL;
gboolean found = FALSE;
if (!name) {
return NULL;
}
for (attrs = __xml_first_child(versioned_params); attrs != NULL; attrs = __xml_next_element(attrs)) {
for (attr = __xml_first_child(attrs); attr != NULL; attr = __xml_next_element(attr)) {
if (safe_str_eq((const char*) attr->name, XML_TAG_RULE)) {
const char *boolean_op = crm_element_value(attr, XML_RULE_ATTR_BOOLEAN_OP);
xmlNode *expr = NULL;
if (boolean_op == NULL) {
boolean_op = "and";
}
summary = add_list_element(summary, boolean_op);
for (expr = __xml_first_child(attr); expr != NULL; expr = __xml_next_element(expr)) {
summary = add_list_element(summary, crm_element_value(expr, XML_EXPR_ATTR_OPERATION));
summary = add_list_element(summary, crm_element_value(expr, XML_EXPR_ATTR_VALUE));
}
} else if (safe_str_eq(crm_element_value(attr, XML_NVPAIR_ATTR_NAME), name)) {
found = TRUE;
summary = add_list_element(summary, crm_element_value(attr, XML_NVPAIR_ATTR_VALUE));
break;
}
}
}
if (!found) {
free(summary);
return NULL;
}
return summary;
}
void
crm_summarize_versioned_params(xmlNode *param_set, xmlNode *versioned_params)
{
xmlNode *attrs = NULL;
xmlNode *attr = NULL;
if (!param_set) {
return;
}
for (attrs = __xml_first_child(versioned_params); attrs != NULL; attrs = __xml_next_element(attrs)) {
for (attr = __xml_first_child(attrs); attr != NULL; attr = __xml_next_element(attr)) {
const char *attr_name = crm_element_value(attr, XML_NVPAIR_ATTR_NAME);
if (attr_name) {
char *summary = crm_versioned_param_summary(versioned_params, attr_name);
crm_xml_replace(param_set, attr_name, summary);
free(summary);
}
}
}
}
diff --git a/lib/common/procfs.c b/lib/common/procfs.c
index 2d25efb599..12d01ff7a7 100644
--- a/lib/common/procfs.c
+++ b/lib/common/procfs.c
@@ -1,142 +1,142 @@
/*
* Copyright (C) 2015 Andrew Beekhof <andrew@beekhof.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <crm_internal.h>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dirent.h>
-/*
+/*!
* \internal
* \brief Get process ID and name associated with a /proc directory entry
*
* \param[in] entry Directory entry (must be result of readdir() on /proc)
* \param[out] name If not NULL, a char[64] to hold the process name
* \param[out] pid If not NULL, will be set to process ID of entry
*
* \return 0 on success, -1 if entry is not for a process or info not found
*
* \note This should be called only on Linux systems, as not all systems that
* support /proc store process names and IDs in the same way.
*/
int
crm_procfs_process_info(struct dirent *entry, char *name, int *pid)
{
int fd, local_pid;
FILE *file;
struct stat statbuf;
char key[16] = { 0 }, procpath[128] = { 0 };
/* We're only interested in entries whose name is a PID,
* so skip anything non-numeric or that is too long.
*
* 114 = 128 - strlen("/proc/") - strlen("/status") - 1
*/
local_pid = atoi(entry->d_name);
if ((local_pid <= 0) || (strlen(entry->d_name) > 114)) {
return -1;
}
if (pid) {
*pid = local_pid;
}
/* Get this entry's file information */
strcpy(procpath, "/proc/");
strcat(procpath, entry->d_name);
fd = open(procpath, O_RDONLY);
if (fd < 0 ) {
return -1;
}
if (fstat(fd, &statbuf) < 0) {
close(fd);
return -1;
}
close(fd);
/* We're only interested in subdirectories */
if (!S_ISDIR(statbuf.st_mode)) {
return -1;
}
/* Read the first entry ("Name:") from the process's status file.
* We could handle the valgrind case if we parsed the cmdline file
* instead, but that's more of a pain than it's worth.
*/
if (name != NULL) {
strcat(procpath, "/status");
file = fopen(procpath, "r");
if (!file) {
return -1;
}
if ((fscanf(file, "%15s%63s", key, name) != 2)
|| safe_str_neq(key, "Name:")) {
fclose(file);
return -1;
}
fclose(file);
}
return 0;
}
-/*
+/*!
* \internal
* \brief Return process ID of a named process
*
* \param[in] name Process name (as used in /proc/.../status)
*
* \return Process ID of named process if running, 0 otherwise
*
* \note This will return 0 if the process is being run via valgrind.
* This should be called only on Linux systems.
*/
int
crm_procfs_pid_of(const char *name)
{
DIR *dp;
struct dirent *entry;
int pid = 0;
char entry_name[64] = { 0 };
dp = opendir("/proc");
if (dp == NULL) {
crm_notice("Can not read /proc directory to track existing components");
return 0;
}
while ((entry = readdir(dp)) != NULL) {
if ((crm_procfs_process_info(entry, entry_name, &pid) == 0)
&& safe_str_eq(entry_name, name)
&& (crm_pid_active(pid, NULL) == 1)) {
crm_info("Found %s active as process %d", name, pid);
break;
}
pid = 0;
}
closedir(dp);
return pid;
}
diff --git a/lib/common/xml.c b/lib/common/xml.c
index e99b901f91..8d8f0780f4 100644
--- a/lib/common/xml.c
+++ b/lib/common/xml.c
@@ -1,5853 +1,5853 @@
/*
* Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
#include <crm_internal.h>
#include <sys/param.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <time.h>
#include <string.h>
#include <dirent.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <ctype.h>
#include <math.h>
#include <crm/crm.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#include <libxml/xmlreader.h>
#if HAVE_BZLIB_H
# include <bzlib.h>
#endif
#if HAVE_LIBXML2
# include <libxml/parser.h>
# include <libxml/tree.h>
# include <libxml/relaxng.h>
#endif
#if HAVE_LIBXSLT
# include <libxslt/xslt.h>
# include <libxslt/transform.h>
#endif
#define XML_BUFFER_SIZE 4096
#define XML_PARSER_DEBUG 0
void
xml_log(int priority, const char *fmt, ...)
G_GNUC_PRINTF(2, 3);
static inline int
__get_prefix(const char *prefix, xmlNode *xml, char *buffer, int offset);
void
xml_log(int priority, const char *fmt, ...)
{
va_list ap;
va_start(ap, fmt);
qb_log_from_external_source_va(__FUNCTION__, __FILE__, fmt, priority, __LINE__, 0, ap);
va_end(ap);
}
typedef struct {
xmlRelaxNGPtr rng;
xmlRelaxNGValidCtxtPtr valid;
xmlRelaxNGParserCtxtPtr parser;
} relaxng_ctx_cache_t;
struct schema_s {
int type;
float version;
char *name;
char *location;
char *transform;
int after_transform;
void *cache;
};
typedef struct {
int found;
const char *string;
} filter_t;
enum xml_private_flags {
xpf_none = 0x0000,
xpf_dirty = 0x0001,
xpf_deleted = 0x0002,
xpf_created = 0x0004,
xpf_modified = 0x0008,
xpf_tracking = 0x0010,
xpf_processed = 0x0020,
xpf_skip = 0x0040,
xpf_moved = 0x0080,
xpf_acl_enabled = 0x0100,
xpf_acl_read = 0x0200,
xpf_acl_write = 0x0400,
xpf_acl_deny = 0x0800,
xpf_acl_create = 0x1000,
xpf_acl_denied = 0x2000,
};
typedef struct xml_private_s
{
long check;
uint32_t flags;
char *user;
GListPtr acls;
GListPtr deleted_paths;
} xml_private_t;
typedef struct xml_acl_s {
enum xml_private_flags mode;
char *xpath;
} xml_acl_t;
/* *INDENT-OFF* */
static filter_t filter[] = {
{ 0, XML_ATTR_ORIGIN },
{ 0, XML_CIB_ATTR_WRITTEN },
{ 0, XML_ATTR_UPDATE_ORIG },
{ 0, XML_ATTR_UPDATE_CLIENT },
{ 0, XML_ATTR_UPDATE_USER },
};
/* *INDENT-ON* */
static struct schema_s *known_schemas = NULL;
static int xml_schema_max = 0;
static xmlNode *subtract_xml_comment(xmlNode * parent, xmlNode * left, xmlNode * right, gboolean * changed);
static xmlNode *find_xml_comment(xmlNode * root, xmlNode * search_comment);
static int add_xml_comment(xmlNode * parent, xmlNode * target, xmlNode * update);
static bool __xml_acl_check(xmlNode *xml, const char *name, enum xml_private_flags mode);
const char *__xml_acl_to_text(enum xml_private_flags flags);
static int
xml_latest_schema_index(void)
{
return xml_schema_max - 4;
}
static int
xml_minimum_schema_index(void)
{
static int best = 0;
if(best == 0) {
int lpc = 0;
float target = 0.0;
best = xml_latest_schema_index();
target = floor(known_schemas[best].version);
for(lpc = best; lpc > 0; lpc--) {
if(known_schemas[lpc].version < target) {
return best;
} else {
best = lpc;
}
}
best = xml_latest_schema_index();
}
return best;
}
const char *
xml_latest_schema(void)
{
return get_schema_name(xml_latest_schema_index());
}
#define CHUNK_SIZE 1024
static inline bool TRACKING_CHANGES(xmlNode *xml)
{
if(xml == NULL || xml->doc == NULL || xml->doc->_private == NULL) {
return FALSE;
} else if(is_set(((xml_private_t *)xml->doc->_private)->flags, xpf_tracking)) {
return TRUE;
}
return FALSE;
}
#define buffer_print(buffer, max, offset, fmt, args...) do { \
int rc = (max); \
if(buffer) { \
rc = snprintf((buffer) + (offset), (max) - (offset), fmt, ##args); \
} \
if(buffer && rc < 0) { \
crm_perror(LOG_ERR, "snprintf failed at offset %d", offset); \
(buffer)[(offset)] = 0; \
} else if(rc >= ((max) - (offset))) { \
char *tmp = NULL; \
(max) = QB_MAX(CHUNK_SIZE, (max) * 2); \
tmp = realloc_safe((buffer), (max) + 1); \
CRM_ASSERT(tmp); \
(buffer) = tmp; \
} else { \
offset += rc; \
break; \
} \
} while(1);
static void
insert_prefix(int options, char **buffer, int *offset, int *max, int depth)
{
if (options & xml_log_option_formatted) {
size_t spaces = 2 * depth;
if ((*buffer) == NULL || spaces >= ((*max) - (*offset))) {
(*max) = QB_MAX(CHUNK_SIZE, (*max) * 2);
(*buffer) = realloc_safe((*buffer), (*max) + 1);
}
memset((*buffer) + (*offset), ' ', spaces);
(*offset) += spaces;
}
}
static const char *
get_schema_root(void)
{
static const char *base = NULL;
if (base == NULL) {
base = getenv("PCMK_schema_directory");
}
if (base == NULL || strlen(base) == 0) {
base = CRM_DTD_DIRECTORY;
}
return base;
}
static char *
get_schema_path(const char *name, const char *file)
{
const char *base = get_schema_root();
if(file) {
return crm_strdup_printf("%s/%s", base, file);
}
return crm_strdup_printf("%s/%s.rng", base, name);
}
static int schema_filter(const struct dirent * a)
{
int rc = 0;
float version = 0;
if(strstr(a->d_name, "pacemaker-") != a->d_name) {
/* crm_trace("%s - wrong prefix", a->d_name); */
} else if (!crm_ends_with(a->d_name, ".rng")) {
/* crm_trace("%s - wrong suffix", a->d_name); */
} else if(sscanf(a->d_name, "pacemaker-%f.rng", &version) == 0) {
/* crm_trace("%s - wrong format", a->d_name); */
} else if(strcmp(a->d_name, "pacemaker-1.1.rng") == 0) {
/* "-1.1" was used for what later became "-next" */
/* crm_trace("%s - hack", a->d_name); */
} else {
/* crm_debug("%s - candidate", a->d_name); */
rc = 1;
}
return rc;
}
static int schema_sort(const struct dirent ** a, const struct dirent **b)
{
int rc = 0;
float a_version = 0.0;
float b_version = 0.0;
sscanf(a[0]->d_name, "pacemaker-%f.rng", &a_version);
sscanf(b[0]->d_name, "pacemaker-%f.rng", &b_version);
if(a_version > b_version) {
rc = 1;
} else if(a_version < b_version) {
rc = -1;
}
/* crm_trace("%s (%f) vs. %s (%f) : %d", a[0]->d_name, a_version, b[0]->d_name, b_version, rc); */
return rc;
}
static void __xml_schema_add(
int type, float version, const char *name, const char *location, const char *transform, int after_transform)
{
int last = xml_schema_max;
xml_schema_max++;
known_schemas = realloc_safe(known_schemas, xml_schema_max*sizeof(struct schema_s));
CRM_ASSERT(known_schemas != NULL);
memset(known_schemas+last, 0, sizeof(struct schema_s));
known_schemas[last].type = type;
known_schemas[last].after_transform = after_transform;
if(version > 0.0) {
known_schemas[last].version = version;
known_schemas[last].name = crm_strdup_printf("pacemaker-%.1f", version);
known_schemas[last].location = crm_strdup_printf("%s.rng", known_schemas[last].name);
} else {
char dummy[1024];
CRM_ASSERT(name);
CRM_ASSERT(location);
sscanf(name, "%[^-]-%f", dummy, &version);
known_schemas[last].version = version;
known_schemas[last].name = strdup(name);
known_schemas[last].location = strdup(location);
}
if(transform) {
known_schemas[last].transform = strdup(transform);
}
if(after_transform == 0) {
after_transform = xml_schema_max;
}
known_schemas[last].after_transform = after_transform;
if(known_schemas[last].after_transform < 0) {
crm_debug("Added supported schema %d: %s (%s)",
last, known_schemas[last].name, known_schemas[last].location);
} else if(known_schemas[last].transform) {
crm_debug("Added supported schema %d: %s (%s upgrades to %d with %s)",
last, known_schemas[last].name, known_schemas[last].location,
known_schemas[last].after_transform,
known_schemas[last].transform);
} else {
crm_debug("Added supported schema %d: %s (%s upgrades to %d)",
last, known_schemas[last].name, known_schemas[last].location,
known_schemas[last].after_transform);
}
}
static int __xml_build_schema_list(void)
{
int lpc, max;
const char *base = get_schema_root();
struct dirent **namelist = NULL;
max = scandir(base, &namelist, schema_filter, schema_sort);
__xml_schema_add(1, 0.0, "pacemaker-0.6", "crm.dtd", "upgrade06.xsl", 3);
__xml_schema_add(1, 0.0, "transitional-0.6", "crm-transitional.dtd", "upgrade06.xsl", 3);
__xml_schema_add(2, 0.0, "pacemaker-0.7", "pacemaker-1.0.rng", NULL, 0);
if (max < 0) {
crm_notice("scandir(%s) failed: %s (%d)", base, strerror(errno), errno);
} else {
for (lpc = 0; lpc < max; lpc++) {
int next = 0;
float version = 0.0;
char *transform = NULL;
sscanf(namelist[lpc]->d_name, "pacemaker-%f.rng", &version);
if((lpc + 1) < max) {
float next_version = 0.0;
sscanf(namelist[lpc+1]->d_name, "pacemaker-%f.rng", &next_version);
if(floor(version) < floor(next_version)) {
struct stat s;
char *xslt = NULL;
transform = crm_strdup_printf("upgrade-%.1f.xsl", version);
xslt = get_schema_path(NULL, transform);
if(stat(xslt, &s) != 0) {
crm_err("Transform %s not found", xslt);
free(xslt);
__xml_schema_add(2, version, NULL, NULL, NULL, -1);
break;
} else {
free(xslt);
}
}
} else {
next = -1;
}
__xml_schema_add(2, version, NULL, NULL, transform, next);
free(namelist[lpc]);
free(transform);
}
}
/* 1.1 was the old name for -next */
__xml_schema_add(2, 0.0, "pacemaker-1.1", "pacemaker-next.rng", NULL, 0);
__xml_schema_add(2, 0.0, "pacemaker-next", "pacemaker-next.rng", NULL, -1);
__xml_schema_add(0, 0.0, "none", "N/A", NULL, -1);
free(namelist);
return TRUE;
}
static void
set_parent_flag(xmlNode *xml, long flag)
{
for(; xml; xml = xml->parent) {
xml_private_t *p = xml->_private;
if(p == NULL) {
/* During calls to xmlDocCopyNode(), _private will be unset for parent nodes */
} else {
p->flags |= flag;
/* crm_trace("Setting flag %x due to %s[@id=%s]", flag, xml->name, ID(xml)); */
}
}
}
static void
set_doc_flag(xmlNode *xml, long flag)
{
if(xml && xml->doc && xml->doc->_private){
/* During calls to xmlDocCopyNode(), xml->doc may be unset */
xml_private_t *p = xml->doc->_private;
p->flags |= flag;
/* crm_trace("Setting flag %x due to %s[@id=%s]", flag, xml->name, ID(xml)); */
}
}
static void
__xml_node_dirty(xmlNode *xml)
{
set_doc_flag(xml, xpf_dirty);
set_parent_flag(xml, xpf_dirty);
}
static void
__xml_node_clean(xmlNode *xml)
{
xmlNode *cIter = NULL;
xml_private_t *p = xml->_private;
if(p) {
p->flags = 0;
}
for (cIter = __xml_first_child(xml); cIter != NULL; cIter = __xml_next(cIter)) {
__xml_node_clean(cIter);
}
}
static void
crm_node_created(xmlNode *xml)
{
xmlNode *cIter = NULL;
xml_private_t *p = xml->_private;
if(p && TRACKING_CHANGES(xml)) {
if(is_not_set(p->flags, xpf_created)) {
p->flags |= xpf_created;
__xml_node_dirty(xml);
}
for (cIter = __xml_first_child(xml); cIter != NULL; cIter = __xml_next(cIter)) {
crm_node_created(cIter);
}
}
}
static void
crm_attr_dirty(xmlAttr *a)
{
xmlNode *parent = a->parent;
xml_private_t *p = NULL;
p = a->_private;
p->flags |= (xpf_dirty|xpf_modified);
p->flags = (p->flags & ~xpf_deleted);
/* crm_trace("Setting flag %x due to %s[@id=%s, @%s=%s]", */
/* xpf_dirty, parent?parent->name:NULL, ID(parent), a->name, a->children->content); */
__xml_node_dirty(parent);
}
int get_tag_name(const char *input, size_t offset, size_t max);
int get_attr_name(const char *input, size_t offset, size_t max);
int get_attr_value(const char *input, size_t offset, size_t max);
gboolean can_prune_leaf(xmlNode * xml_node);
void diff_filter_context(int context, int upper_bound, int lower_bound,
xmlNode * xml_node, xmlNode * parent);
int in_upper_context(int depth, int context, xmlNode * xml_node);
int add_xml_object(xmlNode * parent, xmlNode * target, xmlNode * update, gboolean as_diff);
static inline const char *
crm_attr_value(xmlAttr * attr)
{
if (attr == NULL || attr->children == NULL) {
return NULL;
}
return (const char *)attr->children->content;
}
static inline xmlAttr *
crm_first_attr(xmlNode * xml)
{
if (xml == NULL) {
return NULL;
}
return xml->properties;
}
#define XML_PRIVATE_MAGIC (long) 0x81726354
static void
__xml_acl_free(void *data)
{
if(data) {
xml_acl_t *acl = data;
free(acl->xpath);
free(acl);
}
}
static void
__xml_private_clean(xml_private_t *p)
{
if(p) {
CRM_ASSERT(p->check == XML_PRIVATE_MAGIC);
free(p->user);
p->user = NULL;
if(p->acls) {
g_list_free_full(p->acls, __xml_acl_free);
p->acls = NULL;
}
if(p->deleted_paths) {
g_list_free_full(p->deleted_paths, free);
p->deleted_paths = NULL;
}
}
}
static void
__xml_private_free(xml_private_t *p)
{
__xml_private_clean(p);
free(p);
}
static void
pcmkDeregisterNode(xmlNodePtr node)
{
__xml_private_free(node->_private);
}
static void
pcmkRegisterNode(xmlNodePtr node)
{
xml_private_t *p = NULL;
switch(node->type) {
case XML_ELEMENT_NODE:
case XML_DOCUMENT_NODE:
case XML_ATTRIBUTE_NODE:
case XML_COMMENT_NODE:
p = calloc(1, sizeof(xml_private_t));
p->check = XML_PRIVATE_MAGIC;
/* Flags will be reset if necessary when tracking is enabled */
p->flags |= (xpf_dirty|xpf_created);
node->_private = p;
break;
case XML_TEXT_NODE:
case XML_DTD_NODE:
case XML_CDATA_SECTION_NODE:
break;
default:
/* Ignore */
crm_trace("Ignoring %p %d", node, node->type);
CRM_LOG_ASSERT(node->type == XML_ELEMENT_NODE);
break;
}
if(p && TRACKING_CHANGES(node)) {
/* XML_ELEMENT_NODE doesn't get picked up here, node->doc is
* not hooked up at the point we are called
*/
set_doc_flag(node, xpf_dirty);
__xml_node_dirty(node);
}
}
static xml_acl_t *
__xml_acl_create(xmlNode * xml, xmlNode *target, enum xml_private_flags mode)
{
xml_acl_t *acl = NULL;
xml_private_t *p = NULL;
const char *tag = crm_element_value(xml, XML_ACL_ATTR_TAG);
const char *ref = crm_element_value(xml, XML_ACL_ATTR_REF);
const char *xpath = crm_element_value(xml, XML_ACL_ATTR_XPATH);
if(tag == NULL) {
/* Compatibility handling for pacemaker < 1.1.12 */
tag = crm_element_value(xml, XML_ACL_ATTR_TAGv1);
}
if(ref == NULL) {
/* Compatibility handling for pacemaker < 1.1.12 */
ref = crm_element_value(xml, XML_ACL_ATTR_REFv1);
}
if(target == NULL || target->doc == NULL || target->doc->_private == NULL){
CRM_ASSERT(target);
CRM_ASSERT(target->doc);
CRM_ASSERT(target->doc->_private);
return NULL;
} else if (tag == NULL && ref == NULL && xpath == NULL) {
crm_trace("No criteria %p", xml);
return NULL;
}
p = target->doc->_private;
acl = calloc(1, sizeof(xml_acl_t));
if (acl) {
const char *attr = crm_element_value(xml, XML_ACL_ATTR_ATTRIBUTE);
acl->mode = mode;
if(xpath) {
acl->xpath = strdup(xpath);
crm_trace("Using xpath: %s", acl->xpath);
} else {
int offset = 0;
char buffer[XML_BUFFER_SIZE];
if(tag) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "//%s", tag);
} else {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "//*");
}
if(ref || attr) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "[");
}
if(ref) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "@id='%s'", ref);
}
if(ref && attr) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, " and ");
}
if(attr) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "@%s", attr);
}
if(ref || attr) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "]");
}
CRM_LOG_ASSERT(offset > 0);
acl->xpath = strdup(buffer);
crm_trace("Built xpath: %s", acl->xpath);
}
p->acls = g_list_append(p->acls, acl);
}
return acl;
}
static gboolean
__xml_acl_parse_entry(xmlNode * acl_top, xmlNode * acl_entry, xmlNode *target)
{
xmlNode *child = NULL;
for (child = __xml_first_child(acl_entry); child; child = __xml_next(child)) {
const char *tag = crm_element_name(child);
const char *kind = crm_element_value(child, XML_ACL_ATTR_KIND);
if (strcmp(XML_ACL_TAG_PERMISSION, tag) == 0){
tag = kind;
}
crm_trace("Processing %s %p", tag, child);
if(tag == NULL) {
CRM_ASSERT(tag != NULL);
} else if (strcmp(XML_ACL_TAG_ROLE_REF, tag) == 0
|| strcmp(XML_ACL_TAG_ROLE_REFv1, tag) == 0) {
const char *ref_role = crm_element_value(child, XML_ATTR_ID);
if (ref_role) {
xmlNode *role = NULL;
for (role = __xml_first_child(acl_top); role; role = __xml_next(role)) {
if (strcmp(XML_ACL_TAG_ROLE, (const char *)role->name) == 0) {
const char *role_id = crm_element_value(role, XML_ATTR_ID);
if (role_id && strcmp(ref_role, role_id) == 0) {
crm_debug("Unpacking referenced role: %s", role_id);
__xml_acl_parse_entry(acl_top, role, target);
break;
}
}
}
}
} else if (strcmp(XML_ACL_TAG_READ, tag) == 0) {
__xml_acl_create(child, target, xpf_acl_read);
} else if (strcmp(XML_ACL_TAG_WRITE, tag) == 0) {
__xml_acl_create(child, target, xpf_acl_write);
} else if (strcmp(XML_ACL_TAG_DENY, tag) == 0) {
__xml_acl_create(child, target, xpf_acl_deny);
} else {
crm_warn("Unknown ACL entry: %s/%s", tag, kind);
}
}
return TRUE;
}
/*
<acls>
<acl_target id="l33t-haxor"><role id="auto-l33t-haxor"/></acl_target>
<acl_role id="auto-l33t-haxor">
<acl_permission id="crook-nothing" kind="deny" xpath="/cib"/>
</acl_role>
<acl_target id="niceguy">
<role id="observer"/>
</acl_target>
<acl_role id="observer">
<acl_permission id="observer-read-1" kind="read" xpath="/cib"/>
<acl_permission id="observer-write-1" kind="write" xpath="//nvpair[@name='stonith-enabled']"/>
<acl_permission id="observer-write-2" kind="write" xpath="//nvpair[@name='target-role']"/>
</acl_role>
<acl_target id="badidea"><role id="auto-badidea"/></acl_target>
<acl_role id="auto-badidea">
<acl_permission id="badidea-resources" kind="read" xpath="//meta_attributes"/>
<acl_permission id="badidea-resources-2" kind="deny" reference="dummy-meta_attributes"/>
</acl_role>
</acls>
*/
const char *
__xml_acl_to_text(enum xml_private_flags flags)
{
if(is_set(flags, xpf_acl_deny)) {
return "deny";
}
if(is_set(flags, xpf_acl_write)) {
return "read/write";
}
if(is_set(flags, xpf_acl_read)) {
return "read";
}
return "none";
}
static void
__xml_acl_apply(xmlNode *xml)
{
GListPtr aIter = NULL;
xml_private_t *p = NULL;
xmlXPathObjectPtr xpathObj = NULL;
if(xml_acl_enabled(xml) == FALSE) {
p = xml->doc->_private;
crm_trace("Not applying ACLs for %s", p->user);
return;
}
p = xml->doc->_private;
for(aIter = p->acls; aIter != NULL; aIter = aIter->next) {
int max = 0, lpc = 0;
xml_acl_t *acl = aIter->data;
xpathObj = xpath_search(xml, acl->xpath);
max = numXpathResults(xpathObj);
for(lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpathObj, lpc);
char *path = xml_get_path(match);
p = match->_private;
crm_trace("Applying %x to %s for %s", acl->mode, path, acl->xpath);
#ifdef SUSE_ACL_COMPAT
if(is_not_set(p->flags, acl->mode)) {
if(is_set(p->flags, xpf_acl_read)
|| is_set(p->flags, xpf_acl_write)
|| is_set(p->flags, xpf_acl_deny)) {
crm_config_warn("Configuration element %s is matched by multiple ACL rules, only the first applies ('%s' wins over '%s')",
path, __xml_acl_to_text(p->flags), __xml_acl_to_text(acl->mode));
free(path);
continue;
}
}
#endif
p->flags |= acl->mode;
free(path);
}
crm_trace("Now enforcing ACL: %s (%d matches)", acl->xpath, max);
freeXpathObject(xpathObj);
}
p = xml->_private;
if(is_not_set(p->flags, xpf_acl_read) && is_not_set(p->flags, xpf_acl_write)) {
p->flags |= xpf_acl_deny;
p = xml->doc->_private;
crm_info("Enforcing default ACL for %s to %s", p->user, crm_element_name(xml));
}
}
static void
__xml_acl_unpack(xmlNode *source, xmlNode *target, const char *user)
{
#if ENABLE_ACL
xml_private_t *p = NULL;
if(target == NULL || target->doc == NULL || target->doc->_private == NULL) {
return;
}
p = target->doc->_private;
if(pcmk_acl_required(user) == FALSE) {
crm_trace("no acls needed for '%s'", user);
} else if(p->acls == NULL) {
xmlNode *acls = get_xpath_object("//"XML_CIB_TAG_ACLS, source, LOG_TRACE);
free(p->user);
p->user = strdup(user);
if(acls) {
xmlNode *child = NULL;
for (child = __xml_first_child(acls); child; child = __xml_next(child)) {
const char *tag = crm_element_name(child);
if (strcmp(tag, XML_ACL_TAG_USER) == 0 || strcmp(tag, XML_ACL_TAG_USERv1) == 0) {
const char *id = crm_element_value(child, XML_ATTR_ID);
if(id && strcmp(id, user) == 0) {
crm_debug("Unpacking ACLs for %s", id);
__xml_acl_parse_entry(acls, child, target);
}
}
}
}
}
#endif
}
static inline bool
__xml_acl_mode_test(enum xml_private_flags allowed, enum xml_private_flags requested)
{
if(is_set(allowed, xpf_acl_deny)) {
return FALSE;
} else if(is_set(allowed, requested)) {
return TRUE;
} else if(is_set(requested, xpf_acl_read) && is_set(allowed, xpf_acl_write)) {
return TRUE;
} else if(is_set(requested, xpf_acl_create) && is_set(allowed, xpf_acl_write)) {
return TRUE;
} else if(is_set(requested, xpf_acl_create) && is_set(allowed, xpf_created)) {
return TRUE;
}
return FALSE;
}
/* rc = TRUE if orig_cib has been filtered
* That means '*result' rather than 'xml' should be exploited afterwards
*/
static bool
__xml_purge_attributes(xmlNode *xml)
{
xmlNode *child = NULL;
xmlAttr *xIter = NULL;
bool readable_children = FALSE;
xml_private_t *p = xml->_private;
if(__xml_acl_mode_test(p->flags, xpf_acl_read)) {
crm_trace("%s[@id=%s] is readable", crm_element_name(xml), ID(xml));
return TRUE;
}
xIter = crm_first_attr(xml);
while(xIter != NULL) {
xmlAttr *tmp = xIter;
const char *prop_name = (const char *)xIter->name;
xIter = xIter->next;
if (strcmp(prop_name, XML_ATTR_ID) == 0) {
continue;
}
xmlUnsetProp(xml, tmp->name);
}
child = __xml_first_child(xml);
while ( child != NULL ) {
xmlNode *tmp = child;
child = __xml_next(child);
readable_children |= __xml_purge_attributes(tmp);
}
if(readable_children == FALSE) {
free_xml(xml); /* Nothing readable under here, purge completely */
}
return readable_children;
}
bool
xml_acl_filtered_copy(const char *user, xmlNode* acl_source, xmlNode *xml, xmlNode ** result)
{
GListPtr aIter = NULL;
xmlNode *target = NULL;
xml_private_t *p = NULL;
xml_private_t *doc = NULL;
*result = NULL;
if(xml == NULL || pcmk_acl_required(user) == FALSE) {
crm_trace("no acls needed for '%s'", user);
return FALSE;
}
crm_trace("filtering copy of %p for '%s'", xml, user);
target = copy_xml(xml);
if(target == NULL) {
return TRUE;
}
__xml_acl_unpack(acl_source, target, user);
set_doc_flag(target, xpf_acl_enabled);
__xml_acl_apply(target);
doc = target->doc->_private;
for(aIter = doc->acls; aIter != NULL && target; aIter = aIter->next) {
int max = 0;
xml_acl_t *acl = aIter->data;
if(acl->mode != xpf_acl_deny) {
/* Nothing to do */
} else if(acl->xpath) {
int lpc = 0;
xmlXPathObjectPtr xpathObj = xpath_search(target, acl->xpath);
max = numXpathResults(xpathObj);
for(lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpathObj, lpc);
crm_trace("Purging attributes from %s", acl->xpath);
if(__xml_purge_attributes(match) == FALSE && match == target) {
crm_trace("No access to the entire document for %s", user);
freeXpathObject(xpathObj);
return TRUE;
}
}
crm_trace("Enforced ACL %s (%d matches)", acl->xpath, max);
freeXpathObject(xpathObj);
}
}
p = target->_private;
if(is_set(p->flags, xpf_acl_deny) && __xml_purge_attributes(target) == FALSE) {
crm_trace("No access to the entire document for %s", user);
return TRUE;
}
if(doc->acls) {
g_list_free_full(doc->acls, __xml_acl_free);
doc->acls = NULL;
} else {
crm_trace("Ordinary user '%s' cannot access the CIB without any defined ACLs", doc->user);
free_xml(target);
target = NULL;
}
if(target) {
*result = target;
}
return TRUE;
}
static void
__xml_acl_post_process(xmlNode * xml)
{
xmlNode *cIter = __xml_first_child(xml);
xml_private_t *p = xml->_private;
if(is_set(p->flags, xpf_created)) {
xmlAttr *xIter = NULL;
char *path = xml_get_path(xml);
/* Always allow new scaffolding, ie. node with no attributes or only an 'id'
* Except in the ACLs section
*/
for (xIter = crm_first_attr(xml); xIter != NULL; xIter = xIter->next) {
const char *prop_name = (const char *)xIter->name;
if (strcmp(prop_name, XML_ATTR_ID) == 0 && strstr(path, "/"XML_CIB_TAG_ACLS"/") == NULL) {
/* Delay the acl check */
continue;
} else if(__xml_acl_check(xml, NULL, xpf_acl_write)) {
crm_trace("Creation of %s=%s is allowed", crm_element_name(xml), ID(xml));
break;
} else {
crm_trace("Cannot add new node %s at %s", crm_element_name(xml), path);
if(xml != xmlDocGetRootElement(xml->doc)) {
xmlUnlinkNode(xml);
xmlFreeNode(xml);
}
free(path);
return;
}
}
free(path);
}
while (cIter != NULL) {
xmlNode *child = cIter;
cIter = __xml_next(cIter); /* In case it is free'd */
__xml_acl_post_process(child);
}
}
bool
xml_acl_denied(xmlNode *xml)
{
if(xml && xml->doc && xml->doc->_private){
xml_private_t *p = xml->doc->_private;
return is_set(p->flags, xpf_acl_denied);
}
return FALSE;
}
void
xml_acl_disable(xmlNode *xml)
{
if(xml_acl_enabled(xml)) {
xml_private_t *p = xml->doc->_private;
/* Catch anything that was created but shouldn't have been */
__xml_acl_apply(xml);
__xml_acl_post_process(xml);
clear_bit(p->flags, xpf_acl_enabled);
}
}
bool
xml_acl_enabled(xmlNode *xml)
{
if(xml && xml->doc && xml->doc->_private){
xml_private_t *p = xml->doc->_private;
return is_set(p->flags, xpf_acl_enabled);
}
return FALSE;
}
void
xml_track_changes(xmlNode * xml, const char *user, xmlNode *acl_source, bool enforce_acls)
{
xml_accept_changes(xml);
crm_trace("Tracking changes%s to %p", enforce_acls?" with ACLs":"", xml);
set_doc_flag(xml, xpf_tracking);
if(enforce_acls) {
if(acl_source == NULL) {
acl_source = xml;
}
set_doc_flag(xml, xpf_acl_enabled);
__xml_acl_unpack(acl_source, xml, user);
__xml_acl_apply(xml);
}
}
bool xml_tracking_changes(xmlNode * xml)
{
if(xml == NULL) {
return FALSE;
} else if(is_set(((xml_private_t *)xml->doc->_private)->flags, xpf_tracking)) {
return TRUE;
}
return FALSE;
}
bool xml_document_dirty(xmlNode *xml)
{
if(xml != NULL && xml->doc && xml->doc->_private) {
xml_private_t *doc = xml->doc->_private;
return is_set(doc->flags, xpf_dirty);
}
return FALSE;
}
/*
<diff format="2.0">
<version>
<source admin_epoch="1" epoch="2" num_updates="3"/>
<target admin_epoch="1" epoch="3" num_updates="0"/>
</version>
<change operation="add" xpath="/cib/configuration/nodes">
<node id="node2" uname="node2" description="foo"/>
</change>
<change operation="add" xpath="/cib/configuration/nodes/node[node2]">
<instance_attributes id="nodes-node"><!-- NOTE: can be a full tree -->
<nvpair id="nodes-node2-ram" name="ram" value="1024M"/>
</instance_attributes>
</change>
<change operation="update" xpath="/cib/configuration/nodes[@id='node2']">
<change-list>
<change-attr operation="set" name="type" value="member"/>
<change-attr operation="unset" name="description"/>
</change-list>
<change-result>
<node id="node2" uname="node2" type="member"/><!-- NOTE: not recursive -->
</change-result>
</change>
<change operation="delete" xpath="/cib/configuration/nodes/node[@id='node3'] /">
<change operation="update" xpath="/cib/configuration/resources/group[@id='g1']">
<change-list>
<change-attr operation="set" name="description" value="some grabage here"/>
</change-list>
<change-result>
<group id="g1" description="some grabage here"/><!-- NOTE: not recursive -->
</change-result>
</change>
<change operation="update" xpath="/cib/status/node_state[@id='node2]/lrm[@id='node2']/lrm_resources/lrm_resource[@id='Fence']">
<change-list>
<change-attr operation="set" name="oper" value="member"/>
<change-attr operation="set" name="operation_key" value="Fence_start_0"/>
<change-attr operation="set" name="operation" value="start"/>
<change-attr operation="set" name="transition-key" value="2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"/>
<change-attr operation="set" name="transition-magic" value="0:0;2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"/>
<change-attr operation="set" name="call-id" value="2"/>
<change-attr operation="set" name="rc-code" value="0"/>
</change-list>
<change-result>
<lrm_rsc_op id="Fence_last_0" operation_key="Fence_start_0" operation="start" crm-debug-origin="crm_simulate" transition-key="2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" transition-magic="0:0;2:-1:0:xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" call-id="2" rc-code="0" op-status="0" interval="0" exec-time="0" queue-time="0" op-digest="f2317cad3d54cec5d7d7aa7d0bf35cf8"/>
</change-result>
</change>
</diff>
*/
static int __xml_offset(xmlNode *xml)
{
int position = 0;
xmlNode *cIter = NULL;
for(cIter = xml; cIter->prev; cIter = cIter->prev) {
xml_private_t *p = ((xmlNode*)cIter->prev)->_private;
if(is_not_set(p->flags, xpf_skip)) {
position++;
}
}
return position;
}
static int __xml_offset_no_deletions(xmlNode *xml)
{
int position = 0;
xmlNode *cIter = NULL;
for(cIter = xml; cIter->prev; cIter = cIter->prev) {
xml_private_t *p = ((xmlNode*)cIter->prev)->_private;
if(is_not_set(p->flags, xpf_deleted)) {
position++;
}
}
return position;
}
static void
__xml_build_changes(xmlNode * xml, xmlNode *patchset)
{
xmlNode *cIter = NULL;
xmlAttr *pIter = NULL;
xmlNode *change = NULL;
xml_private_t *p = xml->_private;
if(patchset && is_set(p->flags, xpf_created)) {
int offset = 0;
char buffer[XML_BUFFER_SIZE];
if(__get_prefix(NULL, xml->parent, buffer, offset) > 0) {
int position = __xml_offset_no_deletions(xml);
change = create_xml_node(patchset, XML_DIFF_CHANGE);
crm_xml_add(change, XML_DIFF_OP, "create");
crm_xml_add(change, XML_DIFF_PATH, buffer);
crm_xml_add_int(change, XML_DIFF_POSITION, position);
add_node_copy(change, xml);
}
return;
}
for (pIter = crm_first_attr(xml); pIter != NULL; pIter = pIter->next) {
xmlNode *attr = NULL;
p = pIter->_private;
if(is_not_set(p->flags, xpf_deleted) && is_not_set(p->flags, xpf_dirty)) {
continue;
}
if(change == NULL) {
int offset = 0;
char buffer[XML_BUFFER_SIZE];
if(__get_prefix(NULL, xml, buffer, offset) > 0) {
change = create_xml_node(patchset, XML_DIFF_CHANGE);
crm_xml_add(change, XML_DIFF_OP, "modify");
crm_xml_add(change, XML_DIFF_PATH, buffer);
change = create_xml_node(change, XML_DIFF_LIST);
}
}
attr = create_xml_node(change, XML_DIFF_ATTR);
crm_xml_add(attr, XML_NVPAIR_ATTR_NAME, (const char *)pIter->name);
if(p->flags & xpf_deleted) {
crm_xml_add(attr, XML_DIFF_OP, "unset");
} else {
const char *value = crm_element_value(xml, (const char *)pIter->name);
crm_xml_add(attr, XML_DIFF_OP, "set");
crm_xml_add(attr, XML_NVPAIR_ATTR_VALUE, value);
}
}
if(change) {
xmlNode *result = NULL;
change = create_xml_node(change->parent, XML_DIFF_RESULT);
result = create_xml_node(change, (const char *)xml->name);
for (pIter = crm_first_attr(xml); pIter != NULL; pIter = pIter->next) {
const char *value = crm_element_value(xml, (const char *)pIter->name);
p = pIter->_private;
if (is_not_set(p->flags, xpf_deleted)) {
crm_xml_add(result, (const char *)pIter->name, value);
}
}
}
for (cIter = __xml_first_child(xml); cIter != NULL; cIter = __xml_next(cIter)) {
__xml_build_changes(cIter, patchset);
}
p = xml->_private;
if(patchset && is_set(p->flags, xpf_moved)) {
int offset = 0;
char buffer[XML_BUFFER_SIZE];
crm_trace("%s.%s moved to position %d", xml->name, ID(xml), __xml_offset(xml));
if(__get_prefix(NULL, xml, buffer, offset) > 0) {
change = create_xml_node(patchset, XML_DIFF_CHANGE);
crm_xml_add(change, XML_DIFF_OP, "move");
crm_xml_add(change, XML_DIFF_PATH, buffer);
crm_xml_add_int(change, XML_DIFF_POSITION, __xml_offset_no_deletions(xml));
}
}
}
static void
__xml_accept_changes(xmlNode * xml)
{
xmlNode *cIter = NULL;
xmlAttr *pIter = NULL;
xml_private_t *p = xml->_private;
p->flags = xpf_none;
pIter = crm_first_attr(xml);
while (pIter != NULL) {
const xmlChar *name = pIter->name;
p = pIter->_private;
pIter = pIter->next;
if(p->flags & xpf_deleted) {
xml_remove_prop(xml, (const char *)name);
} else {
p->flags = xpf_none;
}
}
for (cIter = __xml_first_child(xml); cIter != NULL; cIter = __xml_next(cIter)) {
__xml_accept_changes(cIter);
}
}
static bool
is_config_change(xmlNode *xml)
{
GListPtr gIter = NULL;
xml_private_t *p = NULL;
xmlNode *config = first_named_child(xml, XML_CIB_TAG_CONFIGURATION);
if(config) {
p = config->_private;
}
if(p && is_set(p->flags, xpf_dirty)) {
return TRUE;
}
if(xml->doc && xml->doc->_private) {
p = xml->doc->_private;
for(gIter = p->deleted_paths; gIter; gIter = gIter->next) {
char *path = gIter->data;
if(strstr(path, "/"XML_TAG_CIB"/"XML_CIB_TAG_CONFIGURATION) != NULL) {
return TRUE;
}
}
}
return FALSE;
}
static void
xml_repair_v1_diff(xmlNode * last, xmlNode * next, xmlNode * local_diff, gboolean changed)
{
int lpc = 0;
xmlNode *cib = NULL;
xmlNode *diff_child = NULL;
const char *tag = NULL;
const char *vfields[] = {
XML_ATTR_GENERATION_ADMIN,
XML_ATTR_GENERATION,
XML_ATTR_NUMUPDATES,
};
if (local_diff == NULL) {
crm_trace("Nothing to do");
return;
}
tag = "diff-removed";
diff_child = find_xml_node(local_diff, tag, FALSE);
if (diff_child == NULL) {
diff_child = create_xml_node(local_diff, tag);
}
tag = XML_TAG_CIB;
cib = find_xml_node(diff_child, tag, FALSE);
if (cib == NULL) {
cib = create_xml_node(diff_child, tag);
}
for(lpc = 0; last && lpc < DIMOF(vfields); lpc++){
const char *value = crm_element_value(last, vfields[lpc]);
crm_xml_add(diff_child, vfields[lpc], value);
if(changed || lpc == 2) {
crm_xml_add(cib, vfields[lpc], value);
}
}
tag = "diff-added";
diff_child = find_xml_node(local_diff, tag, FALSE);
if (diff_child == NULL) {
diff_child = create_xml_node(local_diff, tag);
}
tag = XML_TAG_CIB;
cib = find_xml_node(diff_child, tag, FALSE);
if (cib == NULL) {
cib = create_xml_node(diff_child, tag);
}
for(lpc = 0; next && lpc < DIMOF(vfields); lpc++){
const char *value = crm_element_value(next, vfields[lpc]);
crm_xml_add(diff_child, vfields[lpc], value);
}
if (next) {
xmlAttrPtr xIter = NULL;
for (xIter = next->properties; xIter; xIter = xIter->next) {
const char *p_name = (const char *)xIter->name;
const char *p_value = crm_element_value(next, p_name);
xmlSetProp(cib, (const xmlChar *)p_name, (const xmlChar *)p_value);
}
}
crm_log_xml_explicit(local_diff, "Repaired-diff");
}
static xmlNode *
xml_create_patchset_v1(xmlNode *source, xmlNode *target, bool config, bool suppress)
{
xmlNode *patchset = diff_xml_object(source, target, suppress);
if(patchset) {
CRM_LOG_ASSERT(xml_document_dirty(target));
xml_repair_v1_diff(source, target, patchset, config);
crm_xml_add(patchset, "format", "1");
}
return patchset;
}
static xmlNode *
xml_create_patchset_v2(xmlNode *source, xmlNode *target)
{
int lpc = 0;
GListPtr gIter = NULL;
xml_private_t *doc = NULL;
xmlNode *v = NULL;
xmlNode *version = NULL;
xmlNode *patchset = NULL;
const char *vfields[] = {
XML_ATTR_GENERATION_ADMIN,
XML_ATTR_GENERATION,
XML_ATTR_NUMUPDATES,
};
CRM_ASSERT(target);
if(xml_document_dirty(target) == FALSE) {
return NULL;
}
CRM_ASSERT(target->doc);
doc = target->doc->_private;
patchset = create_xml_node(NULL, XML_TAG_DIFF);
crm_xml_add_int(patchset, "format", 2);
version = create_xml_node(patchset, XML_DIFF_VERSION);
v = create_xml_node(version, XML_DIFF_VSOURCE);
for(lpc = 0; lpc < DIMOF(vfields); lpc++){
const char *value = crm_element_value(source, vfields[lpc]);
if(value == NULL) {
value = "1";
}
crm_xml_add(v, vfields[lpc], value);
}
v = create_xml_node(version, XML_DIFF_VTARGET);
for(lpc = 0; lpc < DIMOF(vfields); lpc++){
const char *value = crm_element_value(target, vfields[lpc]);
if(value == NULL) {
value = "1";
}
crm_xml_add(v, vfields[lpc], value);
}
for(gIter = doc->deleted_paths; gIter; gIter = gIter->next) {
xmlNode *change = create_xml_node(patchset, XML_DIFF_CHANGE);
crm_xml_add(change, XML_DIFF_OP, "delete");
crm_xml_add(change, XML_DIFF_PATH, gIter->data);
}
__xml_build_changes(target, patchset);
return patchset;
}
static gboolean patch_legacy_mode(void)
{
static gboolean init = TRUE;
static gboolean legacy = FALSE;
if(init) {
init = FALSE;
legacy = daemon_option_enabled("cib", "legacy");
if(legacy) {
crm_notice("Enabled legacy mode");
}
}
return legacy;
}
xmlNode *
xml_create_patchset(int format, xmlNode *source, xmlNode *target, bool *config_changed, bool manage_version)
{
int counter = 0;
bool config = FALSE;
xmlNode *patch = NULL;
const char *version = crm_element_value(source, XML_ATTR_CRM_VERSION);
xml_acl_disable(target);
if(xml_document_dirty(target) == FALSE) {
crm_trace("No change %d", format);
return NULL; /* No change */
}
config = is_config_change(target);
if(config_changed) {
*config_changed = config;
}
if(manage_version && config) {
crm_trace("Config changed %d", format);
crm_xml_add(target, XML_ATTR_NUMUPDATES, "0");
crm_element_value_int(target, XML_ATTR_GENERATION, &counter);
crm_xml_add_int(target, XML_ATTR_GENERATION, counter+1);
} else if(manage_version) {
crm_element_value_int(target, XML_ATTR_NUMUPDATES, &counter);
crm_trace("Status changed %d - %d %s", format, counter, crm_element_value(source, XML_ATTR_NUMUPDATES));
crm_xml_add_int(target, XML_ATTR_NUMUPDATES, counter+1);
}
if(format == 0) {
if(patch_legacy_mode()) {
format = 1;
} else if(compare_version("3.0.8", version) < 0) {
format = 2;
} else {
format = 1;
}
crm_trace("Using patch format %d for version: %s", format, version);
}
switch(format) {
case 1:
patch = xml_create_patchset_v1(source, target, config, FALSE);
break;
case 2:
patch = xml_create_patchset_v2(source, target);
break;
default:
crm_err("Unknown patch format: %d", format);
return NULL;
}
return patch;
}
void
patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, bool with_digest)
{
int format = 1;
const char *version = NULL;
char *digest = NULL;
if (patch == NULL || source == NULL || target == NULL) {
return;
}
/* NOTE: We should always call xml_accept_changes() before calculating digest. */
/* Otherwise, with an on-tracking dirty target, we could get a wrong digest. */
CRM_LOG_ASSERT(xml_document_dirty(target) == FALSE);
crm_element_value_int(patch, "format", &format);
if (format > 1 && with_digest == FALSE) {
return;
}
version = crm_element_value(source, XML_ATTR_CRM_VERSION);
digest = calculate_xml_versioned_digest(target, FALSE, TRUE, version);
crm_xml_add(patch, XML_ATTR_DIGEST, digest);
free(digest);
return;
}
static void
__xml_log_element(int log_level, const char *file, const char *function, int line,
const char *prefix, xmlNode * data, int depth, int options);
void
xml_log_patchset(uint8_t log_level, const char *function, xmlNode * patchset)
{
int format = 1;
xmlNode *child = NULL;
xmlNode *added = NULL;
xmlNode *removed = NULL;
gboolean is_first = TRUE;
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
const char *fmt = NULL;
const char *digest = NULL;
int options = xml_log_option_formatted;
static struct qb_log_callsite *patchset_cs = NULL;
if (patchset_cs == NULL) {
patchset_cs = qb_log_callsite_get(function, __FILE__, "xml-patchset", log_level, __LINE__, 0);
}
if (patchset == NULL) {
crm_trace("Empty patch");
return;
} else if (log_level == 0) {
/* Log to stdout */
} else if (crm_is_callsite_active(patchset_cs, log_level, 0) == FALSE) {
return;
}
xml_patch_versions(patchset, add, del);
fmt = crm_element_value(patchset, "format");
digest = crm_element_value(patchset, XML_ATTR_DIGEST);
if (add[2] != del[2] || add[1] != del[1] || add[0] != del[0]) {
do_crm_log_alias(log_level, __FILE__, function, __LINE__,
"Diff: --- %d.%d.%d %s", del[0], del[1], del[2], fmt);
do_crm_log_alias(log_level, __FILE__, function, __LINE__,
"Diff: +++ %d.%d.%d %s", add[0], add[1], add[2], digest);
} else if (patchset != NULL && (add[0] || add[1] || add[2])) {
do_crm_log_alias(log_level, __FILE__, function, __LINE__,
"%s: Local-only Change: %d.%d.%d", function ? function : "",
add[0], add[1], add[2]);
}
crm_element_value_int(patchset, "format", &format);
if(format == 2) {
xmlNode *change = NULL;
for (change = __xml_first_child(patchset); change != NULL; change = __xml_next(change)) {
const char *op = crm_element_value(change, XML_DIFF_OP);
const char *xpath = crm_element_value(change, XML_DIFF_PATH);
if(op == NULL) {
} else if(strcmp(op, "create") == 0) {
int lpc = 0, max = 0;
char *prefix = crm_strdup_printf("++ %s: ", xpath);
max = strlen(prefix);
__xml_log_element(log_level, __FILE__, function, __LINE__, prefix, change->children,
0, xml_log_option_formatted|xml_log_option_open);
for(lpc = 2; lpc < max; lpc++) {
prefix[lpc] = ' ';
}
__xml_log_element(log_level, __FILE__, function, __LINE__, prefix, change->children,
0, xml_log_option_formatted|xml_log_option_close|xml_log_option_children);
free(prefix);
} else if(strcmp(op, "move") == 0) {
do_crm_log_alias(log_level, __FILE__, function, __LINE__, "+~ %s moved to offset %s", xpath, crm_element_value(change, XML_DIFF_POSITION));
} else if(strcmp(op, "modify") == 0) {
xmlNode *clist = first_named_child(change, XML_DIFF_LIST);
char buffer_set[XML_BUFFER_SIZE];
char buffer_unset[XML_BUFFER_SIZE];
int o_set = 0;
int o_unset = 0;
buffer_set[0] = 0;
buffer_unset[0] = 0;
for (child = __xml_first_child(clist); child != NULL; child = __xml_next(child)) {
const char *name = crm_element_value(child, "name");
op = crm_element_value(child, XML_DIFF_OP);
if(op == NULL) {
} else if(strcmp(op, "set") == 0) {
const char *value = crm_element_value(child, "value");
if(o_set > 0) {
o_set += snprintf(buffer_set + o_set, XML_BUFFER_SIZE - o_set, ", ");
}
o_set += snprintf(buffer_set + o_set, XML_BUFFER_SIZE - o_set, "@%s=%s", name, value);
} else if(strcmp(op, "unset") == 0) {
if(o_unset > 0) {
o_unset += snprintf(buffer_unset + o_unset, XML_BUFFER_SIZE - o_unset, ", ");
}
o_unset += snprintf(buffer_unset + o_unset, XML_BUFFER_SIZE - o_unset, "@%s", name);
}
}
if(o_set) {
do_crm_log_alias(log_level, __FILE__, function, __LINE__, "+ %s: %s", xpath, buffer_set);
}
if(o_unset) {
do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s: %s", xpath, buffer_unset);
}
} else if(strcmp(op, "delete") == 0) {
do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s", xpath);
}
}
return;
}
if (log_level < LOG_DEBUG || function == NULL) {
options |= xml_log_option_diff_short;
}
removed = find_xml_node(patchset, "diff-removed", FALSE);
for (child = __xml_first_child(removed); child != NULL; child = __xml_next(child)) {
log_data_element(log_level, __FILE__, function, __LINE__, "- ", child, 0,
options | xml_log_option_diff_minus);
if (is_first) {
is_first = FALSE;
} else {
do_crm_log_alias(log_level, __FILE__, function, __LINE__, " --- ");
}
}
is_first = TRUE;
added = find_xml_node(patchset, "diff-added", FALSE);
for (child = __xml_first_child(added); child != NULL; child = __xml_next(child)) {
log_data_element(log_level, __FILE__, function, __LINE__, "+ ", child, 0,
options | xml_log_option_diff_plus);
if (is_first) {
is_first = FALSE;
} else {
do_crm_log_alias(log_level, __FILE__, function, __LINE__, " +++ ");
}
}
}
void
xml_log_changes(uint8_t log_level, const char *function, xmlNode * xml)
{
GListPtr gIter = NULL;
xml_private_t *doc = NULL;
CRM_ASSERT(xml);
CRM_ASSERT(xml->doc);
doc = xml->doc->_private;
if(is_not_set(doc->flags, xpf_dirty)) {
return;
}
for(gIter = doc->deleted_paths; gIter; gIter = gIter->next) {
do_crm_log_alias(log_level, __FILE__, function, __LINE__, "-- %s", (char*)gIter->data);
}
log_data_element(log_level, __FILE__, function, __LINE__, "+ ", xml, 0,
xml_log_option_formatted|xml_log_option_dirty_add);
}
void
xml_accept_changes(xmlNode * xml)
{
xmlNode *top = NULL;
xml_private_t *doc = NULL;
if(xml == NULL) {
return;
}
crm_trace("Accepting changes to %p", xml);
doc = xml->doc->_private;
top = xmlDocGetRootElement(xml->doc);
__xml_private_clean(xml->doc->_private);
if(is_not_set(doc->flags, xpf_dirty)) {
doc->flags = xpf_none;
return;
}
doc->flags = xpf_none;
__xml_accept_changes(top);
}
static xmlNode *
find_element(xmlNode *haystack, xmlNode *needle)
{
CRM_CHECK(needle != NULL, return NULL);
return (needle->type == XML_COMMENT_NODE)?
find_xml_comment(haystack, needle)
: find_entity(haystack, crm_element_name(needle), ID(needle));
}
/* Simplified version for applying v1-style XML patches */
static void
__subtract_xml_object(xmlNode * target, xmlNode * patch)
{
xmlNode *patch_child = NULL;
xmlNode *cIter = NULL;
xmlAttrPtr xIter = NULL;
char *id = NULL;
const char *name = NULL;
const char *value = NULL;
if (target == NULL || patch == NULL) {
return;
}
if (target->type == XML_COMMENT_NODE) {
gboolean dummy;
subtract_xml_comment(target->parent, target, patch, &dummy);
}
name = crm_element_name(target);
CRM_CHECK(name != NULL, return);
CRM_CHECK(safe_str_eq(crm_element_name(target), crm_element_name(patch)), return);
CRM_CHECK(safe_str_eq(ID(target), ID(patch)), return);
/* check for XML_DIFF_MARKER in a child */
id = crm_element_value_copy(target, XML_ATTR_ID);
value = crm_element_value(patch, XML_DIFF_MARKER);
if (value != NULL && strcmp(value, "removed:top") == 0) {
crm_trace("We are the root of the deletion: %s.id=%s", name, id);
free_xml(target);
free(id);
return;
}
for (xIter = crm_first_attr(patch); xIter != NULL; xIter = xIter->next) {
const char *p_name = (const char *)xIter->name;
/* Removing and then restoring the id field would change the ordering of properties */
if (safe_str_neq(p_name, XML_ATTR_ID)) {
xml_remove_prop(target, p_name);
}
}
/* changes to child objects */
cIter = __xml_first_child(target);
while (cIter) {
xmlNode *target_child = cIter;
cIter = __xml_next(cIter);
patch_child = find_element(patch, target_child);
__subtract_xml_object(target_child, patch_child);
}
free(id);
}
static void
__add_xml_object(xmlNode * parent, xmlNode * target, xmlNode * patch)
{
xmlNode *patch_child = NULL;
xmlNode *target_child = NULL;
xmlAttrPtr xIter = NULL;
const char *id = NULL;
const char *name = NULL;
const char *value = NULL;
if (patch == NULL) {
return;
} else if (parent == NULL && target == NULL) {
return;
}
/* check for XML_DIFF_MARKER in a child */
value = crm_element_value(patch, XML_DIFF_MARKER);
if (target == NULL
&& value != NULL
&& strcmp(value, "added:top") == 0) {
id = ID(patch);
name = crm_element_name(patch);
crm_trace("We are the root of the addition: %s.id=%s", name, id);
add_node_copy(parent, patch);
return;
} else if(target == NULL) {
id = ID(patch);
name = crm_element_name(patch);
crm_err("Could not locate: %s.id=%s", name, id);
return;
}
if (target->type == XML_COMMENT_NODE) {
add_xml_comment(parent, target, patch);
}
name = crm_element_name(target);
CRM_CHECK(name != NULL, return);
CRM_CHECK(safe_str_eq(crm_element_name(target), crm_element_name(patch)), return);
CRM_CHECK(safe_str_eq(ID(target), ID(patch)), return);
for (xIter = crm_first_attr(patch); xIter != NULL; xIter = xIter->next) {
const char *p_name = (const char *)xIter->name;
const char *p_value = crm_element_value(patch, p_name);
xml_remove_prop(target, p_name); /* Preserve the patch order */
crm_xml_add(target, p_name, p_value);
}
/* changes to child objects */
for (patch_child = __xml_first_child(patch); patch_child != NULL;
patch_child = __xml_next(patch_child)) {
target_child = find_element(target, patch_child);
__add_xml_object(target, target_child, patch_child);
}
}
-/*
+/*!
* \internal
* \brief Find additions or removals in a patch set
*
* \param[in] patchset XML of patch
* \param[in] format Patch version
* \param[in] added TRUE if looking for additions, FALSE if removals
* \param[in/out] patch_node Will be set to node if found
*
* \return TRUE if format is valid, FALSE if invalid
*/
static bool
find_patch_xml_node(xmlNode *patchset, int format, bool added,
xmlNode **patch_node)
{
xmlNode *cib_node;
const char *label;
switch(format) {
case 1:
label = added? "diff-added" : "diff-removed";
*patch_node = find_xml_node(patchset, label, FALSE);
cib_node = find_xml_node(*patch_node, "cib", FALSE);
if (cib_node != NULL) {
*patch_node = cib_node;
}
break;
case 2:
label = added? "target" : "source";
*patch_node = find_xml_node(patchset, "version", FALSE);
*patch_node = find_xml_node(*patch_node, label, FALSE);
break;
default:
crm_warn("Unknown patch format: %d", format);
*patch_node = NULL;
return FALSE;
}
return TRUE;
}
bool xml_patch_versions(xmlNode *patchset, int add[3], int del[3])
{
int lpc = 0;
int format = 1;
xmlNode *tmp = NULL;
const char *vfields[] = {
XML_ATTR_GENERATION_ADMIN,
XML_ATTR_GENERATION,
XML_ATTR_NUMUPDATES,
};
crm_element_value_int(patchset, "format", &format);
/* Process removals */
if (!find_patch_xml_node(patchset, format, FALSE, &tmp)) {
return -EINVAL;
}
if (tmp) {
for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
crm_element_value_int(tmp, vfields[lpc], &(del[lpc]));
crm_trace("Got %d for del[%s]", del[lpc], vfields[lpc]);
}
}
/* Process additions */
if (!find_patch_xml_node(patchset, format, TRUE, &tmp)) {
return -EINVAL;
}
if (tmp) {
for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
crm_element_value_int(tmp, vfields[lpc], &(add[lpc]));
crm_trace("Got %d for add[%s]", add[lpc], vfields[lpc]);
}
}
return pcmk_ok;
}
static int
xml_patch_version_check(xmlNode *xml, xmlNode *patchset, int format)
{
int lpc = 0;
bool changed = FALSE;
int this[] = { 0, 0, 0 };
int add[] = { 0, 0, 0 };
int del[] = { 0, 0, 0 };
const char *vfields[] = {
XML_ATTR_GENERATION_ADMIN,
XML_ATTR_GENERATION,
XML_ATTR_NUMUPDATES,
};
for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
crm_element_value_int(xml, vfields[lpc], &(this[lpc]));
crm_trace("Got %d for this[%s]", this[lpc], vfields[lpc]);
if (this[lpc] < 0) {
this[lpc] = 0;
}
}
/* Set some defaults in case nothing is present */
add[0] = this[0];
add[1] = this[1];
add[2] = this[2] + 1;
for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
del[lpc] = this[lpc];
}
xml_patch_versions(patchset, add, del);
for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
if(this[lpc] < del[lpc]) {
crm_debug("Current %s is too low (%d.%d.%d < %d.%d.%d --> %d.%d.%d)", vfields[lpc],
this[0], this[1], this[2], del[0], del[1], del[2], add[0], add[1], add[2]);
return -pcmk_err_diff_resync;
} else if(this[lpc] > del[lpc]) {
crm_info("Current %s is too high (%d.%d.%d > %d.%d.%d --> %d.%d.%d) %p", vfields[lpc],
this[0], this[1], this[2], del[0], del[1], del[2], add[0], add[1], add[2], patchset);
crm_log_xml_info(patchset, "OldPatch");
return -pcmk_err_old_data;
}
}
for(lpc = 0; lpc < DIMOF(vfields); lpc++) {
if(add[lpc] > del[lpc]) {
changed = TRUE;
}
}
if(changed == FALSE) {
crm_notice("Versions did not change in patch %d.%d.%d", add[0], add[1], add[2]);
return -pcmk_err_old_data;
}
crm_debug("Can apply patch %d.%d.%d to %d.%d.%d",
add[0], add[1], add[2], this[0], this[1], this[2]);
return pcmk_ok;
}
static int
xml_apply_patchset_v1(xmlNode *xml, xmlNode *patchset, bool check_version)
{
int rc = pcmk_ok;
int root_nodes_seen = 0;
char *version = crm_element_value_copy(xml, XML_ATTR_CRM_VERSION);
xmlNode *child_diff = NULL;
xmlNode *added = find_xml_node(patchset, "diff-added", FALSE);
xmlNode *removed = find_xml_node(patchset, "diff-removed", FALSE);
xmlNode *old = copy_xml(xml);
crm_trace("Subtraction Phase");
for (child_diff = __xml_first_child(removed); child_diff != NULL;
child_diff = __xml_next(child_diff)) {
CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
if (root_nodes_seen == 0) {
__subtract_xml_object(xml, child_diff);
}
root_nodes_seen++;
}
if (root_nodes_seen > 1) {
crm_err("(-) Diffs cannot contain more than one change set... saw %d", root_nodes_seen);
rc = -ENOTUNIQ;
}
root_nodes_seen = 0;
crm_trace("Addition Phase");
if (rc == pcmk_ok) {
xmlNode *child_diff = NULL;
for (child_diff = __xml_first_child(added); child_diff != NULL;
child_diff = __xml_next(child_diff)) {
CRM_CHECK(root_nodes_seen == 0, rc = FALSE);
if (root_nodes_seen == 0) {
__add_xml_object(NULL, xml, child_diff);
}
root_nodes_seen++;
}
}
if (root_nodes_seen > 1) {
crm_err("(+) Diffs cannot contain more than one change set... saw %d", root_nodes_seen);
rc = -ENOTUNIQ;
}
purge_diff_markers(xml); /* Purge prior to checking the digest */
free_xml(old);
free(version);
return rc;
}
static xmlNode *
__first_xml_child_match(xmlNode *parent, const char *name, const char *id)
{
xmlNode *cIter = NULL;
for (cIter = __xml_first_child(parent); cIter != NULL; cIter = __xml_next(cIter)) {
if(strcmp((const char *)cIter->name, name) != 0) {
continue;
} else if(id) {
const char *cid = ID(cIter);
if(cid == NULL || strcmp(cid, id) != 0) {
continue;
}
}
return cIter;
}
return NULL;
}
static xmlNode *
__xml_find_path(xmlNode *top, const char *key)
{
xmlNode *target = (xmlNode*)top->doc;
char *id = malloc(XML_BUFFER_SIZE);
char *tag = malloc(XML_BUFFER_SIZE);
char *section = malloc(XML_BUFFER_SIZE);
char *current = strdup(key);
char *remainder = malloc(XML_BUFFER_SIZE);
int rc = 0;
while(current) {
rc = sscanf (current, "/%[^/]%s", section, remainder);
if(rc <= 0) {
crm_trace("Done");
break;
} else if(rc > 2) {
crm_trace("Aborting on %s", current);
target = NULL;
break;
} else if(tag && section) {
int f = sscanf (section, "%[^[][@id='%[^']", tag, id);
switch(f) {
case 1:
target = __first_xml_child_match(target, tag, NULL);
break;
case 2:
target = __first_xml_child_match(target, tag, id);
break;
default:
crm_trace("Aborting on %s", section);
target = NULL;
break;
}
if(rc == 1 || target == NULL) {
crm_trace("Done");
break;
} else {
char *tmp = current;
current = remainder;
remainder = tmp;
}
}
}
if(target) {
char *path = (char *)xmlGetNodePath(target);
crm_trace("Found %s for %s", path, key);
free(path);
} else {
crm_debug("No match for %s", key);
}
free(remainder);
free(current);
free(section);
free(tag);
free(id);
return target;
}
static int
xml_apply_patchset_v2(xmlNode *xml, xmlNode *patchset, bool check_version)
{
int rc = pcmk_ok;
xmlNode *change = NULL;
for (change = __xml_first_child(patchset); change != NULL; change = __xml_next(change)) {
xmlNode *match = NULL;
const char *op = crm_element_value(change, XML_DIFF_OP);
const char *xpath = crm_element_value(change, XML_DIFF_PATH);
crm_trace("Processing %s %s", change->name, op);
if(op == NULL) {
continue;
}
#if 0
match = get_xpath_object(xpath, xml, LOG_TRACE);
#else
match = __xml_find_path(xml, xpath);
#endif
crm_trace("Performing %s on %s with %p", op, xpath, match);
if(match == NULL && strcmp(op, "delete") == 0) {
crm_debug("No %s match for %s in %p", op, xpath, xml->doc);
continue;
} else if(match == NULL) {
crm_err("No %s match for %s in %p", op, xpath, xml->doc);
rc = -pcmk_err_diff_failed;
continue;
} else if(strcmp(op, "create") == 0) {
int position = 0;
xmlNode *child = NULL;
xmlNode *match_child = NULL;
match_child = match->children;
crm_element_value_int(change, XML_DIFF_POSITION, &position);
while(match_child && position != __xml_offset(match_child)) {
match_child = match_child->next;
}
child = xmlDocCopyNode(change->children, match->doc, 1);
if(match_child) {
crm_trace("Adding %s at position %d", child->name, position);
xmlAddPrevSibling(match_child, child);
} else if(match->last) { /* Add to the end */
crm_trace("Adding %s at position %d (end)", child->name, position);
xmlAddNextSibling(match->last, child);
} else {
crm_trace("Adding %s at position %d (first)", child->name, position);
CRM_LOG_ASSERT(position == 0);
xmlAddChild(match, child);
}
crm_node_created(child);
} else if(strcmp(op, "move") == 0) {
int position = 0;
crm_element_value_int(change, XML_DIFF_POSITION, &position);
if(position != __xml_offset(match)) {
xmlNode *match_child = NULL;
int p = position;
if(p > __xml_offset(match)) {
p++; /* Skip ourselves */
}
CRM_ASSERT(match->parent != NULL);
match_child = match->parent->children;
while(match_child && p != __xml_offset(match_child)) {
match_child = match_child->next;
}
crm_trace("Moving %s to position %d (was %d, prev %p, %s %p)",
match->name, position, __xml_offset(match), match->prev,
match_child?"next":"last", match_child?match_child:match->parent->last);
if(match_child) {
xmlAddPrevSibling(match_child, match);
} else {
CRM_ASSERT(match->parent->last != NULL);
xmlAddNextSibling(match->parent->last, match);
}
} else {
crm_trace("%s is already in position %d", match->name, position);
}
if(position != __xml_offset(match)) {
crm_err("Moved %s.%d to position %d instead of %d (%p)",
match->name, ID(match), __xml_offset(match), position, match->prev);
rc = -pcmk_err_diff_failed;
}
} else if(strcmp(op, "delete") == 0) {
free_xml(match);
} else if(strcmp(op, "modify") == 0) {
xmlAttr *pIter = crm_first_attr(match);
xmlNode *attrs = __xml_first_child(first_named_child(change, XML_DIFF_RESULT));
if(attrs == NULL) {
rc = -ENOMSG;
continue;
}
while(pIter != NULL) {
const char *name = (const char *)pIter->name;
pIter = pIter->next;
xml_remove_prop(match, name);
}
for (pIter = crm_first_attr(attrs); pIter != NULL; pIter = pIter->next) {
const char *name = (const char *)pIter->name;
const char *value = crm_element_value(attrs, name);
crm_xml_add(match, name, value);
}
} else {
crm_err("Unknown operation: %s", op);
}
}
return rc;
}
int
xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version)
{
int format = 1;
int rc = pcmk_ok;
xmlNode *old = NULL;
const char *digest = crm_element_value(patchset, XML_ATTR_DIGEST);
if(patchset == NULL) {
return rc;
}
xml_log_patchset(LOG_TRACE, __FUNCTION__, patchset);
crm_element_value_int(patchset, "format", &format);
if(check_version) {
rc = xml_patch_version_check(xml, patchset, format);
if(rc != pcmk_ok) {
return rc;
}
}
if(digest) {
/* Make it available for logging if the result doesn't have the expected digest */
old = copy_xml(xml);
}
if(rc == pcmk_ok) {
switch(format) {
case 1:
rc = xml_apply_patchset_v1(xml, patchset, check_version);
break;
case 2:
rc = xml_apply_patchset_v2(xml, patchset, check_version);
break;
default:
crm_err("Unknown patch format: %d", format);
rc = -EINVAL;
}
}
if(rc == pcmk_ok && digest) {
static struct qb_log_callsite *digest_cs = NULL;
char *new_digest = NULL;
char *version = crm_element_value_copy(xml, XML_ATTR_CRM_VERSION);
if (digest_cs == NULL) {
digest_cs =
qb_log_callsite_get(__func__, __FILE__, "diff-digest", LOG_TRACE, __LINE__,
crm_trace_nonlog);
}
new_digest = calculate_xml_versioned_digest(xml, FALSE, TRUE, version);
if (safe_str_neq(new_digest, digest)) {
crm_info("v%d digest mis-match: expected %s, calculated %s", format, digest, new_digest);
rc = -pcmk_err_diff_failed;
if (digest_cs && digest_cs->targets) {
save_xml_to_file(old, "PatchDigest:input", NULL);
save_xml_to_file(xml, "PatchDigest:result", NULL);
save_xml_to_file(patchset,"PatchDigest:diff", NULL);
} else {
crm_trace("%p %.6x", digest_cs, digest_cs ? digest_cs->targets : 0);
}
} else {
crm_trace("v%d digest matched: expected %s, calculated %s", format, digest, new_digest);
}
free(new_digest);
free(version);
}
free_xml(old);
return rc;
}
xmlNode *
find_xml_node(xmlNode * root, const char *search_path, gboolean must_find)
{
xmlNode *a_child = NULL;
const char *name = "NULL";
if (root != NULL) {
name = crm_element_name(root);
}
if (search_path == NULL) {
crm_warn("Will never find <NULL>");
return NULL;
}
for (a_child = __xml_first_child(root); a_child != NULL; a_child = __xml_next(a_child)) {
if (strcmp((const char *)a_child->name, search_path) == 0) {
/* crm_trace("returning node (%s).", crm_element_name(a_child)); */
return a_child;
}
}
if (must_find) {
crm_warn("Could not find %s in %s.", search_path, name);
} else if (root != NULL) {
crm_trace("Could not find %s in %s.", search_path, name);
} else {
crm_trace("Could not find %s in <NULL>.", search_path);
}
return NULL;
}
xmlNode *
find_entity(xmlNode * parent, const char *node_name, const char *id)
{
xmlNode *a_child = NULL;
for (a_child = __xml_first_child(parent); a_child != NULL; a_child = __xml_next(a_child)) {
/* Uncertain if node_name == NULL check is strictly necessary here */
if (node_name == NULL || strcmp((const char *)a_child->name, node_name) == 0) {
const char *cid = ID(a_child);
if (id == NULL || (cid != NULL && strcmp(id, cid) == 0)) {
return a_child;
}
}
}
crm_trace("node <%s id=%s> not found in %s.", node_name, id, crm_element_name(parent));
return NULL;
}
void
copy_in_properties(xmlNode * target, xmlNode * src)
{
if (src == NULL) {
crm_warn("No node to copy properties from");
} else if (target == NULL) {
crm_err("No node to copy properties into");
} else {
xmlAttrPtr pIter = NULL;
for (pIter = crm_first_attr(src); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
expand_plus_plus(target, p_name, p_value);
}
}
return;
}
void
fix_plus_plus_recursive(xmlNode * target)
{
/* TODO: Remove recursion and use xpath searches for value++ */
xmlNode *child = NULL;
xmlAttrPtr pIter = NULL;
for (pIter = crm_first_attr(target); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
expand_plus_plus(target, p_name, p_value);
}
for (child = __xml_first_child(target); child != NULL; child = __xml_next(child)) {
fix_plus_plus_recursive(child);
}
}
void
expand_plus_plus(xmlNode * target, const char *name, const char *value)
{
int offset = 1;
int name_len = 0;
int int_value = 0;
int value_len = 0;
const char *old_value = NULL;
if (value == NULL || name == NULL) {
return;
}
old_value = crm_element_value(target, name);
if (old_value == NULL) {
/* if no previous value, set unexpanded */
goto set_unexpanded;
} else if (strstr(value, name) != value) {
goto set_unexpanded;
}
name_len = strlen(name);
value_len = strlen(value);
if (value_len < (name_len + 2)
|| value[name_len] != '+' || (value[name_len + 1] != '+' && value[name_len + 1] != '=')) {
goto set_unexpanded;
}
/* if we are expanding ourselves,
* then no previous value was set and leave int_value as 0
*/
if (old_value != value) {
int_value = char2score(old_value);
}
if (value[name_len + 1] != '+') {
const char *offset_s = value + (name_len + 2);
offset = char2score(offset_s);
}
int_value += offset;
if (int_value > INFINITY) {
int_value = (int)INFINITY;
}
crm_xml_add_int(target, name, int_value);
return;
set_unexpanded:
if (old_value == value) {
/* the old value is already set, nothing to do */
return;
}
crm_xml_add(target, name, value);
return;
}
xmlDoc *
getDocPtr(xmlNode * node)
{
xmlDoc *doc = NULL;
CRM_CHECK(node != NULL, return NULL);
doc = node->doc;
if (doc == NULL) {
doc = xmlNewDoc((const xmlChar *)"1.0");
xmlDocSetRootElement(doc, node);
xmlSetTreeDoc(node, doc);
}
return doc;
}
xmlNode *
add_node_copy(xmlNode * parent, xmlNode * src_node)
{
xmlNode *child = NULL;
xmlDoc *doc = getDocPtr(parent);
CRM_CHECK(src_node != NULL, return NULL);
child = xmlDocCopyNode(src_node, doc, 1);
xmlAddChild(parent, child);
crm_node_created(child);
return child;
}
int
add_node_nocopy(xmlNode * parent, const char *name, xmlNode * child)
{
add_node_copy(parent, child);
free_xml(child);
return 1;
}
static bool
__xml_acl_check(xmlNode *xml, const char *name, enum xml_private_flags mode)
{
CRM_ASSERT(xml);
CRM_ASSERT(xml->doc);
CRM_ASSERT(xml->doc->_private);
#if ENABLE_ACL
{
if(TRACKING_CHANGES(xml) && xml_acl_enabled(xml)) {
int offset = 0;
xmlNode *parent = xml;
char buffer[XML_BUFFER_SIZE];
xml_private_t *docp = xml->doc->_private;
if(docp->acls == NULL) {
crm_trace("Ordinary user %s cannot access the CIB without any defined ACLs", docp->user);
set_doc_flag(xml, xpf_acl_denied);
return FALSE;
}
offset = __get_prefix(NULL, xml, buffer, offset);
if(name) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "[@%s]", name);
}
CRM_LOG_ASSERT(offset > 0);
/* Walk the tree upwards looking for xml_acl_* flags
* - Creating an attribute requires write permissions for the node
* - Creating a child requires write permissions for the parent
*/
if(name) {
xmlAttr *attr = xmlHasProp(xml, (const xmlChar *)name);
if(attr && mode == xpf_acl_create) {
mode = xpf_acl_write;
}
}
while(parent && parent->_private) {
xml_private_t *p = parent->_private;
if(__xml_acl_mode_test(p->flags, mode)) {
return TRUE;
} else if(is_set(p->flags, xpf_acl_deny)) {
crm_trace("%x access denied to %s: parent", mode, buffer);
set_doc_flag(xml, xpf_acl_denied);
return FALSE;
}
parent = parent->parent;
}
crm_trace("%x access denied to %s: default", mode, buffer);
set_doc_flag(xml, xpf_acl_denied);
return FALSE;
}
}
#endif
return TRUE;
}
const char *
crm_xml_add(xmlNode * node, const char *name, const char *value)
{
bool dirty = FALSE;
xmlAttr *attr = NULL;
CRM_CHECK(node != NULL, return NULL);
CRM_CHECK(name != NULL, return NULL);
if (value == NULL) {
return NULL;
}
#if XML_PARANOIA_CHECKS
{
const char *old_value = NULL;
old_value = crm_element_value(node, name);
/* Could be re-setting the same value */
CRM_CHECK(old_value != value, crm_err("Cannot reset %s with crm_xml_add(%s)", name, value);
return value);
}
#endif
if(TRACKING_CHANGES(node)) {
const char *old = crm_element_value(node, name);
if(old == NULL || value == NULL || strcmp(old, value) != 0) {
dirty = TRUE;
}
}
if(dirty && __xml_acl_check(node, name, xpf_acl_create) == FALSE) {
crm_trace("Cannot add %s=%s to %s", name, value, node->name);
return NULL;
}
attr = xmlSetProp(node, (const xmlChar *)name, (const xmlChar *)value);
if(dirty) {
crm_attr_dirty(attr);
}
CRM_CHECK(attr && attr->children && attr->children->content, return NULL);
return (char *)attr->children->content;
}
const char *
crm_xml_replace(xmlNode * node, const char *name, const char *value)
{
bool dirty = FALSE;
xmlAttr *attr = NULL;
const char *old_value = NULL;
CRM_CHECK(node != NULL, return NULL);
CRM_CHECK(name != NULL && name[0] != 0, return NULL);
old_value = crm_element_value(node, name);
/* Could be re-setting the same value */
CRM_CHECK(old_value != value, return value);
if(__xml_acl_check(node, name, xpf_acl_write) == FALSE) {
/* Create a fake object linked to doc->_private instead? */
crm_trace("Cannot replace %s=%s to %s", name, value, node->name);
return NULL;
} else if (old_value != NULL && value == NULL) {
xml_remove_prop(node, name);
return NULL;
} else if (value == NULL) {
return NULL;
}
if(TRACKING_CHANGES(node)) {
if(old_value == NULL || value == NULL || strcmp(old_value, value) != 0) {
dirty = TRUE;
}
}
attr = xmlSetProp(node, (const xmlChar *)name, (const xmlChar *)value);
if(dirty) {
crm_attr_dirty(attr);
}
CRM_CHECK(attr && attr->children && attr->children->content, return NULL);
return (char *)attr->children->content;
}
const char *
crm_xml_add_int(xmlNode * node, const char *name, int value)
{
char *number = crm_itoa(value);
const char *added = crm_xml_add(node, name, number);
free(number);
return added;
}
xmlNode *
create_xml_node(xmlNode * parent, const char *name)
{
xmlDoc *doc = NULL;
xmlNode *node = NULL;
if (name == NULL || name[0] == 0) {
CRM_CHECK(name != NULL && name[0] == 0, return NULL);
return NULL;
}
if (parent == NULL) {
doc = xmlNewDoc((const xmlChar *)"1.0");
node = xmlNewDocRawNode(doc, NULL, (const xmlChar *)name, NULL);
xmlDocSetRootElement(doc, node);
} else {
doc = getDocPtr(parent);
node = xmlNewDocRawNode(doc, NULL, (const xmlChar *)name, NULL);
xmlAddChild(parent, node);
}
crm_node_created(node);
return node;
}
static inline int
__get_prefix(const char *prefix, xmlNode *xml, char *buffer, int offset)
{
const char *id = ID(xml);
if(offset == 0 && prefix == NULL && xml->parent) {
offset = __get_prefix(NULL, xml->parent, buffer, offset);
}
if(id) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "/%s[@id='%s']", (const char *)xml->name, id);
} else if(xml->name) {
offset += snprintf(buffer + offset, XML_BUFFER_SIZE - offset, "/%s", (const char *)xml->name);
}
return offset;
}
char *
xml_get_path(xmlNode *xml)
{
int offset = 0;
char buffer[XML_BUFFER_SIZE];
if(__get_prefix(NULL, xml, buffer, offset) > 0) {
return strdup(buffer);
}
return NULL;
}
void
free_xml(xmlNode * child)
{
if (child != NULL) {
xmlNode *top = NULL;
xmlDoc *doc = child->doc;
xml_private_t *p = child->_private;
if (doc != NULL) {
top = xmlDocGetRootElement(doc);
}
if (doc != NULL && top == child) {
/* Free everything */
xmlFreeDoc(doc);
} else if(__xml_acl_check(child, NULL, xpf_acl_write) == FALSE) {
int offset = 0;
char buffer[XML_BUFFER_SIZE];
__get_prefix(NULL, child, buffer, offset);
crm_trace("Cannot remove %s %x", buffer, p->flags);
return;
} else {
if(doc && TRACKING_CHANGES(child) && is_not_set(p->flags, xpf_created)) {
int offset = 0;
char buffer[XML_BUFFER_SIZE];
if(__get_prefix(NULL, child, buffer, offset) > 0) {
crm_trace("Deleting %s %p from %p", buffer, child, doc);
p = doc->_private;
p->deleted_paths = g_list_append(p->deleted_paths, strdup(buffer));
set_doc_flag(child, xpf_dirty);
}
}
/* Free this particular subtree
* Make sure to unlink it from the parent first
*/
xmlUnlinkNode(child);
xmlFreeNode(child);
}
}
}
xmlNode *
copy_xml(xmlNode * src)
{
xmlDoc *doc = xmlNewDoc((const xmlChar *)"1.0");
xmlNode *copy = xmlDocCopyNode(src, doc, 1);
xmlDocSetRootElement(doc, copy);
xmlSetTreeDoc(copy, doc);
return copy;
}
static void
crm_xml_err(void *ctx, const char *msg, ...)
G_GNUC_PRINTF(2, 3);
static void
crm_xml_err(void *ctx, const char *msg, ...)
{
int len = 0;
va_list args;
char *buf = NULL;
static int buffer_len = 0;
static char *buffer = NULL;
static struct qb_log_callsite *xml_error_cs = NULL;
va_start(args, msg);
len = vasprintf(&buf, msg, args);
if(xml_error_cs == NULL) {
xml_error_cs = qb_log_callsite_get(
__func__, __FILE__, "xml library error", LOG_TRACE, __LINE__, crm_trace_nonlog);
}
if (strchr(buf, '\n')) {
buf[len - 1] = 0;
if (buffer) {
crm_err("XML Error: %s%s", buffer, buf);
free(buffer);
} else {
crm_err("XML Error: %s", buf);
}
if (xml_error_cs && xml_error_cs->targets) {
crm_abort(__FILE__, __PRETTY_FUNCTION__, __LINE__, "xml library error", TRUE, TRUE);
}
buffer = NULL;
buffer_len = 0;
} else if (buffer == NULL) {
buffer_len = len;
buffer = buf;
buf = NULL;
} else {
buffer = realloc_safe(buffer, 1 + buffer_len + len);
memcpy(buffer + buffer_len, buf, len);
buffer_len += len;
buffer[buffer_len] = 0;
}
va_end(args);
free(buf);
}
xmlNode *
string2xml(const char *input)
{
xmlNode *xml = NULL;
xmlDocPtr output = NULL;
xmlParserCtxtPtr ctxt = NULL;
xmlErrorPtr last_error = NULL;
if (input == NULL) {
crm_err("Can't parse NULL input");
return NULL;
}
/* create a parser context */
ctxt = xmlNewParserCtxt();
CRM_CHECK(ctxt != NULL, return NULL);
/* xmlCtxtUseOptions(ctxt, XML_PARSE_NOBLANKS|XML_PARSE_RECOVER); */
xmlCtxtResetLastError(ctxt);
xmlSetGenericErrorFunc(ctxt, crm_xml_err);
/* initGenericErrorDefaultFunc(crm_xml_err); */
output =
xmlCtxtReadDoc(ctxt, (const xmlChar *)input, NULL, NULL,
XML_PARSE_NOBLANKS | XML_PARSE_RECOVER);
if (output) {
xml = xmlDocGetRootElement(output);
}
last_error = xmlCtxtGetLastError(ctxt);
if (last_error && last_error->code != XML_ERR_OK) {
/* crm_abort(__FILE__,__FUNCTION__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
/*
* http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
* http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
*/
crm_warn("Parsing failed (domain=%d, level=%d, code=%d): %s",
last_error->domain, last_error->level, last_error->code, last_error->message);
if (last_error->code == XML_ERR_DOCUMENT_EMPTY) {
CRM_LOG_ASSERT("Cannot parse an empty string");
} else if (last_error->code != XML_ERR_DOCUMENT_END) {
crm_err("Couldn't%s parse %d chars: %s", xml ? " fully" : "", (int)strlen(input),
input);
if (xml != NULL) {
crm_log_xml_err(xml, "Partial");
}
} else {
int len = strlen(input);
int lpc = 0;
while(lpc < len) {
crm_warn("Parse error[+%.3d]: %.80s", lpc, input+lpc);
lpc += 80;
}
CRM_LOG_ASSERT("String parsing error");
}
}
xmlFreeParserCtxt(ctxt);
return xml;
}
xmlNode *
stdin2xml(void)
{
size_t data_length = 0;
size_t read_chars = 0;
char *xml_buffer = NULL;
xmlNode *xml_obj = NULL;
do {
size_t next = XML_BUFFER_SIZE + data_length + 1;
if(next <= 0) {
crm_err("Buffer size exceeded at: %l + %d", data_length, XML_BUFFER_SIZE);
break;
}
xml_buffer = realloc_safe(xml_buffer, next);
read_chars = fread(xml_buffer + data_length, 1, XML_BUFFER_SIZE, stdin);
data_length += read_chars;
} while (read_chars > 0);
if (data_length == 0) {
crm_warn("No XML supplied on stdin");
free(xml_buffer);
return NULL;
}
xml_buffer[data_length] = '\0';
xml_obj = string2xml(xml_buffer);
free(xml_buffer);
crm_log_xml_trace(xml_obj, "Created fragment");
return xml_obj;
}
static char *
decompress_file(const char *filename)
{
char *buffer = NULL;
#if HAVE_BZLIB_H
int rc = 0;
size_t length = 0, read_len = 0;
BZFILE *bz_file = NULL;
FILE *input = fopen(filename, "r");
if (input == NULL) {
crm_perror(LOG_ERR, "Could not open %s for reading", filename);
return NULL;
}
bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0);
if (rc != BZ_OK) {
BZ2_bzReadClose(&rc, bz_file);
return NULL;
}
rc = BZ_OK;
while (rc == BZ_OK) {
buffer = realloc_safe(buffer, XML_BUFFER_SIZE + length + 1);
read_len = BZ2_bzRead(&rc, bz_file, buffer + length, XML_BUFFER_SIZE);
crm_trace("Read %ld bytes from file: %d", (long)read_len, rc);
if (rc == BZ_OK || rc == BZ_STREAM_END) {
length += read_len;
}
}
buffer[length] = '\0';
if (rc != BZ_STREAM_END) {
crm_err("Couldn't read compressed xml from file");
free(buffer);
buffer = NULL;
}
BZ2_bzReadClose(&rc, bz_file);
fclose(input);
#else
crm_err("Cannot read compressed files:" " bzlib was not available at compile time");
#endif
return buffer;
}
void
strip_text_nodes(xmlNode * xml)
{
xmlNode *iter = xml->children;
while (iter) {
xmlNode *next = iter->next;
switch (iter->type) {
case XML_TEXT_NODE:
/* Remove it */
xmlUnlinkNode(iter);
xmlFreeNode(iter);
break;
case XML_ELEMENT_NODE:
/* Search it */
strip_text_nodes(iter);
break;
default:
/* Leave it */
break;
}
iter = next;
}
}
xmlNode *
filename2xml(const char *filename)
{
xmlNode *xml = NULL;
xmlDocPtr output = NULL;
gboolean uncompressed = TRUE;
xmlParserCtxtPtr ctxt = NULL;
xmlErrorPtr last_error = NULL;
static int xml_options = XML_PARSE_NOBLANKS | XML_PARSE_RECOVER;
/* create a parser context */
ctxt = xmlNewParserCtxt();
CRM_CHECK(ctxt != NULL, return NULL);
/* xmlCtxtUseOptions(ctxt, XML_PARSE_NOBLANKS|XML_PARSE_RECOVER); */
xmlCtxtResetLastError(ctxt);
xmlSetGenericErrorFunc(ctxt, crm_xml_err);
/* initGenericErrorDefaultFunc(crm_xml_err); */
if (filename) {
uncompressed = !crm_ends_with(filename, ".bz2");
}
if (filename == NULL) {
/* STDIN_FILENO == fileno(stdin) */
output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL, xml_options);
} else if (uncompressed) {
output = xmlCtxtReadFile(ctxt, filename, NULL, xml_options);
} else {
char *input = decompress_file(filename);
output = xmlCtxtReadDoc(ctxt, (const xmlChar *)input, NULL, NULL, xml_options);
free(input);
}
if (output && (xml = xmlDocGetRootElement(output))) {
strip_text_nodes(xml);
}
last_error = xmlCtxtGetLastError(ctxt);
if (last_error && last_error->code != XML_ERR_OK) {
/* crm_abort(__FILE__,__FUNCTION__,__LINE__, "last_error->code != XML_ERR_OK", TRUE, TRUE); */
/*
* http://xmlsoft.org/html/libxml-xmlerror.html#xmlErrorLevel
* http://xmlsoft.org/html/libxml-xmlerror.html#xmlParserErrors
*/
crm_err("Parsing failed (domain=%d, level=%d, code=%d): %s",
last_error->domain, last_error->level, last_error->code, last_error->message);
if (last_error && last_error->code != XML_ERR_OK) {
crm_err("Couldn't%s parse %s", xml ? " fully" : "", filename);
if (xml != NULL) {
crm_log_xml_err(xml, "Partial");
}
}
}
xmlFreeParserCtxt(ctxt);
return xml;
}
/*!
* \internal
* \brief Add a "last written" attribute to an XML node, set to current time
*
* \param[in] xml_node XML node to get attribute
*
* \return Value that was set, or NULL on error
*/
const char *
crm_xml_add_last_written(xmlNode *xml_node)
{
time_t now = time(NULL);
char *now_str = ctime(&now);
now_str[24] = EOS; /* replace the newline */
return crm_xml_add(xml_node, XML_CIB_ATTR_WRITTEN, now_str);
}
static int
write_xml_stream(xmlNode * xml_node, const char *filename, FILE * stream, gboolean compress)
{
int res = 0;
char *buffer = NULL;
unsigned int out = 0;
CRM_CHECK(stream != NULL, return -1);
crm_trace("Writing XML out to %s", filename);
if (xml_node == NULL) {
crm_err("Cannot write NULL to %s", filename);
fclose(stream);
return -1;
}
crm_log_xml_trace(xml_node, "Writing out");
buffer = dump_xml_formatted(xml_node);
CRM_CHECK(buffer != NULL && strlen(buffer) > 0, crm_log_xml_warn(xml_node, "dump:failed");
goto bail);
if (compress) {
#if HAVE_BZLIB_H
int rc = BZ_OK;
unsigned int in = 0;
BZFILE *bz_file = NULL;
bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 30);
if (rc != BZ_OK) {
crm_err("bzWriteOpen failed: %d", rc);
} else {
BZ2_bzWrite(&rc, bz_file, buffer, strlen(buffer));
if (rc != BZ_OK) {
crm_err("bzWrite() failed: %d", rc);
}
}
if (rc == BZ_OK) {
BZ2_bzWriteClose(&rc, bz_file, 0, &in, &out);
if (rc != BZ_OK) {
crm_err("bzWriteClose() failed: %d", rc);
out = -1;
} else {
crm_trace("%s: In: %d, out: %d", filename, in, out);
}
}
#else
crm_err("Cannot write compressed files:" " bzlib was not available at compile time");
#endif
}
if (out <= 0) {
res = fprintf(stream, "%s", buffer);
if (res < 0) {
crm_perror(LOG_ERR, "Cannot write output to %s", filename);
goto bail;
}
}
bail:
if (fflush(stream) != 0) {
crm_perror(LOG_ERR, "fflush for %s failed:", filename);
res = -1;
}
if (fsync(fileno(stream)) < 0) {
crm_perror(LOG_ERR, "fsync for %s failed:", filename);
res = -1;
}
fclose(stream);
crm_trace("Saved %d bytes to the Cib as XML", res);
free(buffer);
return res;
}
int
write_xml_fd(xmlNode * xml_node, const char *filename, int fd, gboolean compress)
{
FILE *stream = NULL;
CRM_CHECK(fd > 0, return -1);
stream = fdopen(fd, "w");
return write_xml_stream(xml_node, filename, stream, compress);
}
int
write_xml_file(xmlNode * xml_node, const char *filename, gboolean compress)
{
FILE *stream = NULL;
stream = fopen(filename, "w");
return write_xml_stream(xml_node, filename, stream, compress);
}
xmlNode *
get_message_xml(xmlNode * msg, const char *field)
{
xmlNode *tmp = first_named_child(msg, field);
return __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;
}
static char *
crm_xml_escape_shuffle(char *text, int start, int *length, const char *replace)
{
int lpc;
int offset = strlen(replace) - 1; /* We have space for 1 char already */
*length += offset;
text = realloc_safe(text, *length);
for (lpc = (*length) - 1; lpc > (start + offset); lpc--) {
text[lpc] = text[lpc - offset];
}
memcpy(text + start, replace, offset + 1);
return text;
}
char *
crm_xml_escape(const char *text)
{
int index;
int changes = 0;
int length = 1 + strlen(text);
char *copy = strdup(text);
/*
* When xmlCtxtReadDoc() parses &lt; and friends in a
* value, it converts them to their human readable
* form.
*
* If one uses xmlNodeDump() to convert it back to a
* string, all is well, because special characters are
* converted back to their escape sequences.
*
* However xmlNodeDump() is randomly dog slow, even with the same
* input. So we need to replicate the escapeing in our custom
* version so that the result can be re-parsed by xmlCtxtReadDoc()
* when necessary.
*/
for (index = 0; index < length; index++) {
switch (copy[index]) {
case 0:
break;
case '<':
copy = crm_xml_escape_shuffle(copy, index, &length, "&lt;");
changes++;
break;
case '>':
copy = crm_xml_escape_shuffle(copy, index, &length, "&gt;");
changes++;
break;
case '"':
copy = crm_xml_escape_shuffle(copy, index, &length, "&quot;");
changes++;
break;
case '\'':
copy = crm_xml_escape_shuffle(copy, index, &length, "&apos;");
changes++;
break;
case '&':
copy = crm_xml_escape_shuffle(copy, index, &length, "&amp;");
changes++;
break;
case '\t':
/* Might as well just expand to a few spaces... */
copy = crm_xml_escape_shuffle(copy, index, &length, " ");
changes++;
break;
case '\n':
/* crm_trace("Convert: \\%.3o", copy[index]); */
copy = crm_xml_escape_shuffle(copy, index, &length, "\\n");
changes++;
break;
case '\r':
copy = crm_xml_escape_shuffle(copy, index, &length, "\\r");
changes++;
break;
/* For debugging...
case '\\':
crm_trace("Passthrough: \\%c", copy[index+1]);
break;
*/
default:
/* Check for and replace non-printing characters with their octal equivalent */
if(copy[index] < ' ' || copy[index] > '~') {
char *replace = crm_strdup_printf("\\%.3o", copy[index]);
/* crm_trace("Convert to octal: \\%.3o", copy[index]); */
copy = crm_xml_escape_shuffle(copy, index, &length, replace);
free(replace);
changes++;
}
}
}
if (changes) {
crm_trace("Dumped '%s'", copy);
}
return copy;
}
static inline void
dump_xml_attr(xmlAttrPtr attr, int options, char **buffer, int *offset, int *max)
{
char *p_value = NULL;
const char *p_name = NULL;
xml_private_t *p = NULL;
CRM_ASSERT(buffer != NULL);
if (attr == NULL || attr->children == NULL) {
return;
}
p = attr->_private;
if (p && is_set(p->flags, xpf_deleted)) {
return;
}
p_name = (const char *)attr->name;
p_value = crm_xml_escape((const char *)attr->children->content);
buffer_print(*buffer, *max, *offset, " %s=\"%s\"", p_name, p_value);
free(p_value);
}
static void
__xml_log_element(int log_level, const char *file, const char *function, int line,
const char *prefix, xmlNode * data, int depth, int options)
{
int max = 0;
int offset = 0;
const char *name = NULL;
const char *hidden = NULL;
xmlNode *child = NULL;
xmlAttrPtr pIter = NULL;
if(data == NULL) {
return;
}
name = crm_element_name(data);
if(is_set(options, xml_log_option_open)) {
char *buffer = NULL;
insert_prefix(options, &buffer, &offset, &max, depth);
if (data->type == XML_COMMENT_NODE) {
buffer_print(buffer, max, offset, "<!--%s-->", data->content);
} else {
buffer_print(buffer, max, offset, "<%s", name);
hidden = crm_element_value(data, "hidden");
for (pIter = crm_first_attr(data); pIter != NULL; pIter = pIter->next) {
xml_private_t *p = pIter->_private;
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
char *p_copy = NULL;
if(is_set(p->flags, xpf_deleted)) {
continue;
} else if ((is_set(options, xml_log_option_diff_plus)
|| is_set(options, xml_log_option_diff_minus))
&& strcmp(XML_DIFF_MARKER, p_name) == 0) {
continue;
} else if (hidden != NULL && p_name[0] != 0 && strstr(hidden, p_name) != NULL) {
p_copy = strdup("*****");
} else {
p_copy = crm_xml_escape(p_value);
}
buffer_print(buffer, max, offset, " %s=\"%s\"", p_name, p_copy);
free(p_copy);
}
if(xml_has_children(data) == FALSE) {
buffer_print(buffer, max, offset, "/>");
} else if(is_set(options, xml_log_option_children)) {
buffer_print(buffer, max, offset, ">");
} else {
buffer_print(buffer, max, offset, "/>");
}
}
do_crm_log_alias(log_level, file, function, line, "%s %s", prefix, buffer);
free(buffer);
}
if(data->type == XML_COMMENT_NODE) {
return;
} else if(xml_has_children(data) == FALSE) {
return;
} else if(is_set(options, xml_log_option_children)) {
offset = 0;
max = 0;
for (child = __xml_first_child(data); child != NULL; child = __xml_next(child)) {
__xml_log_element(log_level, file, function, line, prefix, child, depth + 1, options|xml_log_option_open|xml_log_option_close);
}
}
if(is_set(options, xml_log_option_close)) {
char *buffer = NULL;
insert_prefix(options, &buffer, &offset, &max, depth);
buffer_print(buffer, max, offset, "</%s>", name);
do_crm_log_alias(log_level, file, function, line, "%s %s", prefix, buffer);
free(buffer);
}
}
static void
__xml_log_change_element(int log_level, const char *file, const char *function, int line,
const char *prefix, xmlNode * data, int depth, int options)
{
xml_private_t *p;
char *prefix_m = NULL;
xmlNode *child = NULL;
xmlAttrPtr pIter = NULL;
if(data == NULL) {
return;
}
p = data->_private;
prefix_m = strdup(prefix);
prefix_m[1] = '+';
if(is_set(p->flags, xpf_dirty) && is_set(p->flags, xpf_created)) {
/* Continue and log full subtree */
__xml_log_element(log_level, file, function, line,
prefix_m, data, depth, options|xml_log_option_open|xml_log_option_close|xml_log_option_children);
} else if(is_set(p->flags, xpf_dirty)) {
char *spaces = calloc(80, 1);
int s_count = 0, s_max = 80;
char *prefix_del = NULL;
char *prefix_moved = NULL;
const char *flags = prefix;
insert_prefix(options, &spaces, &s_count, &s_max, depth);
prefix_del = strdup(prefix);
prefix_del[0] = '-';
prefix_del[1] = '-';
prefix_moved = strdup(prefix);
prefix_moved[1] = '~';
if(is_set(p->flags, xpf_moved)) {
flags = prefix_moved;
} else {
flags = prefix;
}
__xml_log_element(log_level, file, function, line,
flags, data, depth, options|xml_log_option_open);
for (pIter = crm_first_attr(data); pIter != NULL; pIter = pIter->next) {
const char *aname = (const char*)pIter->name;
p = pIter->_private;
if(is_set(p->flags, xpf_deleted)) {
const char *value = crm_element_value(data, aname);
flags = prefix_del;
do_crm_log_alias(log_level, file, function, line,
"%s %s @%s=%s", flags, spaces, aname, value);
} else if(is_set(p->flags, xpf_dirty)) {
const char *value = crm_element_value(data, aname);
if(is_set(p->flags, xpf_created)) {
flags = prefix_m;
} else if(is_set(p->flags, xpf_modified)) {
flags = prefix;
} else if(is_set(p->flags, xpf_moved)) {
flags = prefix_moved;
} else {
flags = prefix;
}
do_crm_log_alias(log_level, file, function, line,
"%s %s @%s=%s", flags, spaces, aname, value);
}
}
free(prefix_moved);
free(prefix_del);
free(spaces);
for (child = __xml_first_child(data); child != NULL; child = __xml_next(child)) {
__xml_log_change_element(log_level, file, function, line, prefix, child, depth + 1, options);
}
__xml_log_element(log_level, file, function, line,
prefix, data, depth, options|xml_log_option_close);
} else {
for (child = __xml_first_child(data); child != NULL; child = __xml_next(child)) {
__xml_log_change_element(log_level, file, function, line, prefix, child, depth + 1, options);
}
}
free(prefix_m);
}
void
log_data_element(int log_level, const char *file, const char *function, int line,
const char *prefix, xmlNode * data, int depth, int options)
{
xmlNode *a_child = NULL;
char *prefix_m = NULL;
if (prefix == NULL) {
prefix = "";
}
/* Since we use the same file and line, to avoid confusing libqb, we need to use the same format strings */
if (data == NULL) {
do_crm_log_alias(log_level, file, function, line, "%s: %s", prefix,
"No data to dump as XML");
return;
}
if(is_set(options, xml_log_option_dirty_add) || is_set(options, xml_log_option_dirty_add)) {
__xml_log_change_element(log_level, file, function, line, prefix, data, depth, options);
return;
}
if (is_set(options, xml_log_option_formatted)) {
if (is_set(options, xml_log_option_diff_plus)
&& (data->children == NULL || crm_element_value(data, XML_DIFF_MARKER))) {
options |= xml_log_option_diff_all;
prefix_m = strdup(prefix);
prefix_m[1] = '+';
prefix = prefix_m;
} else if (is_set(options, xml_log_option_diff_minus)
&& (data->children == NULL || crm_element_value(data, XML_DIFF_MARKER))) {
options |= xml_log_option_diff_all;
prefix_m = strdup(prefix);
prefix_m[1] = '-';
prefix = prefix_m;
}
}
if (is_set(options, xml_log_option_diff_short)
&& is_not_set(options, xml_log_option_diff_all)) {
/* Still searching for the actual change */
for (a_child = __xml_first_child(data); a_child != NULL; a_child = __xml_next(a_child)) {
log_data_element(log_level, file, function, line, prefix, a_child, depth + 1, options);
}
} else {
__xml_log_element(log_level, file, function, line, prefix, data, depth,
options|xml_log_option_open|xml_log_option_close|xml_log_option_children);
}
free(prefix_m);
}
static void
dump_filtered_xml(xmlNode * data, int options, char **buffer, int *offset, int *max)
{
int lpc;
xmlAttrPtr xIter = NULL;
static int filter_len = DIMOF(filter);
for (lpc = 0; options && lpc < filter_len; lpc++) {
filter[lpc].found = FALSE;
}
for (xIter = crm_first_attr(data); xIter != NULL; xIter = xIter->next) {
bool skip = FALSE;
const char *p_name = (const char *)xIter->name;
for (lpc = 0; skip == FALSE && lpc < filter_len; lpc++) {
if (filter[lpc].found == FALSE && strcmp(p_name, filter[lpc].string) == 0) {
filter[lpc].found = TRUE;
skip = TRUE;
break;
}
}
if (skip == FALSE) {
dump_xml_attr(xIter, options, buffer, offset, max);
}
}
}
static void
dump_xml_element(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
{
const char *name = NULL;
CRM_ASSERT(max != NULL);
CRM_ASSERT(offset != NULL);
CRM_ASSERT(buffer != NULL);
if (data == NULL) {
crm_trace("Nothing to dump");
return;
}
if (*buffer == NULL) {
*offset = 0;
*max = 0;
}
name = crm_element_name(data);
CRM_ASSERT(name != NULL);
insert_prefix(options, buffer, offset, max, depth);
buffer_print(*buffer, *max, *offset, "<%s", name);
if (options & xml_log_option_filtered) {
dump_filtered_xml(data, options, buffer, offset, max);
} else {
xmlAttrPtr xIter = NULL;
for (xIter = crm_first_attr(data); xIter != NULL; xIter = xIter->next) {
dump_xml_attr(xIter, options, buffer, offset, max);
}
}
if (data->children == NULL) {
buffer_print(*buffer, *max, *offset, "/>");
} else {
buffer_print(*buffer, *max, *offset, ">");
}
if (options & xml_log_option_formatted) {
buffer_print(*buffer, *max, *offset, "\n");
}
if (data->children) {
xmlNode *xChild = NULL;
for(xChild = data->children; xChild != NULL; xChild = xChild->next) {
crm_xml_dump(xChild, options, buffer, offset, max, depth + 1);
}
insert_prefix(options, buffer, offset, max, depth);
buffer_print(*buffer, *max, *offset, "</%s>", name);
if (options & xml_log_option_formatted) {
buffer_print(*buffer, *max, *offset, "\n");
}
}
}
static void
dump_xml_text(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
{
CRM_ASSERT(max != NULL);
CRM_ASSERT(offset != NULL);
CRM_ASSERT(buffer != NULL);
if (data == NULL) {
crm_trace("Nothing to dump");
return;
}
if (*buffer == NULL) {
*offset = 0;
*max = 0;
}
insert_prefix(options, buffer, offset, max, depth);
buffer_print(*buffer, *max, *offset, "%s", data->content);
if (options & xml_log_option_formatted) {
buffer_print(*buffer, *max, *offset, "\n");
}
}
static void
dump_xml_comment(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
{
CRM_ASSERT(max != NULL);
CRM_ASSERT(offset != NULL);
CRM_ASSERT(buffer != NULL);
if (data == NULL) {
crm_trace("Nothing to dump");
return;
}
if (*buffer == NULL) {
*offset = 0;
*max = 0;
}
insert_prefix(options, buffer, offset, max, depth);
buffer_print(*buffer, *max, *offset, "<!--");
buffer_print(*buffer, *max, *offset, "%s", data->content);
buffer_print(*buffer, *max, *offset, "-->");
if (options & xml_log_option_formatted) {
buffer_print(*buffer, *max, *offset, "\n");
}
}
void
crm_xml_dump(xmlNode * data, int options, char **buffer, int *offset, int *max, int depth)
{
if(data == NULL) {
*offset = 0;
*max = 0;
return;
}
#if 0
if (is_not_set(options, xml_log_option_filtered)) {
/* Turning this code on also changes the PE tests for some reason
* (not just newlines). Figure out why before considering to
* enable this permanently.
*
* It exists to help debug slowness in xmlNodeDump() and
* potentially if we ever want to go back to it.
*
* In theory it's a good idea (reuse) but our custom version does
* better for the filtered case and avoids the final strdup() for
* everything
*/
time_t now, next;
xmlDoc *doc = NULL;
xmlBuffer *xml_buffer = NULL;
*buffer = NULL;
doc = getDocPtr(data);
/* doc will only be NULL if data is */
CRM_CHECK(doc != NULL, return);
now = time(NULL);
xml_buffer = xmlBufferCreate();
CRM_ASSERT(xml_buffer != NULL);
/* The default allocator XML_BUFFER_ALLOC_EXACT does far too many
* realloc()s and it can take upwards of 18 seconds (yes, seconds)
* to dump a 28kb tree which XML_BUFFER_ALLOC_DOUBLEIT can do in
* less than 1 second.
*
* We could also use xmlBufferCreateSize() to start with a
* sane-ish initial size and avoid the first few doubles.
*/
xmlBufferSetAllocationScheme(xml_buffer, XML_BUFFER_ALLOC_DOUBLEIT);
*max = xmlNodeDump(xml_buffer, doc, data, 0, (options & xml_log_option_formatted));
if (*max > 0) {
*buffer = strdup((char *)xml_buffer->content);
}
next = time(NULL);
if ((now + 1) < next) {
crm_log_xml_trace(data, "Long time");
crm_err("xmlNodeDump() -> %dbytes took %ds", *max, next - now);
}
xmlBufferFree(xml_buffer);
return;
}
#endif
switch(data->type) {
case XML_ELEMENT_NODE:
/* Handle below */
dump_xml_element(data, options, buffer, offset, max, depth);
break;
case XML_TEXT_NODE:
/* if option xml_log_option_text is enabled, then dump XML_TEXT_NODE */
if (options & xml_log_option_text) {
dump_xml_text(data, options, buffer, offset, max, depth);
}
return;
case XML_COMMENT_NODE:
dump_xml_comment(data, options, buffer, offset, max, depth);
break;
default:
crm_warn("Unhandled type: %d", data->type);
return;
/*
XML_ATTRIBUTE_NODE = 2
XML_CDATA_SECTION_NODE = 4
XML_ENTITY_REF_NODE = 5
XML_ENTITY_NODE = 6
XML_PI_NODE = 7
XML_DOCUMENT_NODE = 9
XML_DOCUMENT_TYPE_NODE = 10
XML_DOCUMENT_FRAG_NODE = 11
XML_NOTATION_NODE = 12
XML_HTML_DOCUMENT_NODE = 13
XML_DTD_NODE = 14
XML_ELEMENT_DECL = 15
XML_ATTRIBUTE_DECL = 16
XML_ENTITY_DECL = 17
XML_NAMESPACE_DECL = 18
XML_XINCLUDE_START = 19
XML_XINCLUDE_END = 20
XML_DOCB_DOCUMENT_NODE = 21
*/
}
}
void
crm_buffer_add_char(char **buffer, int *offset, int *max, char c)
{
buffer_print(*buffer, *max, *offset, "%c", c);
}
char *
dump_xml_formatted_with_text(xmlNode * an_xml_node)
{
char *buffer = NULL;
int offset = 0, max = 0;
crm_xml_dump(an_xml_node, xml_log_option_formatted|xml_log_option_text, &buffer, &offset, &max, 0);
return buffer;
}
char *
dump_xml_formatted(xmlNode * an_xml_node)
{
char *buffer = NULL;
int offset = 0, max = 0;
crm_xml_dump(an_xml_node, xml_log_option_formatted, &buffer, &offset, &max, 0);
return buffer;
}
char *
dump_xml_unformatted(xmlNode * an_xml_node)
{
char *buffer = NULL;
int offset = 0, max = 0;
crm_xml_dump(an_xml_node, 0, &buffer, &offset, &max, 0);
return buffer;
}
gboolean
xml_has_children(const xmlNode * xml_root)
{
if (xml_root != NULL && xml_root->children != NULL) {
return TRUE;
}
return FALSE;
}
int
crm_element_value_int(xmlNode * data, const char *name, int *dest)
{
const char *value = crm_element_value(data, name);
CRM_CHECK(dest != NULL, return -1);
if (value) {
*dest = crm_int_helper(value, NULL);
return 0;
}
return -1;
}
int
crm_element_value_const_int(const xmlNode * data, const char *name, int *dest)
{
return crm_element_value_int((xmlNode *) data, name, dest);
}
const char *
crm_element_value_const(const xmlNode * data, const char *name)
{
return crm_element_value((xmlNode *) data, name);
}
char *
crm_element_value_copy(xmlNode * data, const char *name)
{
char *value_copy = NULL;
const char *value = crm_element_value(data, name);
if (value != NULL) {
value_copy = strdup(value);
}
return value_copy;
}
void
xml_remove_prop(xmlNode * obj, const char *name)
{
if(__xml_acl_check(obj, NULL, xpf_acl_write) == FALSE) {
crm_trace("Cannot remove %s from %s", name, obj->name);
} else if(TRACKING_CHANGES(obj)) {
/* Leave in place (marked for removal) until after the diff is calculated */
xml_private_t *p = NULL;
xmlAttr *attr = xmlHasProp(obj, (const xmlChar *)name);
p = attr->_private;
set_parent_flag(obj, xpf_dirty);
p->flags |= xpf_deleted;
/* crm_trace("Setting flag %x due to %s[@id=%s].%s", xpf_dirty, obj->name, ID(obj), name); */
} else {
xmlUnsetProp(obj, (const xmlChar *)name);
}
}
void
purge_diff_markers(xmlNode * a_node)
{
xmlNode *child = NULL;
CRM_CHECK(a_node != NULL, return);
xml_remove_prop(a_node, XML_DIFF_MARKER);
for (child = __xml_first_child(a_node); child != NULL; child = __xml_next(child)) {
purge_diff_markers(child);
}
}
void
save_xml_to_file(xmlNode * xml, const char *desc, const char *filename)
{
char *f = NULL;
if (filename == NULL) {
char *uuid = crm_generate_uuid();
f = crm_strdup_printf("/tmp/%s", uuid);
filename = f;
free(uuid);
}
crm_info("Saving %s to %s", desc, filename);
write_xml_file(xml, filename, FALSE);
free(f);
}
gboolean
apply_xml_diff(xmlNode * old, xmlNode * diff, xmlNode ** new)
{
gboolean result = TRUE;
int root_nodes_seen = 0;
static struct qb_log_callsite *digest_cs = NULL;
const char *digest = crm_element_value(diff, XML_ATTR_DIGEST);
const char *version = crm_element_value(diff, XML_ATTR_CRM_VERSION);
xmlNode *child_diff = NULL;
xmlNode *added = find_xml_node(diff, "diff-added", FALSE);
xmlNode *removed = find_xml_node(diff, "diff-removed", FALSE);
CRM_CHECK(new != NULL, return FALSE);
if (digest_cs == NULL) {
digest_cs =
qb_log_callsite_get(__func__, __FILE__, "diff-digest", LOG_TRACE, __LINE__,
crm_trace_nonlog);
}
crm_trace("Subtraction Phase");
for (child_diff = __xml_first_child(removed); child_diff != NULL;
child_diff = __xml_next(child_diff)) {
CRM_CHECK(root_nodes_seen == 0, result = FALSE);
if (root_nodes_seen == 0) {
*new = subtract_xml_object(NULL, old, child_diff, FALSE, NULL, NULL);
}
root_nodes_seen++;
}
if (root_nodes_seen == 0) {
*new = copy_xml(old);
} else if (root_nodes_seen > 1) {
crm_err("(-) Diffs cannot contain more than one change set..." " saw %d", root_nodes_seen);
result = FALSE;
}
root_nodes_seen = 0;
crm_trace("Addition Phase");
if (result) {
xmlNode *child_diff = NULL;
for (child_diff = __xml_first_child(added); child_diff != NULL;
child_diff = __xml_next(child_diff)) {
CRM_CHECK(root_nodes_seen == 0, result = FALSE);
if (root_nodes_seen == 0) {
add_xml_object(NULL, *new, child_diff, TRUE);
}
root_nodes_seen++;
}
}
if (root_nodes_seen > 1) {
crm_err("(+) Diffs cannot contain more than one change set..." " saw %d", root_nodes_seen);
result = FALSE;
} else if (result && digest) {
char *new_digest = NULL;
purge_diff_markers(*new); /* Purge now so the diff is ok */
new_digest = calculate_xml_versioned_digest(*new, FALSE, TRUE, version);
if (safe_str_neq(new_digest, digest)) {
crm_info("Digest mis-match: expected %s, calculated %s", digest, new_digest);
result = FALSE;
crm_trace("%p %.6x", digest_cs, digest_cs ? digest_cs->targets : 0);
if (digest_cs && digest_cs->targets) {
save_xml_to_file(old, "diff:original", NULL);
save_xml_to_file(diff, "diff:input", NULL);
save_xml_to_file(*new, "diff:new", NULL);
}
} else {
crm_trace("Digest matched: expected %s, calculated %s", digest, new_digest);
}
free(new_digest);
} else if (result) {
purge_diff_markers(*new); /* Purge now so the diff is ok */
}
return result;
}
static void
__xml_diff_object(xmlNode * old, xmlNode * new)
{
xmlNode *cIter = NULL;
xmlAttr *pIter = NULL;
CRM_CHECK(new != NULL, return);
if(old == NULL) {
crm_node_created(new);
__xml_acl_post_process(new); /* Check creation is allowed */
return;
} else {
xml_private_t *p = new->_private;
if(p->flags & xpf_processed) {
/* Avoid re-comparing nodes */
return;
}
p->flags |= xpf_processed;
}
for (pIter = crm_first_attr(new); pIter != NULL; pIter = pIter->next) {
xml_private_t *p = pIter->_private;
/* Assume everything was just created and take it from there */
p->flags |= xpf_created;
}
for (pIter = crm_first_attr(old); pIter != NULL; ) {
xmlAttr *prop = pIter;
xml_private_t *p = NULL;
const char *name = (const char *)pIter->name;
const char *old_value = crm_element_value(old, name);
xmlAttr *exists = xmlHasProp(new, pIter->name);
pIter = pIter->next;
if(exists == NULL) {
p = new->doc->_private;
/* Prevent the dirty flag being set recursively upwards */
clear_bit(p->flags, xpf_tracking);
exists = xmlSetProp(new, (const xmlChar *)name, (const xmlChar *)old_value);
set_bit(p->flags, xpf_tracking);
p = exists->_private;
p->flags = 0;
crm_trace("Lost %s@%s=%s", old->name, name, old_value);
xml_remove_prop(new, name);
} else {
int p_new = __xml_offset((xmlNode*)exists);
int p_old = __xml_offset((xmlNode*)prop);
const char *value = crm_element_value(new, name);
p = exists->_private;
p->flags = (p->flags & ~xpf_created);
if(strcmp(value, old_value) != 0) {
/* Restore the original value, so we can call crm_xml_add(),
* which checks ACLs
*/
char *vcopy = crm_element_value_copy(new, name);
crm_trace("Modified %s@%s %s->%s", old->name, name, old_value, vcopy);
xmlSetProp(new, prop->name, (const xmlChar *)old_value);
crm_xml_add(new, name, vcopy);
free(vcopy);
} else if(p_old != p_new) {
crm_info("Moved %s@%s (%d -> %d)", old->name, name, p_old, p_new);
__xml_node_dirty(new);
p->flags |= xpf_dirty|xpf_moved;
if(p_old > p_new) {
p = prop->_private;
p->flags |= xpf_skip;
} else {
p = exists->_private;
p->flags |= xpf_skip;
}
}
}
}
for (pIter = crm_first_attr(new); pIter != NULL; ) {
xmlAttr *prop = pIter;
xml_private_t *p = pIter->_private;
pIter = pIter->next;
if(is_set(p->flags, xpf_created)) {
char *name = strdup((const char *)prop->name);
char *value = crm_element_value_copy(new, name);
crm_trace("Created %s@%s=%s", new->name, name, value);
/* Remove plus create won't work as it will modify the relative attribute ordering */
if(__xml_acl_check(new, name, xpf_acl_write)) {
crm_attr_dirty(prop);
} else {
xmlUnsetProp(new, prop->name); /* Remove - change not allowed */
}
free(value);
free(name);
}
}
for (cIter = __xml_first_child(old); cIter != NULL; ) {
xmlNode *old_child = cIter;
xmlNode *new_child = find_element(new, cIter);
cIter = __xml_next(cIter);
if(new_child) {
__xml_diff_object(old_child, new_child);
} else {
/* Create then free (which will check the acls if necessary) */
xmlNode *candidate = add_node_copy(new, old_child);
xmlNode *top = xmlDocGetRootElement(candidate->doc);
__xml_node_clean(candidate);
__xml_acl_apply(top); /* Make sure any ACLs are applied to 'candidate' */
free_xml(candidate);
if (find_element(new, old_child) == NULL) {
xml_private_t *p = old_child->_private;
p->flags |= xpf_skip;
}
}
}
for (cIter = __xml_first_child(new); cIter != NULL; ) {
xmlNode *new_child = cIter;
xmlNode *old_child = find_element(old, cIter);
cIter = __xml_next(cIter);
if(old_child == NULL) {
xml_private_t *p = new_child->_private;
p->flags |= xpf_skip;
__xml_diff_object(old_child, new_child);
} else {
/* Check for movement, we already checked for differences */
int p_new = __xml_offset(new_child);
int p_old = __xml_offset(old_child);
if(p_old != p_new) {
xml_private_t *p = new_child->_private;
crm_info("%s.%s moved from %d to %d",
new_child->name, ID(new_child), p_old, p_new);
__xml_node_dirty(new);
p->flags |= xpf_moved;
if(p_old > p_new) {
p = old_child->_private;
} else {
p = new_child->_private;
}
p->flags |= xpf_skip;
}
}
}
}
void
xml_calculate_changes(xmlNode * old, xmlNode * new)
{
CRM_CHECK(safe_str_eq(crm_element_name(old), crm_element_name(new)), return);
CRM_CHECK(safe_str_eq(ID(old), ID(new)), return);
if(xml_tracking_changes(new) == FALSE) {
xml_track_changes(new, NULL, NULL, FALSE);
}
__xml_diff_object(old, new);
}
xmlNode *
diff_xml_object(xmlNode * old, xmlNode * new, gboolean suppress)
{
xmlNode *tmp1 = NULL;
xmlNode *diff = create_xml_node(NULL, "diff");
xmlNode *removed = create_xml_node(diff, "diff-removed");
xmlNode *added = create_xml_node(diff, "diff-added");
crm_xml_add(diff, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET);
tmp1 = subtract_xml_object(removed, old, new, FALSE, NULL, "removed:top");
if (suppress && tmp1 != NULL && can_prune_leaf(tmp1)) {
free_xml(tmp1);
}
tmp1 = subtract_xml_object(added, new, old, TRUE, NULL, "added:top");
if (suppress && tmp1 != NULL && can_prune_leaf(tmp1)) {
free_xml(tmp1);
}
if (added->children == NULL && removed->children == NULL) {
free_xml(diff);
diff = NULL;
}
return diff;
}
gboolean
can_prune_leaf(xmlNode * xml_node)
{
xmlNode *cIter = NULL;
xmlAttrPtr pIter = NULL;
gboolean can_prune = TRUE;
const char *name = crm_element_name(xml_node);
if (safe_str_eq(name, XML_TAG_RESOURCE_REF)
|| safe_str_eq(name, XML_CIB_TAG_OBJ_REF)
|| safe_str_eq(name, XML_ACL_TAG_ROLE_REF)
|| safe_str_eq(name, XML_ACL_TAG_ROLE_REFv1)) {
return FALSE;
}
for (pIter = crm_first_attr(xml_node); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
if (strcmp(p_name, XML_ATTR_ID) == 0) {
continue;
}
can_prune = FALSE;
}
cIter = __xml_first_child(xml_node);
while (cIter) {
xmlNode *child = cIter;
cIter = __xml_next(cIter);
if (can_prune_leaf(child)) {
free_xml(child);
} else {
can_prune = FALSE;
}
}
return can_prune;
}
void
diff_filter_context(int context, int upper_bound, int lower_bound,
xmlNode * xml_node, xmlNode * parent)
{
xmlNode *us = NULL;
xmlNode *child = NULL;
xmlAttrPtr pIter = NULL;
xmlNode *new_parent = parent;
const char *name = crm_element_name(xml_node);
CRM_CHECK(xml_node != NULL && name != NULL, return);
us = create_xml_node(parent, name);
for (pIter = crm_first_attr(xml_node); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
lower_bound = context;
crm_xml_add(us, p_name, p_value);
}
if (lower_bound >= 0 || upper_bound >= 0) {
crm_xml_add(us, XML_ATTR_ID, ID(xml_node));
new_parent = us;
} else {
upper_bound = in_upper_context(0, context, xml_node);
if (upper_bound >= 0) {
crm_xml_add(us, XML_ATTR_ID, ID(xml_node));
new_parent = us;
} else {
free_xml(us);
us = NULL;
}
}
for (child = __xml_first_child(us); child != NULL; child = __xml_next(child)) {
diff_filter_context(context, upper_bound - 1, lower_bound - 1, child, new_parent);
}
}
int
in_upper_context(int depth, int context, xmlNode * xml_node)
{
if (context == 0) {
return 0;
}
if (xml_node->properties) {
return depth;
} else if (depth < context) {
xmlNode *child = NULL;
for (child = __xml_first_child(xml_node); child != NULL; child = __xml_next(child)) {
if (in_upper_context(depth + 1, context, child)) {
return depth;
}
}
}
return 0;
}
static xmlNode *
find_xml_comment(xmlNode * root, xmlNode * search_comment)
{
xmlNode *a_child = NULL;
CRM_CHECK(search_comment->type == XML_COMMENT_NODE, return NULL);
for (a_child = __xml_first_child(root); a_child != NULL; a_child = __xml_next(a_child)) {
if (a_child->type != XML_COMMENT_NODE) {
continue;
}
if (safe_str_eq((const char *)a_child->content, (const char *)search_comment->content)) {
return a_child;
}
}
return NULL;
}
static xmlNode *
subtract_xml_comment(xmlNode * parent, xmlNode * left, xmlNode * right,
gboolean * changed)
{
CRM_CHECK(left != NULL, return NULL);
CRM_CHECK(left->type == XML_COMMENT_NODE, return NULL);
if (right == NULL
|| safe_str_neq((const char *)left->content, (const char *)right->content)) {
xmlNode *deleted = NULL;
deleted = add_node_copy(parent, left);
*changed = TRUE;
return deleted;
}
return NULL;
}
xmlNode *
subtract_xml_object(xmlNode * parent, xmlNode * left, xmlNode * right,
gboolean full, gboolean * changed, const char *marker)
{
gboolean dummy = FALSE;
gboolean skip = FALSE;
xmlNode *diff = NULL;
xmlNode *right_child = NULL;
xmlNode *left_child = NULL;
xmlAttrPtr xIter = NULL;
const char *id = NULL;
const char *name = NULL;
const char *value = NULL;
const char *right_val = NULL;
int lpc = 0;
static int filter_len = DIMOF(filter);
if (changed == NULL) {
changed = &dummy;
}
if (left == NULL) {
return NULL;
}
if (left->type == XML_COMMENT_NODE) {
return subtract_xml_comment(parent, left, right, changed);
}
id = ID(left);
if (right == NULL) {
xmlNode *deleted = NULL;
crm_trace("Processing <%s id=%s> (complete copy)", crm_element_name(left), id);
deleted = add_node_copy(parent, left);
crm_xml_add(deleted, XML_DIFF_MARKER, marker);
*changed = TRUE;
return deleted;
}
name = crm_element_name(left);
CRM_CHECK(name != NULL, return NULL);
CRM_CHECK(safe_str_eq(crm_element_name(left), crm_element_name(right)), return NULL);
/* check for XML_DIFF_MARKER in a child */
value = crm_element_value(right, XML_DIFF_MARKER);
if (value != NULL && strcmp(value, "removed:top") == 0) {
crm_trace("We are the root of the deletion: %s.id=%s", name, id);
*changed = TRUE;
return NULL;
}
/* Avoiding creating the full heirarchy would save even more work here */
diff = create_xml_node(parent, name);
/* Reset filter */
for (lpc = 0; lpc < filter_len; lpc++) {
filter[lpc].found = FALSE;
}
/* changes to child objects */
for (left_child = __xml_first_child(left); left_child != NULL;
left_child = __xml_next(left_child)) {
gboolean child_changed = FALSE;
right_child = find_element(right, left_child);
subtract_xml_object(diff, left_child, right_child, full, &child_changed, marker);
if (child_changed) {
*changed = TRUE;
}
}
if (*changed == FALSE) {
/* Nothing to do */
} else if (full) {
xmlAttrPtr pIter = NULL;
for (pIter = crm_first_attr(left); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
xmlSetProp(diff, (const xmlChar *)p_name, (const xmlChar *)p_value);
}
/* We already have everything we need... */
goto done;
} else if (id) {
xmlSetProp(diff, (const xmlChar *)XML_ATTR_ID, (const xmlChar *)id);
}
/* changes to name/value pairs */
for (xIter = crm_first_attr(left); xIter != NULL; xIter = xIter->next) {
const char *prop_name = (const char *)xIter->name;
xmlAttrPtr right_attr = NULL;
xml_private_t *p = NULL;
if (strcmp(prop_name, XML_ATTR_ID) == 0) {
continue;
}
skip = FALSE;
for (lpc = 0; skip == FALSE && lpc < filter_len; lpc++) {
if (filter[lpc].found == FALSE && strcmp(prop_name, filter[lpc].string) == 0) {
filter[lpc].found = TRUE;
skip = TRUE;
break;
}
}
if (skip) {
continue;
}
right_attr = xmlHasProp(right, (const xmlChar *)prop_name);
if (right_attr) {
p = right_attr->_private;
}
right_val = crm_element_value(right, prop_name);
if (right_val == NULL || (p && is_set(p->flags, xpf_deleted))) {
/* new */
*changed = TRUE;
if (full) {
xmlAttrPtr pIter = NULL;
for (pIter = crm_first_attr(left); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
xmlSetProp(diff, (const xmlChar *)p_name, (const xmlChar *)p_value);
}
break;
} else {
const char *left_value = crm_element_value(left, prop_name);
xmlSetProp(diff, (const xmlChar *)prop_name, (const xmlChar *)value);
crm_xml_add(diff, prop_name, left_value);
}
} else {
/* Only now do we need the left value */
const char *left_value = crm_element_value(left, prop_name);
if (strcmp(left_value, right_val) == 0) {
/* unchanged */
} else {
*changed = TRUE;
if (full) {
xmlAttrPtr pIter = NULL;
crm_trace("Changes detected to %s in <%s id=%s>", prop_name,
crm_element_name(left), id);
for (pIter = crm_first_attr(left); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
xmlSetProp(diff, (const xmlChar *)p_name, (const xmlChar *)p_value);
}
break;
} else {
crm_trace("Changes detected to %s (%s -> %s) in <%s id=%s>",
prop_name, left_value, right_val, crm_element_name(left), id);
crm_xml_add(diff, prop_name, left_value);
}
}
}
}
if (*changed == FALSE) {
free_xml(diff);
return NULL;
} else if (full == FALSE && id) {
crm_xml_add(diff, XML_ATTR_ID, id);
}
done:
return diff;
}
static int
add_xml_comment(xmlNode * parent, xmlNode * target, xmlNode * update)
{
CRM_CHECK(update != NULL, return 0);
CRM_CHECK(update->type == XML_COMMENT_NODE, return 0);
if (target == NULL) {
target = find_xml_comment(parent, update);
}
if (target == NULL) {
add_node_copy(parent, update);
/* We won't reach here currently */
} else if (safe_str_neq((const char *)target->content, (const char *)update->content)) {
xmlFree(target->content);
target->content = xmlStrdup(update->content);
}
return 0;
}
int
add_xml_object(xmlNode * parent, xmlNode * target, xmlNode * update, gboolean as_diff)
{
xmlNode *a_child = NULL;
const char *object_id = NULL;
const char *object_name = NULL;
#if XML_PARSE_DEBUG
crm_log_xml_trace("update:", update);
crm_log_xml_trace("target:", target);
#endif
CRM_CHECK(update != NULL, return 0);
if (update->type == XML_COMMENT_NODE) {
return add_xml_comment(parent, target, update);
}
object_name = crm_element_name(update);
object_id = ID(update);
CRM_CHECK(object_name != NULL, return 0);
if (target == NULL && object_id == NULL) {
/* placeholder object */
target = find_xml_node(parent, object_name, FALSE);
} else if (target == NULL) {
target = find_entity(parent, object_name, object_id);
}
if (target == NULL) {
target = create_xml_node(parent, object_name);
CRM_CHECK(target != NULL, return 0);
#if XML_PARSER_DEBUG
crm_trace("Added <%s%s%s/>", crm_str(object_name),
object_id ? " id=" : "", object_id ? object_id : "");
} else {
crm_trace("Found node <%s%s%s/> to update",
crm_str(object_name), object_id ? " id=" : "", object_id ? object_id : "");
#endif
}
CRM_CHECK(safe_str_eq(crm_element_name(target), crm_element_name(update)), return 0);
if (as_diff == FALSE) {
/* So that expand_plus_plus() gets called */
copy_in_properties(target, update);
} else {
/* No need for expand_plus_plus(), just raw speed */
xmlAttrPtr pIter = NULL;
for (pIter = crm_first_attr(update); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
/* Remove it first so the ordering of the update is preserved */
xmlUnsetProp(target, (const xmlChar *)p_name);
xmlSetProp(target, (const xmlChar *)p_name, (const xmlChar *)p_value);
}
}
for (a_child = __xml_first_child(update); a_child != NULL; a_child = __xml_next(a_child)) {
#if XML_PARSER_DEBUG
crm_trace("Updating child <%s id=%s>", crm_element_name(a_child), ID(a_child));
#endif
add_xml_object(target, NULL, a_child, as_diff);
}
#if XML_PARSER_DEBUG
crm_trace("Finished with <%s id=%s>", crm_str(object_name), crm_str(object_id));
#endif
return 0;
}
gboolean
update_xml_child(xmlNode * child, xmlNode * to_update)
{
gboolean can_update = TRUE;
xmlNode *child_of_child = NULL;
CRM_CHECK(child != NULL, return FALSE);
CRM_CHECK(to_update != NULL, return FALSE);
if (safe_str_neq(crm_element_name(to_update), crm_element_name(child))) {
can_update = FALSE;
} else if (safe_str_neq(ID(to_update), ID(child))) {
can_update = FALSE;
} else if (can_update) {
#if XML_PARSER_DEBUG
crm_log_xml_trace(child, "Update match found...");
#endif
add_xml_object(NULL, child, to_update, FALSE);
}
for (child_of_child = __xml_first_child(child); child_of_child != NULL;
child_of_child = __xml_next(child_of_child)) {
/* only update the first one */
if (can_update) {
break;
}
can_update = update_xml_child(child_of_child, to_update);
}
return can_update;
}
int
find_xml_children(xmlNode ** children, xmlNode * root,
const char *tag, const char *field, const char *value, gboolean search_matches)
{
int match_found = 0;
CRM_CHECK(root != NULL, return FALSE);
CRM_CHECK(children != NULL, return FALSE);
if (tag != NULL && safe_str_neq(tag, crm_element_name(root))) {
} else if (value != NULL && safe_str_neq(value, crm_element_value(root, field))) {
} else {
if (*children == NULL) {
*children = create_xml_node(NULL, __FUNCTION__);
}
add_node_copy(*children, root);
match_found = 1;
}
if (search_matches || match_found == 0) {
xmlNode *child = NULL;
for (child = __xml_first_child(root); child != NULL; child = __xml_next(child)) {
match_found += find_xml_children(children, child, tag, field, value, search_matches);
}
}
return match_found;
}
gboolean
replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update, gboolean delete_only)
{
gboolean can_delete = FALSE;
xmlNode *child_of_child = NULL;
const char *up_id = NULL;
const char *child_id = NULL;
const char *right_val = NULL;
CRM_CHECK(child != NULL, return FALSE);
CRM_CHECK(update != NULL, return FALSE);
up_id = ID(update);
child_id = ID(child);
if (up_id == NULL || (child_id && strcmp(child_id, up_id) == 0)) {
can_delete = TRUE;
}
if (safe_str_neq(crm_element_name(update), crm_element_name(child))) {
can_delete = FALSE;
}
if (can_delete && delete_only) {
xmlAttrPtr pIter = NULL;
for (pIter = crm_first_attr(update); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
right_val = crm_element_value(child, p_name);
if (safe_str_neq(p_value, right_val)) {
can_delete = FALSE;
}
}
}
if (can_delete && parent != NULL) {
crm_log_xml_trace(child, "Delete match found...");
if (delete_only || update == NULL) {
free_xml(child);
} else {
xmlNode *tmp = copy_xml(update);
xmlDoc *doc = tmp->doc;
xmlNode *old = NULL;
xml_accept_changes(tmp);
old = xmlReplaceNode(child, tmp);
if(xml_tracking_changes(tmp)) {
/* Replaced sections may have included relevant ACLs */
__xml_acl_apply(tmp);
}
xml_calculate_changes(old, tmp);
xmlDocSetRootElement(doc, old);
free_xml(old);
}
child = NULL;
return TRUE;
} else if (can_delete) {
crm_log_xml_debug(child, "Cannot delete the search root");
can_delete = FALSE;
}
child_of_child = __xml_first_child(child);
while (child_of_child) {
xmlNode *next = __xml_next(child_of_child);
can_delete = replace_xml_child(child, child_of_child, update, delete_only);
/* only delete the first one */
if (can_delete) {
child_of_child = NULL;
} else {
child_of_child = next;
}
}
return can_delete;
}
void
hash2nvpair(gpointer key, gpointer value, gpointer user_data)
{
const char *name = key;
const char *s_value = value;
xmlNode *xml_node = user_data;
xmlNode *xml_child = create_xml_node(xml_node, XML_CIB_TAG_NVPAIR);
crm_xml_add(xml_child, XML_ATTR_ID, name);
crm_xml_add(xml_child, XML_NVPAIR_ATTR_NAME, name);
crm_xml_add(xml_child, XML_NVPAIR_ATTR_VALUE, s_value);
crm_trace("dumped: name=%s value=%s", name, s_value);
}
void
hash2smartfield(gpointer key, gpointer value, gpointer user_data)
{
const char *name = key;
const char *s_value = value;
xmlNode *xml_node = user_data;
if (isdigit(name[0])) {
xmlNode *tmp = create_xml_node(xml_node, XML_TAG_PARAM);
crm_xml_add(tmp, XML_NVPAIR_ATTR_NAME, name);
crm_xml_add(tmp, XML_NVPAIR_ATTR_VALUE, s_value);
} else if (crm_element_value(xml_node, name) == NULL) {
crm_xml_add(xml_node, name, s_value);
crm_trace("dumped: %s=%s", name, s_value);
} else {
crm_trace("duplicate: %s=%s", name, s_value);
}
}
void
hash2field(gpointer key, gpointer value, gpointer user_data)
{
const char *name = key;
const char *s_value = value;
xmlNode *xml_node = user_data;
if (crm_element_value(xml_node, name) == NULL) {
crm_xml_add(xml_node, name, s_value);
} else {
crm_trace("duplicate: %s=%s", name, s_value);
}
}
void
hash2metafield(gpointer key, gpointer value, gpointer user_data)
{
char *crm_name = NULL;
if (key == NULL || value == NULL) {
return;
} else if (((char *)key)[0] == '#') {
return;
} else if (strstr(key, ":")) {
return;
}
crm_name = crm_meta_name(key);
hash2field(crm_name, value, user_data);
free(crm_name);
}
GHashTable *
xml2list(xmlNode * parent)
{
xmlNode *child = NULL;
xmlAttrPtr pIter = NULL;
xmlNode *nvpair_list = NULL;
GHashTable *nvpair_hash = g_hash_table_new_full(crm_str_hash, g_str_equal,
g_hash_destroy_str, g_hash_destroy_str);
CRM_CHECK(parent != NULL, return nvpair_hash);
nvpair_list = find_xml_node(parent, XML_TAG_ATTRS, FALSE);
if (nvpair_list == NULL) {
crm_trace("No attributes in %s", crm_element_name(parent));
crm_log_xml_trace(parent, "No attributes for resource op");
}
crm_log_xml_trace(nvpair_list, "Unpacking");
for (pIter = crm_first_attr(nvpair_list); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
crm_trace("Added %s=%s", p_name, p_value);
g_hash_table_insert(nvpair_hash, strdup(p_name), strdup(p_value));
}
for (child = __xml_first_child(nvpair_list); child != NULL; child = __xml_next(child)) {
if (strcmp((const char *)child->name, XML_TAG_PARAM) == 0) {
const char *key = crm_element_value(child, XML_NVPAIR_ATTR_NAME);
const char *value = crm_element_value(child, XML_NVPAIR_ATTR_VALUE);
crm_trace("Added %s=%s", key, value);
if (key != NULL && value != NULL) {
g_hash_table_insert(nvpair_hash, strdup(key), strdup(value));
}
}
}
return nvpair_hash;
}
typedef struct name_value_s {
const char *name;
const void *value;
} name_value_t;
static gint
sort_pairs(gconstpointer a, gconstpointer b)
{
int rc = 0;
const name_value_t *pair_a = a;
const name_value_t *pair_b = b;
CRM_ASSERT(a != NULL);
CRM_ASSERT(pair_a->name != NULL);
CRM_ASSERT(b != NULL);
CRM_ASSERT(pair_b->name != NULL);
rc = strcmp(pair_a->name, pair_b->name);
if (rc < 0) {
return -1;
} else if (rc > 0) {
return 1;
}
return 0;
}
static void
dump_pair(gpointer data, gpointer user_data)
{
name_value_t *pair = data;
xmlNode *parent = user_data;
crm_xml_add(parent, pair->name, pair->value);
}
xmlNode *
sorted_xml(xmlNode * input, xmlNode * parent, gboolean recursive)
{
xmlNode *child = NULL;
GListPtr sorted = NULL;
GListPtr unsorted = NULL;
name_value_t *pair = NULL;
xmlNode *result = NULL;
const char *name = NULL;
xmlAttrPtr pIter = NULL;
CRM_CHECK(input != NULL, return NULL);
name = crm_element_name(input);
CRM_CHECK(name != NULL, return NULL);
result = create_xml_node(parent, name);
for (pIter = crm_first_attr(input); pIter != NULL; pIter = pIter->next) {
const char *p_name = (const char *)pIter->name;
const char *p_value = crm_attr_value(pIter);
pair = calloc(1, sizeof(name_value_t));
pair->name = p_name;
pair->value = p_value;
unsorted = g_list_prepend(unsorted, pair);
pair = NULL;
}
sorted = g_list_sort(unsorted, sort_pairs);
g_list_foreach(sorted, dump_pair, result);
g_list_free_full(sorted, free);
for (child = __xml_first_child(input); child != NULL; child = __xml_next(child)) {
if (recursive) {
sorted_xml(child, result, recursive);
} else {
add_node_copy(result, child);
}
}
return result;
}
static gboolean
validate_with_dtd(xmlDocPtr doc, gboolean to_logs, const char *dtd_file)
{
gboolean valid = TRUE;
xmlDtdPtr dtd = NULL;
xmlValidCtxtPtr cvp = NULL;
CRM_CHECK(doc != NULL, return FALSE);
CRM_CHECK(dtd_file != NULL, return FALSE);
dtd = xmlParseDTD(NULL, (const xmlChar *)dtd_file);
if(dtd == NULL) {
crm_err("Could not locate/parse DTD: %s", dtd_file);
return TRUE;
}
cvp = xmlNewValidCtxt();
if(cvp) {
if (to_logs) {
cvp->userData = (void *)LOG_ERR;
cvp->error = (xmlValidityErrorFunc) xml_log;
cvp->warning = (xmlValidityWarningFunc) xml_log;
} else {
cvp->userData = (void *)stderr;
cvp->error = (xmlValidityErrorFunc) fprintf;
cvp->warning = (xmlValidityWarningFunc) fprintf;
}
if (!xmlValidateDtd(cvp, doc, dtd)) {
valid = FALSE;
}
xmlFreeValidCtxt(cvp);
} else {
crm_err("Internal error: No valid context");
}
xmlFreeDtd(dtd);
return valid;
}
xmlNode *
first_named_child(xmlNode * parent, const char *name)
{
xmlNode *match = NULL;
for (match = __xml_first_child(parent); match != NULL; match = __xml_next(match)) {
/*
* name == NULL gives first child regardless of name; this is
* semantically incorrect in this function, but may be necessary
* due to prior use of xml_child_iter_filter
*/
if (name == NULL || strcmp((const char *)match->name, name) == 0) {
return match;
}
}
return NULL;
}
#if 0
static void
relaxng_invalid_stderr(void *userData, xmlErrorPtr error)
{
/*
Structure xmlError
struct _xmlError {
int domain : What part of the library raised this er
int code : The error code, e.g. an xmlParserError
char * message : human-readable informative error messag
xmlErrorLevel level : how consequent is the error
char * file : the filename
int line : the line number if available
char * str1 : extra string information
char * str2 : extra string information
char * str3 : extra string information
int int1 : extra number information
int int2 : column number of the error or 0 if N/A
void * ctxt : the parser context if available
void * node : the node in the tree
}
*/
crm_err("Structured error: line=%d, level=%d %s", error->line, error->level, error->message);
}
#endif
static gboolean
validate_with_relaxng(xmlDocPtr doc, gboolean to_logs, const char *relaxng_file,
relaxng_ctx_cache_t ** cached_ctx)
{
int rc = 0;
gboolean valid = TRUE;
relaxng_ctx_cache_t *ctx = NULL;
CRM_CHECK(doc != NULL, return FALSE);
CRM_CHECK(relaxng_file != NULL, return FALSE);
if (cached_ctx && *cached_ctx) {
ctx = *cached_ctx;
} else {
crm_info("Creating RNG parser context");
ctx = calloc(1, sizeof(relaxng_ctx_cache_t));
xmlLoadExtDtdDefaultValue = 1;
ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file);
CRM_CHECK(ctx->parser != NULL, goto cleanup);
if (to_logs) {
xmlRelaxNGSetParserErrors(ctx->parser,
(xmlRelaxNGValidityErrorFunc) xml_log,
(xmlRelaxNGValidityWarningFunc) xml_log,
GUINT_TO_POINTER(LOG_ERR));
} else {
xmlRelaxNGSetParserErrors(ctx->parser,
(xmlRelaxNGValidityErrorFunc) fprintf,
(xmlRelaxNGValidityWarningFunc) fprintf, stderr);
}
ctx->rng = xmlRelaxNGParse(ctx->parser);
CRM_CHECK(ctx->rng != NULL, crm_err("Could not find/parse %s", relaxng_file);
goto cleanup);
ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng);
CRM_CHECK(ctx->valid != NULL, goto cleanup);
if (to_logs) {
xmlRelaxNGSetValidErrors(ctx->valid,
(xmlRelaxNGValidityErrorFunc) xml_log,
(xmlRelaxNGValidityWarningFunc) xml_log,
GUINT_TO_POINTER(LOG_ERR));
} else {
xmlRelaxNGSetValidErrors(ctx->valid,
(xmlRelaxNGValidityErrorFunc) fprintf,
(xmlRelaxNGValidityWarningFunc) fprintf, stderr);
}
}
/* xmlRelaxNGSetValidStructuredErrors( */
/* valid, relaxng_invalid_stderr, valid); */
xmlLineNumbersDefault(1);
rc = xmlRelaxNGValidateDoc(ctx->valid, doc);
if (rc > 0) {
valid = FALSE;
} else if (rc < 0) {
crm_err("Internal libxml error during validation\n");
}
cleanup:
if (cached_ctx) {
*cached_ctx = ctx;
} else {
if (ctx->parser != NULL) {
xmlRelaxNGFreeParserCtxt(ctx->parser);
}
if (ctx->valid != NULL) {
xmlRelaxNGFreeValidCtxt(ctx->valid);
}
if (ctx->rng != NULL) {
xmlRelaxNGFree(ctx->rng);
}
free(ctx);
}
return valid;
}
void
crm_xml_init(void)
{
static bool init = TRUE;
if(init) {
init = FALSE;
/* The default allocator XML_BUFFER_ALLOC_EXACT does far too many
* realloc_safe()s and it can take upwards of 18 seconds (yes, seconds)
* to dump a 28kb tree which XML_BUFFER_ALLOC_DOUBLEIT can do in
* less than 1 second.
*/
xmlSetBufferAllocationScheme(XML_BUFFER_ALLOC_DOUBLEIT);
/* Populate and free the _private field when nodes are created and destroyed */
xmlDeregisterNodeDefault(pcmkDeregisterNode);
xmlRegisterNodeDefault(pcmkRegisterNode);
__xml_build_schema_list();
}
}
void
crm_xml_cleanup(void)
{
int lpc = 0;
relaxng_ctx_cache_t *ctx = NULL;
crm_info("Cleaning up memory from libxml2");
for (; lpc < xml_schema_max; lpc++) {
switch (known_schemas[lpc].type) {
case 0:
/* None */
break;
case 1:
/* DTD - Not cached */
break;
case 2:
/* RNG - Cached */
ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache;
if (ctx == NULL) {
break;
}
if (ctx->parser != NULL) {
xmlRelaxNGFreeParserCtxt(ctx->parser);
}
if (ctx->valid != NULL) {
xmlRelaxNGFreeValidCtxt(ctx->valid);
}
if (ctx->rng != NULL) {
xmlRelaxNGFree(ctx->rng);
}
free(ctx);
known_schemas[lpc].cache = NULL;
break;
default:
break;
}
free(known_schemas[lpc].name);
free(known_schemas[lpc].location);
free(known_schemas[lpc].transform);
}
free(known_schemas);
xsltCleanupGlobals();
xmlCleanupParser();
}
static gboolean
validate_with(xmlNode * xml, int method, gboolean to_logs)
{
xmlDocPtr doc = NULL;
gboolean valid = FALSE;
int type = 0;
char *file = NULL;
if(method < 0) {
return FALSE;
}
type = known_schemas[method].type;
if(type == 0) {
return TRUE;
}
CRM_CHECK(xml != NULL, return FALSE);
doc = getDocPtr(xml);
file = get_schema_path(known_schemas[method].name, known_schemas[method].location);
crm_trace("Validating with: %s (type=%d)", crm_str(file), type);
switch (type) {
case 1:
valid = validate_with_dtd(doc, to_logs, file);
break;
case 2:
valid =
validate_with_relaxng(doc, to_logs, file,
(relaxng_ctx_cache_t **) & (known_schemas[method].cache));
break;
default:
crm_err("Unknown validator type: %d", type);
break;
}
free(file);
return valid;
}
#include <stdio.h>
static void
dump_file(const char *filename)
{
FILE *fp = NULL;
int ch, line = 0;
CRM_CHECK(filename != NULL, return);
fp = fopen(filename, "r");
CRM_CHECK(fp != NULL, return);
fprintf(stderr, "%4d ", ++line);
do {
ch = getc(fp);
if (ch == EOF) {
putc('\n', stderr);
break;
} else if (ch == '\n') {
fprintf(stderr, "\n%4d ", ++line);
} else {
putc(ch, stderr);
}
} while (1);
fclose(fp);
}
gboolean
validate_xml_verbose(xmlNode * xml_blob)
{
int fd = 0;
xmlDoc *doc = NULL;
xmlNode *xml = NULL;
gboolean rc = FALSE;
char *filename = strdup(CRM_STATE_DIR "/cib-invalid.XXXXXX");
umask(S_IWGRP | S_IWOTH | S_IROTH);
fd = mkstemp(filename);
write_xml_fd(xml_blob, filename, fd, FALSE);
dump_file(filename);
doc = xmlParseFile(filename);
xml = xmlDocGetRootElement(doc);
rc = validate_xml(xml, NULL, FALSE);
free_xml(xml);
unlink(filename);
free(filename);
return rc;
}
gboolean
validate_xml(xmlNode * xml_blob, const char *validation, gboolean to_logs)
{
int version = 0;
if (validation == NULL) {
validation = crm_element_value(xml_blob, XML_ATTR_VALIDATION);
}
if (validation == NULL) {
int lpc = 0;
bool valid = FALSE;
validation = crm_element_value(xml_blob, "ignore-dtd");
if (crm_is_true(validation)) {
/* Legacy compatibilty */
crm_xml_add(xml_blob, XML_ATTR_VALIDATION, "none");
return TRUE;
}
/* Work it out */
for (lpc = 0; lpc < xml_schema_max; lpc++) {
if(validate_with(xml_blob, lpc, FALSE)) {
valid = TRUE;
crm_xml_add(xml_blob, XML_ATTR_VALIDATION, known_schemas[lpc].name);
crm_info("XML validated against %s", known_schemas[lpc].name);
if(known_schemas[lpc].after_transform == 0) {
break;
}
}
}
return valid;
}
version = get_schema_version(validation);
if (strcmp(validation, "none") == 0) {
return TRUE;
} else if(version < xml_schema_max) {
return validate_with(xml_blob, version, to_logs);
}
crm_err("Unknown validator: %s", validation);
return FALSE;
}
#if HAVE_LIBXSLT
static xmlNode *
apply_transformation(xmlNode * xml, const char *transform)
{
char *xform = NULL;
xmlNode *out = NULL;
xmlDocPtr res = NULL;
xmlDocPtr doc = NULL;
xsltStylesheet *xslt = NULL;
CRM_CHECK(xml != NULL, return FALSE);
doc = getDocPtr(xml);
xform = get_schema_path(NULL, transform);
xmlLoadExtDtdDefaultValue = 1;
xmlSubstituteEntitiesDefault(1);
xslt = xsltParseStylesheetFile((const xmlChar *)xform);
CRM_CHECK(xslt != NULL, goto cleanup);
res = xsltApplyStylesheet(xslt, doc, NULL);
CRM_CHECK(res != NULL, goto cleanup);
out = xmlDocGetRootElement(res);
cleanup:
if (xslt) {
xsltFreeStylesheet(xslt);
}
free(xform);
return out;
}
#endif
const char *
get_schema_name(int version)
{
if (version < 0 || version >= xml_schema_max) {
return "unknown";
}
return known_schemas[version].name;
}
int
get_schema_version(const char *name)
{
int lpc = 0;
if(name == NULL) {
name = "none";
}
for (; lpc < xml_schema_max; lpc++) {
if (safe_str_eq(name, known_schemas[lpc].name)) {
return lpc;
}
}
return -1;
}
/* set which validation to use */
#include <crm/cib.h>
int
update_validation(xmlNode ** xml_blob, int *best, int max, gboolean transform, gboolean to_logs)
{
xmlNode *xml = NULL;
char *value = NULL;
int max_stable_schemas = xml_latest_schema_index();
int lpc = 0, match = -1, rc = pcmk_ok;
CRM_CHECK(best != NULL, return -EINVAL);
CRM_CHECK(xml_blob != NULL, return -EINVAL);
CRM_CHECK(*xml_blob != NULL, return -EINVAL);
*best = 0;
xml = *xml_blob;
value = crm_element_value_copy(xml, XML_ATTR_VALIDATION);
if (value != NULL) {
match = get_schema_version(value);
lpc = match;
if (lpc >= 0 && transform == FALSE) {
lpc++;
} else if (lpc < 0) {
crm_debug("Unknown validation type");
lpc = 0;
}
}
if (match >= max_stable_schemas) {
/* nothing to do */
free(value);
*best = match;
return pcmk_ok;
}
while(lpc <= max_stable_schemas) {
gboolean valid = TRUE;
crm_debug("Testing '%s' validation (%d of %d)",
known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>",
lpc, max_stable_schemas);
valid = validate_with(xml, lpc, to_logs);
if (valid) {
*best = lpc;
} else {
crm_trace("%s validation failed", known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>");
}
if (valid && transform) {
xmlNode *upgrade = NULL;
int next = known_schemas[lpc].after_transform;
if (next < 0) {
crm_trace("Stopping at %s", known_schemas[lpc].name);
break;
} else if (max > 0 && lpc == max) {
crm_trace("Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
known_schemas[lpc].name, lpc, next, max);
break;
} else if (max > 0 && next > max) {
crm_debug("Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)",
known_schemas[lpc].name, lpc, next, max);
break;
} else if (known_schemas[lpc].transform == NULL) {
crm_debug("%s-style configuration is also valid for %s",
known_schemas[lpc].name, known_schemas[next].name);
if (validate_with(xml, next, to_logs)) {
crm_debug("Configuration valid for schema: %s", known_schemas[next].name);
lpc = next;
*best = next;
rc = pcmk_ok;
} else {
crm_info("Configuration not valid for schema: %s", known_schemas[next].name);
}
} else {
crm_debug("Upgrading %s-style configuration to %s with %s",
known_schemas[lpc].name, known_schemas[next].name,
known_schemas[lpc].transform ? known_schemas[lpc].transform : "no-op");
#if HAVE_LIBXSLT
upgrade = apply_transformation(xml, known_schemas[lpc].transform);
#endif
if (upgrade == NULL) {
crm_err("Transformation %s failed", known_schemas[lpc].transform);
rc = -pcmk_err_transform_failed;
} else if (validate_with(upgrade, next, to_logs)) {
crm_info("Transformation %s successful", known_schemas[lpc].transform);
lpc = next;
*best = next;
free_xml(xml);
xml = upgrade;
rc = pcmk_ok;
} else {
crm_err("Transformation %s did not produce a valid configuration",
known_schemas[lpc].transform);
crm_log_xml_info(upgrade, "transform:bad");
free_xml(upgrade);
rc = -pcmk_err_schema_validation;
}
}
}
}
if (*best > match) {
crm_info("%s the configuration from %s to %s",
transform?"Transformed":"Upgraded",
value ? value : "<none>", known_schemas[*best].name);
crm_xml_add(xml, XML_ATTR_VALIDATION, known_schemas[*best].name);
}
*xml_blob = xml;
free(value);
return rc;
}
gboolean
cli_config_update(xmlNode ** xml, int *best_version, gboolean to_logs)
{
gboolean rc = TRUE;
const char *value = crm_element_value(*xml, XML_ATTR_VALIDATION);
int version = get_schema_version(value);
int min_version = xml_minimum_schema_index();
if (version < min_version) {
xmlNode *converted = NULL;
converted = copy_xml(*xml);
update_validation(&converted, &version, 0, TRUE, to_logs);
value = crm_element_value(converted, XML_ATTR_VALIDATION);
if (version < min_version) {
if (to_logs) {
crm_config_err("Your current configuration could only be upgraded to %s... "
"the minimum requirement is %s.\n", crm_str(value),
get_schema_name(min_version));
} else {
fprintf(stderr, "Your current configuration could only be upgraded to %s... "
"the minimum requirement is %s.\n",
crm_str(value), get_schema_name(min_version));
}
free_xml(converted);
converted = NULL;
rc = FALSE;
} else {
free_xml(*xml);
*xml = converted;
if (version < xml_latest_schema_index()) {
crm_config_warn("Your configuration was internally updated to %s... "
"which is acceptable but not the most recent",
get_schema_name(version));
} else if (to_logs) {
crm_info("Your configuration was internally updated to the latest version (%s)",
get_schema_name(version));
}
}
} else if (version >= get_schema_version("none")) {
if (to_logs) {
crm_config_warn("Configuration validation is currently disabled."
" It is highly encouraged and prevents many common cluster issues.");
} else {
fprintf(stderr, "Configuration validation is currently disabled."
" It is highly encouraged and prevents many common cluster issues.\n");
}
}
if (best_version) {
*best_version = version;
}
return rc;
}
xmlNode *
expand_idref(xmlNode * input, xmlNode * top)
{
const char *tag = NULL;
const char *ref = NULL;
xmlNode *result = input;
char *xpath_string = NULL;
if (result == NULL) {
return NULL;
} else if (top == NULL) {
top = input;
}
tag = crm_element_name(result);
ref = crm_element_value(result, XML_ATTR_IDREF);
if (ref != NULL) {
int xpath_max = 512, offset = 0;
xpath_string = calloc(1, xpath_max);
offset += snprintf(xpath_string + offset, xpath_max - offset, "//%s[@id='%s']", tag, ref);
CRM_LOG_ASSERT(offset > 0);
result = get_xpath_object(xpath_string, top, LOG_ERR);
if (result == NULL) {
char *nodePath = (char *)xmlGetNodePath(top);
crm_err("No match for %s found in %s: Invalid configuration", xpath_string,
crm_str(nodePath));
free(nodePath);
}
}
free(xpath_string);
return result;
}
const char *
crm_element_value(xmlNode * data, const char *name)
{
xmlAttr *attr = NULL;
if (data == NULL) {
crm_err("Couldn't find %s in NULL", name ? name : "<null>");
CRM_LOG_ASSERT(data != NULL);
return NULL;
} else if (name == NULL) {
crm_err("Couldn't find NULL in %s", crm_element_name(data));
return NULL;
}
attr = xmlHasProp(data, (const xmlChar *)name);
if (attr == NULL || attr->children == NULL) {
return NULL;
}
return (const char *)attr->children->content;
}
diff --git a/lib/fencing/st_client.c b/lib/fencing/st_client.c
index 5adce31a5e..6d4dce1347 100644
--- a/lib/fencing/st_client.c
+++ b/lib/fencing/st_client.c
@@ -1,2689 +1,2689 @@
/*
* Copyright (c) 2004 Andrew Beekhof <andrew@beekhof.net>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <crm_internal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <glib.h>
#include <dirent.h>
#include <libgen.h> /* Add it for compiling on OSX */
#include <crm/crm.h>
#include <crm/stonith-ng.h>
#include <crm/fencing/internal.h>
#include <crm/msg_xml.h>
#include <crm/common/xml.h>
#ifdef HAVE_STONITH_STONITH_H
# include <stonith/stonith.h>
# define LHA_STONITH_LIBRARY "libstonith.so.1"
static void *lha_agents_lib = NULL;
#endif
#include <crm/common/mainloop.h>
CRM_TRACE_INIT_DATA(stonith);
struct stonith_action_s {
/*! user defined data */
char *agent;
char *action;
char *victim;
char *args;
int timeout;
int async;
void *userdata;
void (*done_cb) (GPid pid, gint status, const char *output, gpointer user_data);
/*! internal async track data */
int fd_stdout;
int fd_stderr;
int last_timeout_signo;
/*! internal timing information */
time_t initial_start_time;
int tries;
int remaining_timeout;
guint timer_sigterm;
guint timer_sigkill;
int max_retries;
/* device output data */
GPid pid;
int rc;
char *output;
char *error;
};
typedef struct stonith_private_s {
char *token;
crm_ipc_t *ipc;
mainloop_io_t *source;
GHashTable *stonith_op_callback_table;
GList *notify_list;
void (*op_callback) (stonith_t * st, stonith_callback_data_t * data);
} stonith_private_t;
typedef struct stonith_notify_client_s {
const char *event;
const char *obj_id; /* implement one day */
const char *obj_type; /* implement one day */
void (*notify) (stonith_t * st, stonith_event_t * e);
} stonith_notify_client_t;
typedef struct stonith_callback_client_s {
void (*callback) (stonith_t * st, stonith_callback_data_t * data);
const char *id;
void *user_data;
gboolean only_success;
gboolean allow_timeout_updates;
struct timer_rec_s *timer;
} stonith_callback_client_t;
struct notify_blob_s {
stonith_t *stonith;
xmlNode *xml;
};
struct timer_rec_s {
int call_id;
int timeout;
guint ref;
stonith_t *stonith;
};
typedef int (*stonith_op_t) (const char *, int, const char *, xmlNode *,
xmlNode *, xmlNode *, xmlNode **, xmlNode **);
#if HAVE_STONITH_STONITH_H
static const char META_TEMPLATE[] =
"<?xml version=\"1.0\"?>\n"
"<!DOCTYPE resource-agent SYSTEM \"ra-api-1.dtd\">\n"
"<resource-agent name=\"%s\">\n"
" <version>1.0</version>\n"
" <longdesc lang=\"en\">\n"
"%s\n"
" </longdesc>\n"
" <shortdesc lang=\"en\">%s</shortdesc>\n"
"%s\n"
" <actions>\n"
" <action name=\"start\" timeout=\"20\" />\n"
" <action name=\"stop\" timeout=\"15\" />\n"
" <action name=\"status\" timeout=\"20\" />\n"
" <action name=\"monitor\" timeout=\"20\" interval=\"3600\"/>\n"
" <action name=\"meta-data\" timeout=\"15\" />\n"
" </actions>\n"
" <special tag=\"heartbeat\">\n"
" <version>2.0</version>\n" " </special>\n" "</resource-agent>\n";
#endif
bool stonith_dispatch(stonith_t * st);
int stonith_dispatch_internal(const char *buffer, ssize_t length, gpointer userdata);
void stonith_perform_callback(stonith_t * stonith, xmlNode * msg, int call_id, int rc);
xmlNode *stonith_create_op(int call_id, const char *token, const char *op, xmlNode * data,
int call_options);
int stonith_send_command(stonith_t * stonith, const char *op, xmlNode * data,
xmlNode ** output_data, int call_options, int timeout);
static void stonith_connection_destroy(gpointer user_data);
static void stonith_send_notification(gpointer data, gpointer user_data);
static int internal_stonith_action_execute(stonith_action_t * action);
static void log_action(stonith_action_t *action, pid_t pid);
static void
log_action(stonith_action_t *action, pid_t pid)
{
if (action->output) {
/* Logging the whole string confuses syslog when the string is xml */
char *prefix = crm_strdup_printf("%s[%d] stdout:", action->agent, pid);
crm_log_output(LOG_TRACE, prefix, action->output);
free(prefix);
}
if (action->error) {
/* Logging the whole string confuses syslog when the string is xml */
char *prefix = crm_strdup_printf("%s[%d] stderr:", action->agent, pid);
crm_log_output(LOG_WARNING, prefix, action->error);
free(prefix);
}
}
static void
stonith_connection_destroy(gpointer user_data)
{
stonith_t *stonith = user_data;
stonith_private_t *native = NULL;
struct notify_blob_s blob;
crm_trace("Sending destroyed notification");
blob.stonith = stonith;
blob.xml = create_xml_node(NULL, "notify");
native = stonith->private;
native->ipc = NULL;
native->source = NULL;
stonith->state = stonith_disconnected;
crm_xml_add(blob.xml, F_TYPE, T_STONITH_NOTIFY);
crm_xml_add(blob.xml, F_SUBTYPE, T_STONITH_NOTIFY_DISCONNECT);
g_list_foreach(native->notify_list, stonith_send_notification, &blob);
free_xml(blob.xml);
}
xmlNode *
create_device_registration_xml(const char *id, const char *namespace, const char *agent,
stonith_key_value_t * params, const char *rsc_provides)
{
xmlNode *data = create_xml_node(NULL, F_STONITH_DEVICE);
xmlNode *args = create_xml_node(data, XML_TAG_ATTRS);
#if HAVE_STONITH_STONITH_H
namespace = get_stonith_provider(agent, namespace);
if (safe_str_eq(namespace, "heartbeat")) {
hash2field((gpointer) "plugin", (gpointer) agent, args);
agent = "fence_legacy";
}
#endif
crm_xml_add(data, XML_ATTR_ID, id);
crm_xml_add(data, F_STONITH_ORIGIN, __FUNCTION__);
crm_xml_add(data, "agent", agent);
crm_xml_add(data, "namespace", namespace);
if (rsc_provides) {
crm_xml_add(data, "rsc_provides", rsc_provides);
}
for (; params; params = params->next) {
hash2field((gpointer) params->key, (gpointer) params->value, args);
}
return data;
}
static int
stonith_api_register_device(stonith_t * st, int call_options,
const char *id, const char *namespace, const char *agent,
stonith_key_value_t * params)
{
int rc = 0;
xmlNode *data = NULL;
data = create_device_registration_xml(id, namespace, agent, params, NULL);
rc = stonith_send_command(st, STONITH_OP_DEVICE_ADD, data, NULL, call_options, 0);
free_xml(data);
return rc;
}
static int
stonith_api_remove_device(stonith_t * st, int call_options, const char *name)
{
int rc = 0;
xmlNode *data = NULL;
data = create_xml_node(NULL, F_STONITH_DEVICE);
crm_xml_add(data, F_STONITH_ORIGIN, __FUNCTION__);
crm_xml_add(data, XML_ATTR_ID, name);
rc = stonith_send_command(st, STONITH_OP_DEVICE_DEL, data, NULL, call_options, 0);
free_xml(data);
return rc;
}
static int
stonith_api_remove_level_full(stonith_t *st, int options,
const char *node, const char *pattern,
const char *attr, const char *value, int level)
{
int rc = 0;
xmlNode *data = NULL;
CRM_CHECK(node || pattern || (attr && value), return -EINVAL);
data = create_xml_node(NULL, XML_TAG_FENCING_LEVEL);
crm_xml_add(data, F_STONITH_ORIGIN, __FUNCTION__);
if (node) {
crm_xml_add(data, XML_ATTR_STONITH_TARGET, node);
} else if (pattern) {
crm_xml_add(data, XML_ATTR_STONITH_TARGET_PATTERN, pattern);
} else {
crm_xml_add(data, XML_ATTR_STONITH_TARGET_ATTRIBUTE, attr);
crm_xml_add(data, XML_ATTR_STONITH_TARGET_VALUE, value);
}
crm_xml_add_int(data, XML_ATTR_STONITH_INDEX, level);
rc = stonith_send_command(st, STONITH_OP_LEVEL_DEL, data, NULL, options, 0);
free_xml(data);
return rc;
}
static int
stonith_api_remove_level(stonith_t * st, int options, const char *node, int level)
{
return stonith_api_remove_level_full(st, options, node,
NULL, NULL, NULL, level);
}
-/*
+/*!
* \internal
* \brief Create XML for stonithd topology level registration request
*
* \param[in] node If not NULL, target level by this node name
* \param[in] pattern If not NULL, target by node name using this regex
* \param[in] attr If not NULL, target by this node attribute
* \param[in] value If not NULL, target by this node attribute value
* \param[in] level Index number of level to register
* \param[in] device_list List of devices in level
*
* \return Newly allocated XML tree on success, NULL otherwise
*
* \note The caller should set only one of node, pattern or attr/value.
*/
xmlNode *
create_level_registration_xml(const char *node, const char *pattern,
const char *attr, const char *value,
int level, stonith_key_value_t *device_list)
{
int len = 0;
char *list = NULL;
xmlNode *data;
CRM_CHECK(node || pattern || (attr && value), return NULL);
data = create_xml_node(NULL, XML_TAG_FENCING_LEVEL);
CRM_CHECK(data, return NULL);
crm_xml_add(data, F_STONITH_ORIGIN, __FUNCTION__);
crm_xml_add_int(data, XML_ATTR_ID, level);
crm_xml_add_int(data, XML_ATTR_STONITH_INDEX, level);
if (node) {
crm_xml_add(data, XML_ATTR_STONITH_TARGET, node);
} else if (pattern) {
crm_xml_add(data, XML_ATTR_STONITH_TARGET_PATTERN, pattern);
} else {
crm_xml_add(data, XML_ATTR_STONITH_TARGET_ATTRIBUTE, attr);
crm_xml_add(data, XML_ATTR_STONITH_TARGET_VALUE, value);
}
for (; device_list; device_list = device_list->next) {
int adding = strlen(device_list->value);
if(list) {
adding++; /* +1 space */
}
crm_trace("Adding %s (%dc) at offset %d", device_list->value, adding, len);
list = realloc_safe(list, len + adding + 1); /* +1 EOS */
CRM_CHECK(list != NULL, free_xml(data); return NULL);
sprintf(list + len, "%s%s", len?",":"", device_list->value);
len += adding;
}
crm_xml_add(data, XML_ATTR_STONITH_DEVICES, list);
free(list);
return data;
}
static int
stonith_api_register_level_full(stonith_t * st, int options, const char *node,
const char *pattern,
const char *attr, const char *value,
int level, stonith_key_value_t *device_list)
{
int rc = 0;
xmlNode *data = create_level_registration_xml(node, pattern, attr, value,
level, device_list);
CRM_CHECK(data != NULL, return -EINVAL);
rc = stonith_send_command(st, STONITH_OP_LEVEL_ADD, data, NULL, options, 0);
free_xml(data);
return rc;
}
static int
stonith_api_register_level(stonith_t * st, int options, const char *node, int level,
stonith_key_value_t * device_list)
{
return stonith_api_register_level_full(st, options, node, NULL, NULL, NULL,
level, device_list);
}
static void
append_arg(gpointer key, gpointer value, gpointer user_data)
{
int len = 3; /* =, \n, \0 */
int last = 0;
char **args = user_data;
CRM_CHECK(key != NULL, return);
CRM_CHECK(value != NULL, return);
if (strstr(key, "pcmk_")) {
return;
} else if (strstr(key, CRM_META)) {
return;
} else if (safe_str_eq(key, "crm_feature_set")) {
return;
}
len += strlen(key);
len += strlen(value);
if (*args != NULL) {
last = strlen(*args);
}
*args = realloc_safe(*args, last + len);
crm_trace("Appending: %s=%s", (char *)key, (char *)value);
sprintf((*args) + last, "%s=%s\n", (char *)key, (char *)value);
}
static void
append_const_arg(const char *key, const char *value, char **arg_list)
{
CRM_LOG_ASSERT(key && value);
if(key && value) {
char *glib_sucks_key = strdup(key);
char *glib_sucks_value = strdup(value);
append_arg(glib_sucks_key, glib_sucks_value, arg_list);
free(glib_sucks_value);
free(glib_sucks_key);
}
}
static void
append_host_specific_args(const char *victim, const char *map, GHashTable * params, char **arg_list)
{
char *name = NULL;
int last = 0, lpc = 0, max = 0;
if (map == NULL) {
/* The best default there is for now... */
crm_debug("Using default arg map: port=uname");
append_const_arg("port", victim, arg_list);
return;
}
max = strlen(map);
crm_debug("Processing arg map: %s", map);
for (; lpc < max + 1; lpc++) {
if (isalpha(map[lpc])) {
/* keep going */
} else if (map[lpc] == '=' || map[lpc] == ':') {
free(name);
name = calloc(1, 1 + lpc - last);
memcpy(name, map + last, lpc - last);
crm_debug("Got name: %s", name);
last = lpc + 1;
} else if (map[lpc] == 0 || map[lpc] == ',' || isspace(map[lpc])) {
char *param = NULL;
const char *value = NULL;
param = calloc(1, 1 + lpc - last);
memcpy(param, map + last, lpc - last);
last = lpc + 1;
crm_debug("Got key: %s", param);
if (name == NULL) {
crm_err("Misparsed '%s', found '%s' without a name", map, param);
free(param);
continue;
}
if (safe_str_eq(param, "uname")) {
value = victim;
} else {
char *key = crm_meta_name(param);
value = g_hash_table_lookup(params, key);
free(key);
}
if (value) {
crm_debug("Setting '%s'='%s' (%s) for %s", name, value, param, victim);
append_const_arg(name, value, arg_list);
} else {
crm_err("No node attribute '%s' for '%s'", name, victim);
}
free(name);
name = NULL;
free(param);
if (map[lpc] == 0) {
break;
}
} else if (isspace(map[lpc])) {
last = lpc;
}
}
free(name);
}
static char *
make_args(const char *agent, const char *action, const char *victim, uint32_t victim_nodeid, GHashTable * device_args,
GHashTable * port_map)
{
char buffer[512];
char *arg_list = NULL;
const char *value = NULL;
const char *_action = action;
CRM_CHECK(action != NULL, return NULL);
buffer[511] = 0;
snprintf(buffer, 511, "pcmk_%s_action", action);
if (device_args) {
value = g_hash_table_lookup(device_args, buffer);
}
if (value == NULL && device_args) {
/* Legacy support for early 1.1 releases - Remove for 1.4 */
snprintf(buffer, 511, "pcmk_%s_cmd", action);
value = g_hash_table_lookup(device_args, buffer);
}
if (value == NULL && device_args && safe_str_eq(action, "off")) {
/* Legacy support for late 1.1 releases - Remove for 1.4 */
value = g_hash_table_lookup(device_args, "pcmk_poweroff_action");
}
if (value) {
crm_info("Substituting action '%s' for requested operation '%s'", value, action);
action = value;
}
append_const_arg(STONITH_ATTR_ACTION_OP, action, &arg_list);
if (victim && device_args) {
const char *alias = victim;
const char *param = g_hash_table_lookup(device_args, STONITH_ATTR_HOSTARG);
if (port_map && g_hash_table_lookup(port_map, victim)) {
alias = g_hash_table_lookup(port_map, victim);
}
/* Always supply the node's name too:
* https://fedorahosted.org/cluster/wiki/FenceAgentAPI
*/
append_const_arg("nodename", victim, &arg_list);
if (victim_nodeid) {
char nodeid_str[33] = { 0, };
if (snprintf(nodeid_str, 33, "%u", (unsigned int)victim_nodeid)) {
crm_info("For stonith action (%s) for victim %s, adding nodeid (%d) to parameters",
action, victim, nodeid_str);
append_const_arg("nodeid", nodeid_str, &arg_list);
}
}
/* Check if we need to supply the victim in any other form */
if(safe_str_eq(agent, "fence_legacy")) {
value = agent;
} else if (param == NULL) {
const char *map = g_hash_table_lookup(device_args, STONITH_ATTR_ARGMAP);
if (map == NULL) {
param = "port";
value = g_hash_table_lookup(device_args, param);
} else {
/* Legacy handling */
append_host_specific_args(alias, map, device_args, &arg_list);
value = map; /* Nothing more to do */
}
} else if (safe_str_eq(param, "none")) {
value = param; /* Nothing more to do */
} else {
value = g_hash_table_lookup(device_args, param);
}
/* Don't overwrite explictly set values for $param */
if (value == NULL || safe_str_eq(value, "dynamic")) {
crm_debug("Performing %s action for node '%s' as '%s=%s'", action, victim, param,
alias);
append_const_arg(param, alias, &arg_list);
}
}
if (device_args) {
g_hash_table_foreach(device_args, append_arg, &arg_list);
}
if(device_args && g_hash_table_lookup(device_args, STONITH_ATTR_ACTION_OP)) {
if(safe_str_eq(_action,"list")
|| safe_str_eq(_action,"status")
|| safe_str_eq(_action,"monitor")
|| safe_str_eq(_action,"metadata")) {
/* Force use of the calculated command for support ops
* We don't want list or monitor ops initiating fencing, regardless of what the admin configured
*/
append_const_arg(STONITH_ATTR_ACTION_OP, action, &arg_list);
}
}
return arg_list;
}
static gboolean
st_child_term(gpointer data)
{
int rc = 0;
stonith_action_t *track = data;
crm_info("Child %d timed out, sending SIGTERM", track->pid);
track->timer_sigterm = 0;
track->last_timeout_signo = SIGTERM;
rc = kill(-track->pid, SIGTERM);
if (rc < 0) {
crm_perror(LOG_ERR, "Couldn't send SIGTERM to %d", track->pid);
}
return FALSE;
}
static gboolean
st_child_kill(gpointer data)
{
int rc = 0;
stonith_action_t *track = data;
crm_info("Child %d timed out, sending SIGKILL", track->pid);
track->timer_sigkill = 0;
track->last_timeout_signo = SIGKILL;
rc = kill(-track->pid, SIGKILL);
if (rc < 0) {
crm_perror(LOG_ERR, "Couldn't send SIGKILL to %d", track->pid);
}
return FALSE;
}
static void
stonith_action_clear_tracking_data(stonith_action_t * action)
{
if (action->timer_sigterm > 0) {
g_source_remove(action->timer_sigterm);
action->timer_sigterm = 0;
}
if (action->timer_sigkill > 0) {
g_source_remove(action->timer_sigkill);
action->timer_sigkill = 0;
}
if (action->fd_stdout) {
close(action->fd_stdout);
action->fd_stdout = 0;
}
if (action->fd_stderr) {
close(action->fd_stderr);
action->fd_stderr = 0;
}
free(action->output);
action->output = NULL;
free(action->error);
action->error = NULL;
action->rc = 0;
action->pid = 0;
action->last_timeout_signo = 0;
}
static void
stonith_action_destroy(stonith_action_t * action)
{
stonith_action_clear_tracking_data(action);
free(action->agent);
free(action->args);
free(action->action);
free(action->victim);
free(action);
}
#define FAILURE_MAX_RETRIES 2
stonith_action_t *
stonith_action_create(const char *agent,
const char *_action,
const char *victim,
uint32_t victim_nodeid,
int timeout, GHashTable * device_args, GHashTable * port_map)
{
stonith_action_t *action;
action = calloc(1, sizeof(stonith_action_t));
crm_debug("Initiating action %s for agent %s (target=%s)", _action, agent, victim);
action->args = make_args(agent, _action, victim, victim_nodeid, device_args, port_map);
action->agent = strdup(agent);
action->action = strdup(_action);
if (victim) {
action->victim = strdup(victim);
}
action->timeout = action->remaining_timeout = timeout;
action->max_retries = FAILURE_MAX_RETRIES;
if (device_args) {
char buffer[512];
const char *value = NULL;
snprintf(buffer, 511, "pcmk_%s_retries", _action);
value = g_hash_table_lookup(device_args, buffer);
if (value) {
action->max_retries = atoi(value);
}
}
return action;
}
#define READ_MAX 500
static char *
read_output(int fd)
{
char buffer[READ_MAX];
char *output = NULL;
int len = 0;
int more = 0;
if (!fd) {
return NULL;
}
do {
errno = 0;
memset(&buffer, 0, READ_MAX);
more = read(fd, buffer, READ_MAX - 1);
if (more > 0) {
buffer[more] = 0; /* Make sure it's nul-terminated for logging
* 'more' is always less than our buffer size
*/
output = realloc_safe(output, len + more + 1);
snprintf(output + len, more + 1, "%s", buffer);
len += more;
}
} while (more == (READ_MAX - 1) || (more < 0 && errno == EINTR));
return output;
}
static gboolean
update_remaining_timeout(stonith_action_t * action)
{
int diff = time(NULL) - action->initial_start_time;
if (action->tries >= action->max_retries) {
crm_info("Attempted to execute agent %s (%s) the maximum number of times (%d) allowed",
action->agent, action->action, action->max_retries);
action->remaining_timeout = 0;
} else if ((action->rc != -ETIME) && diff < (action->timeout * 0.7)) {
/* only set remaining timeout period if there is 30%
* or greater of the original timeout period left */
action->remaining_timeout = action->timeout - diff;
} else {
action->remaining_timeout = 0;
}
return action->remaining_timeout ? TRUE : FALSE;
}
static void
stonith_action_async_done(mainloop_child_t * p, pid_t pid, int core, int signo, int exitcode)
{
stonith_action_t *action = mainloop_child_userdata(p);
if (action->timer_sigterm > 0) {
g_source_remove(action->timer_sigterm);
action->timer_sigterm = 0;
}
if (action->timer_sigkill > 0) {
g_source_remove(action->timer_sigkill);
action->timer_sigkill = 0;
}
action->output = read_output(action->fd_stdout);
action->error = read_output(action->fd_stderr);
if (action->last_timeout_signo) {
action->rc = -ETIME;
crm_notice("Child process %d performing action '%s' timed out with signal %d",
pid, action->action, action->last_timeout_signo);
} else if (signo) {
action->rc = -ECONNABORTED;
crm_notice("Child process %d performing action '%s' timed out with signal %d",
pid, action->action, signo);
} else {
crm_debug("Child process %d performing action '%s' exited with rc %d",
pid, action->action, exitcode);
if (exitcode > 0) {
/* Try to provide a useful error code based on the fence agent's
* error output.
*/
if (action->error == NULL) {
exitcode = -ENODATA;
} else if (strstr(action->error, "imed out")) {
/* Some agents have their own internal timeouts */
exitcode = -ETIMEDOUT;
} else if (strstr(action->error, "Unrecognised action")) {
exitcode = -EOPNOTSUPP;
} else {
exitcode = -pcmk_err_generic;
}
}
action->rc = exitcode;
}
log_action(action, pid);
if (action->rc != pcmk_ok && update_remaining_timeout(action)) {
int rc = internal_stonith_action_execute(action);
if (rc == pcmk_ok) {
return;
}
}
if (action->done_cb) {
action->done_cb(pid, action->rc, action->output, action->userdata);
}
stonith_action_destroy(action);
}
static int
internal_stonith_action_execute(stonith_action_t * action)
{
int pid, status = 0, len, rc = -EPROTO;
int ret;
int total = 0;
int p_read_fd, p_write_fd; /* parent read/write file descriptors */
int c_read_fd, c_write_fd; /* child read/write file descriptors */
int c_stderr_fd, p_stderr_fd; /* parent/child side file descriptors for stderr */
int fd1[2];
int fd2[2];
int fd3[2];
int is_retry = 0;
/* clear any previous tracking data */
stonith_action_clear_tracking_data(action);
if (!action->tries) {
action->initial_start_time = time(NULL);
}
action->tries++;
if (action->tries > 1) {
crm_info("Attempt %d to execute %s (%s). remaining timeout is %d",
action->tries, action->agent, action->action, action->remaining_timeout);
is_retry = 1;
}
c_read_fd = c_write_fd = p_read_fd = p_write_fd = c_stderr_fd = p_stderr_fd = -1;
if (action->args == NULL || action->agent == NULL)
goto fail;
len = strlen(action->args);
if (pipe(fd1))
goto fail;
p_read_fd = fd1[0];
c_write_fd = fd1[1];
if (pipe(fd2))
goto fail;
c_read_fd = fd2[0];
p_write_fd = fd2[1];
if (pipe(fd3))
goto fail;
p_stderr_fd = fd3[0];
c_stderr_fd = fd3[1];
crm_debug("forking");
pid = fork();
if (pid < 0) {
rc = -ECHILD;
goto fail;
}
if (!pid) {
/* child */
setpgid(0, 0);
close(1);
/* coverity[leaked_handle] False positive */
if (dup(c_write_fd) < 0)
goto fail;
close(2);
/* coverity[leaked_handle] False positive */
if (dup(c_stderr_fd) < 0)
goto fail;
close(0);
/* coverity[leaked_handle] False positive */
if (dup(c_read_fd) < 0)
goto fail;
/* keep c_stderr_fd open so parent can report all errors. */
/* keep c_write_fd open so hostlist can be sent to parent. */
close(c_read_fd);
close(p_read_fd);
close(p_write_fd);
close(p_stderr_fd);
/* keep retries from executing out of control */
if (is_retry) {
sleep(1);
}
execlp(action->agent, action->agent, NULL);
exit(EXIT_FAILURE);
}
/* parent */
action->pid = pid;
ret = fcntl(p_read_fd, F_SETFL, fcntl(p_read_fd, F_GETFL, 0) | O_NONBLOCK);
if (ret < 0) {
crm_perror(LOG_NOTICE, "Could not change the output of %s to be non-blocking",
action->agent);
}
ret = fcntl(p_stderr_fd, F_SETFL, fcntl(p_stderr_fd, F_GETFL, 0) | O_NONBLOCK);
if (ret < 0) {
crm_perror(LOG_NOTICE, "Could not change the stderr of %s to be non-blocking",
action->agent);
}
do {
crm_debug("sending args");
ret = write(p_write_fd, action->args + total, len - total);
if (ret > 0) {
total += ret;
}
} while (errno == EINTR && total < len);
if (total != len) {
crm_perror(LOG_ERR, "Sent %d not %d bytes", total, len);
if (ret >= 0) {
rc = -ECOMM;
}
goto fail;
}
close(p_write_fd); p_write_fd = -1;
/* async */
if (action->async) {
action->fd_stdout = p_read_fd;
action->fd_stderr = p_stderr_fd;
mainloop_child_add(pid, 0/* Move the timeout here? */, action->action, action, stonith_action_async_done);
crm_trace("Op: %s on %s, pid: %d, timeout: %ds", action->action, action->agent, pid,
action->remaining_timeout);
action->last_timeout_signo = 0;
if (action->remaining_timeout) {
action->timer_sigterm =
g_timeout_add(1000 * action->remaining_timeout, st_child_term, action);
action->timer_sigkill =
g_timeout_add(1000 * (action->remaining_timeout + 5), st_child_kill, action);
} else {
crm_err("No timeout set for stonith operation %s with device %s",
action->action, action->agent);
}
close(c_write_fd);
close(c_read_fd);
close(c_stderr_fd);
return 0;
} else {
/* sync */
int timeout = action->remaining_timeout + 1;
pid_t p = 0;
while (action->remaining_timeout < 0 || timeout > 0) {
p = waitpid(pid, &status, WNOHANG);
if (p > 0) {
break;
}
sleep(1);
timeout--;
}
if (timeout == 0) {
int killrc = kill(-pid, SIGKILL);
if (killrc && errno != ESRCH) {
crm_err("kill(%d, KILL) failed: %s (%d)", pid, pcmk_strerror(errno), errno);
}
/*
* From sigprocmask(2):
* It is not possible to block SIGKILL or SIGSTOP. Attempts to do so are silently ignored.
*
* This makes it safe to skip WNOHANG here
*/
p = waitpid(pid, &status, 0);
}
if (p <= 0) {
crm_perror(LOG_ERR, "waitpid(%d)", pid);
} else if (p != pid) {
crm_err("Waited for %d, got %d", pid, p);
}
action->output = read_output(p_read_fd);
action->error = read_output(p_stderr_fd);
action->rc = -ECONNABORTED;
log_action(action, pid);
rc = action->rc;
if (timeout == 0) {
action->rc = -ETIME;
} else if (WIFEXITED(status)) {
crm_debug("result = %d", WEXITSTATUS(status));
action->rc = -WEXITSTATUS(status);
rc = 0;
} else if (WIFSIGNALED(status)) {
crm_err("call %s for %s exited due to signal %d", action->action, action->agent,
WTERMSIG(status));
} else {
crm_err("call %s for %s returned unexpected status %#x",
action->action, action->agent, status);
}
}
fail:
if (p_read_fd >= 0) {
close(p_read_fd);
}
if (p_write_fd >= 0) {
close(p_write_fd);
}
if (p_stderr_fd >= 0) {
close(p_stderr_fd);
}
if (c_read_fd >= 0) {
close(c_read_fd);
}
if (c_write_fd >= 0) {
close(c_write_fd);
}
if (c_stderr_fd >= 0) {
close(c_stderr_fd);
}
return rc;
}
GPid
stonith_action_execute_async(stonith_action_t * action,
void *userdata,
void (*done) (GPid pid, int rc, const char *output,
gpointer user_data))
{
int rc = 0;
if (!action) {
return -1;
}
action->userdata = userdata;
action->done_cb = done;
action->async = 1;
rc = internal_stonith_action_execute(action);
return rc < 0 ? rc : action->pid;
}
int
stonith_action_execute(stonith_action_t * action, int *agent_result, char **output)
{
int rc = 0;
if (!action) {
return -1;
}
do {
rc = internal_stonith_action_execute(action);
if (rc == pcmk_ok) {
/* success! */
break;
}
/* keep retrying while we have time left */
} while (update_remaining_timeout(action));
if (rc) {
/* error */
return rc;
}
if (agent_result) {
*agent_result = action->rc;
}
if (output) {
*output = action->output;
action->output = NULL; /* handed it off, do not free */
}
stonith_action_destroy(action);
return rc;
}
static int
stonith_api_device_list(stonith_t * stonith, int call_options, const char *namespace,
stonith_key_value_t ** devices, int timeout)
{
int count = 0;
if (devices == NULL) {
crm_err("Parameter error: stonith_api_device_list");
return -EFAULT;
}
/* Include Heartbeat agents */
if (namespace == NULL || safe_str_eq("heartbeat", namespace)) {
#if HAVE_STONITH_STONITH_H
static gboolean need_init = TRUE;
char **entry = NULL;
char **type_list = NULL;
static char **(*type_list_fn) (void) = NULL;
static void (*type_free_fn) (char **) = NULL;
if (need_init) {
need_init = FALSE;
type_list_fn =
find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY, "stonith_types", FALSE);
type_free_fn =
find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY, "stonith_free_hostlist",
FALSE);
}
if (type_list_fn) {
type_list = (*type_list_fn) ();
}
for (entry = type_list; entry != NULL && *entry; ++entry) {
crm_trace("Added: %s", *entry);
*devices = stonith_key_value_add(*devices, NULL, *entry);
count++;
}
if (type_list && type_free_fn) {
(*type_free_fn) (type_list);
}
#else
if (namespace != NULL) {
return -EINVAL; /* Heartbeat agents not supported */
}
#endif
}
/* Include Red Hat agents, basically: ls -1 @sbin_dir@/fence_* */
if (namespace == NULL || safe_str_eq("redhat", namespace)) {
struct dirent **namelist;
int file_num = scandir(RH_STONITH_DIR, &namelist, 0, alphasort);
if (file_num > 0) {
struct stat prop;
char buffer[FILENAME_MAX + 1];
while (file_num--) {
if ('.' == namelist[file_num]->d_name[0]) {
free(namelist[file_num]);
continue;
} else if (0 != strncmp(RH_STONITH_PREFIX,
namelist[file_num]->d_name, strlen(RH_STONITH_PREFIX))) {
free(namelist[file_num]);
continue;
}
snprintf(buffer, FILENAME_MAX, "%s/%s", RH_STONITH_DIR, namelist[file_num]->d_name);
if (stat(buffer, &prop) == 0 && S_ISREG(prop.st_mode)) {
*devices = stonith_key_value_add(*devices, NULL, namelist[file_num]->d_name);
count++;
}
free(namelist[file_num]);
}
free(namelist);
}
}
return count;
}
#if HAVE_STONITH_STONITH_H
static inline char *
strdup_null(const char *val)
{
if (val) {
return strdup(val);
}
return NULL;
}
static void
stonith_plugin(int priority, const char *fmt, ...) __attribute__((__format__ (__printf__, 2, 3)));
static void
stonith_plugin(int priority, const char *format, ...)
{
int err = errno;
va_list ap;
int len = 0;
char *string = NULL;
va_start(ap, format);
len = vasprintf (&string, format, ap);
CRM_ASSERT(len > 0);
do_crm_log_alias(priority, __FILE__, __func__, __LINE__, "%s", string);
free(string);
errno = err;
}
#endif
static int
stonith_api_device_metadata(stonith_t * stonith, int call_options, const char *agent,
const char *namespace, char **output, int timeout)
{
int rc = 0;
char *buffer = NULL;
const char *provider = get_stonith_provider(agent, namespace);
crm_trace("looking up %s/%s metadata", agent, provider);
/* By having this in a library, we can access it from stonith_admin
* when neither lrmd or stonith-ng are running
* Important for the crm shell's validations...
*/
if (safe_str_eq(provider, "redhat")) {
stonith_action_t *action = stonith_action_create(agent, "metadata", NULL, 0, 5, NULL, NULL);
int exec_rc = stonith_action_execute(action, &rc, &buffer);
xmlNode *xml = NULL;
xmlNode *actions = NULL;
xmlXPathObject *xpathObj = NULL;
if (exec_rc < 0 || rc != 0 || buffer == NULL) {
crm_warn("Could not obtain metadata for %s", agent);
crm_debug("Query failed: %d %d: %s", exec_rc, rc, crm_str(buffer));
free(buffer); /* Just in case */
return -EINVAL;
}
xml = string2xml(buffer);
if(xml == NULL) {
crm_warn("Metadata for %s is invalid", agent);
free(buffer);
return -EINVAL;
}
xpathObj = xpath_search(xml, "//actions");
if (numXpathResults(xpathObj) > 0) {
actions = getXpathResult(xpathObj, 0);
}
freeXpathObject(xpathObj);
/* Now fudge the metadata so that the start/stop actions appear */
xpathObj = xpath_search(xml, "//action[@name='stop']");
if (numXpathResults(xpathObj) <= 0) {
xmlNode *tmp = NULL;
tmp = create_xml_node(actions, "action");
crm_xml_add(tmp, "name", "stop");
crm_xml_add(tmp, "timeout", "20s");
tmp = create_xml_node(actions, "action");
crm_xml_add(tmp, "name", "start");
crm_xml_add(tmp, "timeout", "20s");
}
freeXpathObject(xpathObj);
/* Now fudge the metadata so that the port isn't required in the configuration */
xpathObj = xpath_search(xml, "//parameter[@name='port']");
if (numXpathResults(xpathObj) > 0) {
/* We'll fill this in */
xmlNode *tmp = getXpathResult(xpathObj, 0);
crm_xml_add(tmp, "required", "0");
}
freeXpathObject(xpathObj);
free(buffer);
buffer = dump_xml_formatted_with_text(xml);
free_xml(xml);
if (!buffer) {
return -EINVAL;
}
} else {
#if !HAVE_STONITH_STONITH_H
return -EINVAL; /* Heartbeat agents not supported */
#else
int bufferlen = 0;
static const char *no_parameter_info = "<!-- no value -->";
Stonith *stonith_obj = NULL;
static gboolean need_init = TRUE;
static Stonith *(*st_new_fn) (const char *) = NULL;
static const char *(*st_info_fn) (Stonith *, int) = NULL;
static void (*st_del_fn) (Stonith *) = NULL;
static void (*st_log_fn) (Stonith *, PILLogFun) = NULL;
if (need_init) {
need_init = FALSE;
st_new_fn =
find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY, "stonith_new", FALSE);
st_del_fn =
find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY, "stonith_delete",
FALSE);
st_log_fn =
find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY, "stonith_set_log",
FALSE);
st_info_fn =
find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY, "stonith_get_info",
FALSE);
}
if (lha_agents_lib && st_new_fn && st_del_fn && st_info_fn && st_log_fn) {
char *xml_meta_longdesc = NULL;
char *xml_meta_shortdesc = NULL;
char *meta_param = NULL;
char *meta_longdesc = NULL;
char *meta_shortdesc = NULL;
stonith_obj = (*st_new_fn) (agent);
if (stonith_obj) {
(*st_log_fn) (stonith_obj, (PILLogFun) & stonith_plugin);
meta_longdesc = strdup_null((*st_info_fn) (stonith_obj, ST_DEVICEDESCR));
if (meta_longdesc == NULL) {
crm_warn("no long description in %s's metadata.", agent);
meta_longdesc = strdup(no_parameter_info);
}
meta_shortdesc = strdup_null((*st_info_fn) (stonith_obj, ST_DEVICEID));
if (meta_shortdesc == NULL) {
crm_warn("no short description in %s's metadata.", agent);
meta_shortdesc = strdup(no_parameter_info);
}
meta_param = strdup_null((*st_info_fn) (stonith_obj, ST_CONF_XML));
if (meta_param == NULL) {
crm_warn("no list of parameters in %s's metadata.", agent);
meta_param = strdup(no_parameter_info);
}
(*st_del_fn) (stonith_obj);
} else {
return -EINVAL; /* Heartbeat agents not supported */
}
xml_meta_longdesc =
(char *)xmlEncodeEntitiesReentrant(NULL, (const unsigned char *)meta_longdesc);
xml_meta_shortdesc =
(char *)xmlEncodeEntitiesReentrant(NULL, (const unsigned char *)meta_shortdesc);
bufferlen = strlen(META_TEMPLATE) + strlen(agent)
+ strlen(xml_meta_longdesc) + strlen(xml_meta_shortdesc)
+ strlen(meta_param) + 1;
buffer = calloc(1, bufferlen);
snprintf(buffer, bufferlen - 1, META_TEMPLATE,
agent, xml_meta_longdesc, xml_meta_shortdesc, meta_param);
xmlFree(xml_meta_longdesc);
xmlFree(xml_meta_shortdesc);
free(meta_shortdesc);
free(meta_longdesc);
free(meta_param);
}
#endif
}
if (output) {
*output = buffer;
} else {
free(buffer);
}
return rc;
}
static int
stonith_api_query(stonith_t * stonith, int call_options, const char *target,
stonith_key_value_t ** devices, int timeout)
{
int rc = 0, lpc = 0, max = 0;
xmlNode *data = NULL;
xmlNode *output = NULL;
xmlXPathObjectPtr xpathObj = NULL;
CRM_CHECK(devices != NULL, return -EINVAL);
data = create_xml_node(NULL, F_STONITH_DEVICE);
crm_xml_add(data, F_STONITH_ORIGIN, __FUNCTION__);
crm_xml_add(data, F_STONITH_TARGET, target);
crm_xml_add(data, F_STONITH_ACTION, "off");
rc = stonith_send_command(stonith, STONITH_OP_QUERY, data, &output, call_options, timeout);
if (rc < 0) {
return rc;
}
xpathObj = xpath_search(output, "//@agent");
if (xpathObj) {
max = numXpathResults(xpathObj);
for (lpc = 0; lpc < max; lpc++) {
xmlNode *match = getXpathResult(xpathObj, lpc);
CRM_LOG_ASSERT(match != NULL);
if(match != NULL) {
xmlChar *match_path = xmlGetNodePath(match);
crm_info("%s[%d] = %s", "//@agent", lpc, match_path);
free(match_path);
*devices = stonith_key_value_add(*devices, NULL, crm_element_value(match, XML_ATTR_ID));
}
}
freeXpathObject(xpathObj);
}
free_xml(output);
free_xml(data);
return max;
}
static int
stonith_api_call(stonith_t * stonith,
int call_options,
const char *id,
const char *action, const char *victim, int timeout, xmlNode ** output)
{
int rc = 0;
xmlNode *data = NULL;
data = create_xml_node(NULL, F_STONITH_DEVICE);
crm_xml_add(data, F_STONITH_ORIGIN, __FUNCTION__);
crm_xml_add(data, F_STONITH_DEVICE, id);
crm_xml_add(data, F_STONITH_ACTION, action);
crm_xml_add(data, F_STONITH_TARGET, victim);
rc = stonith_send_command(stonith, STONITH_OP_EXEC, data, output, call_options, timeout);
free_xml(data);
return rc;
}
static int
stonith_api_list(stonith_t * stonith, int call_options, const char *id, char **list_info,
int timeout)
{
int rc;
xmlNode *output = NULL;
rc = stonith_api_call(stonith, call_options, id, "list", NULL, timeout, &output);
if (output && list_info) {
const char *list_str;
list_str = crm_element_value(output, "st_output");
if (list_str) {
*list_info = strdup(list_str);
}
}
if (output) {
free_xml(output);
}
return rc;
}
static int
stonith_api_monitor(stonith_t * stonith, int call_options, const char *id, int timeout)
{
return stonith_api_call(stonith, call_options, id, "monitor", NULL, timeout, NULL);
}
static int
stonith_api_status(stonith_t * stonith, int call_options, const char *id, const char *port,
int timeout)
{
return stonith_api_call(stonith, call_options, id, "status", port, timeout, NULL);
}
static int
stonith_api_fence(stonith_t * stonith, int call_options, const char *node, const char *action,
int timeout, int tolerance)
{
int rc = 0;
xmlNode *data = NULL;
data = create_xml_node(NULL, __FUNCTION__);
crm_xml_add(data, F_STONITH_TARGET, node);
crm_xml_add(data, F_STONITH_ACTION, action);
crm_xml_add_int(data, F_STONITH_TIMEOUT, timeout);
crm_xml_add_int(data, F_STONITH_TOLERANCE, tolerance);
rc = stonith_send_command(stonith, STONITH_OP_FENCE, data, NULL, call_options, timeout);
free_xml(data);
return rc;
}
static int
stonith_api_confirm(stonith_t * stonith, int call_options, const char *target)
{
return stonith_api_fence(stonith, call_options | st_opt_manual_ack, target, "off", 0, 0);
}
static int
stonith_api_history(stonith_t * stonith, int call_options, const char *node,
stonith_history_t ** history, int timeout)
{
int rc = 0;
xmlNode *data = NULL;
xmlNode *output = NULL;
stonith_history_t *last = NULL;
*history = NULL;
if (node) {
data = create_xml_node(NULL, __FUNCTION__);
crm_xml_add(data, F_STONITH_TARGET, node);
}
rc = stonith_send_command(stonith, STONITH_OP_FENCE_HISTORY, data, &output,
call_options | st_opt_sync_call, timeout);
free_xml(data);
if (rc == 0) {
xmlNode *op = NULL;
xmlNode *reply = get_xpath_object("//" F_STONITH_HISTORY_LIST, output, LOG_ERR);
for (op = __xml_first_child(reply); op != NULL; op = __xml_next(op)) {
stonith_history_t *kvp;
kvp = calloc(1, sizeof(stonith_history_t));
kvp->target = crm_element_value_copy(op, F_STONITH_TARGET);
kvp->action = crm_element_value_copy(op, F_STONITH_ACTION);
kvp->origin = crm_element_value_copy(op, F_STONITH_ORIGIN);
kvp->delegate = crm_element_value_copy(op, F_STONITH_DELEGATE);
kvp->client = crm_element_value_copy(op, F_STONITH_CLIENTNAME);
crm_element_value_int(op, F_STONITH_DATE, &kvp->completed);
crm_element_value_int(op, F_STONITH_STATE, &kvp->state);
if (last) {
last->next = kvp;
} else {
*history = kvp;
}
last = kvp;
}
}
return rc;
}
gboolean
is_redhat_agent(const char *agent)
{
int rc = 0;
struct stat prop;
char buffer[FILENAME_MAX + 1];
snprintf(buffer, FILENAME_MAX, "%s/%s", RH_STONITH_DIR, agent);
rc = stat(buffer, &prop);
if (rc >= 0 && S_ISREG(prop.st_mode)) {
return TRUE;
}
return FALSE;
}
const char *
get_stonith_provider(const char *agent, const char *provider)
{
/* This function sucks */
if (is_redhat_agent(agent)) {
return "redhat";
#if HAVE_STONITH_STONITH_H
} else {
Stonith *stonith_obj = NULL;
static gboolean need_init = TRUE;
static Stonith *(*st_new_fn) (const char *) = NULL;
static void (*st_del_fn) (Stonith *) = NULL;
if (need_init) {
need_init = FALSE;
st_new_fn =
find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY, "stonith_new", FALSE);
st_del_fn =
find_library_function(&lha_agents_lib, LHA_STONITH_LIBRARY, "stonith_delete",
FALSE);
}
if (lha_agents_lib && st_new_fn && st_del_fn) {
stonith_obj = (*st_new_fn) (agent);
if (stonith_obj) {
(*st_del_fn) (stonith_obj);
return "heartbeat";
}
}
#endif
}
if (safe_str_eq(provider, "internal")) {
return provider;
} else {
crm_err("No such device: %s", agent);
return NULL;
}
}
static gint
stonithlib_GCompareFunc(gconstpointer a, gconstpointer b)
{
int rc = 0;
const stonith_notify_client_t *a_client = a;
const stonith_notify_client_t *b_client = b;
CRM_CHECK(a_client->event != NULL && b_client->event != NULL, return 0);
rc = strcmp(a_client->event, b_client->event);
if (rc == 0) {
if (a_client->notify == NULL || b_client->notify == NULL) {
return 0;
} else if (a_client->notify == b_client->notify) {
return 0;
} else if (((long)a_client->notify) < ((long)b_client->notify)) {
crm_err("callbacks for %s are not equal: %p vs. %p",
a_client->event, a_client->notify, b_client->notify);
return -1;
}
crm_err("callbacks for %s are not equal: %p vs. %p",
a_client->event, a_client->notify, b_client->notify);
return 1;
}
return rc;
}
xmlNode *
stonith_create_op(int call_id, const char *token, const char *op, xmlNode * data, int call_options)
{
xmlNode *op_msg = create_xml_node(NULL, "stonith_command");
CRM_CHECK(op_msg != NULL, return NULL);
CRM_CHECK(token != NULL, return NULL);
crm_xml_add(op_msg, F_XML_TAGNAME, "stonith_command");
crm_xml_add(op_msg, F_TYPE, T_STONITH_NG);
crm_xml_add(op_msg, F_STONITH_CALLBACK_TOKEN, token);
crm_xml_add(op_msg, F_STONITH_OPERATION, op);
crm_xml_add_int(op_msg, F_STONITH_CALLID, call_id);
crm_trace("Sending call options: %.8lx, %d", (long)call_options, call_options);
crm_xml_add_int(op_msg, F_STONITH_CALLOPTS, call_options);
if (data != NULL) {
add_message_xml(op_msg, F_STONITH_CALLDATA, data);
}
return op_msg;
}
static void
stonith_destroy_op_callback(gpointer data)
{
stonith_callback_client_t *blob = data;
if (blob->timer && blob->timer->ref > 0) {
g_source_remove(blob->timer->ref);
}
free(blob->timer);
free(blob);
}
static int
stonith_api_signoff(stonith_t * stonith)
{
stonith_private_t *native = stonith->private;
crm_debug("Signing out of the STONITH Service");
if (native->source != NULL) {
/* Attached to mainloop */
mainloop_del_ipc_client(native->source);
native->source = NULL;
native->ipc = NULL;
} else if (native->ipc) {
/* Not attached to mainloop */
crm_ipc_t *ipc = native->ipc;
native->ipc = NULL;
crm_ipc_close(ipc);
crm_ipc_destroy(ipc);
}
free(native->token); native->token = NULL;
stonith->state = stonith_disconnected;
return pcmk_ok;
}
static int
stonith_api_signon(stonith_t * stonith, const char *name, int *stonith_fd)
{
int rc = pcmk_ok;
stonith_private_t *native = stonith->private;
static struct ipc_client_callbacks st_callbacks = {
.dispatch = stonith_dispatch_internal,
.destroy = stonith_connection_destroy
};
crm_trace("Connecting command channel");
stonith->state = stonith_connected_command;
if (stonith_fd) {
/* No mainloop */
native->ipc = crm_ipc_new("stonith-ng", 0);
if (native->ipc && crm_ipc_connect(native->ipc)) {
*stonith_fd = crm_ipc_get_fd(native->ipc);
} else if (native->ipc) {
crm_perror(LOG_ERR, "Connection to STONITH manager failed");
rc = -ENOTCONN;
}
} else {
/* With mainloop */
native->source =
mainloop_add_ipc_client("stonith-ng", G_PRIORITY_MEDIUM, 0, stonith, &st_callbacks);
native->ipc = mainloop_get_ipc_client(native->source);
}
if (native->ipc == NULL) {
crm_debug("Could not connect to the Stonith API");
rc = -ENOTCONN;
}
if (rc == pcmk_ok) {
xmlNode *reply = NULL;
xmlNode *hello = create_xml_node(NULL, "stonith_command");
crm_xml_add(hello, F_TYPE, T_STONITH_NG);
crm_xml_add(hello, F_STONITH_OPERATION, CRM_OP_REGISTER);
crm_xml_add(hello, F_STONITH_CLIENTNAME, name);
rc = crm_ipc_send(native->ipc, hello, crm_ipc_client_response, -1, &reply);
if (rc < 0) {
crm_perror(LOG_DEBUG, "Couldn't complete registration with the fencing API: %d", rc);
rc = -ECOMM;
} else if (reply == NULL) {
crm_err("Did not receive registration reply");
rc = -EPROTO;
} else {
const char *msg_type = crm_element_value(reply, F_STONITH_OPERATION);
const char *tmp_ticket = crm_element_value(reply, F_STONITH_CLIENTID);
if (safe_str_neq(msg_type, CRM_OP_REGISTER)) {
crm_err("Invalid registration message: %s", msg_type);
crm_log_xml_err(reply, "Bad reply");
rc = -EPROTO;
} else if (tmp_ticket == NULL) {
crm_err("No registration token provided");
crm_log_xml_err(reply, "Bad reply");
rc = -EPROTO;
} else {
crm_trace("Obtained registration token: %s", tmp_ticket);
native->token = strdup(tmp_ticket);
rc = pcmk_ok;
}
}
free_xml(reply);
free_xml(hello);
}
if (rc == pcmk_ok) {
#if HAVE_MSGFROMIPC_TIMEOUT
stonith->call_timeout = MAX_IPC_DELAY;
#endif
crm_debug("Connection to STONITH successful");
return pcmk_ok;
}
crm_debug("Connection to STONITH failed: %s", pcmk_strerror(rc));
stonith->cmds->disconnect(stonith);
return rc;
}
static int
stonith_set_notification(stonith_t * stonith, const char *callback, int enabled)
{
int rc = pcmk_ok;
xmlNode *notify_msg = create_xml_node(NULL, __FUNCTION__);
stonith_private_t *native = stonith->private;
if (stonith->state != stonith_disconnected) {
crm_xml_add(notify_msg, F_STONITH_OPERATION, T_STONITH_NOTIFY);
if (enabled) {
crm_xml_add(notify_msg, F_STONITH_NOTIFY_ACTIVATE, callback);
} else {
crm_xml_add(notify_msg, F_STONITH_NOTIFY_DEACTIVATE, callback);
}
rc = crm_ipc_send(native->ipc, notify_msg, crm_ipc_client_response, -1, NULL);
if (rc < 0) {
crm_perror(LOG_DEBUG, "Couldn't register for fencing notifications: %d", rc);
rc = -ECOMM;
} else {
rc = pcmk_ok;
}
}
free_xml(notify_msg);
return rc;
}
static int
stonith_api_add_notification(stonith_t * stonith, const char *event,
void (*callback) (stonith_t * stonith, stonith_event_t * e))
{
GList *list_item = NULL;
stonith_notify_client_t *new_client = NULL;
stonith_private_t *private = NULL;
private = stonith->private;
crm_trace("Adding callback for %s events (%d)", event, g_list_length(private->notify_list));
new_client = calloc(1, sizeof(stonith_notify_client_t));
new_client->event = event;
new_client->notify = callback;
list_item = g_list_find_custom(private->notify_list, new_client, stonithlib_GCompareFunc);
if (list_item != NULL) {
crm_warn("Callback already present");
free(new_client);
return -ENOTUNIQ;
} else {
private->notify_list = g_list_append(private->notify_list, new_client);
stonith_set_notification(stonith, event, 1);
crm_trace("Callback added (%d)", g_list_length(private->notify_list));
}
return pcmk_ok;
}
static int
stonith_api_del_notification(stonith_t * stonith, const char *event)
{
GList *list_item = NULL;
stonith_notify_client_t *new_client = NULL;
stonith_private_t *private = NULL;
crm_debug("Removing callback for %s events", event);
private = stonith->private;
new_client = calloc(1, sizeof(stonith_notify_client_t));
new_client->event = event;
new_client->notify = NULL;
list_item = g_list_find_custom(private->notify_list, new_client, stonithlib_GCompareFunc);
stonith_set_notification(stonith, event, 0);
if (list_item != NULL) {
stonith_notify_client_t *list_client = list_item->data;
private->notify_list = g_list_remove(private->notify_list, list_client);
free(list_client);
crm_trace("Removed callback");
} else {
crm_trace("Callback not present");
}
free(new_client);
return pcmk_ok;
}
static gboolean
stonith_async_timeout_handler(gpointer data)
{
struct timer_rec_s *timer = data;
crm_err("Async call %d timed out after %dms", timer->call_id, timer->timeout);
stonith_perform_callback(timer->stonith, NULL, timer->call_id, -ETIME);
/* Always return TRUE, never remove the handler
* We do that in stonith_del_callback()
*/
return TRUE;
}
static void
set_callback_timeout(stonith_callback_client_t * callback, stonith_t * stonith, int call_id,
int timeout)
{
struct timer_rec_s *async_timer = callback->timer;
if (timeout <= 0) {
return;
}
if (!async_timer) {
async_timer = calloc(1, sizeof(struct timer_rec_s));
callback->timer = async_timer;
}
async_timer->stonith = stonith;
async_timer->call_id = call_id;
/* Allow a fair bit of grace to allow the server to tell us of a timeout
* This is only a fallback
*/
async_timer->timeout = (timeout + 60) * 1000;
if (async_timer->ref) {
g_source_remove(async_timer->ref);
}
async_timer->ref =
g_timeout_add(async_timer->timeout, stonith_async_timeout_handler, async_timer);
}
static void
update_callback_timeout(int call_id, int timeout, stonith_t * st)
{
stonith_callback_client_t *callback = NULL;
stonith_private_t *private = st->private;
callback = g_hash_table_lookup(private->stonith_op_callback_table, GINT_TO_POINTER(call_id));
if (!callback || !callback->allow_timeout_updates) {
return;
}
set_callback_timeout(callback, st, call_id, timeout);
}
static void
invoke_callback(stonith_t * st, int call_id, int rc, void *userdata,
void (*callback) (stonith_t * st, stonith_callback_data_t * data))
{
stonith_callback_data_t data = { 0, };
data.call_id = call_id;
data.rc = rc;
data.userdata = userdata;
callback(st, &data);
}
static int
stonith_api_add_callback(stonith_t * stonith, int call_id, int timeout, int options,
void *user_data, const char *callback_name,
void (*callback) (stonith_t * st, stonith_callback_data_t * data))
{
stonith_callback_client_t *blob = NULL;
stonith_private_t *private = NULL;
CRM_CHECK(stonith != NULL, return -EINVAL);
CRM_CHECK(stonith->private != NULL, return -EINVAL);
private = stonith->private;
if (call_id == 0) {
private->op_callback = callback;
} else if (call_id < 0) {
if (!(options & st_opt_report_only_success)) {
crm_trace("Call failed, calling %s: %s", callback_name, pcmk_strerror(call_id));
invoke_callback(stonith, call_id, call_id, user_data, callback);
} else {
crm_warn("STONITH call failed: %s", pcmk_strerror(call_id));
}
return FALSE;
}
blob = calloc(1, sizeof(stonith_callback_client_t));
blob->id = callback_name;
blob->only_success = (options & st_opt_report_only_success) ? TRUE : FALSE;
blob->user_data = user_data;
blob->callback = callback;
blob->allow_timeout_updates = (options & st_opt_timeout_updates) ? TRUE : FALSE;
if (timeout > 0) {
set_callback_timeout(blob, stonith, call_id, timeout);
}
g_hash_table_insert(private->stonith_op_callback_table, GINT_TO_POINTER(call_id), blob);
crm_trace("Added callback to %s for call %d", callback_name, call_id);
return TRUE;
}
static int
stonith_api_del_callback(stonith_t * stonith, int call_id, bool all_callbacks)
{
stonith_private_t *private = stonith->private;
if (all_callbacks) {
private->op_callback = NULL;
g_hash_table_destroy(private->stonith_op_callback_table);
private->stonith_op_callback_table = g_hash_table_new_full(g_direct_hash, g_direct_equal,
NULL,
stonith_destroy_op_callback);
} else if (call_id == 0) {
private->op_callback = NULL;
} else {
g_hash_table_remove(private->stonith_op_callback_table, GINT_TO_POINTER(call_id));
}
return pcmk_ok;
}
static void
stonith_dump_pending_op(gpointer key, gpointer value, gpointer user_data)
{
int call = GPOINTER_TO_INT(key);
stonith_callback_client_t *blob = value;
crm_debug("Call %d (%s): pending", call, crm_str(blob->id));
}
void
stonith_dump_pending_callbacks(stonith_t * stonith)
{
stonith_private_t *private = stonith->private;
if (private->stonith_op_callback_table == NULL) {
return;
}
return g_hash_table_foreach(private->stonith_op_callback_table, stonith_dump_pending_op, NULL);
}
void
stonith_perform_callback(stonith_t * stonith, xmlNode * msg, int call_id, int rc)
{
stonith_private_t *private = NULL;
stonith_callback_client_t *blob = NULL;
stonith_callback_client_t local_blob;
CRM_CHECK(stonith != NULL, return);
CRM_CHECK(stonith->private != NULL, return);
private = stonith->private;
local_blob.id = NULL;
local_blob.callback = NULL;
local_blob.user_data = NULL;
local_blob.only_success = FALSE;
if (msg != NULL) {
crm_element_value_int(msg, F_STONITH_RC, &rc);
crm_element_value_int(msg, F_STONITH_CALLID, &call_id);
}
CRM_CHECK(call_id > 0, crm_log_xml_err(msg, "Bad result"));
blob = g_hash_table_lookup(private->stonith_op_callback_table, GINT_TO_POINTER(call_id));
if (blob != NULL) {
local_blob = *blob;
blob = NULL;
stonith_api_del_callback(stonith, call_id, FALSE);
} else {
crm_trace("No callback found for call %d", call_id);
local_blob.callback = NULL;
}
if (local_blob.callback != NULL && (rc == pcmk_ok || local_blob.only_success == FALSE)) {
crm_trace("Invoking callback %s for call %d", crm_str(local_blob.id), call_id);
invoke_callback(stonith, call_id, rc, local_blob.user_data, local_blob.callback);
} else if (private->op_callback == NULL && rc != pcmk_ok) {
crm_warn("STONITH command failed: %s", pcmk_strerror(rc));
crm_log_xml_debug(msg, "Failed STONITH Update");
}
if (private->op_callback != NULL) {
crm_trace("Invoking global callback for call %d", call_id);
invoke_callback(stonith, call_id, rc, NULL, private->op_callback);
}
crm_trace("OP callback activated.");
}
/*
<notify t="st_notify" subt="st_device_register" st_op="st_device_register" st_rc="0" >
<st_calldata >
<stonith_command t="stonith-ng" st_async_id="088fb640-431a-48b9-b2fc-c4ff78d0a2d9" st_op="st_device_register" st_callid="2" st_callopt="4096" st_timeout="0" st_clientid="088fb640-431a-48b9-b2fc-c4ff78d0a2d9" st_clientname="stonith-test" >
<st_calldata >
<st_device_id id="test-id" origin="create_device_registration_xml" agent="fence_virsh" namespace="stonith-ng" >
<attributes ipaddr="localhost" pcmk-portmal="some-host=pcmk-1 pcmk-3=3,4" login="root" identity_file="/root/.ssh/id_dsa" />
</st_device_id>
</st_calldata>
</stonith_command>
</st_calldata>
</notify>
<notify t="st_notify" subt="st_notify_fence" st_op="st_notify_fence" st_rc="0" >
<st_calldata >
<st_notify_fence st_rc="0" st_target="some-host" st_op="st_fence" st_delegate="test-id" st_origin="61dd7759-e229-4be7-b1f8-ef49dd14d9f0" />
</st_calldata>
</notify>
*/
static stonith_event_t *
xml_to_event(xmlNode * msg)
{
stonith_event_t *event = calloc(1, sizeof(stonith_event_t));
const char *ntype = crm_element_value(msg, F_SUBTYPE);
char *data_addr = crm_strdup_printf("//%s", ntype);
xmlNode *data = get_xpath_object(data_addr, msg, LOG_DEBUG);
crm_log_xml_trace(msg, "stonith_notify");
crm_element_value_int(msg, F_STONITH_RC, &(event->result));
if (safe_str_eq(ntype, T_STONITH_NOTIFY_FENCE)) {
event->operation = crm_element_value_copy(msg, F_STONITH_OPERATION);
if (data) {
event->origin = crm_element_value_copy(data, F_STONITH_ORIGIN);
event->action = crm_element_value_copy(data, F_STONITH_ACTION);
event->target = crm_element_value_copy(data, F_STONITH_TARGET);
event->executioner = crm_element_value_copy(data, F_STONITH_DELEGATE);
event->id = crm_element_value_copy(data, F_STONITH_REMOTE_OP_ID);
event->client_origin = crm_element_value_copy(data, F_STONITH_CLIENTNAME);
event->device = crm_element_value_copy(data, F_STONITH_DEVICE);
} else {
crm_err("No data for %s event", ntype);
crm_log_xml_notice(msg, "BadEvent");
}
}
free(data_addr);
return event;
}
static void
event_free(stonith_event_t * event)
{
free(event->id);
free(event->type);
free(event->message);
free(event->operation);
free(event->origin);
free(event->action);
free(event->target);
free(event->executioner);
free(event->device);
free(event->client_origin);
free(event);
}
static void
stonith_send_notification(gpointer data, gpointer user_data)
{
struct notify_blob_s *blob = user_data;
stonith_notify_client_t *entry = data;
stonith_event_t *st_event = NULL;
const char *event = NULL;
if (blob->xml == NULL) {
crm_warn("Skipping callback - NULL message");
return;
}
event = crm_element_value(blob->xml, F_SUBTYPE);
if (entry == NULL) {
crm_warn("Skipping callback - NULL callback client");
return;
} else if (entry->notify == NULL) {
crm_warn("Skipping callback - NULL callback");
return;
} else if (safe_str_neq(entry->event, event)) {
crm_trace("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event);
return;
}
st_event = xml_to_event(blob->xml);
crm_trace("Invoking callback for %p/%s event...", entry, event);
entry->notify(blob->stonith, st_event);
crm_trace("Callback invoked...");
event_free(st_event);
}
int
stonith_send_command(stonith_t * stonith, const char *op, xmlNode * data, xmlNode ** output_data,
int call_options, int timeout)
{
int rc = 0;
int reply_id = -1;
enum crm_ipc_flags ipc_flags = crm_ipc_flags_none;
xmlNode *op_msg = NULL;
xmlNode *op_reply = NULL;
stonith_private_t *native = stonith->private;
if (stonith->state == stonith_disconnected) {
return -ENOTCONN;
}
if (output_data != NULL) {
*output_data = NULL;
}
if (op == NULL) {
crm_err("No operation specified");
return -EINVAL;
}
if (call_options & st_opt_sync_call) {
ipc_flags |= crm_ipc_client_response;
}
stonith->call_id++;
/* prevent call_id from being negative (or zero) and conflicting
* with the stonith_errors enum
* use 2 because we use it as (stonith->call_id - 1) below
*/
if (stonith->call_id < 1) {
stonith->call_id = 1;
}
CRM_CHECK(native->token != NULL,;
);
op_msg = stonith_create_op(stonith->call_id, native->token, op, data, call_options);
if (op_msg == NULL) {
return -EINVAL;
}
crm_xml_add_int(op_msg, F_STONITH_TIMEOUT, timeout);
crm_trace("Sending %s message to STONITH service, Timeout: %ds", op, timeout);
rc = crm_ipc_send(native->ipc, op_msg, ipc_flags, 1000 * (timeout + 60), &op_reply);
free_xml(op_msg);
if (rc < 0) {
crm_perror(LOG_ERR, "Couldn't perform %s operation (timeout=%ds): %d", op, timeout, rc);
rc = -ECOMM;
goto done;
}
crm_log_xml_trace(op_reply, "Reply");
if (!(call_options & st_opt_sync_call)) {
crm_trace("Async call %d, returning", stonith->call_id);
CRM_CHECK(stonith->call_id != 0, return -EPROTO);
free_xml(op_reply);
return stonith->call_id;
}
rc = pcmk_ok;
crm_element_value_int(op_reply, F_STONITH_CALLID, &reply_id);
if (reply_id == stonith->call_id) {
crm_trace("Synchronous reply %d received", reply_id);
if (crm_element_value_int(op_reply, F_STONITH_RC, &rc) != 0) {
rc = -ENOMSG;
}
if ((call_options & st_opt_discard_reply) || output_data == NULL) {
crm_trace("Discarding reply");
} else {
*output_data = op_reply;
op_reply = NULL; /* Prevent subsequent free */
}
} else if (reply_id <= 0) {
crm_err("Received bad reply: No id set");
crm_log_xml_err(op_reply, "Bad reply");
free_xml(op_reply);
rc = -ENOMSG;
} else {
crm_err("Received bad reply: %d (wanted %d)", reply_id, stonith->call_id);
crm_log_xml_err(op_reply, "Old reply");
free_xml(op_reply);
rc = -ENOMSG;
}
done:
if (crm_ipc_connected(native->ipc) == FALSE) {
crm_err("STONITH disconnected");
stonith->state = stonith_disconnected;
}
free_xml(op_reply);
return rc;
}
/* Not used with mainloop */
bool
stonith_dispatch(stonith_t * st)
{
gboolean stay_connected = TRUE;
stonith_private_t *private = NULL;
CRM_ASSERT(st != NULL);
private = st->private;
while (crm_ipc_ready(private->ipc)) {
if (crm_ipc_read(private->ipc) > 0) {
const char *msg = crm_ipc_buffer(private->ipc);
stonith_dispatch_internal(msg, strlen(msg), st);
}
if (crm_ipc_connected(private->ipc) == FALSE) {
crm_err("Connection closed");
stay_connected = FALSE;
}
}
return stay_connected;
}
int
stonith_dispatch_internal(const char *buffer, ssize_t length, gpointer userdata)
{
const char *type = NULL;
struct notify_blob_s blob;
stonith_t *st = userdata;
stonith_private_t *private = NULL;
CRM_ASSERT(st != NULL);
private = st->private;
blob.stonith = st;
blob.xml = string2xml(buffer);
if (blob.xml == NULL) {
crm_warn("Received a NULL msg from STONITH service: %s.", buffer);
return 0;
}
/* do callbacks */
type = crm_element_value(blob.xml, F_TYPE);
crm_trace("Activating %s callbacks...", type);
if (safe_str_eq(type, T_STONITH_NG)) {
stonith_perform_callback(st, blob.xml, 0, 0);
} else if (safe_str_eq(type, T_STONITH_NOTIFY)) {
g_list_foreach(private->notify_list, stonith_send_notification, &blob);
} else if (safe_str_eq(type, T_STONITH_TIMEOUT_VALUE)) {
int call_id = 0;
int timeout = 0;
crm_element_value_int(blob.xml, F_STONITH_TIMEOUT, &timeout);
crm_element_value_int(blob.xml, F_STONITH_CALLID, &call_id);
update_callback_timeout(call_id, timeout, st);
} else {
crm_err("Unknown message type: %s", type);
crm_log_xml_warn(blob.xml, "BadReply");
}
free_xml(blob.xml);
return 1;
}
static int
stonith_api_free(stonith_t * stonith)
{
int rc = pcmk_ok;
crm_trace("Destroying %p", stonith);
if (stonith->state != stonith_disconnected) {
crm_trace("Disconnecting %p first", stonith);
rc = stonith->cmds->disconnect(stonith);
}
if (stonith->state == stonith_disconnected) {
stonith_private_t *private = stonith->private;
crm_trace("Removing %d callbacks", g_hash_table_size(private->stonith_op_callback_table));
g_hash_table_destroy(private->stonith_op_callback_table);
crm_trace("Destroying %d notification clients", g_list_length(private->notify_list));
g_list_free_full(private->notify_list, free);
free(stonith->private);
free(stonith->cmds);
free(stonith);
} else {
crm_err("Not free'ing active connection: %s (%d)", pcmk_strerror(rc), rc);
}
return rc;
}
void
stonith_api_delete(stonith_t * stonith)
{
crm_trace("Destroying %p", stonith);
if(stonith) {
stonith->cmds->free(stonith);
}
}
stonith_t *
stonith_api_new(void)
{
stonith_t *new_stonith = NULL;
stonith_private_t *private = NULL;
new_stonith = calloc(1, sizeof(stonith_t));
private = calloc(1, sizeof(stonith_private_t));
new_stonith->private = private;
private->stonith_op_callback_table = g_hash_table_new_full(g_direct_hash, g_direct_equal,
NULL, stonith_destroy_op_callback);
private->notify_list = NULL;
new_stonith->call_id = 1;
new_stonith->state = stonith_disconnected;
new_stonith->cmds = calloc(1, sizeof(stonith_api_operations_t));
/* *INDENT-OFF* */
new_stonith->cmds->free = stonith_api_free;
new_stonith->cmds->connect = stonith_api_signon;
new_stonith->cmds->disconnect = stonith_api_signoff;
new_stonith->cmds->list = stonith_api_list;
new_stonith->cmds->monitor = stonith_api_monitor;
new_stonith->cmds->status = stonith_api_status;
new_stonith->cmds->fence = stonith_api_fence;
new_stonith->cmds->confirm = stonith_api_confirm;
new_stonith->cmds->history = stonith_api_history;
new_stonith->cmds->list_agents = stonith_api_device_list;
new_stonith->cmds->metadata = stonith_api_device_metadata;
new_stonith->cmds->query = stonith_api_query;
new_stonith->cmds->remove_device = stonith_api_remove_device;
new_stonith->cmds->register_device = stonith_api_register_device;
new_stonith->cmds->remove_level = stonith_api_remove_level;
new_stonith->cmds->remove_level_full = stonith_api_remove_level_full;
new_stonith->cmds->register_level = stonith_api_register_level;
new_stonith->cmds->register_level_full = stonith_api_register_level_full;
new_stonith->cmds->remove_callback = stonith_api_del_callback;
new_stonith->cmds->register_callback = stonith_api_add_callback;
new_stonith->cmds->remove_notification = stonith_api_del_notification;
new_stonith->cmds->register_notification = stonith_api_add_notification;
/* *INDENT-ON* */
return new_stonith;
}
stonith_key_value_t *
stonith_key_value_add(stonith_key_value_t * head, const char *key, const char *value)
{
stonith_key_value_t *p, *end;
p = calloc(1, sizeof(stonith_key_value_t));
if (key) {
p->key = strdup(key);
}
if (value) {
p->value = strdup(value);
}
end = head;
while (end && end->next) {
end = end->next;
}
if (end) {
end->next = p;
} else {
head = p;
}
return head;
}
void
stonith_key_value_freeall(stonith_key_value_t * head, int keys, int values)
{
stonith_key_value_t *p;
while (head) {
p = head->next;
if (keys) {
free(head->key);
}
if (values) {
free(head->value);
}
free(head);
head = p;
}
}
#define api_log_open() openlog("stonith-api", LOG_CONS | LOG_NDELAY | LOG_PID, LOG_DAEMON)
#define api_log(level, fmt, args...) syslog(level, "%s: "fmt, __FUNCTION__, args)
int
stonith_api_kick(uint32_t nodeid, const char *uname, int timeout, bool off)
{
char *name = NULL;
const char *action = "reboot";
int rc = -EPROTO;
stonith_t *st = NULL;
enum stonith_call_options opts = st_opt_sync_call | st_opt_allow_suicide;
api_log_open();
st = stonith_api_new();
if (st) {
rc = st->cmds->connect(st, "stonith-api", NULL);
if(rc != pcmk_ok) {
api_log(LOG_ERR, "Connection failed, could not kick (%s) node %u/%s : %s (%d)", action, nodeid, uname, pcmk_strerror(rc), rc);
}
}
if (uname != NULL) {
name = strdup(uname);
} else if (nodeid > 0) {
opts |= st_opt_cs_nodeid;
name = crm_itoa(nodeid);
}
if (off) {
action = "off";
}
if (rc == pcmk_ok) {
rc = st->cmds->fence(st, opts, name, action, timeout, 0);
if(rc != pcmk_ok) {
api_log(LOG_ERR, "Could not kick (%s) node %u/%s : %s (%d)", action, nodeid, uname, pcmk_strerror(rc), rc);
} else {
api_log(LOG_NOTICE, "Node %u/%s kicked: %s ", nodeid, uname, action);
}
}
if (st) {
st->cmds->disconnect(st);
stonith_api_delete(st);
}
free(name);
return rc;
}
time_t
stonith_api_time(uint32_t nodeid, const char *uname, bool in_progress)
{
int rc = 0;
char *name = NULL;
time_t when = 0;
stonith_t *st = NULL;
stonith_history_t *history, *hp = NULL;
enum stonith_call_options opts = st_opt_sync_call;
st = stonith_api_new();
if (st) {
rc = st->cmds->connect(st, "stonith-api", NULL);
if(rc != pcmk_ok) {
api_log(LOG_NOTICE, "Connection failed: %s (%d)", pcmk_strerror(rc), rc);
}
}
if (uname != NULL) {
name = strdup(uname);
} else if (nodeid > 0) {
opts |= st_opt_cs_nodeid;
name = crm_itoa(nodeid);
}
if (st && rc == pcmk_ok) {
int entries = 0;
int progress = 0;
int completed = 0;
rc = st->cmds->history(st, opts, name, &history, 120);
for (hp = history; hp; hp = hp->next) {
entries++;
if (in_progress) {
progress++;
if (hp->state != st_done && hp->state != st_failed) {
when = time(NULL);
}
} else if (hp->state == st_done) {
completed++;
if (hp->completed > when) {
when = hp->completed;
}
}
}
if(rc == pcmk_ok) {
api_log(LOG_INFO, "Found %d entries for %u/%s: %d in progress, %d completed", entries, nodeid, uname, progress, completed);
} else {
api_log(LOG_ERR, "Could not retrieve fence history for %u/%s: %s (%d)", nodeid, uname, pcmk_strerror(rc), rc);
}
}
if (st) {
st->cmds->disconnect(st);
stonith_api_delete(st);
}
if(when) {
api_log(LOG_INFO, "Node %u/%s last kicked at: %ld", nodeid, uname, (long int)when);
}
free(name);
return when;
}
#if HAVE_STONITH_STONITH_H
# include <pils/plugin.h>
const char *i_hate_pils(int rc);
const char *
i_hate_pils(int rc)
{
return PIL_strerror(rc);
}
#endif
diff --git a/lib/lrmd/lrmd_client.c b/lib/lrmd/lrmd_client.c
index 8aa19cd2a9..a7e1bc68e1 100644
--- a/lib/lrmd/lrmd_client.c
+++ b/lib/lrmd/lrmd_client.c
@@ -1,2248 +1,2248 @@
/*
* Copyright (c) 2012 David Vossel <davidvossel@gmail.com>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*
*/
#include <crm_internal.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <glib.h>
#include <dirent.h>
#include <crm/crm.h>
#include <crm/lrmd.h>
#include <crm/services.h>
#include <crm/common/mainloop.h>
#include <crm/common/ipcs.h>
#include <crm/msg_xml.h>
#include <crm/stonith-ng.h>
#ifdef HAVE_GNUTLS_GNUTLS_H
# undef KEYFILE
# include <gnutls/gnutls.h>
#endif
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <netdb.h>
#define MAX_TLS_RECV_WAIT 10000
CRM_TRACE_INIT_DATA(lrmd);
static int lrmd_api_disconnect(lrmd_t * lrmd);
static int lrmd_api_is_connected(lrmd_t * lrmd);
/* IPC proxy functions */
int lrmd_internal_proxy_send(lrmd_t * lrmd, xmlNode *msg);
static void lrmd_internal_proxy_dispatch(lrmd_t *lrmd, xmlNode *msg);
void lrmd_internal_set_proxy_callback(lrmd_t * lrmd, void *userdata, void (*callback)(lrmd_t *lrmd, void *userdata, xmlNode *msg));
#ifdef HAVE_GNUTLS_GNUTLS_H
# define LRMD_CLIENT_HANDSHAKE_TIMEOUT 5000 /* 5 seconds */
gnutls_psk_client_credentials_t psk_cred_s;
int lrmd_tls_set_key(gnutls_datum_t * key);
static void lrmd_tls_disconnect(lrmd_t * lrmd);
static int global_remote_msg_id = 0;
int lrmd_tls_send_msg(crm_remote_t * session, xmlNode * msg, uint32_t id, const char *msg_type);
static void lrmd_tls_connection_destroy(gpointer userdata);
#endif
typedef struct lrmd_private_s {
enum client_type type;
char *token;
mainloop_io_t *source;
/* IPC parameters */
crm_ipc_t *ipc;
crm_remote_t *remote;
/* Extra TLS parameters */
char *remote_nodename;
#ifdef HAVE_GNUTLS_GNUTLS_H
char *server;
int port;
gnutls_psk_client_credentials_t psk_cred_c;
/* while the async connection is occuring, this is the id
* of the connection timeout timer. */
int async_timer;
int sock;
/* since tls requires a round trip across the network for a
* request/reply, there are times where we just want to be able
* to send a request from the client and not wait around (or even care
* about) what the reply is. */
int expected_late_replies;
GList *pending_notify;
crm_trigger_t *process_notify;
#endif
lrmd_event_callback callback;
/* Internal IPC proxy msg passing for remote guests */
void (*proxy_callback)(lrmd_t *lrmd, void *userdata, xmlNode *msg);
void *proxy_callback_userdata;
char *peer_version;
} lrmd_private_t;
static lrmd_list_t *
lrmd_list_add(lrmd_list_t * head, const char *value)
{
lrmd_list_t *p, *end;
p = calloc(1, sizeof(lrmd_list_t));
p->val = strdup(value);
end = head;
while (end && end->next) {
end = end->next;
}
if (end) {
end->next = p;
} else {
head = p;
}
return head;
}
void
lrmd_list_freeall(lrmd_list_t * head)
{
lrmd_list_t *p;
while (head) {
char *val = (char *)head->val;
p = head->next;
free(val);
free(head);
head = p;
}
}
lrmd_key_value_t *
lrmd_key_value_add(lrmd_key_value_t * head, const char *key, const char *value)
{
lrmd_key_value_t *p, *end;
p = calloc(1, sizeof(lrmd_key_value_t));
p->key = strdup(key);
p->value = strdup(value);
end = head;
while (end && end->next) {
end = end->next;
}
if (end) {
end->next = p;
} else {
head = p;
}
return head;
}
void
lrmd_key_value_freeall(lrmd_key_value_t * head)
{
lrmd_key_value_t *p;
while (head) {
p = head->next;
free(head->key);
free(head->value);
free(head);
head = p;
}
}
static void
dup_attr(gpointer key, gpointer value, gpointer user_data)
{
g_hash_table_replace(user_data, strdup(key), strdup(value));
}
lrmd_event_data_t *
lrmd_copy_event(lrmd_event_data_t * event)
{
lrmd_event_data_t *copy = NULL;
copy = calloc(1, sizeof(lrmd_event_data_t));
/* This will get all the int values.
* we just have to be careful not to leave any
* dangling pointers to strings. */
memcpy(copy, event, sizeof(lrmd_event_data_t));
copy->rsc_id = event->rsc_id ? strdup(event->rsc_id) : NULL;
copy->op_type = event->op_type ? strdup(event->op_type) : NULL;
copy->user_data = event->user_data ? strdup(event->user_data) : NULL;
copy->output = event->output ? strdup(event->output) : NULL;
copy->exit_reason = event->exit_reason ? strdup(event->exit_reason) : NULL;
copy->remote_nodename = event->remote_nodename ? strdup(event->remote_nodename) : NULL;
if (event->params) {
copy->params = g_hash_table_new_full(crm_str_hash,
g_str_equal, g_hash_destroy_str, g_hash_destroy_str);
if (copy->params != NULL) {
g_hash_table_foreach(event->params, dup_attr, copy->params);
}
}
if (event->versioned_params) {
copy->versioned_params = copy_xml(event->versioned_params);
}
return copy;
}
void
lrmd_free_event(lrmd_event_data_t * event)
{
if (!event) {
return;
}
/* free gives me grief if i try to cast */
free((char *)event->rsc_id);
free((char *)event->op_type);
free((char *)event->user_data);
free((char *)event->output);
free((char *)event->exit_reason);
free((char *)event->remote_nodename);
if (event->params) {
g_hash_table_destroy(event->params);
}
if (event->versioned_params) {
free_xml(event->versioned_params);
}
free(event);
}
static int
lrmd_dispatch_internal(lrmd_t * lrmd, xmlNode * msg)
{
const char *type;
const char *proxy_session = crm_element_value(msg, F_LRMD_IPC_SESSION);
lrmd_private_t *native = lrmd->private;
lrmd_event_data_t event = { 0, };
if (proxy_session != NULL) {
/* this is proxy business */
lrmd_internal_proxy_dispatch(lrmd, msg);
return 1;
} else if (!native->callback) {
/* no callback set */
crm_trace("notify event received but client has not set callback");
return 1;
}
event.remote_nodename = native->remote_nodename;
type = crm_element_value(msg, F_LRMD_OPERATION);
crm_element_value_int(msg, F_LRMD_CALLID, &event.call_id);
event.rsc_id = crm_element_value(msg, F_LRMD_RSC_ID);
if (crm_str_eq(type, LRMD_OP_RSC_REG, TRUE)) {
event.type = lrmd_event_register;
} else if (crm_str_eq(type, LRMD_OP_RSC_UNREG, TRUE)) {
event.type = lrmd_event_unregister;
} else if (crm_str_eq(type, LRMD_OP_RSC_EXEC, TRUE)) {
crm_element_value_int(msg, F_LRMD_TIMEOUT, &event.timeout);
crm_element_value_int(msg, F_LRMD_RSC_INTERVAL, &event.interval);
crm_element_value_int(msg, F_LRMD_RSC_START_DELAY, &event.start_delay);
crm_element_value_int(msg, F_LRMD_EXEC_RC, (int *)&event.rc);
crm_element_value_int(msg, F_LRMD_OP_STATUS, &event.op_status);
crm_element_value_int(msg, F_LRMD_RSC_DELETED, &event.rsc_deleted);
crm_element_value_int(msg, F_LRMD_RSC_RUN_TIME, (int *)&event.t_run);
crm_element_value_int(msg, F_LRMD_RSC_RCCHANGE_TIME, (int *)&event.t_rcchange);
crm_element_value_int(msg, F_LRMD_RSC_EXEC_TIME, (int *)&event.exec_time);
crm_element_value_int(msg, F_LRMD_RSC_QUEUE_TIME, (int *)&event.queue_time);
event.op_type = crm_element_value(msg, F_LRMD_RSC_ACTION);
event.user_data = crm_element_value(msg, F_LRMD_RSC_USERDATA_STR);
event.output = crm_element_value(msg, F_LRMD_RSC_OUTPUT);
event.exit_reason = crm_element_value(msg, F_LRMD_RSC_EXIT_REASON);
event.type = lrmd_event_exec_complete;
event.params = xml2list(msg);
event.versioned_params = first_named_child(msg, XML_TAG_VER_ATTRS);
} else if (crm_str_eq(type, LRMD_OP_NEW_CLIENT, TRUE)) {
event.type = lrmd_event_new_client;
} else if (crm_str_eq(type, LRMD_OP_POKE, TRUE)) {
event.type = lrmd_event_poke;
} else {
return 1;
}
crm_trace("op %s notify event received", type);
native->callback(&event);
if (event.params) {
g_hash_table_destroy(event.params);
}
return 1;
}
static int
lrmd_ipc_dispatch(const char *buffer, ssize_t length, gpointer userdata)
{
lrmd_t *lrmd = userdata;
lrmd_private_t *native = lrmd->private;
xmlNode *msg;
int rc;
if (!native->callback) {
/* no callback set */
return 1;
}
msg = string2xml(buffer);
rc = lrmd_dispatch_internal(lrmd, msg);
free_xml(msg);
return rc;
}
#ifdef HAVE_GNUTLS_GNUTLS_H
static void
lrmd_free_xml(gpointer userdata)
{
free_xml((xmlNode *) userdata);
}
static int
lrmd_tls_connected(lrmd_t * lrmd)
{
lrmd_private_t *native = lrmd->private;
if (native->remote->tls_session) {
return TRUE;
}
return FALSE;
}
static int
lrmd_tls_dispatch(gpointer userdata)
{
lrmd_t *lrmd = userdata;
lrmd_private_t *native = lrmd->private;
xmlNode *xml = NULL;
int rc = 0;
int disconnected = 0;
if (lrmd_tls_connected(lrmd) == FALSE) {
crm_trace("tls dispatch triggered after disconnect");
return 0;
}
crm_trace("tls_dispatch triggered");
/* First check if there are any pending notifies to process that came
* while we were waiting for replies earlier. */
if (native->pending_notify) {
GList *iter = NULL;
crm_trace("Processing pending notifies");
for (iter = native->pending_notify; iter; iter = iter->next) {
lrmd_dispatch_internal(lrmd, iter->data);
}
g_list_free_full(native->pending_notify, lrmd_free_xml);
native->pending_notify = NULL;
}
/* Next read the current buffer and see if there are any messages to handle. */
rc = crm_remote_ready(native->remote, 0);
if (rc == 0) {
/* nothing to read, see if any full messages are already in buffer. */
xml = crm_remote_parse_buffer(native->remote);
} else if (rc < 0) {
disconnected = 1;
} else {
crm_remote_recv(native->remote, -1, &disconnected);
xml = crm_remote_parse_buffer(native->remote);
}
while (xml) {
const char *msg_type = crm_element_value(xml, F_LRMD_REMOTE_MSG_TYPE);
if (safe_str_eq(msg_type, "notify")) {
lrmd_dispatch_internal(lrmd, xml);
} else if (safe_str_eq(msg_type, "reply")) {
if (native->expected_late_replies > 0) {
native->expected_late_replies--;
} else {
int reply_id = 0;
crm_element_value_int(xml, F_LRMD_CALLID, &reply_id);
/* if this happens, we want to know about it */
crm_err("Got outdated reply %d", reply_id);
}
}
free_xml(xml);
xml = crm_remote_parse_buffer(native->remote);
}
if (disconnected) {
crm_info("Server disconnected while reading remote server msg.");
lrmd_tls_disconnect(lrmd);
return 0;
}
return 1;
}
#endif
/* Not used with mainloop */
int
lrmd_poll(lrmd_t * lrmd, int timeout)
{
lrmd_private_t *native = lrmd->private;
switch (native->type) {
case CRM_CLIENT_IPC:
return crm_ipc_ready(native->ipc);
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
if (native->pending_notify) {
return 1;
}
return crm_remote_ready(native->remote, 0);
#endif
default:
crm_err("Unsupported connection type: %d", native->type);
}
return 0;
}
/* Not used with mainloop */
bool
lrmd_dispatch(lrmd_t * lrmd)
{
lrmd_private_t *private = NULL;
CRM_ASSERT(lrmd != NULL);
private = lrmd->private;
switch (private->type) {
case CRM_CLIENT_IPC:
while (crm_ipc_ready(private->ipc)) {
if (crm_ipc_read(private->ipc) > 0) {
const char *msg = crm_ipc_buffer(private->ipc);
lrmd_ipc_dispatch(msg, strlen(msg), lrmd);
}
}
break;
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
lrmd_tls_dispatch(lrmd);
break;
#endif
default:
crm_err("Unsupported connection type: %d", private->type);
}
if (lrmd_api_is_connected(lrmd) == FALSE) {
crm_err("Connection closed");
return FALSE;
}
return TRUE;
}
static xmlNode *
lrmd_create_op(const char *token, const char *op, xmlNode * data, enum lrmd_call_options options)
{
xmlNode *op_msg = create_xml_node(NULL, "lrmd_command");
CRM_CHECK(op_msg != NULL, return NULL);
CRM_CHECK(token != NULL, return NULL);
crm_xml_add(op_msg, F_XML_TAGNAME, "lrmd_command");
crm_xml_add(op_msg, F_TYPE, T_LRMD);
crm_xml_add(op_msg, F_LRMD_CALLBACK_TOKEN, token);
crm_xml_add(op_msg, F_LRMD_OPERATION, op);
crm_trace("Sending call options: %.8lx, %d", (long)options, options);
crm_xml_add_int(op_msg, F_LRMD_CALLOPTS, options);
if (data != NULL) {
add_message_xml(op_msg, F_LRMD_CALLDATA, data);
}
return op_msg;
}
static void
lrmd_ipc_connection_destroy(gpointer userdata)
{
lrmd_t *lrmd = userdata;
lrmd_private_t *native = lrmd->private;
crm_info("IPC connection destroyed");
/* Prevent these from being cleaned up in lrmd_api_disconnect() */
native->ipc = NULL;
native->source = NULL;
if (native->callback) {
lrmd_event_data_t event = { 0, };
event.type = lrmd_event_disconnect;
event.remote_nodename = native->remote_nodename;
native->callback(&event);
}
}
#ifdef HAVE_GNUTLS_GNUTLS_H
static void
lrmd_tls_connection_destroy(gpointer userdata)
{
lrmd_t *lrmd = userdata;
lrmd_private_t *native = lrmd->private;
crm_info("TLS connection destroyed");
if (native->remote->tls_session) {
gnutls_bye(*native->remote->tls_session, GNUTLS_SHUT_RDWR);
gnutls_deinit(*native->remote->tls_session);
gnutls_free(native->remote->tls_session);
}
if (native->psk_cred_c) {
gnutls_psk_free_client_credentials(native->psk_cred_c);
}
if (native->sock) {
close(native->sock);
}
if (native->process_notify) {
mainloop_destroy_trigger(native->process_notify);
native->process_notify = NULL;
}
if (native->pending_notify) {
g_list_free_full(native->pending_notify, lrmd_free_xml);
native->pending_notify = NULL;
}
free(native->remote->buffer);
native->remote->buffer = NULL;
native->source = 0;
native->sock = 0;
native->psk_cred_c = NULL;
native->remote->tls_session = NULL;
native->sock = 0;
if (native->callback) {
lrmd_event_data_t event = { 0, };
event.remote_nodename = native->remote_nodename;
event.type = lrmd_event_disconnect;
native->callback(&event);
}
return;
}
int
lrmd_tls_send_msg(crm_remote_t * session, xmlNode * msg, uint32_t id, const char *msg_type)
{
int rc = -1;
crm_xml_add_int(msg, F_LRMD_REMOTE_MSG_ID, id);
crm_xml_add(msg, F_LRMD_REMOTE_MSG_TYPE, msg_type);
rc = crm_remote_send(session, msg);
if (rc < 0) {
crm_err("Failed to send remote lrmd tls msg, rc = %d", rc);
return rc;
}
return rc;
}
static xmlNode *
lrmd_tls_recv_reply(lrmd_t * lrmd, int total_timeout, int expected_reply_id, int *disconnected)
{
lrmd_private_t *native = lrmd->private;
xmlNode *xml = NULL;
time_t start = time(NULL);
const char *msg_type = NULL;
int reply_id = 0;
int remaining_timeout = 0;
/* A timeout of 0 here makes no sense. We have to wait a period of time
* for the response to come back. If -1 or 0, default to 10 seconds. */
if (total_timeout <= 0 || total_timeout > MAX_TLS_RECV_WAIT) {
total_timeout = MAX_TLS_RECV_WAIT;
}
while (!xml) {
xml = crm_remote_parse_buffer(native->remote);
if (!xml) {
/* read some more off the tls buffer if we still have time left. */
if (remaining_timeout) {
remaining_timeout = remaining_timeout - ((time(NULL) - start) * 1000);
} else {
remaining_timeout = total_timeout;
}
if (remaining_timeout <= 0) {
crm_err("Never received the expected reply during the timeout period, disconnecting.");
*disconnected = TRUE;
return NULL;
}
crm_remote_recv(native->remote, remaining_timeout, disconnected);
xml = crm_remote_parse_buffer(native->remote);
if (!xml) {
crm_err("Unable to receive expected reply, disconnecting.");
*disconnected = TRUE;
return NULL;
} else if (*disconnected) {
return NULL;
}
}
CRM_ASSERT(xml != NULL);
crm_element_value_int(xml, F_LRMD_REMOTE_MSG_ID, &reply_id);
msg_type = crm_element_value(xml, F_LRMD_REMOTE_MSG_TYPE);
if (!msg_type) {
crm_err("Empty msg type received while waiting for reply");
free_xml(xml);
xml = NULL;
} else if (safe_str_eq(msg_type, "notify")) {
/* got a notify while waiting for reply, trigger the notify to be processed later */
crm_info("queueing notify");
native->pending_notify = g_list_append(native->pending_notify, xml);
if (native->process_notify) {
crm_info("notify trigger set.");
mainloop_set_trigger(native->process_notify);
}
xml = NULL;
} else if (safe_str_neq(msg_type, "reply")) {
/* msg isn't a reply, make some noise */
crm_err("Expected a reply, got %s", msg_type);
free_xml(xml);
xml = NULL;
} else if (reply_id != expected_reply_id) {
if (native->expected_late_replies > 0) {
native->expected_late_replies--;
} else {
crm_err("Got outdated reply, expected id %d got id %d", expected_reply_id, reply_id);
}
free_xml(xml);
xml = NULL;
}
}
if (native->remote->buffer && native->process_notify) {
mainloop_set_trigger(native->process_notify);
}
return xml;
}
static int
lrmd_tls_send(lrmd_t * lrmd, xmlNode * msg)
{
int rc = 0;
lrmd_private_t *native = lrmd->private;
global_remote_msg_id++;
if (global_remote_msg_id <= 0) {
global_remote_msg_id = 1;
}
rc = lrmd_tls_send_msg(native->remote, msg, global_remote_msg_id, "request");
if (rc <= 0) {
crm_err("Remote lrmd send failed, disconnecting");
lrmd_tls_disconnect(lrmd);
return -ENOTCONN;
}
return pcmk_ok;
}
static int
lrmd_tls_send_recv(lrmd_t * lrmd, xmlNode * msg, int timeout, xmlNode ** reply)
{
int rc = 0;
int disconnected = 0;
xmlNode *xml = NULL;
if (lrmd_tls_connected(lrmd) == FALSE) {
return -1;
}
rc = lrmd_tls_send(lrmd, msg);
if (rc < 0) {
return rc;
}
xml = lrmd_tls_recv_reply(lrmd, timeout, global_remote_msg_id, &disconnected);
if (disconnected) {
crm_err("Remote lrmd server disconnected while waiting for reply with id %d. ",
global_remote_msg_id);
lrmd_tls_disconnect(lrmd);
rc = -ENOTCONN;
} else if (!xml) {
crm_err("Remote lrmd never received reply for request id %d. timeout: %dms ",
global_remote_msg_id, timeout);
rc = -ECOMM;
}
if (reply) {
*reply = xml;
} else {
free_xml(xml);
}
return rc;
}
#endif
static int
lrmd_send_xml(lrmd_t * lrmd, xmlNode * msg, int timeout, xmlNode ** reply)
{
int rc = -1;
lrmd_private_t *native = lrmd->private;
switch (native->type) {
case CRM_CLIENT_IPC:
rc = crm_ipc_send(native->ipc, msg, crm_ipc_client_response, timeout, reply);
break;
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
rc = lrmd_tls_send_recv(lrmd, msg, timeout, reply);
break;
#endif
default:
crm_err("Unsupported connection type: %d", native->type);
}
return rc;
}
static int
lrmd_send_xml_no_reply(lrmd_t * lrmd, xmlNode * msg)
{
int rc = -1;
lrmd_private_t *native = lrmd->private;
switch (native->type) {
case CRM_CLIENT_IPC:
rc = crm_ipc_send(native->ipc, msg, crm_ipc_flags_none, 0, NULL);
break;
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
rc = lrmd_tls_send(lrmd, msg);
if (rc == pcmk_ok) {
/* we don't want to wait around for the reply, but
* since the request/reply protocol needs to behave the same
* as libqb, a reply will eventually come later anyway. */
native->expected_late_replies++;
}
break;
#endif
default:
crm_err("Unsupported connection type: %d", native->type);
}
return rc;
}
static int
lrmd_api_is_connected(lrmd_t * lrmd)
{
lrmd_private_t *native = lrmd->private;
switch (native->type) {
case CRM_CLIENT_IPC:
return crm_ipc_connected(native->ipc);
break;
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
return lrmd_tls_connected(lrmd);
break;
#endif
default:
crm_err("Unsupported connection type: %d", native->type);
}
return 0;
}
static int
lrmd_send_command(lrmd_t * lrmd, const char *op, xmlNode * data, xmlNode ** output_data, int timeout, /* ms. defaults to 1000 if set to 0 */
enum lrmd_call_options options, gboolean expect_reply)
{ /* TODO we need to reduce usage of this boolean */
int rc = pcmk_ok;
int reply_id = -1;
lrmd_private_t *native = lrmd->private;
xmlNode *op_msg = NULL;
xmlNode *op_reply = NULL;
if (!lrmd_api_is_connected(lrmd)) {
return -ENOTCONN;
}
if (op == NULL) {
crm_err("No operation specified");
return -EINVAL;
}
CRM_CHECK(native->token != NULL,;
);
crm_trace("sending %s op to lrmd", op);
op_msg = lrmd_create_op(native->token, op, data, options);
if (op_msg == NULL) {
return -EINVAL;
}
crm_xml_add_int(op_msg, F_LRMD_TIMEOUT, timeout);
if (expect_reply) {
rc = lrmd_send_xml(lrmd, op_msg, timeout, &op_reply);
} else {
rc = lrmd_send_xml_no_reply(lrmd, op_msg);
goto done;
}
if (rc < 0) {
crm_perror(LOG_ERR, "Couldn't perform %s operation (timeout=%d): %d", op, timeout, rc);
rc = -ECOMM;
goto done;
} else if(op_reply == NULL) {
rc = -ENOMSG;
goto done;
}
rc = pcmk_ok;
crm_element_value_int(op_reply, F_LRMD_CALLID, &reply_id);
crm_trace("%s op reply received", op);
if (crm_element_value_int(op_reply, F_LRMD_RC, &rc) != 0) {
rc = -ENOMSG;
goto done;
}
crm_log_xml_trace(op_reply, "Reply");
if (output_data) {
*output_data = op_reply;
op_reply = NULL; /* Prevent subsequent free */
}
done:
if (lrmd_api_is_connected(lrmd) == FALSE) {
crm_err("LRMD disconnected");
}
free_xml(op_msg);
free_xml(op_reply);
return rc;
}
static int
lrmd_api_poke_connection(lrmd_t * lrmd)
{
int rc;
lrmd_private_t *native = lrmd->private;
xmlNode *data = create_xml_node(NULL, F_LRMD_RSC);
crm_xml_add(data, F_LRMD_ORIGIN, __FUNCTION__);
rc = lrmd_send_command(lrmd, LRMD_OP_POKE, data, NULL, 0, 0, native->type == CRM_CLIENT_IPC ? TRUE : FALSE);
free_xml(data);
return rc < 0 ? rc : pcmk_ok;
}
int
remote_proxy_check(lrmd_t * lrmd, GHashTable *hash)
{
int rc;
const char *value;
lrmd_private_t *native = lrmd->private;
xmlNode *data = create_xml_node(NULL, F_LRMD_OPERATION);
crm_xml_add(data, F_LRMD_ORIGIN, __FUNCTION__);
value = g_hash_table_lookup(hash, "stonith-watchdog-timeout");
crm_xml_add(data, F_LRMD_WATCHDOG, value);
rc = lrmd_send_command(lrmd, LRMD_OP_CHECK, data, NULL, 0, 0, native->type == CRM_CLIENT_IPC ? TRUE : FALSE);
free_xml(data);
return rc < 0 ? rc : pcmk_ok;
}
static int
lrmd_handshake(lrmd_t * lrmd, const char *name)
{
int rc = pcmk_ok;
lrmd_private_t *native = lrmd->private;
xmlNode *reply = NULL;
xmlNode *hello = create_xml_node(NULL, "lrmd_command");
crm_xml_add(hello, F_TYPE, T_LRMD);
crm_xml_add(hello, F_LRMD_OPERATION, CRM_OP_REGISTER);
crm_xml_add(hello, F_LRMD_CLIENTNAME, name);
crm_xml_add(hello, F_LRMD_PROTOCOL_VERSION, LRMD_PROTOCOL_VERSION);
/* advertise that we are a proxy provider */
if (native->proxy_callback) {
crm_xml_add(hello, F_LRMD_IS_IPC_PROVIDER, "true");
}
rc = lrmd_send_xml(lrmd, hello, -1, &reply);
if (rc < 0) {
crm_perror(LOG_DEBUG, "Couldn't complete registration with the lrmd API: %d", rc);
rc = -ECOMM;
} else if (reply == NULL) {
crm_err("Did not receive registration reply");
rc = -EPROTO;
} else {
const char *version = crm_element_value(reply, F_LRMD_PROTOCOL_VERSION);
const char *msg_type = crm_element_value(reply, F_LRMD_OPERATION);
const char *tmp_ticket = crm_element_value(reply, F_LRMD_CLIENTID);
crm_element_value_int(reply, F_LRMD_RC, &rc);
if (rc == -EPROTO) {
crm_err("LRMD protocol mismatch client version %s, server version %s",
LRMD_PROTOCOL_VERSION, version);
crm_log_xml_err(reply, "Protocol Error");
} else if (safe_str_neq(msg_type, CRM_OP_REGISTER)) {
crm_err("Invalid registration message: %s", msg_type);
crm_log_xml_err(reply, "Bad reply");
rc = -EPROTO;
} else if (tmp_ticket == NULL) {
crm_err("No registration token provided");
crm_log_xml_err(reply, "Bad reply");
rc = -EPROTO;
} else {
crm_trace("Obtained registration token: %s", tmp_ticket);
native->token = strdup(tmp_ticket);
native->peer_version = strdup(version?version:"1.0"); /* Included since 1.1 */
rc = pcmk_ok;
}
}
free_xml(reply);
free_xml(hello);
if (rc != pcmk_ok) {
lrmd_api_disconnect(lrmd);
}
return rc;
}
static int
lrmd_ipc_connect(lrmd_t * lrmd, int *fd)
{
int rc = pcmk_ok;
lrmd_private_t *native = lrmd->private;
static struct ipc_client_callbacks lrmd_callbacks = {
.dispatch = lrmd_ipc_dispatch,
.destroy = lrmd_ipc_connection_destroy
};
crm_info("Connecting to lrmd");
if (fd) {
/* No mainloop */
native->ipc = crm_ipc_new(CRM_SYSTEM_LRMD, 0);
if (native->ipc && crm_ipc_connect(native->ipc)) {
*fd = crm_ipc_get_fd(native->ipc);
} else if (native->ipc) {
crm_perror(LOG_ERR, "Connection to local resource manager failed");
rc = -ENOTCONN;
}
} else {
native->source = mainloop_add_ipc_client(CRM_SYSTEM_LRMD, G_PRIORITY_HIGH, 0, lrmd, &lrmd_callbacks);
native->ipc = mainloop_get_ipc_client(native->source);
}
if (native->ipc == NULL) {
crm_debug("Could not connect to the LRMD API");
rc = -ENOTCONN;
}
return rc;
}
#ifdef HAVE_GNUTLS_GNUTLS_H
static int
set_key(gnutls_datum_t * key, const char *location)
{
FILE *stream;
int read_len = 256;
int cur_len = 0;
int buf_len = read_len;
static char *key_cache = NULL;
static size_t key_cache_len = 0;
static time_t key_cache_updated;
if (location == NULL) {
return -1;
}
if (key_cache) {
time_t now = time(NULL);
if ((now - key_cache_updated) < 60) {
key->data = gnutls_malloc(key_cache_len + 1);
key->size = key_cache_len;
memcpy(key->data, key_cache, key_cache_len);
crm_debug("using cached LRMD key");
return 0;
} else {
key_cache_len = 0;
key_cache_updated = 0;
free(key_cache);
key_cache = NULL;
crm_debug("clearing lrmd key cache");
}
}
stream = fopen(location, "r");
if (!stream) {
return -1;
}
key->data = gnutls_malloc(read_len);
while (!feof(stream)) {
int next;
if (cur_len == buf_len) {
buf_len = cur_len + read_len;
key->data = gnutls_realloc(key->data, buf_len);
}
next = fgetc(stream);
if (next == EOF && feof(stream)) {
break;
}
key->data[cur_len] = next;
cur_len++;
}
fclose(stream);
key->size = cur_len;
if (!cur_len) {
gnutls_free(key->data);
key->data = 0;
return -1;
}
if (!key_cache) {
key_cache = calloc(1, key->size + 1);
memcpy(key_cache, key->data, key->size);
key_cache_len = key->size;
key_cache_updated = time(NULL);
}
return 0;
}
int
lrmd_tls_set_key(gnutls_datum_t * key)
{
int rc = 0;
const char *specific_location = getenv("PCMK_authkey_location");
if (set_key(key, specific_location) == 0) {
crm_debug("Using custom authkey location %s", specific_location);
return 0;
} else if (specific_location) {
crm_err("No valid lrmd remote key found at %s, trying default location", specific_location);
}
if (set_key(key, DEFAULT_REMOTE_KEY_LOCATION) != 0) {
rc = set_key(key, ALT_REMOTE_KEY_LOCATION);
}
if (rc) {
crm_err("No valid lrmd remote key found at %s", DEFAULT_REMOTE_KEY_LOCATION);
return -1;
}
return rc;
}
static void
lrmd_gnutls_global_init(void)
{
static int gnutls_init = 0;
if (!gnutls_init) {
crm_gnutls_global_init();
}
gnutls_init = 1;
}
#endif
static void
report_async_connection_result(lrmd_t * lrmd, int rc)
{
lrmd_private_t *native = lrmd->private;
if (native->callback) {
lrmd_event_data_t event = { 0, };
event.type = lrmd_event_connect;
event.remote_nodename = native->remote_nodename;
event.connection_rc = rc;
native->callback(&event);
}
}
#ifdef HAVE_GNUTLS_GNUTLS_H
static void
lrmd_tcp_connect_cb(void *userdata, int sock)
{
lrmd_t *lrmd = userdata;
lrmd_private_t *native = lrmd->private;
char name[256] = { 0, };
static struct mainloop_fd_callbacks lrmd_tls_callbacks = {
.dispatch = lrmd_tls_dispatch,
.destroy = lrmd_tls_connection_destroy,
};
int rc = sock;
gnutls_datum_t psk_key = { NULL, 0 };
native->async_timer = 0;
if (rc < 0) {
lrmd_tls_connection_destroy(lrmd);
crm_info("remote lrmd connect to %s at port %d failed", native->server, native->port);
report_async_connection_result(lrmd, rc);
return;
}
/* TODO continue with tls stuff now that tcp connect passed. make this async as well soon
* to avoid all blocking code in the client. */
native->sock = sock;
if (lrmd_tls_set_key(&psk_key) != 0) {
lrmd_tls_connection_destroy(lrmd);
return;
}
gnutls_psk_allocate_client_credentials(&native->psk_cred_c);
gnutls_psk_set_client_credentials(native->psk_cred_c, DEFAULT_REMOTE_USERNAME, &psk_key, GNUTLS_PSK_KEY_RAW);
gnutls_free(psk_key.data);
native->remote->tls_session = create_psk_tls_session(sock, GNUTLS_CLIENT, native->psk_cred_c);
if (crm_initiate_client_tls_handshake(native->remote, LRMD_CLIENT_HANDSHAKE_TIMEOUT) != 0) {
crm_warn("Client tls handshake failed for server %s:%d. Disconnecting", native->server,
native->port);
gnutls_deinit(*native->remote->tls_session);
gnutls_free(native->remote->tls_session);
native->remote->tls_session = NULL;
lrmd_tls_connection_destroy(lrmd);
report_async_connection_result(lrmd, -1);
return;
}
crm_info("Remote lrmd client TLS connection established with server %s:%d", native->server,
native->port);
snprintf(name, 128, "remote-lrmd-%s:%d", native->server, native->port);
native->process_notify = mainloop_add_trigger(G_PRIORITY_HIGH, lrmd_tls_dispatch, lrmd);
native->source =
mainloop_add_fd(name, G_PRIORITY_HIGH, native->sock, lrmd, &lrmd_tls_callbacks);
rc = lrmd_handshake(lrmd, name);
report_async_connection_result(lrmd, rc);
return;
}
static int
lrmd_tls_connect_async(lrmd_t * lrmd, int timeout /*ms */ )
{
int rc = -1;
int sock = 0;
int timer_id = 0;
lrmd_private_t *native = lrmd->private;
lrmd_gnutls_global_init();
sock = crm_remote_tcp_connect_async(native->server, native->port, timeout, &timer_id, lrmd,
lrmd_tcp_connect_cb);
if (sock != -1) {
native->sock = sock;
rc = 0;
native->async_timer = timer_id;
}
return rc;
}
static int
lrmd_tls_connect(lrmd_t * lrmd, int *fd)
{
static struct mainloop_fd_callbacks lrmd_tls_callbacks = {
.dispatch = lrmd_tls_dispatch,
.destroy = lrmd_tls_connection_destroy,
};
lrmd_private_t *native = lrmd->private;
int sock;
gnutls_datum_t psk_key = { NULL, 0 };
lrmd_gnutls_global_init();
sock = crm_remote_tcp_connect(native->server, native->port);
if (sock < 0) {
crm_warn("Could not establish remote lrmd connection to %s", native->server);
lrmd_tls_connection_destroy(lrmd);
return -ENOTCONN;
}
native->sock = sock;
if (lrmd_tls_set_key(&psk_key) != 0) {
lrmd_tls_connection_destroy(lrmd);
return -1;
}
gnutls_psk_allocate_client_credentials(&native->psk_cred_c);
gnutls_psk_set_client_credentials(native->psk_cred_c, DEFAULT_REMOTE_USERNAME, &psk_key, GNUTLS_PSK_KEY_RAW);
gnutls_free(psk_key.data);
native->remote->tls_session = create_psk_tls_session(sock, GNUTLS_CLIENT, native->psk_cred_c);
if (crm_initiate_client_tls_handshake(native->remote, LRMD_CLIENT_HANDSHAKE_TIMEOUT) != 0) {
crm_err("Session creation for %s:%d failed", native->server, native->port);
gnutls_deinit(*native->remote->tls_session);
gnutls_free(native->remote->tls_session);
native->remote->tls_session = NULL;
lrmd_tls_connection_destroy(lrmd);
return -1;
}
crm_info("Remote lrmd client TLS connection established with server %s:%d", native->server,
native->port);
if (fd) {
*fd = sock;
} else {
char name[256] = { 0, };
snprintf(name, 128, "remote-lrmd-%s:%d", native->server, native->port);
native->process_notify = mainloop_add_trigger(G_PRIORITY_HIGH, lrmd_tls_dispatch, lrmd);
native->source =
mainloop_add_fd(name, G_PRIORITY_HIGH, native->sock, lrmd, &lrmd_tls_callbacks);
}
return pcmk_ok;
}
#endif
static int
lrmd_api_connect(lrmd_t * lrmd, const char *name, int *fd)
{
int rc = -ENOTCONN;
lrmd_private_t *native = lrmd->private;
switch (native->type) {
case CRM_CLIENT_IPC:
rc = lrmd_ipc_connect(lrmd, fd);
break;
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
rc = lrmd_tls_connect(lrmd, fd);
break;
#endif
default:
crm_err("Unsupported connection type: %d", native->type);
}
if (rc == pcmk_ok) {
rc = lrmd_handshake(lrmd, name);
}
return rc;
}
static int
lrmd_api_connect_async(lrmd_t * lrmd, const char *name, int timeout)
{
int rc = 0;
lrmd_private_t *native = lrmd->private;
if (!native->callback) {
crm_err("Async connect not possible, no lrmd client callback set.");
return -1;
}
switch (native->type) {
case CRM_CLIENT_IPC:
/* fake async connection with ipc. it should be fast
* enough that we gain very little from async */
rc = lrmd_api_connect(lrmd, name, NULL);
if (!rc) {
report_async_connection_result(lrmd, rc);
}
break;
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
rc = lrmd_tls_connect_async(lrmd, timeout);
if (rc) {
/* connection failed, report rc now */
report_async_connection_result(lrmd, rc);
}
break;
#endif
default:
crm_err("Unsupported connection type: %d", native->type);
}
return rc;
}
static void
lrmd_ipc_disconnect(lrmd_t * lrmd)
{
lrmd_private_t *native = lrmd->private;
if (native->source != NULL) {
/* Attached to mainloop */
mainloop_del_ipc_client(native->source);
native->source = NULL;
native->ipc = NULL;
} else if (native->ipc) {
/* Not attached to mainloop */
crm_ipc_t *ipc = native->ipc;
native->ipc = NULL;
crm_ipc_close(ipc);
crm_ipc_destroy(ipc);
}
}
#ifdef HAVE_GNUTLS_GNUTLS_H
static void
lrmd_tls_disconnect(lrmd_t * lrmd)
{
lrmd_private_t *native = lrmd->private;
if (native->remote->tls_session) {
gnutls_bye(*native->remote->tls_session, GNUTLS_SHUT_RDWR);
gnutls_deinit(*native->remote->tls_session);
gnutls_free(native->remote->tls_session);
native->remote->tls_session = 0;
}
if (native->async_timer) {
g_source_remove(native->async_timer);
native->async_timer = 0;
}
if (native->source != NULL) {
/* Attached to mainloop */
mainloop_del_ipc_client(native->source);
native->source = NULL;
} else if (native->sock) {
close(native->sock);
native->sock = 0;
}
if (native->pending_notify) {
g_list_free_full(native->pending_notify, lrmd_free_xml);
native->pending_notify = NULL;
}
}
#endif
static int
lrmd_api_disconnect(lrmd_t * lrmd)
{
lrmd_private_t *native = lrmd->private;
crm_info("Disconnecting from %d lrmd service", native->type);
switch (native->type) {
case CRM_CLIENT_IPC:
lrmd_ipc_disconnect(lrmd);
break;
#ifdef HAVE_GNUTLS_GNUTLS_H
case CRM_CLIENT_TLS:
lrmd_tls_disconnect(lrmd);
break;
#endif
default:
crm_err("Unsupported connection type: %d", native->type);
}
free(native->token);
native->token = NULL;
free(native->peer_version);
native->peer_version = NULL;
return 0;
}
static int
lrmd_api_register_rsc(lrmd_t * lrmd,
const char *rsc_id,
const char *class,
const char *provider, const char *type, enum lrmd_call_options options)
{
int rc = pcmk_ok;
xmlNode *data = NULL;
if (!class || !type || !rsc_id) {
return -EINVAL;
}
if (safe_str_eq(class, "ocf") && !provider) {
return -EINVAL;
}
data = create_xml_node(NULL, F_LRMD_RSC);
crm_xml_add(data, F_LRMD_ORIGIN, __FUNCTION__);
crm_xml_add(data, F_LRMD_RSC_ID, rsc_id);
crm_xml_add(data, F_LRMD_CLASS, class);
crm_xml_add(data, F_LRMD_PROVIDER, provider);
crm_xml_add(data, F_LRMD_TYPE, type);
rc = lrmd_send_command(lrmd, LRMD_OP_RSC_REG, data, NULL, 0, options, TRUE);
free_xml(data);
return rc;
}
static int
lrmd_api_unregister_rsc(lrmd_t * lrmd, const char *rsc_id, enum lrmd_call_options options)
{
int rc = pcmk_ok;
xmlNode *data = create_xml_node(NULL, F_LRMD_RSC);
crm_xml_add(data, F_LRMD_ORIGIN, __FUNCTION__);
crm_xml_add(data, F_LRMD_RSC_ID, rsc_id);
rc = lrmd_send_command(lrmd, LRMD_OP_RSC_UNREG, data, NULL, 0, options, TRUE);
free_xml(data);
return rc;
}
lrmd_rsc_info_t *
lrmd_copy_rsc_info(lrmd_rsc_info_t * rsc_info)
{
lrmd_rsc_info_t *copy = NULL;
copy = calloc(1, sizeof(lrmd_rsc_info_t));
copy->id = strdup(rsc_info->id);
copy->type = strdup(rsc_info->type);
copy->class = strdup(rsc_info->class);
if (rsc_info->provider) {
copy->provider = strdup(rsc_info->provider);
}
return copy;
}
void
lrmd_free_rsc_info(lrmd_rsc_info_t * rsc_info)
{
if (!rsc_info) {
return;
}
free(rsc_info->id);
free(rsc_info->type);
free(rsc_info->class);
free(rsc_info->provider);
free(rsc_info);
}
static lrmd_rsc_info_t *
lrmd_api_get_rsc_info(lrmd_t * lrmd, const char *rsc_id, enum lrmd_call_options options)
{
lrmd_rsc_info_t *rsc_info = NULL;
xmlNode *data = create_xml_node(NULL, F_LRMD_RSC);
xmlNode *output = NULL;
const char *class = NULL;
const char *provider = NULL;
const char *type = NULL;
crm_xml_add(data, F_LRMD_ORIGIN, __FUNCTION__);
crm_xml_add(data, F_LRMD_RSC_ID, rsc_id);
lrmd_send_command(lrmd, LRMD_OP_RSC_INFO, data, &output, 0, options, TRUE);
free_xml(data);
if (!output) {
return NULL;
}
class = crm_element_value(output, F_LRMD_CLASS);
provider = crm_element_value(output, F_LRMD_PROVIDER);
type = crm_element_value(output, F_LRMD_TYPE);
if (!class || !type) {
free_xml(output);
return NULL;
} else if (safe_str_eq(class, "ocf") && !provider) {
free_xml(output);
return NULL;
}
rsc_info = calloc(1, sizeof(lrmd_rsc_info_t));
rsc_info->id = strdup(rsc_id);
rsc_info->class = strdup(class);
if (provider) {
rsc_info->provider = strdup(provider);
}
rsc_info->type = strdup(type);
free_xml(output);
return rsc_info;
}
static void
lrmd_api_set_callback(lrmd_t * lrmd, lrmd_event_callback callback)
{
lrmd_private_t *native = lrmd->private;
native->callback = callback;
}
void
lrmd_internal_set_proxy_callback(lrmd_t * lrmd, void *userdata, void (*callback)(lrmd_t *lrmd, void *userdata, xmlNode *msg))
{
lrmd_private_t *native = lrmd->private;
native->proxy_callback = callback;
native->proxy_callback_userdata = userdata;
}
void
lrmd_internal_proxy_dispatch(lrmd_t *lrmd, xmlNode *msg)
{
lrmd_private_t *native = lrmd->private;
if (native->proxy_callback) {
crm_log_xml_trace(msg, "PROXY_INBOUND");
native->proxy_callback(lrmd, native->proxy_callback_userdata, msg);
}
}
int
lrmd_internal_proxy_send(lrmd_t * lrmd, xmlNode *msg)
{
if (lrmd == NULL) {
return -ENOTCONN;
}
crm_xml_add(msg, F_LRMD_OPERATION, CRM_OP_IPC_FWD);
crm_log_xml_trace(msg, "PROXY_OUTBOUND");
return lrmd_send_xml_no_reply(lrmd, msg);
}
static int
stonith_get_metadata(const char *provider, const char *type, char **output)
{
int rc = pcmk_ok;
stonith_t *stonith_api = stonith_api_new();
if(stonith_api) {
stonith_api->cmds->metadata(stonith_api, st_opt_sync_call, type, provider, output, 0);
stonith_api->cmds->free(stonith_api);
}
if (*output == NULL) {
rc = -EIO;
}
return rc;
}
#define lsb_metadata_template \
"<?xml version='1.0'?>\n" \
"<!DOCTYPE resource-agent SYSTEM 'ra-api-1.dtd'>\n" \
"<resource-agent name='%s' version='0.1'>\n" \
" <version>1.0</version>\n" \
" <longdesc lang='en'>\n" \
" %s\n" \
" </longdesc>\n" \
" <shortdesc lang='en'>%s</shortdesc>\n" \
" <parameters>\n" \
" </parameters>\n" \
" <actions>\n" \
" <action name='meta-data' timeout='5' />\n" \
" <action name='start' timeout='15' />\n" \
" <action name='stop' timeout='15' />\n" \
" <action name='status' timeout='15' />\n" \
" <action name='restart' timeout='15' />\n" \
" <action name='force-reload' timeout='15' />\n" \
" <action name='monitor' timeout='15' interval='15' />\n" \
" </actions>\n" \
" <special tag='LSB'>\n" \
" <Provides>%s</Provides>\n" \
" <Required-Start>%s</Required-Start>\n" \
" <Required-Stop>%s</Required-Stop>\n" \
" <Should-Start>%s</Should-Start>\n" \
" <Should-Stop>%s</Should-Stop>\n" \
" <Default-Start>%s</Default-Start>\n" \
" <Default-Stop>%s</Default-Stop>\n" \
" </special>\n" \
"</resource-agent>\n"
#define LSB_INITSCRIPT_INFOBEGIN_TAG "### BEGIN INIT INFO"
#define LSB_INITSCRIPT_INFOEND_TAG "### END INIT INFO"
#define PROVIDES "# Provides:"
#define REQ_START "# Required-Start:"
#define REQ_STOP "# Required-Stop:"
#define SHLD_START "# Should-Start:"
#define SHLD_STOP "# Should-Stop:"
#define DFLT_START "# Default-Start:"
#define DFLT_STOP "# Default-Stop:"
#define SHORT_DSCR "# Short-Description:"
#define DESCRIPTION "# Description:"
#define lsb_meta_helper_free_value(m) \
do { \
if ((m) != NULL) { \
xmlFree(m); \
(m) = NULL; \
} \
} while(0)
-/*
+/*!
* \internal
* \brief Grab an LSB header value
*
* \param[in] line Line read from LSB init script
* \param[in/out] value If not set, will be set to XML-safe copy of value
* \param[in] prefix Set value if line starts with this pattern
*
* \return TRUE if value was set, FALSE otherwise
*/
static inline gboolean
lsb_meta_helper_get_value(const char *line, char **value, const char *prefix)
{
if (!*value && !strncasecmp(line, prefix, strlen(prefix))) {
*value = (char *)xmlEncodeEntitiesReentrant(NULL, BAD_CAST line+strlen(prefix));
return TRUE;
}
return FALSE;
}
static int
lsb_get_metadata(const char *type, char **output)
{
char ra_pathname[PATH_MAX] = { 0, };
FILE *fp;
char buffer[1024];
char *provides = NULL;
char *req_start = NULL;
char *req_stop = NULL;
char *shld_start = NULL;
char *shld_stop = NULL;
char *dflt_start = NULL;
char *dflt_stop = NULL;
char *s_dscrpt = NULL;
char *xml_l_dscrpt = NULL;
int offset = 0;
int max = 2048;
char description[max];
if(type[0] == '/') {
snprintf(ra_pathname, sizeof(ra_pathname), "%s", type);
} else {
snprintf(ra_pathname, sizeof(ra_pathname), "%s/%s", LSB_ROOT_DIR, type);
}
crm_trace("Looking into %s", ra_pathname);
if (!(fp = fopen(ra_pathname, "r"))) {
return -errno;
}
/* Enter into the lsb-compliant comment block */
while (fgets(buffer, sizeof(buffer), fp)) {
/* Now suppose each of the following eight arguments contain only one line */
if (lsb_meta_helper_get_value(buffer, &provides, PROVIDES)) {
continue;
}
if (lsb_meta_helper_get_value(buffer, &req_start, REQ_START)) {
continue;
}
if (lsb_meta_helper_get_value(buffer, &req_stop, REQ_STOP)) {
continue;
}
if (lsb_meta_helper_get_value(buffer, &shld_start, SHLD_START)) {
continue;
}
if (lsb_meta_helper_get_value(buffer, &shld_stop, SHLD_STOP)) {
continue;
}
if (lsb_meta_helper_get_value(buffer, &dflt_start, DFLT_START)) {
continue;
}
if (lsb_meta_helper_get_value(buffer, &dflt_stop, DFLT_STOP)) {
continue;
}
if (lsb_meta_helper_get_value(buffer, &s_dscrpt, SHORT_DSCR)) {
continue;
}
/* Long description may cross multiple lines */
if (offset == 0 && (0 == strncasecmp(buffer, DESCRIPTION, strlen(DESCRIPTION)))) {
/* Between # and keyword, more than one space, or a tab
* character, indicates the continuation line.
*
* Extracted from LSB init script standard
*/
while (fgets(buffer, sizeof(buffer), fp)) {
if (!strncmp(buffer, "# ", 3) || !strncmp(buffer, "#\t", 2)) {
buffer[0] = ' ';
offset += snprintf(description+offset, max-offset, "%s", buffer);
} else {
fputs(buffer, fp);
break; /* Long description ends */
}
}
continue;
}
if (xml_l_dscrpt == NULL && offset > 0) {
xml_l_dscrpt = (char *)xmlEncodeEntitiesReentrant(NULL, BAD_CAST(description));
}
if (!strncasecmp(buffer, LSB_INITSCRIPT_INFOEND_TAG, strlen(LSB_INITSCRIPT_INFOEND_TAG))) {
/* Get to the out border of LSB comment block */
break;
}
if (buffer[0] != '#') {
break; /* Out of comment block in the beginning */
}
}
fclose(fp);
*output = crm_strdup_printf(lsb_metadata_template, type,
(xml_l_dscrpt == NULL) ? type : xml_l_dscrpt,
(s_dscrpt == NULL) ? type : s_dscrpt, (provides == NULL) ? "" : provides,
(req_start == NULL) ? "" : req_start, (req_stop == NULL) ? "" : req_stop,
(shld_start == NULL) ? "" : shld_start, (shld_stop == NULL) ? "" : shld_stop,
(dflt_start == NULL) ? "" : dflt_start, (dflt_stop == NULL) ? "" : dflt_stop);
lsb_meta_helper_free_value(xml_l_dscrpt);
lsb_meta_helper_free_value(s_dscrpt);
lsb_meta_helper_free_value(provides);
lsb_meta_helper_free_value(req_start);
lsb_meta_helper_free_value(req_stop);
lsb_meta_helper_free_value(shld_start);
lsb_meta_helper_free_value(shld_stop);
lsb_meta_helper_free_value(dflt_start);
lsb_meta_helper_free_value(dflt_stop);
crm_trace("Created fake metadata: %llu",
(unsigned long long) strlen(*output));
return pcmk_ok;
}
#if SUPPORT_NAGIOS
static int
nagios_get_metadata(const char *type, char **output)
{
int rc = pcmk_ok;
FILE *file_strm = NULL;
int start = 0, length = 0, read_len = 0;
char *metadata_file = NULL;
int len = 36;
len += strlen(NAGIOS_METADATA_DIR);
len += strlen(type);
metadata_file = calloc(1, len);
CRM_CHECK(metadata_file != NULL, return -ENOMEM);
sprintf(metadata_file, "%s/%s.xml", NAGIOS_METADATA_DIR, type);
file_strm = fopen(metadata_file, "r");
if (file_strm == NULL) {
crm_err("Metadata file %s does not exist", metadata_file);
free(metadata_file);
return -EIO;
}
/* see how big the file is */
start = ftell(file_strm);
fseek(file_strm, 0L, SEEK_END);
length = ftell(file_strm);
fseek(file_strm, 0L, start);
CRM_ASSERT(length >= 0);
CRM_ASSERT(start == ftell(file_strm));
if (length <= 0) {
crm_info("%s was not valid", metadata_file);
free(*output);
*output = NULL;
rc = -EIO;
} else {
crm_trace("Reading %d bytes from file", length);
*output = calloc(1, (length + 1));
read_len = fread(*output, 1, length, file_strm);
if (read_len != length) {
crm_err("Calculated and read bytes differ: %d vs. %d", length, read_len);
free(*output);
*output = NULL;
rc = -EIO;
}
}
fclose(file_strm);
free(metadata_file);
return rc;
}
#endif
#if SUPPORT_HEARTBEAT
/* strictly speaking, support for class=heartbeat style scripts
* does not require "heartbeat support" to be enabled.
* But since those scripts are part of the "heartbeat" package usually,
* and are very unlikely to be present in any other deployment,
* I leave it inside this ifdef.
*
* Yes, I know, these are legacy and should die,
* or at least be rewritten to be a proper OCF style agent.
* But they exist, and custom scripts following these rules do, too.
*
* Taken from the old "glue" lrmd, see
* http://hg.linux-ha.org/glue/file/0a7add1d9996/lib/plugins/lrm/raexechb.c#l49
* http://hg.linux-ha.org/glue/file/0a7add1d9996/lib/plugins/lrm/raexechb.c#l393
*/
static const char hb_metadata_template[] =
"<?xml version='1.0'?>\n"
"<!DOCTYPE resource-agent SYSTEM 'ra-api-1.dtd'>\n"
"<resource-agent name='%s' version='0.1'>\n"
"<version>1.0</version>\n"
"<longdesc lang='en'>\n"
"%s"
"</longdesc>\n"
"<shortdesc lang='en'>%s</shortdesc>\n"
"<parameters>\n"
"<parameter name='1' unique='1' required='0'>\n"
"<longdesc lang='en'>\n"
"This argument will be passed as the first argument to the "
"heartbeat resource agent (assuming it supports one)\n"
"</longdesc>\n"
"<shortdesc lang='en'>argv[1]</shortdesc>\n"
"<content type='string' default=' ' />\n"
"</parameter>\n"
"<parameter name='2' unique='1' required='0'>\n"
"<longdesc lang='en'>\n"
"This argument will be passed as the second argument to the "
"heartbeat resource agent (assuming it supports one)\n"
"</longdesc>\n"
"<shortdesc lang='en'>argv[2]</shortdesc>\n"
"<content type='string' default=' ' />\n"
"</parameter>\n"
"<parameter name='3' unique='1' required='0'>\n"
"<longdesc lang='en'>\n"
"This argument will be passed as the third argument to the "
"heartbeat resource agent (assuming it supports one)\n"
"</longdesc>\n"
"<shortdesc lang='en'>argv[3]</shortdesc>\n"
"<content type='string' default=' ' />\n"
"</parameter>\n"
"<parameter name='4' unique='1' required='0'>\n"
"<longdesc lang='en'>\n"
"This argument will be passed as the fourth argument to the "
"heartbeat resource agent (assuming it supports one)\n"
"</longdesc>\n"
"<shortdesc lang='en'>argv[4]</shortdesc>\n"
"<content type='string' default=' ' />\n"
"</parameter>\n"
"<parameter name='5' unique='1' required='0'>\n"
"<longdesc lang='en'>\n"
"This argument will be passed as the fifth argument to the "
"heartbeat resource agent (assuming it supports one)\n"
"</longdesc>\n"
"<shortdesc lang='en'>argv[5]</shortdesc>\n"
"<content type='string' default=' ' />\n"
"</parameter>\n"
"</parameters>\n"
"<actions>\n"
"<action name='start' timeout='15' />\n"
"<action name='stop' timeout='15' />\n"
"<action name='status' timeout='15' />\n"
"<action name='monitor' timeout='15' interval='15' start-delay='15' />\n"
"<action name='meta-data' timeout='5' />\n"
"</actions>\n"
"<special tag='heartbeat'>\n"
"</special>\n"
"</resource-agent>\n";
static int
heartbeat_get_metadata(const char *type, char **output)
{
*output = crm_strdup_printf(hb_metadata_template, type, type, type);
crm_trace("Created fake metadata: %llu",
(unsigned long long) strlen(*output));
return pcmk_ok;
}
#endif
static int
generic_get_metadata(const char *standard, const char *provider, const char *type, char **output)
{
svc_action_t *action;
action = resources_action_create(type, standard, provider, type,
"meta-data", 0, 30000, NULL, 0);
if (action == NULL) {
crm_err("Unable to retrieve meta-data for %s:%s:%s", standard, provider, type);
services_action_free(action);
return -EINVAL;
}
if (!(services_action_sync(action))) {
crm_err("Failed to retrieve meta-data for %s:%s:%s", standard, provider, type);
services_action_free(action);
return -EIO;
}
if (!action->stdout_data) {
crm_err("Failed to receive meta-data for %s:%s:%s", standard, provider, type);
services_action_free(action);
return -EIO;
}
*output = strdup(action->stdout_data);
services_action_free(action);
return pcmk_ok;
}
static int
lrmd_api_get_metadata(lrmd_t * lrmd,
const char *class,
const char *provider,
const char *type, char **output, enum lrmd_call_options options)
{
if (!class || !type) {
return -EINVAL;
}
if (safe_str_eq(class, "service")) {
class = resources_find_service_class(type);
}
if (safe_str_eq(class, "stonith")) {
return stonith_get_metadata(provider, type, output);
} else if (safe_str_eq(class, "lsb")) {
return lsb_get_metadata(type, output);
#if SUPPORT_NAGIOS
} else if (safe_str_eq(class, "nagios")) {
return nagios_get_metadata(type, output);
#endif
#if SUPPORT_HEARTBEAT
} else if (safe_str_eq(class, "heartbeat")) {
return heartbeat_get_metadata(type, output);
#endif
}
return generic_get_metadata(class, provider, type, output);
}
static int
lrmd_api_exec(lrmd_t * lrmd, const char *rsc_id, const char *action, const char *userdata, int interval, /* ms */
int timeout, /* ms */
int start_delay, /* ms */
enum lrmd_call_options options, lrmd_key_value_t * params)
{
int rc = pcmk_ok;
xmlNode *data = create_xml_node(NULL, F_LRMD_RSC);
xmlNode *args = create_xml_node(data, XML_TAG_ATTRS);
lrmd_key_value_t *tmp = NULL;
const char *versioned_args_key = "#" XML_TAG_VER_ATTRS;
crm_xml_add(data, F_LRMD_ORIGIN, __FUNCTION__);
crm_xml_add(data, F_LRMD_RSC_ID, rsc_id);
crm_xml_add(data, F_LRMD_RSC_ACTION, action);
crm_xml_add(data, F_LRMD_RSC_USERDATA_STR, userdata);
crm_xml_add_int(data, F_LRMD_RSC_INTERVAL, interval);
crm_xml_add_int(data, F_LRMD_TIMEOUT, timeout);
crm_xml_add_int(data, F_LRMD_RSC_START_DELAY, start_delay);
for (tmp = params; tmp; tmp = tmp->next) {
if (safe_str_eq(tmp->key, versioned_args_key)) {
xmlNode *versioned_args = string2xml(tmp->value);
if (versioned_args) {
add_node_nocopy(data, NULL, versioned_args);
}
} else {
hash2smartfield((gpointer) tmp->key, (gpointer) tmp->value, args);
}
}
rc = lrmd_send_command(lrmd, LRMD_OP_RSC_EXEC, data, NULL, timeout, options, TRUE);
free_xml(data);
lrmd_key_value_freeall(params);
return rc;
}
static int
lrmd_api_cancel(lrmd_t * lrmd, const char *rsc_id, const char *action, int interval)
{
int rc = pcmk_ok;
xmlNode *data = create_xml_node(NULL, F_LRMD_RSC);
crm_xml_add(data, F_LRMD_ORIGIN, __FUNCTION__);
crm_xml_add(data, F_LRMD_RSC_ACTION, action);
crm_xml_add(data, F_LRMD_RSC_ID, rsc_id);
crm_xml_add_int(data, F_LRMD_RSC_INTERVAL, interval);
rc = lrmd_send_command(lrmd, LRMD_OP_RSC_CANCEL, data, NULL, 0, 0, TRUE);
free_xml(data);
return rc;
}
static int
list_stonith_agents(lrmd_list_t ** resources)
{
int rc = 0;
stonith_t *stonith_api = stonith_api_new();
stonith_key_value_t *stonith_resources = NULL;
stonith_key_value_t *dIter = NULL;
if(stonith_api) {
stonith_api->cmds->list_agents(stonith_api, st_opt_sync_call, NULL, &stonith_resources, 0);
stonith_api->cmds->free(stonith_api);
}
for (dIter = stonith_resources; dIter; dIter = dIter->next) {
rc++;
if (resources) {
*resources = lrmd_list_add(*resources, dIter->value);
}
}
stonith_key_value_freeall(stonith_resources, 1, 0);
return rc;
}
static int
lrmd_api_list_agents(lrmd_t * lrmd, lrmd_list_t ** resources, const char *class,
const char *provider)
{
int rc = 0;
if (safe_str_eq(class, "stonith")) {
rc += list_stonith_agents(resources);
} else {
GListPtr gIter = NULL;
GList *agents = resources_list_agents(class, provider);
for (gIter = agents; gIter != NULL; gIter = gIter->next) {
*resources = lrmd_list_add(*resources, (const char *)gIter->data);
rc++;
}
g_list_free_full(agents, free);
if (!class) {
rc += list_stonith_agents(resources);
}
}
if (rc == 0) {
crm_notice("No agents found for class %s", class);
rc = -EPROTONOSUPPORT;
}
return rc;
}
static int
does_provider_have_agent(const char *agent, const char *provider, const char *class)
{
int found = 0;
GList *agents = NULL;
GListPtr gIter2 = NULL;
agents = resources_list_agents(class, provider);
for (gIter2 = agents; gIter2 != NULL; gIter2 = gIter2->next) {
if (safe_str_eq(agent, gIter2->data)) {
found = 1;
}
}
g_list_free_full(agents, free);
return found;
}
static int
lrmd_api_list_ocf_providers(lrmd_t * lrmd, const char *agent, lrmd_list_t ** providers)
{
int rc = pcmk_ok;
char *provider = NULL;
GList *ocf_providers = NULL;
GListPtr gIter = NULL;
ocf_providers = resources_list_providers("ocf");
for (gIter = ocf_providers; gIter != NULL; gIter = gIter->next) {
provider = gIter->data;
if (!agent || does_provider_have_agent(agent, provider, "ocf")) {
*providers = lrmd_list_add(*providers, (const char *)gIter->data);
rc++;
}
}
g_list_free_full(ocf_providers, free);
return rc;
}
static int
lrmd_api_list_standards(lrmd_t * lrmd, lrmd_list_t ** supported)
{
int rc = 0;
GList *standards = NULL;
GListPtr gIter = NULL;
standards = resources_list_standards();
for (gIter = standards; gIter != NULL; gIter = gIter->next) {
*supported = lrmd_list_add(*supported, (const char *)gIter->data);
rc++;
}
if (list_stonith_agents(NULL) > 0) {
*supported = lrmd_list_add(*supported, "stonith");
rc++;
}
g_list_free_full(standards, free);
return rc;
}
lrmd_t *
lrmd_api_new(void)
{
lrmd_t *new_lrmd = NULL;
lrmd_private_t *pvt = NULL;
new_lrmd = calloc(1, sizeof(lrmd_t));
pvt = calloc(1, sizeof(lrmd_private_t));
pvt->remote = calloc(1, sizeof(crm_remote_t));
new_lrmd->cmds = calloc(1, sizeof(lrmd_api_operations_t));
pvt->type = CRM_CLIENT_IPC;
new_lrmd->private = pvt;
new_lrmd->cmds->connect = lrmd_api_connect;
new_lrmd->cmds->connect_async = lrmd_api_connect_async;
new_lrmd->cmds->is_connected = lrmd_api_is_connected;
new_lrmd->cmds->poke_connection = lrmd_api_poke_connection;
new_lrmd->cmds->disconnect = lrmd_api_disconnect;
new_lrmd->cmds->register_rsc = lrmd_api_register_rsc;
new_lrmd->cmds->unregister_rsc = lrmd_api_unregister_rsc;
new_lrmd->cmds->get_rsc_info = lrmd_api_get_rsc_info;
new_lrmd->cmds->set_callback = lrmd_api_set_callback;
new_lrmd->cmds->get_metadata = lrmd_api_get_metadata;
new_lrmd->cmds->exec = lrmd_api_exec;
new_lrmd->cmds->cancel = lrmd_api_cancel;
new_lrmd->cmds->list_agents = lrmd_api_list_agents;
new_lrmd->cmds->list_ocf_providers = lrmd_api_list_ocf_providers;
new_lrmd->cmds->list_standards = lrmd_api_list_standards;
return new_lrmd;
}
lrmd_t *
lrmd_remote_api_new(const char *nodename, const char *server, int port)
{
#ifdef HAVE_GNUTLS_GNUTLS_H
lrmd_t *new_lrmd = lrmd_api_new();
lrmd_private_t *native = new_lrmd->private;
if (!nodename && !server) {
lrmd_api_delete(new_lrmd);
return NULL;
}
native->type = CRM_CLIENT_TLS;
native->remote_nodename = nodename ? strdup(nodename) : strdup(server);
native->server = server ? strdup(server) : strdup(nodename);
native->port = port;
if (native->port == 0) {
const char *remote_port_str = getenv("PCMK_remote_port");
native->port = remote_port_str ? atoi(remote_port_str) : DEFAULT_REMOTE_PORT;
}
return new_lrmd;
#else
crm_err("GNUTLS is not enabled for this build, remote LRMD client can not be created");
return NULL;
#endif
}
void
lrmd_api_delete(lrmd_t * lrmd)
{
if (!lrmd) {
return;
}
lrmd->cmds->disconnect(lrmd); /* no-op if already disconnected */
free(lrmd->cmds);
if (lrmd->private) {
lrmd_private_t *native = lrmd->private;
#ifdef HAVE_GNUTLS_GNUTLS_H
free(native->server);
#endif
free(native->remote_nodename);
free(native->remote);
free(native->token);
free(native->peer_version);
}
free(lrmd->private);
free(lrmd);
}
diff --git a/lib/services/services.c b/lib/services/services.c
index bb6dab9cac..a6b776a70b 100644
--- a/lib/services/services.c
+++ b/lib/services/services.c
@@ -1,865 +1,865 @@
/*
* Copyright (C) 2010 Andrew Beekhof <andrew@beekhof.net>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This software is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include <crm_internal.h>
#ifndef _GNU_SOURCE
# define _GNU_SOURCE
#endif
#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <errno.h>
#include <unistd.h>
#include <dirent.h>
#include <fcntl.h>
#include <crm/crm.h>
#include <crm/common/mainloop.h>
#include <crm/services.h>
#include <crm/msg_xml.h>
#include "services_private.h"
#if SUPPORT_UPSTART
# include <upstart.h>
#endif
#if SUPPORT_SYSTEMD
# include <systemd.h>
#endif
/* TODO: Develop a rollover strategy */
static int operations = 0;
GHashTable *recurring_actions = NULL;
/* ops waiting to run async because of conflicting active
* pending ops*/
GList *blocked_ops = NULL;
/* ops currently active (in-flight) */
GList *inflight_ops = NULL;
svc_action_t *
services_action_create(const char *name, const char *action, int interval, int timeout)
{
return resources_action_create(name, "lsb", NULL, name, action, interval, timeout, NULL, 0);
}
const char *
resources_find_service_class(const char *agent)
{
/* Priority is:
* - lsb
* - systemd
* - upstart
*/
int rc = 0;
struct stat st;
char *path = NULL;
#ifdef LSB_ROOT_DIR
rc = asprintf(&path, "%s/%s", LSB_ROOT_DIR, agent);
if (rc > 0 && stat(path, &st) == 0) {
free(path);
return "lsb";
}
free(path);
#endif
#if SUPPORT_SYSTEMD
if (systemd_unit_exists(agent)) {
return "systemd";
}
#endif
#if SUPPORT_UPSTART
if (upstart_job_exists(agent)) {
return "upstart";
}
#endif
return NULL;
}
svc_action_t *
resources_action_create(const char *name, const char *standard, const char *provider,
const char *agent, const char *action, int interval, int timeout,
GHashTable * params, enum svc_action_flags flags)
{
svc_action_t *op = NULL;
/*
* Do some up front sanity checks before we go off and
* build the svc_action_t instance.
*/
if (crm_strlen_zero(name)) {
crm_err("A service or resource action must have a name.");
goto return_error;
}
if (crm_strlen_zero(standard)) {
crm_err("A service action must have a valid standard.");
goto return_error;
}
if (!strcasecmp(standard, "ocf") && crm_strlen_zero(provider)) {
crm_err("An OCF resource action must have a provider.");
goto return_error;
}
if (crm_strlen_zero(agent)) {
crm_err("A service or resource action must have an agent.");
goto return_error;
}
if (crm_strlen_zero(action)) {
crm_err("A service or resource action must specify an action.");
goto return_error;
}
if (safe_str_eq(action, "monitor") && (
#if SUPPORT_HEARTBEAT
safe_str_eq(standard, "heartbeat") ||
#endif
safe_str_eq(standard, "lsb") || safe_str_eq(standard, "service"))) {
action = "status";
}
/*
* Sanity checks passed, proceed!
*/
op = calloc(1, sizeof(svc_action_t));
op->opaque = calloc(1, sizeof(svc_action_private_t));
op->rsc = strdup(name);
op->action = strdup(action);
op->interval = interval;
op->timeout = timeout;
op->standard = strdup(standard);
op->agent = strdup(agent);
op->sequence = ++operations;
op->flags = flags;
if (asprintf(&op->id, "%s_%s_%d", name, action, interval) == -1) {
goto return_error;
}
if (strcasecmp(op->standard, "service") == 0) {
const char *expanded = resources_find_service_class(op->agent);
if(expanded) {
crm_debug("Found a %s agent for %s/%s", expanded, op->rsc, op->agent);
free(op->standard);
op->standard = strdup(expanded);
} else {
crm_info("Cannot determine the standard for %s (%s)", op->rsc, op->agent);
free(op->standard);
op->standard = strdup("lsb");
}
CRM_ASSERT(op->standard);
}
if (strcasecmp(op->standard, "ocf") == 0) {
op->provider = strdup(provider);
op->params = params;
params = NULL;
if (asprintf(&op->opaque->exec, "%s/resource.d/%s/%s", OCF_ROOT_DIR, provider, agent) == -1) {
crm_err("Internal error: cannot create agent path");
goto return_error;
}
op->opaque->args[0] = strdup(op->opaque->exec);
op->opaque->args[1] = strdup(action);
} else if (strcasecmp(op->standard, "lsb") == 0) {
if (op->agent[0] == '/') {
/* if given an absolute path, use that instead
* of tacking on the LSB_ROOT_DIR path to the front */
op->opaque->exec = strdup(op->agent);
} else if (asprintf(&op->opaque->exec, "%s/%s", LSB_ROOT_DIR, op->agent) == -1) {
crm_err("Internal error: cannot create agent path");
goto return_error;
}
op->opaque->args[0] = strdup(op->opaque->exec);
op->opaque->args[1] = strdup(op->action);
op->opaque->args[2] = NULL;
#if SUPPORT_HEARTBEAT
} else if (strcasecmp(op->standard, "heartbeat") == 0) {
int index;
int param_num;
char buf_tmp[20];
void *value_tmp;
if (op->agent[0] == '/') {
/* if given an absolute path, use that instead
* of tacking on the HB_RA_DIR path to the front */
op->opaque->exec = strdup(op->agent);
} else if (asprintf(&op->opaque->exec, "%s/%s", HB_RA_DIR, op->agent) == -1) {
crm_err("Internal error: cannot create agent path");
goto return_error;
}
op->opaque->args[0] = strdup(op->opaque->exec);
/* The "heartbeat" agent class only has positional arguments,
* which we keyed by their decimal position number. */
param_num = 1;
for (index = 1; index <= MAX_ARGC - 3; index++ ) {
snprintf(buf_tmp, sizeof(buf_tmp), "%d", index);
value_tmp = g_hash_table_lookup(params, buf_tmp);
if (value_tmp == NULL) {
/* maybe: strdup("") ??
* But the old lrmd did simply continue as well. */
continue;
}
op->opaque->args[param_num++] = strdup(value_tmp);
}
/* Add operation code as the last argument, */
/* and the teminating NULL pointer */
op->opaque->args[param_num++] = strdup(op->action);
op->opaque->args[param_num] = NULL;
#endif
#if SUPPORT_SYSTEMD
} else if (strcasecmp(op->standard, "systemd") == 0) {
op->opaque->exec = strdup("systemd-dbus");
#endif
#if SUPPORT_UPSTART
} else if (strcasecmp(op->standard, "upstart") == 0) {
op->opaque->exec = strdup("upstart-dbus");
#endif
} else if (strcasecmp(op->standard, "service") == 0) {
op->opaque->exec = strdup(SERVICE_SCRIPT);
op->opaque->args[0] = strdup(SERVICE_SCRIPT);
op->opaque->args[1] = strdup(agent);
op->opaque->args[2] = strdup(action);
#if SUPPORT_NAGIOS
} else if (strcasecmp(op->standard, "nagios") == 0) {
int index = 0;
if (op->agent[0] == '/') {
/* if given an absolute path, use that instead
* of tacking on the NAGIOS_PLUGIN_DIR path to the front */
op->opaque->exec = strdup(op->agent);
} else if (asprintf(&op->opaque->exec, "%s/%s", NAGIOS_PLUGIN_DIR, op->agent) == -1) {
crm_err("Internal error: cannot create agent path");
goto return_error;
}
op->opaque->args[0] = strdup(op->opaque->exec);
index = 1;
if (safe_str_eq(op->action, "monitor") && op->interval == 0) {
/* Invoke --version for a nagios probe */
op->opaque->args[index] = strdup("--version");
index++;
} else if (params) {
GHashTableIter iter;
char *key = NULL;
char *value = NULL;
static int args_size = sizeof(op->opaque->args) / sizeof(char *);
g_hash_table_iter_init(&iter, params);
while (g_hash_table_iter_next(&iter, (gpointer *) & key, (gpointer *) & value) &&
index <= args_size - 3) {
int len = 3;
char *long_opt = NULL;
if (safe_str_eq(key, XML_ATTR_CRM_VERSION) || strstr(key, CRM_META "_")) {
continue;
}
len += strlen(key);
long_opt = calloc(1, len);
sprintf(long_opt, "--%s", key);
long_opt[len - 1] = 0;
op->opaque->args[index] = long_opt;
op->opaque->args[index + 1] = strdup(value);
index += 2;
}
}
op->opaque->args[index] = NULL;
#endif
} else {
crm_err("Unknown resource standard: %s", op->standard);
services_action_free(op);
op = NULL;
}
if(params) {
g_hash_table_destroy(params);
}
return op;
return_error:
if(params) {
g_hash_table_destroy(params);
}
services_action_free(op);
return NULL;
}
svc_action_t *
services_action_create_generic(const char *exec, const char *args[])
{
svc_action_t *op;
unsigned int cur_arg;
op = calloc(1, sizeof(*op));
op->opaque = calloc(1, sizeof(svc_action_private_t));
op->opaque->exec = strdup(exec);
op->opaque->args[0] = strdup(exec);
for (cur_arg = 1; args && args[cur_arg - 1]; cur_arg++) {
op->opaque->args[cur_arg] = strdup(args[cur_arg - 1]);
if (cur_arg == DIMOF(op->opaque->args) - 1) {
crm_err("svc_action_t args list not long enough for '%s' execution request.", exec);
break;
}
}
return op;
}
#if SUPPORT_DBUS
-/*
+/*!
* \internal
* \brief Update operation's pending DBus call, unreferencing old one if needed
*
* \param[in,out] op Operation to modify
* \param[in] pending Pending call to set
*/
void
services_set_op_pending(svc_action_t *op, DBusPendingCall *pending)
{
if (op->opaque->pending && (op->opaque->pending != pending)) {
if (pending) {
crm_info("Lost pending %s DBus call (%p)", op->id, op->opaque->pending);
} else {
crm_trace("Done with pending %s DBus call (%p)", op->id, op->opaque->pending);
}
dbus_pending_call_unref(op->opaque->pending);
}
op->opaque->pending = pending;
if (pending) {
crm_trace("Updated pending %s DBus call (%p)", op->id, pending);
} else {
crm_trace("Cleared pending %s DBus call", op->id);
}
}
#endif
void
services_action_cleanup(svc_action_t * op)
{
if(op->opaque == NULL) {
return;
}
#if SUPPORT_DBUS
if(op->opaque->timerid != 0) {
crm_trace("Removing timer for call %s to %s", op->action, op->rsc);
g_source_remove(op->opaque->timerid);
op->opaque->timerid = 0;
}
if(op->opaque->pending) {
crm_trace("Cleaning up pending dbus call %p %s for %s", op->opaque->pending, op->action, op->rsc);
if(dbus_pending_call_get_completed(op->opaque->pending)) {
crm_warn("Pending dbus call %s for %s did not complete", op->action, op->rsc);
}
dbus_pending_call_cancel(op->opaque->pending);
dbus_pending_call_unref(op->opaque->pending);
op->opaque->pending = NULL;
}
#endif
if (op->opaque->stderr_gsource) {
mainloop_del_fd(op->opaque->stderr_gsource);
op->opaque->stderr_gsource = NULL;
}
if (op->opaque->stdout_gsource) {
mainloop_del_fd(op->opaque->stdout_gsource);
op->opaque->stdout_gsource = NULL;
}
}
void
services_action_free(svc_action_t * op)
{
unsigned int i;
if (op == NULL) {
return;
}
services_action_cleanup(op);
if (op->opaque->repeat_timer) {
g_source_remove(op->opaque->repeat_timer);
op->opaque->repeat_timer = 0;
}
free(op->id);
free(op->opaque->exec);
for (i = 0; i < DIMOF(op->opaque->args); i++) {
free(op->opaque->args[i]);
}
free(op->opaque);
free(op->rsc);
free(op->action);
free(op->standard);
free(op->agent);
free(op->provider);
free(op->stdout_data);
free(op->stderr_data);
if (op->params) {
g_hash_table_destroy(op->params);
op->params = NULL;
}
free(op);
}
gboolean
cancel_recurring_action(svc_action_t * op)
{
crm_info("Cancelling %s operation %s", op->standard, op->id);
if (recurring_actions) {
g_hash_table_remove(recurring_actions, op->id);
}
if (op->opaque->repeat_timer) {
g_source_remove(op->opaque->repeat_timer);
op->opaque->repeat_timer = 0;
}
return TRUE;
}
gboolean
services_action_cancel(const char *name, const char *action, int interval)
{
svc_action_t *op = NULL;
char id[512];
snprintf(id, sizeof(id), "%s_%s_%d", name, action, interval);
if (!(op = g_hash_table_lookup(recurring_actions, id))) {
return FALSE;
}
/* Always kill the recurring timer */
cancel_recurring_action(op);
if (op->pid == 0) {
op->status = PCMK_LRM_OP_CANCELLED;
if (op->opaque->callback) {
op->opaque->callback(op);
}
blocked_ops = g_list_remove(blocked_ops, op);
services_action_free(op);
} else {
crm_info("Cancelling in-flight op: performing early termination of %s (pid=%d)", id, op->pid);
op->cancel = 1;
if (mainloop_child_kill(op->pid) == FALSE) {
/* even though the early termination failed,
* the op will be marked as cancelled once it completes. */
crm_err("Termination of %s (pid=%d) failed", id, op->pid);
return FALSE;
}
}
return TRUE;
}
gboolean
services_action_kick(const char *name, const char *action, int interval /* ms */)
{
svc_action_t * op = NULL;
char *id = NULL;
if (asprintf(&id, "%s_%s_%d", name, action, interval) == -1) {
return FALSE;
}
op = g_hash_table_lookup(recurring_actions, id);
free(id);
if (op == NULL) {
return FALSE;
}
if (op->pid) {
return TRUE;
} else {
if (op->opaque->repeat_timer) {
g_source_remove(op->opaque->repeat_timer);
op->opaque->repeat_timer = 0;
}
recurring_action_timer(op);
return TRUE;
}
}
/* add new recurring operation, check for duplicates.
* - if duplicate found, return TRUE, immediately reschedule op.
* - if no dup, return FALSE, inserve into recurring op list.*/
static gboolean
handle_duplicate_recurring(svc_action_t * op, void (*action_callback) (svc_action_t *))
{
svc_action_t * dup = NULL;
if (recurring_actions == NULL) {
recurring_actions = g_hash_table_new_full(g_str_hash, g_str_equal, NULL, NULL);
return FALSE;
}
/* check for duplicates */
dup = g_hash_table_lookup(recurring_actions, op->id);
if (dup && (dup != op)) {
/* update user data */
if (op->opaque->callback) {
dup->opaque->callback = op->opaque->callback;
dup->cb_data = op->cb_data;
op->cb_data = NULL;
}
/* immediately execute the next interval */
if (dup->pid != 0) {
if (op->opaque->repeat_timer) {
g_source_remove(op->opaque->repeat_timer);
op->opaque->repeat_timer = 0;
}
recurring_action_timer(dup);
}
/* free the dup. */
services_action_free(op);
return TRUE;
}
return FALSE;
}
static gboolean
action_async_helper(svc_action_t * op)
{
if (op->standard && strcasecmp(op->standard, "upstart") == 0) {
#if SUPPORT_UPSTART
return upstart_job_exec(op, FALSE);
#endif
} else if (op->standard && strcasecmp(op->standard, "systemd") == 0) {
#if SUPPORT_SYSTEMD
return systemd_unit_exec(op);
#endif
} else {
return services_os_action_execute(op, FALSE);
}
/* The 'op' has probably been freed if the execution functions return TRUE. */
/* Avoid using the 'op' in here. */
return FALSE;
}
void
services_add_inflight_op(svc_action_t * op)
{
if (op == NULL) {
return;
}
CRM_ASSERT(op->synchronous == FALSE);
/* keep track of ops that are in-flight to avoid collisions in the same namespace */
if (op->rsc) {
inflight_ops = g_list_append(inflight_ops, op);
}
}
gboolean
services_action_async(svc_action_t * op, void (*action_callback) (svc_action_t *))
{
op->synchronous = false;
if (action_callback) {
op->opaque->callback = action_callback;
}
if (op->interval > 0) {
if (handle_duplicate_recurring(op, action_callback) == TRUE) {
/* entry rescheduled, dup freed */
/* exit early */
return TRUE;
}
g_hash_table_replace(recurring_actions, op->id, op);
}
if (op->rsc && is_op_blocked(op->rsc)) {
blocked_ops = g_list_append(blocked_ops, op);
return TRUE;
}
return action_async_helper(op);
}
static gboolean processing_blocked_ops = FALSE;
gboolean
is_op_blocked(const char *rsc)
{
GList *gIter = NULL;
svc_action_t *op = NULL;
for (gIter = inflight_ops; gIter != NULL; gIter = gIter->next) {
op = gIter->data;
if (safe_str_eq(op->rsc, rsc)) {
return TRUE;
}
}
return FALSE;
}
void
handle_blocked_ops(void)
{
GList *executed_ops = NULL;
GList *gIter = NULL;
svc_action_t *op = NULL;
gboolean res = FALSE;
if (processing_blocked_ops) {
/* avoid nested calling of this function */
return;
}
processing_blocked_ops = TRUE;
/* n^2 operation here, but blocked ops are incredibly rare. this list
* will be empty 99% of the time. */
for (gIter = blocked_ops; gIter != NULL; gIter = gIter->next) {
op = gIter->data;
if (is_op_blocked(op->rsc)) {
continue;
}
executed_ops = g_list_append(executed_ops, op);
res = action_async_helper(op);
if (res == FALSE) {
op->status = PCMK_LRM_OP_ERROR;
/* this can cause this function to be called recursively
* which is why we have processing_blocked_ops static variable */
operation_finalize(op);
}
}
for (gIter = executed_ops; gIter != NULL; gIter = gIter->next) {
op = gIter->data;
blocked_ops = g_list_remove(blocked_ops, op);
}
g_list_free(executed_ops);
processing_blocked_ops = FALSE;
}
gboolean
services_action_sync(svc_action_t * op)
{
gboolean rc = TRUE;
if (op == NULL) {
crm_trace("No operation to execute");
return FALSE;
}
op->synchronous = true;
if (op->standard && strcasecmp(op->standard, "upstart") == 0) {
#if SUPPORT_UPSTART
rc = upstart_job_exec(op, TRUE);
#endif
} else if (op->standard && strcasecmp(op->standard, "systemd") == 0) {
#if SUPPORT_SYSTEMD
rc = systemd_unit_exec(op);
#endif
} else {
rc = services_os_action_execute(op, TRUE);
}
crm_trace(" > %s_%s_%d: %s = %d", op->rsc, op->action, op->interval, op->opaque->exec, op->rc);
if (op->stdout_data) {
crm_trace(" > stdout: %s", op->stdout_data);
}
if (op->stderr_data) {
crm_trace(" > stderr: %s", op->stderr_data);
}
return rc;
}
GList *
get_directory_list(const char *root, gboolean files, gboolean executable)
{
return services_os_get_directory_list(root, files, executable);
}
GList *
services_list(void)
{
return resources_list_agents("lsb", NULL);
}
#if SUPPORT_HEARTBEAT
static GList *
resources_os_list_hb_agents(void)
{
return services_os_get_directory_list(HB_RA_DIR, TRUE, TRUE);
}
#endif
GList *
resources_list_standards(void)
{
GList *standards = NULL;
GList *agents = NULL;
standards = g_list_append(standards, strdup("ocf"));
standards = g_list_append(standards, strdup("lsb"));
standards = g_list_append(standards, strdup("service"));
#if SUPPORT_SYSTEMD
agents = systemd_unit_listall();
#else
agents = NULL;
#endif
if (agents) {
standards = g_list_append(standards, strdup("systemd"));
g_list_free_full(agents, free);
}
#if SUPPORT_UPSTART
agents = upstart_job_listall();
#else
agents = NULL;
#endif
if (agents) {
standards = g_list_append(standards, strdup("upstart"));
g_list_free_full(agents, free);
}
#if SUPPORT_NAGIOS
agents = resources_os_list_nagios_agents();
if (agents) {
standards = g_list_append(standards, strdup("nagios"));
g_list_free_full(agents, free);
}
#endif
#if SUPPORT_HEARTBEAT
standards = g_list_append(standards, strdup("heartbeat"));
#endif
return standards;
}
GList *
resources_list_providers(const char *standard)
{
if (strcasecmp(standard, "ocf") == 0) {
return resources_os_list_ocf_providers();
}
return NULL;
}
GList *
resources_list_agents(const char *standard, const char *provider)
{
if (standard == NULL || strcasecmp(standard, "service") == 0) {
GList *tmp1;
GList *tmp2;
GList *result = resources_os_list_lsb_agents();
if (standard == NULL) {
tmp1 = result;
tmp2 = resources_os_list_ocf_agents(NULL);
if (tmp2) {
result = g_list_concat(tmp1, tmp2);
}
}
#if SUPPORT_SYSTEMD
tmp1 = result;
tmp2 = systemd_unit_listall();
if (tmp2) {
result = g_list_concat(tmp1, tmp2);
}
#endif
#if SUPPORT_UPSTART
tmp1 = result;
tmp2 = upstart_job_listall();
if (tmp2) {
result = g_list_concat(tmp1, tmp2);
}
#endif
return result;
} else if (strcasecmp(standard, "ocf") == 0) {
return resources_os_list_ocf_agents(provider);
} else if (strcasecmp(standard, "lsb") == 0) {
return resources_os_list_lsb_agents();
#if SUPPORT_HEARTBEAT
} else if (strcasecmp(standard, "heartbeat") == 0) {
return resources_os_list_hb_agents();
#endif
#if SUPPORT_SYSTEMD
} else if (strcasecmp(standard, "systemd") == 0) {
return systemd_unit_listall();
#endif
#if SUPPORT_UPSTART
} else if (strcasecmp(standard, "upstart") == 0) {
return upstart_job_listall();
#endif
#if SUPPORT_NAGIOS
} else if (strcasecmp(standard, "nagios") == 0) {
return resources_os_list_nagios_agents();
#endif
}
return NULL;
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Jul 8, 6:09 PM (21 h, 27 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2002504
Default Alt Text
(774 KB)

Event Timeline