diff --git a/lib/cib/cib_acl.c b/lib/cib/cib_acl.c index 3edba15c5a..86bc498783 100644 --- a/lib/cib/cib_acl.c +++ b/lib/cib/cib_acl.c @@ -1,792 +1,802 @@ /* * Copyright (C) 2009 Yan Gao * * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include typedef struct acl_obj_s { const char *mode; const char *tag; const char *ref; const char *xpath; const char *attribute; } acl_obj_t; typedef struct xml_perm_s { const char *mode; GHashTable *attribute_perms; } xml_perm_t; static gboolean req_by_privileged(xmlNode *request); static xmlNode *diff_xml_object_orig(xmlNode *old, xmlNode *new, gboolean suppress, xmlNode *new_diff); static gboolean unpack_user_acl(xmlNode *xml_acls, const char *user, GListPtr *user_acl); static gboolean user_match(const char *user, const char *uid); static gboolean unpack_acl(xmlNode *xml_acls, xmlNode *xml_acl, GListPtr *acl); static gboolean unpack_role_acl(xmlNode *xml_acls, const char *role, GListPtr *acl); static gboolean acl_append(xmlNode *acl_child, GListPtr *acl); static void free_acl(GListPtr acl); static gboolean parse_acl_xpath(xmlNode *xml, GListPtr acl, GListPtr *parsed_acl); static gboolean gen_xml_perms(xmlNode *xml, GListPtr acl, GHashTable **xml_perms); static int search_xml_children(GListPtr *children, xmlNode *root, const char *tag, const char *field, const char *value, gboolean search_matches); static int search_xpath_objects(GListPtr *objects, xmlNode *xml_obj, const char *xpath); static gboolean update_xml_perms(xmlNode *xml, acl_obj_t *acl_obj, GHashTable *xml_perms); static gboolean update_xml_children_perms(xmlNode *xml, const char *mode, GHashTable *xml_perms); static void free_xml_perm(gpointer xml_perm); static gboolean acl_filter_xml(xmlNode *xml, GHashTable *xml_perms); static gboolean acl_check_diff_xml(xmlNode *xml, GHashTable *xml_perms); gboolean acl_enabled(GHashTable *config_hash) { const char *value = NULL; gboolean rc = FALSE; value = cib_pref(config_hash, "enable-acl"); rc = crm_is_true(value); crm_debug("CIB ACL is %s", rc?"enabled":"disabled"); return rc; } /* rc = TRUE if orig_cib has been filtered*/ /* That means *filtered_cib rather than orig_cib should be exploited afterwards*/ gboolean acl_filter_cib(xmlNode *request, xmlNode *current_cib, xmlNode *orig_cib, xmlNode **filtered_cib) { const char *user = NULL; xmlNode *xml_acls = NULL; xmlNode *tmp_cib = NULL; GListPtr user_acl = NULL; GHashTable *xml_perms = NULL; *filtered_cib = NULL; if (req_by_privileged(request)) { return FALSE; } if (orig_cib == NULL) { return FALSE; } if (current_cib == NULL) { return TRUE; } xml_acls = get_object_root(XML_CIB_TAG_ACLS, current_cib); if (xml_acls == NULL) { crm_warn("Ordinary users cannot access the CIB without any defined ACLs: '%s'", user); return TRUE; } user = crm_element_value(request, F_CIB_USER); unpack_user_acl(xml_acls, user, &user_acl); tmp_cib = copy_xml(orig_cib); gen_xml_perms(tmp_cib, user_acl, &xml_perms); if (acl_filter_xml(tmp_cib, xml_perms)) { crm_warn("User '%s' doesn't have the permission for the whole CIB", user); tmp_cib = NULL; } g_hash_table_destroy(xml_perms); free_acl(user_acl); *filtered_cib = tmp_cib; return TRUE; } /* rc = TRUE if the request passes the ACL check */ /* rc = FALSE if the permission is denied */ gboolean acl_check_diff(xmlNode *request, xmlNode *current_cib, xmlNode *result_cib, xmlNode *diff) { const char *user = NULL; xmlNode *xml_acls = NULL; GListPtr user_acl = NULL; xmlNode *orig_diff = NULL; xmlNode *diff_child = NULL; int rc = FALSE; if (req_by_privileged(request)) { return TRUE; } if (diff == NULL) { return TRUE; } if (current_cib == NULL) { return FALSE; } xml_acls = get_object_root(XML_CIB_TAG_ACLS, current_cib); if (xml_acls == NULL) { crm_warn("Ordinary users cannot access the CIB without any defined ACLs: '%s'", user); return FALSE; } user = crm_element_value(request, F_CIB_USER); unpack_user_acl(xml_acls, user, &user_acl); orig_diff = diff_xml_object_orig(current_cib, result_cib, FALSE, diff); for (diff_child = __xml_first_child(orig_diff); diff_child; diff_child = __xml_next(diff_child)) { const char *tag = crm_element_name(diff_child); GListPtr parsed_acl = NULL; xmlNode *diff_cib = NULL; crm_debug("Preparing ACL checking on '%s'", tag); if (crm_str_eq(tag, XML_TAG_DIFF_REMOVED, TRUE)) { crm_debug("Parsing any xpaths under the ACL according to the current CIB"); parse_acl_xpath(current_cib, user_acl, &parsed_acl); } else if (crm_str_eq(tag, XML_TAG_DIFF_ADDED, TRUE)) { crm_debug("Parsing any xpaths under the ACL according to the result CIB"); parse_acl_xpath(result_cib, user_acl, &parsed_acl); } else { continue; } for (diff_cib = __xml_first_child(diff_child); diff_cib; diff_cib = __xml_next(diff_cib)) { GHashTable *xml_perms = NULL; gen_xml_perms(diff_cib, parsed_acl, &xml_perms); rc = acl_check_diff_xml(diff_cib, xml_perms); g_hash_table_destroy(xml_perms); if (rc == FALSE) { crm_warn("User '%s' doesn't have enough permission to modify the CIB objects", user); goto done; } } free_acl(parsed_acl); } done: free_xml(orig_diff); free_acl(user_acl); return rc; } static gboolean req_by_privileged(xmlNode *request) { const char *user = crm_element_value(request, F_CIB_USER); if (user == NULL || strcmp(user, "") == 0) { crm_debug("Request without an explicit client user: op=%s, origin=%s, client=%s", crm_element_value(request, F_CIB_OPERATION), crm_element_value(request, F_ORIG)?crm_element_value(request, F_ORIG):"local", crm_element_value(request, F_CIB_CLIENTNAME)); return TRUE; } if (is_privileged(user)) { return TRUE; } return FALSE; } /* Borrowed from lib/common/xml.c: diff_xml_object() */ /* But if a new format of diff ("new_diff") exists, we could reuse its "diff-removed" part */ /* So it would be more time-saving than generating the diff from start */ static xmlNode * diff_xml_object_orig(xmlNode *old, xmlNode *new, gboolean suppress, xmlNode *new_diff) { xmlNode *tmp1 = NULL; xmlNode *diff = create_xml_node(NULL, "diff"); xmlNode *removed = NULL; xmlNode *added = NULL; crm_xml_add(diff, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET); if (new_diff && (tmp1 = find_xml_node(new_diff, "diff-removed", FALSE))) { removed = add_node_copy(diff, tmp1); } else { removed = create_xml_node(diff, "diff-removed"); tmp1 = subtract_xml_object(removed, old, new, FALSE, "removed:top"); if(suppress && tmp1 != NULL && can_prune_leaf(tmp1)) { free_xml_from_parent(removed, tmp1); } } added = create_xml_node(diff, "diff-added"); tmp1 = subtract_xml_object(added, new, old, FALSE, "added:top"); if(suppress && tmp1 != NULL && can_prune_leaf(tmp1)) { free_xml_from_parent(added, tmp1); } if(added->children == NULL && removed->children == NULL) { free_xml(diff); diff = NULL; } return diff; } static gboolean unpack_user_acl(xmlNode *xml_acls, const char *user, GListPtr *user_acl) { xmlNode *xml_acl = NULL; if (xml_acls == NULL) { return FALSE; } for (xml_acl = __xml_first_child(xml_acls); xml_acl; xml_acl = __xml_next(xml_acl)) { const char *tag = crm_element_name(xml_acl); const char *id = crm_element_value(xml_acl, XML_ATTR_ID); if (crm_str_eq(tag, XML_ACL_TAG_USER, TRUE)) { if (user_match(user, id)) { crm_debug("Unpacking ACL of user: '%s'", id); unpack_acl(xml_acls, xml_acl, user_acl); return TRUE; } } } return FALSE; } static gboolean user_match(const char *user, const char *uid) { CRM_CHECK(user != NULL && user[0] != '\0' && uid != NULL && uid[0] != '\0', return FALSE); if (crm_str_eq(user, uid, TRUE)) { return TRUE; } return FALSE; } static gboolean unpack_acl(xmlNode *xml_acls, xmlNode *xml_acl, GListPtr *acl) { xmlNode *acl_child = NULL; for (acl_child = __xml_first_child(xml_acl); acl_child; acl_child = __xml_next(acl_child)) { const char *tag = crm_element_name(acl_child); if (crm_str_eq(XML_ACL_TAG_ROLE_REF, tag, TRUE)) { const char *ref_role = crm_element_value(acl_child, XML_ATTR_ID); if (ref_role) { unpack_role_acl(xml_acls, ref_role, acl); } } else if (crm_str_eq(XML_ACL_TAG_READ, tag, TRUE) || crm_str_eq(XML_ACL_TAG_WRITE, tag, TRUE) || crm_str_eq(XML_ACL_TAG_DENY, tag, TRUE)) { acl_append(acl_child, acl); } } return TRUE; } static gboolean unpack_role_acl(xmlNode *xml_acls, const char *role, GListPtr *acl) { xmlNode *xml_acl = NULL; for (xml_acl = __xml_first_child(xml_acls); xml_acl; xml_acl = __xml_next(xml_acl)) { if(crm_str_eq(XML_ACL_TAG_ROLE, (const char *)xml_acl->name, TRUE)) { const char *role_id = crm_element_value(xml_acl, XML_ATTR_ID); if (role_id && crm_str_eq(role, role_id, TRUE)) { crm_debug("Unpacking ACL of the referenced role: '%s'", role); unpack_acl(xml_acls, xml_acl, acl); return TRUE; } } } return FALSE; } static gboolean acl_append(xmlNode *acl_child, GListPtr *acl) { acl_obj_t *acl_obj = NULL; const char *tag = crm_element_value(acl_child, XML_ACL_ATTR_TAG); const char *ref = crm_element_value(acl_child, XML_ACL_ATTR_REF); const char *xpath = crm_element_value(acl_child, XML_ACL_ATTR_XPATH); if (tag == NULL && ref == NULL && xpath == NULL) { return FALSE; } crm_malloc0(acl_obj, sizeof(acl_obj_t)); if (acl_obj == NULL) { return FALSE; } acl_obj->mode = crm_element_name(acl_child); acl_obj->tag = tag; acl_obj->ref = ref; acl_obj->xpath = xpath; acl_obj->attribute = crm_element_value(acl_child, XML_ACL_ATTR_ATTRIBUTE); *acl = g_list_append(*acl, acl_obj); crm_debug_3("ACL object appended: mode=%s, tag=%s, ref=%s, xpath=%s, attribute=%s", acl_obj->mode, acl_obj->tag, acl_obj->ref, acl_obj->xpath, acl_obj->attribute); return TRUE; } static void free_acl(GListPtr acl) { GListPtr iterator = acl; while(iterator != NULL) { crm_free(iterator->data); iterator = iterator->next; } if(acl != NULL) { g_list_free(acl); } } static gboolean parse_acl_xpath(xmlNode *xml, GListPtr acl, GListPtr *parsed_acl) { GListPtr acl_iterator = acl; acl_obj_t *new_acl_obj = NULL; *parsed_acl = NULL; while (acl_iterator != NULL) { acl_obj_t *acl_obj = acl_iterator->data; if (acl_obj->tag || acl_obj->ref) { crm_malloc0(new_acl_obj, sizeof(acl_obj_t)); if (new_acl_obj == NULL) { return FALSE; } memcpy(new_acl_obj, acl_obj, sizeof(acl_obj_t)); *parsed_acl = g_list_append(*parsed_acl, new_acl_obj); crm_debug_3("Copied ACL object: mode=%s, tag=%s, ref=%s, xpath=%s, attribute=%s", new_acl_obj->mode, new_acl_obj->tag, new_acl_obj->ref, new_acl_obj->xpath, new_acl_obj->attribute); } else if (acl_obj->xpath) { GListPtr children = NULL; GListPtr children_iterator = NULL; search_xpath_objects(&children, xml, acl_obj->xpath); children_iterator = children; while (children_iterator != NULL) { crm_malloc0(new_acl_obj, sizeof(acl_obj_t)); if (new_acl_obj == NULL) { return FALSE; } new_acl_obj->mode = acl_obj->mode; new_acl_obj->tag = crm_element_name((xmlNode*)children_iterator->data); new_acl_obj->ref = crm_element_value(children_iterator->data, XML_ATTR_ID); new_acl_obj->attribute = acl_obj->attribute; *parsed_acl = g_list_append(*parsed_acl, new_acl_obj); crm_debug_3("Parsed the ACL object with xpath '%s' to: mode=%s, tag=%s, ref=%s, xpath=%s, attribute=%s", acl_obj->xpath, new_acl_obj->mode, new_acl_obj->tag, new_acl_obj->ref, new_acl_obj->xpath, new_acl_obj->attribute); children_iterator = children_iterator->next; } g_list_free(children); } acl_iterator = acl_iterator->next; } return TRUE; } static gboolean gen_xml_perms(xmlNode *xml, GListPtr acl, GHashTable **xml_perms) { GListPtr acl_iterator = acl; if (*xml_perms == NULL) { *xml_perms = g_hash_table_new_full(g_direct_hash, g_direct_equal, NULL, free_xml_perm); } while (acl_iterator != NULL) { acl_obj_t *acl_obj = acl_iterator->data; GListPtr children = NULL; GListPtr children_iterator = NULL; crm_debug("Generating permissions with ACL: mode=%s, tag=%s, ref=%s, xpath=%s, attribute=%s", acl_obj->mode, acl_obj->tag, acl_obj->ref, acl_obj->xpath, acl_obj->attribute); if (acl_obj->tag || acl_obj->ref) { search_xml_children(&children, xml, acl_obj->tag, XML_ATTR_ID, acl_obj->ref, TRUE); } else if (acl_obj->xpath) { /* Never be here for a modification operation */ /* Already parse_acl_xpath() previously */ search_xpath_objects(&children, xml, acl_obj->xpath); } children_iterator = children; while (children_iterator != NULL) { update_xml_perms(children_iterator->data, acl_obj, *xml_perms); children_iterator = children_iterator->next; } g_list_free(children); acl_iterator = acl_iterator->next; } return TRUE; } /* Borrowed from lib/common/xml.c: find_xml_children() */ /* But adding the original xmlNode pointers into a GList */ static int search_xml_children(GListPtr *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 { *children = g_list_append(*children, root); match_found = 1; } if(search_matches || match_found == 0) { xmlNode *child = NULL; for (child = __xml_first_child(root); child; child = __xml_next(child)) { match_found += search_xml_children( children, child, tag, field, value, search_matches); } } return match_found; } static int search_xpath_objects(GListPtr *objects, xmlNode *xml_obj, const char *xpath) { int match_found = 0; xmlXPathObjectPtr xpathObj = NULL; if(xpath == NULL) { return 0; } xpathObj = xpath_search(xml_obj, xpath); if(xpathObj == NULL || xpathObj->nodesetval == NULL || xpathObj->nodesetval->nodeNr < 1) { crm_debug("No match for %s in %s", xpath, xmlGetNodePath(xml_obj)); } else if(xpathObj->nodesetval->nodeNr > 0) { int lpc = 0, max = xpathObj->nodesetval->nodeNr; for(lpc = 0; lpc < max; lpc++) { xmlNode *match = getXpathResult(xpathObj, lpc); if (match == NULL) { continue; } *objects = g_list_append(*objects, match); match_found++; } } if(xpathObj) { xmlXPathFreeObject(xpathObj); } return match_found; } static gboolean update_xml_perms(xmlNode *xml, acl_obj_t *acl_obj, GHashTable *xml_perms) { xml_perm_t *perm = NULL; if (g_hash_table_lookup_extended(xml_perms, xml, NULL, (gpointer)&perm)) { if (perm->mode != NULL) { return FALSE; } } else { crm_malloc0(perm, sizeof(xml_perm_t)); if (perm == NULL) { return FALSE; } g_hash_table_insert(xml_perms, xml, perm); } if (acl_obj->attribute == NULL) { xmlNode *child = NULL; perm->mode = acl_obj->mode; crm_debug_3("Permission for element: element_mode=%s, tag=%s, id=%s", perm->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); for (child = __xml_first_child(xml); child; child = __xml_next(child)) { update_xml_children_perms(child, perm->mode, xml_perms); } } else { if (perm->attribute_perms == NULL || (g_hash_table_lookup_extended(perm->attribute_perms, acl_obj->attribute, NULL, NULL) == FALSE)) { if (perm->attribute_perms == NULL) { perm->attribute_perms = g_hash_table_new_full( crm_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); } g_hash_table_insert(perm->attribute_perms, crm_strdup(acl_obj->attribute), crm_strdup(acl_obj->mode)); crm_debug_3("Permission for attribute: attribute_mode=%s, tag=%s, id=%s attribute=%s", acl_obj->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), acl_obj->attribute); } } return TRUE; } static gboolean update_xml_children_perms(xmlNode *xml, const char *mode, GHashTable *xml_perms) { xml_perm_t *perm = NULL; xmlNode *child = NULL; if (g_hash_table_lookup_extended(xml_perms, xml, NULL, (gpointer)&perm)) { if (perm->mode != NULL) { return FALSE; } } else { crm_malloc0(perm, sizeof(xml_perm_t)); if (perm == NULL) { return FALSE; } g_hash_table_insert(xml_perms, xml, perm); } perm->mode = mode; crm_debug_4("Permission for child element: element_mode=%s, tag=%s, id=%s", mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); for (child = __xml_first_child(xml); child; child = __xml_next(child)) { update_xml_children_perms(child, mode, xml_perms); } return TRUE; } static void free_xml_perm(gpointer xml_perm) { xml_perm_t *perm = xml_perm; if (perm == NULL) { return; } if (perm->attribute_perms != NULL) { g_hash_table_destroy(perm->attribute_perms); } crm_free(perm); } #define can_read(mode) (crm_str_eq(mode, XML_ACL_TAG_READ, TRUE) \ || crm_str_eq(mode, XML_ACL_TAG_WRITE, TRUE)) #define can_write(mode) crm_str_eq(mode, XML_ACL_TAG_WRITE, TRUE) /* rc = TRUE if the xml is filtered out*/ static gboolean acl_filter_xml(xmlNode *xml, GHashTable *xml_perms) { int children_counter = 0; xml_perm_t *perm = NULL; int allow_counter = 0; xmlNode *child = NULL; for (child = __xml_first_child(xml); child; child = __xml_next(child)) { if (acl_filter_xml(child, xml_perms) == FALSE) { children_counter++; } } g_hash_table_lookup_extended(xml_perms, xml, NULL, (gpointer)&perm); if (perm == NULL) { crm_debug_4("No ACL defined to read the element: tag=%s, id=%s", crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); goto end_filter; } if (perm->attribute_perms == NULL) { if (can_read(perm->mode)) { return FALSE; } else { crm_debug_4("No enough permission to read the element: element_mode=%s, tag=%s, id=%s", perm->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); goto end_filter; } } - xml_prop_iter(xml, prop_name, prop_value, - gpointer mode = NULL; - + if(xml) { + xmlAttrPtr xIter = param_set->properties; + while(xIter) { + const char *prop_name = (const char *)xIter->name; + const char *prop_value = crm_element_value(xml, prop_name); + gpointer mode = NULL; + + xIter = xIter->next; if (g_hash_table_lookup_extended(perm->attribute_perms, prop_name, NULL, &mode)) { if (can_read(mode)) { allow_counter++; } else { xml_remove_prop(xml, prop_name); crm_debug_4("Filtered out the attribute: attribute_mode=%s, tag=%s, id=%s, attribute=%s", (char *)mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); } } else { if (can_read(perm->mode)) { allow_counter++; } else if (crm_str_eq(prop_name, XML_ATTR_ID, TRUE) == FALSE) { xml_remove_prop(xml, prop_name); crm_debug_4("Filtered out the attribute: element_mode=%s, tag=%s, id=%s, attribute=%s", perm->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); } } - ); + } + } if (allow_counter) { return FALSE; } if (can_read(perm->mode)) { return FALSE; } end_filter: if (children_counter) { crm_debug_4("Don't filter out the element (tag=%s, id=%s) because user can read its children", crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); return FALSE; } free_xml_from_parent(NULL, xml); crm_debug_4("Filtered out the element: tag=%s, id=%s", crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID)); return TRUE; } static gboolean acl_check_diff_xml(xmlNode *xml, GHashTable *xml_perms) { xml_perm_t *perm = NULL; xmlNode *child = NULL; for (child = __xml_first_child(xml); child; child = __xml_next(child)) { if (acl_check_diff_xml(child, xml_perms) == FALSE) { return FALSE; } } g_hash_table_lookup_extended(xml_perms, xml, NULL, (gpointer)&perm); - xml_prop_iter(xml, prop_name, prop_value, + if(xml) { + xmlAttrPtr xIter = NULL; + for(xIter = xml->properties; xIter; xIter = xIter->next) { + const char *prop_name = (const char *)xIter->name; + const char *prop_value = crm_element_value(xml, prop_name); gpointer mode = NULL; if (crm_str_eq(crm_element_name(xml), XML_TAG_CIB, TRUE)) { if (crm_str_eq(prop_name, XML_ATTR_GENERATION, TRUE) || crm_str_eq(prop_name, XML_ATTR_NUMUPDATES, TRUE) || crm_str_eq(prop_name, XML_ATTR_GENERATION_ADMIN, TRUE)) { continue; } } if (crm_str_eq(prop_name, XML_ATTR_ID, TRUE)) { continue; } if (crm_str_eq(prop_name, XML_DIFF_MARKER, TRUE) && xml_has_children(xml)) { continue; } if (perm == NULL) { crm_warn("No ACL defined to modify the element: tag=%s, id=%s, attribute=%s", crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); return FALSE; } if (perm->attribute_perms == NULL) { if (can_write(perm->mode)) { return TRUE; } else { crm_warn("No enough permission to modify the element: element_mode=%s, tag=%s, id=%s, attribute=%s", perm->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); return FALSE; } } if (g_hash_table_lookup_extended(perm->attribute_perms, prop_name, NULL, &mode)) { if (can_write(mode) == FALSE) { crm_warn("No enough permission to modify the attribute: attribute_mode=%s, tag=%s, id=%s, attribute=%s", (char *)mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); return FALSE; } } else if (can_write(perm->mode) == FALSE) { crm_warn("No enough permission to modify the element and the attribute: element_mode=%s, tag=%s, id=%s, attribute=%s", perm->mode, crm_element_name(xml), crm_element_value(xml, XML_ATTR_ID), prop_name); return FALSE; } - - ); + } + } return TRUE; } diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c index 3ef0a7ea11..06dcb6ca68 100644 --- a/lib/cib/cib_utils.c +++ b/lib/cib/cib_utils.c @@ -1,1020 +1,1026 @@ /* * Copyright (c) 2004 International Business Machines * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include struct config_root_s { const char *name; const char *parent; const char *path; }; /* * "//crm_config" will also work in place of "/cib/configuration/crm_config" * The / prefix means find starting from the root, whereas the // prefix means * find anywhere and risks multiple matches */ /* *INDENT-OFF* */ struct config_root_s known_paths[] = { { NULL, NULL, "//cib" }, { XML_TAG_CIB, NULL, "//cib" }, { XML_CIB_TAG_STATUS, "/cib", "//cib/status" }, { XML_CIB_TAG_CONFIGURATION,"/cib", "//cib/configuration" }, { XML_CIB_TAG_CRMCONFIG, "/cib/configuration", "//cib/configuration/crm_config" }, { XML_CIB_TAG_NODES, "/cib/configuration", "//cib/configuration/nodes" }, { XML_CIB_TAG_DOMAINS, "/cib/configuration", "//cib/configuration/domains" }, { XML_CIB_TAG_RESOURCES, "/cib/configuration", "//cib/configuration/resources" }, { XML_CIB_TAG_CONSTRAINTS, "/cib/configuration", "//cib/configuration/constraints" }, { XML_CIB_TAG_OPCONFIG, "/cib/configuration", "//cib/configuration/op_defaults" }, { XML_CIB_TAG_RSCCONFIG, "/cib/configuration", "//cib/configuration/rsc_defaults" }, { XML_CIB_TAG_ACLS, "/cib/configuration", "//cib/configuration/acls" }, { XML_CIB_TAG_SECTION_ALL, NULL, "//cib" }, }; /* *INDENT-ON* */ const char * cib_error2string(enum cib_errors return_code) { const char *error_msg = NULL; switch(return_code) { case cib_bad_permissions: error_msg = "bad permissions for the on-disk configuration. shutdown heartbeat and repair."; break; case cib_bad_digest: error_msg = "the on-disk configuration was manually altered. shutdown heartbeat and repair."; break; case cib_bad_config: error_msg = "the on-disk configuration is not valid"; break; case cib_msg_field_add: error_msg = "failed adding field to cib message"; break; case cib_id_check: error_msg = "missing id or id-collision detected"; break; case cib_operation: error_msg = "invalid operation"; break; case cib_create_msg: error_msg = "couldnt create cib message"; break; case cib_client_gone: error_msg = "client left before we could send reply"; break; case cib_not_connected: error_msg = "not connected"; break; case cib_not_authorized: error_msg = "not authorized"; break; case cib_send_failed: error_msg = "send failed"; break; case cib_reply_failed: error_msg = "reply failed"; break; case cib_return_code: error_msg = "no return code"; break; case cib_output_ptr: error_msg = "nowhere to store output"; break; case cib_output_data: error_msg = "corrupt output data"; break; case cib_connection: error_msg = "connection failed"; break; case cib_callback_register: error_msg = "couldnt register callback channel"; break; case cib_authentication: error_msg = ""; break; case cib_registration_msg: error_msg = "invalid registration msg"; break; case cib_callback_token: error_msg = "callback token not found"; break; case cib_missing: error_msg = "cib object missing"; break; case cib_variant: error_msg = "unknown/corrupt cib variant"; break; case CIBRES_MISSING_ID: error_msg = "The id field is missing"; break; case CIBRES_MISSING_TYPE: error_msg = "The type field is missing"; break; case CIBRES_MISSING_FIELD: error_msg = "A required field is missing"; break; case CIBRES_OBJTYPE_MISMATCH: error_msg = "CIBRES_OBJTYPE_MISMATCH"; break; case cib_EXISTS: error_msg = "The object already exists"; break; case cib_NOTEXISTS: error_msg = "The object/attribute does not exist"; break; case CIBRES_CORRUPT: error_msg = "The CIB is corrupt"; break; case cib_NOOBJECT: error_msg = "The update was empty"; break; case cib_NOPARENT: error_msg = "The parent object does not exist"; break; case cib_NODECOPY: error_msg = "Failed while copying update"; break; case CIBRES_OTHER: error_msg = "CIBRES_OTHER"; break; case cib_ok: error_msg = "ok"; break; case cib_unknown: error_msg = "Unknown error"; break; case cib_STALE: error_msg = "Discarded old update"; break; case cib_ACTIVATION: error_msg = "Activation Failed"; break; case cib_NOSECTION: error_msg = "Required section was missing"; break; case cib_NOTSUPPORTED: error_msg = "The action/feature is not supported"; break; case cib_not_master: error_msg = "Local service is not the master instance"; break; case cib_client_corrupt: error_msg = "Service client not valid"; break; case cib_remote_timeout: error_msg = "Remote node did not respond"; break; case cib_master_timeout: error_msg = "No master service is currently active"; break; case cib_revision_unsupported: error_msg = "The required CIB revision number is not supported"; break; case cib_revision_unknown: error_msg = "The CIB revision number could not be determined"; break; case cib_missing_data: error_msg = "Required data for this CIB API call not found"; break; case cib_no_quorum: error_msg = "Write requires quorum"; break; case cib_diff_failed: error_msg = "Application of an update diff failed"; break; case cib_diff_resync: error_msg = "Application of an update diff failed, requesting a full refresh"; break; case cib_bad_section: error_msg = "Invalid CIB section specified"; break; case cib_old_data: error_msg = "Update was older than existing configuration"; break; case cib_dtd_validation: error_msg = "Update does not conform to the configured schema/DTD"; break; case cib_invalid_argument: error_msg = "Invalid argument"; break; case cib_transform_failed: error_msg = "Schema transform failed"; break; case cib_permission_denied: error_msg = "Permission Denied"; break; } if(error_msg == NULL) { crm_err("Unknown CIB Error Code: %d", return_code); error_msg = ""; } return error_msg; } int cib_section2enum(const char *a_section) { if(a_section == NULL || strcasecmp(a_section, "all") == 0) { return cib_section_all; } else if(strcasecmp(a_section, XML_CIB_TAG_NODES) == 0) { return cib_section_nodes; } else if(strcasecmp(a_section, XML_CIB_TAG_STATUS) == 0) { return cib_section_status; } else if(strcasecmp(a_section, XML_CIB_TAG_CONSTRAINTS) == 0) { return cib_section_constraints; } else if(strcasecmp(a_section, XML_CIB_TAG_RESOURCES) == 0) { return cib_section_resources; } else if(strcasecmp(a_section, XML_CIB_TAG_CRMCONFIG) == 0) { return cib_section_crmconfig; } crm_err("Unknown CIB section: %s", a_section); return cib_section_none; } int cib_compare_generation(xmlNode *left, xmlNode *right) { int lpc = 0; const char *attributes[] = { XML_ATTR_GENERATION_ADMIN, XML_ATTR_GENERATION, XML_ATTR_NUMUPDATES, }; crm_log_xml_debug_3(left, "left"); crm_log_xml_debug_3(right, "right"); for(lpc = 0; lpc < DIMOF(attributes); lpc++) { int int_elem_l = -1; int int_elem_r = -1; const char *elem_r = NULL; const char *elem_l = crm_element_value(left, attributes[lpc]); if(right != NULL) { elem_r = crm_element_value(right, attributes[lpc]); } if(elem_l != NULL) { int_elem_l = crm_parse_int(elem_l, NULL); } if(elem_r != NULL) { int_elem_r = crm_parse_int(elem_r, NULL); } if(int_elem_l < int_elem_r) { crm_debug_2("%s (%s < %s)", attributes[lpc], crm_str(elem_l), crm_str(elem_r)); return -1; } else if(int_elem_l > int_elem_r) { crm_debug_2("%s (%s > %s)", attributes[lpc], crm_str(elem_l), crm_str(elem_r)); return 1; } } return 0; } xmlNode* get_cib_copy(cib_t *cib) { xmlNode *xml_cib; int options = cib_scope_local|cib_sync_call; if(cib->cmds->query(cib, NULL, &xml_cib, options) != cib_ok) { crm_err("Couldnt retrieve the CIB"); return NULL; } else if(xml_cib == NULL) { crm_err("The CIB result was empty"); return NULL; } if(safe_str_eq(crm_element_name(xml_cib), XML_TAG_CIB)) { return xml_cib; } free_xml(xml_cib); return NULL; } xmlNode* cib_get_generation(cib_t *cib) { xmlNode *the_cib = get_cib_copy(cib); xmlNode *generation = create_xml_node( NULL, XML_CIB_TAG_GENERATION_TUPPLE); if(the_cib != NULL) { copy_in_properties(generation, the_cib); free_xml(the_cib); } return generation; } void log_cib_diff(int log_level, xmlNode *diff, const char *function) { int add_updates = 0; int add_epoch = 0; int add_admin_epoch = 0; int del_updates = 0; int del_epoch = 0; int del_admin_epoch = 0; if(diff == NULL) { return; } cib_diff_version_details( diff, &add_admin_epoch, &add_epoch, &add_updates, &del_admin_epoch, &del_epoch, &del_updates); if(add_updates != del_updates) { do_crm_log(log_level, "%s: Diff: --- %d.%d.%d", function, del_admin_epoch, del_epoch, del_updates); do_crm_log(log_level, "%s: Diff: +++ %d.%d.%d", function, add_admin_epoch, add_epoch, add_updates); } else if(diff != NULL) { do_crm_log(log_level, "%s: Local-only Change: %d.%d.%d", function, add_admin_epoch, add_epoch, add_updates); } log_xml_diff(log_level, diff, function); } gboolean cib_version_details( xmlNode *cib, int *admin_epoch, int *epoch, int *updates) { *epoch = -1; *updates = -1; *admin_epoch = -1; if(cib == NULL) { return FALSE; } else { crm_element_value_int(cib, XML_ATTR_GENERATION, epoch); crm_element_value_int(cib, XML_ATTR_NUMUPDATES, updates); crm_element_value_int(cib, XML_ATTR_GENERATION_ADMIN, admin_epoch); } return TRUE; } gboolean cib_diff_version_details( xmlNode *diff, int *admin_epoch, int *epoch, int *updates, int *_admin_epoch, int *_epoch, int *_updates) { xmlNode *tmp = NULL; tmp = find_xml_node(diff, "diff-added", FALSE); tmp = find_xml_node(tmp, XML_TAG_CIB, FALSE); cib_version_details(tmp, admin_epoch, epoch, updates); tmp = find_xml_node(diff, "diff-removed", FALSE); cib_version_details(tmp, _admin_epoch, _epoch, _updates); return TRUE; } /* * The caller should never free the return value */ const char *get_object_path(const char *object_type) { int lpc = 0; int max = DIMOF(known_paths); for(; lpc < max; lpc++) { if((object_type == NULL && known_paths[lpc].name == NULL) || safe_str_eq(object_type, known_paths[lpc].name)) { return known_paths[lpc].path; } } return NULL; } const char *get_object_parent(const char *object_type) { int lpc = 0; int max = DIMOF(known_paths); for(; lpc < max; lpc++) { if(safe_str_eq(object_type, known_paths[lpc].name)) { return known_paths[lpc].parent; } } return NULL; } xmlNode* get_object_root(const char *object_type, xmlNode *the_root) { const char *xpath = get_object_path(object_type); if(xpath == NULL) { return the_root; /* or return NULL? */ } return get_xpath_object(xpath, the_root, LOG_DEBUG_4); } xmlNode* create_cib_fragment_adv( xmlNode *update, const char *update_section, const char *source) { xmlNode *cib = NULL; gboolean whole_cib = FALSE; xmlNode *object_root = NULL; char *local_section = NULL; /* crm_debug("Creating a blank fragment: %s", update_section); */ if(update == NULL && update_section == NULL) { crm_debug_3("Creating a blank fragment"); update = createEmptyCib(); crm_xml_add(cib, XML_ATTR_ORIGIN, source); return update; } else if(update == NULL) { crm_err("No update to create a fragment for"); return NULL; } CRM_CHECK(update_section != NULL, return NULL); if(safe_str_eq(crm_element_name(update), XML_TAG_CIB)) { whole_cib = TRUE; } if(whole_cib == FALSE) { cib = createEmptyCib(); crm_xml_add(cib, XML_ATTR_ORIGIN, source); object_root = get_object_root(update_section, cib); add_node_copy(object_root, update); } else { cib = copy_xml(update); crm_xml_add(cib, XML_ATTR_ORIGIN, source); } crm_free(local_section); crm_debug_3("Verifying created fragment"); return cib; } /* * It is the callers responsibility to free both the new CIB (output) * and the new CIB (input) */ xmlNode* createEmptyCib(void) { xmlNode *cib_root = NULL, *config = NULL; cib_root = create_xml_node(NULL, XML_TAG_CIB); config = create_xml_node(cib_root, XML_CIB_TAG_CONFIGURATION); create_xml_node(cib_root, XML_CIB_TAG_STATUS); /* crm_xml_add(cib_root, "version", "1"); */ create_xml_node(config, XML_CIB_TAG_CRMCONFIG); create_xml_node(config, XML_CIB_TAG_NODES); create_xml_node(config, XML_CIB_TAG_RESOURCES); create_xml_node(config, XML_CIB_TAG_CONSTRAINTS); return cib_root; } static unsigned int dtd_throttle = 0; enum cib_errors cib_perform_op(const char *op, int call_options, cib_op_t *fn, gboolean is_query, const char *section, xmlNode *req, xmlNode *input, gboolean manage_counters, gboolean *config_changed, xmlNode *current_cib, xmlNode **result_cib, xmlNode **diff, xmlNode **output) { int rc = cib_ok; gboolean check_dtd = TRUE; xmlNode *scratch = NULL; xmlNode *local_diff = NULL; const char *current_dtd = "unknown"; CRM_CHECK(output != NULL, return cib_output_data); CRM_CHECK(result_cib != NULL, return cib_output_data); CRM_CHECK(config_changed != NULL, return cib_output_data); *output = NULL; *result_cib = NULL; *config_changed = FALSE; if(fn == NULL) { return cib_operation; } if(is_query) { rc = (*fn)(op, call_options, section, req, input, current_cib, result_cib, output); return rc; } scratch = copy_xml(current_cib); rc = (*fn)(op, call_options, section, req, input, current_cib, &scratch, output); CRM_CHECK(current_cib != scratch, return cib_unknown); if(rc == cib_ok && scratch == NULL) { rc = cib_unknown; } if(rc == cib_ok && scratch) { const char *new_version = crm_element_value(scratch, XML_ATTR_CRM_VERSION); if(new_version && compare_version(new_version, CRM_FEATURE_SET) > 0) { crm_err("Discarding update with feature set '%s' greater than our own '%s'", new_version, CRM_FEATURE_SET); rc = cib_NOTSUPPORTED; } } if(rc == cib_ok && current_cib) { int old = 0; int new = 0; crm_element_value_int(scratch, XML_ATTR_GENERATION_ADMIN, &new); crm_element_value_int(current_cib, XML_ATTR_GENERATION_ADMIN, &old); if(old > new) { crm_err("%s went backwards: %d -> %d (Opts: 0x%x)", XML_ATTR_GENERATION_ADMIN, old, new, call_options); crm_log_xml_warn(req, "Bad Op"); crm_log_xml_warn(input, "Bad Data"); rc = cib_old_data; } else if(old == new) { crm_element_value_int(scratch, XML_ATTR_GENERATION, &new); crm_element_value_int(current_cib, XML_ATTR_GENERATION, &old); if(old > new) { crm_err("%s went backwards: %d -> %d (Opts: 0x%x)", XML_ATTR_GENERATION, old, new, call_options); crm_log_xml_warn(req, "Bad Op"); crm_log_xml_warn(input, "Bad Data"); rc = cib_old_data; } } } if(rc == cib_ok) { fix_plus_plus_recursive(scratch); current_dtd = crm_element_value(scratch, XML_ATTR_VALIDATION); if(manage_counters) { if(is_set(call_options, cib_inhibit_bcast) && safe_str_eq(section, XML_CIB_TAG_STATUS)) { /* Fast-track changes connections which wont be broadcasting anywhere */ cib_update_counter(scratch, XML_ATTR_NUMUPDATES, FALSE); goto done; } /* The diff calculation in cib_config_changed() accounts for 25% of the * CIB's total CPU usage on the DC * * RNG validation on the otherhand, accounts for only 9%... */ *config_changed = cib_config_changed(current_cib, scratch, &local_diff); if(*config_changed) { cib_update_counter(scratch, XML_ATTR_NUMUPDATES, TRUE); cib_update_counter(scratch, XML_ATTR_GENERATION, FALSE); } else { /* Previously we only did this if the diff detected a change * * But we replies are still sent, even if nothing changes so we * don't save any network traffic and means we need to jump * through expensive hoops to detect ordering changes - see below */ cib_update_counter(scratch, XML_ATTR_NUMUPDATES, FALSE); if(local_diff == NULL) { /* Nothing to check */ check_dtd = FALSE; /* Create a fake diff so that notifications, which include a _digest_, * will be sent to our peers * * This is the cheapest way to detect changes to group/set ordering * * Previously we compared the old and new digest in cib_config_changed(), * but that accounted for 15% of the CIB's total CPU usage on the DC */ local_diff = create_xml_node(NULL, "diff"); crm_xml_add(local_diff, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET); create_xml_node(local_diff, "diff-removed"); create_xml_node(local_diff, "diff-added"); /* Usually these are attrd re-updates */ crm_log_xml_trace(req, "Non-change"); } else if(dtd_throttle++ % 20) { /* Throttle the amount of costly validation we perform due to status updates * a) we don't really care whats in the status section * b) we don't validate any of it's contents at the moment anyway */ check_dtd = FALSE; } } } } if(diff != NULL && local_diff != NULL) { /* Only fix the diff if we'll return it... */ xmlNode *cib = NULL; xmlNode *diff_child = NULL; const char *tag = NULL; const char *value = NULL; tag = "diff-removed"; diff_child = find_xml_node(local_diff, tag, FALSE); if(diff_child == NULL) { diff_child = create_xml_node(local_diff, tag); } tag = XML_TAG_CIB; cib = find_xml_node(diff_child, tag, FALSE); if(cib == NULL) { cib = create_xml_node(diff_child, tag); } tag = XML_ATTR_GENERATION_ADMIN; value = crm_element_value(current_cib, tag); crm_xml_add(diff_child, tag, value); if(*config_changed) { crm_xml_add(cib, tag, value); } tag = XML_ATTR_GENERATION; value = crm_element_value(current_cib, tag); crm_xml_add(diff_child, tag, value); if(*config_changed) { crm_xml_add(cib, tag, value); } tag = XML_ATTR_NUMUPDATES; value = crm_element_value(current_cib, tag); crm_xml_add(cib, tag, value); crm_xml_add(diff_child, tag, value); tag = "diff-added"; diff_child = find_xml_node(local_diff, tag, FALSE); if(diff_child == NULL) { diff_child = create_xml_node(local_diff, tag); } tag = XML_TAG_CIB; cib = find_xml_node(diff_child, tag, FALSE); if(cib == NULL) { cib = create_xml_node(diff_child, tag); } - - xml_prop_iter(scratch, p_name, p_value, - xmlSetProp(cib, (const xmlChar*)p_name, (const xmlChar*)p_value)); + + if(scratch) { + xmlAttrPtr xIter = NULL; + for(xIter = scratch->properties; xIter; xIter = xIter->next) { + const char *p_name = (const char *)xIter->name; + const char *p_value = crm_element_value(scratch, p_name); + xmlSetProp(cib, (const xmlChar*)p_name, (const xmlChar*)p_value); + } + } crm_log_xml_trace(local_diff, "Repaired-diff"); *diff = local_diff; local_diff = NULL; } done: if(rc == cib_ok && check_dtd && validate_xml(scratch, NULL, TRUE) == FALSE) { crm_warn("Updated CIB does not validate against %s schema/dtd", crm_str(current_dtd)); rc = cib_dtd_validation; } *result_cib = scratch; free_xml(local_diff); return rc; } int get_channel_token(IPC_Channel *ch, char **token) { int rc = cib_ok; xmlNode *reg_msg = NULL; const char *msg_type = NULL; const char *tmp_ticket = NULL; CRM_CHECK(ch != NULL, return cib_missing); CRM_CHECK(token != NULL, return cib_output_ptr); crm_debug_4("Waiting for msg on command channel"); reg_msg = xmlfromIPC(ch, MAX_IPC_DELAY); if(ch->ops->get_chan_status(ch) != IPC_CONNECT) { crm_err("No reply message - disconnected"); free_xml(reg_msg); return cib_not_connected; } else if(reg_msg == NULL) { crm_err("No reply message - empty"); return cib_reply_failed; } msg_type = crm_element_value(reg_msg, F_CIB_OPERATION); tmp_ticket = crm_element_value(reg_msg, F_CIB_CLIENTID); if(safe_str_neq(msg_type, CRM_OP_REGISTER) ) { crm_err("Invalid registration message: %s", msg_type); rc = cib_registration_msg; } else if(tmp_ticket == NULL) { rc = cib_callback_token; } else { *token = crm_strdup(tmp_ticket); } free_xml(reg_msg); return rc; } xmlNode * cib_create_op( int call_id, const char *token, const char *op, const char *host, const char *section, xmlNode *data, int call_options, const char *user_name) { int rc = HA_OK; xmlNode *op_msg = create_xml_node(NULL, "cib_command"); CRM_CHECK(op_msg != NULL, return NULL); CRM_CHECK(token != NULL, return NULL); crm_xml_add(op_msg, F_XML_TAGNAME, "cib_command"); crm_xml_add(op_msg, F_TYPE, T_CIB); crm_xml_add(op_msg, F_CIB_CALLBACK_TOKEN, token); crm_xml_add(op_msg, F_CIB_OPERATION, op); crm_xml_add(op_msg, F_CIB_HOST, host); crm_xml_add(op_msg, F_CIB_SECTION, section); crm_xml_add_int(op_msg, F_CIB_CALLID, call_id); #if ENABLE_ACL if(user_name) { crm_xml_add(op_msg, F_CIB_USER, user_name); } #endif crm_debug_4("Sending call options: %.8lx, %d", (long)call_options, call_options); crm_xml_add_int(op_msg, F_CIB_CALLOPTS, call_options); if(data != NULL) { add_message_xml(op_msg, F_CIB_CALLDATA, data); } if (rc != HA_OK) { crm_err("Failed to create CIB operation message"); crm_log_xml(LOG_ERR, "op", op_msg); free_xml(op_msg); return NULL; } if(call_options & cib_inhibit_bcast) { CRM_CHECK((call_options & cib_scope_local), return NULL); } return op_msg; } void cib_native_callback(cib_t *cib, xmlNode *msg, int call_id, int rc) { xmlNode *output = NULL; cib_callback_client_t *blob = NULL; cib_callback_client_t local_blob; local_blob.id = NULL; local_blob.callback = NULL; local_blob.user_data = NULL; local_blob.only_success = FALSE; if(msg != NULL) { crm_element_value_int(msg, F_CIB_RC, &rc); crm_element_value_int(msg, F_CIB_CALLID, &call_id); output = get_message_xml(msg, F_CIB_CALLDATA); } blob = g_hash_table_lookup( cib_op_callback_table, GINT_TO_POINTER(call_id)); if(blob != NULL) { local_blob = *blob; blob = NULL; remove_cib_op_callback(call_id, FALSE); } else { crm_debug_2("No callback found for call %d", call_id); local_blob.callback = NULL; } if(cib == NULL) { crm_debug("No cib object supplied"); } if(rc == cib_diff_resync) { /* This is an internal value that clients do not and should not care about */ rc = cib_ok; } if(local_blob.callback != NULL && (rc == cib_ok || local_blob.only_success == FALSE)) { crm_debug_2("Invoking callback %s for call %d", crm_str(local_blob.id), call_id); local_blob.callback(msg, call_id, rc, output, local_blob.user_data); } else if(cib && cib->op_callback == NULL && rc != cib_ok) { crm_warn("CIB command failed: %s", cib_error2string(rc)); crm_log_xml(LOG_DEBUG, "Failed CIB Update", msg); } if(cib && cib->op_callback != NULL) { crm_debug_2("Invoking global callback for call %d", call_id); cib->op_callback(msg, call_id, rc, output); } crm_debug_4("OP callback activated."); } void cib_native_notify(gpointer data, gpointer user_data) { xmlNode *msg = user_data; cib_notify_client_t *entry = data; const char *event = NULL; if(msg == NULL) { crm_warn("Skipping callback - NULL message"); return; } event = crm_element_value(msg, F_SUBTYPE); if(entry == NULL) { crm_warn("Skipping callback - NULL callback client"); return; } else if(entry->callback == NULL) { crm_warn("Skipping callback - NULL callback"); return; } else if(safe_str_neq(entry->event, event)) { crm_debug_4("Skipping callback - event mismatch %p/%s vs. %s", entry, entry->event, event); return; } crm_debug_4("Invoking callback for %p/%s event...", entry, event); entry->callback(event, msg); crm_debug_4("Callback invoked..."); } gboolean determine_host(cib_t *cib_conn, char **node_uname, char **node_uuid) { CRM_CHECK(node_uname != NULL, return FALSE); if(*node_uname == NULL) { struct utsname name; if(uname(&name) < 0) { crm_perror(LOG_ERR,"uname(2) call failed"); return FALSE; } *node_uname = crm_strdup(name.nodename); crm_info("Detected uname: %s", *node_uname); } if(cib_conn && *node_uname != NULL && node_uuid != NULL && *node_uuid == NULL) { int rc = query_node_uuid(cib_conn, *node_uname, node_uuid); if(rc != cib_ok) { fprintf(stderr,"Could not map uname=%s to a UUID: %s\n", *node_uname, cib_error2string(rc)); return FALSE; } crm_info("Mapped %s to %s", *node_uname, crm_str(*node_uuid)); } return TRUE; } pe_cluster_option cib_opts[] = { /* name, old-name, validate, default, description */ { "enable-acl", NULL, "boolean", NULL, "false", &check_boolean, "Enable CIB ACL", NULL }, }; void cib_metadata(void) { config_metadata("Cluster Information Base", "1.0", "Cluster Information Base Options", "This is a fake resource that details the options that can be configured for the Cluster Information Base.", cib_opts, DIMOF(cib_opts)); } void verify_cib_options(GHashTable *options) { verify_all_options(options, cib_opts, DIMOF(cib_opts)); } const char * cib_pref(GHashTable *options, const char *name) { return get_cluster_pref(options, cib_opts, DIMOF(cib_opts), name); } gboolean cib_read_config(GHashTable *options, xmlNode *current_cib) { xmlNode *config = NULL; ha_time_t *now = NULL; if (options == NULL || current_cib == NULL) { return FALSE; } now = new_ha_date(TRUE); g_hash_table_remove_all(options); config = get_object_root(XML_CIB_TAG_CRMCONFIG, current_cib); if (config) { unpack_instance_attributes( current_cib, config, XML_CIB_TAG_PROPSET, NULL, options, CIB_OPTIONS_FIRST, FALSE, now); } verify_cib_options(options); free_ha_date(now); return TRUE; } gboolean cib_internal_config_changed(xmlNode *diff) { gboolean changed = FALSE; const char *config_xpath = "//"XML_TAG_CIB"/"XML_CIB_TAG_CONFIGURATION"/"XML_CIB_TAG_CRMCONFIG; xmlXPathObject *xpathObj = NULL; if (diff == NULL) { return FALSE; } xpathObj = xpath_search(diff, config_xpath); if (xpathObj && xpathObj->nodesetval->nodeNr > 0) { changed = TRUE; } if (xpathObj) { xmlXPathFreeObject(xpathObj); } return changed; } diff --git a/lib/common/utils.c b/lib/common/utils.c index 139e390928..740563b521 100644 --- a/lib/common/utils.c +++ b/lib/common/utils.c @@ -1,2665 +1,2675 @@ /* * Copyright (C) 2004 Andrew Beekhof * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #ifndef _GNU_SOURCE # define _GNU_SOURCE #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if HAVE_HB_CONFIG_H #include /* for HB_COREDIR */ #endif #if HAVE_GLUE_CONFIG_H #include /* for HB_COREDIR */ #endif #ifndef MAXLINE # define MAXLINE 512 #endif #ifdef HAVE_GETOPT_H # include #endif CRM_TRACE_INIT_DATA(common); static uint ref_counter = 0; unsigned int crm_log_level = LOG_INFO; gboolean crm_config_error = FALSE; gboolean crm_config_warning = FALSE; const char *crm_system_name = "unknown"; int node_score_red = 0; int node_score_green = 0; int node_score_yellow = 0; int node_score_infinity = INFINITY; void crm_set_env_options(void); gboolean check_time(const char *value) { if(crm_get_msec(value) < 5000) { return FALSE; } return TRUE; } gboolean check_timer(const char *value) { if(crm_get_msec(value) < 0) { return FALSE; } return TRUE; } gboolean check_boolean(const char *value) { int tmp = FALSE; if(crm_str_to_boolean(value, &tmp) != 1) { return FALSE; } return TRUE; } gboolean check_number(const char *value) { errno = 0; if(value == NULL) { return FALSE; } else if(safe_str_eq(value, MINUS_INFINITY_S)) { } else if(safe_str_eq(value, INFINITY_S)) { } else { crm_int_helper(value, NULL); } if(errno != 0) { return FALSE; } return TRUE; } int char2score(const char *score) { int score_f = 0; if(score == NULL) { } else if(safe_str_eq(score, MINUS_INFINITY_S)) { score_f = -node_score_infinity; } else if(safe_str_eq(score, INFINITY_S)) { score_f = node_score_infinity; } else if(safe_str_eq(score, "+"INFINITY_S)) { score_f = node_score_infinity; } else if(safe_str_eq(score, "red")) { score_f = node_score_red; } else if(safe_str_eq(score, "yellow")) { score_f = node_score_yellow; } else if(safe_str_eq(score, "green")) { score_f = node_score_green; } else { score_f = crm_parse_int(score, NULL); if(score_f > 0 && score_f > node_score_infinity) { score_f = node_score_infinity; } else if(score_f < 0 && score_f < -node_score_infinity) { score_f = -node_score_infinity; } } return score_f; } char * score2char(int score) { if(score >= node_score_infinity) { return crm_strdup(INFINITY_S); } else if(score <= -node_score_infinity) { return crm_strdup("-"INFINITY_S); } return crm_itoa(score); } const char * cluster_option(GHashTable* options, gboolean(*validate)(const char*), const char *name, const char *old_name, const char *def_value) { const char *value = NULL; CRM_ASSERT(name != NULL); if(options != NULL) { value = g_hash_table_lookup(options, name); } if(value == NULL && old_name && options != NULL) { value = g_hash_table_lookup(options, old_name); if(value != NULL) { crm_config_warn("Using deprecated name '%s' for" " cluster option '%s'", old_name, name); g_hash_table_insert( options, crm_strdup(name), crm_strdup(value)); value = g_hash_table_lookup(options, old_name); } } if(value == NULL) { crm_debug_2("Using default value '%s' for cluster option '%s'", def_value, name); if(options == NULL) { return def_value; } g_hash_table_insert( options, crm_strdup(name), crm_strdup(def_value)); value = g_hash_table_lookup(options, name); } if(validate && validate(value) == FALSE) { crm_config_err("Value '%s' for cluster option '%s' is invalid." " Defaulting to %s", value, name, def_value); g_hash_table_replace(options, crm_strdup(name), crm_strdup(def_value)); value = g_hash_table_lookup(options, name); } return value; } const char * get_cluster_pref(GHashTable *options, pe_cluster_option *option_list, int len, const char *name) { int lpc = 0; const char *value = NULL; gboolean found = FALSE; for(lpc = 0; lpc < len; lpc++) { if(safe_str_eq(name, option_list[lpc].name)) { found = TRUE; value = cluster_option(options, option_list[lpc].is_valid, option_list[lpc].name, option_list[lpc].alt_name, option_list[lpc].default_value); } } CRM_CHECK(found, crm_err("No option named: %s", name)); CRM_ASSERT(value != NULL); return value; } void config_metadata(const char *name, const char *version, const char *desc_short, const char *desc_long, pe_cluster_option *option_list, int len) { int lpc = 0; fprintf(stdout, "" "\n" "\n" " %s\n" " %s\n" " %s\n" " \n", name, version, desc_long, desc_short); for(lpc = 0; lpc < len; lpc++) { if(option_list[lpc].description_long == NULL && option_list[lpc].description_short == NULL) { continue; } fprintf(stdout, " \n" " %s\n" " \n" " %s%s%s\n" " \n", option_list[lpc].name, option_list[lpc].description_short, option_list[lpc].type, option_list[lpc].default_value, option_list[lpc].description_long?option_list[lpc].description_long:option_list[lpc].description_short, option_list[lpc].values?" Allowed values: ":"", option_list[lpc].values?option_list[lpc].values:""); } fprintf(stdout, " \n\n"); } void verify_all_options(GHashTable *options, pe_cluster_option *option_list, int len) { int lpc = 0; for(lpc = 0; lpc < len; lpc++) { cluster_option(options, option_list[lpc].is_valid, option_list[lpc].name, option_list[lpc].alt_name, option_list[lpc].default_value); } } char * generateReference(const char *custom1, const char *custom2) { const char *local_cust1 = custom1; const char *local_cust2 = custom2; int reference_len = 4; char *since_epoch = NULL; reference_len += 20; /* too big */ reference_len += 40; /* too big */ if(local_cust1 == NULL) { local_cust1 = "_empty_"; } reference_len += strlen(local_cust1); if(local_cust2 == NULL) { local_cust2 = "_empty_"; } reference_len += strlen(local_cust2); crm_malloc0(since_epoch, reference_len); if(since_epoch != NULL) { sprintf(since_epoch, "%s-%s-%ld-%u", local_cust1, local_cust2, (unsigned long)time(NULL), ref_counter++); } return since_epoch; } gboolean decodeNVpair(const char *srcstring, char separator, char **name, char **value) { int lpc = 0; int len = 0; const char *temp = NULL; CRM_ASSERT(name != NULL && value != NULL); *name = NULL; *value = NULL; crm_debug_4("Attempting to decode: [%s]", srcstring); if (srcstring != NULL) { len = strlen(srcstring); while(lpc <= len) { if (srcstring[lpc] == separator) { crm_malloc0(*name, lpc+1); if(*name == NULL) { break; /* and return FALSE */ } strncpy(*name, srcstring, lpc); (*name)[lpc] = '\0'; /* this sucks but as the strtok manpage says.. * it *is* a bug */ len = len-lpc; len--; if(len <= 0) { *value = NULL; } else { crm_malloc0(*value, len+1); if(*value == NULL) { crm_free(*name); break; /* and return FALSE */ } temp = srcstring+lpc+1; strncpy(*value, temp, len); (*value)[len] = '\0'; } return TRUE; } lpc++; } } if(*name != NULL) { crm_free(*name); } *name = NULL; *value = NULL; return FALSE; } char * crm_concat(const char *prefix, const char *suffix, char join) { int len = 0; char *new_str = NULL; CRM_ASSERT(prefix != NULL); CRM_ASSERT(suffix != NULL); len = strlen(prefix) + strlen(suffix) + 2; crm_malloc0(new_str, (len)); sprintf(new_str, "%s%c%s", prefix, join, suffix); new_str[len-1] = 0; return new_str; } char * generate_hash_key(const char *crm_msg_reference, const char *sys) { char *hash_key = crm_concat(sys?sys:"none", crm_msg_reference, '_'); crm_debug_3("created hash key: (%s)", hash_key); return hash_key; } char * generate_hash_value(const char *src_node, const char *src_subsys) { char *hash_value = NULL; if (src_node == NULL || src_subsys == NULL) { return NULL; } if (strcasecmp(CRM_SYSTEM_DC, src_subsys) == 0) { hash_value = crm_strdup(src_subsys); CRM_ASSERT(hash_value); return hash_value; } hash_value = crm_concat(src_node, src_subsys, '_'); crm_info("created hash value: (%s)", hash_value); return hash_value; } char * crm_itoa(int an_int) { int len = 32; char *buffer = NULL; crm_malloc0(buffer, (len+1)); if(buffer != NULL) { snprintf(buffer, len, "%d", an_int); } return buffer; } extern int LogToLoggingDaemon(int priority, const char * buf, int bstrlen, gboolean use_pri_str); #ifdef HAVE_G_LOG_SET_DEFAULT_HANDLER GLogFunc glib_log_default; static void crm_glib_handler(const gchar *log_domain, GLogLevelFlags flags, const gchar *message, gpointer user_data) { int log_level = LOG_WARNING; GLogLevelFlags msg_level = (flags & G_LOG_LEVEL_MASK); switch(msg_level) { case G_LOG_LEVEL_CRITICAL: /* log and record how we got here */ crm_abort(__FILE__,__PRETTY_FUNCTION__,__LINE__, message, TRUE, TRUE); return; case G_LOG_LEVEL_ERROR: log_level = LOG_ERR; break; case G_LOG_LEVEL_MESSAGE: log_level = LOG_NOTICE; break; case G_LOG_LEVEL_INFO: log_level = LOG_INFO; break; case G_LOG_LEVEL_DEBUG: log_level = LOG_DEBUG; break; case G_LOG_LEVEL_WARNING: case G_LOG_FLAG_RECURSION: case G_LOG_FLAG_FATAL: case G_LOG_LEVEL_MASK: log_level = LOG_WARNING; break; } do_crm_log(log_level, "%s: %s", log_domain, message); } #endif void crm_log_deinit(void) { #ifdef HAVE_G_LOG_SET_DEFAULT_HANDLER g_log_set_default_handler(glib_log_default, NULL); #endif } gboolean crm_log_init( const char *entity, int level, gboolean coredir, gboolean to_stderr, int argc, char **argv) { return crm_log_init_worker(entity, level, coredir, to_stderr, argc, argv, FALSE); } gboolean crm_log_init_quiet( const char *entity, int level, gboolean coredir, gboolean to_stderr, int argc, char **argv) { return crm_log_init_worker(entity, level, coredir, to_stderr, argc, argv, TRUE); } #if SUPPORT_TRACING static int update_trace_data(struct _pcmk_ddebug_query *query, struct _pcmk_ddebug *start, struct _pcmk_ddebug *stop) { int lpc = 0; unsigned nfound = 0; struct _pcmk_ddebug *dp; const char *match = "unknown"; CRM_ASSERT(stop != NULL); CRM_ASSERT(start != NULL); for (dp = start; dp != stop; dp++) { gboolean bump = FALSE; lpc++; /* fprintf(stderr, "checking: %-12s %20s:%u fmt:%s\n", */ /* dp->function, dp->filename, dp->lineno, dp->format); */ if (query->functions && strstr(query->functions, dp->function) != NULL) { match = "function"; bump = TRUE; } if(query->files) { char token[500]; const char *offset = NULL; const char *next = query->files; do { offset = next; next = strchrnul(offset, ','); snprintf(token, 499, "%.*s", (int)(next-offset), offset); if (query->files && strstr(dp->filename, token) != NULL) { match = "file"; bump = TRUE; } else if(next[0] != 0) { next++; } } while(bump == FALSE && next != NULL && next[0] != 0); } if (query->formats && strstr(query->formats, dp->format) != NULL) { match = "format"; bump = TRUE; } if(bump) { nfound++; dp->bump = LOG_NOTICE; do_crm_log_always(LOG_INFO, "Detected '%s' match: %-12s %20s:%u fmt:%s", match, dp->function, dp->filename, dp->lineno, dp->format); } } query->total += lpc; query->matches += nfound; return nfound; } #define _GNU_SOURCE #include #include #include static int ddebug_callback(struct dl_phdr_info *info, size_t size, void *data) { if(strlen(info->dlpi_name) > 0) { struct _pcmk_ddebug_query *query = data; void *handle; void *start; void *stop; char *error; handle = dlopen (info->dlpi_name, RTLD_LAZY); error = dlerror(); if (!handle || error) { crm_err("%s", error); if(handle) { dlclose(handle); } return 0; } start = dlsym(handle, "__start___verbose"); error = dlerror(); if (error) { goto done; } stop = dlsym(handle, "__stop___verbose"); error = dlerror(); if (error) { goto done; } else { unsigned long int len = (unsigned long int)stop - (unsigned long int)start; crm_info("Checking for query matches in %lu trace symbols from: %s (offset: %p)", len/sizeof(struct _pcmk_ddebug), info->dlpi_name, start); update_trace_data(query, start, stop); } done: dlclose(handle); } return 0; } #endif void update_all_trace_data(void) { #if SUPPORT_TRACING gboolean search = FALSE; const char *env_value = NULL; struct _pcmk_ddebug_query query; memset(&query, 0, sizeof(struct _pcmk_ddebug_query)); env_value = getenv("PCMK_trace_files"); if(env_value) { search = TRUE; query.files = env_value; } env_value = getenv("PCMK_trace_formats"); if(env_value) { search = TRUE; query.formats = env_value; } env_value = getenv("PCMK_trace_functions"); if(env_value) { search = TRUE; query.functions = env_value; } if(search) { update_trace_data(&query, __start___verbose, __stop___verbose); dl_iterate_phdr(ddebug_callback, &query); if(query.matches == 0) { do_crm_log_always(LOG_DEBUG, "no matches for query: {fn='%s', file='%s', fmt='%s'} in %llu entries", crm_str(query.functions), crm_str(query.files), crm_str(query.formats), query.total); } else { do_crm_log_always(LOG_INFO, "%llu matches for query: {fn='%s', file='%s', fmt='%s'} in %llu entries", query.matches, crm_str(query.functions), crm_str(query.files), crm_str(query.formats), query.total); } } /* return query.matches; */ #endif } gboolean crm_log_init_worker( const char *entity, int level, gboolean coredir, gboolean to_stderr, int argc, char **argv, gboolean quiet) { /* Redirect messages from glib functions to our handler */ /* cl_malloc_forced_for_glib(); */ #ifdef HAVE_G_LOG_SET_DEFAULT_HANDLER glib_log_default = g_log_set_default_handler(crm_glib_handler, NULL); #endif /* and for good measure... - this enum is a bit field (!) */ g_log_set_always_fatal((GLogLevelFlags)0); /*value out of range*/ if(entity) { crm_system_name = entity; } else if(argc > 0 && argv != NULL) { crm_system_name = basename(argv[0]); if(strstr(crm_system_name, "lt-") == crm_system_name) { crm_system_name += 3; } } else if(crm_system_name == NULL) { crm_system_name = "Unknown"; } setenv("PCMK_service", crm_system_name, 1); cl_log_set_entity(crm_system_name); set_crm_log_level(level); crm_set_env_options(); if(quiet) { /* Nuke any syslog activity */ unsetenv("HA_logfacility"); } else { cl_log_args(argc, argv); if(getenv("HA_logfacility") == NULL) { /* Set a default */ cl_log_set_facility(HA_LOG_FACILITY); } /* else: picked up by crm_set_env_options() */ } cl_log_enable_stderr(to_stderr); if(coredir) { const char *user = getenv("USER"); if(user != NULL && safe_str_neq(user, "root") && safe_str_neq(user, CRM_DAEMON_USER)) { crm_info("Not switching to corefile directory for %s", user); coredir = FALSE; } } if(coredir) { int user = getuid(); const char *base = HA_COREDIR; struct passwd *pwent = getpwuid(user); if (pwent == NULL) { crm_perror(LOG_ERR, "Cannot get name for uid: %d", user); } else if(safe_str_neq(pwent->pw_name, "root") && safe_str_neq(pwent->pw_name, "nobody") && safe_str_neq(pwent->pw_name, CRM_DAEMON_USER)) { crm_debug("Don't change active directory for regular user: %s", pwent->pw_name); } else if (chdir(base) < 0) { crm_perror(LOG_ERR, "Cannot change active directory to %s", base); } else if (chdir(pwent->pw_name) < 0) { crm_perror(LOG_ERR, "Cannot change active directory to %s/%s", base, pwent->pw_name); } else { crm_info("Changed active directory to %s/%s", base, pwent->pw_name); #if 0 { char path[512]; snprintf(path, 512, "%s-%d", crm_system_name, getpid()); mkdir(path, 0750); chdir(path); crm_info("Changed active directory to %s/%s/%s", base, pwent->pw_name, path); } #endif } } update_all_trace_data(); crm_signal(DEBUG_INC, alter_debug); crm_signal(DEBUG_DEC, alter_debug); return TRUE; } /* returns the old value */ unsigned int set_crm_log_level(unsigned int level) { unsigned int old = crm_log_level; crm_log_level = level; return old; } unsigned int get_crm_log_level(void) { return crm_log_level; } static int crm_version_helper(const char *text, char **end_text) { int atoi_result = -1; CRM_ASSERT(end_text != NULL); errno = 0; if(text != NULL && text[0] != 0) { atoi_result = (int)strtol(text, end_text, 10); if(errno == EINVAL) { crm_err("Conversion of '%s' %c failed", text, text[0]); atoi_result = -1; } } return atoi_result; } /* * version1 < version2 : -1 * version1 = version2 : 0 * version1 > version2 : 1 */ int compare_version(const char *version1, const char *version2) { int rc = 0; int lpc = 0; char *ver1_copy = NULL, *ver2_copy = NULL; char *rest1 = NULL, *rest2 = NULL; if(version1 == NULL && version2 == NULL) { return 0; } else if(version1 == NULL) { return -1; } else if(version2 == NULL) { return 1; } ver1_copy = crm_strdup(version1); ver2_copy = crm_strdup(version2); rest1 = ver1_copy; rest2 = ver2_copy; while(1) { int digit1 = 0; int digit2 = 0; lpc++; if(rest1 == rest2) { break; } if(rest1 != NULL) { digit1 = crm_version_helper(rest1, &rest1); } if(rest2 != NULL) { digit2 = crm_version_helper(rest2, &rest2); } if(digit1 < digit2){ rc = -1; crm_debug_5("%d < %d", digit1, digit2); break; } else if (digit1 > digit2){ rc = 1; crm_debug_5("%d > %d", digit1, digit2); break; } if(rest1 != NULL && rest1[0] == '.') { rest1++; } if(rest1 != NULL && rest1[0] == 0) { rest1 = NULL; } if(rest2 != NULL && rest2[0] == '.') { rest2++; } if(rest2 != NULL && rest2[0] == 0) { rest2 = NULL; } } crm_free(ver1_copy); crm_free(ver2_copy); if(rc == 0) { crm_debug_3("%s == %s (%d)", version1, version2, lpc); } else if(rc < 0) { crm_debug_3("%s < %s (%d)", version1, version2, lpc); } else if(rc > 0) { crm_debug_3("%s > %s (%d)", version1, version2, lpc); } return rc; } gboolean do_stderr = FALSE; void alter_debug(int nsig) { crm_signal(DEBUG_INC, alter_debug); crm_signal(DEBUG_DEC, alter_debug); switch(nsig) { case DEBUG_INC: if (crm_log_level < 100) { crm_log_level++; } break; case DEBUG_DEC: if (crm_log_level > 0) { crm_log_level--; } break; default: fprintf(stderr, "Unknown signal %d\n", nsig); cl_log(LOG_ERR, "Unknown signal %d", nsig); break; } } void g_hash_destroy_str(gpointer data) { crm_free(data); } #include /* #include */ /* #include */ long long crm_int_helper(const char *text, char **end_text) { long long result = -1; char *local_end_text = NULL; int saved_errno = 0; 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 saved_errno = errno; /* CRM_CHECK(errno != EINVAL); */ if(errno == EINVAL) { crm_err("Conversion of %s failed", text); result = -1; } else if(errno == ERANGE) { crm_err("Conversion of %s was clipped: %lld", text, result); } else if(errno != 0) { crm_perror(LOG_ERR,"Conversion of %s failed:", text); } if(local_end_text != NULL && local_end_text[0] != '\0') { crm_err("Characters left over after parsing '%s': '%s'", text, local_end_text); } errno = saved_errno; } return result; } int crm_parse_int(const char *text, const char *default_text) { int atoi_result = -1; if(text != NULL) { atoi_result = crm_int_helper(text, NULL); if(errno == 0) { return atoi_result; } } if(default_text != NULL) { atoi_result = crm_int_helper(default_text, NULL); if(errno == 0) { return atoi_result; } } else { crm_err("No default conversion value supplied"); } return -1; } gboolean safe_str_neq(const char *a, const char *b) { if(a == b) { return FALSE; } else if(a==NULL || b==NULL) { return TRUE; } else if(strcasecmp(a, b) == 0) { return FALSE; } return TRUE; } char * crm_strdup_fn(const char *src, const char *file, const char *fn, int line) { char *dup = NULL; CRM_CHECK(src != NULL, crm_err("Could not perform copy at %s:%d (%s)", file, line, fn); return NULL); crm_malloc0(dup, strlen(src) + 1); return strcpy(dup, src); } #define ENV_PREFIX "HA_" void crm_set_env_options(void) { cl_inherit_logging_environment(500); cl_log_set_logd_channel_source(NULL, NULL); if(debug_level > 0 && (debug_level+LOG_INFO) > (int)crm_log_level) { set_crm_log_level(LOG_INFO + debug_level); } } gboolean crm_is_true(const char * s) { gboolean ret = FALSE; if(s != NULL) { crm_str_to_boolean(s, &ret); } return ret; } int crm_str_to_boolean(const char * s, int * ret) { if(s == NULL) { return -1; } else if (strcasecmp(s, "true") == 0 || strcasecmp(s, "on") == 0 || strcasecmp(s, "yes") == 0 || strcasecmp(s, "y") == 0 || strcasecmp(s, "1") == 0){ *ret = TRUE; return 1; } else if (strcasecmp(s, "false") == 0 || strcasecmp(s, "off") == 0 || strcasecmp(s, "no") == 0 || strcasecmp(s, "n") == 0 || strcasecmp(s, "0") == 0){ *ret = FALSE; return 1; } return -1; } #ifndef NUMCHARS # define NUMCHARS "0123456789." #endif #ifndef WHITESPACE # define WHITESPACE " \t\n\r\f" #endif unsigned long long crm_get_interval(const char * input) { ha_time_t *interval = NULL; char *input_copy = crm_strdup(input); char *input_copy_mutable = input_copy; unsigned long long msec = 0; if(input == NULL) { return 0; } else if(input[0] != 'P') { crm_free(input_copy); return crm_get_msec(input); } interval = parse_time_duration(&input_copy_mutable); msec = date_in_seconds(interval); free_ha_date(interval); crm_free(input_copy); return msec * 1000; } long long crm_get_msec(const char * input) { const char *cp = input; const char *units; long long multiplier = 1000; long long divisor = 1; long long msec = -1; char *end_text = NULL; /* double dret; */ if(input == NULL) { return msec; } cp += strspn(cp, WHITESPACE); units = cp + strspn(cp, NUMCHARS); units += strspn(units, WHITESPACE); if (strchr(NUMCHARS, *cp) == NULL) { return msec; } if (strncasecmp(units, "ms", 2) == 0 || strncasecmp(units, "msec", 4) == 0) { multiplier = 1; divisor = 1; } else if (strncasecmp(units, "us", 2) == 0 || strncasecmp(units, "usec", 4) == 0) { multiplier = 1; divisor = 1000; } else if (strncasecmp(units, "s", 1) == 0 || strncasecmp(units, "sec", 3) == 0) { multiplier = 1000; divisor = 1; } else if (strncasecmp(units, "m", 1) == 0 || strncasecmp(units, "min", 3) == 0) { multiplier = 60*1000; divisor = 1; } else if (strncasecmp(units, "h", 1) == 0 || strncasecmp(units, "hr", 2) == 0) { multiplier = 60*60*1000; divisor = 1; } else if (*units != EOS && *units != '\n' && *units != '\r') { return msec; } msec = crm_int_helper(cp, &end_text); msec *= multiplier; msec /= divisor; /* dret += 0.5; */ /* msec = (long long)dret; */ return msec; } const char * op_status2text(op_status_t status) { switch(status) { case LRM_OP_PENDING: return "pending"; break; case LRM_OP_DONE: return "complete"; break; case LRM_OP_ERROR: return "Error"; break; case LRM_OP_TIMEOUT: return "Timed Out"; break; case LRM_OP_NOTSUPPORTED: return "NOT SUPPORTED"; break; case LRM_OP_CANCELLED: return "Cancelled"; break; } crm_err("Unknown status: %d", status); return "UNKNOWN!"; } char * generate_op_key(const char *rsc_id, const char *op_type, int interval) { int len = 35; char *op_id = NULL; CRM_CHECK(rsc_id != NULL, return NULL); CRM_CHECK(op_type != NULL, return NULL); len += strlen(op_type); len += strlen(rsc_id); crm_malloc0(op_id, len); CRM_CHECK(op_id != NULL, return NULL); sprintf(op_id, "%s_%s_%d", rsc_id, op_type, interval); return op_id; } gboolean parse_op_key(const char *key, char **rsc_id, char **op_type, int *interval) { char *notify = NULL; char *mutable_key = NULL; char *mutable_key_ptr = NULL; int len = 0, offset = 0, ch = 0; CRM_CHECK(key != NULL, return FALSE); *interval = 0; len = strlen(key); offset = len-1; crm_debug_3("Source: %s", key); while(offset > 0 && isdigit(key[offset])) { int digits = len-offset; ch = key[offset] - '0'; CRM_CHECK(ch < 10, return FALSE); CRM_CHECK(ch >= 0, return FALSE); while(digits > 1) { digits--; ch = ch * 10; } *interval += ch; offset--; } crm_debug_3(" Interval: %d", *interval); CRM_CHECK(key[offset] == '_', return FALSE); mutable_key = crm_strdup(key); mutable_key_ptr = mutable_key_ptr; mutable_key[offset] = 0; offset--; while(offset > 0 && key[offset] != '_') { offset--; } CRM_CHECK(key[offset] == '_', crm_free(mutable_key); return FALSE); mutable_key_ptr = mutable_key+offset+1; crm_debug_3(" Action: %s", mutable_key_ptr); *op_type = crm_strdup(mutable_key_ptr); mutable_key[offset] = 0; offset--; CRM_CHECK(mutable_key != mutable_key_ptr, crm_free(mutable_key); return FALSE); notify = strstr(mutable_key, "_post_notify"); if(safe_str_eq(notify, "_post_notify")) { notify[0] = 0; } notify = strstr(mutable_key, "_pre_notify"); if(safe_str_eq(notify, "_pre_notify")) { notify[0] = 0; } crm_debug_3(" Resource: %s", mutable_key); *rsc_id = mutable_key; return TRUE; } char * generate_notify_key(const char *rsc_id, const char *notify_type, const char *op_type) { int len = 12; char *op_id = NULL; CRM_CHECK(rsc_id != NULL, return NULL); CRM_CHECK(op_type != NULL, return NULL); CRM_CHECK(notify_type != NULL, return NULL); len += strlen(op_type); len += strlen(rsc_id); len += strlen(notify_type); crm_malloc0(op_id, len); if(op_id != NULL) { sprintf(op_id, "%s_%s_notify_%s_0", rsc_id, notify_type, op_type); } return op_id; } char * generate_transition_magic_v202(const char *transition_key, int op_status) { int len = 80; char *fail_state = NULL; CRM_CHECK(transition_key != NULL, return NULL); len += strlen(transition_key); crm_malloc0(fail_state, len); if(fail_state != NULL) { snprintf(fail_state, len, "%d:%s", op_status,transition_key); } return fail_state; } char * generate_transition_magic(const char *transition_key, int op_status, int op_rc) { int len = 80; char *fail_state = NULL; CRM_CHECK(transition_key != NULL, return NULL); len += strlen(transition_key); crm_malloc0(fail_state, len); if(fail_state != NULL) { snprintf(fail_state, len, "%d:%d;%s", op_status, op_rc, transition_key); } return fail_state; } gboolean decode_transition_magic( const char *magic, char **uuid, int *transition_id, int *action_id, int *op_status, int *op_rc, int *target_rc) { int res = 0; char *key = NULL; gboolean result = TRUE; CRM_CHECK(magic != NULL, return FALSE); CRM_CHECK(op_rc != NULL, return FALSE); CRM_CHECK(op_status != NULL, return FALSE); crm_malloc0(key, strlen(magic)+1); res = sscanf(magic, "%d:%d;%s", op_status, op_rc, key); if(res != 3) { crm_crit("Only found %d items in: %s", res, magic); result = FALSE; goto bail; } CRM_CHECK(decode_transition_key(key, uuid, transition_id, action_id, target_rc), result = FALSE; goto bail; ); bail: crm_free(key); return result; } char * generate_transition_key(int transition_id, int action_id, int target_rc, const char *node) { int len = 40; char *fail_state = NULL; CRM_CHECK(node != NULL, return NULL); len += strlen(node); crm_malloc0(fail_state, len); if(fail_state != NULL) { snprintf(fail_state, len, "%d:%d:%d:%s", action_id, transition_id, target_rc, node); } return fail_state; } gboolean decode_transition_key( const char *key, char **uuid, int *transition_id, int *action_id, int *target_rc) { int res = 0; gboolean done = FALSE; CRM_CHECK(uuid != NULL, return FALSE); CRM_CHECK(target_rc != NULL, return FALSE); CRM_CHECK(action_id != NULL, return FALSE); CRM_CHECK(transition_id != NULL, return FALSE); crm_malloc0(*uuid, strlen(key)+1); res = sscanf(key, "%d:%d:%d:%s", action_id, transition_id, target_rc, *uuid); switch(res) { case 4: /* Post Pacemaker 0.6 */ done = TRUE; break; case 3: case 2: /* this can be tricky - the UUID might start with an integer */ /* Until Pacemaker 0.6 */ done = TRUE; *target_rc = -1; res = sscanf(key, "%d:%d:%s", action_id, transition_id, *uuid); if(res == 2) { *action_id = -1; res = sscanf(key, "%d:%s", transition_id, *uuid); CRM_CHECK(res == 2, done = FALSE); } else if(res != 3) { CRM_CHECK(res == 3, done = FALSE); } break; case 1: /* Prior to Heartbeat 2.0.8 */ done = TRUE; *action_id = -1; *target_rc = -1; res = sscanf(key, "%d:%s", transition_id, *uuid); CRM_CHECK(res == 2, done = FALSE); break; default: crm_crit("Unhandled sscanf result (%d) for %s", res, key); } if(strlen(*uuid) != 36) { crm_warn("Bad UUID (%s) in sscanf result (%d) for %s", *uuid, res, key); } if(done == FALSE) { crm_err("Cannot decode '%s' rc=%d", key, res); crm_free(*uuid); *uuid = NULL; *target_rc = -1; *action_id = -1; *transition_id = -1; } return done; } void filter_action_parameters(xmlNode *param_set, const char *version) { char *key = NULL; char *timeout = NULL; char *interval = NULL; const char *attr_filter[] = { XML_ATTR_ID, XML_ATTR_CRM_VERSION, XML_LRM_ATTR_OP_DIGEST, }; gboolean do_delete = FALSE; int lpc = 0; static int meta_len = 0; if(meta_len == 0) { meta_len = strlen(CRM_META); } if(param_set == NULL) { return; } for(lpc = 0; lpc < DIMOF(attr_filter); lpc++) { xml_remove_prop(param_set, attr_filter[lpc]); } key = crm_meta_name(XML_LRM_ATTR_INTERVAL); interval = crm_element_value_copy(param_set, key); crm_free(key); key = crm_meta_name(XML_ATTR_TIMEOUT); timeout = crm_element_value_copy(param_set, key); - - xml_prop_name_iter(param_set, prop_name, + + if(param_set) { + xmlAttrPtr xIter = param_set->properties; + while(xIter) { + const char *prop_name = (const char *)xIter->name; + xIter = xIter->next; do_delete = FALSE; if(strncasecmp(prop_name, CRM_META, meta_len) == 0) { do_delete = TRUE; } if(do_delete) { xml_remove_prop(param_set, prop_name); } - ); + } + } if(crm_get_msec(interval) > 0 && compare_version(version, "1.0.8") > 0) { /* Re-instate the operation's timeout value */ if(timeout != NULL) { crm_xml_add(param_set, key, timeout); } } crm_free(interval); crm_free(timeout); crm_free(key); } void filter_reload_parameters(xmlNode *param_set, const char *restart_string) { int len = 0; char *name = NULL; char *match = NULL; if(param_set == NULL) { return; } - xml_prop_name_iter(param_set, prop_name, + if(param_set) { + xmlAttrPtr xIter = param_set->properties; + while(xIter) { + const char *prop_name = (const char *)xIter->name; + xIter = xIter->next; name = NULL; len = strlen(prop_name) + 3; crm_malloc0(name, len); sprintf(name, " %s ", prop_name); name[len-1] = 0; match = strstr(restart_string, name); if(match == NULL) { crm_debug_3("%s not found in %s", prop_name, restart_string); xml_remove_prop(param_set, prop_name); } crm_free(name); - ); + } + } } void crm_abort(const char *file, const char *function, int line, const char *assert_condition, gboolean do_core, gboolean do_fork) { int rc = 0; int pid = 0; int status = 0; if(do_core == FALSE) { do_crm_log(LOG_ERR, "%s: Triggered assert at %s:%d : %s", function, file, line, assert_condition); return; } else if(do_fork) { pid=fork(); } else { do_crm_log(LOG_ERR, "%s: Triggered fatal assert at %s:%d : %s", function, file, line, assert_condition); } switch(pid) { case -1: do_crm_log(LOG_CRIT, "%s: Cannot create core for non-fatal assert at %s:%d : %s", function, file, line, assert_condition); return; case 0: /* Child */ abort(); break; default: /* Parent */ do_crm_log(LOG_ERR, "%s: Forked child %d to record non-fatal assert at %s:%d : %s", function, pid, file, line, assert_condition); do { rc = waitpid(pid, &status, 0); if(rc < 0 && errno != EINTR) { crm_perror(LOG_ERR,"%s: Cannot wait on forked child %d", function, pid); } } while(rc < 0 && errno == EINTR); return; } } char * generate_series_filename( const char *directory, const char *series, int sequence, gboolean bzip) { int len = 40; char *filename = NULL; const char *ext = "raw"; CRM_CHECK(directory != NULL, return NULL); CRM_CHECK(series != NULL, return NULL); len += strlen(directory); len += strlen(series); crm_malloc0(filename, len); CRM_CHECK(filename != NULL, return NULL); if(bzip) { ext = "bz2"; } sprintf(filename, "%s/%s-%d.%s", directory, series, sequence, ext); return filename; } int get_last_sequence(const char *directory, const char *series) { FILE *file_strm = NULL; int start = 0, length = 0, read_len = 0; char *series_file = NULL; char *buffer = NULL; int seq = 0; int len = 36; CRM_CHECK(directory != NULL, return 0); CRM_CHECK(series != NULL, return 0); len += strlen(directory); len += strlen(series); crm_malloc0(series_file, len); CRM_CHECK(series_file != NULL, return 0); sprintf(series_file, "%s/%s.last", directory, series); file_strm = fopen(series_file, "r"); if(file_strm == NULL) { crm_debug("Series file %s does not exist", series_file); crm_free(series_file); return 0; } /* see how big the file is */ start = ftell(file_strm); fseek(file_strm, 0L, SEEK_END); length = ftell(file_strm); fseek(file_strm, 0L, start); CRM_ASSERT(length >= 0); CRM_ASSERT(start == ftell(file_strm)); if(length <= 0) { crm_info("%s was not valid", series_file); crm_free(buffer); buffer = NULL; } else { crm_debug_3("Reading %d bytes from file", length); crm_malloc0(buffer, (length+1)); read_len = fread(buffer, 1, length, file_strm); if(read_len != length) { crm_err("Calculated and read bytes differ: %d vs. %d", length, read_len); crm_free(buffer); buffer = NULL; } } crm_free(series_file); seq = crm_parse_int(buffer, "0"); crm_free(buffer); fclose(file_strm); return seq; } void write_last_sequence( const char *directory, const char *series, int sequence, int max) { int rc = 0; int len = 36; FILE *file_strm = NULL; char *series_file = NULL; CRM_CHECK(directory != NULL, return); CRM_CHECK(series != NULL, return); if(max == 0) { return; } while(max > 0 && sequence > max) { sequence -= max; } len += strlen(directory); len += strlen(series); crm_malloc0(series_file, len); sprintf(series_file, "%s/%s.last", directory, series); file_strm = fopen(series_file, "w"); if(file_strm == NULL) { crm_err("Cannout open series file %s for writing", series_file); goto bail; } rc = fprintf(file_strm, "%d", sequence); if(rc < 0) { crm_perror(LOG_ERR,"Cannot write to series file %s", series_file); } bail: if(file_strm != NULL) { fflush(file_strm); fclose(file_strm); } crm_free(series_file); } #define LOCKSTRLEN 11 int crm_pid_active(long pid) { if(pid <= 0) { return -1; } else if (kill(pid, 0) < 0 && errno == ESRCH) { return 0; } #ifndef HAVE_PROC_PID return 1; #else { int rc = 0; int running = 0; char proc_path[PATH_MAX], exe_path[PATH_MAX], myexe_path[PATH_MAX]; /* check to make sure pid hasn't been reused by another process */ snprintf(proc_path, sizeof(proc_path), "/proc/%lu/exe", pid); rc = readlink(proc_path, exe_path, PATH_MAX-1); if(rc < 0) { crm_perror(LOG_ERR, "Could not read from %s", proc_path); goto bail; } exe_path[rc] = 0; snprintf(proc_path, sizeof(proc_path), "/proc/%lu/exe", (long unsigned int)getpid()); rc = readlink(proc_path, myexe_path, PATH_MAX-1); if(rc < 0) { crm_perror(LOG_ERR, "Could not read from %s", proc_path); goto bail; } myexe_path[rc] = 0; if(strcmp(exe_path, myexe_path) == 0) { running = 1; } } bail: return running; #endif } int crm_read_pidfile(const char *filename) { int fd; long pid = -1; char buf[LOCKSTRLEN+1]; if ((fd = open(filename, O_RDONLY)) < 0) { goto bail; } if (read(fd, buf, sizeof(buf)) < 1) { goto bail; } if (sscanf(buf, "%lu", &pid) > 0) { if (pid <= 0){ pid = -LSB_STATUS_STOPPED; } } bail: if(fd >= 0) { close(fd); } return pid; } int crm_lock_pidfile(const char *filename) { struct stat sbuf; int fd = 0, rc = 0; long pid = 0, mypid = 0; char lf_name[256], tf_name[256], buf[LOCKSTRLEN+1]; mypid = (unsigned long) getpid(); snprintf(lf_name, sizeof(lf_name), "%s",filename); snprintf(tf_name, sizeof(tf_name), "%s.%lu", filename, mypid); if ((fd = open(lf_name, O_RDONLY)) >= 0) { if (fstat(fd, &sbuf) >= 0 && sbuf.st_size < LOCKSTRLEN) { sleep(1); /* if someone was about to create one, * give'm a sec to do so * Though if they follow our protocol, * this won't happen. They should really * put the pid in, then link, not the * other way around. */ } if (read(fd, buf, sizeof(buf)) > 0) { if (sscanf(buf, "%lu", &pid) > 0) { if (pid > 1 && pid != getpid() && crm_pid_active(pid)) { /* locked by existing process - give up */ close(fd); return -1; } } } unlink(lf_name); close(fd); } if ((fd = open(tf_name, O_CREAT | O_WRONLY | O_EXCL, 0644)) < 0) { /* Hmmh, why did we fail? Anyway, nothing we can do about it */ return -3; } /* Slight overkill with the %*d format ;-) */ snprintf(buf, sizeof(buf), "%*lu\n", LOCKSTRLEN-1, mypid); if (write(fd, buf, LOCKSTRLEN) != LOCKSTRLEN) { /* Again, nothing we can do about this */ rc = -3; close(fd); goto out; } close(fd); switch (link(tf_name, lf_name)) { case 0: if (stat(tf_name, &sbuf) < 0) { /* something weird happened */ rc = -3; } else if (sbuf.st_nlink < 2) { /* somehow, it didn't get through - NFS trouble? */ rc = -2; } else { rc = 0; } break; case EEXIST: rc = -1; break; default: rc = -3; } out: unlink(tf_name); return rc; } void crm_make_daemon(const char *name, gboolean daemonize, const char *pidfile) { long pid; const char *devnull = "/dev/null"; if(daemonize == FALSE) { return; } pid = fork(); if (pid < 0) { fprintf(stderr, "%s: could not start daemon\n", name); crm_perror(LOG_ERR,"fork"); exit(LSB_EXIT_GENERIC); } else if (pid > 0) { exit(LSB_EXIT_OK); } if (crm_lock_pidfile(pidfile) < 0 ) { pid = crm_read_pidfile(pidfile); if(crm_pid_active(pid) > 0) { crm_warn("%s: already running [pid %ld] (%s).\n", name, pid, pidfile); exit(LSB_EXIT_OK); } } umask(022); close(STDIN_FILENO); (void)open(devnull, O_RDONLY); /* Stdin: fd 0 */ close(STDOUT_FILENO); (void)open(devnull, O_WRONLY); /* Stdout: fd 1 */ close(STDERR_FILENO); (void)open(devnull, O_WRONLY); /* Stderr: fd 2 */ } gboolean crm_is_writable(const char *dir, const char *file, const char *user, const char *group, gboolean need_both) { int s_res = -1; struct stat buf; char *full_file = NULL; const char *target = NULL; gboolean pass = TRUE; gboolean readwritable = FALSE; CRM_ASSERT(dir != NULL); if(file != NULL) { full_file = crm_concat(dir, file, '/'); target = full_file; s_res = stat(full_file, &buf); if( s_res == 0 && S_ISREG(buf.st_mode) == FALSE ) { crm_err("%s must be a regular file", target); pass = FALSE; goto out; } } if (s_res != 0) { target = dir; s_res = stat(dir, &buf); if(s_res != 0) { crm_err("%s must exist and be a directory", dir); pass = FALSE; goto out; } else if( S_ISDIR(buf.st_mode) == FALSE ) { crm_err("%s must be a directory", dir); pass = FALSE; } } if(user) { struct passwd *sys_user = NULL; sys_user = getpwnam(user); readwritable = (sys_user != NULL && buf.st_uid == sys_user->pw_uid && (buf.st_mode & (S_IRUSR|S_IWUSR))); if(readwritable == FALSE) { crm_err("%s must be owned and r/w by user %s", target, user); if(need_both) { pass = FALSE; } } } if(group) { struct group *sys_grp = getgrnam(group); readwritable = ( sys_grp != NULL && buf.st_gid == sys_grp->gr_gid && (buf.st_mode & (S_IRGRP|S_IWGRP))); if(readwritable == FALSE) { if(need_both || user == NULL) { pass = FALSE; crm_err("%s must be owned and r/w by group %s", target, group); } else { crm_warn("%s should be owned and r/w by group %s", target, group); } } } out: crm_free(full_file); return pass; } static unsigned long long crm_bit_filter = 0; /* 0x00000002ULL; */ static unsigned int bit_log_level = LOG_DEBUG_5; long long crm_clear_bit(const char *function, long long word, long long bit) { unsigned int level = bit_log_level; if(bit & crm_bit_filter) { level = LOG_ERR; } do_crm_log_unlikely(level, "Bit 0x%.16llx cleared by %s", bit, function); word &= ~bit; return word; } long long crm_set_bit(const char *function, long long word, long long bit) { unsigned int level = bit_log_level; if(bit & crm_bit_filter) { level = LOG_ERR; } do_crm_log_unlikely(level, "Bit 0x%.16llx set by %s", bit, function); word |= bit; return word; } gboolean crm_str_eq(const char *a, const char *b, gboolean use_case) { if(a == b) { return TRUE; } else if(a == NULL || b == NULL) { /* shouldn't be comparing NULLs */ return FALSE; } else if(use_case && a[0] != b[0]) { return FALSE; } else if(strcasecmp(a, b) == 0) { return TRUE; } return FALSE; } char *crm_meta_name(const char *field) { int lpc = 0; int max = 0; char *crm_name = NULL; CRM_CHECK(field != NULL, return NULL); crm_name = crm_concat(CRM_META, field, '_'); /* Massage the names so they can be used as shell variables */ max = strlen(crm_name); for(; lpc < max; lpc++) { switch(crm_name[lpc]) { case '-': crm_name[lpc] = '_'; break; } } return crm_name; } const char *crm_meta_value(GHashTable *hash, const char *field) { char *key = NULL; const char *value = NULL; key = crm_meta_name(field); if(key) { value = g_hash_table_lookup(hash, key); crm_free(key); } return value; } static struct crm_option *crm_long_options = NULL; static const char *crm_app_description = NULL; static const char *crm_short_options = NULL; static const char *crm_app_usage = NULL; static struct option *crm_create_long_opts(struct crm_option *long_options) { struct option *long_opts = NULL; #ifdef HAVE_GETOPT_H int index = 0, lpc = 0; /* * A previous, possibly poor, choice of '?' as the short form of --help * means that getopt_long() returns '?' for both --help and for "unknown option" * * This dummy entry allows us to differentiate between the two in crm_get_option() * and exit with the correct error code */ crm_realloc(long_opts, (index+1) * sizeof(struct option)); long_opts[index].name = "__dummmy__"; long_opts[index].has_arg = 0; long_opts[index].flag = 0; long_opts[index].val = '_'; index++; for(lpc = 0; long_options[lpc].name != NULL; lpc++) { if(long_options[lpc].name[0] == '-') { continue; } crm_realloc(long_opts, (index+1) * sizeof(struct option)); /*fprintf(stderr, "Creating %d %s = %c\n", index, * long_options[lpc].name, long_options[lpc].val); */ long_opts[index].name = long_options[lpc].name; long_opts[index].has_arg = long_options[lpc].has_arg; long_opts[index].flag = long_options[lpc].flag; long_opts[index].val = long_options[lpc].val; index++; } /* Now create the list terminator */ crm_realloc(long_opts, (index+1) * sizeof(struct option)); long_opts[index].name = NULL; long_opts[index].has_arg = 0; long_opts[index].flag = 0; long_opts[index].val = 0; #endif return long_opts; } void crm_set_options(const char *short_options, const char *app_usage, struct crm_option *long_options, const char *app_desc) { if(short_options) { crm_short_options = short_options; } else if(long_options) { int lpc = 0; int opt_string_len = 0; char *local_short_options = NULL; for(lpc = 0; long_options[lpc].name != NULL; lpc++) { if(long_options[lpc].val) { crm_realloc(local_short_options, opt_string_len + 3); local_short_options[opt_string_len++] = long_options[lpc].val; if(long_options[lpc].has_arg == required_argument) { local_short_options[opt_string_len++] = ':'; } local_short_options[opt_string_len] = 0; } } crm_short_options = local_short_options; crm_trace("Generated short option string: '%s'", local_short_options); } if(long_options) { crm_long_options = long_options; } if(app_desc) { crm_app_description = app_desc; } if(app_usage) { crm_app_usage = app_usage; } } int crm_get_option(int argc, char **argv, int *index) { #ifdef HAVE_GETOPT_H static struct option *long_opts = NULL; if(long_opts == NULL && crm_long_options) { long_opts = crm_create_long_opts(crm_long_options); } if(long_opts) { int flag = getopt_long(argc, argv, crm_short_options, long_opts, index); switch(flag) { case 0: return long_opts[*index].val; case -1: /* End of option processing */ break; case ':': crm_debug_2("Missing argument"); crm_help('?', 1); break; case '?': crm_help('?', *index?0:1); break; } return flag; } #endif if(crm_short_options) { return getopt(argc, argv, crm_short_options); } return -1; } void crm_help(char cmd, int exit_code) { int i = 0; FILE *stream = (exit_code ? stderr : stdout); if(cmd == 'v' || cmd == '$') { fprintf(stream, "Pacemaker %s\n", VERSION); fprintf(stream, "Written by Andrew Beekhof\n"); goto out; } if(cmd == '!') { fprintf(stream, "Pacemaker %s (Build: %s): %s\n", VERSION, BUILD_VERSION, CRM_FEATURES); goto out; } fprintf(stream, "%s - %s\n", crm_system_name, crm_app_description); if(crm_app_usage) { fprintf(stream, "Usage: %s %s\n", crm_system_name, crm_app_usage); } if(crm_long_options) { fprintf(stream, "Options:\n"); for(i = 0; crm_long_options[i].name != NULL; i++) { if(crm_long_options[i].flags & pcmk_option_hidden) { } else if(crm_long_options[i].flags & pcmk_option_paragraph) { fprintf(stream, "%s\n\n", crm_long_options[i].desc); } else if(crm_long_options[i].flags & pcmk_option_example) { fprintf(stream, "\t#%s\n\n", crm_long_options[i].desc); } else if(crm_long_options[i].val == '-' && crm_long_options[i].desc) { fprintf(stream, "%s\n", crm_long_options[i].desc); } else { /* is val printable as char ? */ if(crm_long_options[i].val <= UCHAR_MAX) { fprintf(stream, " -%c,", crm_long_options[i].val); } else { fputs(" ", stream); } fprintf(stream, " --%s%c%s\t%s\n", crm_long_options[i].name, crm_long_options[i].has_arg?'=':' ',crm_long_options[i].has_arg?"value":"", crm_long_options[i].desc?crm_long_options[i].desc:""); } } } else if(crm_short_options) { fprintf(stream, "Usage: %s - %s\n", crm_system_name, crm_app_description); for(i = 0; crm_short_options[i] != 0; i++) { int has_arg = FALSE; if(crm_short_options[i+1] == ':') { has_arg = TRUE; } fprintf(stream, " -%c %s\n", crm_short_options[i], has_arg?"{value}":""); if(has_arg) { i++; } } } fprintf(stream, "\nReport bugs to %s\n", PACKAGE_BUGREPORT); out: if(exit_code >= 0) { exit(exit_code); } } #include <../../tools/attrd.h> gboolean attrd_update_delegate(IPC_Channel *cluster, char command, const char *host, const char *name, const char *value, const char *section, const char *set, const char *dampen, const char *user_name) { gboolean success = FALSE; const char *reason = "Cluster connection failed"; /* remap common aliases */ if(safe_str_eq(section, "reboot")) { section = XML_CIB_TAG_STATUS; } else if(safe_str_eq(section, "forever")) { section = XML_CIB_TAG_NODES; } if(cluster == NULL) { reason = "No connection to the cluster"; } else { xmlNode *update = create_xml_node(NULL, __FUNCTION__); crm_xml_add(update, F_TYPE, T_ATTRD); crm_xml_add(update, F_ORIG, crm_system_name); if(name == NULL && command == 'U') { command = 'R'; } switch(command) { case 'D': case 'U': case 'v': crm_xml_add(update, F_ATTRD_TASK, "update"); crm_xml_add(update, F_ATTRD_ATTRIBUTE, name); break; case 'R': crm_xml_add(update, F_ATTRD_TASK, "refresh"); break; case 'q': crm_xml_add(update, F_ATTRD_TASK, "query"); break; } crm_xml_add(update, F_ATTRD_VALUE, value); crm_xml_add(update, F_ATTRD_DAMPEN, dampen); crm_xml_add(update, F_ATTRD_SECTION, section); crm_xml_add(update, F_ATTRD_HOST, host); crm_xml_add(update, F_ATTRD_SET, set); #if ENABLE_ACL if (user_name) { crm_xml_add(update, F_ATTRD_USER, user_name); } #endif success = send_ipc_message(cluster, update); free_xml(update); } if(success) { crm_debug("Sent update: %s=%s for %s", name, value, host?host:"localhost"); return TRUE; } crm_info("Could not send update: %s=%s for %s", name, value, host?host:"localhost"); return FALSE; } gboolean attrd_lazy_update(char command, const char *host, const char *name, const char *value, const char *section, const char *set, const char *dampen) { int max = 5; gboolean updated = FALSE; static IPC_Channel *cluster = NULL; while(updated == 0 && max > 0) { if(cluster == NULL) { crm_info("Connecting to cluster... %d retries remaining", max); cluster = init_client_ipc_comms_nodispatch(T_ATTRD); } if(cluster != NULL) { updated = attrd_update(cluster, command, host, name, value, section, set, dampen); } if(updated == 0) { cluster = NULL; sleep(2); max--; } else { crm_info("Updated %s=%s for %s", name, value, host); } } return updated; } gboolean attrd_update_no_mainloop(int *connection, char command, const char *host, const char *name, const char *value, const char *section, const char *set, const char *dampen) { int max = 5; gboolean updated = FALSE; static IPC_Channel *cluster = NULL; if(connection && *connection == 0 && cluster) { crm_info("Forcing a new connection to the cluster"); cluster = NULL; } while(updated == 0 && max > 0) { if(cluster == NULL) { crm_info("Connecting to cluster... %d retries remaining", max); cluster = init_client_ipc_comms_nodispatch(T_ATTRD); } if(connection) { if(cluster != NULL) { *connection = cluster->ops->get_recv_select_fd(cluster); } else { *connection = 0; } } if(cluster != NULL) { updated = attrd_update(cluster, command, host, name, value, section, set, dampen); } if(updated == 0) { cluster = NULL; sleep(2); max--; } else { crm_info("Updated %s=%s for %s", name, value, host); } } return updated; } #define FAKE_TE_ID "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" static void append_digest(lrm_op_t *op, xmlNode *update, const char *version, const char *magic, int level) { /* this will enable us to later determine that the * resource's parameters have changed and we should force * a restart */ char *digest = NULL; xmlNode *args_xml = NULL; if(op->params == NULL) { return; } args_xml = create_xml_node(NULL, XML_TAG_PARAMS); g_hash_table_foreach(op->params, hash2field, args_xml); filter_action_parameters(args_xml, version); digest = calculate_operation_digest(args_xml, version); #if 0 if(level < crm_log_level && op->interval == 0 && crm_str_eq(op->op_type, CRMD_ACTION_START, TRUE)) { char *digest_source = dump_xml_unformatted(args_xml); do_crm_log(level, "Calculated digest %s for %s (%s). Source: %s\n", digest, ID(update), magic, digest_source); crm_free(digest_source); } #endif crm_xml_add(update, XML_LRM_ATTR_OP_DIGEST, digest); free_xml(args_xml); crm_free(digest); } int rsc_op_expected_rc(lrm_op_t *op) { int rc = 0; if(op && op->user_data) { int dummy = 0; char *uuid = NULL; decode_transition_key(op->user_data, &uuid, &dummy, &dummy, &rc); crm_free(uuid); } return rc; } gboolean did_rsc_op_fail(lrm_op_t *op, int target_rc) { switch(op->op_status) { case LRM_OP_CANCELLED: case LRM_OP_PENDING: return FALSE; break; case LRM_OP_NOTSUPPORTED: case LRM_OP_TIMEOUT: case LRM_OP_ERROR: return TRUE; break; default: if(target_rc != op->rc) { return TRUE; } } return FALSE; } xmlNode * create_operation_update( xmlNode *parent, lrm_op_t *op, const char *caller_version, int target_rc, const char *origin, int level) { char *key = NULL; char *magic = NULL; char *op_id = NULL; char *local_user_data = NULL; xmlNode *xml_op = NULL; const char *task = NULL; gboolean dc_munges_migrate_ops = (compare_version(caller_version, "3.0.3") < 0); CRM_CHECK(op != NULL, return NULL); do_crm_log(level, "%s: Updating resouce %s after %s %s op (interval=%d)", origin, op->rsc_id, op_status2text(op->op_status), op->op_type, op->interval); if(op->op_status == LRM_OP_CANCELLED) { crm_debug_3("Ignoring cancelled op"); return NULL; } crm_debug_3("DC version: %s", caller_version); task = op->op_type; /* remap the task name under various scenarios * this makes life easier for the PE when its trying determin the current state */ if(crm_str_eq(task, "reload", TRUE)) { if(op->op_status == LRM_OP_DONE) { task = CRMD_ACTION_START; } else { task = CRMD_ACTION_STATUS; } } else if(dc_munges_migrate_ops && crm_str_eq(task, CRMD_ACTION_MIGRATE, TRUE)) { /* if the migrate_from fails it will have enough info to do the right thing */ if(op->op_status == LRM_OP_DONE) { task = CRMD_ACTION_STOP; } else { task = CRMD_ACTION_STATUS; } } else if(dc_munges_migrate_ops && op->op_status == LRM_OP_DONE && crm_str_eq(task, CRMD_ACTION_MIGRATED, TRUE)) { task = CRMD_ACTION_START; } key = generate_op_key(op->rsc_id, task, op->interval); if(op->interval > 0) { op_id = crm_strdup(key); } else if(crm_str_eq(task, CRMD_ACTION_NOTIFY, TRUE)) { const char *n_type = crm_meta_value(op->params, "notify_type"); const char *n_task = crm_meta_value(op->params, "notify_operation"); CRM_LOG_ASSERT(n_type != NULL); CRM_LOG_ASSERT(n_task != NULL); op_id = generate_notify_key(op->rsc_id, n_type, n_task); /* these are not yet allowed to fail */ op->op_status = LRM_OP_DONE; op->rc = 0; } else if (did_rsc_op_fail(op, target_rc)) { op_id = generate_op_key(op->rsc_id, "last_failure", 0); } else { op_id = generate_op_key(op->rsc_id, "last", 0); } xml_op = find_entity(parent, XML_LRM_TAG_RSC_OP, op_id); if(xml_op == NULL) { xml_op = create_xml_node(parent, XML_LRM_TAG_RSC_OP); } if(op->user_data == NULL) { crm_debug("Generating fake transition key for:" " %s_%s_%d %d from %s", op->rsc_id, op->op_type, op->interval, op->call_id, op->app_name); local_user_data = generate_transition_key(-1, op->call_id, target_rc, FAKE_TE_ID); op->user_data = local_user_data; } magic = generate_transition_magic(op->user_data, op->op_status, op->rc); crm_xml_add(xml_op, XML_ATTR_ID, op_id); crm_xml_add(xml_op, XML_LRM_ATTR_TASK_KEY, key); crm_xml_add(xml_op, XML_LRM_ATTR_TASK, task); crm_xml_add(xml_op, XML_ATTR_ORIGIN, origin); crm_xml_add(xml_op, XML_ATTR_CRM_VERSION, caller_version); crm_xml_add(xml_op, XML_ATTR_TRANSITION_KEY, op->user_data); crm_xml_add(xml_op, XML_ATTR_TRANSITION_MAGIC, magic); crm_xml_add_int(xml_op, XML_LRM_ATTR_CALLID, op->call_id); crm_xml_add_int(xml_op, XML_LRM_ATTR_RC, op->rc); crm_xml_add_int(xml_op, XML_LRM_ATTR_OPSTATUS, op->op_status); crm_xml_add_int(xml_op, XML_LRM_ATTR_INTERVAL, op->interval); if(compare_version("2.1", caller_version) <= 0) { if(op->t_run || op->t_rcchange || op->exec_time || op->queue_time) { crm_debug_2("Timing data (%s_%s_%d): last=%lu change=%lu exec=%lu queue=%lu", op->rsc_id, op->op_type, op->interval, op->t_run, op->t_rcchange, op->exec_time, op->queue_time); if(op->interval == 0) { crm_xml_add_int(xml_op, "last-run", op->t_run); } crm_xml_add_int(xml_op, "last-rc-change", op->t_rcchange); crm_xml_add_int(xml_op, "exec-time", op->exec_time); crm_xml_add_int(xml_op, "queue-time", op->queue_time); } } if(crm_str_eq(op->op_type, CRMD_ACTION_MIGRATE, TRUE) || crm_str_eq(op->op_type, CRMD_ACTION_MIGRATED, TRUE)) { /* * Record migrate_source and migrate_target always for migrate ops. */ const char *name = XML_LRM_ATTR_MIGRATE_SOURCE; crm_xml_add(xml_op, name, crm_meta_value(op->params, name)); name = XML_LRM_ATTR_MIGRATE_TARGET; crm_xml_add(xml_op, name, crm_meta_value(op->params, name)); } append_digest(op, xml_op, caller_version, magic, LOG_DEBUG); if(local_user_data) { crm_free(local_user_data); op->user_data = NULL; } crm_free(magic); crm_free(op_id); crm_free(key); return xml_op; } void free_lrm_op(lrm_op_t *op) { if(op == NULL) { return; } g_hash_table_destroy(op->params); crm_free(op->user_data); crm_free(op->output); crm_free(op->rsc_id); crm_free(op->op_type); crm_free(op->app_name); crm_free(op); } #if ENABLE_ACL void determine_request_user(char **user, IPC_Channel *channel, xmlNode *request, const char *field) { /* Get our internal validation out of the way first */ CRM_CHECK(user != NULL && channel != NULL && field != NULL, return); if(*user == NULL) { /* Figure out who our peer is and cache it... */ struct passwd *pwent = getpwuid(channel->farside_uid); if(pwent == NULL) { crm_perror(LOG_ERR, "Cannot get password entry of uid: %d", channel->farside_uid); } else { *user = crm_strdup(pwent->pw_name); } } /* If our peer is a privileged user, we might be doing something on behalf of someone else */ if(is_privileged(*user) == FALSE) { /* We're not a privileged user, set or overwrite any existing value for $field */ crm_xml_replace(request, field, *user); } else if(crm_element_value(request, field) == NULL) { /* Even if we're privileged, make sure there is always a value set */ crm_xml_replace(request, field, *user); /* } else { Legal delegation */ } crm_debug_2("Processing msg for user '%s'", crm_element_value(request, field)); } #endif /* * This re-implements g_str_hash as it was prior to glib2-2.28: * * http://git.gnome.org/browse/glib/commit/?id=354d655ba8a54b754cb5a3efb42767327775696c * * Note that the new g_str_hash is presumably a *better* hash (it's actually * a correct implementation of DJB's hash), but we need to preserve existing * behaviour, because the hash key ultimately determines the "sort" order * when iterating through GHashTables, which affects allocation of scores to * clone instances when iterating through rsc->allowed_nodes. It (somehow) * also appears to have some minor impact on the ordering of a few * pseudo_event IDs in the transition graph. */ guint g_str_hash_traditional(gconstpointer v) { const signed char *p; guint32 h = 0; for (p = v; *p != '\0'; p++) h = (h << 5) - h + *p; return h; } void *find_library_function(void **handle, const char *lib, const char *fn) { char *error; void *a_function; if(*handle == NULL) { *handle = dlopen (lib, RTLD_LAZY); } if (!(*handle)) { crm_err("Could not open %s: %s", lib, dlerror()); exit(100); } a_function = dlsym(*handle, fn); if ((error = dlerror()) != NULL) { crm_err("Could not find %s in %s: %s", fn, lib, error); exit(100); } return a_function; } void *convert_const_pointer(const void *ptr) { /* Worst function ever */ return (void*)ptr; } diff --git a/lib/pengine/complex.c b/lib/pengine/complex.c index 0f24ef9d31..50f583ebb2 100644 --- a/lib/pengine/complex.c +++ b/lib/pengine/complex.c @@ -1,616 +1,621 @@ /* * Copyright (C) 2004 Andrew Beekhof * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include extern xmlNode *get_object_root(const char *object_type,xmlNode *the_root); void populate_hash(xmlNode *nvpair_list, GHashTable *hash, const char **attrs, int attrs_length); resource_object_functions_t resource_class_functions[] = { { native_unpack, native_find_rsc, native_parameter, native_print, native_active, native_resource_state, native_location, native_free }, { group_unpack, native_find_rsc, native_parameter, group_print, group_active, group_resource_state, native_location, group_free }, { clone_unpack, native_find_rsc, native_parameter, clone_print, clone_active, clone_resource_state, native_location, clone_free }, { master_unpack, native_find_rsc, native_parameter, clone_print, clone_active, clone_resource_state, native_location, clone_free } }; enum pe_obj_types get_resource_type(const char *name) { if(safe_str_eq(name, XML_CIB_TAG_RESOURCE)) { return pe_native; } else if(safe_str_eq(name, XML_CIB_TAG_GROUP)) { return pe_group; } else if(safe_str_eq(name, XML_CIB_TAG_INCARNATION)) { return pe_clone; } else if(safe_str_eq(name, XML_CIB_TAG_MASTER)) { return pe_master; } return pe_unknown; } const char *get_resource_typename(enum pe_obj_types type) { switch(type) { case pe_native: return XML_CIB_TAG_RESOURCE; case pe_group: return XML_CIB_TAG_GROUP; case pe_clone: return XML_CIB_TAG_INCARNATION; case pe_master: return XML_CIB_TAG_MASTER; case pe_unknown: return "unknown"; } return ""; } static void dup_attr(gpointer key, gpointer value, gpointer user_data) { add_hash_param(user_data, key, value); } void get_meta_attributes(GHashTable *meta_hash, resource_t *rsc, node_t *node, pe_working_set_t *data_set) { GHashTable *node_hash = NULL; if(node) { node_hash = node->details->attrs; } - - xml_prop_iter(rsc->xml, prop_name, prop_value, - add_hash_param(meta_hash, prop_name, prop_value); - ); + + if(rsc->xml) { + xmlAttrPtr xIter = NULL; + for(xIter = rsc->xml->properties; xIter; xIter = xIter->next) { + const char *prop_name = (const char *)xIter->name; + const char *prop_value = crm_element_value(rsc->xml, prop_name); + add_hash_param(meta_hash, prop_name, prop_value); + } + } unpack_instance_attributes(data_set->input, rsc->xml, XML_TAG_META_SETS, node_hash, meta_hash, NULL, FALSE, data_set->now); /* populate from the regular attributes until the GUI can create * meta attributes */ unpack_instance_attributes(data_set->input, rsc->xml, XML_TAG_ATTR_SETS, node_hash, meta_hash, NULL, FALSE, data_set->now); /* set anything else based on the parent */ if(rsc->parent != NULL) { g_hash_table_foreach(rsc->parent->meta, dup_attr, meta_hash); } /* and finally check the defaults */ unpack_instance_attributes(data_set->input, data_set->rsc_defaults, XML_TAG_META_SETS, node_hash, meta_hash, NULL, FALSE, data_set->now); } void get_rsc_attributes(GHashTable *meta_hash, resource_t *rsc, node_t *node, pe_working_set_t *data_set) { GHashTable *node_hash = NULL; if(node) { node_hash = node->details->attrs; } unpack_instance_attributes(data_set->input, rsc->xml, XML_TAG_ATTR_SETS, node_hash, meta_hash, NULL, FALSE, data_set->now); /* set anything else based on the parent */ if(rsc->parent != NULL) { get_rsc_attributes(meta_hash, rsc->parent, node, data_set); } else { /* and finally check the defaults */ unpack_instance_attributes(data_set->input, data_set->rsc_defaults, XML_TAG_ATTR_SETS, node_hash, meta_hash, NULL, FALSE, data_set->now); } } static char* template_op_key(xmlNode *op) { const char *name = crm_element_value(op, "name"); const char *role = crm_element_value(op, "role"); char *key = NULL; if(role == NULL || crm_str_eq(role, RSC_ROLE_STARTED_S, TRUE) || crm_str_eq(role, RSC_ROLE_SLAVE_S, TRUE)) { role = RSC_ROLE_UNKNOWN_S; } key = crm_concat(name, role, '-'); return key; } static gboolean unpack_template(xmlNode *xml_obj, xmlNode **expanded_xml, pe_working_set_t *data_set) { xmlNode *cib_resources = NULL; xmlNode *template = NULL; xmlNode *new_xml = NULL; xmlNode *child_xml = NULL; xmlNode *rsc_ops = NULL; xmlNode *template_ops = NULL; const char *template_ref = NULL; const char *id = NULL; if(xml_obj == NULL) { pe_err("No resource object for template unpacking"); return FALSE; } template_ref = crm_element_value(xml_obj, XML_CIB_TAG_RSC_TEMPLATE); if(template_ref == NULL) { return TRUE; } id = ID(xml_obj); if(id == NULL) { pe_err("'%s' object must have a id", crm_element_name(xml_obj)); return FALSE; } if(crm_str_eq(template_ref, id, TRUE)) { pe_err("The resource object '%s' should not reference itself", id); return FALSE; } cib_resources = get_object_root(XML_CIB_TAG_RESOURCES, data_set->input); if(cib_resources == NULL) { pe_err("Cannot get the root of object '%s'", XML_CIB_TAG_RESOURCES); return FALSE; } template = find_entity(cib_resources, XML_CIB_TAG_RSC_TEMPLATE, template_ref); if(template == NULL) { pe_err("No template named '%s'", template_ref); return FALSE; } new_xml = copy_xml(template); xmlNodeSetName(new_xml, xml_obj->name); crm_xml_replace(new_xml, XML_ATTR_ID, id); template_ops = find_xml_node(new_xml, "operations", FALSE); for(child_xml = __xml_first_child(xml_obj); child_xml != NULL; child_xml = __xml_next(child_xml)) { xmlNode *new_child = NULL; new_child = add_node_copy(new_xml, child_xml); if(crm_str_eq((const char *)new_child->name, "operations", TRUE)) { rsc_ops = new_child; } } if(template_ops && rsc_ops) { xmlNode * op = NULL; GHashTable *rsc_ops_hash = g_hash_table_new_full( crm_str_hash, g_str_equal, g_hash_destroy_str, NULL); for(op = __xml_first_child(rsc_ops); op != NULL; op = __xml_next(op)) { char *key = template_op_key(op); g_hash_table_insert(rsc_ops_hash, key, op); } for(op = __xml_first_child(template_ops); op != NULL; op = __xml_next(op)) { char *key = template_op_key(op); if(g_hash_table_lookup(rsc_ops_hash, key) == NULL) { add_node_copy(rsc_ops, op); } crm_free(key); } if(rsc_ops_hash) { g_hash_table_destroy(rsc_ops_hash); } free_xml_from_parent(NULL, template_ops); } /*free_xml(*expanded_xml);*/ *expanded_xml = new_xml; /* Disable multi-level templates for now */ /*if(unpack_template(new_xml, expanded_xml, data_set) == FALSE) { free_xml(*expanded_xml); *expanded_xml = NULL; return FALSE; }*/ return TRUE; } gboolean common_unpack(xmlNode * xml_obj, resource_t **rsc, resource_t *parent, pe_working_set_t *data_set) { xmlNode *expanded_xml = NULL; xmlNode *ops = NULL; resource_t *top = NULL; const char *value = NULL; const char *id = crm_element_value(xml_obj, XML_ATTR_ID); const char *class = crm_element_value(xml_obj, XML_AGENT_ATTR_CLASS); crm_log_xml_debug_3(xml_obj, "Processing resource input..."); if(id == NULL) { pe_err("Must specify id tag in "); return FALSE; } else if(rsc == NULL) { pe_err("Nowhere to unpack resource into"); return FALSE; } if(unpack_template(xml_obj, &expanded_xml, data_set) == FALSE) { return FALSE; } crm_malloc0(*rsc, sizeof(resource_t)); if(expanded_xml) { crm_log_xml_debug_3(expanded_xml, "Expanded resource..."); (*rsc)->xml = expanded_xml; (*rsc)->orig_xml = xml_obj; } else { (*rsc)->xml = xml_obj; (*rsc)->orig_xml = NULL; } (*rsc)->parent = parent; ops = find_xml_node((*rsc)->xml, "operations", FALSE); (*rsc)->ops_xml = expand_idref(ops, data_set->input); (*rsc)->variant = get_resource_type(crm_element_name(xml_obj)); if((*rsc)->variant == pe_unknown) { pe_err("Unknown resource type: %s", crm_element_name(xml_obj)); crm_free(*rsc); return FALSE; } (*rsc)->parameters = g_hash_table_new_full( crm_str_hash,g_str_equal, g_hash_destroy_str,g_hash_destroy_str); (*rsc)->meta = g_hash_table_new_full( crm_str_hash,g_str_equal, g_hash_destroy_str,g_hash_destroy_str); (*rsc)->allowed_nodes = g_hash_table_new_full( crm_str_hash,g_str_equal, NULL, g_hash_destroy_str); (*rsc)->known_on = g_hash_table_new_full( crm_str_hash,g_str_equal, NULL, g_hash_destroy_str); value = crm_element_value(xml_obj, XML_RSC_ATTR_INCARNATION); if(value) { (*rsc)->id = crm_concat(id, value, ':'); add_hash_param((*rsc)->meta, XML_RSC_ATTR_INCARNATION, value); } else { (*rsc)->id = crm_strdup(id); } if(parent) { (*rsc)->long_name = crm_concat(parent->long_name, (*rsc)->id, ':'); } else { (*rsc)->long_name = crm_strdup((*rsc)->id); } (*rsc)->fns = &resource_class_functions[(*rsc)->variant]; crm_debug_3("Unpacking resource..."); get_meta_attributes((*rsc)->meta, *rsc, NULL, data_set); (*rsc)->flags = 0; set_bit((*rsc)->flags, pe_rsc_runnable); set_bit((*rsc)->flags, pe_rsc_provisional); if(is_set(data_set->flags, pe_flag_is_managed_default)) { set_bit((*rsc)->flags, pe_rsc_managed); } (*rsc)->rsc_cons = NULL; (*rsc)->rsc_tickets = NULL; (*rsc)->actions = NULL; (*rsc)->role = RSC_ROLE_STOPPED; (*rsc)->next_role = RSC_ROLE_UNKNOWN; (*rsc)->recovery_type = recovery_stop_start; (*rsc)->stickiness = data_set->default_resource_stickiness; (*rsc)->migration_threshold= INFINITY; (*rsc)->failure_timeout = 0; value = g_hash_table_lookup((*rsc)->meta, XML_CIB_ATTR_PRIORITY); (*rsc)->priority = crm_parse_int(value, "0"); (*rsc)->effective_priority = (*rsc)->priority; value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_NOTIFY); if(crm_is_true(value)) { set_bit((*rsc)->flags, pe_rsc_notify); } value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_MANAGED); if(value != NULL && safe_str_neq("default", value)) { gboolean bool_value = TRUE; crm_str_to_boolean(value, &bool_value); if(bool_value == FALSE) { clear_bit((*rsc)->flags, pe_rsc_managed); } else { set_bit((*rsc)->flags, pe_rsc_managed); } } if(is_set(data_set->flags, pe_flag_maintenance_mode)) { clear_bit((*rsc)->flags, pe_rsc_managed); } crm_debug_2("Options for %s", (*rsc)->id); value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_UNIQUE); top = uber_parent(*rsc); if(crm_is_true(value) || top->variant < pe_clone) { set_bit((*rsc)->flags, pe_rsc_unique); } value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_RESTART); if(safe_str_eq(value, "restart")) { (*rsc)->restart_type = pe_restart_restart; crm_debug_2("\tDependency restart handling: restart"); } else { (*rsc)->restart_type = pe_restart_ignore; crm_debug_2("\tDependency restart handling: ignore"); } value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_MULTIPLE); if(safe_str_eq(value, "stop_only")) { (*rsc)->recovery_type = recovery_stop_only; crm_debug_2("\tMultiple running resource recovery: stop only"); } else if(safe_str_eq(value, "block")) { (*rsc)->recovery_type = recovery_block; crm_debug_2("\tMultiple running resource recovery: block"); } else { (*rsc)->recovery_type = recovery_stop_start; crm_debug_2("\tMultiple running resource recovery: stop/start"); } value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_STICKINESS); if(value != NULL && safe_str_neq("default", value)) { (*rsc)->stickiness = char2score(value); } value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_FAIL_STICKINESS); if(value != NULL && safe_str_neq("default", value)) { (*rsc)->migration_threshold = char2score(value); } else if(value == NULL) { /* Make a best-effort guess at a migration threshold for people with 0.6 configs * try with underscores and hyphens, from both the resource and global defaults section */ value = g_hash_table_lookup((*rsc)->meta, "resource-failure-stickiness"); if(value == NULL) { value = g_hash_table_lookup((*rsc)->meta, "resource_failure_stickiness"); } if(value == NULL) { value = g_hash_table_lookup(data_set->config_hash, "default-resource-failure-stickiness"); } if(value == NULL) { value = g_hash_table_lookup(data_set->config_hash, "default_resource_failure_stickiness"); } if(value) { int fail_sticky = char2score(value); if(fail_sticky == -INFINITY) { (*rsc)->migration_threshold = 1; crm_info("Set a migration threshold of %d for %s based on a failure-stickiness of %s", (*rsc)->migration_threshold, (*rsc)->id, value); } else if((*rsc)->stickiness != 0 && fail_sticky != 0) { (*rsc)->migration_threshold = (*rsc)->stickiness / fail_sticky; if((*rsc)->migration_threshold < 0) { /* Make sure it's positive */ (*rsc)->migration_threshold = 0 - (*rsc)->migration_threshold; } (*rsc)->migration_threshold += 1; crm_info("Calculated a migration threshold for %s of %d based on a stickiness of %d/%s", (*rsc)->id, (*rsc)->migration_threshold, (*rsc)->stickiness, value); } } } value = g_hash_table_lookup((*rsc)->meta, XML_RSC_ATTR_FAIL_TIMEOUT); if(value != NULL) { /* call crm_get_msec() and convert back to seconds */ (*rsc)->failure_timeout = (crm_get_msec(value) / 1000); } get_target_role(*rsc, &((*rsc)->next_role)); crm_debug_2("\tDesired next state: %s", (*rsc)->next_role!=RSC_ROLE_UNKNOWN?role2text((*rsc)->next_role):"default"); if((*rsc)->fns->unpack(*rsc, data_set) == FALSE) { return FALSE; } if(is_set(data_set->flags, pe_flag_symmetric_cluster)) { resource_location(*rsc, NULL, 0, "symmetric_default", data_set); } crm_debug_2("\tAction notification: %s", is_set((*rsc)->flags, pe_rsc_notify)?"required":"not required"); if(safe_str_eq(class, "stonith")) { set_bit_inplace(data_set->flags, pe_flag_have_stonith_resource); } (*rsc)->utilization = g_hash_table_new_full( crm_str_hash, g_str_equal, g_hash_destroy_str, g_hash_destroy_str); unpack_instance_attributes(data_set->input, (*rsc)->xml, XML_TAG_UTILIZATION, NULL, (*rsc)->utilization, NULL, FALSE, data_set->now); /* data_set->resources = g_list_append(data_set->resources, (*rsc)); */ return TRUE; } void common_update_score(resource_t *rsc, const char *id, int score) { node_t *node = NULL; node = pe_hash_table_lookup(rsc->allowed_nodes, id); if(node != NULL) { crm_debug_2("Updating score for %s on %s: %d + %d", rsc->id, id, node->weight, score); node->weight = merge_weights(node->weight, score); } if(rsc->children) { GListPtr gIter = rsc->children; for(; gIter != NULL; gIter = gIter->next) { resource_t *child_rsc = (resource_t*)gIter->data; common_update_score(child_rsc, id, score); } } } resource_t *uber_parent(resource_t *rsc) { resource_t *parent = rsc; if(parent == NULL) { return NULL; } while(parent->parent != NULL) { parent = parent->parent; } return parent; } void common_free(resource_t *rsc) { if(rsc == NULL) { return; } crm_debug_5("Freeing %s %d", rsc->id, rsc->variant); g_list_free(rsc->rsc_cons); g_list_free(rsc->rsc_cons_lhs); g_list_free(rsc->rsc_tickets); g_list_free(rsc->dangling_migrations); if(rsc->parameters != NULL) { g_hash_table_destroy(rsc->parameters); } if(rsc->meta != NULL) { g_hash_table_destroy(rsc->meta); } if(rsc->utilization != NULL) { g_hash_table_destroy(rsc->utilization); } if(rsc->orig_xml) { free_xml(rsc->xml); } if(rsc->parent == NULL && is_set(rsc->flags, pe_rsc_orphan)) { if(rsc->orig_xml) { free_xml(rsc->orig_xml); } else { free_xml(rsc->xml); } } if(rsc->running_on) { g_list_free(rsc->running_on); rsc->running_on = NULL; } if(rsc->known_on) { g_hash_table_destroy(rsc->known_on); rsc->known_on = NULL; } if(rsc->actions) { g_list_free(rsc->actions); rsc->actions = NULL; } if(rsc->allowed_nodes) { g_hash_table_destroy(rsc->allowed_nodes); rsc->allowed_nodes = NULL; } g_list_free(rsc->rsc_location); crm_free(rsc->id); crm_free(rsc->long_name); crm_free(rsc->clone_name); crm_free(rsc->allocated_to); crm_free(rsc->variant_opaque); crm_free(rsc); crm_debug_5("Resource freed"); } diff --git a/lib/pengine/utils.c b/lib/pengine/utils.c index 741044c80b..e8011501dc 100644 --- a/lib/pengine/utils.c +++ b/lib/pengine/utils.c @@ -1,1367 +1,1370 @@ /* * Copyright (C) 2004 Andrew Beekhof * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include pe_working_set_t *pe_dataset = NULL; extern xmlNode *get_object_root(const char *object_type,xmlNode *the_root); void print_str_str(gpointer key, gpointer value, gpointer user_data); gboolean ghash_free_str_str(gpointer key, gpointer value, gpointer user_data); void unpack_operation( action_t *action, xmlNode *xml_obj, pe_working_set_t* data_set); node_t * node_copy(node_t *this_node) { node_t *new_node = NULL; CRM_CHECK(this_node != NULL, return NULL); crm_malloc0(new_node, sizeof(node_t)); CRM_ASSERT(new_node != NULL); crm_debug_5("Copying %p (%s) to %p", this_node, this_node->details->uname, new_node); new_node->weight = this_node->weight; new_node->fixed = this_node->fixed; new_node->details = this_node->details; return new_node; } /* any node in list1 or list2 and not in the other gets a score of -INFINITY */ void node_list_exclude(GHashTable *hash, GListPtr list, gboolean merge_scores) { GHashTable *result = hash; node_t *other_node = NULL; GListPtr gIter = list; GHashTableIter iter; node_t *node = NULL; g_hash_table_iter_init (&iter, hash); while (g_hash_table_iter_next (&iter, NULL, (void**)&node)) { other_node = pe_find_node_id(list, node->details->id); if(other_node == NULL) { node->weight = -INFINITY; } else if(merge_scores) { node->weight = merge_weights(node->weight, other_node->weight); } } for(; gIter != NULL; gIter = gIter->next) { node_t *node = (node_t*)gIter->data; other_node = pe_hash_table_lookup(result, node->details->id); if(other_node == NULL) { node_t *new_node = node_copy(node); new_node->weight = -INFINITY; g_hash_table_insert(result, (gpointer)new_node->details->id, new_node); } } } GHashTable * node_hash_from_list(GListPtr list) { GListPtr gIter = list; GHashTable *result = g_hash_table_new_full( crm_str_hash,g_str_equal, NULL, g_hash_destroy_str); for(; gIter != NULL; gIter = gIter->next) { node_t *node = (node_t*)gIter->data; node_t *n = node_copy(node); g_hash_table_insert(result, (gpointer)n->details->id, n); } return result; } GListPtr node_list_dup(GListPtr list1, gboolean reset, gboolean filter) { GListPtr result = NULL; GListPtr gIter = list1; for(; gIter != NULL; gIter = gIter->next) { node_t *new_node = NULL; node_t *this_node = (node_t*)gIter->data; if(filter && this_node->weight < 0) { continue; } new_node = node_copy(this_node); if(reset) { new_node->weight = 0; } if(new_node != NULL) { result = g_list_prepend(result, new_node); } } return result; } static gint sort_node_uname(gconstpointer a, gconstpointer b) { const node_t *node_a = a; const node_t *node_b = b; return strcmp(node_a->details->uname, node_b->details->uname); } void dump_node_scores_worker(int level, const char *file, const char *function, int line, resource_t *rsc, const char *comment, GHashTable *nodes) { GHashTable *hash = nodes; GHashTableIter iter; node_t *node = NULL; if(rsc) { hash = rsc->allowed_nodes; } if(rsc && is_set(rsc->flags, pe_rsc_orphan)) { /* Don't show the allocation scores for orphans */ return; } if(level == 0) { /* For now we want this in sorted order to keep the regression tests happy */ GListPtr gIter = NULL; GListPtr list = g_hash_table_get_values(hash); list = g_list_sort(list, sort_node_uname); gIter = list; for(; gIter != NULL; gIter = gIter->next) { node_t *node = (node_t*)gIter->data; char *score = score2char(node->weight); if(rsc) { printf("%s: %s allocation score on %s: %s\n", comment, rsc->id, node->details->uname, score); } else { printf("%s: %s = %s\n", comment, node->details->uname, score); } crm_free(score); } g_list_free(list); } else if(hash) { g_hash_table_iter_init (&iter, hash); while (g_hash_table_iter_next (&iter, NULL, (void**)&node)) { char *score = score2char(node->weight); if(rsc) { do_crm_log_alias(level, file, function, line, "%s: %s allocation score on %s: %s", comment, rsc->id, node->details->uname, score); } else { do_crm_log_alias(level, file, function, line, "%s: %s = %s", comment, node->details->uname, score); } crm_free(score); } } if(rsc && rsc->children) { GListPtr gIter = NULL; gIter = rsc->children; for(; gIter != NULL; gIter = gIter->next) { resource_t *child = (resource_t*)gIter->data; dump_node_scores_worker(level, file, function, line, child, comment, nodes); } } } static void append_dump_text(gpointer key, gpointer value, gpointer user_data) { char **dump_text = user_data; int len = 0; char *new_text = NULL; len = strlen(*dump_text) + strlen(" ") + strlen(key) + strlen("=") + strlen(value) + 1; crm_malloc0(new_text, len); sprintf(new_text, "%s %s=%s", *dump_text, (char *)key, (char *)value); crm_free(*dump_text); *dump_text = new_text; } void dump_node_capacity(int level, const char *comment, node_t *node) { int len = 0; char *dump_text = NULL; len = strlen(comment) + strlen(": ") + strlen(node->details->uname) + strlen(" capacity:") + 1; crm_malloc0(dump_text, len); sprintf(dump_text, "%s: %s capacity:", comment, node->details->uname); g_hash_table_foreach(node->details->utilization, append_dump_text, &dump_text); if(level == 0) { fprintf(stdout, "%s\n", dump_text); } else { do_crm_log_unlikely(level, "%s", dump_text); } crm_free(dump_text); } void dump_rsc_utilization(int level, const char *comment, resource_t *rsc, node_t *node) { int len = 0; char *dump_text = NULL; len = strlen(comment) + strlen(": ") + strlen(rsc->id) + strlen(" utilization on ") + strlen(node->details->uname) + strlen(":") + 1; crm_malloc0(dump_text, len); sprintf(dump_text, "%s: %s utilization on %s:", comment, rsc->id, node->details->uname); g_hash_table_foreach(rsc->utilization, append_dump_text, &dump_text); if(level == 0) { fprintf(stdout, "%s\n", dump_text); } else { do_crm_log_unlikely(level, "%s", dump_text); } crm_free(dump_text); } gint sort_rsc_index(gconstpointer a, gconstpointer b) { const resource_t *resource1 = (const resource_t*)a; const resource_t *resource2 = (const resource_t*)b; if(a == NULL && b == NULL) { return 0; } if(a == NULL) { return 1; } if(b == NULL) { return -1; } if(resource1->sort_index > resource2->sort_index) { return -1; } if(resource1->sort_index < resource2->sort_index) { return 1; } return 0; } gint sort_rsc_priority(gconstpointer a, gconstpointer b) { const resource_t *resource1 = (const resource_t*)a; const resource_t *resource2 = (const resource_t*)b; if(a == NULL && b == NULL) { return 0; } if(a == NULL) { return 1; } if(b == NULL) { return -1; } if(resource1->priority > resource2->priority) { return -1; } if(resource1->priority < resource2->priority) { return 1; } return 0; } action_t * custom_action(resource_t *rsc, char *key, const char *task, node_t *on_node, gboolean optional, gboolean save_action, pe_working_set_t *data_set) { action_t *action = NULL; GListPtr possible_matches = NULL; CRM_CHECK(key != NULL, return NULL); CRM_CHECK(task != NULL, return NULL); if(save_action && rsc != NULL) { possible_matches = find_actions(rsc->actions, key, on_node); } if(possible_matches != NULL) { crm_free(key); if(g_list_length(possible_matches) > 1) { pe_warn("Action %s for %s on %s exists %d times", task, rsc?rsc->id:"", on_node?on_node->details->uname:"", g_list_length(possible_matches)); } action = g_list_nth_data(possible_matches, 0); crm_debug_4("Found existing action (%d) %s for %s on %s", action->id, task, rsc?rsc->id:"", on_node?on_node->details->uname:""); g_list_free(possible_matches); } if(action == NULL) { if(save_action) { crm_debug_4("Creating%s action %d: %s for %s on %s", optional?"":" manditory", data_set->action_id, key, rsc?rsc->id:"", on_node?on_node->details->uname:""); } crm_malloc0(action, sizeof(action_t)); if(save_action) { action->id = data_set->action_id++; } else { action->id = 0; } action->rsc = rsc; CRM_ASSERT(task != NULL); action->task = crm_strdup(task); if(on_node) { action->node = node_copy(on_node); } action->uuid = key; set_bit_inplace(action->flags, pe_action_failure_is_fatal); set_bit_inplace(action->flags, pe_action_runnable); if(optional) { set_bit_inplace(action->flags, pe_action_optional); } else { clear_bit_inplace(action->flags, pe_action_optional); } /* Implied by crm_malloc0()... action->actions_before = NULL; action->actions_after = NULL; action->pseudo = FALSE; action->dumped = FALSE; action->processed = FALSE; action->seen_count = 0; */ action->extra = g_hash_table_new_full( crm_str_hash, g_str_equal, free, free); action->meta = g_hash_table_new_full( crm_str_hash, g_str_equal, free, free); if(save_action) { data_set->actions = g_list_prepend( data_set->actions, action); } if(rsc != NULL) { action->op_entry = find_rsc_op_entry(rsc, key); unpack_operation( action, action->op_entry, data_set); if(save_action) { rsc->actions = g_list_prepend( rsc->actions, action); } } if(save_action) { crm_debug_4("Action %d created", action->id); } } if(optional == FALSE && (action->flags & pe_action_optional)) { crm_debug_2("Action %d (%s) marked manditory", action->id, action->uuid); clear_bit_inplace(action->flags, pe_action_optional); } if(rsc != NULL) { enum action_tasks a_task = text2task(action->task); int warn_level = LOG_DEBUG_3; if(save_action) { warn_level = LOG_WARNING; } if(is_set(action->flags, pe_action_have_node_attrs) == FALSE && action->node != NULL && action->op_entry != NULL) { set_bit_inplace(action->flags, pe_action_have_node_attrs); unpack_instance_attributes( data_set->input, action->op_entry, XML_TAG_ATTR_SETS, action->node->details->attrs, action->extra, NULL, FALSE, data_set->now); } if(is_set(action->flags, pe_action_pseudo)) { /* leave untouched */ } else if(action->node == NULL) { clear_bit_inplace(action->flags, pe_action_runnable); } else if(is_not_set(rsc->flags, pe_rsc_managed) && g_hash_table_lookup(action->meta, XML_LRM_ATTR_INTERVAL) == NULL) { do_crm_log_unlikely(LOG_DEBUG, "Action %s (unmanaged)", action->uuid); set_bit_inplace(action->flags, pe_action_optional); /* action->runnable = FALSE; */ } else if(action->node->details->online == FALSE) { clear_bit_inplace(action->flags, pe_action_runnable); do_crm_log(warn_level, "Action %s on %s is unrunnable (offline)", action->uuid, action->node->details->uname); if(is_set(action->rsc->flags, pe_rsc_managed) && save_action && a_task == stop_rsc) { do_crm_log(warn_level, "Marking node %s unclean", action->node->details->uname); action->node->details->unclean = TRUE; } } else if(action->node->details->pending) { clear_bit_inplace(action->flags, pe_action_runnable); do_crm_log(warn_level, "Action %s on %s is unrunnable (pending)", action->uuid, action->node->details->uname); } else if(action->needs == rsc_req_nothing) { crm_debug_3("Action %s doesnt require anything", action->uuid); set_bit_inplace(action->flags, pe_action_runnable); #if 0 /* * No point checking this * - if we dont have quorum we cant stonith anyway */ } else if(action->needs == rsc_req_stonith) { crm_debug_3("Action %s requires only stonith", action->uuid); action->runnable = TRUE; #endif } else if(is_set(data_set->flags, pe_flag_have_quorum) == FALSE && data_set->no_quorum_policy == no_quorum_stop) { clear_bit_inplace(action->flags, pe_action_runnable); crm_debug("%s\t%s (cancelled : quorum)", action->node->details->uname, action->uuid); } else if(is_set(data_set->flags, pe_flag_have_quorum) == FALSE && data_set->no_quorum_policy == no_quorum_freeze) { crm_debug_3("Check resource is already active"); if(rsc->fns->active(rsc, TRUE) == FALSE) { clear_bit_inplace(action->flags, pe_action_runnable); crm_debug("%s\t%s (cancelled : quorum freeze)", action->node->details->uname, action->uuid); } } else { crm_debug_3("Action %s is runnable", action->uuid); set_bit_inplace(action->flags, pe_action_runnable); } if(save_action) { switch(a_task) { case stop_rsc: set_bit(rsc->flags, pe_rsc_stopping); break; case start_rsc: clear_bit(rsc->flags, pe_rsc_starting); if(is_set(action->flags, pe_action_runnable)) { set_bit(rsc->flags, pe_rsc_starting); } break; default: break; } } } return action; } void unpack_operation( action_t *action, xmlNode *xml_obj, pe_working_set_t* data_set) { int value_i = 0; unsigned long long interval = 0; unsigned long long start_delay = 0; char *value_ms = NULL; const char *class = NULL; const char *value = NULL; const char *field = NULL; CRM_CHECK(action->rsc != NULL, return); unpack_instance_attributes(data_set->input, data_set->op_defaults, XML_TAG_META_SETS, NULL, action->meta, NULL, FALSE, data_set->now); - xml_prop_iter(xml_obj, name, value, - if (value != NULL) { - g_hash_table_replace(action->meta, crm_strdup(name), crm_strdup(value)); - } - ); + if(xml_obj) { + xmlAttrPtr xIter = NULL; + for(xIter = xml_obj->properties; xIter; xIter = xIter->next) { + const char *prop_name = (const char *)xIter->name; + const char *prop_value = crm_element_value(xml_obj, prop_name); + g_hash_table_replace(action->meta, crm_strdup(prop_name), crm_strdup(prop_value)); + } + } unpack_instance_attributes(data_set->input, xml_obj, XML_TAG_META_SETS, NULL, action->meta, NULL, FALSE, data_set->now); unpack_instance_attributes(data_set->input, xml_obj, XML_TAG_ATTR_SETS, NULL, action->meta, NULL, FALSE, data_set->now); g_hash_table_remove(action->meta, "id"); class = g_hash_table_lookup(action->rsc->meta, "class"); value = g_hash_table_lookup(action->meta, "requires"); if(safe_str_eq(class, "stonith")) { action->needs = rsc_req_nothing; value = "nothing (fencing op)"; } else if(safe_str_eq(value, "nothing")) { action->needs = rsc_req_nothing; } else if(safe_str_eq(value, "quorum")) { action->needs = rsc_req_quorum; } else if(is_set(data_set->flags, pe_flag_stonith_enabled) && safe_str_eq(value, "fencing")) { action->needs = rsc_req_stonith; } else { if(value) { crm_config_err("Invalid value for %s->requires: %s%s", action->rsc->id, value, is_set(data_set->flags, pe_flag_stonith_enabled)?"":" (stonith-enabled=false)"); } if (safe_str_eq(action->task, CRMD_ACTION_STATUS) || safe_str_eq(action->task, CRMD_ACTION_NOTIFY)) { action->needs = rsc_req_nothing; value = "nothing (default)"; } else if (data_set->no_quorum_policy == no_quorum_stop && safe_str_neq(action->task, CRMD_ACTION_START)) { action->needs = rsc_req_nothing; value = "nothing (default)"; } else if (is_set(data_set->flags, pe_flag_stonith_enabled)) { action->needs = rsc_req_stonith; value = "fencing (default)"; } else { action->needs = rsc_req_quorum; value = "quorum (default)"; } } crm_debug_3("\tAction %s requires: %s", action->task, value); value = g_hash_table_lookup(action->meta, XML_OP_ATTR_ON_FAIL); if(safe_str_eq(action->task, CRMD_ACTION_STOP) && safe_str_eq(value, "standby")) { crm_config_err("on-fail=standby is not allowed for stop actions: %s", action->rsc->id); value = NULL; } if(value == NULL) { } else if(safe_str_eq(value, "block")) { action->on_fail = action_fail_block; } else if(safe_str_eq(value, "fence")) { action->on_fail = action_fail_fence; value = "node fencing"; if(is_set(data_set->flags, pe_flag_stonith_enabled) == FALSE) { crm_config_err("Specifying on_fail=fence and" " stonith-enabled=false makes no sense"); action->on_fail = action_fail_stop; action->fail_role = RSC_ROLE_STOPPED; value = "stop resource"; } } else if(safe_str_eq(value, "standby")) { action->on_fail = action_fail_standby; value = "node standby"; } else if(safe_str_eq(value, "ignore") || safe_str_eq(value, "nothing")) { action->on_fail = action_fail_ignore; value = "ignore"; } else if(safe_str_eq(value, "migrate")) { action->on_fail = action_fail_migrate; value = "force migration"; } else if(safe_str_eq(value, "stop")) { action->on_fail = action_fail_stop; action->fail_role = RSC_ROLE_STOPPED; value = "stop resource"; } else if(safe_str_eq(value, "restart")) { action->on_fail = action_fail_recover; value = "restart (and possibly migrate)"; } else { pe_err("Resource %s: Unknown failure type (%s)", action->rsc->id, value); value = NULL; } /* defaults */ if(value == NULL && safe_str_eq(action->task, CRMD_ACTION_STOP)) { if(is_set(data_set->flags, pe_flag_stonith_enabled)) { action->on_fail = action_fail_fence; value = "resource fence (default)"; } else { action->on_fail = action_fail_block; value = "resource block (default)"; } } else if(value == NULL) { action->on_fail = action_fail_recover; value = "restart (and possibly migrate) (default)"; } crm_debug_3("\t%s failure handling: %s", action->task, value); value = NULL; if(xml_obj != NULL) { value = g_hash_table_lookup(action->meta, "role_after_failure"); } if(value != NULL && action->fail_role == RSC_ROLE_UNKNOWN) { action->fail_role = text2role(value); } /* defaults */ if(action->fail_role == RSC_ROLE_UNKNOWN) { if(safe_str_eq(action->task, CRMD_ACTION_PROMOTE)) { action->fail_role = RSC_ROLE_SLAVE; } else { action->fail_role = RSC_ROLE_STARTED; } } crm_debug_3("\t%s failure results in: %s", action->task, role2text(action->fail_role)); field = XML_LRM_ATTR_INTERVAL; value = g_hash_table_lookup(action->meta, field); if(value != NULL) { interval = crm_get_interval(value); if(interval > 0) { value_ms = crm_itoa(interval); g_hash_table_replace(action->meta, crm_strdup(field), value_ms); } else { g_hash_table_remove(action->meta, field); } } field = XML_OP_ATTR_START_DELAY; value = g_hash_table_lookup(action->meta, field); if(value != NULL) { value_i = crm_get_msec(value); if(value_i < 0) { value_i = 0; } start_delay = value_i; value_ms = crm_itoa(value_i); g_hash_table_replace(action->meta, crm_strdup(field), value_ms); } else if(interval > 0 && g_hash_table_lookup(action->meta, XML_OP_ATTR_ORIGIN)) { char *date_str = NULL; char *date_str_mutable = NULL; ha_time_t *origin = NULL; value = g_hash_table_lookup(action->meta, XML_OP_ATTR_ORIGIN); date_str = crm_strdup(value); date_str_mutable = date_str; origin = parse_date(&date_str_mutable); crm_free(date_str); if(origin == NULL) { crm_config_err("Operation %s contained an invalid "XML_OP_ATTR_ORIGIN": %s", ID(xml_obj), value); } else { ha_time_t *delay = NULL; int rc = compare_date(origin, data_set->now); unsigned long long delay_s = 0; while(rc < 0) { add_seconds(origin, interval/1000); rc = compare_date(origin, data_set->now); } delay = subtract_time(origin, data_set->now); delay_s = date_in_seconds(delay); /* log_date(LOG_DEBUG_5, "delay", delay, ha_log_date|ha_log_time|ha_log_local); */ crm_info("Calculated a start delay of %llus for %s", delay_s, ID(xml_obj)); g_hash_table_replace(action->meta, crm_strdup(XML_OP_ATTR_START_DELAY), crm_itoa(delay_s * 1000)); start_delay = delay_s * 1000; free_ha_date(origin); free_ha_date(delay); } } field = XML_ATTR_TIMEOUT; value = g_hash_table_lookup(action->meta, field); if(value == NULL) { value = pe_pref( data_set->config_hash, "default-action-timeout"); } value_i = crm_get_msec(value); if(value_i < 0) { value_i = 0; } value_i += start_delay; value_ms = crm_itoa(value_i); g_hash_table_replace(action->meta, crm_strdup(field), value_ms); } xmlNode * find_rsc_op_entry(resource_t *rsc, const char *key) { int number = 0; gboolean do_retry = TRUE; char *local_key = NULL; const char *name = NULL; const char *value = NULL; const char *interval = NULL; char *match_key = NULL; xmlNode *op = NULL; xmlNode *operation = NULL; retry: for(operation = __xml_first_child(rsc->ops_xml); operation != NULL; operation = __xml_next(operation)) { if(crm_str_eq((const char *)operation->name, "op", TRUE)) { name = crm_element_value(operation, "name"); interval = crm_element_value(operation, XML_LRM_ATTR_INTERVAL); value = crm_element_value(operation, "enabled"); if(value && crm_is_true(value) == FALSE) { continue; } number = crm_get_interval(interval); if(number < 0) { continue; } match_key = generate_op_key(rsc->id, name, number); if(safe_str_eq(key, match_key)) { op = operation; } crm_free(match_key); if(op != NULL) { crm_free(local_key); return op; } } } crm_free(local_key); if(do_retry == FALSE) { return NULL; } do_retry = FALSE; if(strstr(key, CRMD_ACTION_MIGRATE) || strstr(key, CRMD_ACTION_MIGRATED)) { local_key = generate_op_key(rsc->id, "migrate", 0); key = local_key; goto retry; } else if(strstr(key, "_notify_")) { local_key = generate_op_key(rsc->id, "notify", 0); key = local_key; goto retry; } return NULL; } void print_node(const char *pre_text, node_t *node, gboolean details) { if(node == NULL) { crm_debug_4("%s%s: ", pre_text==NULL?"":pre_text, pre_text==NULL?"":": "); return; } crm_debug_4("%s%s%sNode %s: (weight=%d, fixed=%s)", pre_text==NULL?"":pre_text, pre_text==NULL?"":": ", node->details==NULL?"error ":node->details->online?"":"Unavailable/Unclean ", node->details->uname, node->weight, node->fixed?"True":"False"); if(details && node != NULL && node->details != NULL) { char *pe_mutable = crm_strdup("\t\t"); GListPtr gIter = node->details->running_rsc; crm_debug_4("\t\t===Node Attributes"); g_hash_table_foreach(node->details->attrs, print_str_str, pe_mutable); crm_free(pe_mutable); crm_debug_4("\t\t=== Resources"); for(; gIter != NULL; gIter = gIter->next) { resource_t *rsc = (resource_t*)gIter->data; print_resource(LOG_DEBUG_4, "\t\t", rsc, FALSE); } } } /* * Used by the HashTable for-loop */ void print_str_str(gpointer key, gpointer value, gpointer user_data) { crm_debug_4("%s%s %s ==> %s", user_data==NULL?"":(char*)user_data, user_data==NULL?"":": ", (char*)key, (char*)value); } void print_resource( int log_level, const char *pre_text, resource_t *rsc, gboolean details) { long options = pe_print_log; if(rsc == NULL) { do_crm_log(log_level-1, "%s%s: ", pre_text==NULL?"":pre_text, pre_text==NULL?"":": "); return; } if(details) { options |= pe_print_details; } rsc->fns->print(rsc, pre_text, options, &log_level); } void pe_free_action(action_t *action) { if(action == NULL) { return; } slist_basic_destroy(action->actions_before);/* action_warpper_t* */ slist_basic_destroy(action->actions_after); /* action_warpper_t* */ if(action->extra) { g_hash_table_destroy(action->extra); } if(action->meta) { g_hash_table_destroy(action->meta); } crm_free(action->task); crm_free(action->uuid); crm_free(action->node); crm_free(action); } GListPtr find_recurring_actions(GListPtr input, node_t *not_on_node) { const char *value = NULL; GListPtr result = NULL; GListPtr gIter = input; CRM_CHECK(input != NULL, return NULL); for(; gIter != NULL; gIter = gIter->next) { action_t *action = (action_t*)gIter->data; value = g_hash_table_lookup(action->meta, XML_LRM_ATTR_INTERVAL); if(value == NULL) { /* skip */ } else if(safe_str_eq(value, "0")) { /* skip */ } else if(safe_str_eq(CRMD_ACTION_CANCEL, action->task)) { /* skip */ } else if(not_on_node == NULL) { crm_debug_5("(null) Found: %s", action->uuid); result = g_list_prepend(result, action); } else if(action->node == NULL) { /* skip */ } else if(action->node->details != not_on_node->details) { crm_debug_5("Found: %s", action->uuid); result = g_list_prepend(result, action); } } return result; } enum action_tasks get_complex_task(resource_t *rsc, const char *name, gboolean allow_non_atomic) { enum action_tasks task = text2task(name); if(rsc == NULL) { return task; } else if(allow_non_atomic == FALSE || rsc->variant == pe_native) { switch(task) { case stopped_rsc: case started_rsc: case action_demoted: case action_promoted: crm_trace("Folding %s back into its atomic counterpart for %s", name, rsc->id); return task-1; break; default: break; } } return task; } action_t * find_first_action(GListPtr input, const char *uuid, const char *task, node_t *on_node) { GListPtr gIter = NULL; CRM_CHECK(uuid || task, return NULL); for(gIter = input; gIter != NULL; gIter = gIter->next) { action_t *action = (action_t*)gIter->data; if(uuid != NULL && safe_str_neq(uuid, action->uuid)) { continue; } else if(task != NULL && safe_str_neq(task, action->task)) { continue; } else if(on_node == NULL) { return action; } else if(action->node == NULL) { continue; } else if(on_node->details == action->node->details) { return action; } } return NULL; } GListPtr find_actions(GListPtr input, const char *key, node_t *on_node) { GListPtr gIter = input; GListPtr result = NULL; CRM_CHECK(key != NULL, return NULL); for(; gIter != NULL; gIter = gIter->next) { action_t *action = (action_t*)gIter->data; crm_debug_5("Matching %s against %s", key, action->uuid); if(safe_str_neq(key, action->uuid)) { continue; } else if(on_node == NULL) { result = g_list_prepend(result, action); } else if(action->node == NULL) { /* skip */ crm_debug_2("While looking for %s action on %s, " "found an unallocated one. Assigning" " it to the requested node...", key, on_node->details->uname); action->node = node_copy(on_node); result = g_list_prepend(result, action); } else if(on_node->details == action->node->details) { result = g_list_prepend(result, action); } } return result; } GListPtr find_actions_exact(GListPtr input, const char *key, node_t *on_node) { GListPtr gIter = input; GListPtr result = NULL; CRM_CHECK(key != NULL, return NULL); for(; gIter != NULL; gIter = gIter->next) { action_t *action = (action_t*)gIter->data; crm_debug_5("Matching %s against %s", key, action->uuid); if(safe_str_neq(key, action->uuid)) { crm_debug_3("Key mismatch: %s vs. %s", key, action->uuid); continue; } else if(on_node == NULL || action->node == NULL) { crm_debug_3("on_node=%p, action->node=%p", on_node, action->node); continue; } else if(safe_str_eq(on_node->details->id, action->node->details->id)) { result = g_list_prepend(result, action); } crm_debug_2("Node mismatch: %s vs. %s", on_node->details->id, action->node->details->id); } return result; } static void resource_node_score(resource_t *rsc, node_t *node, int score, const char *tag) { node_t *match = NULL; if(rsc->children) { GListPtr gIter = rsc->children; for(; gIter != NULL; gIter = gIter->next) { resource_t *child_rsc = (resource_t*)gIter->data; resource_node_score(child_rsc, node, score, tag); } } crm_debug_2("Setting %s for %s on %s: %d", tag, rsc->id, node->details->uname, score); match = pe_hash_table_lookup(rsc->allowed_nodes, node->details->id); if(match == NULL) { match = node_copy(node); match->weight = merge_weights(score, node->weight); g_hash_table_insert(rsc->allowed_nodes, (gpointer)match->details->id, match); } match->weight = merge_weights(match->weight, score); } void resource_location(resource_t *rsc, node_t *node, int score, const char *tag, pe_working_set_t *data_set) { if(node != NULL) { resource_node_score(rsc, node, score, tag); } else if(data_set != NULL) { GListPtr gIter = data_set->nodes; for(; gIter != NULL; gIter = gIter->next) { node_t *node = (node_t*)gIter->data; resource_node_score(rsc, node, score, tag); } } else { GHashTableIter iter; node_t *node = NULL; g_hash_table_iter_init (&iter, rsc->allowed_nodes); while (g_hash_table_iter_next (&iter, NULL, (void**)&node)) { resource_node_score(rsc, node, score, tag); } } if(node == NULL && score == -INFINITY) { if(rsc->allocated_to) { crm_info("Deallocating %s from %s", rsc->id, rsc->allocated_to->details->uname); crm_free(rsc->allocated_to); rsc->allocated_to = NULL; } } } #define sort_return(an_int, why) do { \ crm_free(a_uuid); \ crm_free(b_uuid); \ crm_trace("%s (%d) %c %s (%d) : %s", \ a_xml_id, a_call_id, an_int>0?'>':an_int<0?'<':'=', \ b_xml_id, b_call_id, why); \ return an_int; \ } while(0) gint sort_op_by_callid(gconstpointer a, gconstpointer b) { char *a_uuid = NULL; char *b_uuid = NULL; const xmlNode *xml_a = a; const xmlNode *xml_b = b; const char *a_xml_id = crm_element_value_const(xml_a, XML_ATTR_ID); const char *b_xml_id = crm_element_value_const(xml_b, XML_ATTR_ID); const char *a_task_id = crm_element_value_const(xml_a, XML_LRM_ATTR_CALLID); const char *b_task_id = crm_element_value_const(xml_b, XML_LRM_ATTR_CALLID); const char *a_key = crm_element_value_const(xml_a, XML_ATTR_TRANSITION_MAGIC); const char *b_key = crm_element_value_const(xml_b, XML_ATTR_TRANSITION_MAGIC); int dummy = -1; int a_id = -1; int b_id = -1; int a_rc = -1; int b_rc = -1; int a_status = -1; int b_status = -1; int a_call_id = -1; int b_call_id = -1; if(safe_str_eq(a_xml_id, b_xml_id)) { /* We have duplicate lrm_rsc_op entries in the status * section which is unliklely to be a good thing * - we can handle it easily enough, but we need to get * to the bottom of why its happening. */ pe_err("Duplicate lrm_rsc_op entries named %s", a_xml_id); sort_return(0, "duplicate"); } CRM_CHECK(a_task_id != NULL, sort_return(0, "bad id a")); CRM_CHECK(a_task_id != NULL, sort_return(0, "bad id b")); a_call_id = crm_parse_int(a_task_id, NULL); b_call_id = crm_parse_int(b_task_id, NULL); if(a_call_id == -1 && b_call_id == -1) { /* both are pending ops so it doesnt matter since * stops are never pending */ sort_return(0, "pending"); } else if(a_call_id >= 0 && a_call_id < b_call_id) { sort_return(-1, "call id"); } else if(b_call_id >= 0 && a_call_id > b_call_id) { sort_return(1, "call id"); } else if(b_call_id >= 0 && a_call_id == b_call_id) { /* The last op and last_failed_op are the same */ sort_return(0, "duplicate failure"); } /* now process pending ops */ CRM_CHECK(a_key != NULL && b_key != NULL, sort_return(0, "No key")); CRM_CHECK(decode_transition_magic( a_key, &a_uuid, &a_id, &dummy, &a_status, &a_rc, &dummy), sort_return(0, "bad magic a")); CRM_CHECK(decode_transition_magic( b_key, &b_uuid, &b_id, &dummy, &b_status, &b_rc, &dummy), sort_return(0, "bad magic b")); /* try and determin the relative age of the operation... * some pending operations (ie. a start) may have been supuerceeded * by a subsequent stop * * [a|b]_id == -1 means its a shutdown operation and _always_ comes last */ if(safe_str_neq(a_uuid, b_uuid) || a_id == b_id) { /* * some of the logic in here may be redundant... * * if the UUID from the TE doesnt match then one better * be a pending operation. * pending operations dont survive between elections and joins * because we query the LRM directly */ CRM_CHECK(a_call_id == -1 || b_call_id == -1, sort_return(0, "odd")); CRM_CHECK(a_call_id >= 0 || b_call_id >= 0, sort_return(0, "odd")); if(b_call_id == -1) { sort_return(-1, "transition + call"); } else if(a_call_id == -1) { sort_return(1, "transition + call"); } } else if((a_id >= 0 && a_id < b_id) || b_id == -1) { sort_return(-1, "transition"); } else if((b_id >= 0 && a_id > b_id) || a_id == -1) { sort_return(1, "transition"); } /* we should never end up here */ CRM_CHECK(FALSE, sort_return(0, "default")); } time_t get_timet_now(pe_working_set_t *data_set) { time_t now = 0; if(data_set && data_set->now) { now = data_set->now->tm_now; } if(now == 0) { /* eventually we should convert data_set->now into time_tm * for now, its only triggered by PE regression tests */ now = time(NULL); crm_crit("Defaulting to 'now'"); if(data_set && data_set->now) { data_set->now->tm_now = now; } } return now; } struct fail_search { resource_t *rsc; int count; long long last; char *key; }; static void get_failcount_by_prefix(gpointer key_p, gpointer value, gpointer user_data) { struct fail_search *search = user_data; const char *key = key_p; const char *match = strstr(key, search->key); if(match) { if(strstr(key, "last-failure-") == key && (key+13) == match) { search->last = crm_int_helper(value, NULL); } else if(strstr(key, "fail-count-") == key && (key+11) == match) { search->count += char2score(value); } } } int get_failcount(node_t *node, resource_t *rsc, int *last_failure, pe_working_set_t *data_set) { struct fail_search search = {rsc, 0, 0, NULL}; search.key = crm_strdup(rsc->id); if(is_not_set(rsc->flags, pe_rsc_unique)) { int lpc = 0; search.rsc = uber_parent(rsc); /* Strip the clone incarnation */ for(lpc = strlen(search.key); lpc > 0; lpc--) { if(search.key[lpc] == ':') { search.key[lpc+1] = 0; break; } } g_hash_table_foreach(node->details->attrs, get_failcount_by_prefix, &search); } else { /* Optimize the "normal" case */ char *key = NULL; const char *value = NULL; key = crm_concat("fail-count", rsc->id, '-'); value = g_hash_table_lookup(node->details->attrs, key); search.count = char2score(value); crm_free(key); key = crm_concat("last-failure", rsc->id, '-'); value = g_hash_table_lookup(node->details->attrs, key); search.last = crm_int_helper(value, NULL); crm_free(key); } if(search.count != 0 && search.last != 0 && rsc->failure_timeout) { if(last_failure) { *last_failure = search.last; } if(search.last > 0) { time_t now = get_timet_now(data_set); if(now > (search.last + rsc->failure_timeout)) { crm_notice("Failcount for %s on %s has expired (limit was %ds)", search.rsc->id, node->details->uname, rsc->failure_timeout); search.count = 0; } } } if(search.count != 0) { crm_info("%s has failed %s times on %s", search.rsc->id, score2char(search.count), node->details->uname); } crm_free(search.key); return search.count; } gboolean get_target_role(resource_t *rsc, enum rsc_role_e *role) { enum rsc_role_e local_role = RSC_ROLE_UNKNOWN; const char *value = g_hash_table_lookup(rsc->meta, XML_RSC_ATTR_TARGET_ROLE); CRM_CHECK(role != NULL, return FALSE); if(value == NULL || safe_str_eq("started", value) || safe_str_eq("default", value)) { return FALSE; } local_role = text2role(value); if(local_role == RSC_ROLE_UNKNOWN) { crm_config_err("%s: Unknown value for %s: %s", rsc->id, XML_RSC_ATTR_TARGET_ROLE, value); return FALSE; } else if(local_role > RSC_ROLE_STARTED) { if(uber_parent(rsc)->variant == pe_master) { if(local_role > RSC_ROLE_SLAVE) { /* This is what we'd do anyway, just leave the default to avoid messing up the placement algorithm */ return FALSE; } } else { crm_config_err("%s is not part of a master/slave resource, a %s of '%s' makes no sense", rsc->id, XML_RSC_ATTR_TARGET_ROLE, value); return FALSE; } } *role = local_role; return TRUE; } diff --git a/tools/cib_shadow.c b/tools/cib_shadow.c index a7bad0654c..5253c3ead0 100644 --- a/tools/cib_shadow.c +++ b/tools/cib_shadow.c @@ -1,603 +1,608 @@ /* * 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 of the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ #include #include #include #include #include #include #include #include #include #include #include #include #include int exit_code = cib_ok; GMainLoop *mainloop = NULL; IPC_Channel *crmd_channel = NULL; const char *host = NULL; void usage(const char *cmd, int exit_status); int command_options = cib_sync_call; const char *cib_action = NULL; cib_t *real_cib = NULL; int dump_data_element(int depth, char **buffer, int *max, int *offset, const char *prefix, xmlNode * data, gboolean formatted); void print_xml_diff(FILE * where, xmlNode * diff); static int force_flag = 0; static int batch_flag = 0; static char * get_shadow_prompt(const char *name) { int len = 16; char *prompt = NULL; CRM_ASSERT(name != NULL); len += strlen(name); crm_malloc0(prompt, len); snprintf(prompt, len, "shadow[%s] # ", name); return prompt; } static void shadow_setup(char *name, gboolean do_switch) { const char *prompt = getenv("PS1"); const char *shell = getenv("SHELL"); char *new_prompt = get_shadow_prompt(name); printf("Setting up shadow instance\n"); if (safe_str_eq(new_prompt, prompt)) { /* nothing to do */ goto done; } else if (batch_flag == FALSE && shell != NULL) { setenv("PS1", new_prompt, 1); setenv("CIB_shadow", name, 1); printf("Type Ctrl-D to exit the crm_shadow shell\n"); if (strstr(shell, "bash")) { execl(shell, "--norc", "--noprofile", NULL); } else { execl(shell, "--noprofile", NULL); } } else if (do_switch) { printf("To switch to the named shadow instance, paste the following into your shell:\n"); } else { printf ("A new shadow instance was created. To begin using it paste the following into your shell:\n"); } printf(" CIB_shadow=%s ; export CIB_shadow\n", name); done: crm_free(new_prompt); } static void shadow_teardown(char *name) { const char *prompt = getenv("PS1"); char *our_prompt = get_shadow_prompt(name); if (prompt != NULL && strstr(prompt, our_prompt)) { printf("Now type Ctrl-D to exit the crm_shadow shell\n"); } else { printf ("Please remember to unset the CIB_shadow variable by pasting the following into your shell:\n"); printf(" unset CIB_shadow\n"); } crm_free(our_prompt); } /* *INDENT-OFF* */ static struct crm_option long_options[] = { /* Top-level Options */ {"help", 0, 0, '?', "\t\tThis text"}, {"version", 0, 0, '$', "\t\tVersion information" }, {"verbose", 0, 0, 'V', "\t\tIncrease debug output"}, {"-spacer-", 1, 0, '-', "\nQueries:"}, {"which", no_argument, NULL, 'w', "\t\tIndicate the active shadow copy"}, {"display", no_argument, NULL, 'p', "\t\tDisplay the contents of the active shadow copy"}, {"edit", no_argument, NULL, 'E', "\t\tEdit the contents of the active shadow copy with your favorite $EDITOR"}, {"diff", no_argument, NULL, 'd', "\t\tDisplay the changes in the active shadow copy\n"}, {"file", no_argument, NULL, 'F', "\t\tDisplay the location of the active shadow copy file\n"}, {"-spacer-", 1, 0, '-', "\nCommands:"}, {"create", required_argument, NULL, 'c', "\tCreate the named shadow copy of the active cluster configuration"}, {"create-empty", required_argument, NULL, 'e', "Create the named shadow copy with an empty cluster configuration"}, {"commit", required_argument, NULL, 'C', "\tUpload the contents of the named shadow copy to the cluster"}, {"delete", required_argument, NULL, 'D', "\tDelete the contents of the named shadow copy"}, {"reset", required_argument, NULL, 'r', "\tRecreate the named shadow copy from the active cluster configuration"}, {"switch", required_argument, NULL, 's', "\t(Advanced) Switch to the named shadow copy"}, {"-spacer-", 1, 0, '-', "\nAdditional Options:"}, {"force", no_argument, NULL, 'f', "\t\t(Advanced) Force the action to be performed"}, {"batch", no_argument, NULL, 'b', "\t\t(Advanced) Don't spawn a new shell" }, {"all", no_argument, NULL, 'a', "\t\t(Advanced) Upload the entire CIB, including status, with --commit" }, {"-spacer-", 1, 0, '-', "\nExamples:", pcmk_option_paragraph}, {"-spacer-", 1, 0, '-', "Create a blank shadow configuration:", pcmk_option_paragraph}, {"-spacer-", 1, 0, '-', " crm_shadow --create-empty myShadow", pcmk_option_example}, {"-spacer-", 1, 0, '-', "Create a shadow configuration from the running cluster:", pcmk_option_paragraph}, {"-spacer-", 1, 0, '-', " crm_shadow --create myShadow", pcmk_option_example}, {"-spacer-", 1, 0, '-', "Display the current shadow configuration:", pcmk_option_paragraph}, {"-spacer-", 1, 0, '-', " crm_shadow --display", pcmk_option_example}, {"-spacer-", 1, 0, '-', "Discard the current shadow configuration (named myShadow):", pcmk_option_paragraph}, {"-spacer-", 1, 0, '-', " crm_shadow --delete myShadow", pcmk_option_example}, {"-spacer-", 1, 0, '-', "Upload the current shadow configuration (named myShadow) to the running cluster:", pcmk_option_paragraph}, {"-spacer-", 1, 0, '-', " crm_shadow --commit myShadow", pcmk_option_example}, {0, 0, 0, 0} }; /* *INDENT-ON* */ int main(int argc, char **argv) { int rc = 0; int flag; int argerr = 0; static int command = '?'; char *shadow = NULL; char *shadow_file = NULL; gboolean full_upload = FALSE; gboolean dangerous_cmd = FALSE; struct stat buf; int option_index = 0; crm_log_init("crm_shadow", LOG_CRIT, FALSE, FALSE, argc, argv); crm_set_options(NULL, "(query|command) [modifiers]", long_options, "Perform configuration changes in a sandbox before updating the live cluster." "\n\nSets up an environment in which configuration tools (cibadmin, crm_resource, etc) work" " offline instead of against a live cluster, allowing changes to be previewed and tested" " for side-effects.\n"); if (argc < 2) { crm_help('?', LSB_EXIT_EINVAL); } while (1) { flag = crm_get_option(argc, argv, &option_index); if (flag == -1 || flag == 0) break; switch (flag) { case 'a': full_upload = TRUE; break; case 'd': case 'E': case 'p': case 'w': case 'F': command = flag; crm_free(shadow); shadow = crm_strdup(getenv("CIB_shadow")); break; case 'e': case 'c': case 's': case 'r': command = flag; crm_free(shadow); shadow = crm_strdup(optarg); break; case 'C': case 'D': command = flag; dangerous_cmd = TRUE; crm_free(shadow); shadow = crm_strdup(optarg); break; case 'V': command_options = command_options | cib_verbose; cl_log_enable_stderr(TRUE); alter_debug(DEBUG_INC); break; case '$': case '?': crm_help(flag, LSB_EXIT_OK); break; case 'f': command_options |= cib_quorum_override; force_flag = 1; break; case 'b': batch_flag = 1; break; default: printf("Argument code 0%o (%c)" " is not (?yet?) supported\n", flag, flag); ++argerr; break; } } if (optind < argc) { printf("non-option ARGV-elements: "); while (optind < argc) printf("%s ", argv[optind++]); printf("\n"); crm_help('?', LSB_EXIT_EINVAL); } if (optind > argc) { ++argerr; } if (argerr) { crm_help('?', LSB_EXIT_GENERIC); } if (command == 'w') { /* which shadow instance is active? */ const char *local = getenv("CIB_shadow"); if (local == NULL) { fprintf(stderr, "No shadow instance provided\n"); rc = cib_NOTEXISTS; goto done; } fprintf(stdout, "%s\n", local); rc = 0; goto done; } if (shadow == NULL) { fprintf(stderr, "No shadow instance provided\n"); fflush(stderr); rc = CIBRES_MISSING_FIELD; goto done; } else if (command != 's' && command != 'c') { const char *local = getenv("CIB_shadow"); if (local != NULL && safe_str_neq(local, shadow) && force_flag == FALSE) { fprintf(stderr, "The supplied shadow instance (%s) is not the same as the active one (%s).\n" " To prevent accidental destruction of the cluster," " the --force flag is required in order to proceed.\n", shadow, local); fflush(stderr); rc = LSB_EXIT_GENERIC; goto done; } } if (dangerous_cmd && force_flag == FALSE) { fprintf(stderr, "The supplied command is considered dangerous." " To prevent accidental destruction of the cluster," " the --force flag is required in order to proceed.\n"); fflush(stderr); rc = LSB_EXIT_GENERIC; goto done; } shadow_file = get_shadow_file(shadow); if (command == 'D') { /* delete the file */ rc = stat(shadow_file, &buf); if (rc == 0) { rc = unlink(shadow_file); if (rc != 0) { fprintf(stderr, "Could not remove shadow instance '%s': %s\n", shadow, strerror(errno)); goto done; } } shadow_teardown(shadow); goto done; } else if (command == 'F') { printf("%s\n", shadow_file); rc = 0; goto done; } if (command == 'd' || command == 'r' || command == 'c' || command == 'C') { real_cib = cib_new_no_shadow(); rc = real_cib->cmds->signon(real_cib, crm_system_name, cib_command); if (rc != cib_ok) { fprintf(stderr, "Signon to CIB failed: %s\n", cib_error2string(rc)); goto done; } } rc = stat(shadow_file, &buf); if (command == 'e' || command == 'c') { if (rc == 0 && force_flag == FALSE) { fprintf(stderr, "A shadow instance '%s' already exists.\n" " To prevent accidental destruction of the cluster," " the --force flag is required in order to proceed.\n", shadow); rc = cib_EXISTS; goto done; } } else if (rc != 0) { fprintf(stderr, "Could not access shadow instance '%s': %s\n", shadow, strerror(errno)); rc = cib_NOTEXISTS; goto done; } rc = cib_ok; if (command == 'c' || command == 'e') { xmlNode *output = NULL; /* create a shadow instance based on the current cluster config */ if (command == 'c') { rc = real_cib->cmds->query(real_cib, NULL, &output, command_options); if (rc != cib_ok) { fprintf(stderr, "Could not connect to the CIB: %s\n", cib_error2string(rc)); goto done; } } else { output = createEmptyCib(); crm_xml_add(output, XML_ATTR_GENERATION, "0"); crm_xml_add(output, XML_ATTR_NUMUPDATES, "0"); crm_xml_add(output, XML_ATTR_GENERATION_ADMIN, "0"); crm_xml_add(output, XML_ATTR_VALIDATION, LATEST_SCHEMA_VERSION); } rc = write_xml_file(output, shadow_file, FALSE); free_xml(output); if (rc < 0) { fprintf(stderr, "Could not create the shadow instance '%s': %s\n", shadow, strerror(errno)); goto done; } shadow_setup(shadow, FALSE); rc = cib_ok; } else if (command == 'E') { const char *err = NULL; char *editor = getenv("EDITOR"); if (editor == NULL) { fprintf(stderr, "No value for $EDITOR defined\n"); rc = cib_missing; goto done; } execlp(editor, "--", shadow_file, NULL); err = strerror(errno); fprintf(stderr, "Could not invoke $EDITOR (%s %s): %s\n", editor, shadow_file, err); rc = cib_missing; goto done; } else if (command == 's') { shadow_setup(shadow, TRUE); rc = 0; goto done; } else if (command == 'P') { /* display the current contents */ char *output_s = NULL; xmlNode *output = filename2xml(shadow_file); output_s = dump_xml_formatted(output); printf("%s", output_s); crm_free(output_s); free_xml(output); } else if (command == 'd') { /* diff against cluster */ xmlNode *diff = NULL; xmlNode *old_config = NULL; xmlNode *new_config = filename2xml(shadow_file); rc = real_cib->cmds->query(real_cib, NULL, &old_config, command_options); if (rc != cib_ok) { fprintf(stderr, "Could not query the CIB: %s\n", cib_error2string(rc)); goto done; } diff = diff_xml_object(old_config, new_config, FALSE); if (diff != NULL) { print_xml_diff(stdout, diff); rc = 1; goto done; } rc = 0; goto done; } else if (command == 'C') { /* commit to the cluster */ xmlNode *input = filename2xml(shadow_file); if (full_upload) { rc = real_cib->cmds->replace(real_cib, NULL, input, command_options); } else { xmlNode *config = first_named_child(input, XML_CIB_TAG_CONFIGURATION); rc = real_cib->cmds->replace(real_cib, XML_CIB_TAG_CONFIGURATION, config, command_options); } if (rc != cib_ok) { fprintf(stderr, "Could not commit shadow instance '%s' to the CIB: %s\n", shadow, cib_error2string(rc)); return rc; } shadow_teardown(shadow); free_xml(input); } done: crm_xml_cleanup(); crm_free(shadow_file); crm_free(shadow); return rc; } #define bhead(buffer, offset) ((*buffer) + (*offset)) #define bremain(max, offset) ((*max) - (*offset)) #define update_buffer_head(len) do { \ int total = (*offset) + len + 1; \ if(total >= (*max)) { /* too late */ \ (*buffer) = EOS; return -1; \ } else if(((*max) - total) < 256) { \ (*max) *= 10; \ crm_realloc(*buffer, (*max)); \ } \ (*offset) += len; \ } while(0) extern int print_spaces(char *buffer, int depth, int max); int dump_data_element(int depth, char **buffer, int *max, int *offset, const char *prefix, xmlNode * data, gboolean formatted) { int printed = 0; int has_children = 0; xmlNode *child = NULL; const char *name = NULL; CRM_CHECK(data != NULL, return 0); name = crm_element_name(data); CRM_CHECK(name != NULL, return 0); CRM_CHECK(buffer != NULL && *buffer != NULL, return 0); crm_debug_5("Dumping %s...", name); if (prefix) { printed = snprintf(bhead(buffer, offset), bremain(max, offset), "%s", prefix); update_buffer_head(printed); } if (formatted) { printed = print_spaces(bhead(buffer, offset), depth, bremain(max, offset)); update_buffer_head(printed); } printed = snprintf(bhead(buffer, offset), bremain(max, offset), "<%s", name); update_buffer_head(printed); - xml_prop_iter(data, prop_name, prop_value, - crm_debug_5("Dumping <%s %s=\"%s\"...", - name, prop_name, prop_value); - printed = - snprintf(bhead(buffer, offset), bremain(max, offset), " %s=\"%s\"", prop_name, - prop_value); update_buffer_head(printed);); + if(data) { + xmlAttrPtr xIter = NULL; + for(xIter = data->properties; xIter; xIter = xIter->next) { + const char *prop_name = (const char *)xIter->name; + const char *prop_value = crm_element_value(data, prop_name); + crm_debug_5("Dumping <%s %s=\"%s\"...", + name, prop_name, prop_value); + printed = snprintf(bhead(buffer, offset), bremain(max, offset), " %s=\"%s\"", prop_name, prop_value); + update_buffer_head(printed); + } + } has_children = xml_has_children(data); printed = snprintf(bhead(buffer, offset), bremain(max, offset), "%s>%s", has_children == 0 ? "/" : "", formatted ? "\n" : ""); update_buffer_head(printed); if (has_children == 0) { return 0; } for (child = __xml_first_child(data); child != NULL; child = __xml_next(child)) { if (dump_data_element(depth + 1, buffer, max, offset, prefix, child, formatted) < 0) { return -1; } } if (prefix) { printed = snprintf(bhead(buffer, offset), bremain(max, offset), "%s", prefix); update_buffer_head(printed); } if (formatted) { printed = print_spaces(bhead(buffer, offset), depth, bremain(max, offset)); update_buffer_head(printed); } printed = snprintf(bhead(buffer, offset), bremain(max, offset), "%s", name, formatted ? "\n" : ""); update_buffer_head(printed); crm_debug_5("Dumped %s...", name); return has_children; } void print_xml_diff(FILE * where, xmlNode * diff) { char *buffer = NULL; xmlNode *child = NULL; int max = 1024, len = 0; gboolean is_first = TRUE; xmlNode *added = find_xml_node(diff, "diff-added", FALSE); xmlNode *removed = find_xml_node(diff, "diff-removed", FALSE); is_first = TRUE; for (child = __xml_first_child(removed); child != NULL; child = __xml_next(child)) { len = 0; max = 1024; crm_free(buffer); crm_malloc0(buffer, max); if (is_first) { is_first = FALSE; } else { fprintf(where, " --- \n"); } CRM_CHECK(dump_data_element(0, &buffer, &max, &len, "-", child, TRUE) >= 0, continue); fprintf(where, "%s", buffer); } is_first = TRUE; for (child = __xml_first_child(added); child != NULL; child = __xml_next(child)) { len = 0; max = 1024; crm_free(buffer); crm_malloc0(buffer, max); if (is_first) { is_first = FALSE; } else { fprintf(where, " +++ \n"); } CRM_CHECK(dump_data_element(0, &buffer, &max, &len, "+", child, TRUE) >= 0, continue); fprintf(where, "%s", buffer); } }