diff options
Diffstat (limited to '')
75 files changed, 1858 insertions, 1262 deletions
diff --git a/lib/common/Makefile.am b/lib/common/Makefile.am index ef729d4..f9c43b9 100644 --- a/lib/common/Makefile.am +++ b/lib/common/Makefile.am @@ -8,7 +8,8 @@ # include $(top_srcdir)/mk/common.mk -AM_CPPFLAGS += -I$(top_builddir)/lib/gnu -I$(top_srcdir)/lib/gnu +AM_CPPFLAGS += -I$(top_builddir)/lib/gnu \ + -I$(top_srcdir)/lib/gnu ## libraries lib_LTLIBRARIES = libcrmcommon.la @@ -29,14 +30,16 @@ CFLAGS = $(CFLAGS_COPY:-Wcast-qual=) -fPIC # changes the order so the subdirectories are processed afterwards. SUBDIRS = . tests -noinst_HEADERS = crmcommon_private.h mock_private.h +noinst_HEADERS = crmcommon_private.h \ + mock_private.h -libcrmcommon_la_LDFLAGS = -version-info 45:0:11 +libcrmcommon_la_LDFLAGS = -version-info 46:0:12 libcrmcommon_la_CFLAGS = $(CFLAGS_HARDENED_LIB) libcrmcommon_la_LDFLAGS += $(LDFLAGS_HARDENED_LIB) -libcrmcommon_la_LIBADD = @LIBADD_DL@ $(top_builddir)/lib/gnu/libgnu.la +libcrmcommon_la_LIBADD = @LIBADD_DL@ \ + $(top_builddir)/lib/gnu/libgnu.la # If configured with --with-profiling or --with-coverage, BUILD_PROFILING will # be set and -fno-builtin will be added to the CFLAGS. However, libcrmcommon @@ -47,9 +50,10 @@ if BUILD_PROFILING libcrmcommon_la_LIBADD += -lm endif -# Use += rather than backlashed continuation lines for parsing by bumplibs +## Library sources (*must* use += format for bumplibs) libcrmcommon_la_SOURCES = libcrmcommon_la_SOURCES += acl.c +libcrmcommon_la_SOURCES += actions.c libcrmcommon_la_SOURCES += agents.c libcrmcommon_la_SOURCES += alerts.c libcrmcommon_la_SOURCES += attrs.c @@ -75,7 +79,6 @@ libcrmcommon_la_SOURCES += mainloop.c libcrmcommon_la_SOURCES += messages.c libcrmcommon_la_SOURCES += nodes.c libcrmcommon_la_SOURCES += nvpair.c -libcrmcommon_la_SOURCES += operations.c libcrmcommon_la_SOURCES += options.c libcrmcommon_la_SOURCES += output.c libcrmcommon_la_SOURCES += output_html.c @@ -89,12 +92,14 @@ libcrmcommon_la_SOURCES += pid.c libcrmcommon_la_SOURCES += procfs.c libcrmcommon_la_SOURCES += remote.c libcrmcommon_la_SOURCES += results.c +libcrmcommon_la_SOURCES += scheduler.c libcrmcommon_la_SOURCES += schemas.c libcrmcommon_la_SOURCES += scores.c libcrmcommon_la_SOURCES += strings.c libcrmcommon_la_SOURCES += utils.c libcrmcommon_la_SOURCES += watchdog.c libcrmcommon_la_SOURCES += xml.c +libcrmcommon_la_SOURCES += xml_attr.c libcrmcommon_la_SOURCES += xml_display.c libcrmcommon_la_SOURCES += xpath.c @@ -107,18 +112,22 @@ include $(top_srcdir)/mk/tap.mk libcrmcommon_test_la_SOURCES = $(libcrmcommon_la_SOURCES) libcrmcommon_test_la_SOURCES += mock.c -libcrmcommon_test_la_LDFLAGS = $(libcrmcommon_la_LDFLAGS) -rpath $(libdir) $(LDFLAGS_WRAP) +libcrmcommon_test_la_LDFLAGS = $(libcrmcommon_la_LDFLAGS) \ + -rpath $(libdir) \ + $(LDFLAGS_WRAP) # If GCC emits a builtin function in place of something we've mocked up, that will # get used instead of the mocked version which leads to unexpected test results. So # disable all builtins. Older versions of GCC (at least, on RHEL7) will still emit # replacement code for strdup (and possibly other functions) unless -fno-inline is # also added. -libcrmcommon_test_la_CFLAGS = $(libcrmcommon_la_CFLAGS) -DPCMK__UNIT_TESTING -fno-builtin -fno-inline +libcrmcommon_test_la_CFLAGS = $(libcrmcommon_la_CFLAGS) \ + -DPCMK__UNIT_TESTING \ + -fno-builtin \ + -fno-inline # If -fno-builtin is used, -lm also needs to be added. See the comment at # BUILD_PROFILING above. -libcrmcommon_test_la_LIBADD = $(libcrmcommon_la_LIBADD) -lcmocka -lm +libcrmcommon_test_la_LIBADD = $(libcrmcommon_la_LIBADD) \ + -lcmocka \ + -lm nodist_libcrmcommon_test_la_SOURCES = $(nodist_libcrmcommon_la_SOURCES) - -clean-generic: - rm -f *.log *.debug *.xml *~ diff --git a/lib/common/acl.c b/lib/common/acl.c index 33a4e00..1ebd765 100644 --- a/lib/common/acl.c +++ b/lib/common/acl.c @@ -26,7 +26,7 @@ typedef struct xml_acl_s { enum xml_private_flags mode; - char *xpath; + gchar *xpath; } xml_acl_t; static void @@ -35,7 +35,7 @@ free_acl(void *data) if (data) { xml_acl_t *acl = data; - free(acl->xpath); + g_free(acl->xpath); free(acl); } } @@ -68,7 +68,7 @@ create_acl(const xmlNode *xml, GList *acls, enum xml_private_flags mode) if ((tag == NULL) && (ref == NULL) && (xpath == NULL)) { // Schema should prevent this, but to be safe ... crm_trace("Ignoring ACL <%s> element without selection criteria", - crm_element_name(xml)); + xml->name); return NULL; } @@ -77,10 +77,9 @@ create_acl(const xmlNode *xml, GList *acls, enum xml_private_flags mode) acl->mode = mode; if (xpath) { - acl->xpath = strdup(xpath); - CRM_ASSERT(acl->xpath != NULL); + acl->xpath = g_strdup(xpath); crm_trace("Unpacked ACL <%s> element using xpath: %s", - crm_element_name(xml), acl->xpath); + xml->name, acl->xpath); } else { GString *buf = g_string_sized_new(128); @@ -101,12 +100,11 @@ create_acl(const xmlNode *xml, GList *acls, enum xml_private_flags mode) pcmk__g_strcat(buf, "//", pcmk__s(tag, "*"), NULL); } - acl->xpath = strdup((const char *) buf->str); - CRM_ASSERT(acl->xpath != NULL); + acl->xpath = buf->str; - g_string_free(buf, TRUE); + g_string_free(buf, FALSE); crm_trace("Unpacked ACL <%s> element as xpath: %s", - crm_element_name(xml), acl->xpath); + xml->name, acl->xpath); } return g_list_append(acls, acl); @@ -131,10 +129,10 @@ parse_acl_entry(const xmlNode *acl_top, const xmlNode *acl_entry, GList *acls) for (child = pcmk__xe_first_child(acl_entry); child; child = pcmk__xe_next(child)) { - const char *tag = crm_element_name(child); + const char *tag = (const char *) child->name; const char *kind = crm_element_value(child, XML_ACL_ATTR_KIND); - if (strcmp(XML_ACL_TAG_PERMISSION, tag) == 0){ + if (pcmk__xe_is(child, XML_ACL_TAG_PERMISSION)) { CRM_ASSERT(kind != NULL); crm_trace("Unpacking ACL <%s> element of kind '%s'", tag, kind); tag = kind; @@ -157,7 +155,7 @@ parse_acl_entry(const xmlNode *acl_top, const xmlNode *acl_entry, GList *acls) if (role_id && strcmp(ref_role, role_id) == 0) { crm_trace("Unpacking referenced role '%s' in ACL <%s> element", - role_id, crm_element_name(acl_entry)); + role_id, acl_entry->name); acls = parse_acl_entry(acl_top, role, acls); break; } @@ -304,10 +302,9 @@ pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user) for (child = pcmk__xe_first_child(acls); child; child = pcmk__xe_next(child)) { - const char *tag = crm_element_name(child); - if (!strcmp(tag, XML_ACL_TAG_USER) - || !strcmp(tag, XML_ACL_TAG_USERv1)) { + if (pcmk__xe_is(child, XML_ACL_TAG_USER) + || pcmk__xe_is(child, XML_ACL_TAG_USERv1)) { const char *id = crm_element_value(child, XML_ATTR_NAME); if (id == NULL) { @@ -318,7 +315,7 @@ pcmk__unpack_acl(xmlNode *source, xmlNode *target, const char *user) crm_debug("Unpacking ACLs for user '%s'", id); docpriv->acls = parse_acl_entry(acls, child, docpriv->acls); } - } else if (!strcmp(tag, XML_ACL_TAG_GROUP)) { + } else if (pcmk__xe_is(child, XML_ACL_TAG_GROUP)) { const char *id = crm_element_value(child, XML_ATTR_NAME); if (id == NULL) { @@ -392,7 +389,7 @@ purge_xml_attributes(xmlNode *xml) if (test_acl_mode(nodepriv->flags, pcmk__xf_acl_read)) { crm_trace("%s[@" XML_ATTR_ID "=%s] is readable", - crm_element_name(xml), ID(xml)); + xml->name, ID(xml)); return true; } @@ -571,22 +568,22 @@ pcmk__apply_creation_acl(xmlNode *xml, bool check_top) if (implicitly_allowed(xml)) { crm_trace("Creation of <%s> scaffolding with id=\"%s\"" " is implicitly allowed", - crm_element_name(xml), display_id(xml)); + xml->name, display_id(xml)); } else if (pcmk__check_acl(xml, NULL, pcmk__xf_acl_write)) { crm_trace("ACLs allow creation of <%s> with id=\"%s\"", - crm_element_name(xml), display_id(xml)); + xml->name, display_id(xml)); } else if (check_top) { crm_trace("ACLs disallow creation of <%s> with id=\"%s\"", - crm_element_name(xml), display_id(xml)); + xml->name, display_id(xml)); pcmk_free_xml_subtree(xml); return; } else { crm_notice("ACLs would disallow creation of %s<%s> with id=\"%s\"", ((xml == xmlDocGetRootElement(xml->doc))? "root element " : ""), - crm_element_name(xml), display_id(xml)); + xml->name, display_id(xml)); } } diff --git a/lib/common/operations.c b/lib/common/actions.c index 3db96cd..e710615 100644 --- a/lib/common/operations.c +++ b/lib/common/actions.c @@ -107,15 +107,15 @@ parse_op_key(const char *key, char **rsc_id, char **op_type, guint *interval_ms) * contain underbars. Here, list action names and name prefixes that can. */ const char *actions_with_underbars[] = { - CRMD_ACTION_MIGRATED, - CRMD_ACTION_MIGRATE, + PCMK_ACTION_MIGRATE_FROM, + PCMK_ACTION_MIGRATE_TO, NULL }; const char *action_prefixes_with_underbars[] = { - "pre_" CRMD_ACTION_NOTIFY, - "post_" CRMD_ACTION_NOTIFY, - "confirmed-pre_" CRMD_ACTION_NOTIFY, - "confirmed-post_" CRMD_ACTION_NOTIFY, + "pre_" PCMK_ACTION_NOTIFY, + "post_" PCMK_ACTION_NOTIFY, + "confirmed-pre_" PCMK_ACTION_NOTIFY, + "confirmed-post_" PCMK_ACTION_NOTIFY, NULL, }; @@ -470,11 +470,11 @@ crm_op_needs_metadata(const char *rsc_class, const char *op) } // Metadata is needed only for these actions - return pcmk__str_any_of(op, CRMD_ACTION_START, CRMD_ACTION_STATUS, - CRMD_ACTION_PROMOTE, CRMD_ACTION_DEMOTE, - CRMD_ACTION_RELOAD, CRMD_ACTION_RELOAD_AGENT, - CRMD_ACTION_MIGRATE, CRMD_ACTION_MIGRATED, - CRMD_ACTION_NOTIFY, NULL); + return pcmk__str_any_of(op, PCMK_ACTION_START, PCMK_ACTION_MONITOR, + PCMK_ACTION_PROMOTE, PCMK_ACTION_DEMOTE, + PCMK_ACTION_RELOAD, PCMK_ACTION_RELOAD_AGENT, + PCMK_ACTION_MIGRATE_TO, PCMK_ACTION_MIGRATE_FROM, + PCMK_ACTION_NOTIFY, NULL); } /*! @@ -488,7 +488,8 @@ crm_op_needs_metadata(const char *rsc_class, const char *op) bool pcmk__is_fencing_action(const char *action) { - return pcmk__str_any_of(action, "off", "reboot", "poweroff", NULL); + return pcmk__str_any_of(action, PCMK_ACTION_OFF, PCMK_ACTION_REBOOT, + "poweroff", NULL); } bool @@ -498,7 +499,8 @@ pcmk_is_probe(const char *task, guint interval) return false; } - return (interval == 0) && pcmk__str_eq(task, CRMD_ACTION_STATUS, pcmk__str_none); + return (interval == 0) + && pcmk__str_eq(task, PCMK_ACTION_MONITOR, pcmk__str_none); } bool diff --git a/lib/common/alerts.c b/lib/common/alerts.c index abdadef..98b1e3f 100644 --- a/lib/common/alerts.c +++ b/lib/common/alerts.c @@ -1,5 +1,5 @@ /* - * Copyright 2015-2022 the Pacemaker project contributors + * Copyright 2015-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -12,8 +12,8 @@ #include <crm/lrmd.h> #include <crm/msg_xml.h> #include <crm/common/alerts_internal.h> +#include <crm/common/cib_internal.h> #include <crm/common/xml_internal.h> -#include <crm/cib/internal.h> /* for F_CIB_UPDATE_RESULT */ /* * to allow script compatibility we can have more than one @@ -168,86 +168,3 @@ pcmk__add_alert_key_int(GHashTable *table, enum pcmk__alert_keys_e name, g_hash_table_insert(table, strdup(*key), pcmk__itoa(value)); } } - -#define XPATH_PATCHSET1_DIFF "//" F_CIB_UPDATE_RESULT "//" XML_TAG_DIFF_ADDED - -#define XPATH_PATCHSET1_CRMCONFIG XPATH_PATCHSET1_DIFF "//" XML_CIB_TAG_CRMCONFIG -#define XPATH_PATCHSET1_ALERTS XPATH_PATCHSET1_DIFF "//" XML_CIB_TAG_ALERTS - -#define XPATH_PATCHSET1_EITHER \ - XPATH_PATCHSET1_CRMCONFIG " | " XPATH_PATCHSET1_ALERTS - -#define XPATH_CONFIG "/" XML_TAG_CIB "/" XML_CIB_TAG_CONFIGURATION - -#define XPATH_CRMCONFIG XPATH_CONFIG "/" XML_CIB_TAG_CRMCONFIG "/" -#define XPATH_ALERTS XPATH_CONFIG "/" XML_CIB_TAG_ALERTS - -/*! - * \internal - * \brief Check whether a CIB update affects alerts - * - * \param[in] msg XML containing CIB update - * \param[in] config Whether to check for crmconfig change as well - * - * \return TRUE if update affects alerts, FALSE otherwise - */ -bool -pcmk__alert_in_patchset(xmlNode *msg, bool config) -{ - int rc = -1; - int format= 1; - xmlNode *patchset = get_message_xml(msg, F_CIB_UPDATE_RESULT); - xmlNode *change = NULL; - xmlXPathObject *xpathObj = NULL; - - CRM_CHECK(msg != NULL, return FALSE); - - crm_element_value_int(msg, F_CIB_RC, &rc); - if (rc < pcmk_ok) { - crm_trace("Ignore failed CIB update: %s (%d)", pcmk_strerror(rc), rc); - return FALSE; - } - - crm_element_value_int(patchset, "format", &format); - if (format == 1) { - const char *diff = (config? XPATH_PATCHSET1_EITHER : XPATH_PATCHSET1_ALERTS); - - if ((xpathObj = xpath_search(msg, diff)) != NULL) { - freeXpathObject(xpathObj); - return TRUE; - } - } else if (format == 2) { - for (change = pcmk__xml_first_child(patchset); change != NULL; - change = pcmk__xml_next(change)) { - const char *xpath = crm_element_value(change, XML_DIFF_PATH); - - if (xpath == NULL) { - continue; - } - - if ((!config || !strstr(xpath, XPATH_CRMCONFIG)) - && !strstr(xpath, XPATH_ALERTS)) { - - /* this is not a change to an existing section ... */ - - xmlNode *section = NULL; - const char *name = NULL; - - if ((strcmp(xpath, XPATH_CONFIG) != 0) || - ((section = pcmk__xml_first_child(change)) == NULL) || - ((name = crm_element_name(section)) == NULL) || - (strcmp(name, XML_CIB_TAG_ALERTS) != 0)) { - - /* ... nor is it a newly added alerts section */ - continue; - } - } - - return TRUE; - } - - } else { - crm_warn("Unknown patch format: %d", format); - } - return FALSE; -} diff --git a/lib/common/cib.c b/lib/common/cib.c index b84c5e8..fee7881 100644 --- a/lib/common/cib.c +++ b/lib/common/cib.c @@ -1,6 +1,6 @@ /* * Original copyright 2004 International Business Machines - * Later changes copyright 2008-2021 the Pacemaker project contributors + * Later changes copyright 2008-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -14,6 +14,8 @@ #include <libxml/tree.h> // xmlNode #include <crm/msg_xml.h> +#include <crm/common/cib.h> +#include <crm/common/cib_internal.h> /* * Functions to help find particular sections of the CIB @@ -99,7 +101,7 @@ static struct { }; /*! - * \brief Get the XPath needed to find a specified CIB element name + * \brief Get the relative XPath needed to find a specified CIB element name * * \param[in] element_name Name of CIB element * @@ -120,6 +122,23 @@ pcmk_cib_xpath_for(const char *element_name) } /*! + * \internal + * \brief Get the absolute XPath needed to find a specified CIB element name + * + * \param[in] element Name of CIB element + * + * \return XPath for finding \p element in CIB XML (or \c NULL if unknown) + */ +const char * +pcmk__cib_abs_xpath_for(const char *element) +{ + const char *xpath = pcmk_cib_xpath_for(element); + + // XPaths returned by pcmk_cib_xpath_for() are relative (starting with "//") + return ((xpath != NULL)? (xpath + 1) : NULL); +} + +/*! * \brief Get the parent element name of a given CIB element name * * \param[in] element_name Name of CIB element diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h index 7faccb6..121d663 100644 --- a/lib/common/crmcommon_private.h +++ b/lib/common/crmcommon_private.h @@ -63,7 +63,7 @@ typedef struct xml_doc_private_s { } while (0) G_GNUC_INTERNAL -void pcmk__xml2text(xmlNodePtr data, uint32_t options, GString *buffer, +void pcmk__xml2text(const xmlNode *data, uint32_t options, GString *buffer, int depth); G_GNUC_INTERNAL @@ -116,12 +116,14 @@ G_GNUC_INTERNAL void pcmk__log_xmllib_err(void *ctx, const char *fmt, ...) G_GNUC_PRINTF(2, 3); -static inline const char * -pcmk__xml_attr_value(const xmlAttr *attr) -{ - return ((attr == NULL) || (attr->children == NULL))? NULL - : (const char *) attr->children->content; -} +G_GNUC_INTERNAL +void pcmk__mark_xml_node_dirty(xmlNode *xml); + +G_GNUC_INTERNAL +bool pcmk__marked_as_deleted(xmlAttrPtr a, void *user_data); + +G_GNUC_INTERNAL +void pcmk__dump_xml_attr(const xmlAttr *attr, GString *buffer); /* * IPC @@ -173,11 +175,11 @@ typedef struct pcmk__ipc_methods_s { * \brief Check whether an IPC request results in a reply * * \param[in,out] api IPC API connection - * \param[in,out] request IPC request XML + * \param[in] request IPC request XML * * \return true if request would result in an IPC reply, false otherwise */ - bool (*reply_expected)(pcmk_ipc_api_t *api, xmlNode *request); + bool (*reply_expected)(pcmk_ipc_api_t *api, const xmlNode *request); /*! * \internal @@ -222,7 +224,7 @@ typedef struct pcmk__ipc_header_s { } pcmk__ipc_header_t; G_GNUC_INTERNAL -int pcmk__send_ipc_request(pcmk_ipc_api_t *api, xmlNode *request); +int pcmk__send_ipc_request(pcmk_ipc_api_t *api, const xmlNode *request); G_GNUC_INTERNAL void pcmk__call_ipc_callback(pcmk_ipc_api_t *api, @@ -264,47 +266,6 @@ pcmk__ipc_methods_t *pcmk__schedulerd_api_methods(void); //! XML has been moved #define PCMK__XML_PREFIX_MOVED "+~" -/*! - * \brief Check the authenticity of the IPC socket peer process - * - * If everything goes well, peer's authenticity is verified by the means - * of comparing against provided referential UID and GID (either satisfies), - * and the result of this check can be deduced from the return value. - * As an exception, detected UID of 0 ("root") satisfies arbitrary - * provided referential daemon's credentials. - * - * \param[in] qb_ipc libqb client connection if available - * \param[in] sock IPC related, connected Unix socket to check peer of - * \param[in] refuid referential UID to check against - * \param[in] refgid referential GID to check against - * \param[out] gotpid to optionally store obtained PID of the peer - * (not available on FreeBSD, special value of 1 - * used instead, and the caller is required to - * special case this value respectively) - * \param[out] gotuid to optionally store obtained UID of the peer - * \param[out] gotgid to optionally store obtained GID of the peer - * - * \return Standard Pacemaker return code - * ie: 0 if it the connection is authentic - * pcmk_rc_ipc_unauthorized if the connection is not authentic, - * standard errors. - * - * \note While this function is tolerant on what constitutes authorized - * IPC daemon process (its effective user matches UID=0 or \p refuid, - * or at least its group matches \p refgid), either or both (in case - * of UID=0) mismatches on the expected credentials of such peer - * process \e shall be investigated at the caller when value of 1 - * gets returned there, since higher-than-expected privileges in - * respect to the expected/intended credentials possibly violate - * the least privilege principle and may pose an additional risk - * (i.e. such accidental inconsistency shall be eventually fixed). - */ -int pcmk__crm_ipc_is_authentic_process(qb_ipcc_connection_t *qb_ipc, int sock, - uid_t refuid, gid_t refgid, - pid_t *gotpid, uid_t *gotuid, - gid_t *gotgid); - - /* * Output */ diff --git a/lib/common/digest.c b/lib/common/digest.c index 3bf04bf..4de6f97 100644 --- a/lib/common/digest.c +++ b/lib/common/digest.c @@ -1,5 +1,5 @@ /* - * Copyright 2015-2022 the Pacemaker project contributors + * Copyright 2015-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -89,7 +89,7 @@ calculate_xml_digest_v1(xmlNode *input, gboolean sort, gboolean ignored) * \return Newly allocated string containing digest */ static char * -calculate_xml_digest_v2(xmlNode *source, gboolean do_filter) +calculate_xml_digest_v2(const xmlNode *source, gboolean do_filter) { char *digest = NULL; GString *buffer = g_string_sized_new(1024); diff --git a/lib/common/io.c b/lib/common/io.c index 2264e16..35efbe9 100644 --- a/lib/common/io.c +++ b/lib/common/io.c @@ -460,11 +460,17 @@ pcmk__file_contents(const char *filename, char **contents) goto bail; } rewind(fp); - read_len = fread(*contents, 1, length, fp); /* Coverity: False positive */ + + read_len = fread(*contents, 1, length, fp); if (read_len != length) { free(*contents); *contents = NULL; rc = EIO; + } else { + /* Coverity thinks *contents isn't null-terminated. It doesn't + * understand calloc(). + */ + (*contents)[length] = '\0'; } } diff --git a/lib/common/ipc_attrd.c b/lib/common/ipc_attrd.c index 7c40aa7..9caaabe 100644 --- a/lib/common/ipc_attrd.c +++ b/lib/common/ipc_attrd.c @@ -44,7 +44,7 @@ set_pairs_data(pcmk__attrd_api_reply_t *data, xmlNode *msg_data) } static bool -reply_expected(pcmk_ipc_api_t *api, xmlNode *request) +reply_expected(pcmk_ipc_api_t *api, const xmlNode *request) { const char *command = crm_element_value(request, PCMK__XA_TASK); @@ -169,32 +169,29 @@ destroy_api(pcmk_ipc_api_t *api) } static int -connect_and_send_attrd_request(pcmk_ipc_api_t *api, xmlNode *request) +connect_and_send_attrd_request(pcmk_ipc_api_t *api, const xmlNode *request) { int rc = pcmk_rc_ok; - int max = 5; - - while (max > 0) { - crm_info("Connecting to cluster... %d retries remaining", max); - rc = pcmk_connect_ipc(api, pcmk_ipc_dispatch_sync); - - if (rc == pcmk_rc_ok) { - rc = pcmk__send_ipc_request(api, request); - break; - } else if (rc == EAGAIN || rc == EALREADY) { - sleep(5 - max); - max--; - } else { - crm_err("Could not connect to attrd: %s", pcmk_rc_str(rc)); - break; - } + + rc = pcmk__connect_ipc(api, pcmk_ipc_dispatch_sync, 5); + if (rc != pcmk_rc_ok) { + crm_err("Could not connect to %s: %s", + pcmk_ipc_name(api, true), pcmk_rc_str(rc)); + return rc; } - return rc; + rc = pcmk__send_ipc_request(api, request); + if (rc != pcmk_rc_ok) { + crm_err("Could not send request to %s: %s", + pcmk_ipc_name(api, true), pcmk_rc_str(rc)); + return rc; + } + + return pcmk_rc_ok; } static int -send_attrd_request(pcmk_ipc_api_t *api, xmlNode *request) +send_attrd_request(pcmk_ipc_api_t *api, const xmlNode *request) { return pcmk__send_ipc_request(api, request); } diff --git a/lib/common/ipc_client.c b/lib/common/ipc_client.c index c6d1645..0d38650 100644 --- a/lib/common/ipc_client.c +++ b/lib/common/ipc_client.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2022 the Pacemaker project contributors + * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -31,6 +31,10 @@ #include <crm/common/ipc_internal.h> #include "crmcommon_private.h" +static int is_ipc_provider_expected(qb_ipcc_connection_t *qb_ipc, int sock, + uid_t refuid, gid_t refgid, pid_t *gotpid, + uid_t *gotuid, gid_t *gotgid); + /*! * \brief Create a new object for using Pacemaker daemon IPC * @@ -164,7 +168,7 @@ ipc_post_disconnect(gpointer user_data) { pcmk_ipc_api_t *api = user_data; - crm_info("Disconnected from %s IPC API", pcmk_ipc_name(api, true)); + crm_info("Disconnected from %s", pcmk_ipc_name(api, true)); // Perform any daemon-specific handling needed if ((api->cmds != NULL) && (api->cmds->post_disconnect != NULL)) { @@ -389,7 +393,7 @@ dispatch_ipc_source_data(const char *buffer, ssize_t length, gpointer user_data) * meaning no data is available; all other values indicate errors. * \todo This does not allow the caller to poll multiple file descriptors at * once. If there is demand for that, we could add a wrapper for - * crm_ipc_get_fd(api->ipc), so the caller can call poll() themselves. + * pcmk__ipc_fd(api->ipc), so the caller can call poll() themselves. */ int pcmk_poll_ipc(const pcmk_ipc_api_t *api, int timeout_ms) @@ -400,7 +404,14 @@ pcmk_poll_ipc(const pcmk_ipc_api_t *api, int timeout_ms) if ((api == NULL) || (api->dispatch_type != pcmk_ipc_dispatch_poll)) { return EINVAL; } - pollfd.fd = crm_ipc_get_fd(api->ipc); + + rc = pcmk__ipc_fd(api->ipc, &(pollfd.fd)); + if (rc != pcmk_rc_ok) { + crm_debug("Could not obtain file descriptor for %s IPC: %s", + pcmk_ipc_name(api, true), pcmk_rc_str(rc)); + return rc; + } + pollfd.events = POLLIN; rc = poll(&pollfd, 1, timeout_ms); if (rc < 0) { @@ -465,54 +476,54 @@ connect_with_main_loop(pcmk_ipc_api_t *api) static int connect_without_main_loop(pcmk_ipc_api_t *api) { - int rc; + int rc = pcmk__connect_generic_ipc(api->ipc); - if (!crm_ipc_connect(api->ipc)) { - rc = errno; + if (rc != pcmk_rc_ok) { crm_ipc_close(api->ipc); - return rc; + } else { + crm_debug("Connected to %s IPC (without main loop)", + pcmk_ipc_name(api, true)); } - crm_debug("Connected to %s IPC (without main loop)", - pcmk_ipc_name(api, true)); - return pcmk_rc_ok; + return rc; } /*! - * \brief Connect to a Pacemaker daemon via IPC + * \internal + * \brief Connect to a Pacemaker daemon via IPC (retrying after soft errors) * * \param[in,out] api IPC API instance * \param[in] dispatch_type How IPC replies should be dispatched + * \param[in] attempts How many times to try (in case of soft error) * * \return Standard Pacemaker return code */ int -pcmk_connect_ipc(pcmk_ipc_api_t *api, enum pcmk_ipc_dispatch dispatch_type) +pcmk__connect_ipc(pcmk_ipc_api_t *api, enum pcmk_ipc_dispatch dispatch_type, + int attempts) { - const int n_attempts = 2; int rc = pcmk_rc_ok; - if (api == NULL) { - crm_err("Cannot connect to uninitialized API object"); + if ((api == NULL) || (attempts < 1)) { return EINVAL; } if (api->ipc == NULL) { - api->ipc = crm_ipc_new(pcmk_ipc_name(api, false), - api->ipc_size_max); + api->ipc = crm_ipc_new(pcmk_ipc_name(api, false), api->ipc_size_max); if (api->ipc == NULL) { - crm_err("Failed to re-create IPC API"); return ENOMEM; } } if (crm_ipc_connected(api->ipc)) { - crm_trace("Already connected to %s IPC API", pcmk_ipc_name(api, true)); + crm_trace("Already connected to %s", pcmk_ipc_name(api, true)); return pcmk_rc_ok; } api->dispatch_type = dispatch_type; - for (int i = 0; i < n_attempts; i++) { + crm_debug("Attempting connection to %s (up to %d time%s)", + pcmk_ipc_name(api, true), attempts, pcmk__plural_s(attempts)); + for (int remaining = attempts - 1; remaining >= 0; --remaining) { switch (dispatch_type) { case pcmk_ipc_dispatch_main: rc = connect_with_main_loop(api); @@ -524,17 +535,15 @@ pcmk_connect_ipc(pcmk_ipc_api_t *api, enum pcmk_ipc_dispatch dispatch_type) break; } - if (rc != EAGAIN) { - break; + if ((remaining == 0) || ((rc != EAGAIN) && (rc != EALREADY))) { + break; // Result is final } - /* EAGAIN may occur due to interruption by a signal or due to some - * transient issue. Try one more time to be more resilient. - */ - if (i < (n_attempts - 1)) { - crm_trace("Connection to %s IPC API failed with EAGAIN, retrying", - pcmk_ipc_name(api, true)); - } + // Retry after soft error (interrupted by signal, etc.) + pcmk__sleep_ms((attempts - remaining) * 500); + crm_debug("Re-attempting connection to %s (%d attempt%s remaining)", + pcmk_ipc_name(api, true), remaining, + pcmk__plural_s(remaining)); } if (rc != pcmk_rc_ok) { @@ -551,6 +560,26 @@ pcmk_connect_ipc(pcmk_ipc_api_t *api, enum pcmk_ipc_dispatch dispatch_type) } /*! + * \brief Connect to a Pacemaker daemon via IPC + * + * \param[in,out] api IPC API instance + * \param[in] dispatch_type How IPC replies should be dispatched + * + * \return Standard Pacemaker return code + */ +int +pcmk_connect_ipc(pcmk_ipc_api_t *api, enum pcmk_ipc_dispatch dispatch_type) +{ + int rc = pcmk__connect_ipc(api, dispatch_type, 2); + + if (rc != pcmk_rc_ok) { + crm_err("Connection to %s failed: %s", + pcmk_ipc_name(api, true), pcmk_rc_str(rc)); + } + return rc; +} + +/*! * \brief Disconnect an IPC API instance * * \param[in,out] api IPC API connection @@ -628,7 +657,7 @@ pcmk_register_ipc_callback(pcmk_ipc_api_t *api, pcmk_ipc_callback_t cb, * \brief Send an XML request across an IPC API connection * * \param[in,out] api IPC API connection - * \param[in,out] request XML request to send + * \param[in] request XML request to send * * \return Standard Pacemaker return code * @@ -636,7 +665,7 @@ pcmk_register_ipc_callback(pcmk_ipc_api_t *api, pcmk_ipc_callback_t cb, * requests, because it handles different dispatch types appropriately. */ int -pcmk__send_ipc_request(pcmk_ipc_api_t *api, xmlNode *request) +pcmk__send_ipc_request(pcmk_ipc_api_t *api, const xmlNode *request) { int rc; xmlNode *reply = NULL; @@ -855,6 +884,77 @@ crm_ipc_new(const char *name, size_t max_size) } /*! + * \internal + * \brief Connect a generic (not daemon-specific) IPC object + * + * \param[in,out] ipc Generic IPC object to connect + * + * \return Standard Pacemaker return code + */ +int +pcmk__connect_generic_ipc(crm_ipc_t *ipc) +{ + uid_t cl_uid = 0; + gid_t cl_gid = 0; + pid_t found_pid = 0; + uid_t found_uid = 0; + gid_t found_gid = 0; + int rc = pcmk_rc_ok; + + if (ipc == NULL) { + return EINVAL; + } + + ipc->need_reply = FALSE; + ipc->ipc = qb_ipcc_connect(ipc->server_name, ipc->buf_size); + if (ipc->ipc == NULL) { + return errno; + } + + rc = qb_ipcc_fd_get(ipc->ipc, &ipc->pfd.fd); + if (rc < 0) { // -errno + crm_ipc_close(ipc); + return -rc; + } + + rc = pcmk_daemon_user(&cl_uid, &cl_gid); + rc = pcmk_legacy2rc(rc); + if (rc != pcmk_rc_ok) { + crm_ipc_close(ipc); + return rc; + } + + rc = is_ipc_provider_expected(ipc->ipc, ipc->pfd.fd, cl_uid, cl_gid, + &found_pid, &found_uid, &found_gid); + if (rc != pcmk_rc_ok) { + if (rc == pcmk_rc_ipc_unauthorized) { + crm_info("%s IPC provider authentication failed: process %lld has " + "uid %lld (expected %lld) and gid %lld (expected %lld)", + ipc->server_name, + (long long) PCMK__SPECIAL_PID_AS_0(found_pid), + (long long) found_uid, (long long) cl_uid, + (long long) found_gid, (long long) cl_gid); + } + crm_ipc_close(ipc); + return rc; + } + + ipc->max_buf_size = qb_ipcc_get_buffer_size(ipc->ipc); + if (ipc->max_buf_size > ipc->buf_size) { + free(ipc->buffer); + ipc->buffer = calloc(ipc->max_buf_size, sizeof(char)); + if (ipc->buffer == NULL) { + rc = errno; + crm_ipc_close(ipc); + return rc; + } + ipc->buf_size = ipc->max_buf_size; + } + + return pcmk_rc_ok; +} + +/*! * \brief Establish an IPC connection to a Pacemaker component * * \param[in,out] client Connection instance obtained from crm_ipc_new() @@ -866,76 +966,26 @@ crm_ipc_new(const char *name, size_t max_size) bool crm_ipc_connect(crm_ipc_t *client) { - uid_t cl_uid = 0; - gid_t cl_gid = 0; - pid_t found_pid = 0; uid_t found_uid = 0; gid_t found_gid = 0; - int rv; + int rc = pcmk__connect_generic_ipc(client); - if (client == NULL) { - errno = EINVAL; - return false; + if (rc == pcmk_rc_ok) { + return true; } - - client->need_reply = FALSE; - client->ipc = qb_ipcc_connect(client->server_name, client->buf_size); - - if (client->ipc == NULL) { + if ((client != NULL) && (client->ipc == NULL)) { + errno = (rc > 0)? rc : ENOTCONN; crm_debug("Could not establish %s IPC connection: %s (%d)", client->server_name, pcmk_rc_str(errno), errno); - return false; - } - - client->pfd.fd = crm_ipc_get_fd(client); - if (client->pfd.fd < 0) { - rv = errno; - /* message already omitted */ - crm_ipc_close(client); - errno = rv; - return false; - } - - rv = pcmk_daemon_user(&cl_uid, &cl_gid); - if (rv < 0) { - /* message already omitted */ - crm_ipc_close(client); - errno = -rv; - return false; - } - - if ((rv = pcmk__crm_ipc_is_authentic_process(client->ipc, client->pfd.fd, cl_uid, cl_gid, - &found_pid, &found_uid, - &found_gid)) == pcmk_rc_ipc_unauthorized) { - crm_err("%s IPC provider authentication failed: process %lld has " - "uid %lld (expected %lld) and gid %lld (expected %lld)", - client->server_name, - (long long) PCMK__SPECIAL_PID_AS_0(found_pid), - (long long) found_uid, (long long) cl_uid, - (long long) found_gid, (long long) cl_gid); - crm_ipc_close(client); + } else if (rc == pcmk_rc_ipc_unauthorized) { + crm_err("%s IPC provider authentication failed", + (client == NULL)? "Pacemaker" : client->server_name); errno = ECONNABORTED; - return false; - - } else if (rv != pcmk_rc_ok) { - crm_perror(LOG_ERR, "Could not verify authenticity of %s IPC provider", - client->server_name); - crm_ipc_close(client); - if (rv > 0) { - errno = rv; - } else { - errno = ENOTCONN; - } - return false; - } - - qb_ipcc_context_set(client->ipc, client); - - client->max_buf_size = qb_ipcc_get_buffer_size(client->ipc); - if (client->max_buf_size > client->buf_size) { - free(client->buffer); - client->buffer = calloc(1, client->max_buf_size); - client->buf_size = client->max_buf_size; + } else { + crm_perror(LOG_ERR, + "Could not verify authenticity of %s IPC provider", + (client == NULL)? "Pacemaker" : client->server_name); + errno = ENOTCONN; } - return true; + return false; } void @@ -977,18 +1027,40 @@ crm_ipc_destroy(crm_ipc_t * client) } } +/*! + * \internal + * \brief Get the file descriptor for a generic IPC object + * + * \param[in,out] ipc Generic IPC object to get file descriptor for + * \param[out] fd Where to store file descriptor + * + * \return Standard Pacemaker return code + */ +int +pcmk__ipc_fd(crm_ipc_t *ipc, int *fd) +{ + if ((ipc == NULL) || (fd == NULL)) { + return EINVAL; + } + if ((ipc->ipc == NULL) || (ipc->pfd.fd < 0)) { + return ENOTCONN; + } + *fd = ipc->pfd.fd; + return pcmk_rc_ok; +} + int crm_ipc_get_fd(crm_ipc_t * client) { - int fd = 0; + int fd = -1; - if (client && client->ipc && (qb_ipcc_fd_get(client->ipc, &fd) == 0)) { - return fd; + if (pcmk__ipc_fd(client, &fd) != pcmk_rc_ok) { + crm_err("Could not obtain file descriptor for %s IPC", + ((client == NULL)? "unspecified" : client->server_name)); + errno = EINVAL; + return -EINVAL; } - errno = EINVAL; - crm_perror(LOG_ERR, "Could not obtain file descriptor for %s IPC", - (client? client->server_name : "unspecified")); - return -errno; + return fd; } bool @@ -1057,12 +1129,13 @@ crm_ipc_decompress(crm_ipc_t * client) rc = BZ2_bzBuffToBuffDecompress(uncompressed + sizeof(pcmk__ipc_header_t), &size_u, client->buffer + sizeof(pcmk__ipc_header_t), header->size_compressed, 1, 0); + rc = pcmk__bzlib2rc(rc); - if (rc != BZ_OK) { - crm_err("Decompression failed: %s " CRM_XS " bzerror=%d", - bz2_strerror(rc), rc); + if (rc != pcmk_rc_ok) { + crm_err("Decompression failed: %s " CRM_XS " rc=%d", + pcmk_rc_str(rc), rc); free(uncompressed); - return EILSEQ; + return rc; } /* @@ -1221,7 +1294,7 @@ internal_ipc_get_reply(crm_ipc_t *client, int request_id, int ms_timeout, * \brief Send an IPC XML message * * \param[in,out] client Connection to IPC server - * \param[in,out] message XML message to send + * \param[in] message XML message to send * \param[in] flags Bitmask of crm_ipc_flags * \param[in] ms_timeout Give up if not sent within this much time * (5 seconds if 0, or no timeout if negative) @@ -1231,8 +1304,8 @@ internal_ipc_get_reply(crm_ipc_t *client, int request_id, int ms_timeout, * if reply was needed, otherwise number of bytes sent */ int -crm_ipc_send(crm_ipc_t * client, xmlNode * message, enum crm_ipc_flags flags, int32_t ms_timeout, - xmlNode ** reply) +crm_ipc_send(crm_ipc_t *client, const xmlNode *message, + enum crm_ipc_flags flags, int32_t ms_timeout, xmlNode **reply) { int rc = 0; ssize_t qb_rc = 0; @@ -1385,89 +1458,129 @@ crm_ipc_send(crm_ipc_t * client, xmlNode * message, enum crm_ipc_flags flags, in return rc; } -int -pcmk__crm_ipc_is_authentic_process(qb_ipcc_connection_t *qb_ipc, int sock, uid_t refuid, gid_t refgid, - pid_t *gotpid, uid_t *gotuid, gid_t *gotgid) +/*! + * \brief Ensure an IPC provider has expected user or group + * + * \param[in] qb_ipc libqb client connection if available + * \param[in] sock Connected Unix socket for IPC + * \param[in] refuid Expected user ID + * \param[in] refgid Expected group ID + * \param[out] gotpid If not NULL, where to store provider's actual process ID + * (or 1 on platforms where ID is not available) + * \param[out] gotuid If not NULL, where to store provider's actual user ID + * \param[out] gotgid If not NULL, where to store provider's actual group ID + * + * \return Standard Pacemaker return code + * \note An actual user ID of 0 (root) will always be considered authorized, + * regardless of the expected values provided. The caller can use the + * output arguments to be stricter than this function. + */ +static int +is_ipc_provider_expected(qb_ipcc_connection_t *qb_ipc, int sock, + uid_t refuid, gid_t refgid, + pid_t *gotpid, uid_t *gotuid, gid_t *gotgid) { - int ret = 0; - pid_t found_pid = 0; uid_t found_uid = 0; gid_t found_gid = 0; -#if defined(HAVE_UCRED) - struct ucred ucred; - socklen_t ucred_len = sizeof(ucred); -#endif + int rc = EOPNOTSUPP; + pid_t found_pid = 0; + uid_t found_uid = 0; + gid_t found_gid = 0; #ifdef HAVE_QB_IPCC_AUTH_GET - if (qb_ipc && !qb_ipcc_auth_get(qb_ipc, &found_pid, &found_uid, &found_gid)) { - goto do_checks; + if (qb_ipc != NULL) { + rc = qb_ipcc_auth_get(qb_ipc, &found_pid, &found_uid, &found_gid); + rc = -rc; // libqb returns 0 or -errno + if (rc == pcmk_rc_ok) { + goto found; + } } #endif -#if defined(HAVE_UCRED) - if (!getsockopt(sock, SOL_SOCKET, SO_PEERCRED, - &ucred, &ucred_len) - && ucred_len == sizeof(ucred)) { - found_pid = ucred.pid; found_uid = ucred.uid; found_gid = ucred.gid; +#ifdef HAVE_UCRED + { + struct ucred ucred; + socklen_t ucred_len = sizeof(ucred); -#elif defined(HAVE_SOCKPEERCRED) - struct sockpeercred sockpeercred; - socklen_t sockpeercred_len = sizeof(sockpeercred); - - if (!getsockopt(sock, SOL_SOCKET, SO_PEERCRED, - &sockpeercred, &sockpeercred_len) - && sockpeercred_len == sizeof(sockpeercred_len)) { - found_pid = sockpeercred.pid; - found_uid = sockpeercred.uid; found_gid = sockpeercred.gid; + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, &ucred, &ucred_len) < 0) { + rc = errno; + } else if (ucred_len != sizeof(ucred)) { + rc = EOPNOTSUPP; + } else { + found_pid = ucred.pid; + found_uid = ucred.uid; + found_gid = ucred.gid; + goto found; + } + } +#endif -#elif defined(HAVE_GETPEEREID) - if (!getpeereid(sock, &found_uid, &found_gid)) { - found_pid = PCMK__SPECIAL_PID; /* cannot obtain PID (FreeBSD) */ +#ifdef HAVE_SOCKPEERCRED + { + struct sockpeercred sockpeercred; + socklen_t sockpeercred_len = sizeof(sockpeercred); -#elif defined(HAVE_GETPEERUCRED) - ucred_t *ucred; - if (!getpeerucred(sock, &ucred)) { - errno = 0; - found_pid = ucred_getpid(ucred); - found_uid = ucred_geteuid(ucred); found_gid = ucred_getegid(ucred); - ret = -errno; - ucred_free(ucred); - if (ret) { - return (ret < 0) ? ret : -pcmk_err_generic; + if (getsockopt(sock, SOL_SOCKET, SO_PEERCRED, + &sockpeercred, &sockpeercred_len) < 0) { + rc = errno; + } else if (sockpeercred_len != sizeof(sockpeercred)) { + rc = EOPNOTSUPP; + } else { + found_pid = sockpeercred.pid; + found_uid = sockpeercred.uid; + found_gid = sockpeercred.gid; + goto found; } - -#else -# error "No way to authenticate a Unix socket peer" - errno = 0; - if (0) { + } #endif -#ifdef HAVE_QB_IPCC_AUTH_GET - do_checks: + +#ifdef HAVE_GETPEEREID // For example, FreeBSD + if (getpeereid(sock, &found_uid, &found_gid) < 0) { + rc = errno; + } else { + found_pid = PCMK__SPECIAL_PID; + goto found; + } #endif - if (gotpid != NULL) { - *gotpid = found_pid; - } - if (gotuid != NULL) { - *gotuid = found_uid; - } - if (gotgid != NULL) { - *gotgid = found_gid; - } - if (found_uid == 0 || found_uid == refuid || found_gid == refgid) { - ret = 0; + +#ifdef HAVE_GETPEERUCRED + { + ucred_t *ucred = NULL; + + if (getpeerucred(sock, &ucred) < 0) { + rc = errno; } else { - ret = pcmk_rc_ipc_unauthorized; + found_pid = ucred_getpid(ucred); + found_uid = ucred_geteuid(ucred); + found_gid = ucred_getegid(ucred); + ucred_free(ucred); + goto found; } - } else { - ret = (errno > 0) ? errno : pcmk_rc_error; } - return ret; +#endif + + return rc; // If we get here, nothing succeeded + +found: + if (gotpid != NULL) { + *gotpid = found_pid; + } + if (gotuid != NULL) { + *gotuid = found_uid; + } + if (gotgid != NULL) { + *gotgid = found_gid; + } + if ((found_uid != 0) && (found_uid != refuid) && (found_gid != refgid)) { + return pcmk_rc_ipc_unauthorized; + } + return pcmk_rc_ok; } int crm_ipc_is_authentic_process(int sock, uid_t refuid, gid_t refgid, pid_t *gotpid, uid_t *gotuid, gid_t *gotgid) { - int ret = pcmk__crm_ipc_is_authentic_process(NULL, sock, refuid, refgid, - gotpid, gotuid, gotgid); + int ret = is_ipc_provider_expected(NULL, sock, refuid, refgid, + gotpid, gotuid, gotgid); /* The old function had some very odd return codes*/ if (ret == 0) { @@ -1528,8 +1641,8 @@ pcmk__ipc_is_authentic_process_active(const char *name, uid_t refuid, goto bail; } - auth_rc = pcmk__crm_ipc_is_authentic_process(c, fd, refuid, refgid, &found_pid, - &found_uid, &found_gid); + auth_rc = is_ipc_provider_expected(c, fd, refuid, refgid, + &found_pid, &found_uid, &found_gid); if (auth_rc == pcmk_rc_ipc_unauthorized) { crm_err("Daemon (IPC %s) effectively blocked with unauthorized" " process %lld (uid: %lld, gid: %lld)", diff --git a/lib/common/ipc_common.c b/lib/common/ipc_common.c index d0c0636..a48b0e9 100644 --- a/lib/common/ipc_common.c +++ b/lib/common/ipc_common.c @@ -35,7 +35,7 @@ pcmk__ipc_buffer_size(unsigned int max) if (global_max == 0) { long long global_ll; - if ((pcmk__scan_ll(getenv("PCMK_ipc_buffer"), &global_ll, + if ((pcmk__scan_ll(pcmk__env_option(PCMK__ENV_IPC_BUFFER), &global_ll, 0LL) != pcmk_rc_ok) || (global_ll <= 0)) { global_max = MAX_MSG_SIZE; // Default for unset or invalid diff --git a/lib/common/ipc_controld.c b/lib/common/ipc_controld.c index 9303afd..8e2016e 100644 --- a/lib/common/ipc_controld.c +++ b/lib/common/ipc_controld.c @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the Pacemaker project contributors + * Copyright 2020-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -135,7 +135,7 @@ set_node_info_data(pcmk_controld_api_reply_t *data, xmlNode *msg_data) data->data.node_info.uuid = crm_element_value(msg_data, XML_ATTR_ID); data->data.node_info.uname = crm_element_value(msg_data, XML_ATTR_UNAME); - data->data.node_info.state = crm_element_value(msg_data, XML_NODE_IS_PEER); + data->data.node_info.state = crm_element_value(msg_data, PCMK__XA_CRMD); } static void @@ -169,26 +169,24 @@ set_nodes_data(pcmk_controld_api_reply_t *data, xmlNode *msg_data) node_info->id = id_ll; } node_info->uname = crm_element_value(node, XML_ATTR_UNAME); - node_info->state = crm_element_value(node, XML_NODE_IN_CLUSTER); + node_info->state = crm_element_value(node, PCMK__XA_IN_CCM); data->data.nodes = g_list_prepend(data->data.nodes, node_info); } } static bool -reply_expected(pcmk_ipc_api_t *api, xmlNode *request) +reply_expected(pcmk_ipc_api_t *api, const xmlNode *request) { - const char *command = crm_element_value(request, F_CRM_TASK); - - if (command == NULL) { - return false; - } - - // We only need to handle commands that functions in this file can send - return !strcmp(command, CRM_OP_REPROBE) - || !strcmp(command, CRM_OP_NODE_INFO) - || !strcmp(command, CRM_OP_PING) - || !strcmp(command, CRM_OP_LRM_FAIL) - || !strcmp(command, CRM_OP_LRM_DELETE); + // We only need to handle commands that API functions can send + return pcmk__str_any_of(crm_element_value(request, F_CRM_TASK), + PCMK__CONTROLD_CMD_NODES, + CRM_OP_LRM_DELETE, + CRM_OP_LRM_FAIL, + CRM_OP_NODE_INFO, + CRM_OP_PING, + CRM_OP_REPROBE, + CRM_OP_RM_NODE_CACHE, + NULL); } static bool @@ -202,22 +200,12 @@ dispatch(pcmk_ipc_api_t *api, xmlNode *reply) pcmk_controld_reply_unknown, NULL, NULL, }; - /* If we got an ACK, return true so the caller knows to expect more responses - * from the IPC server. We do this before decrementing replies_expected because - * ACKs are not going to be included in that value. - * - * Note that we cannot do the same kind of status checking here that we do in - * ipc_pacemakerd.c. The ACK message we receive does not necessarily contain - * a status attribute. That is, we may receive this: - * - * <ack function="crmd_remote_proxy_cb" line="556"/> - * - * Instead of this: - * - * <ack function="dispatch_controller_ipc" line="391" status="112"/> - */ - if (pcmk__str_eq(crm_element_name(reply), "ack", pcmk__str_none)) { - return true; // More replies needed + if (pcmk__xe_is(reply, "ack")) { + /* ACKs are trivial responses that do not count toward expected replies, + * and do not have all the fields that validation requires, so skip that + * processing. + */ + return private->replies_expected > 0; } if (private->replies_expected > 0) { @@ -341,21 +329,18 @@ create_controller_request(const pcmk_ipc_api_t *api, const char *op, // \return Standard Pacemaker return code static int -send_controller_request(pcmk_ipc_api_t *api, xmlNode *request, +send_controller_request(pcmk_ipc_api_t *api, const xmlNode *request, bool reply_is_expected) { - int rc; - if (crm_element_value(request, XML_ATTR_REFERENCE) == NULL) { return EINVAL; } - rc = pcmk__send_ipc_request(api, request); - if ((rc == pcmk_rc_ok) && reply_is_expected) { + if (reply_is_expected) { struct controld_api_private_s *private = api->api_data; private->replies_expected++; } - return rc; + return pcmk__send_ipc_request(api, request); } static xmlNode * diff --git a/lib/common/ipc_pacemakerd.c b/lib/common/ipc_pacemakerd.c index 91a3143..2f03709 100644 --- a/lib/common/ipc_pacemakerd.c +++ b/lib/common/ipc_pacemakerd.c @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the Pacemaker project contributors + * Copyright 2020-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -178,7 +178,7 @@ post_disconnect(pcmk_ipc_api_t *api) } static bool -reply_expected(pcmk_ipc_api_t *api, xmlNode *request) +reply_expected(pcmk_ipc_api_t *api, const xmlNode *request) { const char *command = crm_element_value(request, F_CRM_TASK); diff --git a/lib/common/ipc_schedulerd.c b/lib/common/ipc_schedulerd.c index c1b81a4..cf788e5 100644 --- a/lib/common/ipc_schedulerd.c +++ b/lib/common/ipc_schedulerd.c @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 the Pacemaker project contributors + * Copyright 2021-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -62,7 +62,7 @@ post_connect(pcmk_ipc_api_t *api) } static bool -reply_expected(pcmk_ipc_api_t *api, xmlNode *request) +reply_expected(pcmk_ipc_api_t *api, const xmlNode *request) { const char *command = crm_element_value(request, F_CRM_TASK); diff --git a/lib/common/ipc_server.c b/lib/common/ipc_server.c index 60f20fb..5cd7e70 100644 --- a/lib/common/ipc_server.c +++ b/lib/common/ipc_server.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2022 the Pacemaker project contributors + * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -421,9 +421,11 @@ pcmk__client_data2xml(pcmk__client_t *c, void *data, uint32_t *id, rc = BZ2_bzBuffToBuffDecompress(uncompressed, &size_u, text, header->size_compressed, 1, 0); text = uncompressed; - if (rc != BZ_OK) { - crm_err("Decompression failed: %s " CRM_XS " bzerror=%d", - bz2_strerror(rc), rc); + rc = pcmk__bzlib2rc(rc); + + if (rc != pcmk_rc_ok) { + crm_err("Decompression failed: %s " CRM_XS " rc=%d", + pcmk_rc_str(rc), rc); free(uncompressed); return NULL; } @@ -568,16 +570,16 @@ crm_ipcs_flush_events(pcmk__client_t *c) * \internal * \brief Create an I/O vector for sending an IPC XML message * - * \param[in] request Identifier for libqb response header - * \param[in,out] message XML message to send - * \param[in] max_send_size If 0, default IPC buffer size is used - * \param[out] result Where to store prepared I/O vector - * \param[out] bytes Size of prepared data in bytes + * \param[in] request Identifier for libqb response header + * \param[in] message XML message to send + * \param[in] max_send_size If 0, default IPC buffer size is used + * \param[out] result Where to store prepared I/O vector + * \param[out] bytes Size of prepared data in bytes * * \return Standard Pacemaker return code */ int -pcmk__ipc_prepare_iov(uint32_t request, xmlNode *message, +pcmk__ipc_prepare_iov(uint32_t request, const xmlNode *message, uint32_t max_send_size, struct iovec **result, ssize_t *bytes) { @@ -741,7 +743,7 @@ pcmk__ipc_send_iov(pcmk__client_t *c, struct iovec *iov, uint32_t flags) } int -pcmk__ipc_send_xml(pcmk__client_t *c, uint32_t request, xmlNode *message, +pcmk__ipc_send_xml(pcmk__client_t *c, uint32_t request, const xmlNode *message, uint32_t flags) { struct iovec *iov = NULL; @@ -819,6 +821,7 @@ pcmk__ipc_send_ack_as(const char *function, int line, pcmk__client_t *c, if (ack != NULL) { crm_trace("Ack'ing IPC message from client %s as <%s status=%d>", pcmk__client_name(c), tag, status); + crm_log_xml_trace(ack, "sent-ack"); c->request_id = 0; rc = pcmk__ipc_send_xml(c, request, ack, flags); free_xml(ack); @@ -995,14 +998,17 @@ pcmk__serve_schedulerd_ipc(struct qb_ipcs_service_handlers *cb) bool crm_is_daemon_name(const char *name) { - name = pcmk__message_name(name); - return (!strcmp(name, CRM_SYSTEM_CRMD) - || !strcmp(name, CRM_SYSTEM_STONITHD) - || !strcmp(name, "stonith-ng") - || !strcmp(name, "attrd") - || !strcmp(name, CRM_SYSTEM_CIB) - || !strcmp(name, CRM_SYSTEM_MCP) - || !strcmp(name, CRM_SYSTEM_DC) - || !strcmp(name, CRM_SYSTEM_TENGINE) - || !strcmp(name, CRM_SYSTEM_LRMD)); + return pcmk__str_any_of(pcmk__message_name(name), + "attrd", + CRM_SYSTEM_CIB, + CRM_SYSTEM_CRMD, + CRM_SYSTEM_DC, + CRM_SYSTEM_LRMD, + CRM_SYSTEM_MCP, + CRM_SYSTEM_PENGINE, + CRM_SYSTEM_STONITHD, + CRM_SYSTEM_TENGINE, + "pacemaker-remoted", + "stonith-ng", + NULL); } diff --git a/lib/common/iso8601.c b/lib/common/iso8601.c index 3e000e1..9de018f 100644 --- a/lib/common/iso8601.c +++ b/lib/common/iso8601.c @@ -1930,9 +1930,10 @@ pcmk__readable_interval(guint interval_ms) #define MS_IN_H (MS_IN_M * 60) #define MS_IN_D (MS_IN_H * 24) #define MAXSTR sizeof("..d..h..m..s...ms") - static char str[MAXSTR] = { '\0', }; + static char str[MAXSTR]; int offset = 0; + str[0] = '\0'; if (interval_ms > MS_IN_D) { offset += snprintf(str + offset, MAXSTR - offset, "%ud", interval_ms / MS_IN_D); diff --git a/lib/common/logging.c b/lib/common/logging.c index dded873..7768c35 100644 --- a/lib/common/logging.c +++ b/lib/common/logging.c @@ -51,6 +51,11 @@ static unsigned int crm_log_priority = LOG_NOTICE; static GLogFunc glib_log_default = NULL; static pcmk__output_t *logger_out = NULL; +pcmk__config_error_func pcmk__config_error_handler = NULL; +pcmk__config_warning_func pcmk__config_warning_handler = NULL; +void *pcmk__config_error_context = NULL; +void *pcmk__config_warning_context = NULL; + static gboolean crm_tracing_enabled(void); static void @@ -237,7 +242,7 @@ chown_logfile(const char *filename, int logfd) static void chmod_logfile(const char *filename, int logfd) { - const char *modestr = getenv("PCMK_logfile_mode"); + const char *modestr = pcmk__env_option(PCMK__ENV_LOGFILE_MODE); mode_t filemode = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP; if (modestr != NULL) { @@ -297,7 +302,7 @@ setenv_logfile(const char *filename) { // Some resource agents will log only if environment variable is set if (pcmk__env_option(PCMK__ENV_LOGFILE) == NULL) { - pcmk__set_env_option(PCMK__ENV_LOGFILE, filename); + pcmk__set_env_option(PCMK__ENV_LOGFILE, filename, true); } } @@ -609,6 +614,20 @@ crm_log_filter_source(int source, const char *trace_files, const char *trace_fns } } +#ifndef HAVE_STRCHRNUL +/* strchrnul() is a GNU extension. If not present, use our own definition. + * The GNU version returns char*, but we only need it to be const char*. + */ +static const char * +strchrnul(const char *s, int c) +{ + while ((*s != c) && (*s != '\0')) { + ++s; + } + return s; +} +#endif + static void crm_log_filter(struct qb_log_callsite *cs) { @@ -622,11 +641,11 @@ crm_log_filter(struct qb_log_callsite *cs) if (need_init) { need_init = 0; - trace_fns = getenv("PCMK_trace_functions"); - trace_fmts = getenv("PCMK_trace_formats"); - trace_tags = getenv("PCMK_trace_tags"); - trace_files = getenv("PCMK_trace_files"); - trace_blackbox = getenv("PCMK_trace_blackbox"); + trace_fns = pcmk__env_option(PCMK__ENV_TRACE_FUNCTIONS); + trace_fmts = pcmk__env_option(PCMK__ENV_TRACE_FORMATS); + trace_tags = pcmk__env_option(PCMK__ENV_TRACE_TAGS); + trace_files = pcmk__env_option(PCMK__ENV_TRACE_FILES); + trace_blackbox = pcmk__env_option(PCMK__ENV_TRACE_BLACKBOX); if (trace_tags != NULL) { uint32_t tag; @@ -695,8 +714,10 @@ crm_update_callsites(void) log = FALSE; crm_debug ("Enabling callsites based on priority=%d, files=%s, functions=%s, formats=%s, tags=%s", - crm_log_level, getenv("PCMK_trace_files"), getenv("PCMK_trace_functions"), - getenv("PCMK_trace_formats"), getenv("PCMK_trace_tags")); + crm_log_level, pcmk__env_option(PCMK__ENV_TRACE_FILES), + pcmk__env_option(PCMK__ENV_TRACE_FUNCTIONS), + pcmk__env_option(PCMK__ENV_TRACE_FORMATS), + pcmk__env_option(PCMK__ENV_TRACE_TAGS)); } qb_log_filter_fn_set(crm_log_filter); } @@ -704,13 +725,11 @@ crm_update_callsites(void) static gboolean crm_tracing_enabled(void) { - if (crm_log_level == LOG_TRACE) { - return TRUE; - } else if (getenv("PCMK_trace_files") || getenv("PCMK_trace_functions") - || getenv("PCMK_trace_formats") || getenv("PCMK_trace_tags")) { - return TRUE; - } - return FALSE; + return (crm_log_level == LOG_TRACE) + || (pcmk__env_option(PCMK__ENV_TRACE_FILES) != NULL) + || (pcmk__env_option(PCMK__ENV_TRACE_FUNCTIONS) != NULL) + || (pcmk__env_option(PCMK__ENV_TRACE_FORMATS) != NULL) + || (pcmk__env_option(PCMK__ENV_TRACE_TAGS) != NULL); } static int @@ -784,7 +803,8 @@ set_identity(const char *entity, int argc, char *const *argv) CRM_ASSERT(crm_system_name != NULL); - setenv("PCMK_service", crm_system_name, 1); + // Used by fencing.py.py (in fence-agents) + pcmk__set_env_option(PCMK__ENV_SERVICE, crm_system_name, false); } void @@ -897,7 +917,7 @@ crm_log_init(const char *entity, uint8_t level, gboolean daemon, gboolean to_std } else { facility = PCMK__VALUE_NONE; } - pcmk__set_env_option(PCMK__ENV_LOGFACILITY, facility); + pcmk__set_env_option(PCMK__ENV_LOGFACILITY, facility, true); } if (pcmk__str_eq(facility, PCMK__VALUE_NONE, pcmk__str_casei)) { @@ -1127,16 +1147,21 @@ pcmk__cli_init_logging(const char *name, unsigned int verbosity) /*! * \brief Log XML line-by-line in a formatted fashion * - * \param[in] level Priority at which to log the messages - * \param[in] text Prefix for each line - * \param[in] xml XML to log + * \param[in] file File name to use for log filtering + * \param[in] function Function name to use for log filtering + * \param[in] line Line number to use for log filtering + * \param[in] tags Logging tags to use for log filtering + * \param[in] level Priority at which to log the messages + * \param[in] text Prefix for each line + * \param[in] xml XML to log * * \note This does nothing when \p level is \p LOG_STDOUT. * \note Do not call this function directly. It should be called only from the * \p do_crm_log_xml() macro. */ void -pcmk_log_xml_impl(uint8_t level, const char *text, const xmlNode *xml) +pcmk_log_xml_as(const char *file, const char *function, uint32_t line, + uint32_t tags, uint8_t level, const char *text, const xmlNode *xml) { if (xml == NULL) { do_crm_log(level, "%s%sNo data to dump as XML", @@ -1148,12 +1173,76 @@ pcmk_log_xml_impl(uint8_t level, const char *text, const xmlNode *xml) } pcmk__output_set_log_level(logger_out, level); + pcmk__output_set_log_filter(logger_out, file, function, line, tags); pcmk__xml_show(logger_out, text, xml, 1, pcmk__xml_fmt_pretty |pcmk__xml_fmt_open |pcmk__xml_fmt_children |pcmk__xml_fmt_close); + pcmk__output_set_log_filter(logger_out, NULL, NULL, 0U, 0U); + } +} + +/*! + * \internal + * \brief Log XML changes line-by-line in a formatted fashion + * + * \param[in] file File name to use for log filtering + * \param[in] function Function name to use for log filtering + * \param[in] line Line number to use for log filtering + * \param[in] tags Logging tags to use for log filtering + * \param[in] level Priority at which to log the messages + * \param[in] xml XML whose changes to log + * + * \note This does nothing when \p level is \c LOG_STDOUT. + */ +void +pcmk__log_xml_changes_as(const char *file, const char *function, uint32_t line, + uint32_t tags, uint8_t level, const xmlNode *xml) +{ + if (xml == NULL) { + do_crm_log(level, "No XML to dump"); + return; + } + + if (logger_out == NULL) { + CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, return); } + pcmk__output_set_log_level(logger_out, level); + pcmk__output_set_log_filter(logger_out, file, function, line, tags); + pcmk__xml_show_changes(logger_out, xml); + pcmk__output_set_log_filter(logger_out, NULL, NULL, 0U, 0U); +} + +/*! + * \internal + * \brief Log an XML patchset line-by-line in a formatted fashion + * + * \param[in] file File name to use for log filtering + * \param[in] function Function name to use for log filtering + * \param[in] line Line number to use for log filtering + * \param[in] tags Logging tags to use for log filtering + * \param[in] level Priority at which to log the messages + * \param[in] patchset XML patchset to log + * + * \note This does nothing when \p level is \c LOG_STDOUT. + */ +void +pcmk__log_xml_patchset_as(const char *file, const char *function, uint32_t line, + uint32_t tags, uint8_t level, const xmlNode *patchset) +{ + if (patchset == NULL) { + do_crm_log(level, "No patchset to dump"); + return; + } + + if (logger_out == NULL) { + CRM_CHECK(pcmk__log_output_new(&logger_out) == pcmk_rc_ok, return); + } + pcmk__output_set_log_level(logger_out, level); + pcmk__output_set_log_filter(logger_out, file, function, line, tags); + logger_out->message(logger_out, "xml-patchset", patchset); + pcmk__output_set_log_filter(logger_out, NULL, NULL, 0U, 0U); } /*! @@ -1188,5 +1277,23 @@ crm_add_logfile(const char *filename) return pcmk__add_logfile(filename) == pcmk_rc_ok; } +void +pcmk_log_xml_impl(uint8_t level, const char *text, const xmlNode *xml) +{ + pcmk_log_xml_as(__FILE__, __func__, __LINE__, 0, level, text, xml); +} + // LCOV_EXCL_STOP // End deprecated API + +void pcmk__set_config_error_handler(pcmk__config_error_func error_handler, void *error_context) +{ + pcmk__config_error_handler = error_handler; + pcmk__config_error_context = error_context; +} + +void pcmk__set_config_warning_handler(pcmk__config_warning_func warning_handler, void *warning_context) +{ + pcmk__config_warning_handler = warning_handler; + pcmk__config_warning_context = warning_context; +}
\ No newline at end of file diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 3124e43..f971713 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -393,16 +393,6 @@ mainloop_add_signal(int sig, void (*dispatch) (int sig)) mainloop_destroy_signal_entry(sig); return FALSE; } -#if 0 - /* If we want signals to interrupt mainloop's poll(), instead of waiting for - * the timeout, then we should call siginterrupt() below - * - * For now, just enforce a low timeout - */ - if (siginterrupt(sig, 1) < 0) { - crm_perror(LOG_INFO, "Could not enable system call interruptions for signal %d", sig); - } -#endif return TRUE; } @@ -624,7 +614,7 @@ struct qb_ipcs_poll_handlers gio_poll_funcs = { static enum qb_ipc_type pick_ipc_type(enum qb_ipc_type requested) { - const char *env = getenv("PCMK_ipc_type"); + const char *env = pcmk__env_option(PCMK__ENV_IPC_TYPE); if (env && strcmp("shared-mem", env) == 0) { return QB_IPC_SHM; @@ -668,7 +658,8 @@ mainloop_add_ipc_server_with_prio(const char *name, enum qb_ipc_type type, server = qb_ipcs_create(name, 0, pick_ipc_type(type), callbacks); if (server == NULL) { - crm_err("Could not create %s IPC server: %s (%d)", name, pcmk_strerror(rc), rc); + crm_err("Could not create %s IPC server: %s (%d)", + name, pcmk_rc_str(errno), errno); return NULL; } @@ -874,21 +865,34 @@ pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata, const struct ipc_client_callbacks *callbacks, mainloop_io_t **source) { + int rc = pcmk_rc_ok; + int fd = -1; + const char *ipc_name = NULL; + CRM_CHECK((ipc != NULL) && (callbacks != NULL), return EINVAL); - if (!crm_ipc_connect(ipc)) { - int rc = errno; - crm_debug("Connection to %s failed: %d", crm_ipc_name(ipc), errno); + ipc_name = pcmk__s(crm_ipc_name(ipc), "Pacemaker"); + rc = pcmk__connect_generic_ipc(ipc); + if (rc != pcmk_rc_ok) { + crm_debug("Connection to %s failed: %s", ipc_name, pcmk_rc_str(rc)); return rc; } - *source = mainloop_add_fd(crm_ipc_name(ipc), priority, crm_ipc_get_fd(ipc), - userdata, NULL); - if (*source == NULL) { - int rc = errno; + rc = pcmk__ipc_fd(ipc, &fd); + if (rc != pcmk_rc_ok) { + crm_debug("Could not obtain file descriptor for %s IPC: %s", + ipc_name, pcmk_rc_str(rc)); crm_ipc_close(ipc); return rc; } + + *source = mainloop_add_fd(ipc_name, priority, fd, userdata, NULL); + if (*source == NULL) { + rc = errno; + crm_ipc_close(ipc); + return rc; + } + (*source)->ipc = ipc; (*source)->destroy_fn = callbacks->destroy; (*source)->dispatch_fn_ipc = callbacks->dispatch; diff --git a/lib/common/mock.c b/lib/common/mock.c index 2bd8334..6f837ad 100644 --- a/lib/common/mock.c +++ b/lib/common/mock.c @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 the Pacemaker project contributors + * Copyright 2021-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -7,6 +7,8 @@ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. */ +#include <crm_internal.h> + #include <errno.h> #include <pwd.h> #include <stdarg.h> @@ -262,6 +264,8 @@ __wrap_endgrent(void) { * will_return(__wrap_fopen, errno_to_set); * * expect_* functions: https://api.cmocka.org/group__cmocka__param.html + * + * This has two mocked functions, since fopen() is sometimes actually fopen64(). */ bool pcmk__mock_fopen = false; @@ -285,6 +289,26 @@ __wrap_fopen(const char *pathname, const char *mode) } } +#ifdef HAVE_FOPEN64 +FILE * +__wrap_fopen64(const char *pathname, const char *mode) +{ + if (pcmk__mock_fopen) { + check_expected_ptr(pathname); + check_expected_ptr(mode); + errno = mock_type(int); + + if (errno != 0) { + return NULL; + } else { + return __real_fopen64(pathname, mode); + } + + } else { + return __real_fopen64(pathname, mode); + } +} +#endif /* getpwnam_r() * diff --git a/lib/common/mock_private.h b/lib/common/mock_private.h index 45207c4..b0e0ed2 100644 --- a/lib/common/mock_private.h +++ b/lib/common/mock_private.h @@ -1,5 +1,5 @@ /* - * Copyright 2021-2022 the Pacemaker project contributors + * Copyright 2021-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -29,6 +29,10 @@ void *__wrap_calloc(size_t nmemb, size_t size); extern bool pcmk__mock_fopen; FILE *__real_fopen(const char *pathname, const char *mode); FILE *__wrap_fopen(const char *pathname, const char *mode); +#ifdef HAVE_FOPEN64 +FILE *__real_fopen64(const char *pathname, const char *mode); +FILE *__wrap_fopen64(const char *pathname, const char *mode); +#endif extern bool pcmk__mock_getenv; char *__real_getenv(const char *name); diff --git a/lib/common/nvpair.c b/lib/common/nvpair.c index 3766c45..dbb9c99 100644 --- a/lib/common/nvpair.c +++ b/lib/common/nvpair.c @@ -334,55 +334,6 @@ crm_xml_add(xmlNode *node, const char *name, const char *value) } /*! - * \brief Replace an XML attribute with specified name and (possibly NULL) value - * - * \param[in,out] node XML node to modify - * \param[in] name Attribute name to set - * \param[in] value Attribute value to set - * - * \return New value on success, \c NULL otherwise - * \note This does nothing if node or name is \c NULL or empty. - */ -const char * -crm_xml_replace(xmlNode *node, const char *name, const char *value) -{ - bool dirty = FALSE; - xmlAttr *attr = NULL; - const char *old_value = NULL; - - CRM_CHECK(node != NULL, return NULL); - CRM_CHECK(name != NULL && name[0] != 0, return NULL); - - old_value = crm_element_value(node, name); - - /* Could be re-setting the same value */ - CRM_CHECK(old_value != value, return value); - - if (pcmk__check_acl(node, name, pcmk__xf_acl_write) == FALSE) { - /* Create a fake object linked to doc->_private instead? */ - crm_trace("Cannot replace %s=%s to %s", name, value, node->name); - return NULL; - - } else if (old_value && !value) { - xml_remove_prop(node, name); - return NULL; - } - - if (pcmk__tracking_xml_changes(node, FALSE)) { - if (!old_value || !value || !strcmp(old_value, value)) { - dirty = TRUE; - } - } - - attr = xmlSetProp(node, (pcmkXmlStr) name, (pcmkXmlStr) value); - if (dirty) { - pcmk__mark_xml_attr_dirty(attr); - } - CRM_CHECK(attr && attr->children && attr->children->content, return NULL); - return (char *) attr->children->content; -} - -/*! * \brief Create an XML attribute with specified name and integer value * * This is like \c crm_xml_add() but taking an integer value. @@ -503,7 +454,7 @@ crm_element_value(const xmlNode *data, const char *name) return NULL; } else if (name == NULL) { - crm_err("Couldn't find NULL in %s", crm_element_name(data)); + crm_err("Couldn't find NULL in %s", data->name); return NULL; } @@ -883,7 +834,7 @@ xml2list(const xmlNode *parent) nvpair_list = find_xml_node(parent, XML_TAG_ATTRS, FALSE); if (nvpair_list == NULL) { - crm_trace("No attributes in %s", crm_element_name(parent)); + crm_trace("No attributes in %s", parent->name); crm_log_xml_trace(parent, "No attributes for resource op"); } @@ -988,5 +939,44 @@ pcmk_format_named_time(const char *name, time_t epoch_time) return result; } +const char * +crm_xml_replace(xmlNode *node, const char *name, const char *value) +{ + bool dirty = FALSE; + xmlAttr *attr = NULL; + const char *old_value = NULL; + + CRM_CHECK(node != NULL, return NULL); + CRM_CHECK(name != NULL && name[0] != 0, return NULL); + + old_value = crm_element_value(node, name); + + /* Could be re-setting the same value */ + CRM_CHECK(old_value != value, return value); + + if (pcmk__check_acl(node, name, pcmk__xf_acl_write) == FALSE) { + /* Create a fake object linked to doc->_private instead? */ + crm_trace("Cannot replace %s=%s to %s", name, value, node->name); + return NULL; + + } else if (old_value && !value) { + xml_remove_prop(node, name); + return NULL; + } + + if (pcmk__tracking_xml_changes(node, FALSE)) { + if (!old_value || !value || !strcmp(old_value, value)) { + dirty = TRUE; + } + } + + attr = xmlSetProp(node, (pcmkXmlStr) name, (pcmkXmlStr) value); + if (dirty) { + pcmk__mark_xml_attr_dirty(attr); + } + CRM_CHECK(attr && attr->children && attr->children->content, return NULL); + return (char *) attr->children->content; +} + // LCOV_EXCL_STOP // End deprecated API diff --git a/lib/common/options.c b/lib/common/options.c index cb32b3f..2d86ebc 100644 --- a/lib/common/options.c +++ b/lib/common/options.c @@ -91,15 +91,23 @@ pcmk__env_option(const char *option) /*! * \brief Set or unset a Pacemaker environment variable option * - * Set an environment variable option with both a PCMK_ and (for - * backward compatibility) HA_ prefix. + * Set an environment variable option with a \c "PCMK_" prefix and optionally + * an \c "HA_" prefix for backward compatibility. * * \param[in] option Environment variable name (without prefix) * \param[in] value New value (or NULL to unset) + * \param[in] compat If false and \p value is not \c NULL, set only + * \c "PCMK_<option>"; otherwise, set (or unset) both + * \c "PCMK_<option>" and \c "HA_<option>" + * + * \note \p compat is ignored when \p value is \c NULL. A \c NULL \p value + * means we're unsetting \p option. \c pcmk__get_env_option() checks for + * both prefixes, so we want to clear them both. */ void -pcmk__set_env_option(const char *option, const char *value) +pcmk__set_env_option(const char *option, const char *value, bool compat) { + // @COMPAT Drop support for "HA_" options eventually const char *const prefixes[] = {"PCMK_", "HA_"}; char env_name[NAME_MAX]; @@ -132,6 +140,11 @@ pcmk__set_env_option(const char *option, const char *value) crm_err("Failed to %sset %s: %s", (value != NULL)? "" : "un", env_name, strerror(errno)); } + + if (!compat && (value != NULL)) { + // For set, don't proceed to HA_<option> unless compat is enabled + break; + } } } diff --git a/lib/common/output_html.c b/lib/common/output_html.c index 47b14c1..92e9010 100644 --- a/lib/common/output_html.c +++ b/lib/common/output_html.c @@ -152,7 +152,7 @@ html_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy * anything else that the user could add, and we want it done last to pick up * any options that may have been given. */ - head_node = xmlNewNode(NULL, (pcmkXmlStr) "head"); + head_node = xmlNewDocRawNode(NULL, NULL, (pcmkXmlStr) "head", NULL); if (title != NULL ) { pcmk_create_xml_text_node(head_node, "title", title); @@ -458,7 +458,7 @@ pcmk__html_add_header(const char *name, ...) { va_start(ap, name); - header_node = xmlNewNode(NULL, (pcmkXmlStr) name); + header_node = xmlNewDocRawNode(NULL, NULL, (pcmkXmlStr) name, NULL); while (1) { char *key = va_arg(ap, char *); char *value; diff --git a/lib/common/output_log.c b/lib/common/output_log.c index aca168d..54fa37e 100644 --- a/lib/common/output_log.c +++ b/lib/common/output_log.c @@ -12,6 +12,7 @@ #include <ctype.h> #include <stdarg.h> +#include <stdint.h> #include <stdlib.h> #include <stdio.h> @@ -23,8 +24,43 @@ typedef struct private_data_s { /* gathered in log_begin_list */ GQueue/*<char*>*/ *prefixes; uint8_t log_level; + const char *function; + const char *file; + uint32_t line; + uint32_t tags; } private_data_t; +/*! + * \internal + * \brief Log a message using output object's log level and filters + * + * \param[in] priv Output object's private_data_t + * \param[in] fmt printf(3)-style format string + * \param[in] args... Format string arguments + */ +#define logger(priv, fmt, args...) do { \ + qb_log_from_external_source(pcmk__s((priv)->function, __func__), \ + pcmk__s((priv)->file, __FILE__), fmt, (priv)->log_level, \ + (((priv)->line == 0)? __LINE__ : (priv)->line), (priv)->tags, \ + ##args); \ + } while (0); + +/*! + * \internal + * \brief Log a message using an explicit log level and output object's filters + * + * \param[in] priv Output object's private_data_t + * \param[in] level Log level + * \param[in] fmt printf(3)-style format string + * \param[in] ap Variadic arguments + */ +#define logger_va(priv, level, fmt, ap) do { \ + qb_log_from_external_source_va(pcmk__s((priv)->function, __func__), \ + pcmk__s((priv)->file, __FILE__), fmt, level, \ + (((priv)->line == 0)? __LINE__ : (priv)->line), (priv)->tags, \ + ap); \ + } while (0); + static void log_subprocess_output(pcmk__output_t *out, int exit_status, const char *proc_stdout, const char *proc_stderr) { @@ -94,35 +130,31 @@ log_version(pcmk__output_t *out, bool extended) { priv = out->priv; if (extended) { - do_crm_log(priv->log_level, "Pacemaker %s (Build: %s): %s", - PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES); + logger(priv, "Pacemaker %s (Build: %s): %s", + PACEMAKER_VERSION, BUILD_VERSION, CRM_FEATURES); } else { - do_crm_log(priv->log_level, "Pacemaker %s", PACEMAKER_VERSION); - do_crm_log(priv->log_level, "Written by Andrew Beekhof and" - "the Pacemaker project contributors"); + logger(priv, "Pacemaker " PACEMAKER_VERSION); + logger(priv, "Written by Andrew Beekhof and " + "the Pacemaker project contributors"); } } G_GNUC_PRINTF(2, 3) static void -log_err(pcmk__output_t *out, const char *format, ...) { +log_err(pcmk__output_t *out, const char *format, ...) +{ va_list ap; - char* buffer = NULL; - int len = 0; + private_data_t *priv = NULL; - CRM_ASSERT(out != NULL); + CRM_ASSERT((out != NULL) && (out->priv != NULL)); + priv = out->priv; - va_start(ap, format); - /* Informational output does not get indented, to separate it from other + /* Error output does not get indented, to separate it from other * potentially indented list output. */ - len = vasprintf(&buffer, format, ap); - CRM_ASSERT(len >= 0); + va_start(ap, format); + logger_va(priv, LOG_ERR, format, ap); va_end(ap); - - crm_err("%s", buffer); - - free(buffer); } static void @@ -195,15 +227,15 @@ log_list_item(pcmk__output_t *out, const char *name, const char *format, ...) { if (strcmp(buffer, "") != 0) { /* We don't want empty messages */ if ((name != NULL) && (strcmp(name, "") != 0)) { if (strcmp(prefix, "") != 0) { - do_crm_log(priv->log_level, "%s: %s: %s", prefix, name, buffer); + logger(priv, "%s: %s: %s", prefix, name, buffer); } else { - do_crm_log(priv->log_level, "%s: %s", name, buffer); + logger(priv, "%s: %s", name, buffer); } } else { if (strcmp(prefix, "") != 0) { - do_crm_log(priv->log_level, "%s: %s", prefix, buffer); + logger(priv, "%s: %s", prefix, buffer); } else { - do_crm_log(priv->log_level, "%s", buffer); + logger(priv, "%s", buffer); } } } @@ -228,23 +260,21 @@ log_end_list(pcmk__output_t *out) { G_GNUC_PRINTF(2, 3) static int -log_info(pcmk__output_t *out, const char *format, ...) { - private_data_t *priv = NULL; - int len = 0; +log_info(pcmk__output_t *out, const char *format, ...) +{ va_list ap; - char* buffer = NULL; + private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; + /* Informational output does not get indented, to separate it from other + * potentially indented list output. + */ va_start(ap, format); - len = vasprintf(&buffer, format, ap); - CRM_ASSERT(len >= 0); + logger_va(priv, priv->log_level, format, ap); va_end(ap); - do_crm_log(priv->log_level, "%s", buffer); - - free(buffer); return pcmk_rc_ok; } @@ -252,22 +282,16 @@ G_GNUC_PRINTF(2, 3) static int log_transient(pcmk__output_t *out, const char *format, ...) { - private_data_t *priv = NULL; - int len = 0; va_list ap; - char *buffer = NULL; + private_data_t *priv = NULL; CRM_ASSERT(out != NULL && out->priv != NULL); priv = out->priv; va_start(ap, format); - len = vasprintf(&buffer, format, ap); - CRM_ASSERT(len >= 0); + logger_va(priv, QB_MAX(priv->log_level, LOG_DEBUG), format, ap); va_end(ap); - do_crm_log(QB_MAX(priv->log_level, LOG_DEBUG), "%s", buffer); - - free(buffer); return pcmk_rc_ok; } @@ -351,3 +375,33 @@ pcmk__output_set_log_level(pcmk__output_t *out, uint8_t log_level) { priv = out->priv; priv->log_level = log_level; } + +/*! + * \internal + * \brief Set the file, function, line, and tags used to filter log output + * + * \param[in,out] out Logger output object + * \param[in] file File name to filter with (or NULL for default) + * \param[in] function Function name to filter with (or NULL for default) + * \param[in] line Line number to filter with (or 0 for default) + * \param[in] tags Tags to filter with (or 0 for none) + * + * \note Custom filters should generally be used only in short areas of a single + * function. When done, callers should call this function again with + * NULL/0 arguments to reset the filters. + */ +void +pcmk__output_set_log_filter(pcmk__output_t *out, const char *file, + const char *function, uint32_t line, uint32_t tags) +{ + private_data_t *priv = NULL; + + CRM_ASSERT((out != NULL) && (out->priv != NULL)); + CRM_CHECK(pcmk__str_eq(out->fmt_name, "log", pcmk__str_none), return); + + priv = out->priv; + priv->file = file; + priv->function = function; + priv->line = line; + priv->tags = tags; +} diff --git a/lib/common/output_xml.c b/lib/common/output_xml.c index 0972638..ba61145 100644 --- a/lib/common/output_xml.c +++ b/lib/common/output_xml.c @@ -1,5 +1,5 @@ /* - * Copyright 2019-2022 the Pacemaker project contributors + * Copyright 2019-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -13,6 +13,10 @@ #include <stdarg.h> #include <stdlib.h> #include <stdio.h> +#include <crm/crm.h> +#include <crm/common/output.h> +#include <crm/common/xml.h> +#include <crm/common/xml_internal.h> /* pcmk__xml2fd */ #include <glib.h> #include <crm/common/cmdline_internal.h> @@ -43,8 +47,8 @@ typedef struct subst_s { static subst_t substitutions[] = { { "Active Resources", "resources" }, - { "Allocation Scores", "allocations" }, - { "Allocation Scores and Utilization Information", "allocations_utilizations" }, + { "Assignment Scores", "allocations" }, + { "Assignment Scores and Utilization Information", "allocations_utilizations" }, { "Cluster Summary", "summary" }, { "Current cluster status", "cluster_status" }, { "Executing Cluster Transition", "transition" }, @@ -190,10 +194,7 @@ xml_finish(pcmk__output_t *out, crm_exit_t exit_status, bool print, void **copy_ } if (print) { - char *buf = dump_xml_formatted_with_text(priv->root); - fprintf(out->dest, "%s", buf); - fflush(out->dest); - free(buf); + pcmk__xml2fd(fileno(out->dest), priv->root); } if (copy_dest != NULL) { @@ -286,7 +287,10 @@ xml_output_xml(pcmk__output_t *out, const char *name, const char *buf) { CRM_ASSERT(out != NULL); parent = pcmk__output_create_xml_node(out, name, NULL); - cdata_node = xmlNewCDataBlock(getDocPtr(parent), (pcmkXmlStr) buf, strlen(buf)); + if (parent == NULL) { + return; + } + cdata_node = xmlNewCDataBlock(parent->doc, (pcmkXmlStr) buf, strlen(buf)); xmlAddChild(parent, cdata_node); } diff --git a/lib/common/patchset.c b/lib/common/patchset.c index 8c1362d..34e27fb 100644 --- a/lib/common/patchset.c +++ b/lib/common/patchset.c @@ -41,6 +41,14 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) xml_node_private_t *nodepriv = xml->_private; const char *value = NULL; + if (nodepriv == NULL) { + /* Elements that shouldn't occur in a CIB don't have _private set. They + * should be stripped out, ignored, or have an error thrown by any code + * that processes their parent, so we ignore any changes to them. + */ + return; + } + // If this XML node is new, just report that if (patchset && pcmk_is_set(nodepriv->flags, pcmk__xf_created)) { GString *xpath = pcmk__element_xpath(xml->parent); @@ -93,7 +101,7 @@ add_xml_changes_to_patchset(xmlNode *xml, xmlNode *patchset) } else { crm_xml_add(attr, XML_DIFF_OP, "set"); - value = crm_element_value(xml, (const char *) pIter->name); + value = pcmk__xml_attr_value(pIter); crm_xml_add(attr, XML_NVPAIR_ATTR_VALUE, value); } } @@ -189,7 +197,7 @@ xml_repair_v1_diff(xmlNode *last, xmlNode *next, xmlNode *local_diff, return; } - tag = "diff-removed"; + tag = XML_TAG_DIFF_REMOVED; diff_child = find_xml_node(local_diff, tag, FALSE); if (diff_child == NULL) { diff_child = create_xml_node(local_diff, tag); @@ -210,7 +218,7 @@ xml_repair_v1_diff(xmlNode *last, xmlNode *next, xmlNode *local_diff, } } - tag = "diff-added"; + tag = XML_TAG_DIFF_ADDED; diff_child = find_xml_node(local_diff, tag, FALSE); if (diff_child == NULL) { diff_child = create_xml_node(local_diff, tag); @@ -229,7 +237,8 @@ xml_repair_v1_diff(xmlNode *last, xmlNode *next, xmlNode *local_diff, } for (xmlAttrPtr a = pcmk__xe_first_attr(next); a != NULL; a = a->next) { - const char *p_value = crm_element_value(next, (const char *) a->name); + + const char *p_value = pcmk__xml_attr_value(a); xmlSetProp(cib, a->name, (pcmkXmlStr) p_value); } @@ -246,7 +255,7 @@ xml_create_patchset_v1(xmlNode *source, xmlNode *target, bool config, if (patchset) { CRM_LOG_ASSERT(xml_document_dirty(target)); xml_repair_v1_diff(source, target, patchset, config); - crm_xml_add(patchset, "format", "1"); + crm_xml_add(patchset, PCMK_XA_FORMAT, "1"); } return patchset; } @@ -276,7 +285,7 @@ xml_create_patchset_v2(xmlNode *source, xmlNode *target) docpriv = target->doc->_private; patchset = create_xml_node(NULL, XML_TAG_DIFF); - crm_xml_add_int(patchset, "format", 2); + crm_xml_add_int(patchset, PCMK_XA_FORMAT, 2); version = create_xml_node(patchset, XML_DIFF_VERSION); @@ -389,7 +398,7 @@ patchset_process_digest(xmlNode *patch, xmlNode *source, xmlNode *target, */ CRM_LOG_ASSERT(!xml_document_dirty(target)); - crm_element_value_int(patch, "format", &format); + crm_element_value_int(patch, PCMK_XA_FORMAT, &format); if ((format > 1) && !with_digest) { return; } @@ -418,7 +427,6 @@ process_v1_removals(xmlNode *target, xmlNode *patch) xmlNode *cIter = NULL; char *id = NULL; - const char *name = NULL; const char *value = NULL; if ((target == NULL) || (patch == NULL)) { @@ -431,18 +439,15 @@ process_v1_removals(xmlNode *target, xmlNode *patch) subtract_xml_comment(target->parent, target, patch, &dummy); } - name = crm_element_name(target); - CRM_CHECK(name != NULL, return); - CRM_CHECK(pcmk__str_eq(crm_element_name(target), crm_element_name(patch), - pcmk__str_casei), - return); + CRM_CHECK(pcmk__xe_is(target, (const char *) patch->name), return); CRM_CHECK(pcmk__str_eq(ID(target), ID(patch), pcmk__str_casei), return); // Check for XML_DIFF_MARKER in a child id = crm_element_value_copy(target, XML_ATTR_ID); value = crm_element_value(patch, XML_DIFF_MARKER); if ((value != NULL) && (strcmp(value, "removed:top") == 0)) { - crm_trace("We are the root of the deletion: %s.id=%s", name, id); + crm_trace("We are the root of the deletion: %s.id=%s", + target->name, id); free_xml(target); free(id); return; @@ -482,18 +487,17 @@ process_v1_additions(xmlNode *parent, xmlNode *target, xmlNode *patch) } // Check for XML_DIFF_MARKER in a child + name = (const char *) patch->name; value = crm_element_value(patch, XML_DIFF_MARKER); if ((target == NULL) && (value != NULL) && (strcmp(value, "added:top") == 0)) { id = ID(patch); - name = crm_element_name(patch); crm_trace("We are the root of the addition: %s.id=%s", name, id); add_node_copy(parent, patch); return; } else if (target == NULL) { id = ID(patch); - name = crm_element_name(patch); crm_err("Could not locate: %s.id=%s", name, id); return; } @@ -502,17 +506,13 @@ process_v1_additions(xmlNode *parent, xmlNode *target, xmlNode *patch) pcmk__xc_update(parent, target, patch); } - name = crm_element_name(target); - CRM_CHECK(name != NULL, return); - CRM_CHECK(pcmk__str_eq(crm_element_name(target), crm_element_name(patch), - pcmk__str_casei), - return); + CRM_CHECK(pcmk__xe_is(target, name), return); CRM_CHECK(pcmk__str_eq(ID(target), ID(patch), pcmk__str_casei), return); for (xIter = pcmk__xe_first_attr(patch); xIter != NULL; xIter = xIter->next) { const char *p_name = (const char *) xIter->name; - const char *p_value = crm_element_value(patch, p_name); + const char *p_value = pcmk__xml_attr_value(xIter); xml_remove_prop(target, p_name); // Preserve patch order crm_xml_add(target, p_name, p_value); @@ -547,7 +547,7 @@ find_patch_xml_node(const xmlNode *patchset, int format, bool added, switch (format) { case 1: - label = added? "diff-added" : "diff-removed"; + label = added? XML_TAG_DIFF_ADDED : XML_TAG_DIFF_REMOVED; *patch_node = find_xml_node(patchset, label, FALSE); cib_node = find_xml_node(*patch_node, "cib", FALSE); if (cib_node != NULL) { @@ -582,7 +582,7 @@ xml_patch_versions(const xmlNode *patchset, int add[3], int del[3]) }; - crm_element_value_int(patchset, "format", &format); + crm_element_value_int(patchset, PCMK_XA_FORMAT, &format); /* Process removals */ if (!find_patch_xml_node(patchset, format, FALSE, &tmp)) { @@ -614,12 +614,11 @@ xml_patch_versions(const xmlNode *patchset, int add[3], int del[3]) * * \param[in] xml Root of current CIB * \param[in] patchset Patchset to check - * \param[in] format Patchset version * * \return Standard Pacemaker return code */ static int -xml_patch_version_check(const xmlNode *xml, const xmlNode *patchset, int format) +xml_patch_version_check(const xmlNode *xml, const xmlNode *patchset) { int lpc = 0; bool changed = FALSE; @@ -701,8 +700,8 @@ apply_v1_patchset(xmlNode *xml, const xmlNode *patchset) int root_nodes_seen = 0; xmlNode *child_diff = NULL; - xmlNode *added = find_xml_node(patchset, "diff-added", FALSE); - xmlNode *removed = find_xml_node(patchset, "diff-removed", FALSE); + xmlNode *added = find_xml_node(patchset, XML_TAG_DIFF_ADDED, FALSE); + xmlNode *removed = find_xml_node(patchset, XML_TAG_DIFF_REMOVED, FALSE); xmlNode *old = copy_xml(xml); crm_trace("Subtraction Phase"); @@ -981,7 +980,7 @@ apply_v2_patchset(xmlNode *xml, const xmlNode *patchset) for (xmlAttrPtr pIter = pcmk__xe_first_attr(attrs); pIter != NULL; pIter = pIter->next) { const char *name = (const char *) pIter->name; - const char *value = crm_element_value(attrs, name); + const char *value = pcmk__xml_attr_value(pIter); crm_xml_add(match, name, value); } @@ -1022,6 +1021,10 @@ apply_v2_patchset(xmlNode *xml, const xmlNode *patchset) } child = xmlDocCopyNode(change->children, match->doc, 1); + if (child == NULL) { + return ENOMEM; + } + if (match_child) { crm_trace("Adding %s at position %d", child->name, position); xmlAddPrevSibling(match_child, child); @@ -1098,43 +1101,31 @@ xml_apply_patchset(xmlNode *xml, xmlNode *patchset, bool check_version) int format = 1; int rc = pcmk_ok; xmlNode *old = NULL; - const char *digest = crm_element_value(patchset, XML_ATTR_DIGEST); + const char *digest = NULL; if (patchset == NULL) { return rc; } - pcmk__if_tracing( - { - pcmk__output_t *logger_out = NULL; - - rc = pcmk_rc2legacy(pcmk__log_output_new(&logger_out)); - CRM_CHECK(rc == pcmk_ok, return rc); + pcmk__log_xml_patchset(LOG_TRACE, patchset); - pcmk__output_set_log_level(logger_out, LOG_TRACE); - rc = logger_out->message(logger_out, "xml-patchset", patchset); - logger_out->finish(logger_out, pcmk_rc2exitc(rc), true, - NULL); - pcmk__output_free(logger_out); - rc = pcmk_ok; - }, - {} - ); - - crm_element_value_int(patchset, "format", &format); if (check_version) { - rc = pcmk_rc2legacy(xml_patch_version_check(xml, patchset, format)); + rc = pcmk_rc2legacy(xml_patch_version_check(xml, patchset)); if (rc != pcmk_ok) { return rc; } } - if (digest) { - // Make it available for logging if result doesn't have expected digest - old = copy_xml(xml); + digest = crm_element_value(patchset, XML_ATTR_DIGEST); + if (digest != NULL) { + /* Make original XML available for logging in case result doesn't have + * expected digest + */ + pcmk__if_tracing(old = copy_xml(xml), {}); } if (rc == pcmk_ok) { + crm_element_value_int(patchset, PCMK_XA_FORMAT, &format); switch (format) { case 1: rc = pcmk_rc2legacy(apply_v1_patchset(xml, patchset)); @@ -1195,9 +1186,9 @@ xmlNode * diff_xml_object(xmlNode *old, xmlNode *new, gboolean suppress) { xmlNode *tmp1 = NULL; - xmlNode *diff = create_xml_node(NULL, "diff"); - xmlNode *removed = create_xml_node(diff, "diff-removed"); - xmlNode *added = create_xml_node(diff, "diff-added"); + xmlNode *diff = create_xml_node(NULL, XML_TAG_DIFF); + xmlNode *removed = create_xml_node(diff, XML_TAG_DIFF_REMOVED); + xmlNode *added = create_xml_node(diff, XML_TAG_DIFF_ADDED); crm_xml_add(diff, XML_ATTR_CRM_VERSION, CRM_FEATURE_SET); @@ -1268,11 +1259,12 @@ subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right, } id = ID(left); + name = (const char *) left->name; if (right == NULL) { xmlNode *deleted = NULL; crm_trace("Processing <%s " XML_ATTR_ID "=%s> (complete copy)", - crm_element_name(left), id); + name, id); deleted = add_node_copy(parent, left); crm_xml_add(deleted, XML_DIFF_MARKER, marker); @@ -1280,11 +1272,8 @@ subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right, return deleted; } - name = crm_element_name(left); CRM_CHECK(name != NULL, return NULL); - CRM_CHECK(pcmk__str_eq(crm_element_name(left), crm_element_name(right), - pcmk__str_casei), - return NULL); + CRM_CHECK(pcmk__xe_is(left, (const char *) right->name), return NULL); // Check for XML_DIFF_MARKER in a child value = crm_element_value(right, XML_DIFF_MARKER); @@ -1367,7 +1356,7 @@ subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right, break; } else { - const char *left_value = crm_element_value(left, prop_name); + const char *left_value = pcmk__xml_attr_value(xIter); xmlSetProp(diff, (pcmkXmlStr) prop_name, (pcmkXmlStr) value); crm_xml_add(diff, prop_name, left_value); @@ -1375,7 +1364,7 @@ subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right, } else { /* Only now do we need the left value */ - const char *left_value = crm_element_value(left, prop_name); + const char *left_value = pcmk__xml_attr_value(xIter); if (strcmp(left_value, right_val) == 0) { /* unchanged */ @@ -1386,8 +1375,7 @@ subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right, xmlAttrPtr pIter = NULL; crm_trace("Changes detected to %s in " - "<%s " XML_ATTR_ID "=%s>", - prop_name, crm_element_name(left), id); + "<%s " XML_ATTR_ID "=%s>", prop_name, name, id); for (pIter = pcmk__xe_first_attr(left); pIter != NULL; pIter = pIter->next) { const char *p_name = (const char *) pIter->name; @@ -1401,8 +1389,7 @@ subtract_xml_object(xmlNode *parent, xmlNode *left, xmlNode *right, } else { crm_trace("Changes detected to %s (%s -> %s) in " "<%s " XML_ATTR_ID "=%s>", - prop_name, left_value, right_val, - crm_element_name(left), id); + prop_name, left_value, right_val, name, id); crm_xml_add(diff, prop_name, left_value); } } @@ -1434,8 +1421,8 @@ apply_xml_diff(xmlNode *old_xml, xmlNode *diff, xmlNode **new_xml) const char *version = crm_element_value(diff, XML_ATTR_CRM_VERSION); xmlNode *child_diff = NULL; - xmlNode *added = find_xml_node(diff, "diff-added", FALSE); - xmlNode *removed = find_xml_node(diff, "diff-removed", FALSE); + xmlNode *added = find_xml_node(diff, XML_TAG_DIFF_ADDED, FALSE); + xmlNode *removed = find_xml_node(diff, XML_TAG_DIFF_REMOVED, FALSE); CRM_CHECK(new_xml != NULL, return FALSE); diff --git a/lib/common/patchset_display.c b/lib/common/patchset_display.c index 731d437..5cc0b52 100644 --- a/lib/common/patchset_display.c +++ b/lib/common/patchset_display.c @@ -47,7 +47,7 @@ xml_show_patchset_header(pcmk__output_t *out, const xmlNode *patchset) xml_patch_versions(patchset, add, del); if ((add[0] != del[0]) || (add[1] != del[1]) || (add[2] != del[2])) { - const char *fmt = crm_element_value(patchset, "format"); + const char *fmt = crm_element_value(patchset, PCMK_XA_FORMAT); const char *digest = crm_element_value(patchset, XML_ATTR_DIGEST); out->info(out, "Diff: --- %d.%d.%d %s", del[0], del[1], del[2], fmt); @@ -80,7 +80,7 @@ static int xml_show_patchset_v1_recursive(pcmk__output_t *out, const char *prefix, const xmlNode *data, int depth, uint32_t options) { - if (!xml_has_children(data) + if ((data->children == NULL) || (crm_element_value(data, XML_DIFF_MARKER) != NULL)) { // Found a change; clear the pcmk__xml_fmt_diff_short option if set @@ -143,7 +143,7 @@ xml_show_patchset_v1(pcmk__output_t *out, const xmlNode *patchset, * However, v1 patchsets can only exist during rolling upgrades from * Pacemaker 1.1.11, so not worth worrying about. */ - removed = find_xml_node(patchset, "diff-removed", FALSE); + removed = find_xml_node(patchset, XML_TAG_DIFF_REMOVED, FALSE); for (child = pcmk__xml_first_child(removed); child != NULL; child = pcmk__xml_next(child)) { int temp_rc = xml_show_patchset_v1_recursive(out, "- ", child, 0, @@ -159,7 +159,7 @@ xml_show_patchset_v1(pcmk__output_t *out, const xmlNode *patchset, } is_first = true; - added = find_xml_node(patchset, "diff-added", FALSE); + added = find_xml_node(patchset, XML_TAG_DIFF_ADDED, FALSE); for (child = pcmk__xml_first_child(added); child != NULL; child = pcmk__xml_next(child)) { int temp_rc = xml_show_patchset_v1_recursive(out, "+ ", child, 0, @@ -303,11 +303,11 @@ xml_show_patchset_v2(pcmk__output_t *out, const xmlNode *patchset) * * \note \p args should contain only the XML patchset */ -PCMK__OUTPUT_ARGS("xml-patchset", "xmlNodePtr") +PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *") static int xml_patchset_default(pcmk__output_t *out, va_list args) { - xmlNodePtr patchset = va_arg(args, xmlNodePtr); + const xmlNode *patchset = va_arg(args, const xmlNode *); int format = 1; @@ -316,7 +316,7 @@ xml_patchset_default(pcmk__output_t *out, va_list args) return pcmk_rc_no_output; } - crm_element_value_int(patchset, "format", &format); + crm_element_value_int(patchset, PCMK_XA_FORMAT, &format); switch (format) { case 1: return xml_show_patchset_v1(out, patchset, pcmk__xml_fmt_pretty); @@ -342,13 +342,13 @@ xml_patchset_default(pcmk__output_t *out, va_list args) * * \note \p args should contain only the XML patchset */ -PCMK__OUTPUT_ARGS("xml-patchset", "xmlNodePtr") +PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *") static int xml_patchset_log(pcmk__output_t *out, va_list args) { static struct qb_log_callsite *patchset_cs = NULL; - xmlNodePtr patchset = va_arg(args, xmlNodePtr); + const xmlNode *patchset = va_arg(args, const xmlNode *); uint8_t log_level = pcmk__output_get_log_level(out); int format = 1; @@ -373,7 +373,7 @@ xml_patchset_log(pcmk__output_t *out, va_list args) return pcmk_rc_no_output; } - crm_element_value_int(patchset, "format", &format); + crm_element_value_int(patchset, PCMK_XA_FORMAT, &format); switch (format) { case 1: if (log_level < LOG_DEBUG) { @@ -404,11 +404,11 @@ xml_patchset_log(pcmk__output_t *out, va_list args) * * \note \p args should contain only the XML patchset */ -PCMK__OUTPUT_ARGS("xml-patchset", "xmlNodePtr") +PCMK__OUTPUT_ARGS("xml-patchset", "const xmlNode *") static int xml_patchset_xml(pcmk__output_t *out, va_list args) { - xmlNodePtr patchset = va_arg(args, xmlNodePtr); + const xmlNode *patchset = va_arg(args, const xmlNode *); if (patchset != NULL) { char *buf = dump_xml_formatted_with_text(patchset); @@ -490,7 +490,7 @@ xml_log_patchset(uint8_t log_level, const char *function, goto done; } - crm_element_value_int(patchset, "format", &format); + crm_element_value_int(patchset, PCMK_XA_FORMAT, &format); switch (format) { case 1: if (log_level < LOG_DEBUG) { diff --git a/lib/common/remote.c b/lib/common/remote.c index 8c5969a..fe19296 100644 --- a/lib/common/remote.c +++ b/lib/common/remote.c @@ -1,5 +1,5 @@ /* - * Copyright 2008-2022 the Pacemaker project contributors + * Copyright 2008-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -167,7 +167,8 @@ set_minimum_dh_bits(const gnutls_session_t *session) { int dh_min_bits; - pcmk__scan_min_int(getenv("PCMK_dh_min_bits"), &dh_min_bits, 0); + pcmk__scan_min_int(pcmk__env_option(PCMK__ENV_DH_MIN_BITS), &dh_min_bits, + 0); /* This function is deprecated since GnuTLS 3.1.7, in favor of letting * the priority string imply the DH requirements, but this is the only @@ -186,8 +187,11 @@ get_bound_dh_bits(unsigned int dh_bits) int dh_min_bits; int dh_max_bits; - pcmk__scan_min_int(getenv("PCMK_dh_min_bits"), &dh_min_bits, 0); - pcmk__scan_min_int(getenv("PCMK_dh_max_bits"), &dh_max_bits, 0); + pcmk__scan_min_int(pcmk__env_option(PCMK__ENV_DH_MIN_BITS), &dh_min_bits, + 0); + pcmk__scan_min_int(pcmk__env_option(PCMK__ENV_DH_MAX_BITS), &dh_max_bits, + 0); + if ((dh_max_bits > 0) && (dh_max_bits < dh_min_bits)) { crm_warn("Ignoring PCMK_dh_max_bits less than PCMK_dh_min_bits"); dh_max_bits = 0; @@ -228,7 +232,7 @@ pcmk__new_tls_session(int csock, unsigned int conn_type, * http://www.manpagez.com/info/gnutls/gnutls-2.10.4/gnutls_81.php#Echo-Server-with-anonymous-authentication */ - prio_base = getenv("PCMK_tls_priorities"); + prio_base = pcmk__env_option(PCMK__ENV_TLS_PRIORITIES); if (prio_base == NULL) { prio_base = PCMK_GNUTLS_PRIORITIES; } @@ -485,7 +489,7 @@ remote_send_iovs(pcmk__remote_t *remote, struct iovec *iov, int iovs) * \return Standard Pacemaker return code */ int -pcmk__remote_send_xml(pcmk__remote_t *remote, xmlNode *msg) +pcmk__remote_send_xml(pcmk__remote_t *remote, const xmlNode *msg) { int rc = pcmk_rc_ok; static uint64_t id = 0; @@ -558,16 +562,17 @@ pcmk__remote_message_xml(pcmk__remote_t *remote) rc = BZ2_bzBuffToBuffDecompress(uncompressed + header->payload_offset, &size_u, remote->buffer + header->payload_offset, header->payload_compressed, 1, 0); + rc = pcmk__bzlib2rc(rc); - if (rc != BZ_OK && header->version > REMOTE_MSG_VERSION) { + if (rc != pcmk_rc_ok && header->version > REMOTE_MSG_VERSION) { crm_warn("Couldn't decompress v%d message, we only understand v%d", header->version, REMOTE_MSG_VERSION); free(uncompressed); return NULL; - } else if (rc != BZ_OK) { - crm_err("Decompression failed: %s " CRM_XS " bzerror=%d", - bz2_strerror(rc), rc); + } else if (rc != pcmk_rc_ok) { + crm_err("Decompression failed: %s " CRM_XS " rc=%d", + pcmk_rc_str(rc), rc); free(uncompressed); return NULL; } @@ -1079,13 +1084,16 @@ pcmk__connect_remote(const char *host, int port, int timeout, int *timer_id, hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ hints.ai_socktype = SOCK_STREAM; hints.ai_flags = AI_CANONNAME; + rc = getaddrinfo(server, NULL, &hints, &res); - if (rc != 0) { + rc = pcmk__gaierror2rc(rc); + + if (rc != pcmk_rc_ok) { crm_err("Unable to get IP address info for %s: %s", - server, gai_strerror(rc)); - rc = ENOTCONN; + server, pcmk_rc_str(rc)); goto async_cleanup; } + if (!res || !res->ai_addr) { crm_err("Unable to get IP address info for %s: no result", server); rc = ENOTCONN; @@ -1252,13 +1260,14 @@ crm_default_remote_port(void) static int port = 0; if (port == 0) { - const char *env = getenv("PCMK_remote_port"); + const char *env = pcmk__env_option(PCMK__ENV_REMOTE_PORT); if (env) { errno = 0; port = strtol(env, NULL, 10); if (errno || (port < 1) || (port > 65535)) { - crm_warn("Environment variable PCMK_remote_port has invalid value '%s', using %d instead", + crm_warn("Environment variable PCMK_" PCMK__ENV_REMOTE_PORT + " has invalid value '%s', using %d instead", env, DEFAULT_REMOTE_PORT); port = DEFAULT_REMOTE_PORT; } diff --git a/lib/common/results.c b/lib/common/results.c index 93d79eb..dde8b27 100644 --- a/lib/common/results.c +++ b/lib/common/results.c @@ -15,6 +15,7 @@ #include <bzlib.h> #include <errno.h> +#include <netdb.h> #include <stdlib.h> #include <string.h> #include <qb/qbdefs.h> @@ -305,6 +306,18 @@ static const struct pcmk__rc_info { "Bad XML patch format", -pcmk_err_generic, }, + { "pcmk_rc_no_transaction", + "No active transaction found", + -pcmk_err_generic, + }, + { "pcmk_rc_ns_resolution", + "Nameserver resolution error", + -pcmk_err_generic, + }, + { "pcmk_rc_compression", + "Compression/decompression error", + -pcmk_err_generic, + }, }; /*! @@ -716,6 +729,7 @@ pcmk_rc2exitc(int rc) case ENOSYS: case EOVERFLOW: case pcmk_rc_underflow: + case pcmk_rc_compression: return CRM_EX_SOFTWARE; case EBADMSG: @@ -759,10 +773,12 @@ pcmk_rc2exitc(int rc) case ENODEV: case ENOENT: case ENXIO: + case pcmk_rc_no_transaction: case pcmk_rc_unknown_format: return CRM_EX_NOSUCH; case pcmk_rc_node_unknown: + case pcmk_rc_ns_resolution: return CRM_EX_NOHOST; case ETIME: @@ -837,37 +853,83 @@ pcmk_rc2ocf(int rc) // Other functions -const char * -bz2_strerror(int rc) +/*! + * \brief Map a getaddrinfo() return code to the most similar Pacemaker + * return code + * + * \param[in] gai getaddrinfo() return code + * + * \return Most similar Pacemaker return code + */ +int +pcmk__gaierror2rc(int gai) { - // See ftp://sources.redhat.com/pub/bzip2/docs/manual_3.html#SEC17 - switch (rc) { + switch (gai) { + case 0: + return pcmk_rc_ok; + + case EAI_AGAIN: + return EAGAIN; + + case EAI_BADFLAGS: + case EAI_SERVICE: + return EINVAL; + + case EAI_FAMILY: + return EAFNOSUPPORT; + + case EAI_MEMORY: + return ENOMEM; + + case EAI_NONAME: + return pcmk_rc_node_unknown; + + case EAI_SOCKTYPE: + return ESOCKTNOSUPPORT; + + case EAI_SYSTEM: + return errno; + + default: + return pcmk_rc_ns_resolution; + } +} + +/*! + * \brief Map a bz2 return code to the most similar Pacemaker return code + * + * \param[in] bz2 bz2 return code + * + * \return Most similar Pacemaker return code + */ +int +pcmk__bzlib2rc(int bz2) +{ + switch (bz2) { case BZ_OK: case BZ_RUN_OK: case BZ_FLUSH_OK: case BZ_FINISH_OK: case BZ_STREAM_END: - return "Ok"; - case BZ_CONFIG_ERROR: - return "libbz2 has been improperly compiled on your platform"; - case BZ_SEQUENCE_ERROR: - return "library functions called in the wrong order"; - case BZ_PARAM_ERROR: - return "parameter is out of range or otherwise incorrect"; + return pcmk_rc_ok; + case BZ_MEM_ERROR: - return "memory allocation failed"; + return ENOMEM; + case BZ_DATA_ERROR: - return "data integrity error is detected during decompression"; case BZ_DATA_ERROR_MAGIC: - return "the compressed stream does not start with the correct magic bytes"; - case BZ_IO_ERROR: - return "error reading or writing in the compressed file"; case BZ_UNEXPECTED_EOF: - return "compressed file finishes before the logical end of stream is detected"; + return pcmk_rc_bad_input; + + case BZ_IO_ERROR: + return EIO; + case BZ_OUTBUFF_FULL: - return "output data will not fit into the buffer provided"; + return EFBIG; + + default: + return pcmk_rc_compression; } - return "Data compression error"; } crm_exit_t @@ -1039,6 +1101,39 @@ pcmk__copy_result(const pcmk__action_result_t *src, pcmk__action_result_t *dst) #include <crm/common/results_compat.h> +const char * +bz2_strerror(int rc) +{ + // See ftp://sources.redhat.com/pub/bzip2/docs/manual_3.html#SEC17 + switch (rc) { + case BZ_OK: + case BZ_RUN_OK: + case BZ_FLUSH_OK: + case BZ_FINISH_OK: + case BZ_STREAM_END: + return "Ok"; + case BZ_CONFIG_ERROR: + return "libbz2 has been improperly compiled on your platform"; + case BZ_SEQUENCE_ERROR: + return "library functions called in the wrong order"; + case BZ_PARAM_ERROR: + return "parameter is out of range or otherwise incorrect"; + case BZ_MEM_ERROR: + return "memory allocation failed"; + case BZ_DATA_ERROR: + return "data integrity error is detected during decompression"; + case BZ_DATA_ERROR_MAGIC: + return "the compressed stream does not start with the correct magic bytes"; + case BZ_IO_ERROR: + return "error reading or writing in the compressed file"; + case BZ_UNEXPECTED_EOF: + return "compressed file finishes before the logical end of stream is detected"; + case BZ_OUTBUFF_FULL: + return "output data will not fit into the buffer provided"; + } + return "Data compression error"; +} + crm_exit_t crm_errno2exit(int rc) { diff --git a/lib/common/scheduler.c b/lib/common/scheduler.c new file mode 100644 index 0000000..20e6fdf --- /dev/null +++ b/lib/common/scheduler.c @@ -0,0 +1,14 @@ +/* + * Copyright 2004-2023 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 <stdint.h> // uint32_t + +uint32_t pcmk__warnings = 0; diff --git a/lib/common/schemas.c b/lib/common/schemas.c index 88a3051..b3c09eb 100644 --- a/lib/common/schemas.c +++ b/lib/common/schemas.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2022 the Pacemaker project contributors + * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -432,34 +432,8 @@ crm_schema_init(void) NULL, NULL, FALSE, -1); } -#if 0 -static void -relaxng_invalid_stderr(void *userData, xmlErrorPtr error) -{ - /* - Structure xmlError - struct _xmlError { - int domain : What part of the library raised this er - int code : The error code, e.g. an xmlParserError - char * message : human-readable informative error messag - xmlErrorLevel level : how consequent is the error - char * file : the filename - int line : the line number if available - char * str1 : extra string information - char * str2 : extra string information - char * str3 : extra string information - int int1 : extra number information - int int2 : column number of the error or 0 if N/A - void * ctxt : the parser context if available - void * node : the node in the tree - } - */ - crm_err("Structured error: line=%d, level=%d %s", error->line, error->level, error->message); -} -#endif - static gboolean -validate_with_relaxng(xmlDocPtr doc, gboolean to_logs, const char *relaxng_file, +validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler, void *error_handler_context, const char *relaxng_file, relaxng_ctx_cache_t **cached_ctx) { int rc = 0; @@ -476,15 +450,14 @@ validate_with_relaxng(xmlDocPtr doc, gboolean to_logs, const char *relaxng_file, crm_debug("Creating RNG parser context"); ctx = calloc(1, sizeof(relaxng_ctx_cache_t)); - xmlLoadExtDtdDefaultValue = 1; ctx->parser = xmlRelaxNGNewParserCtxt(relaxng_file); CRM_CHECK(ctx->parser != NULL, goto cleanup); - if (to_logs) { + if (error_handler) { xmlRelaxNGSetParserErrors(ctx->parser, - (xmlRelaxNGValidityErrorFunc) xml_log, - (xmlRelaxNGValidityWarningFunc) xml_log, - GUINT_TO_POINTER(LOG_ERR)); + (xmlRelaxNGValidityErrorFunc) error_handler, + (xmlRelaxNGValidityWarningFunc) error_handler, + error_handler_context); } else { xmlRelaxNGSetParserErrors(ctx->parser, (xmlRelaxNGValidityErrorFunc) fprintf, @@ -500,11 +473,11 @@ validate_with_relaxng(xmlDocPtr doc, gboolean to_logs, const char *relaxng_file, ctx->valid = xmlRelaxNGNewValidCtxt(ctx->rng); CRM_CHECK(ctx->valid != NULL, goto cleanup); - if (to_logs) { + if (error_handler) { xmlRelaxNGSetValidErrors(ctx->valid, - (xmlRelaxNGValidityErrorFunc) xml_log, - (xmlRelaxNGValidityWarningFunc) xml_log, - GUINT_TO_POINTER(LOG_ERR)); + (xmlRelaxNGValidityErrorFunc) error_handler, + (xmlRelaxNGValidityWarningFunc) error_handler, + error_handler_context); } else { xmlRelaxNGSetValidErrors(ctx->valid, (xmlRelaxNGValidityErrorFunc) fprintf, @@ -513,10 +486,6 @@ validate_with_relaxng(xmlDocPtr doc, gboolean to_logs, const char *relaxng_file, } } - /* xmlRelaxNGSetValidStructuredErrors( */ - /* valid, relaxng_invalid_stderr, valid); */ - - xmlLineNumbersDefault(1); rc = xmlRelaxNGValidateDoc(ctx->valid, doc); if (rc > 0) { valid = FALSE; @@ -590,39 +559,36 @@ crm_schema_cleanup(void) } static gboolean -validate_with(xmlNode *xml, int method, gboolean to_logs) +validate_with(xmlNode *xml, int method, xmlRelaxNGValidityErrorFunc error_handler, void* error_handler_context) { - xmlDocPtr doc = NULL; gboolean valid = FALSE; char *file = NULL; + struct schema_s *schema = NULL; + relaxng_ctx_cache_t **cache = NULL; if (method < 0) { return FALSE; } - if (known_schemas[method].validator == schema_validator_none) { + schema = &(known_schemas[method]); + if (schema->validator == schema_validator_none) { return TRUE; } - CRM_CHECK(xml != NULL, return FALSE); - - if (pcmk__str_eq(known_schemas[method].name, "pacemaker-next", - pcmk__str_none)) { + if (pcmk__str_eq(schema->name, "pacemaker-next", pcmk__str_none)) { crm_warn("The pacemaker-next schema is deprecated and will be removed " "in a future release."); } - doc = getDocPtr(xml); file = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng, - known_schemas[method].name); + schema->name); crm_trace("Validating with %s (type=%d)", - pcmk__s(file, "missing schema"), known_schemas[method].validator); - switch (known_schemas[method].validator) { + pcmk__s(file, "missing schema"), schema->validator); + switch (schema->validator) { case schema_validator_rng: - valid = - validate_with_relaxng(doc, to_logs, file, - (relaxng_ctx_cache_t **) & (known_schemas[method].cache)); + cache = (relaxng_ctx_cache_t **) &(schema->cache); + valid = validate_with_relaxng(xml->doc, error_handler, error_handler_context, file, cache); break; default: crm_err("Unknown validator type: %d", @@ -639,7 +605,7 @@ validate_with_silent(xmlNode *xml, int method) { bool rc, sl_backup = silent_logging; silent_logging = TRUE; - rc = validate_with(xml, method, TRUE); + rc = validate_with(xml, method, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR)); silent_logging = sl_backup; return rc; } @@ -676,7 +642,7 @@ dump_file(const char *filename) } gboolean -validate_xml_verbose(xmlNode *xml_blob) +validate_xml_verbose(const xmlNode *xml_blob) { int fd = 0; xmlDoc *doc = NULL; @@ -692,7 +658,7 @@ validate_xml_verbose(xmlNode *xml_blob) dump_file(filename); - doc = xmlParseFile(filename); + doc = xmlReadFile(filename, NULL, 0); xml = xmlDocGetRootElement(doc); rc = validate_xml(xml, NULL, FALSE); free_xml(xml); @@ -706,8 +672,16 @@ validate_xml_verbose(xmlNode *xml_blob) gboolean validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs) { + return pcmk__validate_xml(xml_blob, validation, to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL, GUINT_TO_POINTER(LOG_ERR)); +} + +gboolean +pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidityErrorFunc error_handler, void* error_handler_context) +{ int version = 0; + CRM_CHECK((xml_blob != NULL) && (xml_blob->doc != NULL), return FALSE); + if (validation == NULL) { validation = crm_element_value(xml_blob, XML_ATTR_VALIDATION); } @@ -717,7 +691,7 @@ validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs) bool valid = FALSE; for (lpc = 0; lpc < xml_schema_max; lpc++) { - if (validate_with(xml_blob, lpc, FALSE)) { + if (validate_with(xml_blob, lpc, NULL, NULL)) { valid = TRUE; crm_xml_add(xml_blob, XML_ATTR_VALIDATION, known_schemas[lpc].name); @@ -735,7 +709,7 @@ validate_xml(xmlNode *xml_blob, const char *validation, gboolean to_logs) if (strcmp(validation, PCMK__VALUE_NONE) == 0) { return TRUE; } else if (version < xml_schema_max) { - return validate_with(xml_blob, version, to_logs); + return validate_with(xml_blob, version, error_handler, error_handler_context); } crm_err("Unknown validator: %s", validation); @@ -884,47 +858,17 @@ cib_upgrade_err(void *ctx, const char *fmt, ...) va_end(ap); } - -/* Denotes temporary emergency fix for "xmldiff'ing not text-node-ready"; - proper fix is most likely to teach __xml_diff_object and friends to - deal with XML_TEXT_NODE (and more?), i.e., those nodes currently - missing "_private" field (implicitly as NULL) which clashes with - unchecked accesses (e.g. in __xml_offset) -- the outcome may be that - those unexpected XML nodes will simply be ignored for the purpose of - diff'ing, or it may be made more robust, or per the user's preference - (which then may be exposed as crm_diff switch). - - Said XML_TEXT_NODE may appear unexpectedly due to how upgrade-2.10.xsl - is arranged. - - The emergency fix is simple: reparse XSLT output with blank-ignoring - parser. */ -#ifndef PCMK_SCHEMAS_EMERGENCY_XSLT -#define PCMK_SCHEMAS_EMERGENCY_XSLT 1 -#endif - static xmlNode * apply_transformation(xmlNode *xml, const char *transform, gboolean to_logs) { char *xform = NULL; xmlNode *out = NULL; xmlDocPtr res = NULL; - xmlDocPtr doc = NULL; xsltStylesheet *xslt = NULL; -#if PCMK_SCHEMAS_EMERGENCY_XSLT != 0 - xmlChar *emergency_result; - int emergency_txt_len; - int emergency_res; -#endif - - CRM_CHECK(xml != NULL, return FALSE); - doc = getDocPtr(xml); + xform = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt, transform); - xmlLoadExtDtdDefaultValue = 1; - xmlSubstituteEntitiesDefault(1); - /* for capturing, e.g., what's emitted via <xsl:message> */ if (to_logs) { xsltSetGenericErrorFunc(NULL, cib_upgrade_err); @@ -935,22 +879,12 @@ apply_transformation(xmlNode *xml, const char *transform, gboolean to_logs) xslt = xsltParseStylesheetFile((pcmkXmlStr) xform); CRM_CHECK(xslt != NULL, goto cleanup); - res = xsltApplyStylesheet(xslt, doc, NULL); + res = xsltApplyStylesheet(xslt, xml->doc, NULL); CRM_CHECK(res != NULL, goto cleanup); xsltSetGenericErrorFunc(NULL, NULL); /* restore default one */ - -#if PCMK_SCHEMAS_EMERGENCY_XSLT != 0 - emergency_res = xsltSaveResultToString(&emergency_result, - &emergency_txt_len, res, xslt); - xmlFreeDoc(res); - CRM_CHECK(emergency_res == 0, goto cleanup); - out = string2xml((const char *) emergency_result); - free(emergency_result); -#else out = xmlDocGetRootElement(res); -#endif cleanup: if (xslt) { @@ -1055,12 +989,15 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, int max_stable_schemas = xml_latest_schema_index(); int lpc = 0, match = -1, rc = pcmk_ok; int next = -1; /* -1 denotes "inactive" value */ + xmlRelaxNGValidityErrorFunc error_handler = + to_logs ? (xmlRelaxNGValidityErrorFunc) xml_log : NULL; CRM_CHECK(best != NULL, return -EINVAL); *best = 0; - CRM_CHECK(xml_blob != NULL, return -EINVAL); - CRM_CHECK(*xml_blob != NULL, return -EINVAL); + CRM_CHECK((xml_blob != NULL) && (*xml_blob != NULL) + && ((*xml_blob)->doc != NULL), + return -EINVAL); xml = *xml_blob; value = crm_element_value_copy(xml, XML_ATTR_VALIDATION); @@ -1090,7 +1027,7 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, known_schemas[lpc].name ? known_schemas[lpc].name : "<unset>", lpc, max_stable_schemas); - if (validate_with(xml, lpc, to_logs) == FALSE) { + if (validate_with(xml, lpc, error_handler, GUINT_TO_POINTER(LOG_ERR)) == FALSE) { if (next != -1) { crm_info("Configuration not valid for schema: %s", known_schemas[lpc].name); @@ -1155,7 +1092,7 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, known_schemas[lpc].transform); rc = -pcmk_err_transform_failed; - } else if (validate_with(upgrade, next, to_logs)) { + } else if (validate_with(upgrade, next, error_handler, GUINT_TO_POINTER(LOG_ERR))) { crm_info("Transformation %s.xsl successful", known_schemas[lpc].transform); lpc = next; diff --git a/lib/common/strings.c b/lib/common/strings.c index b245102..d9d2fda 100644 --- a/lib/common/strings.c +++ b/lib/common/strings.c @@ -417,10 +417,7 @@ crm_is_true(const char *s) { gboolean ret = FALSE; - if (s != NULL) { - crm_str_to_boolean(s, &ret); - } - return ret; + return (crm_str_to_boolean(s, &ret) < 0)? FALSE : ret; } int @@ -768,12 +765,15 @@ pcmk__compress(const char *data, unsigned int length, unsigned int max, *result_len = max; rc = BZ2_bzBuffToBuffCompress(compressed, result_len, uncompressed, length, CRM_BZ2_BLOCKS, 0, CRM_BZ2_WORK); + rc = pcmk__bzlib2rc(rc); + free(uncompressed); - if (rc != BZ_OK) { - crm_err("Compression of %d bytes failed: %s " CRM_XS " bzerror=%d", - length, bz2_strerror(rc), rc); + + if (rc != pcmk_rc_ok) { + crm_err("Compression of %d bytes failed: %s " CRM_XS " rc=%d", + length, pcmk_rc_str(rc), rc); free(compressed); - return pcmk_rc_error; + return rc; } #ifdef CLOCK_MONOTONIC diff --git a/lib/common/tests/Makefile.am b/lib/common/tests/Makefile.am index b147309..c0407e5 100644 --- a/lib/common/tests/Makefile.am +++ b/lib/common/tests/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2020-2022 the Pacemaker project contributors +# Copyright 2020-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -9,6 +9,7 @@ SUBDIRS = \ acl \ + actions \ agents \ cmdline \ flags \ @@ -17,7 +18,6 @@ SUBDIRS = \ iso8601 \ lists \ nvpair \ - operations \ options \ output \ results \ diff --git a/lib/common/tests/acl/Makefile.am b/lib/common/tests/acl/Makefile.am index 50408f9..19903db 100644 --- a/lib/common/tests/acl/Makefile.am +++ b/lib/common/tests/acl/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2021-2022 the Pacemaker project contributors +# Copyright 2021-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -12,10 +12,9 @@ include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = \ - pcmk__is_user_in_group_test \ - pcmk_acl_required_test \ - xml_acl_denied_test \ - xml_acl_enabled_test +check_PROGRAMS = pcmk__is_user_in_group_test \ + pcmk_acl_required_test \ + xml_acl_denied_test \ + xml_acl_enabled_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/operations/Makefile.am b/lib/common/tests/actions/Makefile.am index 4687e1b..6890b84 100644 --- a/lib/common/tests/operations/Makefile.am +++ b/lib/common/tests/actions/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2020-2022 the Pacemaker project contributors +# Copyright 2020-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -11,12 +11,12 @@ include $(top_srcdir)/mk/tap.mk include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = copy_in_properties_test \ - expand_plus_plus_test \ - fix_plus_plus_recursive_test \ - parse_op_key_test \ - pcmk_is_probe_test \ - pcmk_xe_is_probe_test \ - pcmk_xe_mask_probe_failure_test +check_PROGRAMS = copy_in_properties_test \ + expand_plus_plus_test \ + fix_plus_plus_recursive_test \ + parse_op_key_test \ + pcmk_is_probe_test \ + pcmk_xe_is_probe_test \ + pcmk_xe_mask_probe_failure_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/operations/copy_in_properties_test.c b/lib/common/tests/actions/copy_in_properties_test.c index 7882551..7882551 100644 --- a/lib/common/tests/operations/copy_in_properties_test.c +++ b/lib/common/tests/actions/copy_in_properties_test.c diff --git a/lib/common/tests/operations/expand_plus_plus_test.c b/lib/common/tests/actions/expand_plus_plus_test.c index 41471f9..41471f9 100644 --- a/lib/common/tests/operations/expand_plus_plus_test.c +++ b/lib/common/tests/actions/expand_plus_plus_test.c diff --git a/lib/common/tests/operations/fix_plus_plus_recursive_test.c b/lib/common/tests/actions/fix_plus_plus_recursive_test.c index b3c7cc2..b3c7cc2 100644 --- a/lib/common/tests/operations/fix_plus_plus_recursive_test.c +++ b/lib/common/tests/actions/fix_plus_plus_recursive_test.c diff --git a/lib/common/tests/operations/parse_op_key_test.c b/lib/common/tests/actions/parse_op_key_test.c index 1b1bfff..1b1bfff 100644 --- a/lib/common/tests/operations/parse_op_key_test.c +++ b/lib/common/tests/actions/parse_op_key_test.c diff --git a/lib/common/tests/operations/pcmk_is_probe_test.c b/lib/common/tests/actions/pcmk_is_probe_test.c index 4a65e3f..4a65e3f 100644 --- a/lib/common/tests/operations/pcmk_is_probe_test.c +++ b/lib/common/tests/actions/pcmk_is_probe_test.c diff --git a/lib/common/tests/operations/pcmk_xe_is_probe_test.c b/lib/common/tests/actions/pcmk_xe_is_probe_test.c index 62b21d9..62b21d9 100644 --- a/lib/common/tests/operations/pcmk_xe_is_probe_test.c +++ b/lib/common/tests/actions/pcmk_xe_is_probe_test.c diff --git a/lib/common/tests/operations/pcmk_xe_mask_probe_failure_test.c b/lib/common/tests/actions/pcmk_xe_mask_probe_failure_test.c index 9e38019..9e38019 100644 --- a/lib/common/tests/operations/pcmk_xe_mask_probe_failure_test.c +++ b/lib/common/tests/actions/pcmk_xe_mask_probe_failure_test.c diff --git a/lib/common/tests/agents/Makefile.am b/lib/common/tests/agents/Makefile.am index 7a54b7d..b3837d7 100644 --- a/lib/common/tests/agents/Makefile.am +++ b/lib/common/tests/agents/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2020-2022 the Pacemaker project contributors +# Copyright 2020-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -11,10 +11,10 @@ include $(top_srcdir)/mk/tap.mk include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = crm_generate_ra_key_test \ - crm_parse_agent_spec_test \ - pcmk__effective_rc_test \ - pcmk_get_ra_caps_test \ - pcmk_stonith_param_test +check_PROGRAMS = crm_generate_ra_key_test \ + crm_parse_agent_spec_test \ + pcmk__effective_rc_test \ + pcmk_get_ra_caps_test \ + pcmk_stonith_param_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/agents/crm_parse_agent_spec_test.c b/lib/common/tests/agents/crm_parse_agent_spec_test.c index cfd75f0..1d44459 100644 --- a/lib/common/tests/agents/crm_parse_agent_spec_test.c +++ b/lib/common/tests/agents/crm_parse_agent_spec_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2022 the Pacemaker project contributors + * Copyright 2022-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -22,14 +22,22 @@ all_params_null(void **state) { static void no_prov_or_type(void **state) { - assert_int_equal(crm_parse_agent_spec("ocf", NULL, NULL, NULL), -EINVAL); - assert_int_equal(crm_parse_agent_spec("ocf:", NULL, NULL, NULL), -EINVAL); - assert_int_equal(crm_parse_agent_spec("ocf::", NULL, NULL, NULL), -EINVAL); + char *std = NULL; + char *prov = NULL; + char *ty = NULL; + + assert_int_equal(crm_parse_agent_spec("ocf", &std, &prov, &ty), -EINVAL); + assert_int_equal(crm_parse_agent_spec("ocf:", &std, &prov, &ty), -EINVAL); + assert_int_equal(crm_parse_agent_spec("ocf::", &std, &prov, &ty), -EINVAL); } static void no_type(void **state) { - assert_int_equal(crm_parse_agent_spec("ocf:pacemaker:", NULL, NULL, NULL), -EINVAL); + char *std = NULL; + char *prov = NULL; + char *ty = NULL; + + assert_int_equal(crm_parse_agent_spec("ocf:pacemaker:", &std, &prov, &ty), -EINVAL); } static void diff --git a/lib/common/tests/cmdline/Makefile.am b/lib/common/tests/cmdline/Makefile.am index d781ed5..792425b 100644 --- a/lib/common/tests/cmdline/Makefile.am +++ b/lib/common/tests/cmdline/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2020-2022 the Pacemaker project contributors +# Copyright 2020-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -12,6 +12,7 @@ include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. check_PROGRAMS = pcmk__cmdline_preproc_test \ - pcmk__quote_cmdline_test + pcmk__new_common_args_test \ + pcmk__quote_cmdline_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/cmdline/pcmk__cmdline_preproc_test.c b/lib/common/tests/cmdline/pcmk__cmdline_preproc_test.c index 863fbb9..299fec6 100644 --- a/lib/common/tests/cmdline/pcmk__cmdline_preproc_test.c +++ b/lib/common/tests/cmdline/pcmk__cmdline_preproc_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the Pacemaker project contributors + * Copyright 2020-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -112,6 +112,16 @@ negative_score_2(void **state) { } static void +negative_score_3(void **state) { + const char *argv[] = { "crm_attribute", "-p", "-v", "-INFINITY", NULL }; + const gchar *expected[] = { "crm_attribute", "-p", "-v", "-INFINITY", NULL }; + + gchar **processed = pcmk__cmdline_preproc((char **) argv, "pv"); + LISTS_EQ(processed, expected); + g_strfreev(processed); +} + +static void string_arg_with_dash(void **state) { const char *argv[] = { "crm_mon", "-n", "crm_mon_options", "-v", "--opt1 --opt2", NULL }; const gchar *expected[] = { "crm_mon", "-n", "crm_mon_options", "-v", "--opt1 --opt2", NULL }; @@ -151,6 +161,7 @@ PCMK__UNIT_TEST(NULL, NULL, cmocka_unit_test(long_arg), cmocka_unit_test(negative_score), cmocka_unit_test(negative_score_2), + cmocka_unit_test(negative_score_3), cmocka_unit_test(string_arg_with_dash), cmocka_unit_test(string_arg_with_dash_2), cmocka_unit_test(string_arg_with_dash_3)) diff --git a/lib/common/tests/cmdline/pcmk__new_common_args_test.c b/lib/common/tests/cmdline/pcmk__new_common_args_test.c new file mode 100644 index 0000000..6b70465 --- /dev/null +++ b/lib/common/tests/cmdline/pcmk__new_common_args_test.c @@ -0,0 +1,62 @@ +/* + * Copyright 2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <crm/common/unittest_internal.h> +#include <crm/common/cmdline_internal.h> + +#include "mock_private.h" + +#include <glib.h> + +static void +calloc_fails(void **state) +{ + pcmk__assert_exits(CRM_EX_OSERR, + { + pcmk__mock_calloc = true; // calloc() will return NULL + expect_value(__wrap_calloc, nmemb, 1); + expect_value(__wrap_calloc, size, sizeof(pcmk__common_args_t)); + pcmk__new_common_args("boring summary"); + pcmk__mock_calloc = false; // Use real calloc() + } + ); +} + +static void +strdup_fails(void **state) +{ + pcmk__assert_exits(CRM_EX_OSERR, + { + pcmk__mock_strdup = true; // strdup() will return NULL + expect_string(__wrap_strdup, s, "boring summary"); + pcmk__new_common_args("boring summary"); + pcmk__mock_strdup = false; // Use the real strdup() + } + ); +} + +static void +success(void **state) +{ + pcmk__common_args_t *args = pcmk__new_common_args("boring summary"); + assert_string_equal(args->summary, "boring summary"); + assert_null(args->output_as_descr); + assert_false(args->version); + assert_false(args->quiet); + assert_int_equal(args->verbosity, 0); + assert_null(args->output_ty); + assert_null(args->output_dest); +} + +PCMK__UNIT_TEST(NULL, NULL, + cmocka_unit_test(calloc_fails), + cmocka_unit_test(strdup_fails), + cmocka_unit_test(success)) diff --git a/lib/common/tests/flags/Makefile.am b/lib/common/tests/flags/Makefile.am index 16d8ffb..22a101a 100644 --- a/lib/common/tests/flags/Makefile.am +++ b/lib/common/tests/flags/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2020-2022 the Pacemaker project contributors +# Copyright 2020-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -11,10 +11,9 @@ include $(top_srcdir)/mk/tap.mk include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = \ - pcmk__clear_flags_as_test \ - pcmk__set_flags_as_test \ - pcmk_all_flags_set_test \ - pcmk_any_flags_set_test +check_PROGRAMS = pcmk__clear_flags_as_test \ + pcmk__set_flags_as_test \ + pcmk_all_flags_set_test \ + pcmk_any_flags_set_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/io/Makefile.am b/lib/common/tests/io/Makefile.am index c26482c..f7519d8 100644 --- a/lib/common/tests/io/Makefile.am +++ b/lib/common/tests/io/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2020-2022 the Pacemaker project contributors +# Copyright 2020-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -11,8 +11,7 @@ include $(top_srcdir)/mk/tap.mk include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = \ - pcmk__full_path_test \ - pcmk__get_tmpdir_test +check_PROGRAMS = pcmk__full_path_test \ + pcmk__get_tmpdir_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/lists/Makefile.am b/lib/common/tests/lists/Makefile.am index ae0c0b6..0fa1e15 100644 --- a/lib/common/tests/lists/Makefile.am +++ b/lib/common/tests/lists/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2022 the Pacemaker project contributors +# Copyright 2022-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -12,9 +12,8 @@ include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = \ - pcmk__list_of_1_test \ - pcmk__list_of_multiple_test \ - pcmk__subtract_lists_test +check_PROGRAMS = pcmk__list_of_1_test \ + pcmk__list_of_multiple_test \ + pcmk__subtract_lists_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/nvpair/Makefile.am b/lib/common/tests/nvpair/Makefile.am index 7acaba3..7f406bd 100644 --- a/lib/common/tests/nvpair/Makefile.am +++ b/lib/common/tests/nvpair/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2021-2022 the Pacemaker project contributors +# Copyright 2021-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -11,8 +11,8 @@ include $(top_srcdir)/mk/tap.mk include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = pcmk__xe_attr_is_true_test \ - pcmk__xe_get_bool_attr_test \ - pcmk__xe_set_bool_attr_test +check_PROGRAMS = pcmk__xe_attr_is_true_test \ + pcmk__xe_get_bool_attr_test \ + pcmk__xe_set_bool_attr_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/options/Makefile.am b/lib/common/tests/options/Makefile.am index 9a5fa98..cc1008e 100644 --- a/lib/common/tests/options/Makefile.am +++ b/lib/common/tests/options/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2022 the Pacemaker project contributors +# Copyright 2022-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -11,9 +11,8 @@ include $(top_srcdir)/mk/tap.mk include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = \ - pcmk__env_option_test \ - pcmk__set_env_option_test \ - pcmk__env_option_enabled_test +check_PROGRAMS = pcmk__env_option_test \ + pcmk__set_env_option_test \ + pcmk__env_option_enabled_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/options/pcmk__set_env_option_test.c b/lib/common/tests/options/pcmk__set_env_option_test.c index 753bf74..22fd795 100644 --- a/lib/common/tests/options/pcmk__set_env_option_test.c +++ b/lib/common/tests/options/pcmk__set_env_option_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2022 the Pacemaker project contributors + * Copyright 2022-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -20,18 +20,18 @@ bad_input_string(void **state) // Never call setenv() pcmk__mock_setenv = true; - pcmk__set_env_option(NULL, "new_value"); - pcmk__set_env_option("", "new_value"); - pcmk__set_env_option("name=val", "new_value"); + pcmk__set_env_option(NULL, "new_value", true); + pcmk__set_env_option("", "new_value", true); + pcmk__set_env_option("name=val", "new_value", true); pcmk__mock_setenv = false; // Never call unsetenv() pcmk__mock_unsetenv = true; - pcmk__set_env_option(NULL, NULL); - pcmk__set_env_option("", NULL); - pcmk__set_env_option("name=val", NULL); + pcmk__set_env_option(NULL, NULL, true); + pcmk__set_env_option("", NULL, true); + pcmk__set_env_option("name=val", NULL, true); pcmk__mock_unsetenv = false; } @@ -53,11 +53,11 @@ input_too_long_for_both(void **state) // Never call setenv() or unsetenv() pcmk__mock_setenv = true; - pcmk__set_env_option(long_opt, "new_value"); + pcmk__set_env_option(long_opt, "new_value", true); pcmk__mock_setenv = false; pcmk__mock_unsetenv = true; - pcmk__set_env_option(long_opt, NULL); + pcmk__set_env_option(long_opt, NULL, true); pcmk__mock_unsetenv = false; } @@ -87,7 +87,7 @@ input_too_long_for_pcmk(void **state) expect_string(__wrap_setenv, value, "new_value"); expect_value(__wrap_setenv, overwrite, 1); will_return(__wrap_setenv, 0); - pcmk__set_env_option(long_opt, "new_value"); + pcmk__set_env_option(long_opt, "new_value", true); pcmk__mock_setenv = false; @@ -96,7 +96,7 @@ input_too_long_for_pcmk(void **state) expect_string(__wrap_unsetenv, name, buf); will_return(__wrap_unsetenv, 0); - pcmk__set_env_option(long_opt, NULL); + pcmk__set_env_option(long_opt, NULL, true); pcmk__mock_unsetenv = false; } @@ -115,7 +115,7 @@ valid_inputs_set(void **state) expect_string(__wrap_setenv, value, "new_value"); expect_value(__wrap_setenv, overwrite, 1); will_return(__wrap_setenv, 0); - pcmk__set_env_option("env_var", "new_value"); + pcmk__set_env_option("env_var", "new_value", true); // Empty string is also a valid value expect_string(__wrap_setenv, name, "PCMK_env_var"); @@ -126,7 +126,7 @@ valid_inputs_set(void **state) expect_string(__wrap_setenv, value, ""); expect_value(__wrap_setenv, overwrite, 1); will_return(__wrap_setenv, 0); - pcmk__set_env_option("env_var", ""); + pcmk__set_env_option("env_var", "", true); pcmk__mock_setenv = false; } @@ -141,7 +141,33 @@ valid_inputs_unset(void **state) will_return(__wrap_unsetenv, 0); expect_string(__wrap_unsetenv, name, "HA_env_var"); will_return(__wrap_unsetenv, 0); - pcmk__set_env_option("env_var", NULL); + pcmk__set_env_option("env_var", NULL, true); + + pcmk__mock_unsetenv = false; +} + +static void +disable_compat(void **state) +{ + // Make sure we set only "PCMK_<option>" and not "HA_<option>" + pcmk__mock_setenv = true; + + expect_string(__wrap_setenv, name, "PCMK_env_var"); + expect_string(__wrap_setenv, value, "new_value"); + expect_value(__wrap_setenv, overwrite, 1); + will_return(__wrap_setenv, 0); + pcmk__set_env_option("env_var", "new_value", false); + + pcmk__mock_setenv = false; + + // Make sure we clear both "PCMK_<option>" and "HA_<option>" + pcmk__mock_unsetenv = true; + + expect_string(__wrap_unsetenv, name, "PCMK_env_var"); + will_return(__wrap_unsetenv, 0); + expect_string(__wrap_unsetenv, name, "HA_env_var"); + will_return(__wrap_unsetenv, 0); + pcmk__set_env_option("env_var", NULL, false); pcmk__mock_unsetenv = false; } @@ -151,4 +177,5 @@ PCMK__UNIT_TEST(NULL, NULL, cmocka_unit_test(input_too_long_for_both), cmocka_unit_test(input_too_long_for_pcmk), cmocka_unit_test(valid_inputs_set), - cmocka_unit_test(valid_inputs_unset)) + cmocka_unit_test(valid_inputs_unset), + cmocka_unit_test(disable_compat)) diff --git a/lib/common/tests/output/Makefile.am b/lib/common/tests/output/Makefile.am index 6ac7b5f..30f1494 100644 --- a/lib/common/tests/output/Makefile.am +++ b/lib/common/tests/output/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2021-2022 the Pacemaker project contributors +# Copyright 2021-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -11,14 +11,14 @@ include $(top_srcdir)/mk/tap.mk include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = pcmk__call_message_test \ - pcmk__output_and_clear_error_test \ - pcmk__output_free_test \ - pcmk__output_new_test \ - pcmk__register_format_test \ - pcmk__register_formats_test \ - pcmk__register_message_test \ - pcmk__register_messages_test \ - pcmk__unregister_formats_test +check_PROGRAMS = pcmk__call_message_test \ + pcmk__output_and_clear_error_test \ + pcmk__output_free_test \ + pcmk__output_new_test \ + pcmk__register_format_test \ + pcmk__register_formats_test \ + pcmk__register_message_test \ + pcmk__register_messages_test \ + pcmk__unregister_formats_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/output/pcmk__output_new_test.c b/lib/common/tests/output/pcmk__output_new_test.c index de4268c..a05d9a7 100644 --- a/lib/common/tests/output/pcmk__output_new_test.c +++ b/lib/common/tests/output/pcmk__output_new_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2022 the Pacemaker project contributors + * Copyright 2022-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -95,9 +95,15 @@ fopen_fails(void **state) { pcmk__output_t *out = NULL; pcmk__mock_fopen = true; +#if defined(HAVE_FOPEN64) && defined(_FILE_OFFSET_BITS) && (_FILE_OFFSET_BITS == 64) && (SIZEOF_LONG < 8) + expect_string(__wrap_fopen64, pathname, "destfile"); + expect_string(__wrap_fopen64, mode, "w"); + will_return(__wrap_fopen64, EPERM); +#else expect_string(__wrap_fopen, pathname, "destfile"); expect_string(__wrap_fopen, mode, "w"); will_return(__wrap_fopen, EPERM); +#endif assert_int_equal(pcmk__output_new(&out, "text", "destfile", NULL), EPERM); diff --git a/lib/common/tests/results/Makefile.am b/lib/common/tests/results/Makefile.am index 8d51d12..a7d5663 100644 --- a/lib/common/tests/results/Makefile.am +++ b/lib/common/tests/results/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2021-2022 the Pacemaker project contributors +# Copyright 2021-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -11,6 +11,6 @@ include $(top_srcdir)/mk/tap.mk include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = pcmk__results_test +check_PROGRAMS = pcmk__results_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/results/pcmk__results_test.c b/lib/common/tests/results/pcmk__results_test.c index 53665d1..016eb7f 100644 --- a/lib/common/tests/results/pcmk__results_test.c +++ b/lib/common/tests/results/pcmk__results_test.c @@ -47,15 +47,9 @@ test_for_pcmk_rc2exitc(void **state) { assert_int_equal(pcmk_rc2exitc(-7777777), CRM_EX_ERROR); } -static void -test_for_bz2_strerror(void **state) { - assert_string_equal(bz2_strerror(BZ_STREAM_END), "Ok"); -} - PCMK__UNIT_TEST(NULL, NULL, cmocka_unit_test(test_for_pcmk_rc_name), cmocka_unit_test(test_for_pcmk_rc_str), cmocka_unit_test(test_for_crm_exit_name), cmocka_unit_test(test_for_crm_exit_str), - cmocka_unit_test(test_for_pcmk_rc2exitc), - cmocka_unit_test(test_for_bz2_strerror)) + cmocka_unit_test(test_for_pcmk_rc2exitc)) diff --git a/lib/common/tests/scores/Makefile.am b/lib/common/tests/scores/Makefile.am index 66ca073..cb96155 100644 --- a/lib/common/tests/scores/Makefile.am +++ b/lib/common/tests/scores/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2020-2022 the Pacemaker project contributors +# Copyright 2020-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -11,9 +11,8 @@ include $(top_srcdir)/mk/tap.mk include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = \ - char2score_test \ - pcmk__add_scores_test \ - pcmk_readable_score_test +check_PROGRAMS = char2score_test \ + pcmk__add_scores_test \ + pcmk_readable_score_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/scores/pcmk__add_scores_test.c b/lib/common/tests/scores/pcmk__add_scores_test.c index 85ac232..1309659 100644 --- a/lib/common/tests/scores/pcmk__add_scores_test.c +++ b/lib/common/tests/scores/pcmk__add_scores_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2022 the Pacemaker project contributors + * Copyright 2022-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -53,6 +53,8 @@ result_infinite(void **state) assert_int_equal(pcmk__add_scores(INT_MAX, INT_MAX), CRM_SCORE_INFINITY); assert_int_equal(pcmk__add_scores(INT_MIN, INT_MIN), -CRM_SCORE_INFINITY); assert_int_equal(pcmk__add_scores(2000000, 50), CRM_SCORE_INFINITY); + assert_int_equal(pcmk__add_scores(CRM_SCORE_INFINITY/2, CRM_SCORE_INFINITY/2), CRM_SCORE_INFINITY); + assert_int_equal(pcmk__add_scores(-CRM_SCORE_INFINITY/2, -CRM_SCORE_INFINITY/2), -CRM_SCORE_INFINITY); assert_int_equal(pcmk__add_scores(-4000000, 50), -CRM_SCORE_INFINITY); } diff --git a/lib/common/tests/strings/Makefile.am b/lib/common/tests/strings/Makefile.am index 9abb8e9..e66af0d 100644 --- a/lib/common/tests/strings/Makefile.am +++ b/lib/common/tests/strings/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2020-2022 the Pacemaker project contributors +# Copyright 2020-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -11,31 +11,31 @@ include $(top_srcdir)/mk/tap.mk include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = \ - crm_get_msec_test \ - crm_is_true_test \ - crm_str_to_boolean_test \ - pcmk__add_word_test \ - pcmk__btoa_test \ - pcmk__char_in_any_str_test \ - pcmk__compress_test \ - pcmk__ends_with_test \ - pcmk__g_strcat_test \ - pcmk__guint_from_hash_test \ - pcmk__numeric_strcasecmp_test \ - pcmk__parse_ll_range_test \ - pcmk__s_test \ - pcmk__scan_double_test \ - pcmk__scan_min_int_test \ - pcmk__scan_port_test \ - pcmk__starts_with_test \ - pcmk__str_any_of_test \ - pcmk__str_in_list_test \ - pcmk__str_table_dup_test \ - pcmk__str_update_test \ - pcmk__strcmp_test \ - pcmk__strkey_table_test \ - pcmk__strikey_table_test \ - pcmk__trim_test +check_PROGRAMS = crm_get_msec_test \ + crm_is_true_test \ + crm_str_to_boolean_test \ + pcmk__add_word_test \ + pcmk__btoa_test \ + pcmk__char_in_any_str_test \ + pcmk__compress_test \ + pcmk__ends_with_test \ + pcmk__g_strcat_test \ + pcmk__guint_from_hash_test \ + pcmk__numeric_strcasecmp_test \ + pcmk__parse_ll_range_test \ + pcmk__s_test \ + pcmk__scan_double_test \ + pcmk__scan_ll_test \ + pcmk__scan_min_int_test \ + pcmk__scan_port_test \ + pcmk__starts_with_test \ + pcmk__str_any_of_test \ + pcmk__str_in_list_test \ + pcmk__str_table_dup_test \ + pcmk__str_update_test \ + pcmk__strcmp_test \ + pcmk__strkey_table_test \ + pcmk__strikey_table_test \ + pcmk__trim_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/strings/pcmk__compress_test.c b/lib/common/tests/strings/pcmk__compress_test.c index 7480937..7b59d9d 100644 --- a/lib/common/tests/strings/pcmk__compress_test.c +++ b/lib/common/tests/strings/pcmk__compress_test.c @@ -33,7 +33,7 @@ max_too_small(void **state) char *result = calloc(1024, sizeof(char)); unsigned int len; - assert_int_equal(pcmk__compress(SIMPLE_DATA, 40, 10, &result, &len), pcmk_rc_error); + assert_int_equal(pcmk__compress(SIMPLE_DATA, 40, 10, &result, &len), EFBIG); } static void diff --git a/lib/common/tests/strings/pcmk__guint_from_hash_test.c b/lib/common/tests/strings/pcmk__guint_from_hash_test.c index e2b4762..225c5b3 100644 --- a/lib/common/tests/strings/pcmk__guint_from_hash_test.c +++ b/lib/common/tests/strings/pcmk__guint_from_hash_test.c @@ -59,6 +59,7 @@ conversion_errors(void **state) g_hash_table_insert(tbl, strdup("negative"), strdup("-3")); g_hash_table_insert(tbl, strdup("toobig"), strdup("20000000000000000")); + g_hash_table_insert(tbl, strdup("baddata"), strdup("asdf")); assert_int_equal(pcmk__guint_from_hash(tbl, "negative", 456, &result), ERANGE); assert_int_equal(result, 456); @@ -66,6 +67,9 @@ conversion_errors(void **state) assert_int_equal(pcmk__guint_from_hash(tbl, "toobig", 456, &result), ERANGE); assert_int_equal(result, 456); + assert_int_equal(pcmk__guint_from_hash(tbl, "baddata", 456, &result), EINVAL); + assert_int_equal(result, 456); + g_hash_table_destroy(tbl); } diff --git a/lib/common/tests/strings/pcmk__scan_ll_test.c b/lib/common/tests/strings/pcmk__scan_ll_test.c new file mode 100644 index 0000000..645ecb4 --- /dev/null +++ b/lib/common/tests/strings/pcmk__scan_ll_test.c @@ -0,0 +1,64 @@ +/* + * Copyright 2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <crm/common/unittest_internal.h> + +static void +empty_input_string(void **state) +{ + long long result; + + assert_int_equal(pcmk__scan_ll(NULL, &result, 47), pcmk_rc_ok); + assert_int_equal(result, 47); +} + +static void +bad_input_string(void **state) +{ + long long result; + + assert_int_equal(pcmk__scan_ll("asdf", &result, 47), EINVAL); + assert_int_equal(result, 47); + assert_int_equal(pcmk__scan_ll("as12", &result, 47), EINVAL); + assert_int_equal(result, 47); +} + +static void +trailing_chars(void **state) +{ + long long result; + + assert_int_equal(pcmk__scan_ll("12as", &result, 47), pcmk_rc_ok); + assert_int_equal(result, 12); +} + +static void +no_result_variable(void **state) +{ + assert_int_equal(pcmk__scan_ll("1234", NULL, 47), pcmk_rc_ok); + assert_int_equal(pcmk__scan_ll("asdf", NULL, 47), EINVAL); +} + +static void +typical_case(void **state) +{ + long long result; + + assert_int_equal(pcmk__scan_ll("1234", &result, 47), pcmk_rc_ok); + assert_int_equal(result, 1234); +} + +PCMK__UNIT_TEST(NULL, NULL, + cmocka_unit_test(empty_input_string), + cmocka_unit_test(bad_input_string), + cmocka_unit_test(trailing_chars), + cmocka_unit_test(no_result_variable), + cmocka_unit_test(typical_case)) diff --git a/lib/common/tests/utils/Makefile.am b/lib/common/tests/utils/Makefile.am index edccf09..f028ce4 100644 --- a/lib/common/tests/utils/Makefile.am +++ b/lib/common/tests/utils/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2020-2022 the Pacemaker project contributors +# Copyright 2020-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -11,15 +11,17 @@ include $(top_srcdir)/mk/tap.mk include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = \ - compare_version_test \ - crm_meta_name_test \ - crm_meta_value_test \ - crm_user_lookup_test \ - pcmk_daemon_user_test \ - pcmk_str_is_infinity_test \ - pcmk_str_is_minus_infinity_test \ - pcmk__getpid_s_test +check_PROGRAMS = compare_version_test \ + crm_meta_name_test \ + crm_meta_value_test \ + crm_user_lookup_test \ + pcmk_daemon_user_test \ + pcmk_str_is_infinity_test \ + pcmk_str_is_minus_infinity_test \ + pcmk__fail_attr_name_test \ + pcmk__failcount_name_test \ + pcmk__getpid_s_test \ + pcmk__lastfailure_name_test if WRAPPABLE_UNAME check_PROGRAMS += pcmk_hostname_test diff --git a/lib/common/tests/utils/pcmk__fail_attr_name_test.c b/lib/common/tests/utils/pcmk__fail_attr_name_test.c new file mode 100644 index 0000000..c6c25fc --- /dev/null +++ b/lib/common/tests/utils/pcmk__fail_attr_name_test.c @@ -0,0 +1,36 @@ +/* + * Copyright 2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <crm/common/unittest_internal.h> + +static void +null_arguments(void **state) +{ + assert_null(pcmk__fail_attr_name(NULL, NULL, NULL, 30000)); + assert_null(pcmk__fail_attr_name(NULL, "myrsc", "monitor", 30000)); + assert_null(pcmk__fail_attr_name("xyz", NULL, "monitor", 30000)); + assert_null(pcmk__fail_attr_name("xyz", "myrsc", NULL, 30000)); +} + +static void +standard_usage(void **state) +{ + char *s = NULL; + + assert_string_equal(pcmk__fail_attr_name("xyz", "myrsc", "monitor", 30000), + "xyz-myrsc#monitor_30000"); + + free(s); +} + +PCMK__UNIT_TEST(NULL, NULL, + cmocka_unit_test(null_arguments), + cmocka_unit_test(standard_usage)) diff --git a/lib/common/tests/utils/pcmk__failcount_name_test.c b/lib/common/tests/utils/pcmk__failcount_name_test.c new file mode 100644 index 0000000..a801f4d --- /dev/null +++ b/lib/common/tests/utils/pcmk__failcount_name_test.c @@ -0,0 +1,35 @@ +/* + * Copyright 2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <crm/common/unittest_internal.h> + +static void +null_arguments(void **state) +{ + assert_null(pcmk__failcount_name(NULL, NULL, 30000)); + assert_null(pcmk__failcount_name("myrsc", NULL, 30000)); + assert_null(pcmk__failcount_name(NULL, "monitor", 30000)); +} + +static void +standard_usage(void **state) +{ + char *s = NULL; + + assert_string_equal(pcmk__failcount_name("myrsc", "monitor", 30000), + "fail-count-myrsc#monitor_30000"); + + free(s); +} + +PCMK__UNIT_TEST(NULL, NULL, + cmocka_unit_test(null_arguments), + cmocka_unit_test(standard_usage)) diff --git a/lib/common/tests/utils/pcmk__lastfailure_name_test.c b/lib/common/tests/utils/pcmk__lastfailure_name_test.c new file mode 100644 index 0000000..eab01f2 --- /dev/null +++ b/lib/common/tests/utils/pcmk__lastfailure_name_test.c @@ -0,0 +1,35 @@ +/* + * Copyright 2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU General Public License version 2 + * or later (GPLv2+) WITHOUT ANY WARRANTY. + */ + +#include <crm_internal.h> + +#include <crm/common/unittest_internal.h> + +static void +null_arguments(void **state) +{ + assert_null(pcmk__lastfailure_name(NULL, NULL, 30000)); + assert_null(pcmk__lastfailure_name("myrsc", NULL, 30000)); + assert_null(pcmk__lastfailure_name(NULL, "monitor", 30000)); +} + +static void +standard_usage(void **state) +{ + char *s = NULL; + + assert_string_equal(pcmk__lastfailure_name("myrsc", "monitor", 30000), + "last-failure-myrsc#monitor_30000"); + + free(s); +} + +PCMK__UNIT_TEST(NULL, NULL, + cmocka_unit_test(null_arguments), + cmocka_unit_test(standard_usage)) diff --git a/lib/common/tests/xml/Makefile.am b/lib/common/tests/xml/Makefile.am index 0ccdcc3..465c950 100644 --- a/lib/common/tests/xml/Makefile.am +++ b/lib/common/tests/xml/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2022 the Pacemaker project contributors +# Copyright 2022-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -11,7 +11,7 @@ include $(top_srcdir)/mk/tap.mk include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = pcmk__xe_foreach_child_test \ - pcmk__xe_match_test +check_PROGRAMS = pcmk__xe_foreach_child_test \ + pcmk__xe_match_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/xml/pcmk__xe_foreach_child_test.c b/lib/common/tests/xml/pcmk__xe_foreach_child_test.c index 9bcba87..ffb9171 100644 --- a/lib/common/tests/xml/pcmk__xe_foreach_child_test.c +++ b/lib/common/tests/xml/pcmk__xe_foreach_child_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2022 the Pacemaker project contributors + * Copyright 2022-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -14,7 +14,7 @@ static int compare_name_handler(xmlNode *xml, void *userdata) { function_called(); - assert_string_equal((char *) userdata, crm_element_name(xml)); + assert_string_equal((char *) userdata, (const char *) xml->name); return pcmk_rc_ok; } @@ -140,7 +140,8 @@ const char *str3 = static int any_of_handler(xmlNode *xml, void *userdata) { function_called(); - assert_true(pcmk__str_any_of(crm_element_name(xml), "node1", "node2", "node3", NULL)); + assert_true(pcmk__str_any_of((const char *) xml->name, + "node1", "node2", "node3", NULL)); return pcmk_rc_ok; } @@ -160,7 +161,7 @@ any_of_test(void **state) { static int stops_on_first_handler(xmlNode *xml, void *userdata) { function_called(); - if (pcmk__str_eq(crm_element_name(xml), "node1", pcmk__str_none)) { + if (pcmk__xe_is(xml, "node1")) { return pcmk_rc_error; } else { return pcmk_rc_ok; @@ -170,7 +171,7 @@ static int stops_on_first_handler(xmlNode *xml, void *userdata) { static int stops_on_second_handler(xmlNode *xml, void *userdata) { function_called(); - if (pcmk__str_eq(crm_element_name(xml), "node2", pcmk__str_none)) { + if (pcmk__xe_is(xml, "node2")) { return pcmk_rc_error; } else { return pcmk_rc_ok; @@ -180,7 +181,7 @@ static int stops_on_second_handler(xmlNode *xml, void *userdata) { static int stops_on_third_handler(xmlNode *xml, void *userdata) { function_called(); - if (pcmk__str_eq(crm_element_name(xml), "node3", pcmk__str_none)) { + if (pcmk__xe_is(xml, "node3")) { return pcmk_rc_error; } else { return pcmk_rc_ok; diff --git a/lib/common/tests/xpath/Makefile.am b/lib/common/tests/xpath/Makefile.am index 94abeee..d4c504b 100644 --- a/lib/common/tests/xpath/Makefile.am +++ b/lib/common/tests/xpath/Makefile.am @@ -1,5 +1,5 @@ # -# Copyright 2021-2022 the Pacemaker project contributors +# Copyright 2021-2023 the Pacemaker project contributors # # The version control history for this file may have further details. # @@ -11,6 +11,6 @@ include $(top_srcdir)/mk/tap.mk include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = pcmk__xpath_node_id_test +check_PROGRAMS = pcmk__xpath_node_id_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/watchdog.c b/lib/common/watchdog.c index ff2d273..e569214 100644 --- a/lib/common/watchdog.c +++ b/lib/common/watchdog.c @@ -20,10 +20,6 @@ #include <dirent.h> #include <signal.h> -#ifdef _POSIX_MEMLOCK -# include <sys/mman.h> -#endif - static pid_t sbd_pid = 0; static void @@ -56,6 +52,7 @@ panic_local(void) int rc = pcmk_ok; uid_t uid = geteuid(); pid_t ppid = getppid(); + const char *panic_action = pcmk__env_option(PCMK__ENV_PANIC_ACTION); if(uid != 0 && ppid > 1) { /* We're a non-root pacemaker daemon (pacemaker-based, @@ -93,13 +90,15 @@ panic_local(void) /* We're either pacemakerd, or a pacemaker daemon running as root */ - if (pcmk__str_eq("crash", getenv("PCMK_panic_action"), pcmk__str_casei)) { + if (pcmk__str_eq(panic_action, "crash", pcmk__str_casei)) { sysrq_trigger('c'); - } else if (pcmk__str_eq("sync-crash", getenv("PCMK_panic_action"), pcmk__str_casei)) { + + } else if (pcmk__str_eq(panic_action, "sync-crash", pcmk__str_casei)) { sync(); sysrq_trigger('c'); + } else { - if (pcmk__str_eq("sync-reboot", getenv("PCMK_panic_action"), pcmk__str_casei)) { + if (pcmk__str_eq(panic_action, "sync-reboot", pcmk__str_casei)) { sync(); } sysrq_trigger('b'); diff --git a/lib/common/xml.c b/lib/common/xml.c index 22078ce..53ebff7 100644 --- a/lib/common/xml.c +++ b/lib/common/xml.c @@ -42,7 +42,8 @@ * parsing without XML_PARSE_RECOVER, and if that fails, try parsing again with * it, logging a warning if it succeeds. */ -#define PCMK__XML_PARSE_OPTS (XML_PARSE_NOBLANKS | XML_PARSE_RECOVER) +#define PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER (XML_PARSE_NOBLANKS) +#define PCMK__XML_PARSE_OPTS_WITH_RECOVER (XML_PARSE_NOBLANKS | XML_PARSE_RECOVER) bool pcmk__tracking_xml_changes(xmlNode *xml, bool lazy) @@ -85,8 +86,8 @@ pcmk__set_xml_doc_flag(xmlNode *xml, enum xml_private_flags flag) } // Mark document, element, and all element's parents as changed -static inline void -mark_xml_node_dirty(xmlNode *xml) +void +pcmk__mark_xml_node_dirty(xmlNode *xml) { pcmk__set_xml_doc_flag(xml, pcmk__xf_dirty); set_parent_flag(xml, pcmk__xf_dirty); @@ -114,12 +115,15 @@ void pcmk__mark_xml_created(xmlNode *xml) { xmlNode *cIter = NULL; - xml_node_private_t *nodepriv = xml->_private; + xml_node_private_t *nodepriv = NULL; + + CRM_ASSERT(xml != NULL); + nodepriv = xml->_private; if (nodepriv && pcmk__tracking_xml_changes(xml, FALSE)) { if (!pcmk_is_set(nodepriv->flags, pcmk__xf_created)) { pcmk__set_xml_flags(nodepriv, pcmk__xf_created); - mark_xml_node_dirty(xml); + pcmk__mark_xml_node_dirty(xml); } for (cIter = pcmk__xml_first_child(xml); cIter != NULL; cIter = pcmk__xml_next(cIter)) { @@ -128,17 +132,6 @@ pcmk__mark_xml_created(xmlNode *xml) } } -void -pcmk__mark_xml_attr_dirty(xmlAttr *a) -{ - xmlNode *parent = a->parent; - xml_node_private_t *nodepriv = a->_private; - - pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_modified); - pcmk__clear_xml_flags(nodepriv, pcmk__xf_deleted); - mark_xml_node_dirty(parent); -} - #define XML_DOC_PRIVATE_MAGIC 0x81726354UL #define XML_NODE_PRIVATE_MAGIC 0x54637281UL @@ -250,7 +243,7 @@ new_private_data(xmlNode *node) /* XML_ELEMENT_NODE doesn't get picked up here, node->doc is * not hooked up at the point we are called */ - mark_xml_node_dirty(node); + pcmk__mark_xml_node_dirty(node); } break; } @@ -321,19 +314,6 @@ pcmk__xml_position(const xmlNode *xml, enum xml_private_flags ignore_if_set) return position; } -// This also clears attribute's flags if not marked as deleted -static bool -marked_as_deleted(xmlAttrPtr a, void *user_data) -{ - xml_node_private_t *nodepriv = a->_private; - - if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) { - return true; - } - nodepriv->flags = pcmk__xf_none; - return false; -} - // Remove all attributes marked as deleted from an XML node static void accept_attr_deletions(xmlNode *xml) @@ -342,7 +322,7 @@ accept_attr_deletions(xmlNode *xml) ((xml_node_private_t *) xml->_private)->flags = pcmk__xf_none; // Remove this XML node's attributes that were marked as deleted - pcmk__xe_remove_matching_attrs(xml, marked_as_deleted, NULL); + pcmk__xe_remove_matching_attrs(xml, pcmk__marked_as_deleted, NULL); // Recursively do the same for this XML node's children for (xmlNodePtr cIter = pcmk__xml_first_child(xml); cIter != NULL; @@ -371,7 +351,7 @@ pcmk__xml_match(const xmlNode *haystack, const xmlNode *needle, bool exact) const char *id = ID(needle); const char *attr = (id == NULL)? NULL : XML_ATTR_ID; - return pcmk__xe_match(haystack, crm_element_name(needle), attr, id); + return pcmk__xe_match(haystack, (const char *) needle->name, attr, id); } } @@ -404,11 +384,7 @@ xmlNode * find_xml_node(const xmlNode *root, const char *search_path, gboolean must_find) { xmlNode *a_child = NULL; - const char *name = "NULL"; - - if (root != NULL) { - name = crm_element_name(root); - } + const char *name = (root == NULL)? "<NULL>" : (const char *) root->name; if (search_path == NULL) { crm_warn("Will never find <NULL>"); @@ -418,7 +394,6 @@ find_xml_node(const xmlNode *root, const char *search_path, gboolean must_find) for (a_child = pcmk__xml_first_child(root); a_child != NULL; a_child = pcmk__xml_next(a_child)) { if (strcmp((const char *)a_child->name, search_path) == 0) { -/* crm_trace("returning node (%s).", crm_element_name(a_child)); */ return a_child; } } @@ -473,7 +448,7 @@ pcmk__xe_match(const xmlNode *parent, const char *node_name, (attr_n? attr_n : ""), (attr_n? "=" : ""), (attr_n? attr_v : ""), - crm_element_name(parent)); + (const char *) parent->name); return NULL; } @@ -643,31 +618,17 @@ pcmk__xe_remove_matching_attrs(xmlNode *element, } } -xmlDoc * -getDocPtr(xmlNode * node) -{ - xmlDoc *doc = NULL; - - CRM_CHECK(node != NULL, return NULL); - - doc = node->doc; - if (doc == NULL) { - doc = xmlNewDoc((pcmkXmlStr) "1.0"); - xmlDocSetRootElement(doc, node); - xmlSetTreeDoc(node, doc); - } - return doc; -} - xmlNode * add_node_copy(xmlNode * parent, xmlNode * src_node) { xmlNode *child = NULL; - xmlDoc *doc = getDocPtr(parent); - CRM_CHECK(src_node != NULL, return NULL); + CRM_CHECK((parent != NULL) && (src_node != NULL), return NULL); - child = xmlDocCopyNode(src_node, doc, 1); + child = xmlDocCopyNode(src_node, parent->doc, 1); + if (child == NULL) { + return NULL; + } xmlAddChild(parent, child); pcmk__mark_xml_created(child); return child; @@ -686,13 +647,22 @@ create_xml_node(xmlNode * parent, const char *name) if (parent == NULL) { doc = xmlNewDoc((pcmkXmlStr) "1.0"); + if (doc == NULL) { + return NULL; + } + node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL); + if (node == NULL) { + xmlFreeDoc(doc); + return NULL; + } xmlDocSetRootElement(doc, node); } else { - doc = getDocPtr(parent); - node = xmlNewDocRawNode(doc, NULL, (pcmkXmlStr) name, NULL); - xmlAddChild(parent, node); + node = xmlNewChild(parent, NULL, (pcmkXmlStr) name, NULL); + if (node == NULL) { + return NULL; + } } pcmk__mark_xml_created(node); return node; @@ -823,7 +793,6 @@ copy_xml(xmlNode * src) CRM_ASSERT(copy != NULL); xmlDocSetRootElement(doc, copy); - xmlSetTreeDoc(copy, doc); return copy; } @@ -833,7 +802,7 @@ string2xml(const char *input) xmlNode *xml = NULL; xmlDocPtr output = NULL; xmlParserCtxtPtr ctxt = NULL; - xmlErrorPtr last_error = NULL; + const xmlError *last_error = NULL; if (input == NULL) { crm_err("Can't parse NULL input"); @@ -847,7 +816,17 @@ string2xml(const char *input) xmlCtxtResetLastError(ctxt); xmlSetGenericErrorFunc(ctxt, pcmk__log_xmllib_err); output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL, - PCMK__XML_PARSE_OPTS); + PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER); + + if (output == NULL) { + output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL, + PCMK__XML_PARSE_OPTS_WITH_RECOVER); + if (output) { + crm_warn("Successfully recovered from XML errors " + "(note: a future release will treat this as a fatal failure)"); + } + } + if (output) { xml = xmlDocGetRootElement(output); } @@ -933,9 +912,11 @@ decompress_file(const char *filename) } bz_file = BZ2_bzReadOpen(&rc, input, 0, 0, NULL, 0); - if (rc != BZ_OK) { + rc = pcmk__bzlib2rc(rc); + + if (rc != pcmk_rc_ok) { crm_err("Could not prepare to read compressed %s: %s " - CRM_XS " bzerror=%d", filename, bz2_strerror(rc), rc); + CRM_XS " rc=%d", filename, pcmk_rc_str(rc), rc); BZ2_bzReadClose(&rc, bz_file); fclose(input); return NULL; @@ -957,9 +938,11 @@ decompress_file(const char *filename) buffer[length] = '\0'; - if (rc != BZ_STREAM_END) { - crm_err("Could not read compressed %s: %s " - CRM_XS " bzerror=%d", filename, bz2_strerror(rc), rc); + rc = pcmk__bzlib2rc(rc); + + if (rc != pcmk_rc_ok) { + crm_err("Could not read compressed %s: %s " CRM_XS " rc=%d", + filename, pcmk_rc_str(rc), rc); free(buffer); buffer = NULL; } @@ -1010,7 +993,7 @@ filename2xml(const char *filename) xmlDocPtr output = NULL; bool uncompressed = true; xmlParserCtxtPtr ctxt = NULL; - xmlErrorPtr last_error = NULL; + const xmlError *last_error = NULL; /* create a parser context */ ctxt = xmlNewParserCtxt(); @@ -1026,16 +1009,45 @@ filename2xml(const char *filename) if (pcmk__str_eq(filename, "-", pcmk__str_null_matches)) { /* STDIN_FILENO == fileno(stdin) */ output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL, - PCMK__XML_PARSE_OPTS); + PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER); + + if (output == NULL) { + output = xmlCtxtReadFd(ctxt, STDIN_FILENO, "unknown.xml", NULL, + PCMK__XML_PARSE_OPTS_WITH_RECOVER); + if (output) { + crm_warn("Successfully recovered from XML errors " + "(note: a future release will treat this as a fatal failure)"); + } + } } else if (uncompressed) { - output = xmlCtxtReadFile(ctxt, filename, NULL, PCMK__XML_PARSE_OPTS); + output = xmlCtxtReadFile(ctxt, filename, NULL, + PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER); + + if (output == NULL) { + output = xmlCtxtReadFile(ctxt, filename, NULL, + PCMK__XML_PARSE_OPTS_WITH_RECOVER); + if (output) { + crm_warn("Successfully recovered from XML errors " + "(note: a future release will treat this as a fatal failure)"); + } + } } else { char *input = decompress_file(filename); output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL, - PCMK__XML_PARSE_OPTS); + PCMK__XML_PARSE_OPTS_WITHOUT_RECOVER); + + if (output == NULL) { + output = xmlCtxtReadDoc(ctxt, (pcmkXmlStr) input, NULL, NULL, + PCMK__XML_PARSE_OPTS_WITH_RECOVER); + if (output) { + crm_warn("Successfully recovered from XML errors " + "(note: a future release will treat this as a fatal failure)"); + } + } + free(input); } @@ -1134,7 +1146,7 @@ crm_xml_set_id(xmlNode *xml, const char *format, ...) * \internal * \brief Write XML to a file stream * - * \param[in] xml_node XML to write + * \param[in] xml XML to write * \param[in] filename Name of file being written (for logging only) * \param[in,out] stream Open file stream corresponding to filename * \param[in] compress Whether to compress XML before writing @@ -1143,18 +1155,18 @@ crm_xml_set_id(xmlNode *xml, const char *format, ...) * \return Standard Pacemaker return code */ static int -write_xml_stream(xmlNode *xml_node, const char *filename, FILE *stream, +write_xml_stream(const xmlNode *xml, const char *filename, FILE *stream, bool compress, unsigned int *nbytes) { int rc = pcmk_rc_ok; char *buffer = NULL; *nbytes = 0; - crm_log_xml_trace(xml_node, "writing"); + crm_log_xml_trace(xml, "writing"); - buffer = dump_xml_formatted(xml_node); + buffer = dump_xml_formatted(xml); CRM_CHECK(buffer && strlen(buffer), - crm_log_xml_warn(xml_node, "formatting failed"); + crm_log_xml_warn(xml, "formatting failed"); rc = pcmk_rc_error; goto bail); @@ -1164,24 +1176,30 @@ write_xml_stream(xmlNode *xml_node, const char *filename, FILE *stream, rc = BZ_OK; bz_file = BZ2_bzWriteOpen(&rc, stream, 5, 0, 30); - if (rc != BZ_OK) { + rc = pcmk__bzlib2rc(rc); + + if (rc != pcmk_rc_ok) { crm_warn("Not compressing %s: could not prepare file stream: %s " - CRM_XS " bzerror=%d", filename, bz2_strerror(rc), rc); + CRM_XS " rc=%d", filename, pcmk_rc_str(rc), rc); } else { BZ2_bzWrite(&rc, bz_file, buffer, strlen(buffer)); - if (rc != BZ_OK) { + rc = pcmk__bzlib2rc(rc); + + if (rc != pcmk_rc_ok) { crm_warn("Not compressing %s: could not compress data: %s " - CRM_XS " bzerror=%d errno=%d", - filename, bz2_strerror(rc), rc, errno); + CRM_XS " rc=%d errno=%d", + filename, pcmk_rc_str(rc), rc, errno); } } - if (rc == BZ_OK) { + if (rc == pcmk_rc_ok) { BZ2_bzWriteClose(&rc, bz_file, 0, &in, nbytes); - if (rc != BZ_OK) { + rc = pcmk__bzlib2rc(rc); + + if (rc != pcmk_rc_ok) { crm_warn("Not compressing %s: could not write compressed data: %s " - CRM_XS " bzerror=%d errno=%d", - filename, bz2_strerror(rc), rc, errno); + CRM_XS " rc=%d errno=%d", + filename, pcmk_rc_str(rc), rc, errno); *nbytes = 0; // retry without compression } else { crm_trace("Compressed XML for %s from %u bytes to %u", @@ -1226,7 +1244,7 @@ write_xml_stream(xmlNode *xml_node, const char *filename, FILE *stream, /*! * \brief Write XML to a file descriptor * - * \param[in] xml_node XML to write + * \param[in] xml XML to write * \param[in] filename Name of file being written (for logging only) * \param[in] fd Open file descriptor corresponding to filename * \param[in] compress Whether to compress XML before writing @@ -1234,18 +1252,19 @@ write_xml_stream(xmlNode *xml_node, const char *filename, FILE *stream, * \return Number of bytes written on success, -errno otherwise */ int -write_xml_fd(xmlNode * xml_node, const char *filename, int fd, gboolean compress) +write_xml_fd(const xmlNode *xml, const char *filename, int fd, + gboolean compress) { FILE *stream = NULL; unsigned int nbytes = 0; int rc = pcmk_rc_ok; - CRM_CHECK(xml_node && (fd > 0), return -EINVAL); + CRM_CHECK((xml != NULL) && (fd > 0), return -EINVAL); stream = fdopen(fd, "w"); if (stream == NULL) { return -errno; } - rc = write_xml_stream(xml_node, filename, stream, compress, &nbytes); + rc = write_xml_stream(xml, filename, stream, compress, &nbytes); if (rc != pcmk_rc_ok) { return pcmk_rc2legacy(rc); } @@ -1255,25 +1274,25 @@ write_xml_fd(xmlNode * xml_node, const char *filename, int fd, gboolean compress /*! * \brief Write XML to a file * - * \param[in] xml_node XML to write + * \param[in] xml XML to write * \param[in] filename Name of file to write * \param[in] compress Whether to compress XML before writing * * \return Number of bytes written on success, -errno otherwise */ int -write_xml_file(xmlNode * xml_node, const char *filename, gboolean compress) +write_xml_file(const xmlNode *xml, const char *filename, gboolean compress) { FILE *stream = NULL; unsigned int nbytes = 0; int rc = pcmk_rc_ok; - CRM_CHECK(xml_node && filename, return -EINVAL); + CRM_CHECK((xml != NULL) && (filename != NULL), return -EINVAL); stream = fopen(filename, "w"); if (stream == NULL) { return -errno; } - rc = write_xml_stream(xml_node, filename, stream, compress, &nbytes); + rc = write_xml_stream(xml, filename, stream, compress, &nbytes); if (rc != pcmk_rc_ok) { return pcmk_rc2legacy(rc); } @@ -1382,37 +1401,6 @@ crm_xml_escape(const char *text) /*! * \internal - * \brief Append an XML attribute to a buffer - * - * \param[in] attr Attribute to append - * \param[in,out] buffer Where to append the content (must not be \p NULL) - */ -static void -dump_xml_attr(const xmlAttr *attr, GString *buffer) -{ - char *p_value = NULL; - const char *p_name = NULL; - xml_node_private_t *nodepriv = NULL; - - if (attr == NULL || attr->children == NULL) { - return; - } - - nodepriv = attr->_private; - if (nodepriv && pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) { - return; - } - - p_name = (const char *) attr->name; - p_value = crm_xml_escape((const char *)attr->children->content); - pcmk__g_strcat(buffer, " ", p_name, "=\"", pcmk__s(p_value, "<null>"), "\"", - NULL); - - free(p_value); -} - -/*! - * \internal * \brief Append a string representation of an XML element to a buffer * * \param[in] data XML whose representation to append @@ -1424,24 +1412,21 @@ static void dump_xml_element(const xmlNode *data, uint32_t options, GString *buffer, int depth) { - const char *name = crm_element_name(data); bool pretty = pcmk_is_set(options, pcmk__xml_fmt_pretty); bool filtered = pcmk_is_set(options, pcmk__xml_fmt_filtered); int spaces = pretty? (2 * depth) : 0; - CRM_ASSERT(name != NULL); - for (int lpc = 0; lpc < spaces; lpc++) { g_string_append_c(buffer, ' '); } - pcmk__g_strcat(buffer, "<", name, NULL); + pcmk__g_strcat(buffer, "<", data->name, NULL); for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL; attr = attr->next) { if (!filtered || !pcmk__xa_filterable((const char *) (attr->name))) { - dump_xml_attr(attr, buffer); + pcmk__dump_xml_attr(attr, buffer); } } @@ -1457,16 +1442,16 @@ dump_xml_element(const xmlNode *data, uint32_t options, GString *buffer, } if (data->children) { - xmlNode *xChild = NULL; - for(xChild = data->children; xChild != NULL; xChild = xChild->next) { - pcmk__xml2text(xChild, options, buffer, depth + 1); + for (const xmlNode *child = data->children; child != NULL; + child = child->next) { + pcmk__xml2text(child, options, buffer, depth + 1); } for (int lpc = 0; lpc < spaces; lpc++) { g_string_append_c(buffer, ' '); } - pcmk__g_strcat(buffer, "</", name, ">", NULL); + pcmk__g_strcat(buffer, "</", data->name, ">", NULL); if (pretty) { g_string_append_c(buffer, '\n'); @@ -1559,7 +1544,45 @@ dump_xml_comment(const xmlNode *data, uint32_t options, GString *buffer, } } -#define PCMK__XMLDUMP_STATS 0 +/*! + * \internal + * \brief Get a string representation of an XML element type + * + * \param[in] type XML element type + * + * \return String representation of \p type + */ +static const char * +xml_element_type2str(xmlElementType type) +{ + static const char *const element_type_names[] = { + [XML_ELEMENT_NODE] = "element", + [XML_ATTRIBUTE_NODE] = "attribute", + [XML_TEXT_NODE] = "text", + [XML_CDATA_SECTION_NODE] = "CDATA section", + [XML_ENTITY_REF_NODE] = "entity reference", + [XML_ENTITY_NODE] = "entity", + [XML_PI_NODE] = "PI", + [XML_COMMENT_NODE] = "comment", + [XML_DOCUMENT_NODE] = "document", + [XML_DOCUMENT_TYPE_NODE] = "document type", + [XML_DOCUMENT_FRAG_NODE] = "document fragment", + [XML_NOTATION_NODE] = "notation", + [XML_HTML_DOCUMENT_NODE] = "HTML document", + [XML_DTD_NODE] = "DTD", + [XML_ELEMENT_DECL] = "element declaration", + [XML_ATTRIBUTE_DECL] = "attribute declaration", + [XML_ENTITY_DECL] = "entity declaration", + [XML_NAMESPACE_DECL] = "namespace declaration", + [XML_XINCLUDE_START] = "XInclude start", + [XML_XINCLUDE_END] = "XInclude end", + }; + + if ((type < 0) || (type >= PCMK__NELEM(element_type_names))) { + return "unrecognized type"; + } + return element_type_names[type]; +} /*! * \internal @@ -1571,7 +1594,8 @@ dump_xml_comment(const xmlNode *data, uint32_t options, GString *buffer, * \param[in] depth Current indentation level */ void -pcmk__xml2text(xmlNodePtr data, uint32_t options, GString *buffer, int depth) +pcmk__xml2text(const xmlNode *data, uint32_t options, GString *buffer, + int depth) { if (data == NULL) { crm_trace("Nothing to dump"); @@ -1581,60 +1605,6 @@ pcmk__xml2text(xmlNodePtr data, uint32_t options, GString *buffer, int depth) CRM_ASSERT(buffer != NULL); CRM_CHECK(depth >= 0, depth = 0); - if (pcmk_is_set(options, pcmk__xml_fmt_full)) { - /* libxml's serialization reuse is a good idea, sadly we cannot - apply it for the filtered cases (preceding filtering pass - would preclude further reuse of such in-situ modified XML - in generic context and is likely not a win performance-wise), - and there's also a historically unstable throughput argument - (likely stemming from memory allocation overhead, eventhough - that shall be minimized with defaults preset in crm_xml_init) */ -#if (PCMK__XMLDUMP_STATS - 0) - time_t next, new = time(NULL); -#endif - xmlDoc *doc; - xmlOutputBuffer *xml_buffer; - - doc = getDocPtr(data); - /* doc will only be NULL if data is */ - CRM_CHECK(doc != NULL, return); - - xml_buffer = xmlAllocOutputBuffer(NULL); - CRM_ASSERT(xml_buffer != NULL); - - /* XXX we could setup custom allocation scheme for the particular - buffer, but it's subsumed with crm_xml_init that needs to - be invoked prior to entering this function as such, since - its other branch vitally depends on it -- what can be done - about this all is to have a facade parsing functions that - would 100% mark entering libxml code for us, since we don't - do anything as crazy as swapping out the binary form of the - parsed tree (but those would need to be strictly used as - opposed to libxml's raw functions) */ - - xmlNodeDumpOutput(xml_buffer, doc, data, 0, - pcmk_is_set(options, pcmk__xml_fmt_pretty), NULL); - /* attempt adding final NL - failing shouldn't be fatal here */ - (void) xmlOutputBufferWrite(xml_buffer, sizeof("\n") - 1, "\n"); - if (xml_buffer->buffer != NULL) { - g_string_append(buffer, - (const gchar *) xmlBufContent(xml_buffer->buffer)); - } - -#if (PCMK__XMLDUMP_STATS - 0) - next = time(NULL); - if ((now + 1) < next) { - crm_log_xml_trace(data, "Long time"); - crm_err("xmlNodeDumpOutput() -> %lld bytes took %ds", - (long long) buffer->len, next - now); - } -#endif - - /* asserted allocation before so there should be something to remove */ - (void) xmlOutputBufferClose(xml_buffer); - return; - } - switch(data->type) { case XML_ELEMENT_NODE: /* Handle below */ @@ -1642,11 +1612,6 @@ pcmk__xml2text(xmlNodePtr data, uint32_t options, GString *buffer, int depth) break; case XML_TEXT_NODE: if (pcmk_is_set(options, pcmk__xml_fmt_text)) { - /* @COMPAT: Remove when log_data_element() is removed. There are - * no other internal code paths that set pcmk__xml_fmt_text. - * Keep an empty case handler so that we don't log an unhandled - * type warning. - */ dump_xml_text(data, options, buffer, depth); } break; @@ -1657,39 +1622,23 @@ pcmk__xml2text(xmlNodePtr data, uint32_t options, GString *buffer, int depth) dump_xml_cdata(data, options, buffer, depth); break; default: - crm_warn("Unhandled type: %d", data->type); + crm_warn("Cannot convert XML %s node to text " CRM_XS " type=%d", + xml_element_type2str(data->type), data->type); break; - - /* - XML_ATTRIBUTE_NODE = 2 - XML_ENTITY_REF_NODE = 5 - XML_ENTITY_NODE = 6 - XML_PI_NODE = 7 - XML_DOCUMENT_NODE = 9 - XML_DOCUMENT_TYPE_NODE = 10 - XML_DOCUMENT_FRAG_NODE = 11 - XML_NOTATION_NODE = 12 - XML_HTML_DOCUMENT_NODE = 13 - XML_DTD_NODE = 14 - XML_ELEMENT_DECL = 15 - XML_ATTRIBUTE_DECL = 16 - XML_ENTITY_DECL = 17 - XML_NAMESPACE_DECL = 18 - XML_XINCLUDE_START = 19 - XML_XINCLUDE_END = 20 - XML_DOCB_DOCUMENT_NODE = 21 - */ } } char * -dump_xml_formatted_with_text(xmlNode * an_xml_node) +dump_xml_formatted_with_text(const xmlNode *xml) { + /* libxml's xmlNodeDumpOutput() would work here since we're not specifically + * filtering out any nodes. However, use pcmk__xml2text() for consistency, + * to escape attribute values, and to allow a const argument. + */ char *buffer = NULL; GString *g_buffer = g_string_sized_new(1024); - pcmk__xml2text(an_xml_node, pcmk__xml_fmt_pretty|pcmk__xml_fmt_full, - g_buffer, 0); + pcmk__xml2text(xml, pcmk__xml_fmt_pretty|pcmk__xml_fmt_text, g_buffer, 0); pcmk__str_update(&buffer, g_buffer->str); g_string_free(g_buffer, TRUE); @@ -1697,12 +1646,12 @@ dump_xml_formatted_with_text(xmlNode * an_xml_node) } char * -dump_xml_formatted(xmlNode * an_xml_node) +dump_xml_formatted(const xmlNode *xml) { char *buffer = NULL; GString *g_buffer = g_string_sized_new(1024); - pcmk__xml2text(an_xml_node, pcmk__xml_fmt_pretty, g_buffer, 0); + pcmk__xml2text(xml, pcmk__xml_fmt_pretty, g_buffer, 0); pcmk__str_update(&buffer, g_buffer->str); g_string_free(g_buffer, TRUE); @@ -1710,30 +1659,46 @@ dump_xml_formatted(xmlNode * an_xml_node) } char * -dump_xml_unformatted(xmlNode * an_xml_node) +dump_xml_unformatted(const xmlNode *xml) { char *buffer = NULL; GString *g_buffer = g_string_sized_new(1024); - pcmk__xml2text(an_xml_node, 0, g_buffer, 0); + pcmk__xml2text(xml, 0, g_buffer, 0); pcmk__str_update(&buffer, g_buffer->str); g_string_free(g_buffer, TRUE); return buffer; } -gboolean -xml_has_children(const xmlNode * xml_root) +int +pcmk__xml2fd(int fd, xmlNode *cur) { - if (xml_root != NULL && xml_root->children != NULL) { - return TRUE; + bool success; + + xmlOutputBuffer *fd_out = xmlOutputBufferCreateFd(fd, NULL); + CRM_ASSERT(fd_out != NULL); + xmlNodeDumpOutput(fd_out, cur->doc, cur, 0, pcmk__xml_fmt_pretty, NULL); + + success = xmlOutputBufferWrite(fd_out, sizeof("\n") - 1, "\n") != -1; + + success = xmlOutputBufferClose(fd_out) != -1 && success; + + if (!success) { + return EIO; } - return FALSE; + + fsync(fd); + return pcmk_rc_ok; } void xml_remove_prop(xmlNode * obj, const char *name) { + if (crm_element_value(obj, name) == NULL) { + return; + } + if (pcmk__check_acl(obj, NULL, pcmk__xf_acl_write) == FALSE) { crm_trace("Cannot remove %s from %s", name, obj->name); @@ -1750,7 +1715,7 @@ xml_remove_prop(xmlNode * obj, const char *name) } void -save_xml_to_file(xmlNode * xml, const char *desc, const char *filename) +save_xml_to_file(const xmlNode *xml, const char *desc, const char *filename) { char *f = NULL; @@ -1864,7 +1829,7 @@ mark_attr_moved(xmlNode *new_xml, const char *element, xmlAttr *old_attr, old_attr->name, p_old, p_new, element); // Mark document, element, and all element's parents as changed - mark_xml_node_dirty(new_xml); + pcmk__mark_xml_node_dirty(new_xml); // Mark attribute as changed pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_moved); @@ -1886,10 +1851,10 @@ xml_diff_old_attrs(xmlNode *old_xml, xmlNode *new_xml) xmlAttr *attr_iter = pcmk__xe_first_attr(old_xml); while (attr_iter != NULL) { + const char *name = (const char *) attr_iter->name; xmlAttr *old_attr = attr_iter; xmlAttr *new_attr = xmlHasProp(new_xml, attr_iter->name); - const char *name = (const char *) attr_iter->name; - const char *old_value = crm_element_value(old_xml, name); + const char *old_value = pcmk__xml_attr_value(attr_iter); attr_iter = attr_iter->next; if (new_attr == NULL) { @@ -1943,7 +1908,7 @@ mark_created_attrs(xmlNode *new_xml) const char *attr_name = (const char *) new_attr->name; crm_trace("Created new attribute %s=%s in %s", - attr_name, crm_element_value(new_xml, attr_name), + attr_name, pcmk__xml_attr_value(new_attr), new_xml->name); /* Check ACLs (we can't use the remove-then-create trick because it @@ -2017,7 +1982,7 @@ mark_child_moved(xmlNode *old_child, xmlNode *new_parent, xmlNode *new_child, crm_trace("Child element %s with id='%s' moved from position %d to %d under %s", new_child->name, (ID(new_child)? ID(new_child) : "<no id>"), p_old, p_new, new_parent->name); - mark_xml_node_dirty(new_parent); + pcmk__mark_xml_node_dirty(new_parent); pcmk__set_xml_flags(nodepriv, pcmk__xf_moved); if (p_old > p_new) { @@ -2102,9 +2067,10 @@ xml_calculate_significant_changes(xmlNode *old_xml, xmlNode *new_xml) void xml_calculate_changes(xmlNode *old_xml, xmlNode *new_xml) { - CRM_CHECK(pcmk__str_eq(crm_element_name(old_xml), crm_element_name(new_xml), pcmk__str_casei), + CRM_CHECK((old_xml != NULL) && (new_xml != NULL) + && pcmk__xe_is(old_xml, (const char *) new_xml->name) + && pcmk__str_eq(ID(old_xml), ID(new_xml), pcmk__str_none), return); - CRM_CHECK(pcmk__str_eq(ID(old_xml), ID(new_xml), pcmk__str_casei), return); if(xml_tracking_changes(new_xml) == FALSE) { xml_track_changes(new_xml, NULL, NULL, FALSE); @@ -2118,10 +2084,13 @@ can_prune_leaf(xmlNode * xml_node) { xmlNode *cIter = NULL; gboolean can_prune = TRUE; - const char *name = crm_element_name(xml_node); - if (pcmk__strcase_any_of(name, XML_TAG_RESOURCE_REF, XML_CIB_TAG_OBJ_REF, - XML_ACL_TAG_ROLE_REF, XML_ACL_TAG_ROLE_REFv1, NULL)) { + CRM_CHECK(xml_node != NULL, return FALSE); + + if (pcmk__strcase_any_of((const char *) xml_node->name, + XML_TAG_RESOURCE_REF, XML_CIB_TAG_OBJ_REF, + XML_ACL_TAG_ROLE_REF, XML_ACL_TAG_ROLE_REFv1, + NULL)) { return FALSE; } @@ -2257,7 +2226,7 @@ pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update, return; } - object_name = crm_element_name(update); + object_name = (const char *) update->name; object_href_val = ID(update); if (object_href_val != NULL) { object_href = XML_ATTR_ID; @@ -2294,9 +2263,7 @@ pcmk__xml_update(xmlNode *parent, xmlNode *target, xmlNode *update, #endif } - CRM_CHECK(pcmk__str_eq(crm_element_name(target), crm_element_name(update), - pcmk__str_casei), - return); + CRM_CHECK(pcmk__xe_is(target, (const char *) update->name), return); if (as_diff == FALSE) { /* So that expand_plus_plus() gets called */ @@ -2345,7 +2312,7 @@ update_xml_child(xmlNode * child, xmlNode * to_update) CRM_CHECK(child != NULL, return FALSE); CRM_CHECK(to_update != NULL, return FALSE); - if (!pcmk__str_eq(crm_element_name(to_update), crm_element_name(child), pcmk__str_none)) { + if (!pcmk__xe_is(to_update, (const char *) child->name)) { can_update = FALSE; } else if (!pcmk__str_eq(ID(to_update), ID(child), pcmk__str_none)) { @@ -2379,7 +2346,7 @@ find_xml_children(xmlNode ** children, xmlNode * root, CRM_CHECK(root != NULL, return FALSE); CRM_CHECK(children != NULL, return FALSE); - if (tag != NULL && !pcmk__str_eq(tag, crm_element_name(root), pcmk__str_casei)) { + if ((tag != NULL) && !pcmk__xe_is(root, tag)) { } else if (value != NULL && !pcmk__str_eq(value, crm_element_value(root, field), pcmk__str_casei)) { @@ -2422,7 +2389,7 @@ replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update, gboolean if (up_id == NULL || (child_id && strcmp(child_id, up_id) == 0)) { can_delete = TRUE; } - if (!pcmk__str_eq(crm_element_name(update), crm_element_name(child), pcmk__str_casei)) { + if (!pcmk__xe_is(update, (const char *) child->name)) { can_delete = FALSE; } if (can_delete && delete_only) { @@ -2444,23 +2411,23 @@ replace_xml_child(xmlNode * parent, xmlNode * child, xmlNode * update, gboolean free_xml(child); } else { - xmlNode *tmp = copy_xml(update); - xmlDoc *doc = tmp->doc; - xmlNode *old = NULL; + xmlNode *old = child; + xmlNode *new = xmlCopyNode(update, 1); - xml_accept_changes(tmp); - old = xmlReplaceNode(child, tmp); + CRM_ASSERT(new != NULL); - if(xml_tracking_changes(tmp)) { - /* Replaced sections may have included relevant ACLs */ - pcmk__apply_acl(tmp); - } + // May be unnecessary but avoids slight changes to some test outputs + reset_xml_node_flags(new); - xml_calculate_changes(old, tmp); - xmlDocSetRootElement(doc, old); - free_xml(old); + old = xmlReplaceNode(old, new); + + if (xml_tracking_changes(new)) { + // Replaced sections may have included relevant ACLs + pcmk__apply_acl(new); + } + xml_calculate_changes(old, new); + xmlFreeNode(old); } - child = NULL; return TRUE; } else if (can_delete) { @@ -2491,14 +2458,10 @@ sorted_xml(xmlNode *input, xmlNode *parent, gboolean recursive) xmlNode *child = NULL; GSList *nvpairs = NULL; xmlNode *result = NULL; - const char *name = NULL; CRM_CHECK(input != NULL, return NULL); - name = crm_element_name(input); - CRM_CHECK(name != NULL, return NULL); - - result = create_xml_node(parent, name); + result = create_xml_node(parent, (const char *) input->name); nvpairs = pcmk_xml_attrs2nvpairs(input); nvpairs = pcmk_sort_nvpairs(nvpairs); pcmk_nvpairs2xml_attrs(nvpairs, result); @@ -2547,10 +2510,9 @@ xmlNode * crm_next_same_xml(const xmlNode *sibling) { xmlNode *match = pcmk__xe_next(sibling); - const char *name = crm_element_name(sibling); while (match != NULL) { - if (!strcmp(crm_element_name(match), name)) { + if (pcmk__xe_is(match, (const char *) sibling->name)) { return match; } match = pcmk__xe_next(match); @@ -2592,7 +2554,6 @@ crm_xml_cleanup(void) xmlNode * expand_idref(xmlNode * input, xmlNode * top) { - const char *tag = NULL; const char *ref = NULL; xmlNode *result = input; @@ -2603,12 +2564,10 @@ expand_idref(xmlNode * input, xmlNode * top) top = input; } - tag = crm_element_name(result); ref = crm_element_value(result, XML_ATTR_IDREF); - if (ref != NULL) { char *xpath_string = crm_strdup_printf("//%s[@" XML_ATTR_ID "='%s']", - tag, ref); + result->name, ref); result = get_xpath_object(xpath_string, top, LOG_ERR); if (result == NULL) { @@ -2630,7 +2589,7 @@ pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns) char *ret = NULL; if (base == NULL) { - base = getenv("PCMK_schema_directory"); + base = pcmk__env_option(PCMK__ENV_SCHEMA_DIRECTORY); } if (pcmk__str_empty(base)) { base = CRM_SCHEMA_DIRECTORY; @@ -2741,6 +2700,21 @@ crm_destroy_xml(gpointer data) free_xml(data); } +xmlDoc * +getDocPtr(xmlNode *node) +{ + xmlDoc *doc = NULL; + + CRM_CHECK(node != NULL, return NULL); + + doc = node->doc; + if (doc == NULL) { + doc = xmlNewDoc((pcmkXmlStr) "1.0"); + xmlDocSetRootElement(doc, node); + } + return doc; +} + int add_node_nocopy(xmlNode *parent, const char *name, xmlNode *child) { @@ -2749,5 +2723,14 @@ add_node_nocopy(xmlNode *parent, const char *name, xmlNode *child) return 1; } +gboolean +xml_has_children(const xmlNode * xml_root) +{ + if (xml_root != NULL && xml_root->children != NULL) { + return TRUE; + } + return FALSE; +} + // LCOV_EXCL_STOP // End deprecated API diff --git a/lib/common/xml_attr.c b/lib/common/xml_attr.c new file mode 100644 index 0000000..427d267 --- /dev/null +++ b/lib/common/xml_attr.c @@ -0,0 +1,84 @@ +/* + * Copyright 2004-2023 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 <sys/types.h> +#include <unistd.h> +#include <time.h> +#include <string.h> +#include <stdlib.h> +#include <stdarg.h> +#include <bzlib.h> + +#include <libxml/parser.h> +#include <libxml/tree.h> +#include <libxml/xmlIO.h> /* xmlAllocOutputBuffer */ + +#include <crm/crm.h> +#include <crm/msg_xml.h> +#include <crm/common/xml.h> +#include <crm/common/xml_internal.h> // PCMK__XML_LOG_BASE, etc. +#include "crmcommon_private.h" + +void +pcmk__mark_xml_attr_dirty(xmlAttr *a) +{ + xmlNode *parent = a->parent; + xml_node_private_t *nodepriv = a->_private; + + pcmk__set_xml_flags(nodepriv, pcmk__xf_dirty|pcmk__xf_modified); + pcmk__clear_xml_flags(nodepriv, pcmk__xf_deleted); + pcmk__mark_xml_node_dirty(parent); +} + +// This also clears attribute's flags if not marked as deleted +bool +pcmk__marked_as_deleted(xmlAttrPtr a, void *user_data) +{ + xml_node_private_t *nodepriv = a->_private; + + if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) { + return true; + } + nodepriv->flags = pcmk__xf_none; + return false; +} + +/*! + * \internal + * \brief Append an XML attribute to a buffer + * + * \param[in] attr Attribute to append + * \param[in,out] buffer Where to append the content (must not be \p NULL) + */ +void +pcmk__dump_xml_attr(const xmlAttr *attr, GString *buffer) +{ + char *p_value = NULL; + const char *p_name = NULL; + xml_node_private_t *nodepriv = NULL; + + if (attr == NULL || attr->children == NULL) { + return; + } + + nodepriv = attr->_private; + if (nodepriv && pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) { + return; + } + + p_name = (const char *) attr->name; + p_value = crm_xml_escape((const char *)attr->children->content); + pcmk__g_strcat(buffer, " ", p_name, "=\"", pcmk__s(p_value, "<null>"), "\"", + NULL); + + free(p_value); +}
\ No newline at end of file diff --git a/lib/common/xml_display.c b/lib/common/xml_display.c index e2d46ce..18cd3b9 100644 --- a/lib/common/xml_display.c +++ b/lib/common/xml_display.c @@ -92,7 +92,6 @@ static int show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix, const xmlNode *data, int depth, uint32_t options) { - const char *name = crm_element_name(data); int spaces = pcmk_is_set(options, pcmk__xml_fmt_pretty)? (2 * depth) : 0; int rc = pcmk_rc_no_output; @@ -104,7 +103,7 @@ show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix, for (int lpc = 0; lpc < spaces; lpc++) { g_string_append_c(buffer, ' '); } - pcmk__g_strcat(buffer, "<", name, NULL); + pcmk__g_strcat(buffer, "<", data->name, NULL); for (const xmlAttr *attr = pcmk__xe_first_attr(data); attr != NULL; attr = attr->next) { @@ -138,7 +137,7 @@ show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix, free(p_copy); } - if (xml_has_children(data) + if ((data->children != NULL) && pcmk_is_set(options, pcmk__xml_fmt_children)) { g_string_append_c(buffer, '>'); @@ -151,7 +150,7 @@ show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix, buffer->str); } - if (!xml_has_children(data)) { + if (data->children == NULL) { return rc; } @@ -171,7 +170,7 @@ show_xml_element(pcmk__output_t *out, GString *buffer, const char *prefix, int temp_rc = out->info(out, "%s%s%*s</%s>", pcmk__s(prefix, ""), pcmk__str_empty(prefix)? "" : " ", - spaces, "", name); + spaces, "", data->name); rc = pcmk__output_select_rc(rc, temp_rc); } @@ -304,14 +303,14 @@ show_xml_changes_recursive(pcmk__output_t *out, const xmlNode *data, int depth, nodepriv = attr->_private; if (pcmk_is_set(nodepriv->flags, pcmk__xf_deleted)) { - const char *value = crm_element_value(data, name); + const char *value = pcmk__xml_attr_value(attr); temp_rc = out->info(out, "%s %*s @%s=%s", PCMK__XML_PREFIX_DELETED, spaces, "", name, value); } else if (pcmk_is_set(nodepriv->flags, pcmk__xf_dirty)) { - const char *value = crm_element_value(data, name); + const char *value = pcmk__xml_attr_value(attr); if (pcmk_is_set(nodepriv->flags, pcmk__xf_created)) { prefix = PCMK__XML_PREFIX_CREATED; @@ -447,9 +446,6 @@ log_data_element(int log_level, const char *file, const char *function, if (pcmk_is_set(legacy_options, xml_log_option_formatted)) { options |= pcmk__xml_fmt_pretty; } - if (pcmk_is_set(legacy_options, xml_log_option_full_fledged)) { - options |= pcmk__xml_fmt_full; - } if (pcmk_is_set(legacy_options, xml_log_option_open)) { options |= pcmk__xml_fmt_open; } @@ -480,7 +476,7 @@ log_data_element(int log_level, const char *file, const char *function, } if (pcmk_is_set(options, pcmk__xml_fmt_pretty) - && (!xml_has_children(data) + && ((data->children == NULL) || (crm_element_value(data, XML_DIFF_MARKER) != NULL))) { if (pcmk_is_set(options, pcmk__xml_fmt_diff_plus)) { diff --git a/lib/common/xpath.c b/lib/common/xpath.c index 1f5c0a8..d90f1c5 100644 --- a/lib/common/xpath.c +++ b/lib/common/xpath.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2022 the Pacemaker project contributors + * Copyright 2004-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -136,9 +136,8 @@ dedupXpathResults(xmlXPathObjectPtr xpathObj) /* the caller needs to check if the result contains a xmlDocPtr or xmlNodePtr */ xmlXPathObjectPtr -xpath_search(xmlNode * xml_top, const char *path) +xpath_search(const xmlNode *xml_top, const char *path) { - xmlDocPtr doc = NULL; xmlXPathObjectPtr xpathObj = NULL; xmlXPathContextPtr xpathCtx = NULL; const xmlChar *xpathExpr = (pcmkXmlStr) path; @@ -147,9 +146,7 @@ xpath_search(xmlNode * xml_top, const char *path) CRM_CHECK(xml_top != NULL, return NULL); CRM_CHECK(strlen(path) > 0, return NULL); - doc = getDocPtr(xml_top); - - xpathCtx = xmlXPathNewContext(doc); + xpathCtx = xmlXPathNewContext(xml_top->doc); CRM_ASSERT(xpathCtx != NULL); xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx); @@ -298,9 +295,9 @@ pcmk__element_xpath(const xmlNode *xml) if (parent == NULL) { g_string_append_c(xpath, '/'); } else if (parent->parent == NULL) { - g_string_append(xpath, TYPE(xml)); + g_string_append(xpath, (const gchar *) xml->name); } else { - pcmk__g_strcat(xpath, "/", TYPE(xml), NULL); + pcmk__g_strcat(xpath, "/", (const char *) xml->name, NULL); } id = ID(xml); |