diff --git a/lib/ais/utils.c b/lib/ais/utils.c index 1d196e6309..7b977cbd90 100644 --- a/lib/ais/utils.c +++ b/lib/ais/utils.c @@ -1,740 +1,740 @@ /* * Copyright (C) 2004 Andrew Beekhof * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "./utils.h" int in_shutdown = FALSE; extern GHashTable *membership_notify_list; extern int send_cluster_msg_raw(AIS_Message *ais_msg); void log_ais_message(int level, AIS_Message *msg) { char *data = get_ais_data(msg); log_printf(level, "Msg[%d] (dest=%s:%s, from=%s:%s.%d, remote=%s, size=%d): %.90s", msg->id, ais_dest(&(msg->host)), msg_type2text(msg->host.type), ais_dest(&(msg->sender)), msg_type2text(msg->sender.type), msg->sender.pid, msg->sender.uname==local_uname?"false":"true", ais_data_len(msg), data); /* do_ais_log(level, */ /* "Msg[%d] (dest=%s:%s, from=%s:%s.%d, remote=%s, size=%d): %.90s", */ /* msg->id, ais_dest(&(msg->host)), msg_type2text(msg->host.type), */ /* ais_dest(&(msg->sender)), msg_type2text(msg->sender.type), */ /* msg->sender.pid, */ /* msg->sender.uname==local_uname?"false":"true", */ /* ais_data_len(msg), data); */ ais_free(data); } /* static gboolean ghash_find_by_uname(gpointer key, gpointer value, gpointer user_data) { crm_node_t *node = value; int id = GPOINTER_TO_INT(user_data); if (node->id == id) { return TRUE; } return FALSE; } */ static gboolean ghash_send_removal(gpointer key, gpointer value, gpointer data) { if(send_client_msg(value, crm_class_rmpeer, crm_msg_none, data) != 0) { /* remove it */ return TRUE; } return FALSE; } static void ais_remove_peer(char *node_id) { uint32_t id = ais_get_int(node_id, NULL); crm_node_t *node = g_hash_table_lookup(membership_list, GUINT_TO_POINTER(id)); if(node == NULL) { ais_info("Peer %u is unknown", id); } else if(ais_str_eq(CRM_NODE_MEMBER, node->state)) { ais_warn("Peer %u/%s is still active", id, node->uname); } else if(g_hash_table_remove(membership_list, GUINT_TO_POINTER(id))) { ais_notice("Removed dead peer %u from the membership list", id); ais_info("Sending removal of %u to %d children", id, g_hash_table_size(membership_notify_list)); g_hash_table_foreach_remove(membership_notify_list, ghash_send_removal, node_id); } else { ais_warn("Peer %u/%s was not removed", id, node->uname); } } gboolean process_ais_message(AIS_Message *msg) { int len = ais_data_len(msg); char *data = get_ais_data(msg); do_ais_log(LOG_DEBUG, "Msg[%d] (dest=%s:%s, from=%s:%s.%d, remote=%s, size=%d): %.90s", msg->id, ais_dest(&(msg->host)), msg_type2text(msg->host.type), ais_dest(&(msg->sender)), msg_type2text(msg->sender.type), msg->sender.pid, msg->sender.uname==local_uname?"false":"true", ais_data_len(msg), data); if(data && len > 12 && strncmp("remove-peer:", data, 12) == 0) { char *node = data+12; ais_remove_peer(node); } ais_free(data); return TRUE; } static int ais_string_to_boolean(const char * s) { int rc = 0; if(s == NULL) { return rc; } if(strcasecmp(s, "true") == 0 || strcasecmp(s, "on") == 0 || strcasecmp(s, "yes") == 0 || strcasecmp(s, "y") == 0 || strcasecmp(s, "1") == 0) { rc = 1; } return rc; } gboolean spawn_child(crm_child_t *child) { int rc = 0; int lpc = 0; int uid = 0; int gid = 0; struct rlimit oflimits; struct passwd *pwentry = NULL; gboolean use_valgrind = FALSE; const char *devnull = "/dev/null"; const char *env_valgrind = getenv("HA_VALGRIND_ENABLED"); if(child->command == NULL) { ais_info("Nothing to do for child \"%s\"", child->name); return TRUE; } if(child->uid) { pwentry = getpwnam(child->uid); if(pwentry == NULL) { ais_err("Invalid uid (%s) specified for %s", child->uid, child->name); return TRUE; } uid = pwentry->pw_uid; gid = pwentry->pw_gid; } if(env_valgrind == NULL) { use_valgrind = FALSE; } else if(ais_string_to_boolean(env_valgrind)) { use_valgrind = TRUE; } else if(strstr(env_valgrind, child->name)) { use_valgrind = TRUE; } if(use_valgrind && strlen(VALGRIND_BIN) == 0) { ais_warn("Cannot enable valgrind for %s:" " The location of the valgrind binary is unknown", child->name); use_valgrind = FALSE; } child->pid = fork(); AIS_ASSERT(child->pid != -1); if(child->pid > 0) { /* parent */ ais_info("Forked child %d for process %s%s", child->pid, child->name, use_valgrind?" (valgrind enabled)":""); return TRUE; } /* else child */ /* The child should only log fatal errors, there is a possibility of a * deadlock if the parent was logging just before the fork() executed. * So any logging must relate to conditions worse than a deadlock */ if(0 && gid) { rc = setgid(gid); if(rc < 0) { ais_perror("Could not set group to %d", gid); } } if(uid) { rc = setuid(uid); if(rc < 0) { ais_perror("Could not set user to %d (%s)", uid, child->uid); } } /* A precautionary measure */ getrlimit(RLIMIT_NOFILE, &oflimits); for (; lpc < oflimits.rlim_cur; lpc++) { close(lpc); } (void)open(devnull, O_RDONLY); /* Stdin: fd 0 */ (void)open(devnull, O_WRONLY); /* Stdout: fd 1 */ (void)open(devnull, O_WRONLY); /* Stderr: fd 2 */ if(use_valgrind) { char *opts[] = { ais_strdup(VALGRIND_BIN), ais_strdup(child->command), NULL }; (void)execvp(VALGRIND_BIN, opts); } else { char *opts[] = { ais_strdup(child->command), NULL }; (void)execvp(child->command, opts); } ais_perror("FATAL: Cannot exec %s", child->command); exit(100); return TRUE; /* never reached */ } gboolean stop_child(crm_child_t *child, int signal) { if(signal == 0) { signal = SIGTERM; } if(child->command == NULL) { ais_info("Nothing to do for child \"%s\"", child->name); return TRUE; } ais_debug("Stopping CRM child \"%s\"", child->name); if (child->pid <= 0) { ais_debug_2("Client %s not running", child->name); return TRUE; } errno = 0; if(kill(child->pid, signal) == 0) { ais_notice("Sent -%d to %s: [%d]", signal, child->name, child->pid); } else { ais_perror("Sent -%d to %s: [%d]", signal, child->name, child->pid); } return TRUE; } void destroy_ais_node(gpointer data) { crm_node_t *node = data; ais_info("Destroying entry for node %u", node->id); ais_free(node->addr); ais_free(node->uname); ais_free(node->state); ais_free(node); } int update_member(unsigned int id, uint64_t born, uint64_t seq, int32_t votes, uint32_t procs, const char *uname, const char *state, const char *version) { int changed = 0; crm_node_t *node = NULL; node = g_hash_table_lookup(membership_list, GUINT_TO_POINTER(id)); if(node == NULL) { ais_malloc0(node, sizeof(crm_node_t)); ais_info("Creating entry for node %u born on "U64T"", id, seq); node->id = id; node->addr = NULL; node->state = ais_strdup("unknown"); g_hash_table_insert(membership_list, GUINT_TO_POINTER(id), node); node = g_hash_table_lookup(membership_list, GUINT_TO_POINTER(id)); } if(seq != 0) { node->last_seen = seq; } if(born != 0) { changed = TRUE; node->born = born; ais_info("%p Node %u (%s) born on: "U64T, node, id, uname, born); } if(version != NULL) { ais_free(node->version); node->version = ais_strdup(version); } if(uname != NULL) { if(node->uname == NULL || ais_str_eq(node->uname, uname) == FALSE) { ais_info("%p Node %u now known as %s (was: %s)", node, id, uname, node->uname); ais_free(node->uname); node->uname = ais_strdup(uname); changed = TRUE; } } if(procs != 0 && procs != node->processes) { ais_info("Node %s now has process list: %.32x (%u)", node->uname, procs, procs); node->processes = procs; changed = TRUE; } if(votes >= 0 && votes != node->votes) { ais_info("Node %s now has %d quorum votes (was %d)", node->uname, votes, node->votes); node->votes = votes; changed = TRUE; } if(state != NULL) { if(node->state == NULL || ais_str_eq(node->state, state) == FALSE) { ais_free(node->state); node->state = ais_strdup(state); ais_info("Node %u/%s is now: %s", id, node->uname?node->uname:"unknown", state); changed = TRUE; } } AIS_ASSERT(node != NULL); return changed; } void delete_member(uint32_t id, const char *uname) { if(uname == NULL) { g_hash_table_remove(membership_list, GUINT_TO_POINTER(id)); return; } ais_err("Deleting by uname is not yet supported"); } const char *member_uname(uint32_t id) { crm_node_t *node = g_hash_table_lookup( membership_list, GUINT_TO_POINTER(id)); if(node == NULL) { return ".unknown."; } if(node->uname == NULL) { return ".pending."; } return node->uname; } char *append_member(char *data, crm_node_t *node) { int size = 1; /* nul */ int offset = 0; static int fixed_len = 4 + 8 + 7 + 6 + 6 + 7 + 11; if(data) { size = strlen(data); } offset = size; size += fixed_len; size += 32; /* node->id */ size += 100; /* node->seq, node->born */ size += strlen(node->state); if(node->uname) { size += (7 + strlen(node->uname)); } if(node->addr) { size += (6 + strlen(node->addr)); } if(node->version) { size += (9 + strlen(node->version)); } data = realloc(data, size); offset += snprintf(data + offset, size - offset, "id); if(node->uname) { offset += snprintf(data + offset, size - offset, "uname=\"%s\" ", node->uname); } offset += snprintf(data + offset, size - offset, "state=\"%s\" ", node->state); offset += snprintf(data + offset, size - offset, "born=\""U64T"\" ", node->born); offset += snprintf(data + offset, size - offset, "seen=\""U64T"\" ", node->last_seen); offset += snprintf(data + offset, size - offset, "votes=\"%d\" ", node->votes); offset += snprintf(data + offset, size - offset, "processes=\"%u\" ", node->processes); if(node->addr) { offset += snprintf(data + offset, size - offset, "addr=\"%s\" ", node->addr); } if(node->version) { offset += snprintf(data + offset, size - offset, "version=\"%s\" ", node->version); } offset += snprintf(data + offset, size - offset, "/>"); return data; } void swap_sender(AIS_Message *msg) { int tmp = 0; char tmp_s[256]; tmp = msg->host.type; msg->host.type = msg->sender.type; msg->sender.type = tmp; tmp = msg->host.type; msg->host.size = msg->sender.type; msg->sender.type = tmp; memcpy(tmp_s, msg->host.uname, 256); memcpy(msg->host.uname, msg->sender.uname, 256); memcpy(msg->sender.uname, tmp_s, 256); } char *get_ais_data(AIS_Message *msg) { int rc = BZ_OK; char *uncompressed = NULL; - unsigned int new_size = msg->size; + unsigned int new_size = msg->size + 1; if(msg->is_compressed == FALSE) { uncompressed = strdup(msg->data); } else { ais_malloc0(uncompressed, new_size); rc = BZ2_bzBuffToBuffDecompress( uncompressed, &new_size, msg->data, msg->compressed_size, 1, 0); if(rc != BZ_OK) { ais_info("rc=%d, new=%u expected=%u", rc, new_size, msg->size); } AIS_ASSERT(rc == BZ_OK); AIS_ASSERT(new_size == msg->size); } return uncompressed; } int send_cluster_msg( enum crm_ais_msg_types type, const char *host, const char *data) { int rc = 0; int data_len = 0; AIS_Message *ais_msg = NULL; int total_size = sizeof(AIS_Message); AIS_ASSERT(local_nodeid != 0); if(data != NULL) { data_len = 1 + strlen(data); total_size += data_len; } ais_malloc0(ais_msg, total_size); ais_msg->header.size = total_size; ais_msg->header.error = SA_AIS_OK; ais_msg->header.id = 0; ais_msg->size = data_len; memcpy(ais_msg->data, data, data_len); ais_msg->sender.type = crm_msg_ais; ais_msg->host.type = type; ais_msg->host.id = 0; if(host) { ais_msg->host.size = strlen(host); memset(ais_msg->host.uname, 0, MAX_NAME); memcpy(ais_msg->host.uname, host, ais_msg->host.size); /* ais_msg->host.id = nodeid_lookup(host); */ } else { ais_msg->host.type = type; ais_msg->host.size = 0; memset(ais_msg->host.uname, 0, MAX_NAME); } rc = send_cluster_msg_raw(ais_msg); ais_free(ais_msg); return rc; } extern struct corosync_api_v1 *crm_api; int send_client_ipc(void *conn, AIS_Message *ais_msg) { int rc = -1; if (conn == NULL) { rc = -2; } else if (!libais_connection_active(conn)) { ais_warn("Connection no longer active"); rc = -3; /* } else if ((queue->size - 1) == queue->used) { */ /* ais_err("Connection is throttled: %d", queue->size); */ } else { #ifdef AIS_WHITETANK rc = openais_dispatch_send (conn, ais_msg, ais_msg->header.size); #endif #ifdef AIS_COROSYNC rc = crm_api->ipc_dispatch_send (conn, ais_msg, ais_msg->header.size); #endif } return rc; } int send_client_msg( void *conn, enum crm_ais_msg_class class, enum crm_ais_msg_types type, const char *data) { int rc = 0; int data_len = 0; int total_size = sizeof(AIS_Message); AIS_Message *ais_msg = NULL; static int msg_id = 0; AIS_ASSERT(local_nodeid != 0); msg_id++; AIS_ASSERT(msg_id != 0 /* wrap-around */); if(data != NULL) { data_len = 1 + strlen(data); } total_size += data_len; ais_malloc0(ais_msg, total_size); ais_msg->id = msg_id; ais_msg->header.id = class; ais_msg->header.size = total_size; ais_msg->header.error = SA_AIS_OK; ais_msg->size = data_len; memcpy(ais_msg->data, data, data_len); ais_msg->host.size = 0; ais_msg->host.type = type; memset(ais_msg->host.uname, 0, MAX_NAME); ais_msg->host.id = 0; ais_msg->sender.type = crm_msg_ais; ais_msg->sender.size = local_uname_len; memset(ais_msg->sender.uname, 0, MAX_NAME); memcpy(ais_msg->sender.uname, local_uname, ais_msg->sender.size); ais_msg->sender.id = local_nodeid; rc = send_client_ipc(conn, ais_msg); if(rc != 0) { ais_warn("Sending message to %s failed: %d", msg_type2text(type), rc); log_ais_message(LOG_DEBUG, ais_msg); return FALSE; } ais_free(ais_msg); return rc; } char * ais_concat(const char *prefix, const char *suffix, char join) { int len = 0; char *new_str = NULL; AIS_ASSERT(prefix != NULL); AIS_ASSERT(suffix != NULL); len = strlen(prefix) + strlen(suffix) + 2; ais_malloc0(new_str, (len)); sprintf(new_str, "%s%c%s", prefix, join, suffix); new_str[len-1] = 0; return new_str; } unsigned int config_find_init(plugin_init_type *config, char *name) { unsigned int local_handle = 0; #ifdef AIS_COROSYNC config->object_find_create(OBJECT_PARENT_HANDLE, name, strlen(name), &local_handle); #endif #ifdef AIS_WHITETANK config->object_find_reset (OBJECT_PARENT_HANDLE); #endif return local_handle; } unsigned int config_find_next(plugin_init_type *config, char *name, unsigned int top_handle) { int rc = 0; unsigned int local_handle = 0; #ifdef AIS_COROSYNC rc = config->object_find_next (top_handle, &local_handle); #endif #ifdef AIS_WHITETANK rc = config->object_find(OBJECT_PARENT_HANDLE, name, strlen (name), &local_handle); #endif if(rc < 0) { ais_info("No additional configuration supplied for: %s", name); local_handle = 0; } else { ais_info("Processing additional %s options...", name); } return local_handle; } void config_find_done(plugin_init_type *config, unsigned int local_handle) { #ifdef AIS_COROSYNC config->object_find_destroy (local_handle); #endif } int get_config_opt( plugin_init_type *config, unsigned int object_service_handle, char *key, char **value, const char *fallback) { char *env_key = NULL; *value = NULL; if(object_service_handle > 0) { config->object_key_get( object_service_handle, key, strlen(key), (void**)value, NULL); } if (*value) { ais_info("Found '%s' for option: %s", *value, key); return 0; } env_key = ais_concat("HA", key, '_'); *value = getenv(env_key); ais_free(env_key); if (*value) { ais_info("Found '%s' in ENV for option: %s", *value, key); return 0; } if(fallback) { ais_info("Defaulting to '%s' for option: %s", fallback, key); *value = ais_strdup(fallback); } else { ais_info("No default for option: %s", key); } return -1; } int ais_get_boolean(const char * value) { if(value == NULL) { return 0; } else if (strcasecmp(value, "true") == 0 || strcasecmp(value, "on") == 0 || strcasecmp(value, "yes") == 0 || strcasecmp(value, "y") == 0 || strcasecmp(value, "1") == 0){ return 1; } return 0; } long long ais_get_int(const char *text, char **end_text) { long long result = -1; char *local_end_text = NULL; errno = 0; if(text != NULL) { #ifdef ANSI_ONLY if(end_text != NULL) { result = strtol(text, end_text, 10); } else { result = strtol(text, &local_end_text, 10); } #else if(end_text != NULL) { result = strtoll(text, end_text, 10); } else { result = strtoll(text, &local_end_text, 10); } #endif if(errno == EINVAL) { ais_err("Conversion of %s failed", text); result = -1; } else if(errno == ERANGE) { ais_err("Conversion of %s was clipped: %lld", text, result); } else if(errno != 0) { ais_perror("Conversion of %s failed:", text); } if(local_end_text != NULL && local_end_text[0] != '\0') { ais_err("Characters left over after parsing '%s': '%s'", text, local_end_text); } } return result; } diff --git a/lib/common/ais.c b/lib/common/ais.c index fbfb4258f2..68ea66dd46 100644 --- a/lib/common/ais.c +++ b/lib/common/ais.c @@ -1,749 +1,749 @@ /* * Copyright (C) 2004 Andrew Beekhof * * 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 #include #include #include #include #include "stack.h" #include #include enum crm_ais_msg_types text2msg_type(const char *text) { int type = crm_msg_none; CRM_CHECK(text != NULL, return type); if(safe_str_eq(text, "ais")) { type = crm_msg_ais; } else if(safe_str_eq(text, "crm_plugin")) { type = crm_msg_ais; } else if(safe_str_eq(text, CRM_SYSTEM_CIB)) { type = crm_msg_cib; } else if(safe_str_eq(text, CRM_SYSTEM_CRMD)) { type = crm_msg_crmd; } else if(safe_str_eq(text, CRM_SYSTEM_DC)) { type = crm_msg_crmd; } else if(safe_str_eq(text, CRM_SYSTEM_TENGINE)) { type = crm_msg_te; } else if(safe_str_eq(text, CRM_SYSTEM_PENGINE)) { type = crm_msg_pe; } else if(safe_str_eq(text, CRM_SYSTEM_LRMD)) { type = crm_msg_lrmd; } else if(safe_str_eq(text, CRM_SYSTEM_STONITHD)) { type = crm_msg_stonithd; } else if(safe_str_eq(text, "attrd")) { type = crm_msg_attrd; } else { crm_debug_2("Unknown message type: %s", text); } return type; } char *get_ais_data(AIS_Message *msg) { int rc = BZ_OK; char *uncompressed = NULL; - unsigned int new_size = msg->size; + unsigned int new_size = msg->size + 1; if(msg->is_compressed == FALSE) { crm_debug_2("Returning uncompressed message data"); uncompressed = strdup(msg->data); } else { crm_debug_2("Decompressing message data"); crm_malloc0(uncompressed, new_size); rc = BZ2_bzBuffToBuffDecompress( uncompressed, &new_size, msg->data, msg->compressed_size, 1, 0); CRM_ASSERT(rc = BZ_OK); CRM_ASSERT(new_size == msg->size); } return uncompressed; } #if SUPPORT_AIS int ais_fd_sync = -1; int ais_fd_async = -1; /* never send messages via this channel */ void *ais_ipc_ctx = NULL; GFDSource *ais_source = NULL; GFDSource *ais_source_sync = NULL; gboolean get_ais_nodeid(uint32_t *id, char **uname) { struct iovec iov; int retries = 0; int rc = SA_AIS_OK; mar_res_header_t header; struct crm_ais_nodeid_resp_s answer; header.error = SA_AIS_OK; header.id = crm_class_nodeid; header.size = sizeof(mar_res_header_t); CRM_CHECK(id != NULL, return FALSE); CRM_CHECK(uname != NULL, return FALSE); iov.iov_base = &header; iov.iov_len = header.size; retry: errno = 0; #ifdef TRADITIONAL_AIS_IPC rc = saSendReceiveReply(ais_fd_sync, &header, header.size, &answer, sizeof (struct crm_ais_nodeid_resp_s)); #else rc = openais_msg_send_reply_receive( ais_ipc_ctx, &iov, 1, &answer, sizeof (answer)); #endif if(rc == SA_AIS_OK) { CRM_CHECK(answer.header.size == sizeof (struct crm_ais_nodeid_resp_s), crm_err("Odd message: id=%d, size=%d, error=%d", answer.header.id, answer.header.size, answer.header.error)); CRM_CHECK(answer.header.id == crm_class_nodeid, crm_err("Bad response id: %d", answer.header.id)); } if(rc == SA_AIS_ERR_TRY_AGAIN && retries < 20) { retries++; crm_info("Peer overloaded: Re-sending message (Attempt %d of 20)", retries); mssleep(retries * 100); /* Proportional back off */ goto retry; } if(rc != SA_AIS_OK) { crm_err("Sending nodeid request: FAILED (rc=%d): %s", rc, ais_error2text(rc)); return FALSE; } else if(answer.header.error != SA_AIS_OK) { crm_err("Bad response from peer: (rc=%d): %s", rc, ais_error2text(rc)); return FALSE; } crm_info("Server details: id=%u uname=%s", answer.id, answer.uname); *id = answer.id; *uname = crm_strdup(answer.uname); return TRUE; } gboolean send_ais_text(int class, const char *data, gboolean local, const char *node, enum crm_ais_msg_types dest) { static int msg_id = 0; static int local_pid = 0; int retries = 0; int rc = SA_AIS_OK; int buf_len = sizeof(mar_res_header_t); char *buf = NULL; struct iovec iov; mar_res_header_t *header; AIS_Message *ais_msg = NULL; enum crm_ais_msg_types sender = text2msg_type(crm_system_name); /* There are only 5 handlers registered to crm_lib_service in plugin.c */ CRM_CHECK(class < 5, crm_err("Invalid message class: %d", class); return FALSE); CRM_CHECK(data != NULL, return FALSE); if(local_pid == 0) { local_pid = getpid(); } crm_malloc0(ais_msg, sizeof(AIS_Message)); ais_msg->id = msg_id++; ais_msg->header.id = class; ais_msg->header.error = SA_AIS_OK; ais_msg->host.type = dest; ais_msg->host.local = local; if(node) { ais_msg->host.size = strlen(node); memset(ais_msg->host.uname, 0, MAX_NAME); memcpy(ais_msg->host.uname, node, ais_msg->host.size); ais_msg->host.id = 0; } else { ais_msg->host.size = 0; memset(ais_msg->host.uname, 0, MAX_NAME); ais_msg->host.id = 0; } ais_msg->sender.type = sender; ais_msg->sender.pid = local_pid; ais_msg->sender.size = 0; memset(ais_msg->sender.uname, 0, MAX_NAME); ais_msg->sender.id = 0; ais_msg->size = 1 + strlen(data); if(ais_msg->size < CRM_BZ2_THRESHOLD) { failback: crm_realloc(ais_msg, sizeof(AIS_Message) + ais_msg->size); memcpy(ais_msg->data, data, ais_msg->size); } else { char *compressed = NULL; char *uncompressed = crm_strdup(data); unsigned int len = (ais_msg->size * 1.1) + 600; /* recomended size */ crm_debug_5("Compressing message payload"); crm_malloc(compressed, len); rc = BZ2_bzBuffToBuffCompress( compressed, &len, uncompressed, ais_msg->size, CRM_BZ2_BLOCKS, 0, CRM_BZ2_WORK); crm_free(uncompressed); if(rc != BZ_OK) { crm_err("Compression failed: %d", rc); crm_free(compressed); goto failback; } crm_realloc(ais_msg, sizeof(AIS_Message) + len + 1); memcpy(ais_msg->data, compressed, len); ais_msg->data[len] = 0; crm_free(compressed); ais_msg->is_compressed = TRUE; ais_msg->compressed_size = len; crm_debug_2("Compression details: %d -> %d", ais_msg->size, ais_data_len(ais_msg)); } ais_msg->header.size = sizeof(AIS_Message) + ais_data_len(ais_msg); crm_debug_3("Sending%s message %d to %s.%s (data=%d, total=%d)", ais_msg->is_compressed?" compressed":"", ais_msg->id, ais_dest(&(ais_msg->host)), msg_type2text(dest), ais_data_len(ais_msg), ais_msg->header.size); iov.iov_base = ais_msg; iov.iov_len = ais_msg->header.size; retry: errno = 0; crm_realloc(buf, buf_len); #ifdef TRADITIONAL_AIS_IPC rc = saSendReceiveReply(ais_fd_sync, ais_msg, ais_msg->header.size, buf, buf_len); #else rc = openais_msg_send_reply_receive(ais_ipc_ctx, &iov, 1, buf, buf_len); #endif header = (mar_res_header_t *)buf; if(rc == SA_AIS_ERR_TRY_AGAIN && retries < 20) { retries++; crm_info("Peer overloaded: Re-sending message (Attempt %d of 20)", retries); mssleep(retries * 100); /* Proportional back off */ goto retry; } else if(rc == SA_AIS_OK) { CRM_CHECK_AND_STORE(header->size == sizeof (mar_res_header_t), crm_err("Odd message: id=%d, size=%d, class=%d, error=%d", header->id, header->size, class, header->error)); if(buf_len < header->size) { crm_err("Increasing buffer length to %d and retrying", header->size); buf_len = header->size + 1; goto retry; } else if(header->id == crm_class_nodeid && header->size == sizeof (struct crm_ais_nodeid_resp_s)){ struct crm_ais_nodeid_resp_s *answer = (struct crm_ais_nodeid_resp_s *)header; crm_err("Server details: id=%u uname=%s counter=%u", answer->id, answer->uname, answer->counter); } else { CRM_CHECK_AND_STORE(header->id == CRM_MESSAGE_IPC_ACK, crm_err("Bad response id (%d) for request (%d)", header->id, ais_msg->header.id)); CRM_CHECK(header->error == SA_AIS_OK, rc = header->error); } } if(rc != SA_AIS_OK) { crm_perror(LOG_ERR,"Sending message %d: FAILED (rc=%d): %s", ais_msg->id, rc, ais_error2text(rc)); ais_fd_async = -1; } else { crm_debug_4("Message %d: sent", ais_msg->id); } crm_free(buf); crm_free(ais_msg); return (rc == SA_AIS_OK); } gboolean send_ais_message(xmlNode *msg, gboolean local, const char *node, enum crm_ais_msg_types dest) { gboolean rc = TRUE; char *data = NULL; if(ais_fd_async < 0 || ais_source == NULL) { crm_err("Not connected to AIS"); return FALSE; } data = dump_xml_unformatted(msg); rc = send_ais_text(0, data, local, node, dest); crm_free(data); return rc; } void terminate_ais_connection(void) { #ifndef TRADITIONAL_AIS_IPC if(ais_ipc_ctx) { openais_service_disconnect(ais_ipc_ctx); } #else if(ais_fd_sync > 0) { close(ais_fd_sync); } if(ais_fd_async > 0) { close(ais_fd_async); } #endif crm_notice("Disconnected from AIS"); /* G_main_del_fd(ais_source); */ /* G_main_del_fd(ais_source_sync); */ } int ais_membership_timer = 0; gboolean ais_membership_force = FALSE; static gboolean ais_membership_dampen(gpointer data) { crm_debug_2("Requesting cluster membership after stabilization delay"); send_ais_text(crm_class_members, __FUNCTION__, TRUE, NULL, crm_msg_ais); ais_membership_force = TRUE; ais_membership_timer = 0; return FALSE; /* never repeat automatically */ } gboolean ais_dispatch(int sender, gpointer user_data) { char *data = NULL; char *uncompressed = NULL; int rc = SA_AIS_OK; AIS_Message *msg = NULL; gboolean (*dispatch)(AIS_Message*,char*,int) = user_data; #ifdef TRADITIONAL_AIS_IPC mar_res_header_t *header = NULL; static int header_len = sizeof(mar_res_header_t); crm_malloc0(header, header_len); errno = 0; rc = saRecvRetry(sender, header, header_len); if (rc != SA_AIS_OK) { crm_perror(LOG_ERR, "Receiving message header failed: (%d/%d) %s", rc, errno, ais_error2text(rc)); goto bail; } else if(header->size == header_len) { crm_err("Empty message: id=%d, size=%d, error=%d, header_len=%d", header->id, header->size, header->error, header_len); goto done; } else if(header->size == 0 || header->size < header_len) { crm_err("Mangled header: size=%d, header=%d, error=%d", header->size, header_len, header->error); goto done; } else if(header->error != SA_AIS_OK) { crm_err("Header contined error: %d", header->error); } crm_debug_2("Looking for %d (%d - %d) more bytes", header->size - header_len, header->size, header_len); crm_realloc(header, header->size); /* Use a char* so we can store the remainder into an offset */ data = (char*)header; errno = 0; rc = saRecvRetry(sender, data+header_len, header->size - header_len); #else crm_malloc0(data, 1000000); rc = openais_dispatch_recv (ais_ipc_ctx, data, 0); #endif msg = (AIS_Message*)data; if (rc != SA_AIS_OK) { crm_perror(LOG_ERR,"Receiving message body failed: (%d) %s", rc, ais_error2text(rc)); goto bail; } crm_debug_3("Got new%s message (size=%d, %d, %d)", msg->is_compressed?" compressed":"", ais_data_len(msg), msg->size, msg->compressed_size); data = msg->data; if(msg->is_compressed && msg->size > 0) { int rc = BZ_OK; - unsigned int new_size = msg->size; + unsigned int new_size = msg->size + 1; if(check_message_sanity(msg, NULL) == FALSE) { goto badmsg; } crm_debug_5("Decompressing message data"); crm_malloc0(uncompressed, new_size); rc = BZ2_bzBuffToBuffDecompress( uncompressed, &new_size, data, msg->compressed_size, 1, 0); if(rc != BZ_OK) { crm_err("Decompression failed: %d", rc); goto badmsg; } CRM_ASSERT(rc == BZ_OK); CRM_ASSERT(new_size == msg->size); data = uncompressed; } else if(check_message_sanity(msg, data) == FALSE) { goto badmsg; } else if(safe_str_eq("identify", data)) { int pid = getpid(); char *pid_s = crm_itoa(pid); send_ais_text(0, pid_s, TRUE, NULL, crm_msg_ais); crm_free(pid_s); goto done; } if(msg->header.id == crm_class_rmpeer) { uint32_t id = crm_int_helper(data, NULL); crm_info("Removing peer %s/%u", data, id); reap_crm_member(id); crm_calculate_quorum(); goto done; } if(msg->header.id == crm_class_members) { xmlNode *xml = string2xml(data); if(xml != NULL) { gboolean do_ask = FALSE; gboolean do_process = TRUE; int new_size = 0; unsigned long long seq = 0; int current_size = crm_active_members(); const char *reason = "unknown"; const char *value = crm_element_value(xml, "id"); seq = crm_int_helper(value, NULL); crm_debug_2("Received membership %llu", seq); xml_child_iter(xml, node, const char *state = crm_element_value(node, "state"); if(safe_str_eq(state, CRM_NODE_MEMBER)) { new_size++; } ); if(ais_membership_force) { /* always process */ crm_debug_2("Processing delayed membership change"); #if 0 } else if(current_size == 0 && new_size == 1) { do_ask = TRUE; do_process = FALSE; reason = "We've come up alone"; #endif } else if(new_size < (current_size/2)) { do_process = FALSE; reason = "We've lost more than half our peers"; if(ais_membership_timer == 0) { reason = "We've lost more than half our peers"; crm_log_xml_debug(xml, __PRETTY_FUNCTION__); do_ask = TRUE; } } if(do_process) { static long long last = 0; /* if there is a timer running - let it run * there is no harm in getting an extra membership message */ crm_peer_seq = seq; /* Skip resends */ if(last < seq) { crm_info("Processing membership %llu", seq); } /* crm_log_xml_debug(xml, __PRETTY_FUNCTION__); */ if(ais_membership_force) { ais_membership_force = FALSE; } xml_child_iter(xml, node, crm_update_ais_node(node, seq)); crm_calculate_quorum(); last = seq; } else if(do_ask) { dispatch = NULL; crm_warn("Pausing to allow membership stability (size %d -> %d): %s", current_size, new_size, reason); ais_membership_timer = Gmain_timeout_add(4*1000, ais_membership_dampen, NULL); /* process node additions */ xml_child_iter(xml, node, const char *state = crm_element_value(node, "state"); if(crm_str_eq(state, CRM_NODE_MEMBER, FALSE)) { crm_update_ais_node(node, seq); } ); } else { dispatch = NULL; crm_warn("Membership is still unstable (size %d -> %d): %s", current_size, new_size, reason); } } else { crm_warn("Invalid peer update: %s", data); } free_xml(xml); } else { const char *uuid = msg->sender.uname; crm_update_peer(msg->sender.id, 0,0,0,0, uuid, msg->sender.uname, NULL, NULL); } if(dispatch != NULL) { dispatch(msg, data, sender); } done: crm_free(uncompressed); crm_free(msg); return TRUE; badmsg: crm_err("Invalid message (id=%d, dest=%s:%s, from=%s:%s.%d):" " min=%d, total=%d, size=%d, bz2_size=%d", msg->id, ais_dest(&(msg->host)), msg_type2text(msg->host.type), ais_dest(&(msg->sender)), msg_type2text(msg->sender.type), msg->sender.pid, (int)sizeof(AIS_Message), msg->header.size, msg->size, msg->compressed_size); goto done; bail: crm_err("AIS connection failed"); return FALSE; } static void ais_destroy(gpointer user_data) { crm_err("AIS connection terminated"); ais_fd_sync = -1; exit(1); } gboolean init_ais_connection( gboolean (*dispatch)(AIS_Message*,char*,int), void (*destroy)(gpointer), char **our_uuid, char **our_uname, int *nodeid) { int pid = 0; int retries = 0; int rc = SA_AIS_OK; char *pid_s = NULL; struct utsname name; uint32_t local_nodeid = 0; char *local_uname = NULL; retry: crm_info("Creating connection to our AIS plugin"); #ifdef TRADITIONAL_AIS_IPC rc = saServiceConnect (&ais_fd_sync, &ais_fd_async, CRM_SERVICE); #else rc = openais_service_connect(CRM_SERVICE, &ais_ipc_ctx); if(ais_ipc_ctx) { ais_fd_async = openais_fd_get(ais_ipc_ctx); } else if(rc == SA_AIS_OK) { crm_err("No context created, but connection reported 'ok'"); rc = SA_AIS_ERR_LIBRARY; } #endif if (rc != SA_AIS_OK) { crm_info("Connection to our AIS plugin (%d) failed: %s (%d)", CRM_SERVICE, ais_error2text(rc), rc); } switch(rc) { case SA_AIS_OK: break; case SA_AIS_ERR_TRY_AGAIN: if(retries < 30) { sleep(1); retries++; goto retry; } crm_err("Retry count exceeded"); return FALSE; default: return FALSE; } if(destroy == NULL) { crm_debug("Using the default destroy handler"); destroy = ais_destroy; } crm_info("AIS connection established"); pid = getpid(); pid_s = crm_itoa(pid); send_ais_text(0, pid_s, TRUE, NULL, crm_msg_ais); crm_free(pid_s); crm_peer_init(); get_ais_nodeid(&local_nodeid, &local_uname); if(uname(&name) < 0) { crm_perror(LOG_ERR,"uname(2) call failed"); exit(100); } if(safe_str_neq(name.nodename, local_uname)) { crm_crit("Node name mismatch! OpenAIS supplied %s, our lookup returned %s", local_uname, name.nodename); crm_notice("Node name mismatches usually occur when assigned automatically by DHCP servers"); crm_notice("If this node was part of the cluster with a different name," " you will need to remove the old entry with crm_node --remove"); } if(our_uuid != NULL) { *our_uuid = crm_strdup(local_uname); } if(our_uname != NULL) { *our_uname = local_uname; } if(nodeid != NULL) { *nodeid = local_nodeid; } if(local_nodeid != 0) { /* Ensure the local node always exists */ crm_update_peer(local_nodeid, 0, 0, 0, 0, local_uname, local_uname, NULL, NULL); } if(dispatch) { ais_source = G_main_add_fd( G_PRIORITY_HIGH, ais_fd_async, FALSE, ais_dispatch, dispatch, destroy); } return TRUE; } gboolean check_message_sanity(AIS_Message *msg, char *data) { gboolean sane = TRUE; gboolean repaired = FALSE; int dest = msg->host.type; int tmp_size = msg->header.size - sizeof(AIS_Message); if(sane && msg->header.size == 0) { crm_warn("Message with no size"); sane = FALSE; } if(sane && msg->header.error != SA_AIS_OK) { crm_warn("Message header contains an error: %d", msg->header.error); sane = FALSE; } if(sane && ais_data_len(msg) != tmp_size) { int cur_size = ais_data_len(msg); repaired = TRUE; if(msg->is_compressed) { msg->compressed_size = tmp_size; } else { msg->size = tmp_size; } crm_warn("Repaired message payload size %d -> %d", cur_size, tmp_size); } if(sane && ais_data_len(msg) == 0) { crm_warn("Message with no payload"); sane = FALSE; } if(sane && data && msg->is_compressed == FALSE) { int str_size = strlen(data) + 1; if(ais_data_len(msg) != str_size) { int lpc = 0; crm_warn("Message payload is corrupted: expected %d bytes, got %d", ais_data_len(msg), str_size); sane = FALSE; for(lpc = (str_size - 10); lpc < msg->size; lpc++) { if(lpc < 0) { lpc = 0; } crm_debug("bad_data[%d]: %d / '%c'", lpc, data[lpc], data[lpc]); } } } if(sane == FALSE) { crm_err("Invalid message %d: (dest=%s:%s, from=%s:%s.%d, compressed=%d, size=%d, total=%d)", msg->id, ais_dest(&(msg->host)), msg_type2text(dest), ais_dest(&(msg->sender)), msg_type2text(msg->sender.type), msg->sender.pid, msg->is_compressed, ais_data_len(msg), msg->header.size); } else if(repaired) { crm_err("Repaired message %d: (dest=%s:%s, from=%s:%s.%d, compressed=%d, size=%d, total=%d)", msg->id, ais_dest(&(msg->host)), msg_type2text(dest), ais_dest(&(msg->sender)), msg_type2text(msg->sender.type), msg->sender.pid, msg->is_compressed, ais_data_len(msg), msg->header.size); } else { crm_debug_3("Verfied message %d: (dest=%s:%s, from=%s:%s.%d, compressed=%d, size=%d, total=%d)", msg->id, ais_dest(&(msg->host)), msg_type2text(dest), ais_dest(&(msg->sender)), msg_type2text(msg->sender.type), msg->sender.pid, msg->is_compressed, ais_data_len(msg), msg->header.size); } return sane; } #endif diff --git a/lib/common/xml.c b/lib/common/xml.c index c908d75828..23323c4de3 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -1,2702 +1,2709 @@ /* * Copyright (C) 2004 Andrew Beekhof * * 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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_BZLIB_H # include #endif #define XML_BUFFER_SIZE 4096 #define XML_PARSER_DEBUG 0 xmlDoc *getDocPtr(xmlNode *node); struct schema_s { int type; const char *name; const char *location; const char *transform; int after_transform; }; struct schema_s known_schemas[] = { /* 0 */ { 0, "none", NULL, NULL, 1 }, /* 1 */ { 1, "pacemaker-0.6", DTD_DIRECTORY"/crm.dtd", DTD_DIRECTORY"/upgrade06.xsl", 4 }, /* 2 */ { 1, "transitional-0.6", DTD_DIRECTORY"/crm-transitional.dtd", DTD_DIRECTORY"/upgrade06.xsl", 4 }, /* 3 */ { 2, "pacemaker-0.7", DTD_DIRECTORY"/pacemaker-1.0.rng", NULL, 0 }, /* 4 */ { 2, "pacemaker-1.0", DTD_DIRECTORY"/pacemaker-1.0.rng", NULL, 0 }, }; static const char *filter[] = { XML_ATTR_ORIGIN, XML_DIFF_MARKER, XML_CIB_ATTR_WRITTEN, }; static void add_ha_nocopy(HA_Message *parent, HA_Message *child, const char *field) { int next = parent->nfields; if (parent->nfields >= parent->nalloc && ha_msg_expand(parent) != HA_OK ) { crm_err("Parent expansion failed"); return; } parent->names[next] = crm_strdup(field); parent->nlens[next] = strlen(field); parent->values[next] = child; parent->vlens[next] = sizeof(HA_Message); parent->types[next] = FT_UNCOMPRESS; parent->nfields++; } int print_spaces(char *buffer, int spaces, int max); int log_data_element(const char *function, const char *prefix, int log_level, int depth, xmlNode *data, gboolean formatted); 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 write_file(const char *string, const char *filename); xmlNode * find_xml_node(xmlNode *root, const char * search_path, gboolean must_find) { const char *name = "NULL"; if(must_find || root != NULL) { crm_validate_data(root); } if(root != NULL) { name = crm_element_name(root); } if(search_path == NULL) { crm_warn("Will never find "); return NULL; } xml_child_iter_filter( root, a_child, search_path, /* crm_debug_5("returning node (%s).", crm_element_name(a_child)); */ crm_validate_data(a_child); return a_child; ); if(must_find) { crm_warn("Could not find %s in %s.", search_path, name); } else if(root != NULL) { crm_debug_3("Could not find %s in %s.", search_path, name); } else { crm_debug_3("Could not find %s in .", search_path); } return NULL; } xmlNode* find_entity(xmlNode *parent, const char *node_name, const char *id) { crm_validate_data(parent); xml_child_iter_filter( parent, a_child, node_name, if(id == NULL || crm_str_eq(id, ID(a_child), TRUE)) { crm_debug_4("returning node (%s).", crm_element_name(a_child)); return a_child; } ); crm_debug_3("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) { crm_validate_data(src); crm_validate_data(target); if(src == NULL) { crm_warn("No node to copy properties from"); } else if (target == NULL) { crm_err("No node to copy properties into"); } else { xml_prop_iter( src, local_prop_name, local_prop_value, expand_plus_plus(target, local_prop_name, local_prop_value) ); crm_validate_data(target); } return; } void fix_plus_plus_recursive(xmlNode* target) { xml_prop_iter(target, name, value, expand_plus_plus(target, name, value)); xml_child_iter(target, 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 = 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); return child; } int add_node_nocopy(xmlNode *parent, const char *name, xmlNode *child) { add_node_copy(parent, child); free_xml(child); return HA_OK; } const char * crm_xml_add(xmlNode* node, const char *name, const char *value) { 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_AND_STORE(old_value != value, crm_err("Cannot reset %s with crm_xml_add(%s)", name, value); return value); } #endif attr = xmlSetProp(node, (const xmlChar*)name, (const xmlChar*)value); 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) { 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_AND_STORE(old_value != value, return value); if (old_value != NULL && value == NULL) { xml_remove_prop(node, name); return NULL; } else if(value == NULL) { return NULL; } attr = xmlSetProp(node, (const xmlChar*)name, (const xmlChar*)value); 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); crm_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) { 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); } return node; } void free_xml_from_parent(xmlNode *parent, xmlNode *a_node) { CRM_CHECK(a_node != NULL, return); xmlUnlinkNode(a_node); xmlFreeNode(a_node); } 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); extern size_t strlcat(char * dest, const char *source, size_t len); int write_file(const char *string, const char *filename) { int rc = 0; FILE *file_output_strm = NULL; CRM_CHECK(filename != NULL, return -1); if (string == NULL) { crm_err("Cannot write NULL to %s", filename); return -1; } file_output_strm = fopen(filename, "w"); if(file_output_strm == NULL) { crm_perror(LOG_ERR,"Cannot open %s for writing", filename); return -1; } rc = fprintf(file_output_strm, "%s", string); if(rc < 0) { crm_perror(LOG_ERR,"Cannot write output to %s", filename); } if(fflush(file_output_strm) != 0) { crm_perror(LOG_ERR,"fflush for %s failed:", filename); rc = -1; } if(fsync(fileno(file_output_strm)) < 0) { crm_perror(LOG_ERR,"fsync for %s failed:", filename); rc = -1; } fclose(file_output_strm); return rc; } 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; va_start(args, msg); len = vasprintf(&buf, msg, args); 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); } buffer = NULL; buffer_len = 0; } else if(buffer == NULL) { buffer_len = len; buffer = buf; buf = NULL; } else { buffer_len += len; buffer = realloc(buffer, buffer_len); strlcat(buffer, buf, buffer_len); } va_end(args); if(buf) { 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__,__PRETTY_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_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); crm_warn("String start: %.50s", input); + crm_warn("String start+%d: %s", len-50, input+len-50); crm_abort(__FILE__,__PRETTY_FUNCTION__,__LINE__, "String parsing error", TRUE, TRUE); } } 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 { crm_realloc(xml_buffer, XML_BUFFER_SIZE + data_length + 1); 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"); return NULL; } xml_buffer[data_length] = '\0'; xml_obj = string2xml(xml_buffer); crm_free(xml_buffer); crm_log_xml_debug_3(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 ) { crm_realloc(buffer, XML_BUFFER_SIZE + length + 1); read_len = BZ2_bzRead ( &rc, bz_file, buffer + length, XML_BUFFER_SIZE); crm_debug_5("Read %ld bytes from file: %d", (long)read_len, rc); if ( rc == BZ_OK || rc == BZ_STREAM_END) { length += read_len; } } buffer[length] = '\0'; read_len = length; if ( rc != BZ_STREAM_END ) { crm_err("Couldnt read compressed xml from file"); crm_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; } xmlNode * filename2xml(const char *filename) { xmlNode *xml = NULL; xmlDocPtr output = NULL; 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 == NULL) { /* STDIN_FILENO == fileno(stdin) */ output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL, xml_options); } else if(strstr(filename, ".bz2") == NULL) { output = xmlCtxtReadFile(ctxt, filename, NULL, xml_options); } else { char *input = decompress_file(filename); output = xmlCtxtReadDoc(ctxt, (const xmlChar*)input, NULL, NULL, xml_options); crm_free(input); } if(output) { xml = xmlDocGetRootElement(output); } last_error = xmlCtxtGetLastError(ctxt); if(last_error && last_error->code != XML_ERR_OK) { /* crm_abort(__FILE__,__PRETTY_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; } int write_xml_file(xmlNode *xml_node, const char *filename, gboolean compress) { int res = 0; time_t now; char *buffer = NULL; char *now_str = NULL; unsigned int out = 0; FILE *file_output_strm = NULL; static mode_t cib_mode = S_IRUSR|S_IWUSR; CRM_CHECK(filename != NULL, return -1); crm_debug_3("Writing XML out to %s", filename); crm_validate_data(xml_node); if (xml_node == NULL) { crm_err("Cannot write NULL to %s", filename); return -1; } file_output_strm = fopen(filename, "w"); if(file_output_strm == NULL) { crm_perror(LOG_ERR,"Cannot open %s for writing", filename); return -1; } /* establish the correct permissions */ fchmod(fileno(file_output_strm), cib_mode); crm_log_xml_debug_4(xml_node, "Writing out"); now = time(NULL); now_str = ctime(&now); now_str[24] = EOS; /* replace the newline */ crm_xml_add(xml_node, XML_CIB_ATTR_WRITTEN, now_str); crm_validate_data(xml_node); 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, file_output_strm, 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_debug_2("%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(file_output_strm, "%s", buffer); if(res < 0) { crm_perror(LOG_ERR,"Cannot write output to %s", filename); goto bail; } } bail: if(fflush(file_output_strm) != 0) { crm_perror(LOG_ERR,"fflush for %s failed:", filename); res = -1; } if(fsync(fileno(file_output_strm)) < 0) { crm_perror(LOG_ERR,"fsync for %s failed:", filename); res = -1; } fclose(file_output_strm); crm_debug_3("Saved %d bytes to the Cib as XML", res); crm_free(buffer); return res; } void print_xml_formatted(int log_level, const char *function, xmlNode *msg, const char *text) { if(msg == NULL) { do_crm_log(log_level, "%s: %s: NULL", function, crm_str(text)); return; } crm_validate_data(msg); log_data_element(function, text, log_level, 0, msg, TRUE); return; } static HA_Message* convert_xml_message_struct(HA_Message *parent, xmlNode *src_node, const char *field) { xmlNode *child = NULL; xmlNode *__crm_xml_iter = src_node->children; xmlAttrPtr prop_iter = src_node->properties; const char *name = NULL; const char *value = NULL; HA_Message *result = ha_msg_new(3); ha_msg_add(result, F_XML_TAGNAME, (const char *)src_node->name); while(prop_iter != NULL) { name = (const char *)prop_iter->name; value = (const char *)xmlGetProp(src_node, prop_iter->name); prop_iter = prop_iter->next; ha_msg_add(result, name, value); } while(__crm_xml_iter != NULL) { child = __crm_xml_iter; __crm_xml_iter = __crm_xml_iter->next; convert_xml_message_struct(result, child, NULL); } if(parent == NULL) { return result; } if(field) { HA_Message *holder = holder = ha_msg_new(3); CRM_ASSERT(holder != NULL); ha_msg_add(holder, F_XML_TAGNAME, field); add_ha_nocopy(holder, result, (const char*)src_node->name); ha_msg_addstruct_compress(parent, field, holder); ha_msg_del(holder); } else { add_ha_nocopy(parent, result, (const char*)src_node->name); } return result; } static void convert_xml_child(HA_Message *msg, xmlNode *xml) { int orig = 0; int rc = BZ_OK; unsigned int len = 0; char *buffer = NULL; char *compressed = NULL; const char *name = NULL; name = (const char *)xml->name; buffer = dump_xml_unformatted(xml); orig = strlen(buffer); if(orig < CRM_BZ2_THRESHOLD) { ha_msg_add(msg, name, buffer); goto done; } len = (orig * 1.1) + 600; /* recomended size */ crm_malloc(compressed, len); rc = BZ2_bzBuffToBuffCompress(compressed, &len, buffer, orig, CRM_BZ2_BLOCKS, 0, CRM_BZ2_WORK); if(rc != BZ_OK) { crm_err("Compression failed: %d", rc); crm_free(compressed); convert_xml_message_struct(msg, xml, name); goto done; } crm_free(buffer); buffer = compressed; crm_debug_2("Compression details: %d -> %d", orig, len); ha_msg_addbin(msg, name, buffer, len); done: crm_free(buffer); # if 0 { unsigned int used = orig; char *uncompressed = NULL; crm_debug("Trying to decompress %d bytes", len); crm_malloc0(uncompressed, orig); rc = BZ2_bzBuffToBuffDecompress( uncompressed, &used, compressed, len, 1, 0); CRM_CHECK(rc == BZ_OK, ;); CRM_CHECK(used == orig, ;); crm_debug("rc=%d, used=%d", rc, used); if(rc != BZ_OK) { exit(100); } crm_debug("Original %s, decompressed %s", buffer, uncompressed); crm_free(uncompressed); } # endif } HA_Message* convert_xml_message(xmlNode *xml) { HA_Message *result = NULL; result = ha_msg_new(3); ha_msg_add(result, F_XML_TAGNAME, (const char *)xml->name); xml_prop_iter(xml, name, value, ha_msg_add(result, name, value)); xml_child_iter(xml, child, convert_xml_child(result, child)); return result; } static void convert_ha_field(xmlNode *parent, HA_Message *msg, int lpc) { int type = 0; const char *name = NULL; const char *value = NULL; xmlNode *xml = NULL; int rc = BZ_OK; size_t orig_len = 0; unsigned int used = 0; char *uncompressed = NULL; char *compressed = NULL; int size = orig_len * 10; CRM_CHECK(parent != NULL, return); CRM_CHECK(msg != NULL, return); name = msg->names[lpc]; type = cl_get_type(msg, name); switch(type) { case FT_COMPRESS: case FT_STRUCT: convert_ha_message(parent, msg->values[lpc], name); break; case FT_UNCOMPRESS: convert_ha_message(parent, cl_get_struct(msg, name), name); break; case FT_STRING: value = cl_get_string(msg, name); CRM_CHECK(value != NULL, return); crm_debug_5("Converting %s/%d/%s", name, type, value[0] == '<' ? "xml":"field"); if( value[0] != '<' ) { crm_xml_add(parent, name, value); break; } /* unpack xml string */ xml = string2xml(value); if(xml == NULL) { crm_err("Conversion of field '%s' failed", name); return; } add_node_nocopy(parent, NULL, xml); break; case FT_BINARY: value = cl_get_binary(msg, name, &orig_len); - size = orig_len * 10; + size = orig_len * 10 + 1; /* +1 because an exact 10x compression factor happens occasionally */ if(orig_len < 3 || value[0] != 'B' || value[1] != 'Z' || value[2] != 'h') { if(strstr(name, "uuid") == NULL) { crm_err("Skipping non-bzip binary field: %s", name); } return; } crm_malloc0(compressed, orig_len); memcpy(compressed, value, orig_len); crm_debug_2("Trying to decompress %d bytes", (int)orig_len); retry: crm_realloc(uncompressed, size); memset(uncompressed, 0, size); - used = size; + used = size - 1; /* always leave room for a trailing '\0' + * BZ2_bzBuffToBuffDecompress wont say anything if + * the uncompressed data is exactly 'size' bytes + */ rc = BZ2_bzBuffToBuffDecompress( uncompressed, &used, compressed, orig_len, 1, 0); if(rc == BZ_OUTBUFF_FULL) { size = size * 2; /* dont try to allocate more memory than we have */ if(size > 0) { goto retry; } } if(rc != BZ_OK) { crm_err("Decompression of %s (%d bytes) into %d failed: %d", name, (int)orig_len, size, rc); } else { + CRM_ASSERT(used < size); + CRM_CHECK(uncompressed[used] == 0, uncompressed[used] = 0); xml = string2xml(uncompressed); } if(xml != NULL) { add_node_copy(parent, xml); free_xml(xml); } crm_free(uncompressed); crm_free(compressed); break; } } xmlNode * convert_ha_message(xmlNode *parent, HA_Message *msg, const char *field) { int lpc = 0; xmlNode *child = NULL; const char *tag = NULL; CRM_CHECK(msg != NULL, crm_err("Empty message for %s", field); return parent); tag = cl_get_string(msg, F_XML_TAGNAME); if(tag == NULL) { tag = field; } if(parent == NULL) { parent = create_xml_node(NULL, tag); child = parent; } else { child = create_xml_node(parent, tag); } for (lpc = 0; lpc < msg->nfields; lpc++) { convert_ha_field(child, msg, lpc); } return parent; } xmlNode *convert_ipc_message(IPC_Message *msg, const char *field) { HA_Message *hmsg = wirefmt2msg((char *)msg->msg_body, msg->msg_len, 0); xmlNode *xml = convert_ha_message(NULL, hmsg, __FUNCTION__); crm_msg_del(hmsg); return xml; } xmlNode * get_message_xml(xmlNode *msg, const char *field) { xmlNode *tmp = first_named_child(msg, field); return first_named_child(tmp, NULL); } 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 * dump_xml(xmlNode *an_xml_node, gboolean formatted, gboolean for_digest) { int len = 0; char *buffer = NULL; xmlBuffer *xml_buffer = NULL; xmlDoc *doc = getDocPtr(an_xml_node); /* doc will only be NULL if an_xml_node is */ CRM_CHECK(doc != NULL, return NULL); xml_buffer = xmlBufferCreate(); CRM_ASSERT(xml_buffer != NULL); len = xmlNodeDump(xml_buffer, doc, an_xml_node, 0, formatted); if(len > 0) { if(for_digest) { /* for compatability with the old result which is used for digests */ len += 3; crm_malloc0(buffer, len); snprintf(buffer, len, " %s\n", (char *)xml_buffer->content); } else { buffer = crm_strdup((char *)xml_buffer->content); } } else { crm_err("Conversion failed"); } xmlBufferFree(xml_buffer); return buffer; } char * dump_xml_formatted(xmlNode *an_xml_node) { return dump_xml(an_xml_node, TRUE, FALSE); } char * dump_xml_unformatted(xmlNode *an_xml_node) { return dump_xml(an_xml_node, FALSE, FALSE); } #define update_buffer() do { \ if(printed < 0) { \ crm_perror(LOG_ERR,"snprintf failed"); \ goto print; \ } else if(printed >= (buffer_len - offset)) { \ crm_err("Output truncated: available=%d, needed=%d", buffer_len - offset, printed); \ offset += printed; \ goto print; \ } else if(offset >= buffer_len) { \ crm_err("Buffer exceeded"); \ offset += printed; \ goto print; \ } else { \ offset += printed; \ } \ } while(0) int print_spaces(char *buffer, int depth, int max) { int lpc = 0; int spaces = 2*depth; max--; /* <= so that we always print 1 space - prevents problems with syslog */ for(lpc = 0; lpc <= spaces && lpc < max; lpc++) { if(sprintf(buffer+lpc, "%c", ' ') < 1) { return -1; } } return lpc; } int log_data_element( const char *function, const char *prefix, int log_level, int depth, xmlNode *data, gboolean formatted) { int child_result = 0; int offset = 0; int printed = 0; char *buffer = NULL; int buffer_len = 1000; const char *name = NULL; const char *hidden = NULL; if(data == NULL) { crm_warn("No data to dump as XML"); return 0; } name = crm_element_name(data); CRM_ASSERT(name != NULL); crm_debug_5("Dumping %s", name); crm_malloc0(buffer, buffer_len); if(formatted) { offset = print_spaces(buffer, depth, buffer_len - offset); } printed = snprintf(buffer + offset, buffer_len - offset, "<%s", name); update_buffer(); hidden = crm_element_value(data, "hidden"); xml_prop_iter( data, prop_name, prop_value, if(prop_name == NULL || safe_str_eq(F_XML_TAGNAME, prop_name)) { continue; } else if(hidden != NULL && prop_name[0] != 0 && strstr(hidden, prop_name) != NULL) { prop_value = "*****"; } crm_debug_5("Dumping <%s %s=\"%s\"...", name, prop_name, prop_value); printed = snprintf(buffer + offset, buffer_len - offset, " %s=\"%s\"", prop_name, prop_value); update_buffer(); ); printed = snprintf(buffer + offset, buffer_len - offset, " %s>", xml_has_children(data)?"":"/"); update_buffer(); print: do_crm_log(log_level, "%s: %s%s", function, prefix?prefix:"", buffer); if(xml_has_children(data) == FALSE) { crm_free(buffer); return 0; } xml_child_iter( data, a_child, child_result = log_data_element( function, prefix, log_level, depth+1, a_child, formatted); ); if(formatted) { offset = print_spaces(buffer, depth, buffer_len); } do_crm_log(log_level, "%s: %s%s", function, prefix?prefix:"", buffer, name); crm_free(buffer); return 1; } gboolean xml_has_children(const xmlNode *xml_root) { if(xml_root != NULL && xml_root->children != NULL) { return TRUE; } return FALSE; } void xml_validate(const xmlNode *xml_root) { CRM_ASSERT(xml_root != NULL); } 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; } 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 = crm_strdup(value); } return value_copy; } void xml_remove_prop(xmlNode *obj, const char *name) { xmlUnsetProp(obj, (const xmlChar*)name); } void log_xml_diff(unsigned int log_level, xmlNode *diff, const char *function) { xmlNode *added = find_xml_node(diff, "diff-added", FALSE); xmlNode *removed = find_xml_node(diff, "diff-removed", FALSE); gboolean is_first = TRUE; if(crm_log_level < log_level) { /* nothing will ever be printed */ return; } xml_child_iter( removed, child, log_data_element(function, "-", log_level, 0, child, TRUE); if(is_first) { is_first = FALSE; } else { do_crm_log(log_level, " --- "); } ); is_first = TRUE; xml_child_iter( added, child, log_data_element(function, "+", log_level, 0, child, TRUE); if(is_first) { is_first = FALSE; } else { do_crm_log(log_level, " +++ "); } ); } void purge_diff_markers(xmlNode *a_node) { CRM_CHECK(a_node != NULL, return); xml_remove_prop(a_node, XML_DIFF_MARKER); xml_child_iter(a_node, child, purge_diff_markers(child); ); } gboolean apply_xml_diff(xmlNode *old, xmlNode *diff, xmlNode **new) { gboolean result = TRUE; const char *digest = crm_element_value(diff, XML_ATTR_DIGEST); xmlNode *added = find_xml_node(diff, "diff-added", FALSE); xmlNode *removed = find_xml_node(diff, "diff-removed", FALSE); int root_nodes_seen = 0; CRM_CHECK(new != NULL, return FALSE); crm_debug_2("Substraction Phase"); xml_child_iter(removed, child_diff, CRM_CHECK(root_nodes_seen == 0, result = FALSE); if(root_nodes_seen == 0) { *new = subtract_xml_object(old, child_diff, 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_debug_2("Addition Phase"); if(result) { xml_child_iter(added, child_diff, CRM_CHECK(root_nodes_seen == 0, result = FALSE); if(root_nodes_seen == 0) { add_xml_object(NULL, *new, 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); result = FALSE; } else if(result && digest) { char *new_digest = calculate_xml_digest(*new, FALSE, TRUE); if(safe_str_neq(new_digest, digest)) { crm_info("Digest mis-match: expected %s, calculated %s", digest, new_digest); result = FALSE; } else { crm_debug_2("Digest matched: expected %s, calculated %s", digest, new_digest); } crm_free(new_digest); } else if(result) { int lpc = 0; xmlNode *intermediate = NULL; xmlNode *diff_of_diff = NULL; xmlNode *calc_added = NULL; xmlNode *calc_removed = NULL; const char *value = NULL; const char *name = NULL; const char *version_attrs[] = { XML_ATTR_NUMUPDATES, XML_ATTR_GENERATION, XML_ATTR_GENERATION_ADMIN }; crm_debug_2("Verification Phase"); intermediate = diff_xml_object(old, *new, FALSE); calc_added = find_xml_node(intermediate, "diff-added", FALSE); calc_removed = find_xml_node(intermediate, "diff-removed", FALSE); /* add any version details to the diff so they match */ for(lpc = 0; lpc < DIMOF(version_attrs); lpc++) { name = version_attrs[lpc]; value = crm_element_value(added, name); crm_xml_add(calc_added, name, value); value = crm_element_value(removed, name); crm_xml_add(calc_removed, name, value); } diff_of_diff = diff_xml_object(intermediate, diff, TRUE); if(diff_of_diff != NULL) { crm_info("Diff application failed!"); crm_log_xml_debug(old, "diff:original"); crm_log_xml_debug(diff, "diff:input"); result = FALSE; } free_xml(diff_of_diff); free_xml(intermediate); diff_of_diff = NULL; intermediate = NULL; } if(result) { purge_diff_markers(*new); } return result; } xmlNode * diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress) { xmlNode *diff = NULL; xmlNode *tmp1 = NULL; xmlNode *added = NULL; xmlNode *removed = NULL; tmp1 = subtract_xml_object(old, new, "removed:top"); if(tmp1 != NULL) { if(suppress && can_prune_leaf(tmp1)) { free_xml(tmp1); } else { diff = create_xml_node(NULL, "diff"); removed = create_xml_node(diff, "diff-removed"); added = create_xml_node(diff, "diff-added"); add_node_nocopy(removed, NULL, tmp1); } } tmp1 = subtract_xml_object(new, old, "added:top"); if(tmp1 != NULL) { if(suppress && can_prune_leaf(tmp1)) { free_xml(tmp1); return diff; } if(diff == NULL) { diff = create_xml_node(NULL, "diff"); } if(removed == NULL) { removed = create_xml_node(diff, "diff-removed"); } if(added == NULL) { added = create_xml_node(diff, "diff-added"); } add_node_nocopy(added, NULL, tmp1); } return diff; } gboolean can_prune_leaf(xmlNode *xml_node) { gboolean can_prune = TRUE; /* return FALSE; */ xml_prop_iter(xml_node, prop_name, prop_value, if(safe_str_eq(prop_name, XML_ATTR_ID)) { continue; } can_prune = FALSE; ); xml_child_iter(xml_node, child, 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 *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); xml_prop_iter(xml_node, prop_name, prop_value, lower_bound = context; crm_xml_add(us, prop_name, prop_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; } } xml_child_iter(us, 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) { gboolean has_attributes = FALSE; if(context == 0) { return 0; } xml_prop_iter(xml_node, prop_name, prop_value, has_attributes = TRUE; break; ); if(has_attributes) { return depth; } else if(depth < context) { xml_child_iter(xml_node, child, if(in_upper_context(depth+1, context, child)) { return depth; } ); } return 0; } xmlNode * subtract_xml_object(xmlNode *left, xmlNode *right, const char *marker) { gboolean skip = FALSE; gboolean differences = FALSE; xmlNode *diff = NULL; xmlNode *child_diff = NULL; xmlNode *right_child = 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(left == NULL) { return NULL; } id = ID(left); if(right == NULL) { xmlNode *deleted = NULL; crm_debug_5("Processing <%s id=%s> (complete copy)", crm_element_name(left), id); deleted = copy_xml(left); crm_xml_add(deleted, XML_DIFF_MARKER, marker); return deleted; } name = crm_element_name(left); CRM_CHECK(name != NULL, return NULL); diff = create_xml_node(NULL, name); /* changes to name/value pairs */ xml_prop_iter(left, prop_name, left_value, if(crm_str_eq(prop_name, XML_ATTR_ID, TRUE)) { continue; } skip = FALSE; for(lpc = 0; skip == FALSE && lpc < filter_len; lpc++){ if(crm_str_eq(prop_name, filter[lpc], TRUE)) { skip = TRUE; } } if(skip) { continue; } right_val = crm_element_value(right, prop_name); if(right_val == NULL) { /* new */ differences = TRUE; crm_xml_add(diff, prop_name, left_value); } else if(strcmp(left_value, right_val) == 0) { /* unchanged */ } else { /* changed */ differences = TRUE; crm_xml_add(diff, prop_name, left_value); } ); /* changes to child objects */ xml_child_iter( left, left_child, right_child = find_entity( right, crm_element_name(left_child), ID(left_child)); child_diff = subtract_xml_object( left_child, right_child, marker); if(child_diff != NULL) { differences = TRUE; add_node_nocopy(diff, NULL, child_diff); } ); if(differences == FALSE) { /* check for XML_DIFF_MARKER in a child */ xml_child_iter( right, right_child, value = crm_element_value(right_child, XML_DIFF_MARKER); if(value != NULL && safe_str_eq(value, "removed:top")) { crm_debug_3("Found the root of the deletion: %s", name); differences = TRUE; break; } ); } if(differences == FALSE) { free_xml(diff); crm_debug_5("\tNo changes to <%s id=%s>", crm_str(name), id); return NULL; } crm_xml_add(diff, XML_ATTR_ID, id); return diff; } int add_xml_object(xmlNode *parent, xmlNode *target, xmlNode *update) { const char *object_id = NULL; const char *object_name = NULL; #if XML_PARSE_DEBUG crm_log_xml(LOG_DEBUG_5, "update:", update); crm_log_xml(LOG_DEBUG_5, "target:", target); #endif CRM_CHECK(update != NULL, return 0); 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_debug_2("Added <%s%s%s/>", crm_str(object_name), object_id?" id=":"", object_id?object_id:""); } else { crm_debug_3("Found node <%s%s%s/> to update", crm_str(object_name), object_id?" id=":"", object_id?object_id:""); #endif } copy_in_properties(target, update); xml_child_iter( update, a_child, #if XML_PARSER_DEBUG crm_debug_4("Updating child <%s id=%s>", crm_element_name(a_child), ID(a_child)); #endif add_xml_object(target, NULL, a_child); ); #if XML_PARSER_DEBUG crm_debug_3("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; 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_debug_2(child, "Update match found..."); #endif add_xml_object(NULL, child, to_update); } xml_child_iter( child, 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) { xml_child_iter( root, 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; 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 || safe_str_eq(child_id, up_id)) { can_delete = TRUE; } if(safe_str_neq(crm_element_name(update), crm_element_name(child))) { can_delete = FALSE; } if(can_delete && delete_only) { xml_prop_iter(update, prop_name, left_value, right_val = crm_element_value(child, prop_name); if(safe_str_neq(left_value, right_val)) { can_delete = FALSE; } ); } if(can_delete && parent != NULL) { crm_log_xml_debug_4(child, "Delete match found..."); if(delete_only || update == NULL) { free_xml_from_parent(NULL, child); } else { xmlNode *tmp = copy_xml(update); xmlNode *old = xmlReplaceNode(child, tmp); free_xml_from_parent(NULL, old); } child = NULL; return TRUE; } else if(can_delete) { crm_log_xml_debug(child, "Cannot delete the search root"); can_delete = FALSE; } xml_child_iter( child, child_of_child, /* only delete the first one */ if(can_delete) { break; } can_delete = replace_xml_child(child, child_of_child, update, delete_only); ); 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_debug_3("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_debug_3("dumped: %s=%s", name, s_value); } else { crm_debug_2("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); crm_debug_3("dumped: %s=%s", name, s_value); } else { crm_debug_2("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; } crm_name = crm_meta_name(key); hash2field(crm_name, value, user_data); crm_free(crm_name); } GHashTable * xml2list(xmlNode *parent) { xmlNode *nvpair_list = NULL; GHashTable *nvpair_hash = g_hash_table_new_full( g_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_debug_2("No attributes in %s", crm_element_name(parent)); crm_log_xml_debug_2( parent,"No attributes for resource op"); } crm_log_xml_debug_3(nvpair_list, "Unpacking"); xml_prop_iter( nvpair_list, key, value, crm_debug_4("Added %s=%s", key, value); g_hash_table_insert( nvpair_hash, crm_strdup(key), crm_strdup(value)); ); xml_child_iter_filter( nvpair_list, child, XML_TAG_PARAM, const char *key = crm_element_value(child, XML_NVPAIR_ATTR_NAME); const char *value = crm_element_value(child, XML_NVPAIR_ATTR_VALUE); crm_debug_4("Added %s=%s", key, value); if(key != NULL && value != NULL) { g_hash_table_insert(nvpair_hash, crm_strdup(key), crm_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) { GListPtr sorted = NULL; GListPtr unsorted = NULL; name_value_t *pair = NULL; xmlNode *result = NULL; const char *name = crm_element_name(input); CRM_CHECK(input != NULL, return NULL); name = crm_element_name(input); CRM_CHECK(name != NULL, return NULL); result = create_xml_node(parent, name); xml_prop_iter(input, p_name, p_value, crm_malloc0(pair, 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); slist_destroy(name_value_t, child, sorted, crm_free(child)); if(recursive) { xml_child_iter(input, child, sorted_xml(child, result, recursive)); } else { xml_child_iter(input, child, add_node_copy(result, child)); } return result; } static void filter_xml(xmlNode *data, const char **filter, int filter_len, gboolean recursive) { int lpc = 0; for(lpc = 0; lpc < filter_len; lpc++) { xml_remove_prop(data, filter[lpc]); } if(recursive == FALSE) { return; } xml_child_iter(data, child, filter_xml(child, filter, filter_len, recursive)); } /* "c048eae664dba840e1d2060f00299e9d" */ char * calculate_xml_digest(xmlNode *input, gboolean sort, gboolean do_filter) { int i = 0; int digest_len = 16; char *digest = NULL; unsigned char *raw_digest = NULL; xmlNode *sorted = NULL; char *buffer = NULL; size_t buffer_len = 0; if(sort || do_filter) { sorted = sorted_xml(input, NULL, TRUE); } else { sorted = copy_xml(input); } if(do_filter) { filter_xml(sorted, filter, DIMOF(filter), TRUE); } buffer = dump_xml(sorted, FALSE, TRUE); buffer_len = strlen(buffer); CRM_CHECK(buffer != NULL && buffer_len > 0, free_xml(sorted); crm_free(buffer); return NULL); crm_malloc(digest, (2 * digest_len + 1)); crm_malloc(raw_digest, (digest_len + 1)); MD5((unsigned char *)buffer, buffer_len, raw_digest); for(i = 0; i < digest_len; i++) { sprintf(digest+(2*i), "%02x", raw_digest[i]); } digest[(2*digest_len)] = 0; crm_debug_2("Digest %s: %s\n", digest, buffer); crm_log_xml(LOG_DEBUG_3, "digest:source", sorted); crm_free(buffer); crm_free(raw_digest); free_xml(sorted); return digest; } #if HAVE_LIBXML2 # include # include # include # include # include #endif 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); CRM_CHECK(dtd != NULL, crm_err("Could not find/parse %s", dtd_file); goto cleanup); cvp = xmlNewValidCtxt(); CRM_CHECK(cvp != NULL, goto cleanup); if(to_logs) { cvp->userData = (void *) LOG_ERR; cvp->error = (xmlValidityErrorFunc) cl_log; cvp->warning = (xmlValidityWarningFunc) cl_log; } else { cvp->userData = (void *) stderr; cvp->error = (xmlValidityErrorFunc) fprintf; cvp->warning = (xmlValidityWarningFunc) fprintf; } if (!xmlValidateDtd(cvp, doc, dtd)) { valid = FALSE; } cleanup: if(cvp) { xmlFreeValidCtxt(cvp); } if(dtd) { xmlFreeDtd(dtd); } return valid; } xmlNode *first_named_child(xmlNode *parent, const char *name) { xml_child_iter_filter(parent, match, name, 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) { gboolean valid = TRUE; int rc = 0; xmlRelaxNGPtr rng = NULL; xmlRelaxNGValidCtxtPtr valid_ctx = NULL; xmlRelaxNGParserCtxtPtr parser_ctx = NULL; CRM_CHECK(doc != NULL, return FALSE); CRM_CHECK(relaxng_file != NULL, return FALSE); xmlLoadExtDtdDefaultValue = 1; parser_ctx = xmlRelaxNGNewParserCtxt(relaxng_file); CRM_CHECK(parser_ctx != NULL, goto cleanup); if(to_logs) { xmlRelaxNGSetParserErrors(parser_ctx, (xmlRelaxNGValidityErrorFunc) cl_log, (xmlRelaxNGValidityWarningFunc) cl_log, GUINT_TO_POINTER(LOG_ERR)); } else { xmlRelaxNGSetParserErrors(parser_ctx, (xmlRelaxNGValidityErrorFunc) fprintf, (xmlRelaxNGValidityWarningFunc) fprintf, stderr); } rng = xmlRelaxNGParse(parser_ctx); CRM_CHECK(rng != NULL, crm_err("Could not find/parse %s", relaxng_file); goto cleanup); valid_ctx = xmlRelaxNGNewValidCtxt(rng); CRM_CHECK(valid_ctx != NULL, goto cleanup); if(to_logs) { xmlRelaxNGSetValidErrors(valid_ctx, (xmlRelaxNGValidityErrorFunc) cl_log, (xmlRelaxNGValidityWarningFunc) cl_log, GUINT_TO_POINTER(LOG_ERR)); } else { xmlRelaxNGSetValidErrors(valid_ctx, (xmlRelaxNGValidityErrorFunc) fprintf, (xmlRelaxNGValidityWarningFunc) fprintf, stderr); } /* xmlRelaxNGSetValidStructuredErrors( */ /* valid_ctx, relaxng_invalid_stderr, valid_ctx); */ xmlLineNumbersDefault(1); rc = xmlRelaxNGValidateDoc(valid_ctx, doc); if (rc > 0) { valid = FALSE; } else if (rc < 0) { crm_err("Internal libxml error during validation\n"); } cleanup: if(parser_ctx != NULL) { xmlRelaxNGFreeParserCtxt(parser_ctx); xmlCleanupParser(); } if(valid_ctx != NULL) { xmlRelaxNGFreeValidCtxt(valid_ctx); } if (rng != NULL) { xmlRelaxNGFree(rng); } return valid; } static int max_schemas = DIMOF(known_schemas); static gboolean validate_with(xmlNode *xml, int method, gboolean to_logs) { xmlDocPtr doc = NULL; gboolean valid = FALSE; int type = known_schemas[method].type; const char *file = known_schemas[method].location; CRM_CHECK(xml != NULL, return FALSE); doc = getDocPtr(xml); crm_debug_2("Validating with: %s (type=%d)", crm_str(file), type); switch(type) { case 0: valid = TRUE; break; case 1: valid = validate_with_dtd(doc, to_logs, file); break; case 2: valid = validate_with_relaxng(doc, to_logs, file); break; default: crm_err("Unknown validator type: %d", type); break; } return valid; } #include 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) { xmlDoc *doc = NULL; xmlNode *xml = NULL; gboolean rc = FALSE; char *filename = NULL; static char *template = NULL; if(template == NULL) { template = crm_strdup(HA_VARRUNDIR"/shadow.XXXXXX"); } filename = mktemp(template); write_xml_file(xml_blob, filename, FALSE); dump_file(filename); doc = xmlParseFile(filename); xml = xmlDocGetRootElement(doc); rc = validate_xml(xml, NULL, FALSE); free_xml(xml); return rc; } gboolean validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs) { int lpc = 0; if(validation == NULL) { validation = crm_element_value(xml_blob, XML_ATTR_VALIDATION); } if(validation == NULL) { validation = crm_element_value(xml_blob, "ignore-dtd"); if(crm_is_true(validation)) { validation = "none"; } else { validation = "transitional-0.6"; } } if(safe_str_eq(validation, "none")) { return TRUE; } for(; lpc < max_schemas; lpc++) { if(safe_str_eq(validation, known_schemas[lpc].name)) { return validate_with(xml_blob, lpc, to_logs); } } crm_err("Unknown validator: %s", validation); return FALSE; } static xmlNode *apply_transformation(xmlNode *xml, const char *transform) { xmlNode *out = NULL; xmlDocPtr res = NULL; xmlDocPtr doc = NULL; xsltStylesheet *xslt = NULL; CRM_CHECK(xml != NULL, return FALSE); doc = getDocPtr(xml); xmlLoadExtDtdDefaultValue = 1; xmlSubstituteEntitiesDefault(1); xslt = xsltParseStylesheetFile((const xmlChar *)transform); 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); } xsltCleanupGlobals(); xmlCleanupParser(); return out; } const char *get_schema_name(int version) { if(version < 0 || version >= max_schemas) { return "unknown"; } return known_schemas[version].name; } int get_schema_version(const char *name) { int lpc = 0; for(; lpc < max_schemas; lpc++) { if(safe_str_eq(name, known_schemas[lpc].name)) { return lpc; } } return -1; } /* set which validation to use */ #include int update_validation( xmlNode **xml_blob, int *best, gboolean transform, gboolean to_logs) { xmlNode *xml = NULL; char *value = NULL; int lpc = 0, match = -1, rc = cib_ok; CRM_CHECK(best != NULL, return cib_invalid_argument); CRM_CHECK(xml_blob != NULL, return cib_invalid_argument); CRM_CHECK(*xml_blob != NULL, return cib_invalid_argument); *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_schemas - 1)) { /* nothing to do */ crm_free(value); *best = match; return cib_ok; } for(; lpc < max_schemas; lpc++) { gboolean valid = TRUE; crm_debug("Testing '%s' validation", known_schemas[lpc].name); valid = validate_with(xml, lpc, to_logs); if(valid) { *best = lpc; } if(valid && transform && known_schemas[lpc].transform != NULL) { xmlNode *upgrade = NULL; int next = known_schemas[lpc].after_transform; if(next <= 0) { next = lpc+1; } crm_notice("Upgrading %s-style configuration to %s with %s", known_schemas[lpc].name, known_schemas[lpc+1].name, known_schemas[lpc].transform); upgrade = apply_transformation(xml, known_schemas[lpc].transform); if(upgrade == NULL) { crm_err("Transformation %s failed", known_schemas[lpc].transform); rc = cib_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 = cib_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 = cib_dtd_validation; } } } if(*best > match) { crm_notice("Upgraded from %s to %s validation", value?value:"", known_schemas[*best].name); crm_xml_add(xml, XML_ATTR_VALIDATION, known_schemas[*best].name); } *xml_blob = xml; crm_free(value); return rc; } xmlNode * getXpathResult(xmlXPathObjectPtr xpathObj, int index) { xmlNode *match = NULL; CRM_CHECK(index >= 0, return NULL); CRM_CHECK(xpathObj != NULL, return NULL); if(index >= xpathObj->nodesetval->nodeNr) { crm_err("Requested index %d of only %d items", index, xpathObj->nodesetval->nodeNr); return NULL; } match = xpathObj->nodesetval->nodeTab[index]; CRM_CHECK(match != NULL, return NULL); if(match->type == XML_DOCUMENT_NODE) { /* Will happen if section = '/' */ match = match->children; } return match; } /* the caller needs to check if the result contains a xmlDocPtr or xmlNodePtr */ xmlXPathObjectPtr xpath_search(xmlNode *xml_top, const char *path) { xmlDocPtr doc = NULL; xmlXPathObjectPtr xpathObj = NULL; xmlXPathContextPtr xpathCtx = NULL; const xmlChar *xpathExpr = (const xmlChar *)path; CRM_CHECK(path != NULL, return NULL); CRM_CHECK(xml_top != NULL, return NULL); CRM_CHECK(strlen(path) > 0, return NULL); doc = getDocPtr(xml_top); crm_debug_2("Evaluating: %s", path); xpathCtx = xmlXPathNewContext(doc); CRM_ASSERT(xpathCtx != NULL); xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx); xmlXPathFreeContext(xpathCtx); return xpathObj; } gboolean cli_config_update(xmlNode **xml, int *best_version) { gboolean rc = TRUE; const char *value = crm_element_value(*xml, XML_ATTR_VALIDATION); int min_version = get_schema_version(MINIMUM_SCHEMA_VERSION); int max_version = get_schema_version(LATEST_SCHEMA_VERSION); int version = get_schema_version(value); if(version < max_version) { xmlNode *converted = NULL; converted = copy_xml(*xml); update_validation(&converted, &version, TRUE, FALSE); if(best_version) { *best_version = version; } value = crm_element_value(converted, XML_ATTR_VALIDATION); if(version < min_version) { fprintf(stderr, "Your current configuration could only be upgraded to %s... " "the minimum requirement is %s.\n", crm_str(value), MINIMUM_SCHEMA_VERSION); free_xml(converted); converted = NULL; rc = FALSE; } else { free_xml(*xml); *xml = converted; if(version < max_version) { crm_config_warn("Your configuration was internally updated to %s... " "which is acceptable but not the most recent", get_schema_name(version)); } else { crm_config_warn("Your configuration was internally updated to the latest version (%s)", get_schema_name(version)); } } } return rc; } xmlNode *expand_idref(xmlNode *input) { const char *tag = NULL; const char *ref = NULL; xmlNode *result = input; if(result == NULL) { return NULL; } tag = crm_element_name(result); ref = crm_element_value(result, XML_ATTR_IDREF); if(ref != NULL) { char *xpath_string = NULL; int xpath_max = 512, offset = 0; crm_malloc0(xpath_string, xpath_max); offset += snprintf(xpath_string + offset, xpath_max - offset, "//%s[@id='%s']", tag, ref); result = get_xpath_object(xpath_string, input, LOG_ERR); crm_free(xpath_string); } return result; } xmlNode* get_xpath_object_relative(const char *xpath, xmlNode *xml_obj, int error_level) { int len = 0; xmlNode *result = NULL; char *xpath_full = NULL; const char *xpath_prefix = NULL; if(xml_obj == NULL || xpath == NULL) { return NULL; } xpath_prefix = (const char *)xmlGetNodePath(xml_obj); len += strlen(xpath_prefix); len += strlen(xpath); xpath_full = crm_strdup(xpath_prefix); crm_realloc(xpath_full, len+1); strncat(xpath_full, xpath, len); result = get_xpath_object(xpath_full, xml_obj, error_level); crm_free(xpath_full); return result; } xmlNode* get_xpath_object(const char *xpath, xmlNode *xml_obj, int error_level) { xmlNode *result = NULL; xmlXPathObjectPtr xpathObj = NULL; if(xpath == NULL) { return xml_obj; /* or return NULL? */ } xpathObj = xpath_search(xml_obj, xpath); if(xpathObj == NULL || xpathObj->nodesetval == NULL || xpathObj->nodesetval->nodeNr < 1) { do_crm_log(error_level, "No match for %s in %s", xpath, xmlGetNodePath(xml_obj)); crm_log_xml(LOG_DEBUG_2, "Bad Input", xml_obj); } else if(xpathObj->nodesetval->nodeNr > 1) { int lpc = 0, max = xpathObj->nodesetval->nodeNr; do_crm_log(error_level, "Too many matches for %s in %s", xpath, xmlGetNodePath(xml_obj)); for(lpc = 0; lpc < max; lpc++) { xmlNode *match = getXpathResult(xpathObj, lpc); CRM_CHECK(match != NULL, continue); do_crm_log(error_level, "%s[%d] = %s", xpath, lpc, xmlGetNodePath(match)); } crm_log_xml(LOG_DEBUG_2, "Bad Input", xml_obj); } else { result = getXpathResult(xpathObj, 0); } if(xpathObj) { xmlXPathFreeObject(xpathObj); } 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:""); 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; }