Page Menu
Home
ClusterLabs Projects
Search
Configure Global Search
Log In
Files
F4638724
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Flag For Later
Award Token
Size
25 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/lib/cib/cib_ops.c b/lib/cib/cib_ops.c
index 5ba95338b9..0c7d9815b0 100644
--- a/lib/cib/cib_ops.c
+++ b/lib/cib/cib_ops.c
@@ -1,827 +1,813 @@
/*
* Copyright 2004-2025 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <time.h>
#include <sys/param.h>
#include <sys/types.h>
#include <glib.h>
#include <libxml/tree.h>
#include <libxml/xpath.h> // xmlXPathObject
#include <crm/crm.h>
#include <crm/cib/internal.h>
#include <crm/common/xml.h>
#include <crm/common/xml_internal.h>
// @TODO: Free this via crm_exit() when libcib gets merged with libcrmcommon
static GHashTable *operation_table = NULL;
static const cib__operation_t cib_ops[] = {
{
PCMK__CIB_REQUEST_ABS_DELETE, cib__op_abs_delete,
cib__op_attr_modifies|cib__op_attr_privileged
},
{
PCMK__CIB_REQUEST_APPLY_PATCH, cib__op_apply_patch,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_transaction
},
{
PCMK__CIB_REQUEST_BUMP, cib__op_bump,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_transaction
},
{
PCMK__CIB_REQUEST_COMMIT_TRANSACT, cib__op_commit_transact,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_replaces
|cib__op_attr_writes_through
},
{
PCMK__CIB_REQUEST_CREATE, cib__op_create,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_transaction
},
{
PCMK__CIB_REQUEST_DELETE, cib__op_delete,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_transaction
},
{
PCMK__CIB_REQUEST_ERASE, cib__op_erase,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_replaces
|cib__op_attr_transaction
},
{
PCMK__CIB_REQUEST_IS_PRIMARY, cib__op_is_primary,
cib__op_attr_privileged
},
{
PCMK__CIB_REQUEST_MODIFY, cib__op_modify,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_transaction
},
{
PCMK__CIB_REQUEST_NOOP, cib__op_noop, cib__op_attr_none
},
{
CRM_OP_PING, cib__op_ping, cib__op_attr_none
},
{
// @COMPAT: Drop cib__op_attr_modifies when we drop legacy mode support
PCMK__CIB_REQUEST_PRIMARY, cib__op_primary,
cib__op_attr_modifies|cib__op_attr_privileged|cib__op_attr_local
},
{
PCMK__CIB_REQUEST_QUERY, cib__op_query, cib__op_attr_none
},
{
PCMK__CIB_REQUEST_REPLACE, cib__op_replace,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_replaces
|cib__op_attr_writes_through
|cib__op_attr_transaction
},
{
PCMK__CIB_REQUEST_SECONDARY, cib__op_secondary,
cib__op_attr_privileged|cib__op_attr_local
},
{
PCMK__CIB_REQUEST_SHUTDOWN, cib__op_shutdown, cib__op_attr_privileged
},
{
PCMK__CIB_REQUEST_SYNC_TO_ALL, cib__op_sync_all, cib__op_attr_privileged
},
{
PCMK__CIB_REQUEST_SYNC_TO_ONE, cib__op_sync_one, cib__op_attr_privileged
},
{
PCMK__CIB_REQUEST_UPGRADE, cib__op_upgrade,
cib__op_attr_modifies
|cib__op_attr_privileged
|cib__op_attr_writes_through
|cib__op_attr_transaction
},
{
PCMK__CIB_REQUEST_SCHEMAS, cib__op_schemas, cib__op_attr_local
}
};
/*!
* \internal
* \brief Get the \c cib__operation_t object for a given CIB operation name
*
* \param[in] op CIB operation name
* \param[out] operation Where to store CIB operation object
*
* \return Standard Pacemaker return code
*/
int
cib__get_operation(const char *op, const cib__operation_t **operation)
{
pcmk__assert((op != NULL) && (operation != NULL));
if (operation_table == NULL) {
operation_table = pcmk__strkey_table(NULL, NULL);
for (int lpc = 0; lpc < PCMK__NELEM(cib_ops); lpc++) {
const cib__operation_t *oper = &(cib_ops[lpc]);
g_hash_table_insert(operation_table, (gpointer) oper->name,
(gpointer) oper);
}
}
*operation = g_hash_table_lookup(operation_table, op);
if (*operation == NULL) {
crm_err("Operation %s is invalid", op);
return EINVAL;
}
return pcmk_rc_ok;
}
int
cib_process_query(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
xmlNode *obj_root = NULL;
int result = pcmk_ok;
crm_trace("Processing %s for %s section",
op, pcmk__s(section, "unspecified"));
if (options & cib_xpath) {
return cib_process_xpath(op, options, section, req, input,
existing_cib, result_cib, answer);
}
CRM_CHECK(*answer == NULL, pcmk__xml_free(*answer));
*answer = NULL;
if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) {
section = NULL;
}
obj_root = pcmk_find_cib_element(existing_cib, section);
if (obj_root == NULL) {
result = -ENXIO;
} else if (options & cib_no_children) {
xmlNode *shallow = pcmk__xe_create(*answer,
(const char *) obj_root->name);
pcmk__xe_copy_attrs(shallow, obj_root, pcmk__xaf_none);
*answer = shallow;
} else {
*answer = obj_root;
}
if (result == pcmk_ok && *answer == NULL) {
crm_err("Error creating query response");
result = -ENOMSG;
}
return result;
}
static int
update_counter(xmlNode *xml_obj, const char *field, bool reset)
{
char *new_value = NULL;
char *old_value = NULL;
int int_value = -1;
if (!reset && crm_element_value(xml_obj, field) != NULL) {
old_value = crm_element_value_copy(xml_obj, field);
}
if (old_value != NULL) {
int_value = atoi(old_value);
new_value = pcmk__itoa(++int_value);
} else {
new_value = pcmk__str_copy("1");
}
crm_trace("Update %s from %s to %s",
field, pcmk__s(old_value, "unset"), new_value);
crm_xml_add(xml_obj, field, new_value);
free(new_value);
free(old_value);
return pcmk_ok;
}
int
cib_process_erase(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
int result = pcmk_ok;
crm_trace("Processing \"%s\" event", op);
if (*result_cib != existing_cib) {
pcmk__xml_free(*result_cib);
}
*result_cib = createEmptyCib(0);
pcmk__xe_copy_attrs(*result_cib, existing_cib, pcmk__xaf_none);
update_counter(*result_cib, PCMK_XA_ADMIN_EPOCH, false);
*answer = NULL;
return result;
}
int
cib_process_upgrade(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer)
{
int rc = 0;
const char *max_schema = crm_element_value(req, PCMK__XA_CIB_SCHEMA_MAX);
const char *original_schema = NULL;
const char *new_schema = NULL;
*answer = NULL;
crm_trace("Processing \"%s\" event with max=%s", op, max_schema);
original_schema = crm_element_value(existing_cib, PCMK_XA_VALIDATE_WITH);
rc = pcmk__update_schema(result_cib, max_schema, true,
!pcmk_is_set(options, cib_verbose));
rc = pcmk_rc2legacy(rc);
new_schema = crm_element_value(*result_cib, PCMK_XA_VALIDATE_WITH);
if (pcmk__cmp_schemas_by_name(new_schema, original_schema) > 0) {
update_counter(*result_cib, PCMK_XA_ADMIN_EPOCH, false);
update_counter(*result_cib, PCMK_XA_EPOCH, true);
update_counter(*result_cib, PCMK_XA_NUM_UPDATES, true);
return pcmk_ok;
}
return rc;
}
int
cib_process_bump(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
int result = pcmk_ok;
crm_trace("Processing %s for epoch='%s'", op,
pcmk__s(crm_element_value(existing_cib, PCMK_XA_EPOCH), ""));
*answer = NULL;
update_counter(*result_cib, PCMK_XA_EPOCH, false);
return result;
}
int
cib_process_replace(const char *op, int options, const char *section, xmlNode * req,
xmlNode * input, xmlNode * existing_cib, xmlNode ** result_cib,
xmlNode ** answer)
{
int result = pcmk_ok;
crm_trace("Processing %s for %s section",
op, pcmk__s(section, "unspecified"));
if (options & cib_xpath) {
return cib_process_xpath(op, options, section, req, input,
existing_cib, result_cib, answer);
}
*answer = NULL;
if (input == NULL) {
return -EINVAL;
}
if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) {
section = NULL;
} else if (pcmk__xe_is(input, section)) {
section = NULL;
}
if (pcmk__xe_is(input, PCMK_XE_CIB)) {
int updates = 0;
int epoch = 0;
int admin_epoch = 0;
int replace_updates = 0;
int replace_epoch = 0;
int replace_admin_epoch = 0;
const char *reason = NULL;
const char *peer = crm_element_value(req, PCMK__XA_SRC);
const char *digest = crm_element_value(req, PCMK__XA_DIGEST);
if (digest) {
char *digest_verify = pcmk__digest_xml(input, true);
if (!pcmk__str_eq(digest_verify, digest, pcmk__str_casei)) {
crm_err("Digest mis-match on replace from %s: %s vs. %s (expected)", peer,
digest_verify, digest);
reason = "digest mismatch";
} else {
crm_info("Digest matched on replace from %s: %s", peer, digest);
}
free(digest_verify);
} else {
crm_trace("No digest to verify");
}
cib_version_details(existing_cib, &admin_epoch, &epoch, &updates);
cib_version_details(input, &replace_admin_epoch, &replace_epoch, &replace_updates);
if (replace_admin_epoch < admin_epoch) {
reason = PCMK_XA_ADMIN_EPOCH;
} else if (replace_admin_epoch > admin_epoch) {
/* no more checks */
} else if (replace_epoch < epoch) {
reason = PCMK_XA_EPOCH;
} else if (replace_epoch > epoch) {
/* no more checks */
} else if (replace_updates < updates) {
reason = PCMK_XA_NUM_UPDATES;
}
if (reason != NULL) {
crm_info("Replacement %d.%d.%d from %s not applied to %d.%d.%d:"
" current %s is greater than the replacement",
replace_admin_epoch, replace_epoch,
replace_updates, peer, admin_epoch, epoch, updates, reason);
result = -pcmk_err_old_data;
} else {
crm_info("Replaced %d.%d.%d with %d.%d.%d from %s",
admin_epoch, epoch, updates,
replace_admin_epoch, replace_epoch, replace_updates, peer);
}
if (*result_cib != existing_cib) {
pcmk__xml_free(*result_cib);
}
*result_cib = pcmk__xml_copy(NULL, input);
} else {
xmlNode *obj_root = NULL;
obj_root = pcmk_find_cib_element(*result_cib, section);
result = pcmk__xe_replace_match(obj_root, input);
result = pcmk_rc2legacy(result);
if (result != pcmk_ok) {
crm_trace("No matching object to replace");
}
}
return result;
}
static int
delete_child(xmlNode *child, void *userdata)
{
xmlNode *obj_root = userdata;
if (pcmk__xe_delete_match(obj_root, child) != pcmk_rc_ok) {
crm_trace("No matching object to delete: %s=%s",
child->name, pcmk__xe_id(child));
}
return pcmk_rc_ok;
}
int
cib_process_delete(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
xmlNode *obj_root = NULL;
crm_trace("Processing \"%s\" event", op);
if (options & cib_xpath) {
return cib_process_xpath(op, options, section, req, input,
existing_cib, result_cib, answer);
}
if (input == NULL) {
crm_err("Cannot perform modification with no data");
return -EINVAL;
}
obj_root = pcmk_find_cib_element(*result_cib, section);
if (pcmk__xe_is(input, section)) {
pcmk__xe_foreach_child(input, NULL, delete_child, obj_root);
} else {
delete_child(input, obj_root);
}
return pcmk_ok;
}
int
cib_process_modify(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
xmlNode *obj_root = NULL;
uint32_t flags = pcmk__xaf_none;
crm_trace("Processing \"%s\" event", op);
if (options & cib_xpath) {
return cib_process_xpath(op, options, section, req, input,
existing_cib, result_cib, answer);
}
if (input == NULL) {
crm_err("Cannot perform modification with no data");
return -EINVAL;
}
obj_root = pcmk_find_cib_element(*result_cib, section);
if (obj_root == NULL) {
xmlNode *tmp_section = NULL;
const char *path = pcmk_cib_parent_name_for(section);
if (path == NULL) {
return -EINVAL;
}
tmp_section = pcmk__xe_create(NULL, section);
cib_process_xpath(PCMK__CIB_REQUEST_CREATE, 0, path, NULL, tmp_section,
NULL, result_cib, answer);
pcmk__xml_free(tmp_section);
obj_root = pcmk_find_cib_element(*result_cib, section);
}
CRM_CHECK(obj_root != NULL, return -EINVAL);
if (pcmk_is_set(options, cib_score_update)) {
flags |= pcmk__xaf_score_update;
}
if (pcmk__xe_update_match(obj_root, input, flags) != pcmk_rc_ok) {
if (options & cib_can_create) {
pcmk__xml_copy(obj_root, input);
} else {
return -ENXIO;
}
}
return pcmk_ok;
}
static int
add_cib_object(xmlNode * parent, xmlNode * new_obj)
{
const char *object_name = NULL;
const char *object_id = NULL;
if ((parent == NULL) || (new_obj == NULL)) {
return -EINVAL;
}
object_name = (const char *) new_obj->name;
if (object_name == NULL) {
return -EINVAL;
}
object_id = pcmk__xe_id(new_obj);
if (pcmk__xe_first_child(parent, object_name,
((object_id != NULL)? PCMK_XA_ID : NULL),
object_id)) {
return -EEXIST;
}
if (object_id != NULL) {
crm_trace("Processing creation of <%s " PCMK_XA_ID "='%s'>",
object_name, object_id);
} else {
crm_trace("Processing creation of <%s>", object_name);
}
/* @COMPAT PCMK__XA_REPLACE is deprecated since 2.1.6. Due to a legacy use
* case, PCMK__XA_REPLACE has special meaning and should not be included in
* the newly created object until we can break behavioral backward
* compatibility.
*
* At a compatibility break, drop this and drop the definition of
* PCMK__XA_REPLACE. Treat it like any other attribute.
*/
pcmk__xml_tree_foreach(new_obj, pcmk__xe_remove_attr_cb,
(void *) PCMK__XA_REPLACE);
pcmk__xml_copy(parent, new_obj);
return pcmk_ok;
}
static bool
update_results(xmlNode *failed, xmlNode *target, const char *operation,
int return_code)
{
xmlNode *xml_node = NULL;
bool was_error = false;
const char *error_msg = NULL;
if (return_code != pcmk_ok) {
error_msg = pcmk_strerror(return_code);
was_error = true;
xml_node = pcmk__xe_create(failed, PCMK__XE_FAILED_UPDATE);
pcmk__xml_copy(xml_node, target);
crm_xml_add(xml_node, PCMK_XA_ID, pcmk__xe_id(target));
crm_xml_add(xml_node, PCMK_XA_OBJECT_TYPE, (const char *) target->name);
crm_xml_add(xml_node, PCMK_XA_OPERATION, operation);
crm_xml_add(xml_node, PCMK_XA_REASON, error_msg);
crm_warn("Action %s failed: %s (cde=%d)",
operation, error_msg, return_code);
}
return was_error;
}
int
cib_process_create(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
xmlNode *failed = NULL;
int result = pcmk_ok;
xmlNode *update_section = NULL;
crm_trace("Processing %s for %s section",
op, pcmk__s(section, "unspecified"));
if (pcmk__str_eq(PCMK__XE_ALL, section, pcmk__str_casei)) {
section = NULL;
} else if (pcmk__str_eq(section, PCMK_XE_CIB, pcmk__str_casei)) {
section = NULL;
} else if (pcmk__xe_is(input, PCMK_XE_CIB)) {
section = NULL;
}
CRM_CHECK(strcmp(op, PCMK__CIB_REQUEST_CREATE) == 0, return -EINVAL);
if (input == NULL) {
crm_err("Cannot perform modification with no data");
return -EINVAL;
}
if (section == NULL) {
return cib_process_modify(op, options, section, req, input, existing_cib, result_cib,
answer);
}
// @COMPAT Deprecated since 2.1.8
failed = pcmk__xe_create(NULL, PCMK__XE_FAILED);
update_section = pcmk_find_cib_element(*result_cib, section);
if (pcmk__xe_is(input, section)) {
xmlNode *a_child = NULL;
for (a_child = pcmk__xml_first_child(input); a_child != NULL;
a_child = pcmk__xml_next(a_child)) {
result = add_cib_object(update_section, a_child);
if (update_results(failed, a_child, op, result)) {
break;
}
}
} else {
result = add_cib_object(update_section, input);
update_results(failed, input, op, result);
}
if ((result == pcmk_ok) && (failed->children != NULL)) {
result = -EINVAL;
}
if (result != pcmk_ok) {
crm_log_xml_err(failed, "CIB Update failures");
*answer = failed;
} else {
pcmk__xml_free(failed);
}
return result;
}
int
cib_process_diff(const char *op, int options, const char *section, xmlNode * req, xmlNode * input,
xmlNode * existing_cib, xmlNode ** result_cib, xmlNode ** answer)
{
const char *originator = NULL;
if (req != NULL) {
originator = crm_element_value(req, PCMK__XA_SRC);
}
crm_trace("Processing \"%s\" event from %s%s",
op, originator,
(pcmk_is_set(options, cib_force_diff)? " (global update)" : ""));
if (*result_cib != existing_cib) {
pcmk__xml_free(*result_cib);
}
*result_cib = pcmk__xml_copy(NULL, existing_cib);
return xml_apply_patchset(*result_cib, input, TRUE);
}
int
cib_process_xpath(const char *op, int options, const char *section,
const xmlNode *req, xmlNode *input, xmlNode *existing_cib,
xmlNode **result_cib, xmlNode **answer)
{
int lpc = 0;
int max = 0;
int rc = pcmk_ok;
bool is_query = pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none);
xmlXPathObject *xpathObj = NULL;
crm_trace("Processing \"%s\" event", op);
if (is_query) {
xpathObj = pcmk__xpath_search(existing_cib->doc, section);
} else {
xpathObj = pcmk__xpath_search((*result_cib)->doc, section);
}
max = pcmk__xpath_num_results(xpathObj);
if ((max < 1)
&& pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) {
crm_debug("%s was already removed", section);
} else if (max < 1) {
crm_debug("%s: %s does not exist", op, section);
rc = -ENXIO;
} else if (is_query) {
if (max > 1) {
*answer = pcmk__xe_create(NULL, PCMK__XE_XPATH_QUERY);
}
}
if (pcmk_is_set(options, cib_multiple)
&& pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) {
dedupXpathResults(xpathObj);
}
for (lpc = 0; lpc < max; lpc++) {
xmlChar *path = NULL;
xmlNode *match = pcmk__xpath_result(xpathObj, lpc);
if (match == NULL) {
continue;
}
- /* @TODO This is a longstanding bug in the CIB API. If an XPath
- * expression matches a non-element node (for example, an attribute),
- * then we operate on the parent element. Similarly, if it matches the
- * document itself, then we operate on the root element (which at least
- * seems more reasonable).
- *
- * Some CIB operations might not make much sense for non-element nodes.
- * However, operating on a matched node's parent is surprising behavior.
- */
- match = pcmk__xpath_match_element(match);
- if (match == NULL) {
- continue;
- }
-
path = xmlGetNodePath(match);
crm_debug("Processing %s op for %s with %s", op, section, path);
free(path);
if (pcmk__str_eq(op, PCMK__CIB_REQUEST_DELETE, pcmk__str_none)) {
if (match == *result_cib) {
/* Attempting to delete the whole "/cib" */
crm_warn("Cannot perform %s for %s: The xpath is addressing the whole /cib", op, section);
rc = -EINVAL;
break;
}
pcmk__xml_free(match);
if ((options & cib_multiple) == 0) {
break;
}
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_MODIFY, pcmk__str_none)) {
uint32_t flags = pcmk__xaf_none;
if (pcmk_is_set(options, cib_score_update)) {
flags |= pcmk__xaf_score_update;
}
if (pcmk__xe_update_match(match, input, flags) != pcmk_rc_ok) {
rc = -ENXIO;
} else if ((options & cib_multiple) == 0) {
break;
}
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_CREATE, pcmk__str_none)) {
pcmk__xml_copy(match, input);
break;
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_QUERY, pcmk__str_none)) {
if (options & cib_no_children) {
xmlNode *shallow = pcmk__xe_create(*answer,
(const char *) match->name);
pcmk__xe_copy_attrs(shallow, match, pcmk__xaf_none);
if (*answer == NULL) {
*answer = shallow;
}
} else if (options & cib_xpath_address) {
char *path = NULL;
xmlNode *parent = match;
while (parent && parent->type == XML_ELEMENT_NODE) {
const char *id = crm_element_value(parent, PCMK_XA_ID);
char *new_path = NULL;
if (id) {
new_path = crm_strdup_printf("/%s[@" PCMK_XA_ID "='%s']"
"%s",
parent->name, id,
pcmk__s(path, ""));
} else {
new_path = crm_strdup_printf("/%s%s", parent->name,
pcmk__s(path, ""));
}
free(path);
path = new_path;
parent = parent->parent;
}
crm_trace("Got: %s", path);
if (*answer == NULL) {
*answer = pcmk__xe_create(NULL, PCMK__XE_XPATH_QUERY);
}
parent = pcmk__xe_create(*answer, PCMK__XE_XPATH_QUERY_PATH);
crm_xml_add(parent, PCMK_XA_ID, path);
free(path);
} else if (*answer) {
pcmk__xml_copy(*answer, match);
} else {
*answer = match;
}
} else if (pcmk__str_eq(op, PCMK__CIB_REQUEST_REPLACE,
pcmk__str_none)) {
xmlNode *parent = match->parent;
pcmk__xml_free(match);
pcmk__xml_copy(parent, input);
if ((options & cib_multiple) == 0) {
break;
}
}
}
freeXpathObject(xpathObj);
return rc;
}
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Jul 10, 1:12 AM (12 h, 4 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2009444
Default Alt Text
(25 KB)
Attached To
Mode
rP Pacemaker
Attached
Detach File
Event Timeline
Log In to Comment