diff --git a/001-schema-glib.patch b/001-schema-glib.patch deleted file mode 100644 index c38d1d8080f0d91cfa5acbeeba39f1b7d00beac5..0000000000000000000000000000000000000000 --- a/001-schema-glib.patch +++ /dev/null @@ -1,2334 +0,0 @@ -From a59d703de97a49a27564f572dac52b455b356ba9 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Wed, 25 Oct 2023 12:14:57 -0400 -Subject: [PATCH 01/20] Refactor: libcrmcommon: Remove prototypes for internal - functions. - -These are only here to give a place to put the G_GNUC_PRINTF attribute, -but that can go in the function definition itself. ---- - lib/common/schemas.c | 12 ++---------- - 1 file changed, 2 insertions(+), 10 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index b3c09eb..a85438c 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -64,11 +64,7 @@ static struct schema_s *known_schemas = NULL; - static int xml_schema_max = 0; - static bool silent_logging = FALSE; - --static void --xml_log(int priority, const char *fmt, ...) --G_GNUC_PRINTF(2, 3); -- --static void -+static void G_GNUC_PRINTF(2, 3) - xml_log(int priority, const char *fmt, ...) - { - va_list ap; -@@ -716,10 +712,6 @@ pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidity - return FALSE; - } - --static void --cib_upgrade_err(void *ctx, const char *fmt, ...) --G_GNUC_PRINTF(2, 3); -- - /* With this arrangement, an attempt to identify the message severity - as explicitly signalled directly from XSLT is performed in rather - a smart way (no reliance on formatting string + arguments being -@@ -743,7 +735,7 @@ G_GNUC_PRINTF(2, 3); - (suspicious, likely internal errors or some runaways) is - LOG_WARNING. - */ --static void -+static void G_GNUC_PRINTF(2, 3) - cib_upgrade_err(void *ctx, const char *fmt, ...) - { - va_list ap, aq; --- -2.31.1 - -From 3d50aeebce74e520606036ec7db8b5c70fe327b5 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Wed, 25 Oct 2023 12:54:37 -0400 -Subject: [PATCH 02/20] Refactor: libcrmcommon: validate_with should take a - schema as argument. - -...instead of taking an index, and then finding that in the -known_schemas array. ---- - lib/common/schemas.c | 25 ++++++++++++------------- - 1 file changed, 12 insertions(+), 13 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index a85438c..f1f86f4 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -555,18 +555,16 @@ crm_schema_cleanup(void) - } - - static gboolean --validate_with(xmlNode *xml, int method, xmlRelaxNGValidityErrorFunc error_handler, void* error_handler_context) -+validate_with(xmlNode *xml, struct schema_s *schema, xmlRelaxNGValidityErrorFunc error_handler, void* error_handler_context) - { - gboolean valid = FALSE; - char *file = NULL; -- struct schema_s *schema = NULL; - relaxng_ctx_cache_t **cache = NULL; - -- if (method < 0) { -+ if (schema == NULL) { - return FALSE; - } - -- schema = &(known_schemas[method]); - if (schema->validator == schema_validator_none) { - return TRUE; - } -@@ -587,8 +585,7 @@ validate_with(xmlNode *xml, int method, xmlRelaxNGValidityErrorFunc error_handle - valid = validate_with_relaxng(xml->doc, error_handler, error_handler_context, file, cache); - break; - default: -- crm_err("Unknown validator type: %d", -- known_schemas[method].validator); -+ crm_err("Unknown validator type: %d", schema->validator); - break; - } - -@@ -597,11 +594,11 @@ validate_with(xmlNode *xml, int method, xmlRelaxNGValidityErrorFunc error_handle - } - - static bool --validate_with_silent(xmlNode *xml, int method) -+validate_with_silent(xmlNode *xml, struct schema_s *schema) - { - bool rc, sl_backup = silent_logging; - silent_logging = TRUE; -- rc = validate_with(xml, method, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR)); -+ rc = validate_with(xml, schema, (xmlRelaxNGValidityErrorFunc) xml_log, GUINT_TO_POINTER(LOG_ERR)); - silent_logging = sl_backup; - return rc; - } -@@ -687,7 +684,7 @@ pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidity - bool valid = FALSE; - - for (lpc = 0; lpc < xml_schema_max; lpc++) { -- if (validate_with(xml_blob, lpc, NULL, NULL)) { -+ if (validate_with(xml_blob, &known_schemas[lpc], NULL, NULL)) { - valid = TRUE; - crm_xml_add(xml_blob, XML_ATTR_VALIDATION, - known_schemas[lpc].name); -@@ -705,7 +702,8 @@ pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidity - if (strcmp(validation, PCMK__VALUE_NONE) == 0) { - return TRUE; - } else if (version < xml_schema_max) { -- return validate_with(xml_blob, version, error_handler, error_handler_context); -+ return validate_with(xml_blob, version >= 0 ? &known_schemas[version] : NULL, -+ error_handler, error_handler_context); - } - - crm_err("Unknown validator: %s", validation); -@@ -1019,7 +1017,7 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - known_schemas[lpc].name ? known_schemas[lpc].name : "", - lpc, max_stable_schemas); - -- if (validate_with(xml, lpc, error_handler, GUINT_TO_POINTER(LOG_ERR)) == FALSE) { -+ if (validate_with(xml, &known_schemas[lpc], error_handler, GUINT_TO_POINTER(LOG_ERR)) == FALSE) { - if (next != -1) { - crm_info("Configuration not valid for schema: %s", - known_schemas[lpc].name); -@@ -1067,7 +1065,7 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - version boundary, as X.0 "transitional" version is - expected to be more strict than it's successors that - may re-allow constructs from previous major line) */ -- || validate_with_silent(xml, next)) { -+ || validate_with_silent(xml, next >= 0 ? &known_schemas[next] : NULL)) { - crm_debug("%s-style configuration is also valid for %s", - known_schemas[lpc].name, known_schemas[next].name); - -@@ -1084,7 +1082,8 @@ 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, error_handler, GUINT_TO_POINTER(LOG_ERR))) { -+ } else if (validate_with(upgrade, next >= 0 ? &known_schemas[next] : NULL, -+ error_handler, GUINT_TO_POINTER(LOG_ERR))) { - crm_info("Transformation %s.xsl successful", - known_schemas[lpc].transform); - lpc = next; --- -2.31.1 - -From 26a17af650a842659f57c9e58185c290c30a3fb3 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Wed, 25 Oct 2023 14:33:11 -0400 -Subject: [PATCH 03/20] Refactor: libcrmcommon: Break schema freeing out into a - function. - ---- - lib/common/schemas.c | 69 +++++++++++++++++++++++++------------------- - 1 file changed, 40 insertions(+), 29 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index f1f86f4..c21b9ae 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -511,6 +511,43 @@ validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler, - return valid; - } - -+static void -+free_schema(struct schema_s *schema) -+{ -+ relaxng_ctx_cache_t *ctx = NULL; -+ -+ switch (schema->validator) { -+ case schema_validator_none: // not cached -+ break; -+ -+ case schema_validator_rng: // cached -+ ctx = (relaxng_ctx_cache_t *) schema->cache; -+ if (ctx == NULL) { -+ break; -+ } -+ -+ if (ctx->parser != NULL) { -+ xmlRelaxNGFreeParserCtxt(ctx->parser); -+ } -+ -+ if (ctx->valid != NULL) { -+ xmlRelaxNGFreeValidCtxt(ctx->valid); -+ } -+ -+ if (ctx->rng != NULL) { -+ xmlRelaxNGFree(ctx->rng); -+ } -+ -+ free(ctx); -+ schema->cache = NULL; -+ break; -+ } -+ -+ free(schema->name); -+ free(schema->transform); -+ free(schema->transform_enter); -+} -+ - /*! - * \internal - * \brief Clean up global memory associated with XML schemas -@@ -518,36 +555,10 @@ validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler, - void - crm_schema_cleanup(void) - { -- int lpc; -- relaxng_ctx_cache_t *ctx = NULL; -- -- for (lpc = 0; lpc < xml_schema_max; lpc++) { -- -- switch (known_schemas[lpc].validator) { -- case schema_validator_none: // not cached -- break; -- case schema_validator_rng: // cached -- ctx = (relaxng_ctx_cache_t *) known_schemas[lpc].cache; -- if (ctx == NULL) { -- break; -- } -- if (ctx->parser != NULL) { -- xmlRelaxNGFreeParserCtxt(ctx->parser); -- } -- if (ctx->valid != NULL) { -- xmlRelaxNGFreeValidCtxt(ctx->valid); -- } -- if (ctx->rng != NULL) { -- xmlRelaxNGFree(ctx->rng); -- } -- free(ctx); -- known_schemas[lpc].cache = NULL; -- break; -- } -- free(known_schemas[lpc].name); -- free(known_schemas[lpc].transform); -- free(known_schemas[lpc].transform_enter); -+ for (int lpc = 0; lpc < xml_schema_max; lpc++) { -+ free_schema(&known_schemas[lpc]); - } -+ - free(known_schemas); - known_schemas = NULL; - --- -2.31.1 - -From fdf66811c23d93715fcd34e16eb58fce5f4294d7 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Wed, 25 Oct 2023 14:36:07 -0400 -Subject: [PATCH 04/20] Refactor: libcrmcommon: Clean up add_schema a bit. - -* Use true/false instead of TRUE/FALSE. - -* Call CRM_ASSERT after all the strdups. - -* There's no need to have a for loop over a two element list. If the - version number struct ever changes, plenty of other places will have - to be changed as well so this isn't saving us much. ---- - lib/common/schemas.c | 20 +++++++++++++------- - 1 file changed, 13 insertions(+), 7 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index c21b9ae..17cd21f 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -178,7 +178,7 @@ add_schema(enum schema_validator_e validator, const schema_version_t *version, - int after_transform) - { - int last = xml_schema_max; -- bool have_version = FALSE; -+ bool have_version = false; - - xml_schema_max++; - known_schemas = pcmk__realloc(known_schemas, -@@ -188,26 +188,32 @@ add_schema(enum schema_validator_e validator, const schema_version_t *version, - known_schemas[last].validator = validator; - known_schemas[last].after_transform = after_transform; - -- for (int i = 0; i < 2; ++i) { -- known_schemas[last].version.v[i] = version->v[i]; -- if (version->v[i]) { -- have_version = TRUE; -- } -+ known_schemas[last].version.v[0] = version->v[0]; -+ known_schemas[last].version.v[1] = version->v[1]; -+ -+ if (version->v[0] || version->v[1]) { -+ have_version = true; - } -+ - if (have_version) { - known_schemas[last].name = schema_strdup_printf("pacemaker-", *version, ""); - } else { -- CRM_ASSERT(name); -+ CRM_ASSERT(name != NULL); - schema_scanf(name, "%*[^-]-", known_schemas[last].version, ""); - known_schemas[last].name = strdup(name); -+ CRM_ASSERT(known_schemas[last].name != NULL); - } - - if (transform) { - known_schemas[last].transform = strdup(transform); -+ CRM_ASSERT(known_schemas[last].transform != NULL); - } -+ - if (transform_enter) { - known_schemas[last].transform_enter = strdup(transform_enter); -+ CRM_ASSERT(known_schemas[last].transform_enter != NULL); - } -+ - known_schemas[last].transform_onleave = transform_onleave; - if (after_transform == 0) { - after_transform = xml_schema_max; /* upgrade is a one-way */ --- -2.31.1 - -From ef89e0536fae09036a657cf651da1eed75356054 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Wed, 25 Oct 2023 15:16:11 -0400 -Subject: [PATCH 05/20] Refactor: libcrmcommon: Use pcmk__s in schemas.c where - possible. - ---- - lib/common/schemas.c | 8 ++++---- - 1 file changed, 4 insertions(+), 4 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index 17cd21f..fca81e4 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -1031,7 +1031,7 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - - while (lpc <= max_stable_schemas) { - crm_debug("Testing '%s' validation (%d of %d)", -- known_schemas[lpc].name ? known_schemas[lpc].name : "", -+ pcmk__s(known_schemas[lpc].name, ""), - lpc, max_stable_schemas); - - if (validate_with(xml, &known_schemas[lpc], error_handler, GUINT_TO_POINTER(LOG_ERR)) == FALSE) { -@@ -1041,7 +1041,7 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - next = -1; - } else { - crm_trace("%s validation failed", -- known_schemas[lpc].name ? known_schemas[lpc].name : ""); -+ pcmk__s(known_schemas[lpc].name, "")); - } - if (*best) { - /* we've satisfied the validation, no need to check further */ -@@ -1128,8 +1128,8 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - - if (*best > match && *best) { - crm_info("%s the configuration from %s to %s", -- transform?"Transformed":"Upgraded", -- value ? value : "", known_schemas[*best].name); -+ transform?"Transformed":"Upgraded", pcmk__s(value, ""), -+ known_schemas[*best].name); - crm_xml_add(xml, XML_ATTR_VALIDATION, known_schemas[*best].name); - } - --- -2.31.1 - -From 574c3c1f5ca00514eff77b927821b695c980a683 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Wed, 25 Oct 2023 15:20:04 -0400 -Subject: [PATCH 06/20] Refactor: libcrmcommon: Use a schema variable in - update_validation. - -This just gets rid of a ton of references to the known_schemas array, -making it easier to replace that array with something else in a future -commit. ---- - lib/common/schemas.c | 38 +++++++++++++++++--------------------- - 1 file changed, 17 insertions(+), 21 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index fca81e4..888a473 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -1030,18 +1030,18 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - } - - while (lpc <= max_stable_schemas) { -+ struct schema_s *schema = &known_schemas[lpc]; -+ - crm_debug("Testing '%s' validation (%d of %d)", -- pcmk__s(known_schemas[lpc].name, ""), -- lpc, max_stable_schemas); -+ pcmk__s(schema->name, ""), lpc, max_stable_schemas); - -- if (validate_with(xml, &known_schemas[lpc], error_handler, GUINT_TO_POINTER(LOG_ERR)) == FALSE) { -+ if (validate_with(xml, schema, error_handler, GUINT_TO_POINTER(LOG_ERR)) == FALSE) { - if (next != -1) { - crm_info("Configuration not valid for schema: %s", -- known_schemas[lpc].name); -+ schema->name); - next = -1; - } else { -- crm_trace("%s validation failed", -- pcmk__s(known_schemas[lpc].name, "")); -+ crm_trace("%s validation failed", pcmk__s(schema->name, "")); - } - if (*best) { - /* we've satisfied the validation, no need to check further */ -@@ -1051,8 +1051,7 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - - } else { - if (next != -1) { -- crm_debug("Configuration valid for schema: %s", -- known_schemas[next].name); -+ crm_debug("Configuration valid for schema: %s", schema->name); - next = -1; - } - rc = pcmk_ok; -@@ -1064,19 +1063,19 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - - if (rc == pcmk_ok && transform) { - xmlNode *upgrade = NULL; -- next = known_schemas[lpc].after_transform; -+ next = schema->after_transform; - - if (next <= lpc) { - /* There is no next version, or next would regress */ -- crm_trace("Stopping at %s", known_schemas[lpc].name); -+ crm_trace("Stopping at %s", schema->name); - break; - - } else if (max > 0 && (lpc == max || next > max)) { - crm_trace("Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)", -- known_schemas[lpc].name, lpc, next, max); -+ schema->name, lpc, next, max); - break; - -- } else if (known_schemas[lpc].transform == NULL -+ } else if (schema->transform == NULL - /* possibly avoid transforming when readily valid - (in general more restricted when crossing the major - version boundary, as X.0 "transitional" version is -@@ -1084,25 +1083,22 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - may re-allow constructs from previous major line) */ - || validate_with_silent(xml, next >= 0 ? &known_schemas[next] : NULL)) { - crm_debug("%s-style configuration is also valid for %s", -- known_schemas[lpc].name, known_schemas[next].name); -+ schema->name, known_schemas[next].name); - - lpc = next; - - } else { - crm_debug("Upgrading %s-style configuration to %s with %s.xsl", -- known_schemas[lpc].name, known_schemas[next].name, -- known_schemas[lpc].transform); -+ schema->name, known_schemas[next].name, schema->transform); - -- upgrade = apply_upgrade(xml, &known_schemas[lpc], to_logs); -+ upgrade = apply_upgrade(xml, schema, to_logs); - if (upgrade == NULL) { -- crm_err("Transformation %s.xsl failed", -- known_schemas[lpc].transform); -+ crm_err("Transformation %s.xsl failed", schema->transform); - rc = -pcmk_err_transform_failed; - - } else if (validate_with(upgrade, next >= 0 ? &known_schemas[next] : NULL, - error_handler, GUINT_TO_POINTER(LOG_ERR))) { -- crm_info("Transformation %s.xsl successful", -- known_schemas[lpc].transform); -+ crm_info("Transformation %s.xsl successful", schema->transform); - lpc = next; - *best = next; - free_xml(xml); -@@ -1111,7 +1107,7 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - - } else { - crm_err("Transformation %s.xsl did not produce a valid configuration", -- known_schemas[lpc].transform); -+ schema->transform); - crm_log_xml_info(upgrade, "transform:bad"); - free_xml(upgrade); - rc = -pcmk_err_schema_validation; --- -2.31.1 - -From dc724c940014fbe60aa506d8acb652b2dd5dce90 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Wed, 25 Oct 2023 15:31:27 -0400 -Subject: [PATCH 07/20] Refactor: libcrmcommon: Use a variable for the next - schema, too. - -This gets rid of further references to known_schemas update_validation, -also with the purpose of making it easier to change the implementation -of that array. ---- - lib/common/schemas.c | 20 +++++++++++++------- - 1 file changed, 13 insertions(+), 7 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index 888a473..8e5c22e 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -1063,41 +1063,47 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - - if (rc == pcmk_ok && transform) { - xmlNode *upgrade = NULL; -+ struct schema_s *next_schema = NULL; - next = schema->after_transform; - - if (next <= lpc) { - /* There is no next version, or next would regress */ - crm_trace("Stopping at %s", schema->name); - break; -+ } - -- } else if (max > 0 && (lpc == max || next > max)) { -+ if (max > 0 && (lpc == max || next > max)) { - crm_trace("Upgrade limit reached at %s (lpc=%d, next=%d, max=%d)", - schema->name, lpc, next, max); - break; -+ } -+ -+ next_schema = &known_schemas[next]; -+ CRM_ASSERT(next_schema != NULL); - -- } else if (schema->transform == NULL -+ if (schema->transform == NULL - /* possibly avoid transforming when readily valid - (in general more restricted when crossing the major - version boundary, as X.0 "transitional" version is - expected to be more strict than it's successors that - may re-allow constructs from previous major line) */ -- || validate_with_silent(xml, next >= 0 ? &known_schemas[next] : NULL)) { -+ || validate_with_silent(xml, next_schema)) { - crm_debug("%s-style configuration is also valid for %s", -- schema->name, known_schemas[next].name); -+ schema->name, next_schema->name); - - lpc = next; - - } else { - crm_debug("Upgrading %s-style configuration to %s with %s.xsl", -- schema->name, known_schemas[next].name, schema->transform); -+ schema->name, next_schema->name, schema->transform); - - upgrade = apply_upgrade(xml, schema, to_logs); - if (upgrade == NULL) { - crm_err("Transformation %s.xsl failed", schema->transform); - rc = -pcmk_err_transform_failed; - -- } else if (validate_with(upgrade, next >= 0 ? &known_schemas[next] : NULL, -- error_handler, GUINT_TO_POINTER(LOG_ERR))) { -+ } else if (validate_with(upgrade, next_schema, error_handler, -+ GUINT_TO_POINTER(LOG_ERR))) { - crm_info("Transformation %s.xsl successful", schema->transform); - lpc = next; - *best = next; --- -2.31.1 - -From 0664f7e327c612d5602515ecf4bb32fb7c3503f6 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Wed, 25 Oct 2023 16:09:30 -0400 -Subject: [PATCH 08/20] Refactor: libcrmcommon: Add pcmk__dump_known_schemas. - -The debug logging in add_schema isn't necessarily all that useful. -Typically, schema adding happens in crm_log_preinit which means it -happens before logging is set up, so nothing that we log actually goes -anywhere. - -This function does the same thing but can be called where needed. ---- - include/crm/common/xml_internal.h | 3 +++ - lib/common/schemas.c | 21 +++++++++++++++++++++ - 2 files changed, 24 insertions(+) - -diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h -index ddb4384..f319856 100644 ---- a/include/crm/common/xml_internal.h -+++ b/include/crm/common/xml_internal.h -@@ -441,8 +441,11 @@ pcmk__xml_attr_value(const xmlAttr *attr) - : (const char *) attr->children->content; - } - -+ - gboolean pcmk__validate_xml(xmlNode *xml_blob, const char *validation, - xmlRelaxNGValidityErrorFunc error_handler, - void *error_handler_context); - -+void pcmk__log_known_schemas(void); -+ - #endif // PCMK__XML_INTERNAL__H -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index 8e5c22e..41ca138 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -1248,3 +1248,24 @@ cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs) - free(orig_value); - return rc; - } -+ -+void -+pcmk__log_known_schemas(void) -+{ -+ for (int lpc = 0; lpc < xml_schema_max; lpc++) { -+ if (known_schemas[lpc].after_transform < 0) { -+ crm_debug("known_schemas[%d] => %s", lpc, known_schemas[lpc].name); -+ -+ } else if (known_schemas[lpc].transform != NULL) { -+ crm_debug("known_schemas[%d] => %s (upgrades to %d with %s.xsl)", -+ lpc, known_schemas[lpc].name, -+ known_schemas[lpc].after_transform, -+ known_schemas[lpc].transform); -+ -+ } else { -+ crm_debug("known_schemas[%d] => %s (upgrades to %d)", -+ lpc, known_schemas[lpc].name, -+ known_schemas[lpc].after_transform); -+ } -+ } -+} --- -2.31.1 - -From 423a28fd5c2b71945d75b68b168a607279d795f7 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Wed, 25 Oct 2023 17:22:53 -0400 -Subject: [PATCH 09/20] Refactor: libcrmcommon: Store known_schemas as a GList. - -Instead of managing our own array with realloc, use GList and the -various glib list functions. - -In many places, this makes the code easier to follow - we can simply -iterate over the list and do something on each node. In other places, -we're still relying on list indices too much to help. Those spots can -probably be cleaned up in future commits. ---- - lib/common/schemas.c | 181 ++++++++++++++++++++++++++----------------- - 1 file changed, 108 insertions(+), 73 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index 41ca138..6e6f32e 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -60,7 +60,7 @@ struct schema_s { - bool transform_onleave; - }; - --static struct schema_s *known_schemas = NULL; -+static GList *known_schemas = NULL; - static int xml_schema_max = 0; - static bool silent_logging = FALSE; - -@@ -81,27 +81,45 @@ static int - xml_latest_schema_index(void) - { - // @COMPAT: pacemaker-next is deprecated since 2.1.5 -- return xml_schema_max - 3; // index from 0, ignore "pacemaker-next"/"none" -+ // FIXME: This function assumes at least three schemas have been added -+ // before it has been called for the first time. -+ return g_list_length(known_schemas) - 3; // index from 0, ignore "pacemaker-next"/"none" - } - - static int - xml_minimum_schema_index(void) - { - static int best = 0; -- if (best == 0) { -- int lpc = 0; -- -- best = xml_latest_schema_index(); -- for (lpc = best; lpc > 0; lpc--) { -- if (known_schemas[lpc].version.v[0] -- < known_schemas[best].version.v[0]) { -- return best; -- } else { -- best = lpc; -- } -+ struct schema_s *best_schema = NULL; -+ GList *last_real_ele = NULL; -+ -+ if (best != 0) { -+ return best; -+ } -+ -+ best = xml_latest_schema_index(); -+ -+ /* We can't just use g_list_last here because "pacemaker-next" and "none" -+ * are stored at the end of the list. We need to start several elements -+ * back, at the last real schema. -+ */ -+ last_real_ele = g_list_nth(known_schemas, best); -+ best_schema = last_real_ele->data; -+ -+ for (GList *iter = last_real_ele; iter != NULL; iter = iter->prev) { -+ struct schema_s *schema = iter->data; -+ -+ if (schema->version.v[0] < best_schema->version.v[0]) { -+ return best; -+ } else { -+ best--; - } -- best = xml_latest_schema_index(); - } -+ -+ /* If we never found a schema that meets the above criteria, default to -+ * the last one. -+ */ -+ best = xml_latest_schema_index(); - return best; - } - -@@ -177,63 +195,61 @@ add_schema(enum schema_validator_e validator, const schema_version_t *version, - const char *transform_enter, bool transform_onleave, - int after_transform) - { -+ struct schema_s *schema = NULL; - int last = xml_schema_max; - bool have_version = false; - - xml_schema_max++; -- known_schemas = pcmk__realloc(known_schemas, -- xml_schema_max * sizeof(struct schema_s)); -- CRM_ASSERT(known_schemas != NULL); -- memset(known_schemas+last, 0, sizeof(struct schema_s)); -- known_schemas[last].validator = validator; -- known_schemas[last].after_transform = after_transform; - -- known_schemas[last].version.v[0] = version->v[0]; -- known_schemas[last].version.v[1] = version->v[1]; -+ schema = calloc(1, sizeof(struct schema_s)); -+ CRM_ASSERT(schema != NULL); -+ -+ schema->validator = validator; -+ schema->after_transform = after_transform; -+ schema->version.v[0] = version->v[0]; -+ schema->version.v[1] = version->v[1]; - - if (version->v[0] || version->v[1]) { - have_version = true; - } - - if (have_version) { -- known_schemas[last].name = schema_strdup_printf("pacemaker-", *version, ""); -+ schema->name = schema_strdup_printf("pacemaker-", *version, ""); - } else { - CRM_ASSERT(name != NULL); -- schema_scanf(name, "%*[^-]-", known_schemas[last].version, ""); -- known_schemas[last].name = strdup(name); -- CRM_ASSERT(known_schemas[last].name != NULL); -+ schema_scanf(name, "%*[^-]-", schema->version, ""); -+ schema->name = strdup(name); -+ CRM_ASSERT(schema->name != NULL); - } - - if (transform) { -- known_schemas[last].transform = strdup(transform); -- CRM_ASSERT(known_schemas[last].transform != NULL); -+ schema->transform = strdup(transform); -+ CRM_ASSERT(schema->transform != NULL); - } - - if (transform_enter) { -- known_schemas[last].transform_enter = strdup(transform_enter); -- CRM_ASSERT(known_schemas[last].transform_enter != NULL); -+ schema->transform_enter = strdup(transform_enter); -+ CRM_ASSERT(schema->transform_enter != NULL); - } - -- known_schemas[last].transform_onleave = transform_onleave; -+ schema->transform_onleave = transform_onleave; - if (after_transform == 0) { - after_transform = xml_schema_max; /* upgrade is a one-way */ - } -- known_schemas[last].after_transform = after_transform; -+ schema->after_transform = after_transform; - -- if (known_schemas[last].after_transform < 0) { -- crm_debug("Added supported schema %d: %s", -- last, known_schemas[last].name); -+ known_schemas = g_list_append(known_schemas, schema); - -- } else if (known_schemas[last].transform) { -+ if (schema->after_transform < 0) { -+ crm_debug("Added supported schema %d: %s", last, schema->name); -+ -+ } else if (schema->transform != NULL) { - crm_debug("Added supported schema %d: %s (upgrades to %d with %s.xsl)", -- last, known_schemas[last].name, -- known_schemas[last].after_transform, -- known_schemas[last].transform); -+ last, schema->name, schema->after_transform, schema->transform); - - } else { - crm_debug("Added supported schema %d: %s (upgrades to %d)", -- last, known_schemas[last].name, -- known_schemas[last].after_transform); -+ last, schema->name, schema->after_transform); - } - } - -@@ -518,8 +534,9 @@ validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler, - } - - static void --free_schema(struct schema_s *schema) -+free_schema(gpointer data) - { -+ struct schema_s *schema = data; - relaxng_ctx_cache_t *ctx = NULL; - - switch (schema->validator) { -@@ -561,11 +578,7 @@ free_schema(struct schema_s *schema) - void - crm_schema_cleanup(void) - { -- for (int lpc = 0; lpc < xml_schema_max; lpc++) { -- free_schema(&known_schemas[lpc]); -- } -- -- free(known_schemas); -+ g_list_free_full(known_schemas, free_schema); - known_schemas = NULL; - - wrap_libxslt(true); -@@ -697,16 +710,17 @@ pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidity - } - - if (validation == NULL) { -- int lpc = 0; - bool valid = FALSE; - -- for (lpc = 0; lpc < xml_schema_max; lpc++) { -- if (validate_with(xml_blob, &known_schemas[lpc], NULL, NULL)) { -+ for (GList *iter = known_schemas; iter != NULL; iter = iter->next) { -+ struct schema_s *schema = iter->data; -+ -+ if (validate_with(xml_blob, schema, NULL, NULL)) { - valid = TRUE; -- crm_xml_add(xml_blob, XML_ATTR_VALIDATION, -- known_schemas[lpc].name); -- crm_info("XML validated against %s", known_schemas[lpc].name); -- if(known_schemas[lpc].after_transform == 0) { -+ crm_xml_add(xml_blob, XML_ATTR_VALIDATION, schema->name); -+ crm_info("XML validated against %s", schema->name); -+ -+ if (schema->after_transform == 0) { - break; - } - } -@@ -719,8 +733,9 @@ pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidity - if (strcmp(validation, PCMK__VALUE_NONE) == 0) { - return TRUE; - } else if (version < xml_schema_max) { -- return validate_with(xml_blob, version >= 0 ? &known_schemas[version] : NULL, -- error_handler, error_handler_context); -+ struct schema_s *schema = g_list_nth_data(known_schemas, version); -+ return validate_with(xml_blob, schema, error_handler, -+ error_handler_context); - } - - crm_err("Unknown validator: %s", validation); -@@ -964,10 +979,13 @@ apply_upgrade(xmlNode *xml, const struct schema_s *schema, gboolean to_logs) - const char * - get_schema_name(int version) - { -- if (version < 0 || version >= xml_schema_max) { -+ struct schema_s *schema = g_list_nth_data(known_schemas, version); -+ -+ if (schema == NULL) { - return "unknown"; - } -- return known_schemas[version].name; -+ -+ return schema->name; - } - - int -@@ -978,11 +996,17 @@ get_schema_version(const char *name) - if (name == NULL) { - name = PCMK__VALUE_NONE; - } -- for (; lpc < xml_schema_max; lpc++) { -- if (pcmk__str_eq(name, known_schemas[lpc].name, pcmk__str_casei)) { -+ -+ for (GList *iter = known_schemas; iter != NULL; iter = iter->next) { -+ struct schema_s *schema = iter->data; -+ -+ if (pcmk__str_eq(name, schema->name, pcmk__str_casei)) { - return lpc; - } -+ -+ lpc++; - } -+ - return -1; - } - -@@ -1030,7 +1054,12 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - } - - while (lpc <= max_stable_schemas) { -- struct schema_s *schema = &known_schemas[lpc]; -+ /* FIXME: This will cause us to walk the known_schemas list every time -+ * this loop iterates, which is not ideal. However, for now it's a lot -+ * easier than trying to get all the loop indices we're using here -+ * sorted out and working correctly. -+ */ -+ struct schema_s *schema = g_list_nth_data(known_schemas, lpc); - - crm_debug("Testing '%s' validation (%d of %d)", - pcmk__s(schema->name, ""), lpc, max_stable_schemas); -@@ -1078,7 +1107,7 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - break; - } - -- next_schema = &known_schemas[next]; -+ next_schema = g_list_nth_data(known_schemas, next); - CRM_ASSERT(next_schema != NULL); - - if (schema->transform == NULL -@@ -1129,10 +1158,12 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - } - - if (*best > match && *best) { -+ struct schema_s *best_schema = g_list_nth_data(known_schemas, *best); -+ - crm_info("%s the configuration from %s to %s", - transform?"Transformed":"Upgraded", pcmk__s(value, ""), -- known_schemas[*best].name); -- crm_xml_add(xml, XML_ATTR_VALIDATION, known_schemas[*best].name); -+ best_schema->name); -+ crm_xml_add(xml, XML_ATTR_VALIDATION, best_schema->name); - } - - *xml_blob = xml; -@@ -1252,20 +1283,24 @@ cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs) - void - pcmk__log_known_schemas(void) - { -- for (int lpc = 0; lpc < xml_schema_max; lpc++) { -- if (known_schemas[lpc].after_transform < 0) { -- crm_debug("known_schemas[%d] => %s", lpc, known_schemas[lpc].name); -+ int lpc = 0; - -- } else if (known_schemas[lpc].transform != NULL) { -+ for (GList *iter = known_schemas; iter != NULL; iter = iter->next) { -+ struct schema_s *schema = iter->data; -+ -+ if (schema->after_transform < 0) { -+ crm_debug("known_schemas[%d] => %s", lpc, schema->name); -+ -+ } else if (schema->transform != NULL) { - crm_debug("known_schemas[%d] => %s (upgrades to %d with %s.xsl)", -- lpc, known_schemas[lpc].name, -- known_schemas[lpc].after_transform, -- known_schemas[lpc].transform); -+ lpc, schema->name, schema->after_transform, -+ schema->transform); - - } else { - crm_debug("known_schemas[%d] => %s (upgrades to %d)", -- lpc, known_schemas[lpc].name, -- known_schemas[lpc].after_transform); -+ lpc, schema->name, schema->after_transform); - } -+ -+ lpc++; - } - } --- -2.31.1 - -From 33cfcc0d98603e04dde15d69acd46823679405f0 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Wed, 25 Oct 2023 17:34:25 -0400 -Subject: [PATCH 10/20] Refactor: libcrmcommon: Get rid of xml_schema_max. - -This is just the length of the known_schemas list. ---- - lib/common/schemas.c | 9 +++------ - 1 file changed, 3 insertions(+), 6 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index 6e6f32e..d4ce68e 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -61,7 +61,6 @@ struct schema_s { - }; - - static GList *known_schemas = NULL; --static int xml_schema_max = 0; - static bool silent_logging = FALSE; - - static void G_GNUC_PRINTF(2, 3) -@@ -196,11 +195,9 @@ add_schema(enum schema_validator_e validator, const schema_version_t *version, - int after_transform) - { - struct schema_s *schema = NULL; -- int last = xml_schema_max; -+ int last = g_list_length(known_schemas); - bool have_version = false; - -- xml_schema_max++; -- - schema = calloc(1, sizeof(struct schema_s)); - CRM_ASSERT(schema != NULL); - -@@ -234,7 +231,7 @@ add_schema(enum schema_validator_e validator, const schema_version_t *version, - - schema->transform_onleave = transform_onleave; - if (after_transform == 0) { -- after_transform = xml_schema_max; /* upgrade is a one-way */ -+ after_transform = last + 1; /* upgrade is a one-way */ - } - schema->after_transform = after_transform; - -@@ -732,7 +729,7 @@ pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidity - version = get_schema_version(validation); - if (strcmp(validation, PCMK__VALUE_NONE) == 0) { - return TRUE; -- } else if (version < xml_schema_max) { -+ } else if (version < g_list_length(known_schemas)) { - struct schema_s *schema = g_list_nth_data(known_schemas, version); - return validate_with(xml_blob, schema, error_handler, - error_handler_context); --- -2.31.1 - -From 6ace4c912d34f495ab5f52500628c82fb1533256 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Thu, 26 Oct 2023 12:52:14 -0400 -Subject: [PATCH 11/20] Refactor: libcrmcommon: Rename - xml_minimum_schema_index. - -This function's name is unclear. It actually returns the most recent -X.0 schema index. The new name is pretty bad, but I think it's at least -clear. - -And then while I'm at it, rewrite it to make it more clear. Just -iterate the known_schemas list, looking for the right .0 one. This code -does not get run very often, and it caches its result, so there's no -need to do the reverse traversal with a lagging index. ---- - lib/common/schemas.c | 41 +++++++++++++++++++++++++---------------- - 1 file changed, 25 insertions(+), 16 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index d4ce68e..55519e8 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -85,39 +85,48 @@ xml_latest_schema_index(void) - return g_list_length(known_schemas) - 3; // index from 0, ignore "pacemaker-next"/"none" - } - -+/* Return the index of the most recent X.0 schema. */ - static int --xml_minimum_schema_index(void) -+xml_find_x_0_schema_index(void) - { - static int best = 0; -+ int i = 0; - struct schema_s *best_schema = NULL; -- GList *last_real_ele = NULL; - - if (best != 0) { - return best; - } - -+ /* Get the most recent schema so we can look at its version number. */ - best = xml_latest_schema_index(); -+ best_schema = g_list_nth(known_schemas, best)->data; - -- /* We can't just use g_list_last here because "pacemaker-next" and "none" -- * are stored at the end of the list. We need to start several elements -- * back, at the last real schema. -+ /* Iterate over the schema list until we find a schema with the same major -+ * version as best, and with a minor version number of 0. -+ * -+ * This assumes that the first schema in a major series is always X.0, -+ * which seems like a safe assumption. - */ -- last_real_ele = g_list_nth(known_schemas, best); -- best_schema = last_real_ele->data; -- -- for (GList *iter = last_real_ele; iter != NULL; iter = iter->prev) { -+ for (GList *iter = known_schemas; iter != NULL; iter = iter->next) { - struct schema_s *schema = iter->data; - -- if (schema->version.v[0] < best_schema->version.v[0]) { -+ /* If we hit the initial best schema, the only things left in the list -+ * are "pacemaker-next" and "none" which aren't worth checking. -+ */ -+ if (schema == best_schema) { -+ break; -+ } -+ -+ if (schema->version.v[0] == best_schema->version.v[0] && -+ schema->version.v[1] == 0) { -+ best = i; - return best; -- } else { -- best--; - } -+ -+ i++; - } - -- /* If we never found a schema that meets the above criteria, default to -- * the last one. -- */ -+ /* If we got here, we never found a match. Just return the latest. */ - best = xml_latest_schema_index(); - return best; - } -@@ -1177,7 +1186,7 @@ cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs) - - int version = get_schema_version(value); - int orig_version = version; -- int min_version = xml_minimum_schema_index(); -+ int min_version = xml_find_x_0_schema_index(); - - if (version < min_version) { - // Current configuration schema is not acceptable, try to update --- -2.31.1 - -From 7df1d2df9e8710183735b19947a885bb129e523e Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Thu, 26 Oct 2023 14:14:02 -0400 -Subject: [PATCH 12/20] Refactor: libcrmcommon: Remove an unnecessary check in - validate_xml. - -I believe that add_schema ensures after_transform is never 0 - it's -either negative, or some positive non-zero value. So this check should -be pointless. ---- - lib/common/schemas.c | 4 ---- - 1 file changed, 4 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index 55519e8..cfb83dd 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -725,10 +725,6 @@ pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidity - valid = TRUE; - crm_xml_add(xml_blob, XML_ATTR_VALIDATION, schema->name); - crm_info("XML validated against %s", schema->name); -- -- if (schema->after_transform == 0) { -- break; -- } - } - } - --- -2.31.1 - -From dbf94f5a3c146992fb60c231a7eda21271b62b99 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Fri, 27 Oct 2023 10:49:49 -0400 -Subject: [PATCH 13/20] Refactor: libcrmcommon: Change how schema upgrade - versions are handled - -...in update_validation. Schemas always either upgrade to the next one -in the list, or do not upgrade. The latter only happens when we get to -the last real version and the next one is pacemaker-next/none. - -With that change made, we also need to change the conditional. There's -no need to check that the upgrade will regress. We only need to check -that we've run off the end of the list of real schema versions. - -A future commit will remove the after_transform variable entirely, but -since this is its most visible and complicated use, splitting this into -a separate commit seems worth it. ---- - lib/common/schemas.c | 6 +++--- - 1 file changed, 3 insertions(+), 3 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index cfb83dd..3ebdf1c 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -1095,10 +1095,10 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - if (rc == pcmk_ok && transform) { - xmlNode *upgrade = NULL; - struct schema_s *next_schema = NULL; -- next = schema->after_transform; -+ next = lpc+1; - -- if (next <= lpc) { -- /* There is no next version, or next would regress */ -+ if (next > max_stable_schemas) { -+ /* There is no next version */ - crm_trace("Stopping at %s", schema->name); - break; - } --- -2.31.1 - -From b2da7aaba5a1afe9d4f56989c0b81dd55abaf1b8 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Fri, 27 Oct 2023 11:00:12 -0400 -Subject: [PATCH 14/20] Refactor: libcrmcommon: Get rid of after_transform. - -As stated in the previous commit, schemas always just upgrade to the -next one in the list. There's no need to keep track of that fact, so -get rid of the variable that held it. This then allows us to get rid of -all the places that value was being set and passed around. ---- - lib/common/schemas.c | 54 +++++++++++++------------------------------- - 1 file changed, 16 insertions(+), 38 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index 3ebdf1c..e33d3c7 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -54,7 +54,6 @@ struct schema_s { - char *transform; - void *cache; - enum schema_validator_e validator; -- int after_transform; - schema_version_t version; - char *transform_enter; - bool transform_onleave; -@@ -200,8 +199,7 @@ schema_sort(const struct dirent **a, const struct dirent **b) - static void - add_schema(enum schema_validator_e validator, const schema_version_t *version, - const char *name, const char *transform, -- const char *transform_enter, bool transform_onleave, -- int after_transform) -+ const char *transform_enter, bool transform_onleave) - { - struct schema_s *schema = NULL; - int last = g_list_length(known_schemas); -@@ -211,9 +209,9 @@ add_schema(enum schema_validator_e validator, const schema_version_t *version, - CRM_ASSERT(schema != NULL); - - schema->validator = validator; -- schema->after_transform = after_transform; - schema->version.v[0] = version->v[0]; - schema->version.v[1] = version->v[1]; -+ schema->transform_onleave = transform_onleave; - - if (version->v[0] || version->v[1]) { - have_version = true; -@@ -238,24 +236,14 @@ add_schema(enum schema_validator_e validator, const schema_version_t *version, - CRM_ASSERT(schema->transform_enter != NULL); - } - -- schema->transform_onleave = transform_onleave; -- if (after_transform == 0) { -- after_transform = last + 1; /* upgrade is a one-way */ -- } -- schema->after_transform = after_transform; -- - known_schemas = g_list_append(known_schemas, schema); - -- if (schema->after_transform < 0) { -- crm_debug("Added supported schema %d: %s", last, schema->name); -- -- } else if (schema->transform != NULL) { -- crm_debug("Added supported schema %d: %s (upgrades to %d with %s.xsl)", -- last, schema->name, schema->after_transform, schema->transform); -+ if (schema->transform != NULL) { -+ crm_debug("Added supported schema %d: %s (upgrades with %s.xsl)", -+ last, schema->name, schema->transform); - - } else { -- crm_debug("Added supported schema %d: %s (upgrades to %d)", -- last, schema->name, schema->after_transform); -+ crm_debug("Added supported schema %d: %s", last, schema->name); - } - } - -@@ -288,8 +276,7 @@ add_schema(enum schema_validator_e validator, const schema_version_t *version, - * . name convention: (see "upgrade-enter") - */ - static int --add_schema_by_version(const schema_version_t *version, int next, -- bool transform_expected) -+add_schema_by_version(const schema_version_t *version, bool transform_expected) - { - bool transform_onleave = FALSE; - int rc = pcmk_rc_ok; -@@ -345,12 +332,11 @@ add_schema_by_version(const schema_version_t *version, int next, - free(xslt); - free(transform_upgrade); - transform_upgrade = NULL; -- next = -1; - rc = ENOENT; - } - - add_schema(schema_validator_rng, version, NULL, -- transform_upgrade, transform_enter, transform_onleave, next); -+ transform_upgrade, transform_enter, transform_onleave); - - free(transform_upgrade); - free(transform_enter); -@@ -416,7 +402,6 @@ crm_schema_init(void) - free(base); - for (lpc = 0; lpc < max; lpc++) { - bool transform_expected = FALSE; -- int next = 0; - schema_version_t version = SCHEMA_ZERO; - - if (!version_from_filename(namelist[lpc]->d_name, &version)) { -@@ -432,11 +417,9 @@ crm_schema_init(void) - && (version.v[0] < next_version.v[0])) { - transform_expected = TRUE; - } -- -- } else { -- next = -1; - } -- if (add_schema_by_version(&version, next, transform_expected) -+ -+ if (add_schema_by_version(&version, transform_expected) - == ENOENT) { - break; - } -@@ -450,10 +433,10 @@ crm_schema_init(void) - - // @COMPAT: Deprecated since 2.1.5 - add_schema(schema_validator_rng, &zero, "pacemaker-next", -- NULL, NULL, FALSE, -1); -+ NULL, NULL, FALSE); - - add_schema(schema_validator_none, &zero, PCMK__VALUE_NONE, -- NULL, NULL, FALSE, -1); -+ NULL, NULL, FALSE); - } - - static gboolean -@@ -1290,17 +1273,12 @@ pcmk__log_known_schemas(void) - for (GList *iter = known_schemas; iter != NULL; iter = iter->next) { - struct schema_s *schema = iter->data; - -- if (schema->after_transform < 0) { -- crm_debug("known_schemas[%d] => %s", lpc, schema->name); -- -- } else if (schema->transform != NULL) { -- crm_debug("known_schemas[%d] => %s (upgrades to %d with %s.xsl)", -- lpc, schema->name, schema->after_transform, -- schema->transform); -+ if (schema->transform != NULL) { -+ crm_debug("known_schemas[%d] => %s (upgrades with %s.xsl)", -+ lpc, schema->name, schema->transform); - - } else { -- crm_debug("known_schemas[%d] => %s (upgrades to %d)", -- lpc, schema->name, schema->after_transform); -+ crm_debug("known_schemas[%d] => %s", lpc, schema->name); - } - - lpc++; --- -2.31.1 - -From 645bb233e52b9f5f559ffcd354b2f4ef0bcdee90 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Mon, 6 Nov 2023 08:22:04 -0500 -Subject: [PATCH 15/20] Refactor: libcrmcommon: Remove unnecessary schema code. - -The block that sets the version if we didn't previously do so is no -longer necessary. This block only executes if the version parameter is -all zeros, which at the moment is only "pacemaker-next" and "none". We -could probably guarantee this will continue to be the case. - -Additionally, I don't see that this would even do anything useful -anymore. Scanning the name for a version number is going to fail for -"pacemaker-next" and "none". So really, this block was just handling -the possibility that we passed in no version number but that the name -contained a number. - -And with that done, there's only one more spot using schema_scanf so we -can just replace that with a call to sscanf. ---- - lib/common/schemas.c | 14 +------------- - 1 file changed, 1 insertion(+), 13 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index e33d3c7..7b91f71 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -32,9 +32,6 @@ typedef struct { - - #define SCHEMA_ZERO { .v = { 0, 0 } } - --#define schema_scanf(s, prefix, version, suffix) \ -- sscanf((s), prefix "%hhu.%hhu" suffix, &((version).v[0]), &((version).v[1])) -- - #define schema_strdup_printf(prefix, version, suffix) \ - crm_strdup_printf(prefix "%u.%u" suffix, (version).v[0], (version).v[1]) - -@@ -139,9 +136,7 @@ xml_latest_schema(void) - static inline bool - version_from_filename(const char *filename, schema_version_t *version) - { -- int rc = schema_scanf(filename, "pacemaker-", *version, ".rng"); -- -- return (rc == 2); -+ return sscanf(filename, "pacemaker-%hhu.%hhu.rng", &(version->v[0]), &(version->v[1])) == 2; - } - - static int -@@ -203,7 +198,6 @@ add_schema(enum schema_validator_e validator, const schema_version_t *version, - { - struct schema_s *schema = NULL; - int last = g_list_length(known_schemas); -- bool have_version = false; - - schema = calloc(1, sizeof(struct schema_s)); - CRM_ASSERT(schema != NULL); -@@ -214,14 +208,8 @@ add_schema(enum schema_validator_e validator, const schema_version_t *version, - schema->transform_onleave = transform_onleave; - - if (version->v[0] || version->v[1]) { -- have_version = true; -- } -- -- if (have_version) { - schema->name = schema_strdup_printf("pacemaker-", *version, ""); - } else { -- CRM_ASSERT(name != NULL); -- schema_scanf(name, "%*[^-]-", schema->version, ""); - schema->name = strdup(name); - CRM_ASSERT(schema->name != NULL); - } --- -2.31.1 - -From 0943f1ff0a9e72e88c5a234a32bb83d0f2e02c84 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Fri, 17 Nov 2023 09:42:55 -0500 -Subject: [PATCH 16/20] Refactor: libcrmcommon: Improve - xml_find_x_0_schema_index. - -* Lots of comments to explain how it works. - -* Walk the list backwards, stopping on the first one in the major - version series. This means the first one no longer has to be X.0. - -* Require that known_schemas be non-NULL. - -* Don't use the returned index to also mean we've found something since - that means if the index we actually want to return is 0, the function - will have to run every time. ---- - lib/common/schemas.c | 62 +++++++++++++++++++++++++++++--------------- - 1 file changed, 41 insertions(+), 21 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index 7b91f71..466ad5a 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -85,45 +85,65 @@ xml_latest_schema_index(void) - static int - xml_find_x_0_schema_index(void) - { -+ /* We can't just use best to determine whether we've found the index -+ * or not. What if we have a very long list of schemas all in the -+ * same major version series? We'd return 0 for that, which means -+ * we would still run this function every time. -+ */ -+ static bool found = false; - static int best = 0; -- int i = 0; -+ int i; -+ GList *best_node = NULL; - struct schema_s *best_schema = NULL; - -- if (best != 0) { -+ if (found) { - return best; - } - -+ CRM_ASSERT(known_schemas != NULL); -+ - /* Get the most recent schema so we can look at its version number. */ - best = xml_latest_schema_index(); -- best_schema = g_list_nth(known_schemas, best)->data; -+ best_node = g_list_nth(known_schemas, best); -+ best_schema = best_node->data; -+ -+ /* If this is a singleton list, we're done. */ -+ if (pcmk__list_of_1(known_schemas)) { -+ goto done; -+ } - -- /* Iterate over the schema list until we find a schema with the same major -- * version as best, and with a minor version number of 0. -- * -- * This assumes that the first schema in a major series is always X.0, -- * which seems like a safe assumption. -+ /* Start comparing the list from the node before the best schema (there's -+ * no point in comparing something to itself). Then, 'i' is an index -+ * starting at the best schema and will always point at the node after -+ * 'iter'. This makes it the value we want to return when we find what -+ * we're looking for. - */ -- for (GList *iter = known_schemas; iter != NULL; iter = iter->next) { -+ i = best; -+ -+ for (GList *iter = best_node->prev; iter != NULL; iter = iter->prev) { - struct schema_s *schema = iter->data; - -- /* If we hit the initial best schema, the only things left in the list -- * are "pacemaker-next" and "none" which aren't worth checking. -+ /* We've found a schema in an older major version series. Return -+ * the index of the first one in the same major version series as -+ * the best schema. - */ -- if (schema == best_schema) { -- break; -- } -- -- if (schema->version.v[0] == best_schema->version.v[0] && -- schema->version.v[1] == 0) { -+ if (schema->version.v[0] < best_schema->version.v[0]) { - best = i; -- return best; -+ goto done; -+ -+ /* We're out of list to examine. This probably means there was only -+ * one major version series, so return index 0. -+ */ -+ } else if (iter->prev == NULL) { -+ best = 0; -+ goto done; - } - -- i++; -+ i--; - } - -- /* If we got here, we never found a match. Just return the latest. */ -- best = xml_latest_schema_index(); -+done: -+ found = true; - return best; - } - --- -2.31.1 - -From eeeb36338f48d40f9f15a51c18aeca533b6c260d Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Fri, 17 Nov 2023 11:18:34 -0500 -Subject: [PATCH 17/20] Refactor: libcrmcommon: Add a parameter to a couple - schema functions. - -Instead of assuming known_schemas, pass the list to use as a parameter. ---- - lib/common/schemas.c | 22 +++++++++++----------- - 1 file changed, 11 insertions(+), 11 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index 466ad5a..9d98695 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -73,17 +73,17 @@ xml_log(int priority, const char *fmt, ...) - } - - static int --xml_latest_schema_index(void) -+xml_latest_schema_index(GList *schemas) - { - // @COMPAT: pacemaker-next is deprecated since 2.1.5 - // FIXME: This function assumes at least three schemas have been added - // before it has been called for the first time. -- return g_list_length(known_schemas) - 3; // index from 0, ignore "pacemaker-next"/"none" -+ return g_list_length(schemas) - 3; // index from 0, ignore "pacemaker-next"/"none" - } - - /* Return the index of the most recent X.0 schema. */ - static int --xml_find_x_0_schema_index(void) -+xml_find_x_0_schema_index(GList *schemas) - { - /* We can't just use best to determine whether we've found the index - * or not. What if we have a very long list of schemas all in the -@@ -100,15 +100,15 @@ xml_find_x_0_schema_index(void) - return best; - } - -- CRM_ASSERT(known_schemas != NULL); -+ CRM_ASSERT(schemas != NULL); - - /* Get the most recent schema so we can look at its version number. */ -- best = xml_latest_schema_index(); -- best_node = g_list_nth(known_schemas, best); -+ best = xml_latest_schema_index(schemas); -+ best_node = g_list_nth(schemas, best); - best_schema = best_node->data; - - /* If this is a singleton list, we're done. */ -- if (pcmk__list_of_1(known_schemas)) { -+ if (pcmk__list_of_1(schemas)) { - goto done; - } - -@@ -150,7 +150,7 @@ done: - const char * - xml_latest_schema(void) - { -- return get_schema_name(xml_latest_schema_index()); -+ return get_schema_name(xml_latest_schema_index(known_schemas)); - } - - static inline bool -@@ -1010,7 +1010,7 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - { - xmlNode *xml = NULL; - char *value = NULL; -- int max_stable_schemas = xml_latest_schema_index(); -+ int max_stable_schemas = xml_latest_schema_index(known_schemas); - int lpc = 0, match = -1, rc = pcmk_ok; - int next = -1; /* -1 denotes "inactive" value */ - xmlRelaxNGValidityErrorFunc error_handler = -@@ -1173,7 +1173,7 @@ cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs) - - int version = get_schema_version(value); - int orig_version = version; -- int min_version = xml_find_x_0_schema_index(); -+ int min_version = xml_find_x_0_schema_index(known_schemas); - - if (version < min_version) { - // Current configuration schema is not acceptable, try to update -@@ -1235,7 +1235,7 @@ cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs) - free_xml(*xml); - *xml = converted; - -- if (version < xml_latest_schema_index()) { -+ if (version < xml_latest_schema_index(known_schemas)) { - if (to_logs) { - pcmk__config_warn("Configuration with schema %s was " - "internally upgraded to acceptable (but " --- -2.31.1 - -From 12e7b982da61c6cc6cf01164d45bb8f7b0255a8a Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Fri, 17 Nov 2023 11:30:18 -0500 -Subject: [PATCH 18/20] Refactor: libcrmcommon: Rename several schema-related - types. - -Give them pcmk__ names indicating they are private. This is in -preparation for moving them out into a header file. ---- - lib/common/schemas.c | 80 ++++++++++++++++++++++---------------------- - 1 file changed, 40 insertions(+), 40 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index 9d98695..cf8f325 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -28,7 +28,7 @@ - - typedef struct { - unsigned char v[2]; --} schema_version_t; -+} pcmk__schema_version_t; - - #define SCHEMA_ZERO { .v = { 0, 0 } } - -@@ -41,20 +41,20 @@ typedef struct { - xmlRelaxNGParserCtxtPtr parser; - } relaxng_ctx_cache_t; - --enum schema_validator_e { -- schema_validator_none, -- schema_validator_rng -+enum pcmk__schema_validator { -+ pcmk__schema_validator_none, -+ pcmk__schema_validator_rng - }; - --struct schema_s { -+typedef struct { - char *name; - char *transform; - void *cache; -- enum schema_validator_e validator; -- schema_version_t version; -+ enum pcmk__schema_validator validator; -+ pcmk__schema_version_t version; - char *transform_enter; - bool transform_onleave; --}; -+} pcmk__schema_t; - - static GList *known_schemas = NULL; - static bool silent_logging = FALSE; -@@ -94,7 +94,7 @@ xml_find_x_0_schema_index(GList *schemas) - static int best = 0; - int i; - GList *best_node = NULL; -- struct schema_s *best_schema = NULL; -+ pcmk__schema_t *best_schema = NULL; - - if (found) { - return best; -@@ -121,7 +121,7 @@ xml_find_x_0_schema_index(GList *schemas) - i = best; - - for (GList *iter = best_node->prev; iter != NULL; iter = iter->prev) { -- struct schema_s *schema = iter->data; -+ pcmk__schema_t *schema = iter->data; - - /* We've found a schema in an older major version series. Return - * the index of the first one in the same major version series as -@@ -154,7 +154,7 @@ xml_latest_schema(void) - } - - static inline bool --version_from_filename(const char *filename, schema_version_t *version) -+version_from_filename(const char *filename, pcmk__schema_version_t *version) - { - return sscanf(filename, "pacemaker-%hhu.%hhu.rng", &(version->v[0]), &(version->v[1])) == 2; - } -@@ -163,7 +163,7 @@ static int - schema_filter(const struct dirent *a) - { - int rc = 0; -- schema_version_t version = SCHEMA_ZERO; -+ pcmk__schema_version_t version = SCHEMA_ZERO; - - if (strstr(a->d_name, "pacemaker-") != a->d_name) { - /* crm_trace("%s - wrong prefix", a->d_name); */ -@@ -185,8 +185,8 @@ schema_filter(const struct dirent *a) - static int - schema_sort(const struct dirent **a, const struct dirent **b) - { -- schema_version_t a_version = SCHEMA_ZERO; -- schema_version_t b_version = SCHEMA_ZERO; -+ pcmk__schema_version_t a_version = SCHEMA_ZERO; -+ pcmk__schema_version_t b_version = SCHEMA_ZERO; - - if (!version_from_filename(a[0]->d_name, &a_version) - || !version_from_filename(b[0]->d_name, &b_version)) { -@@ -212,14 +212,14 @@ schema_sort(const struct dirent **a, const struct dirent **b) - * through \c add_schema_by_version. - */ - static void --add_schema(enum schema_validator_e validator, const schema_version_t *version, -+add_schema(enum pcmk__schema_validator validator, const pcmk__schema_version_t *version, - const char *name, const char *transform, - const char *transform_enter, bool transform_onleave) - { -- struct schema_s *schema = NULL; -+ pcmk__schema_t *schema = NULL; - int last = g_list_length(known_schemas); - -- schema = calloc(1, sizeof(struct schema_s)); -+ schema = calloc(1, sizeof(pcmk__schema_t)); - CRM_ASSERT(schema != NULL); - - schema->validator = validator; -@@ -284,7 +284,7 @@ add_schema(enum schema_validator_e validator, const schema_version_t *version, - * . name convention: (see "upgrade-enter") - */ - static int --add_schema_by_version(const schema_version_t *version, bool transform_expected) -+add_schema_by_version(const pcmk__schema_version_t *version, bool transform_expected) - { - bool transform_onleave = FALSE; - int rc = pcmk_rc_ok; -@@ -343,7 +343,7 @@ add_schema_by_version(const schema_version_t *version, bool transform_expected) - rc = ENOENT; - } - -- add_schema(schema_validator_rng, version, NULL, -+ add_schema(pcmk__schema_validator_rng, version, NULL, - transform_upgrade, transform_enter, transform_onleave); - - free(transform_upgrade); -@@ -397,7 +397,7 @@ crm_schema_init(void) - int lpc, max; - char *base = pcmk__xml_artefact_root(pcmk__xml_artefact_ns_legacy_rng); - struct dirent **namelist = NULL; -- const schema_version_t zero = SCHEMA_ZERO; -+ const pcmk__schema_version_t zero = SCHEMA_ZERO; - - wrap_libxslt(false); - -@@ -410,7 +410,7 @@ crm_schema_init(void) - free(base); - for (lpc = 0; lpc < max; lpc++) { - bool transform_expected = FALSE; -- schema_version_t version = SCHEMA_ZERO; -+ pcmk__schema_version_t version = SCHEMA_ZERO; - - if (!version_from_filename(namelist[lpc]->d_name, &version)) { - // Shouldn't be possible, but makes static analysis happy -@@ -419,7 +419,7 @@ crm_schema_init(void) - continue; - } - if ((lpc + 1) < max) { -- schema_version_t next_version = SCHEMA_ZERO; -+ pcmk__schema_version_t next_version = SCHEMA_ZERO; - - if (version_from_filename(namelist[lpc+1]->d_name, &next_version) - && (version.v[0] < next_version.v[0])) { -@@ -440,10 +440,10 @@ crm_schema_init(void) - } - - // @COMPAT: Deprecated since 2.1.5 -- add_schema(schema_validator_rng, &zero, "pacemaker-next", -+ add_schema(pcmk__schema_validator_rng, &zero, "pacemaker-next", - NULL, NULL, FALSE); - -- add_schema(schema_validator_none, &zero, PCMK__VALUE_NONE, -+ add_schema(pcmk__schema_validator_none, &zero, PCMK__VALUE_NONE, - NULL, NULL, FALSE); - } - -@@ -533,14 +533,14 @@ validate_with_relaxng(xmlDocPtr doc, xmlRelaxNGValidityErrorFunc error_handler, - static void - free_schema(gpointer data) - { -- struct schema_s *schema = data; -+ pcmk__schema_t *schema = data; - relaxng_ctx_cache_t *ctx = NULL; - - switch (schema->validator) { -- case schema_validator_none: // not cached -+ case pcmk__schema_validator_none: // not cached - break; - -- case schema_validator_rng: // cached -+ case pcmk__schema_validator_rng: // cached - ctx = (relaxng_ctx_cache_t *) schema->cache; - if (ctx == NULL) { - break; -@@ -582,7 +582,7 @@ crm_schema_cleanup(void) - } - - static gboolean --validate_with(xmlNode *xml, struct schema_s *schema, xmlRelaxNGValidityErrorFunc error_handler, void* error_handler_context) -+validate_with(xmlNode *xml, pcmk__schema_t *schema, xmlRelaxNGValidityErrorFunc error_handler, void* error_handler_context) - { - gboolean valid = FALSE; - char *file = NULL; -@@ -592,7 +592,7 @@ validate_with(xmlNode *xml, struct schema_s *schema, xmlRelaxNGValidityErrorFunc - return FALSE; - } - -- if (schema->validator == schema_validator_none) { -+ if (schema->validator == pcmk__schema_validator_none) { - return TRUE; - } - -@@ -607,7 +607,7 @@ validate_with(xmlNode *xml, struct schema_s *schema, xmlRelaxNGValidityErrorFunc - crm_trace("Validating with %s (type=%d)", - pcmk__s(file, "missing schema"), schema->validator); - switch (schema->validator) { -- case schema_validator_rng: -+ case pcmk__schema_validator_rng: - cache = (relaxng_ctx_cache_t **) &(schema->cache); - valid = validate_with_relaxng(xml->doc, error_handler, error_handler_context, file, cache); - break; -@@ -621,7 +621,7 @@ validate_with(xmlNode *xml, struct schema_s *schema, xmlRelaxNGValidityErrorFunc - } - - static bool --validate_with_silent(xmlNode *xml, struct schema_s *schema) -+validate_with_silent(xmlNode *xml, pcmk__schema_t *schema) - { - bool rc, sl_backup = silent_logging; - silent_logging = TRUE; -@@ -710,7 +710,7 @@ pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidity - bool valid = FALSE; - - for (GList *iter = known_schemas; iter != NULL; iter = iter->next) { -- struct schema_s *schema = iter->data; -+ pcmk__schema_t *schema = iter->data; - - if (validate_with(xml_blob, schema, NULL, NULL)) { - valid = TRUE; -@@ -726,7 +726,7 @@ pcmk__validate_xml(xmlNode *xml_blob, const char *validation, xmlRelaxNGValidity - if (strcmp(validation, PCMK__VALUE_NONE) == 0) { - return TRUE; - } else if (version < g_list_length(known_schemas)) { -- struct schema_s *schema = g_list_nth_data(known_schemas, version); -+ pcmk__schema_t *schema = g_list_nth_data(known_schemas, version); - return validate_with(xml_blob, schema, error_handler, - error_handler_context); - } -@@ -918,7 +918,7 @@ apply_transformation(xmlNode *xml, const char *transform, gboolean to_logs) - * \note Only emits warnings about enter/leave phases in case of issues. - */ - static xmlNode * --apply_upgrade(xmlNode *xml, const struct schema_s *schema, gboolean to_logs) -+apply_upgrade(xmlNode *xml, const pcmk__schema_t *schema, gboolean to_logs) - { - bool transform_onleave = schema->transform_onleave; - char *transform_leave; -@@ -972,7 +972,7 @@ apply_upgrade(xmlNode *xml, const struct schema_s *schema, gboolean to_logs) - const char * - get_schema_name(int version) - { -- struct schema_s *schema = g_list_nth_data(known_schemas, version); -+ pcmk__schema_t *schema = g_list_nth_data(known_schemas, version); - - if (schema == NULL) { - return "unknown"; -@@ -991,7 +991,7 @@ get_schema_version(const char *name) - } - - for (GList *iter = known_schemas; iter != NULL; iter = iter->next) { -- struct schema_s *schema = iter->data; -+ pcmk__schema_t *schema = iter->data; - - if (pcmk__str_eq(name, schema->name, pcmk__str_casei)) { - return lpc; -@@ -1052,7 +1052,7 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - * easier than trying to get all the loop indices we're using here - * sorted out and working correctly. - */ -- struct schema_s *schema = g_list_nth_data(known_schemas, lpc); -+ pcmk__schema_t *schema = g_list_nth_data(known_schemas, lpc); - - crm_debug("Testing '%s' validation (%d of %d)", - pcmk__s(schema->name, ""), lpc, max_stable_schemas); -@@ -1085,7 +1085,7 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - - if (rc == pcmk_ok && transform) { - xmlNode *upgrade = NULL; -- struct schema_s *next_schema = NULL; -+ pcmk__schema_t *next_schema = NULL; - next = lpc+1; - - if (next > max_stable_schemas) { -@@ -1151,7 +1151,7 @@ update_validation(xmlNode **xml_blob, int *best, int max, gboolean transform, - } - - if (*best > match && *best) { -- struct schema_s *best_schema = g_list_nth_data(known_schemas, *best); -+ pcmk__schema_t *best_schema = g_list_nth_data(known_schemas, *best); - - crm_info("%s the configuration from %s to %s", - transform?"Transformed":"Upgraded", pcmk__s(value, ""), -@@ -1279,7 +1279,7 @@ pcmk__log_known_schemas(void) - int lpc = 0; - - for (GList *iter = known_schemas; iter != NULL; iter = iter->next) { -- struct schema_s *schema = iter->data; -+ pcmk__schema_t *schema = iter->data; - - if (schema->transform != NULL) { - crm_debug("known_schemas[%d] => %s (upgrades with %s.xsl)", --- -2.31.1 - -From 97b3fa3462039d4d7bdad6c6ff328a5124977e5f Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Fri, 17 Nov 2023 11:32:55 -0500 -Subject: [PATCH 19/20] Refactor: libcrmcommon: Make various schema stuff - non-static. - -This is the minimum amount necessary to make the function unit testable. -None of this is intended to ever become public. ---- - lib/common/crmcommon_private.h | 26 ++++++++++++++++++++++++++ - lib/common/schemas.c | 25 ++++--------------------- - 2 files changed, 30 insertions(+), 21 deletions(-) - -diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h -index 121d663..6ab9de1 100644 ---- a/lib/common/crmcommon_private.h -+++ b/lib/common/crmcommon_private.h -@@ -283,4 +283,30 @@ void pcmk__register_patchset_messages(pcmk__output_t *out); - #define PCMK__PW_BUFFER_LEN 500 - - -+/* -+ * Schemas -+ */ -+typedef struct { -+ unsigned char v[2]; -+} pcmk__schema_version_t; -+ -+enum pcmk__schema_validator { -+ pcmk__schema_validator_none, -+ pcmk__schema_validator_rng -+}; -+ -+typedef struct { -+ char *name; -+ char *transform; -+ void *cache; -+ enum pcmk__schema_validator validator; -+ pcmk__schema_version_t version; -+ char *transform_enter; -+ bool transform_onleave; -+} pcmk__schema_t; -+ -+G_GNUC_INTERNAL -+int pcmk__find_x_0_schema_index(GList *schemas); -+ -+ - #endif // CRMCOMMON_PRIVATE__H -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index cf8f325..83334b4 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -26,9 +26,7 @@ - #include - #include /* PCMK__XML_LOG_BASE */ - --typedef struct { -- unsigned char v[2]; --} pcmk__schema_version_t; -+#include "crmcommon_private.h" - - #define SCHEMA_ZERO { .v = { 0, 0 } } - -@@ -41,21 +39,6 @@ typedef struct { - xmlRelaxNGParserCtxtPtr parser; - } relaxng_ctx_cache_t; - --enum pcmk__schema_validator { -- pcmk__schema_validator_none, -- pcmk__schema_validator_rng --}; -- --typedef struct { -- char *name; -- char *transform; -- void *cache; -- enum pcmk__schema_validator validator; -- pcmk__schema_version_t version; -- char *transform_enter; -- bool transform_onleave; --} pcmk__schema_t; -- - static GList *known_schemas = NULL; - static bool silent_logging = FALSE; - -@@ -82,8 +65,8 @@ xml_latest_schema_index(GList *schemas) - } - - /* Return the index of the most recent X.0 schema. */ --static int --xml_find_x_0_schema_index(GList *schemas) -+int -+pcmk__find_x_0_schema_index(GList *schemas) - { - /* We can't just use best to determine whether we've found the index - * or not. What if we have a very long list of schemas all in the -@@ -1173,7 +1156,7 @@ cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs) - - int version = get_schema_version(value); - int orig_version = version; -- int min_version = xml_find_x_0_schema_index(known_schemas); -+ int min_version = pcmk__find_x_0_schema_index(known_schemas); - - if (version < min_version) { - // Current configuration schema is not acceptable, try to update --- -2.31.1 - -From c4c093c0785e06ca3371556b19ede1d5b44d090a Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Fri, 17 Nov 2023 12:39:53 -0500 -Subject: [PATCH 20/20] Test: libcrmcommon: Add unit tests for - pcmk__xml_find_x_0_schema_index. - -This requires making various things in the function conditional, which I -kind of hate. But, it all comes down to the fact that when we are -running for real, we're adding the pacemaker-next/none schemas with -crm_schema_init. - -When we are unit testing, we aren't doing any of that. The only -"schemas" we have are the ones we are adding directly. So, the list has -two items fewer than the real function expects. I think this is okay -and doesn't totally invalidating the testing. ---- - configure.ac | 1 + - lib/common/schemas.c | 33 +++++- - lib/common/tests/Makefile.am | 1 + - lib/common/tests/schemas/Makefile.am | 16 +++ - .../pcmk__xml_find_x_0_schema_index_test.c | 112 ++++++++++++++++++ - 5 files changed, 161 insertions(+), 2 deletions(-) - create mode 100644 lib/common/tests/schemas/Makefile.am - create mode 100644 lib/common/tests/schemas/pcmk__xml_find_x_0_schema_index_test.c - -diff --git a/configure.ac b/configure.ac -index 6bff02e..9eb7539 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -2153,6 +2153,7 @@ AC_CONFIG_FILES(Makefile \ - lib/common/tests/output/Makefile \ - lib/common/tests/procfs/Makefile \ - lib/common/tests/results/Makefile \ -+ lib/common/tests/schemas/Makefile \ - lib/common/tests/scores/Makefile \ - lib/common/tests/strings/Makefile \ - lib/common/tests/utils/Makefile \ -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index 83334b4..372e872 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -60,8 +60,13 @@ xml_latest_schema_index(GList *schemas) - { - // @COMPAT: pacemaker-next is deprecated since 2.1.5 - // FIXME: This function assumes at least three schemas have been added -- // before it has been called for the first time. -+ // before it has been called for the first time, which is only the case -+ // if we are not unit testing. -+#if defined(PCMK__UNIT_TESTING) -+ return g_list_length(schemas) - 1; // index from 0 -+#else - return g_list_length(schemas) - 3; // index from 0, ignore "pacemaker-next"/"none" -+#endif - } - - /* Return the index of the most recent X.0 schema. */ -@@ -73,8 +78,17 @@ pcmk__find_x_0_schema_index(GList *schemas) - * same major version series? We'd return 0 for that, which means - * we would still run this function every time. - */ -+#if defined(PCMK__UNIT_TESTING) -+ /* If we're unit testing, these can't be static because they'll stick -+ * around from one test run to the next. They need to be cleared out -+ * every time. -+ */ -+ bool found = false; -+ int best = 0; -+#else - static bool found = false; - static int best = 0; -+#endif - int i; - GList *best_node = NULL; - pcmk__schema_t *best_schema = NULL; -@@ -90,10 +104,25 @@ pcmk__find_x_0_schema_index(GList *schemas) - best_node = g_list_nth(schemas, best); - best_schema = best_node->data; - -- /* If this is a singleton list, we're done. */ -+ /* If we are unit testing, we don't add the pacemaker-next/none schemas -+ * to the list because we're not using the standard schema adding -+ * functions. Thus, a singleton list means we're done. -+ * -+ * On the other hand, if we are running as usually, we have those two -+ * schemas added to the list. A list of length three actually only has -+ * one useful schema. So we're still done. -+ * -+ * @COMPAT Change this when we stop adding those schemas. -+ */ -+#if defined(PCMK__UNIT_TESTING) - if (pcmk__list_of_1(schemas)) { - goto done; - } -+#else -+ if (g_list_length(schemas) == 3) { -+ goto done; -+ } -+#endif - - /* Start comparing the list from the node before the best schema (there's - * no point in comparing something to itself). Then, 'i' is an index -diff --git a/lib/common/tests/Makefile.am b/lib/common/tests/Makefile.am -index c0407e5..22fb32e 100644 ---- a/lib/common/tests/Makefile.am -+++ b/lib/common/tests/Makefile.am -@@ -21,6 +21,7 @@ SUBDIRS = \ - options \ - output \ - results \ -+ schemas \ - scores \ - strings \ - utils \ -diff --git a/lib/common/tests/schemas/Makefile.am b/lib/common/tests/schemas/Makefile.am -new file mode 100644 -index 0000000..5f485b3 ---- /dev/null -+++ b/lib/common/tests/schemas/Makefile.am -@@ -0,0 +1,16 @@ -+# -+# 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 $(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__xml_find_x_0_schema_index_test -+ -+TESTS = $(check_PROGRAMS) -diff --git a/lib/common/tests/schemas/pcmk__xml_find_x_0_schema_index_test.c b/lib/common/tests/schemas/pcmk__xml_find_x_0_schema_index_test.c -new file mode 100644 -index 0000000..9f16ba1 ---- /dev/null -+++ b/lib/common/tests/schemas/pcmk__xml_find_x_0_schema_index_test.c -@@ -0,0 +1,112 @@ -+/* -+ * 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 -+#include -+ -+#include -+ -+#include "crmcommon_private.h" -+ -+static pcmk__schema_t * -+mk_schema(const char *name, unsigned char x, unsigned char y) -+{ -+ pcmk__schema_t *schema = malloc(sizeof(pcmk__schema_t)); -+ -+ schema->name = strdup(name); -+ schema->version.v[0] = x; -+ schema->version.v[1] = y; -+ return schema; -+} -+ -+static void -+free_schema(void *data) -+{ -+ pcmk__schema_t *schema = data; -+ free(schema->name); -+ free(schema); -+} -+ -+static void -+empty_schema_list(void **state) -+{ -+ pcmk__assert_asserts(pcmk__find_x_0_schema_index(NULL)); -+} -+ -+static void -+singleton_schema_list(void **state) -+{ -+ GList *schemas = NULL; -+ -+ schemas = g_list_append(schemas, mk_schema("pacemaker-1.0", 1, 0)); -+ assert_int_equal(0, pcmk__find_x_0_schema_index(schemas)); -+ g_list_free_full(schemas, free_schema); -+} -+ -+static void -+one_major_version(void **state) -+{ -+ GList *schemas = NULL; -+ -+ schemas = g_list_append(schemas, mk_schema("pacemaker-1.0", 1, 0)); -+ schemas = g_list_append(schemas, mk_schema("pacemaker-1.2", 1, 2)); -+ schemas = g_list_append(schemas, mk_schema("pacemaker-1.3", 1, 3)); -+ assert_int_equal(0, pcmk__find_x_0_schema_index(schemas)); -+ g_list_free_full(schemas, free_schema); -+} -+ -+static void -+first_version_is_not_0(void **state) -+{ -+ GList *schemas = NULL; -+ -+ schemas = g_list_append(schemas, mk_schema("pacemaker-1.1", 1, 1)); -+ schemas = g_list_append(schemas, mk_schema("pacemaker-1.2", 1, 2)); -+ schemas = g_list_append(schemas, mk_schema("pacemaker-1.3", 1, 3)); -+ assert_int_equal(0, pcmk__find_x_0_schema_index(schemas)); -+ g_list_free_full(schemas, free_schema); -+} -+ -+static void -+multiple_major_versions(void **state) -+{ -+ GList *schemas = NULL; -+ -+ schemas = g_list_append(schemas, mk_schema("pacemaker-1.0", 1, 0)); -+ schemas = g_list_append(schemas, mk_schema("pacemaker-1.1", 1, 1)); -+ schemas = g_list_append(schemas, mk_schema("pacemaker-2.0", 2, 0)); -+ assert_int_equal(2, pcmk__find_x_0_schema_index(schemas)); -+ g_list_free_full(schemas, free_schema); -+} -+ -+static void -+many_versions(void **state) -+{ -+ GList *schemas = NULL; -+ -+ schemas = g_list_append(schemas, mk_schema("pacemaker-1.0", 1, 0)); -+ schemas = g_list_append(schemas, mk_schema("pacemaker-1.1", 1, 1)); -+ schemas = g_list_append(schemas, mk_schema("pacemaker-1.2", 1, 2)); -+ schemas = g_list_append(schemas, mk_schema("pacemaker-2.0", 2, 0)); -+ schemas = g_list_append(schemas, mk_schema("pacemaker-2.1", 2, 1)); -+ schemas = g_list_append(schemas, mk_schema("pacemaker-2.2", 2, 2)); -+ schemas = g_list_append(schemas, mk_schema("pacemaker-3.0", 3, 0)); -+ schemas = g_list_append(schemas, mk_schema("pacemaker-3.1", 3, 1)); -+ schemas = g_list_append(schemas, mk_schema("pacemaker-3.2", 3, 2)); -+ assert_int_equal(6, pcmk__find_x_0_schema_index(schemas)); -+ g_list_free_full(schemas, free_schema); -+} -+ -+PCMK__UNIT_TEST(NULL, NULL, -+ cmocka_unit_test(empty_schema_list), -+ cmocka_unit_test(singleton_schema_list), -+ cmocka_unit_test(one_major_version), -+ cmocka_unit_test(first_version_is_not_0), -+ cmocka_unit_test(multiple_major_versions), -+ cmocka_unit_test(many_versions)) --- -2.31.1 - diff --git a/002-schema-transfer.patch b/002-schema-transfer.patch deleted file mode 100644 index 9c1c05b4bd5fa2ec233fd3bed3e75b0d227124ab..0000000000000000000000000000000000000000 --- a/002-schema-transfer.patch +++ /dev/null @@ -1,1986 +0,0 @@ -From 9e0c58dc3b949e4eadfa43364e60677478b7aa0f Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Thu, 7 Sep 2023 16:48:52 -0400 -Subject: [PATCH 01/15] Refactor: libcrmcommon: Break schema version comparison - out... - -...into its own function. Then, wrap this function with -schema_sort_directory so existing code doesn't need to be changed. This -allows us to use the version comparison elsewhere. ---- - lib/common/schemas.c | 26 ++++++++++++++++---------- - 1 file changed, 16 insertions(+), 10 deletions(-) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index 372e87223..b3ff05917 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -195,7 +195,20 @@ schema_filter(const struct dirent *a) - } - - static int --schema_sort(const struct dirent **a, const struct dirent **b) -+schema_cmp(pcmk__schema_version_t a_version, pcmk__schema_version_t b_version) -+{ -+ for (int i = 0; i < 2; ++i) { -+ if (a_version.v[i] < b_version.v[i]) { -+ return -1; -+ } else if (a_version.v[i] > b_version.v[i]) { -+ return 1; -+ } -+ } -+ return 0; -+} -+ -+static int -+schema_cmp_directory(const struct dirent **a, const struct dirent **b) - { - pcmk__schema_version_t a_version = SCHEMA_ZERO; - pcmk__schema_version_t b_version = SCHEMA_ZERO; -@@ -206,14 +219,7 @@ schema_sort(const struct dirent **a, const struct dirent **b) - return 0; - } - -- for (int i = 0; i < 2; ++i) { -- if (a_version.v[i] < b_version.v[i]) { -- return -1; -- } else if (a_version.v[i] > b_version.v[i]) { -- return 1; -- } -- } -- return 0; -+ return schema_cmp(a_version, b_version); - } - - /*! -@@ -413,7 +419,7 @@ crm_schema_init(void) - - wrap_libxslt(false); - -- max = scandir(base, &namelist, schema_filter, schema_sort); -+ max = scandir(base, &namelist, schema_filter, schema_cmp_directory); - if (max < 0) { - crm_notice("scandir(%s) failed: %s (%d)", base, strerror(errno), errno); - free(base); --- -2.41.0 - -From e7d7c33eb8329c3c50fe648133cbb7651c1ecb9d Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Wed, 29 Nov 2023 09:37:31 -0500 -Subject: [PATCH 02/15] Feature: libcrmcommon: Add - pcmk__schema_files_later_than. - -This function takes in a schema version and returns a list of all RNG -and XSLT files from all schema versions more recent than that one. - -Also, add unit tests for the new function. ---- - include/crm/common/internal.h | 1 + - lib/common/schemas.c | 62 ++++++++++- - lib/common/tests/schemas/Makefile.am | 42 ++++++- - .../pcmk__schema_files_later_than_test.c | 104 ++++++++++++++++++ - 4 files changed, 207 insertions(+), 2 deletions(-) - create mode 100644 lib/common/tests/schemas/pcmk__schema_files_later_than_test.c - -diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h -index 307860636..a3cd455dc 100644 ---- a/include/crm/common/internal.h -+++ b/include/crm/common/internal.h -@@ -134,6 +134,7 @@ bool pcmk__procfs_has_pids(void); - void crm_schema_init(void); - void crm_schema_cleanup(void); - -+GList *pcmk__schema_files_later_than(const char *name); - - /* internal functions related to process IDs (from pid.c) */ - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index b3ff05917..1c60738ec 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -168,7 +168,11 @@ xml_latest_schema(void) - static inline bool - version_from_filename(const char *filename, pcmk__schema_version_t *version) - { -- return sscanf(filename, "pacemaker-%hhu.%hhu.rng", &(version->v[0]), &(version->v[1])) == 2; -+ if (pcmk__ends_with(filename, ".rng")) { -+ return sscanf(filename, "pacemaker-%hhu.%hhu.rng", &(version->v[0]), &(version->v[1])) == 2; -+ } else { -+ return sscanf(filename, "pacemaker-%hhu.%hhu", &(version->v[0]), &(version->v[1])) == 2; -+ } - } - - static int -@@ -1291,6 +1295,62 @@ cli_config_update(xmlNode **xml, int *best_version, gboolean to_logs) - return rc; - } - -+/*! -+ * \internal -+ * \brief Return a list of all schema files and any associated XSLT files -+ * later than the given one -+ * \brief Return a list of all schema versions later than the given one -+ * -+ * \param[in] schema The schema to compare against (for example, -+ * "pacemaker-3.1.rng" or "pacemaker-3.1") -+ * -+ * \note The caller is responsible for freeing both the returned list and -+ * the elements of the list -+ */ -+GList * -+pcmk__schema_files_later_than(const char *name) -+{ -+ GList *lst = NULL; -+ pcmk__schema_version_t ver; -+ -+ if (!version_from_filename(name, &ver)) { -+ return lst; -+ } -+ -+ for (GList *iter = g_list_nth(known_schemas, xml_latest_schema_index(known_schemas)); -+ iter != NULL; iter = iter->prev) { -+ pcmk__schema_t *schema = iter->data; -+ char *s = NULL; -+ -+ if (schema_cmp(ver, schema->version) != -1) { -+ continue; -+ } -+ -+ s = crm_strdup_printf("%s.rng", schema->name); -+ lst = g_list_prepend(lst, s); -+ -+ if (schema->transform != NULL) { -+ char *xform = crm_strdup_printf("%s.xsl", schema->transform); -+ lst = g_list_prepend(lst, xform); -+ } -+ -+ if (schema->transform_enter != NULL) { -+ char *enter = crm_strdup_printf("%s.xsl", schema->transform_enter); -+ -+ lst = g_list_prepend(lst, enter); -+ -+ if (schema->transform_onleave) { -+ int last_dash = strrchr(enter, '-') - enter; -+ char *leave = crm_strdup_printf("%.*s-leave.xsl", last_dash, enter); -+ -+ lst = g_list_prepend(lst, leave); -+ } -+ } -+ } -+ -+ return lst; -+} -+ - void - pcmk__log_known_schemas(void) - { -diff --git a/lib/common/tests/schemas/Makefile.am b/lib/common/tests/schemas/Makefile.am -index 5f485b3e9..b5c5e7f3c 100644 ---- a/lib/common/tests/schemas/Makefile.am -+++ b/lib/common/tests/schemas/Makefile.am -@@ -10,7 +10,47 @@ - include $(top_srcdir)/mk/tap.mk - include $(top_srcdir)/mk/unittest.mk - -+CFLAGS += -DPCMK__TEST_SCHEMA_DIR='"$(abs_builddir)/schemas"' -+ - # Add "_test" to the end of all test program names to simplify .gitignore. --check_PROGRAMS = pcmk__xml_find_x_0_schema_index_test -+check_PROGRAMS = pcmk__schema_files_later_than_test \ -+ pcmk__xml_find_x_0_schema_index_test - - TESTS = $(check_PROGRAMS) -+ -+$(TESTS): setup-schema-dir -+ -+# Set up a temporary schemas/ directory containing only some of the full set of -+# pacemaker schema files. This lets us know exactly how many schemas are present, -+# allowing us to write tests without having to make changes when new schemas are -+# added. -+# -+# This directory contains the following: -+# -+# * pacemaker-next.rng - Used to verify that this sorts before all versions -+# * upgrade-*.xsl - Required by various schema versions -+# * pacemaker-[0-9]*.rng - We're only pulling in 15 schemas, which is enough -+# to get everything through pacemaker-3.0.rng. This -+# includes 2.10, needed so we can check that versions -+# are compared as numbers instead of strings. -+# * other RNG files - This catches everything except the pacemaker-*rng -+# files. These files are included by the top-level -+# pacemaker-*rng files, so we need them for tests. -+# This will glob more than we need, but the extra ones -+# won't get in the way. -+.PHONY: setup-schema-dir -+setup-schema-dir: -+ $(MKDIR_P) schemas -+ ( cd schemas ; \ -+ ln -sf $(abs_top_builddir)/xml/pacemaker-next.rng . ; \ -+ ln -sf $(abs_top_builddir)/xml/upgrade-*.xsl . ; \ -+ for f in $(shell ls -1v $(abs_top_builddir)/xml/pacemaker-[0-9]*.rng | head -15); do \ -+ ln -sf $$f $$(basename $$f); \ -+ done ; \ -+ for f in $(shell ls -1 $(top_srcdir)/xml/*.rng | grep -v pacemaker); do \ -+ ln -sf ../$$f $$(basename $$f); \ -+ done ) -+ -+.PHONY: clean-local -+clean-local: -+ -rm -rf schemas -diff --git a/lib/common/tests/schemas/pcmk__schema_files_later_than_test.c b/lib/common/tests/schemas/pcmk__schema_files_later_than_test.c -new file mode 100644 -index 000000000..50b5f4334 ---- /dev/null -+++ b/lib/common/tests/schemas/pcmk__schema_files_later_than_test.c -@@ -0,0 +1,104 @@ -+/* -+ * 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 -+ -+#include -+#include -+ -+#include -+ -+static int -+setup(void **state) { -+ setenv("PCMK_schema_directory", PCMK__TEST_SCHEMA_DIR, 1); -+ crm_schema_init(); -+ return 0; -+} -+ -+static int -+teardown(void **state) { -+ crm_schema_cleanup(); -+ unsetenv("PCMK_schema_directory"); -+ return 0; -+} -+ -+static void -+invalid_name(void **state) -+{ -+ assert_null(pcmk__schema_files_later_than("xyz")); -+ assert_null(pcmk__schema_files_later_than("pacemaker-")); -+} -+ -+static void -+valid_name(void **state) -+{ -+ GList *schemas = NULL; -+ -+ schemas = pcmk__schema_files_later_than("pacemaker-1.0"); -+ assert_int_equal(g_list_length(schemas), 18); -+ /* There is no "pacemaker-1.1". */ -+ assert_string_equal("pacemaker-1.2.rng", g_list_nth_data(schemas, 0)); -+ assert_string_equal("upgrade-1.3.xsl", g_list_nth_data(schemas, 1)); -+ assert_string_equal("pacemaker-1.3.rng", g_list_nth_data(schemas, 2)); -+ assert_string_equal("pacemaker-2.0.rng", g_list_nth_data(schemas, 3)); -+ assert_string_equal("pacemaker-2.1.rng", g_list_nth_data(schemas, 4)); -+ assert_string_equal("pacemaker-2.2.rng", g_list_nth_data(schemas, 5)); -+ assert_string_equal("pacemaker-2.3.rng", g_list_nth_data(schemas, 6)); -+ assert_string_equal("pacemaker-2.4.rng", g_list_nth_data(schemas, 7)); -+ assert_string_equal("pacemaker-2.5.rng", g_list_nth_data(schemas, 8)); -+ assert_string_equal("pacemaker-2.6.rng", g_list_nth_data(schemas, 9)); -+ assert_string_equal("pacemaker-2.7.rng", g_list_nth_data(schemas, 10)); -+ assert_string_equal("pacemaker-2.8.rng", g_list_nth_data(schemas, 11)); -+ assert_string_equal("pacemaker-2.9.rng", g_list_nth_data(schemas, 12)); -+ assert_string_equal("upgrade-2.10-leave.xsl", g_list_nth_data(schemas, 13)); -+ assert_string_equal("upgrade-2.10-enter.xsl", g_list_nth_data(schemas, 14)); -+ assert_string_equal("upgrade-2.10.xsl", g_list_nth_data(schemas, 15)); -+ assert_string_equal("pacemaker-2.10.rng", g_list_nth_data(schemas, 16)); -+ assert_string_equal("pacemaker-3.0.rng", g_list_nth_data(schemas, 17)); -+ g_list_free_full(schemas, free); -+ -+ /* Adding .rng to the end of the schema we're requesting is also valid. */ -+ schemas = pcmk__schema_files_later_than("pacemaker-2.0.rng"); -+ assert_int_equal(g_list_length(schemas), 14); -+ assert_string_equal("pacemaker-2.1.rng", g_list_nth_data(schemas, 0)); -+ assert_string_equal("pacemaker-2.2.rng", g_list_nth_data(schemas, 1)); -+ assert_string_equal("pacemaker-2.3.rng", g_list_nth_data(schemas, 2)); -+ assert_string_equal("pacemaker-2.4.rng", g_list_nth_data(schemas, 3)); -+ assert_string_equal("pacemaker-2.5.rng", g_list_nth_data(schemas, 4)); -+ assert_string_equal("pacemaker-2.6.rng", g_list_nth_data(schemas, 5)); -+ assert_string_equal("pacemaker-2.7.rng", g_list_nth_data(schemas, 6)); -+ assert_string_equal("pacemaker-2.8.rng", g_list_nth_data(schemas, 7)); -+ assert_string_equal("pacemaker-2.9.rng", g_list_nth_data(schemas, 8)); -+ assert_string_equal("upgrade-2.10-leave.xsl", g_list_nth_data(schemas, 9)); -+ assert_string_equal("upgrade-2.10-enter.xsl", g_list_nth_data(schemas, 10)); -+ assert_string_equal("upgrade-2.10.xsl", g_list_nth_data(schemas, 11)); -+ assert_string_equal("pacemaker-2.10.rng", g_list_nth_data(schemas, 12)); -+ assert_string_equal("pacemaker-3.0.rng", g_list_nth_data(schemas, 13)); -+ g_list_free_full(schemas, free); -+ -+ /* Check that "pacemaker-2.10" counts as later than "pacemaker-2.9". */ -+ schemas = pcmk__schema_files_later_than("pacemaker-2.9"); -+ assert_int_equal(g_list_length(schemas), 5); -+ assert_string_equal("upgrade-2.10-leave.xsl", g_list_nth_data(schemas, 0)); -+ assert_string_equal("upgrade-2.10-enter.xsl", g_list_nth_data(schemas, 1)); -+ assert_string_equal("upgrade-2.10.xsl", g_list_nth_data(schemas, 2)); -+ assert_string_equal("pacemaker-2.10.rng", g_list_nth_data(schemas, 3)); -+ assert_string_equal("pacemaker-3.0.rng", g_list_nth_data(schemas, 4)); -+ g_list_free_full(schemas, free); -+ -+ /* And then something way in the future that will never apply due to our -+ * special schema directory. -+ */ -+ schemas = pcmk__schema_files_later_than("pacemaker-9.0"); -+ assert_null(schemas); -+} -+ -+PCMK__UNIT_TEST(setup, teardown, -+ cmocka_unit_test(invalid_name), -+ cmocka_unit_test(valid_name)) --- -2.41.0 - -From 76859d61f4d35e1f8b7c35d8766d3d0c123d4552 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Mon, 11 Sep 2023 08:56:22 -0400 -Subject: [PATCH 03/15] Refactor: libcrmcommon: Allow more specs in - pcmk__xml_artefact_path. - -If the given filespec already has a .rng or .xsl extension, don't add an -additional one. This allows reusing this function to grab files given -in another schema file's externalRef links without modification. ---- - lib/common/xml.c | 12 ++++++++++-- - 1 file changed, 10 insertions(+), 2 deletions(-) - -diff --git a/lib/common/xml.c b/lib/common/xml.c -index 53ebff770..142b501df 100644 ---- a/lib/common/xml.c -+++ b/lib/common/xml.c -@@ -2618,11 +2618,19 @@ pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec) - switch (ns) { - case pcmk__xml_artefact_ns_legacy_rng: - case pcmk__xml_artefact_ns_base_rng: -- ret = crm_strdup_printf("%s/%s.rng", base, filespec); -+ if (pcmk__ends_with(filespec, ".rng")) { -+ ret = crm_strdup_printf("%s/%s", base, filespec); -+ } else { -+ ret = crm_strdup_printf("%s/%s.rng", base, filespec); -+ } - break; - case pcmk__xml_artefact_ns_legacy_xslt: - case pcmk__xml_artefact_ns_base_xslt: -- ret = crm_strdup_printf("%s/%s.xsl", base, filespec); -+ if (pcmk__ends_with(filespec, ".xsl")) { -+ ret = crm_strdup_printf("%s/%s", base, filespec); -+ } else { -+ ret = crm_strdup_printf("%s/%s.xsl", base, filespec); -+ } - break; - default: - crm_err("XML artefact family specified as %u not recognized", ns); --- -2.41.0 - -From 0d702bba5b50e1eede201853c7680a2517fade9f Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Mon, 18 Sep 2023 12:37:46 -0400 -Subject: [PATCH 04/15] Feature: build: Add PCMK__REMOTE_SCHEMA_DIR to - configure.ac. - -This is a new subdirectory where schema files downloaded to a remote -executor can be stored. - -Also, add support for an environment variable that can override this -compile-time setting and explain it in the documentation. ---- - configure.ac | 5 +++++ - doc/sphinx/Pacemaker_Explained/local-options.rst | 12 ++++++++++++ - etc/sysconfig/pacemaker.in | 7 +++++++ - include/crm/common/options_internal.h | 1 + - 4 files changed, 25 insertions(+) - -diff --git a/configure.ac b/configure.ac -index 17cee41e9..bd548200c 100644 ---- a/configure.ac -+++ b/configure.ac -@@ -838,6 +838,11 @@ AC_DEFINE_UNQUOTED([CRM_SCHEMA_DIRECTORY], ["$CRM_SCHEMA_DIRECTORY"], - [Location for the Pacemaker Relax-NG Schema]) - AC_SUBST(CRM_SCHEMA_DIRECTORY) - -+PCMK__REMOTE_SCHEMA_DIR="${localstatedir}/lib/pacemaker/schemas" -+AC_DEFINE_UNQUOTED([PCMK__REMOTE_SCHEMA_DIR], ["$PCMK__REMOTE_SCHEMA_DIR"], -+ [Location to store Relax-NG Schema files on remote nodes]) -+AC_SUBST(PCMK__REMOTE_SCHEMA_DIR) -+ - CRM_CORE_DIR="${localstatedir}/lib/pacemaker/cores" - AC_DEFINE_UNQUOTED([CRM_CORE_DIR], ["$CRM_CORE_DIR"], - [Directory Pacemaker daemons should change to (without systemd, core files will go here)]) -diff --git a/doc/sphinx/Pacemaker_Explained/local-options.rst b/doc/sphinx/Pacemaker_Explained/local-options.rst -index 91eda6632..118256247 100644 ---- a/doc/sphinx/Pacemaker_Explained/local-options.rst -+++ b/doc/sphinx/Pacemaker_Explained/local-options.rst -@@ -478,6 +478,18 @@ environment variables when Pacemaker daemons start up. - - *Advanced Use Only:* Specify an alternate location for RNG schemas and - XSL transforms. - -+ * - .. _pcmk_remote_schema_directory: -+ -+ .. index:: -+ pair:: node option; PCMK_remote_schema_directory -+ -+ PCMK_remote_schema_directory -+ - :ref:`text ` -+ - |PCMK__REMOTE_SCHEMA_DIR| -+ - *Advanced Use Only:* Specify an alternate location on Pacemaker Remote -+ nodes for storing newer RNG schemas and XSL transforms fetched from -+ the cluster. -+ - * - .. _pcmk_valgrind_enabled: - - .. index:: -diff --git a/etc/sysconfig/pacemaker.in b/etc/sysconfig/pacemaker.in -index 0c3609d8e..487a104a7 100644 ---- a/etc/sysconfig/pacemaker.in -+++ b/etc/sysconfig/pacemaker.in -@@ -339,6 +339,13 @@ - # - # Default: PCMK_schema_directory="@CRM_SCHEMA_DIRECTORY@" - -+# PCMK_remote_schema_directory (Advanced Use Only) -+# -+# Specify an alternate location on Pacemaker Remote nodes for storing newer -+# RNG schemas and XSL transforms fetched from the cluster. -+# -+# Default: PCMK_remote_schema_directory="@PCMK__REMOTE_SCHEMA_DIR@" -+ - # G_SLICE (Advanced Use Only) - # - # Affect the behavior of glib's memory allocator. Setting to "always-malloc" -diff --git a/include/crm/common/options_internal.h b/include/crm/common/options_internal.h -index 5c561fd1f..a9316ca33 100644 ---- a/include/crm/common/options_internal.h -+++ b/include/crm/common/options_internal.h -@@ -95,6 +95,7 @@ bool pcmk__valid_sbd_timeout(const char *value); - #define PCMK__ENV_PANIC_ACTION "panic_action" - #define PCMK__ENV_PHYSICAL_HOST "physical_host" - #define PCMK__ENV_REMOTE_ADDRESS "remote_address" -+#define PCMK__ENV_REMOTE_SCHEMA_DIR "remote_schema_directory" - #define PCMK__ENV_REMOTE_PID1 "remote_pid1" - #define PCMK__ENV_REMOTE_PORT "remote_port" - #define PCMK__ENV_RESPAWNED "respawned" --- -2.41.0 - -From 16d46de389f33a8b29cbd74ee2c9077f28029446 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Wed, 6 Dec 2023 12:38:12 -0500 -Subject: [PATCH 05/15] Feature: libcrmcommon: Add pcmk__remote_schema_dir. - -This function checks both the environment (thus, -/etc/sysconfig/pacemaker as well) and ./configure options for the -location where any additional schema files should be stored. ---- - include/crm/common/xml_internal.h | 1 + - lib/common/schemas.c | 17 +++++++++++++++++ - 2 files changed, 18 insertions(+) - -diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h -index f319856c8..cb27ec6b2 100644 ---- a/include/crm/common/xml_internal.h -+++ b/include/crm/common/xml_internal.h -@@ -447,5 +447,6 @@ gboolean pcmk__validate_xml(xmlNode *xml_blob, const char *validation, - void *error_handler_context); - - void pcmk__log_known_schemas(void); -+const char *pcmk__remote_schema_dir(void); - - #endif // PCMK__XML_INTERNAL__H -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index 1c60738ec..c03d80036 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -1351,6 +1351,23 @@ pcmk__schema_files_later_than(const char *name) - return lst; - } - -+/*! -+ * \internal -+ * \brief Return the directory containing any extra schema files that a -+ * Pacemaker Remote node fetched from the cluster -+ */ -+const char * -+pcmk__remote_schema_dir(void) -+{ -+ const char *dir = pcmk__env_option(PCMK__ENV_REMOTE_SCHEMA_DIR); -+ -+ if (pcmk__str_empty(dir)) { -+ return PCMK__REMOTE_SCHEMA_DIR; -+ } -+ -+ return dir; -+} -+ - void - pcmk__log_known_schemas(void) - { --- -2.41.0 - -From d2ed95651ea7c44153a34ba009494c70928319d7 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Tue, 19 Sep 2023 11:40:59 -0400 -Subject: [PATCH 06/15] Refactor: libcrmcommon: Look in more dirs in - pcmk__xml_artefact_path. - -If the directory returned by pcmk__xml_artefact_root doesn't contain the -file we were looking for, fall back on PCMK__REMOTE_SCHEMA_DIR. If that -still doesn't contain the file, return NULL. ---- - lib/common/xml.c | 32 +++++++++++++++++++++++++------- - 1 file changed, 25 insertions(+), 7 deletions(-) - -diff --git a/lib/common/xml.c b/lib/common/xml.c -index 142b501df..a1b1291f9 100644 ---- a/lib/common/xml.c -+++ b/lib/common/xml.c -@@ -10,6 +10,7 @@ - #include - - #include -+#include - #include - #include - #include -@@ -2610,33 +2611,50 @@ pcmk__xml_artefact_root(enum pcmk__xml_artefact_ns ns) - return ret; - } - --char * --pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec) -+static char * -+find_artefact(enum pcmk__xml_artefact_ns ns, const char *path, const char *filespec) - { -- char *base = pcmk__xml_artefact_root(ns), *ret = NULL; -+ char *ret = NULL; - - switch (ns) { - case pcmk__xml_artefact_ns_legacy_rng: - case pcmk__xml_artefact_ns_base_rng: - if (pcmk__ends_with(filespec, ".rng")) { -- ret = crm_strdup_printf("%s/%s", base, filespec); -+ ret = crm_strdup_printf("%s/%s", path, filespec); - } else { -- ret = crm_strdup_printf("%s/%s.rng", base, filespec); -+ ret = crm_strdup_printf("%s/%s.rng", path, filespec); - } - break; - case pcmk__xml_artefact_ns_legacy_xslt: - case pcmk__xml_artefact_ns_base_xslt: - if (pcmk__ends_with(filespec, ".xsl")) { -- ret = crm_strdup_printf("%s/%s", base, filespec); -+ ret = crm_strdup_printf("%s/%s", path, filespec); - } else { -- ret = crm_strdup_printf("%s/%s.xsl", base, filespec); -+ ret = crm_strdup_printf("%s/%s.xsl", path, filespec); - } - break; - default: - crm_err("XML artefact family specified as %u not recognized", ns); - } -+ -+ return ret; -+} -+ -+char * -+pcmk__xml_artefact_path(enum pcmk__xml_artefact_ns ns, const char *filespec) -+{ -+ struct stat sb; -+ char *base = pcmk__xml_artefact_root(ns); -+ char *ret = NULL; -+ -+ ret = find_artefact(ns, base, filespec); - free(base); - -+ if (stat(ret, &sb) != 0 || !S_ISREG(sb.st_mode)) { -+ const char *remote_schema_dir = pcmk__remote_schema_dir(); -+ ret = find_artefact(ns, remote_schema_dir, filespec); -+ } -+ - return ret; - } - --- -2.41.0 - -From 9f4da4102a40f7dbfe0d78c7735c2801ba852f4b Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Mon, 11 Sep 2023 09:17:18 -0400 -Subject: [PATCH 07/15] Feature: libcrmcommon: Add XML attrs needed for schema - file transfer. - -Also, move PCMK__XA_CONN_HOST to be alphabetized. ---- - include/crm_internal.h | 6 +++++- - 1 file changed, 5 insertions(+), 1 deletion(-) - -diff --git a/include/crm_internal.h b/include/crm_internal.h -index 71a0f7efa..3bc8d096a 100644 ---- a/include/crm_internal.h -+++ b/include/crm_internal.h -@@ -81,17 +81,21 @@ - #define PCMK__XA_CONFIG_ERRORS "config-errors" - #define PCMK__XA_CONFIG_WARNINGS "config-warnings" - #define PCMK__XA_CONFIRM "confirm" -+#define PCMK__XA_CONN_HOST "connection_host" - #define PCMK__XA_CRMD "crmd" - #define PCMK__XA_EXPECTED "expected" -+#define PCMK__XA_FILE "file" - #define PCMK__XA_GRAPH_ERRORS "graph-errors" - #define PCMK__XA_GRAPH_WARNINGS "graph-warnings" - #define PCMK__XA_IN_CCM "in_ccm" - #define PCMK__XA_JOIN "join" - #define PCMK__XA_MODE "mode" - #define PCMK__XA_NODE_START_STATE "node_start_state" -+#define PCMK__XA_PATH "path" -+#define PCMK__XA_SCHEMA "schema" -+#define PCMK__XA_SCHEMAS "schemas" - #define PCMK__XA_TASK "task" - #define PCMK__XA_UPTIME "uptime" --#define PCMK__XA_CONN_HOST "connection_host" - - - /* --- -2.41.0 - -From 3384643bd4572ad03e183b16d5cc84fe69599380 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Mon, 11 Sep 2023 10:46:26 -0400 -Subject: [PATCH 08/15] Feature: libcrmcommon: Add pcmk__build_schema_xml_node. - -This function adds a given RNG or XSLT file and all of the files they -refer to as children of a given XML node. - -Also, add unit tests for the new function. ---- - include/crm/common/internal.h | 3 + - lib/common/schemas.c | 144 +++++++++++++++++ - lib/common/tests/schemas/Makefile.am | 3 +- - .../pcmk__build_schema_xml_node_test.c | 149 ++++++++++++++++++ - 4 files changed, 298 insertions(+), 1 deletion(-) - create mode 100644 lib/common/tests/schemas/pcmk__build_schema_xml_node_test.c - -diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h -index a3cd455dc..318003efe 100644 ---- a/include/crm/common/internal.h -+++ b/include/crm/common/internal.h -@@ -135,6 +135,9 @@ void crm_schema_init(void); - void crm_schema_cleanup(void); - - GList *pcmk__schema_files_later_than(const char *name); -+void pcmk__build_schema_xml_node(xmlNode *parent, const char *name, -+ GList **already_included); -+ - - /* internal functions related to process IDs (from pid.c) */ - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index c03d80036..1bcdff031 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -1351,6 +1351,150 @@ pcmk__schema_files_later_than(const char *name) - return lst; - } - -+static void -+append_href(xmlNode *xml, void *user_data) -+{ -+ GList **list = user_data; -+ const char *href = crm_element_value(xml, "href"); -+ char *s = NULL; -+ -+ if (href == NULL) { -+ return; -+ } -+ -+ s = strdup(href); -+ CRM_ASSERT(s != NULL); -+ *list = g_list_prepend(*list, s); -+} -+ -+static void -+external_refs_in_schema(GList **list, const char *contents) -+{ -+ /* local-name()= is needed to ignore the xmlns= setting at the top of -+ * the XML file. Otherwise, the xpath query will always return nothing. -+ */ -+ const char *search = "//*[local-name()='externalRef'] | //*[local-name()='include']"; -+ xmlNode *xml = string2xml(contents); -+ -+ crm_foreach_xpath_result(xml, search, append_href, list); -+ free_xml(xml); -+} -+ -+static int -+read_file_contents(const char *file, char **contents) -+{ -+ int rc = pcmk_rc_ok; -+ char *path = NULL; -+ -+ if (pcmk__ends_with(file, ".rng")) { -+ path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_rng, file); -+ } else { -+ path = pcmk__xml_artefact_path(pcmk__xml_artefact_ns_legacy_xslt, file); -+ } -+ -+ rc = pcmk__file_contents(path, contents); -+ -+ free(path); -+ return rc; -+} -+ -+static void -+add_schema_file_to_xml(xmlNode *parent, const char *file, GList **already_included) -+{ -+ char *contents = NULL; -+ char *path = NULL; -+ xmlNode *file_node = NULL; -+ GList *includes = NULL; -+ int rc = pcmk_rc_ok; -+ -+ /* If we already included this file, don't do so again. */ -+ if (g_list_find_custom(*already_included, file, (GCompareFunc) strcmp) != NULL) { -+ return; -+ } -+ -+ /* Ensure whatever file we were given has a suffix we know about. If not, -+ * just assume it's an RNG file. -+ */ -+ if (!pcmk__ends_with(file, ".rng") && !pcmk__ends_with(file, ".xsl")) { -+ path = crm_strdup_printf("%s.rng", file); -+ } else { -+ path = strdup(file); -+ CRM_ASSERT(path != NULL); -+ } -+ -+ rc = read_file_contents(path, &contents); -+ if (rc != pcmk_rc_ok || contents == NULL) { -+ crm_warn("Could not read schema file %s: %s", file, pcmk_rc_str(rc)); -+ free(path); -+ return; -+ } -+ -+ /* Create a new node with the contents of the file -+ * as a CDATA block underneath it. -+ */ -+ file_node = create_xml_node(parent, PCMK__XA_FILE); -+ if (file_node == NULL) { -+ free(contents); -+ free(path); -+ return; -+ } -+ -+ crm_xml_add(file_node, PCMK__XA_PATH, path); -+ *already_included = g_list_prepend(*already_included, path); -+ -+ xmlAddChild(file_node, xmlNewCDataBlock(parent->doc, (pcmkXmlStr) contents, -+ strlen(contents))); -+ -+ /* Scan the file for any or nodes and build up -+ * a list of the files they reference. -+ */ -+ external_refs_in_schema(&includes, contents); -+ -+ /* For each referenced file, recurse to add it (and potentially anything it -+ * references, ...) to the XML. -+ */ -+ for (GList *iter = includes; iter != NULL; iter = iter->next) { -+ add_schema_file_to_xml(parent, iter->data, already_included); -+ } -+ -+ free(contents); -+ g_list_free_full(includes, free); -+} -+ -+/*! -+ * \internal -+ * \brief Add an XML schema file and all the files it references as children -+ * of a given XML node -+ * -+ * \param[in,out] parent The parent XML node -+ * \param[in] name The schema version to compare against -+ * (for example, "pacemaker-3.1" or "pacemaker-3.1.rng") -+ * \param[in,out] already_included A list of names that have already been added -+ * to the parent node. -+ * -+ * \note The caller is responsible for freeing both the returned list and -+ * the elements of the list -+ */ -+void -+pcmk__build_schema_xml_node(xmlNode *parent, const char *name, GList **already_included) -+{ -+ /* First, create an unattached node to add all the schema files to as children. */ -+ xmlNode *schema_node = create_xml_node(NULL, PCMK__XA_SCHEMA); -+ -+ crm_xml_add(schema_node, XML_ATTR_VERSION, name); -+ add_schema_file_to_xml(schema_node, name, already_included); -+ -+ /* Then, if we actually added any children, attach the node to parent. If -+ * we did not add any children (for instance, name was invalid), this prevents -+ * us from returning a document with additional empty children. -+ */ -+ if (schema_node->children != NULL) { -+ xmlAddChild(parent, schema_node); -+ } else { -+ free_xml(schema_node); -+ } -+} -+ - /*! - * \internal - * \brief Return the directory containing any extra schema files that a -diff --git a/lib/common/tests/schemas/Makefile.am b/lib/common/tests/schemas/Makefile.am -index b5c5e7f3c..8854eb264 100644 ---- a/lib/common/tests/schemas/Makefile.am -+++ b/lib/common/tests/schemas/Makefile.am -@@ -13,7 +13,8 @@ include $(top_srcdir)/mk/unittest.mk - CFLAGS += -DPCMK__TEST_SCHEMA_DIR='"$(abs_builddir)/schemas"' - - # Add "_test" to the end of all test program names to simplify .gitignore. --check_PROGRAMS = pcmk__schema_files_later_than_test \ -+check_PROGRAMS = pcmk__build_schema_xml_node_test \ -+ pcmk__schema_files_later_than_test \ - pcmk__xml_find_x_0_schema_index_test - - TESTS = $(check_PROGRAMS) -diff --git a/lib/common/tests/schemas/pcmk__build_schema_xml_node_test.c b/lib/common/tests/schemas/pcmk__build_schema_xml_node_test.c -new file mode 100644 -index 000000000..1f5cb6ce1 ---- /dev/null -+++ b/lib/common/tests/schemas/pcmk__build_schema_xml_node_test.c -@@ -0,0 +1,149 @@ -+/* -+ * 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 -+ -+#include -+#include -+#include -+ -+#include -+ -+const char *rngs1[] = { "pacemaker-3.0.rng", "status-1.0.rng", "alerts-2.10.rng", -+ "nvset-2.9.rng", "score.rng", "rule-2.9.rng", -+ "tags-1.3.rng", "acls-2.0.rng", "fencing-2.4.rng", -+ "constraints-3.0.rng", "resources-3.0.rng", "nvset-3.0.rng", -+ "nodes-3.0.rng", "options-3.0.rng", NULL }; -+ -+const char *rngs2[] = { "pacemaker-2.0.rng", "status-1.0.rng", "tags-1.3.rng", -+ "acls-2.0.rng", "fencing-1.2.rng", "constraints-1.2.rng", -+ "rule.rng", "score.rng", "resources-1.3.rng", -+ "nvset-1.3.rng", "nodes-1.3.rng", "options-1.0.rng", -+ "nvset.rng", "cib-1.2.rng", NULL }; -+ -+const char *rngs3[] = { "pacemaker-2.1.rng", "constraints-2.1.rng", NULL }; -+ -+static int -+setup(void **state) { -+ setenv("PCMK_schema_directory", PCMK__TEST_SCHEMA_DIR, 1); -+ crm_schema_init(); -+ return 0; -+} -+ -+static int -+teardown(void **state) { -+ crm_schema_cleanup(); -+ unsetenv("PCMK_schema_directory"); -+ return 0; -+} -+ -+static void -+invalid_name(void **state) -+{ -+ GList *already_included = NULL; -+ xmlNode *parent = create_xml_node(NULL, PCMK__XA_SCHEMAS); -+ -+ pcmk__build_schema_xml_node(parent, "pacemaker-9.0", &already_included); -+ assert_null(parent->children); -+ assert_null(already_included); -+ free_xml(parent); -+} -+ -+static void -+single_schema(void **state) -+{ -+ GList *already_included = NULL; -+ xmlNode *parent = create_xml_node(NULL, PCMK__XA_SCHEMAS); -+ xmlNode *schema_node = NULL; -+ xmlNode *file_node = NULL; -+ int i = 0; -+ -+ pcmk__build_schema_xml_node(parent, "pacemaker-3.0", &already_included); -+ -+ assert_non_null(already_included); -+ assert_non_null(parent->children); -+ -+ /* Test that the result looks like this: -+ * -+ * -+ * -+ * CDATA -+ * CDATA -+ * ... -+ * -+ * -+ */ -+ schema_node = pcmk__xml_first_child(parent); -+ assert_string_equal("pacemaker-3.0", crm_element_value(schema_node, XML_ATTR_VERSION)); -+ -+ file_node = pcmk__xml_first_child(schema_node); -+ while (file_node != NULL && rngs1[i] != NULL) { -+ assert_string_equal(rngs1[i], crm_element_value(file_node, PCMK__XA_PATH)); -+ assert_int_equal(pcmk__xml_first_child(file_node)->type, XML_CDATA_SECTION_NODE); -+ -+ file_node = pcmk__xml_next(file_node); -+ i++; -+ } -+ -+ g_list_free_full(already_included, free); -+ free_xml(parent); -+} -+ -+static void -+multiple_schemas(void **state) -+{ -+ GList *already_included = NULL; -+ xmlNode *parent = create_xml_node(NULL, PCMK__XA_SCHEMAS); -+ xmlNode *schema_node = NULL; -+ xmlNode *file_node = NULL; -+ int i = 0; -+ -+ pcmk__build_schema_xml_node(parent, "pacemaker-2.0", &already_included); -+ pcmk__build_schema_xml_node(parent, "pacemaker-2.1", &already_included); -+ -+ assert_non_null(already_included); -+ assert_non_null(parent->children); -+ -+ /* Like single_schema, but make sure files aren't included multiple times -+ * when the function is called repeatedly. -+ */ -+ schema_node = pcmk__xml_first_child(parent); -+ assert_string_equal("pacemaker-2.0", crm_element_value(schema_node, XML_ATTR_VERSION)); -+ -+ file_node = pcmk__xml_first_child(schema_node); -+ while (file_node != NULL && rngs2[i] != NULL) { -+ assert_string_equal(rngs2[i], crm_element_value(file_node, PCMK__XA_PATH)); -+ assert_int_equal(pcmk__xml_first_child(file_node)->type, XML_CDATA_SECTION_NODE); -+ -+ file_node = pcmk__xml_next(file_node); -+ i++; -+ } -+ -+ schema_node = pcmk__xml_next(schema_node); -+ assert_string_equal("pacemaker-2.1", crm_element_value(schema_node, XML_ATTR_VERSION)); -+ -+ file_node = pcmk__xml_first_child(schema_node); -+ i = 0; -+ -+ while (file_node != NULL && rngs3[i] != NULL) { -+ assert_string_equal(rngs3[i], crm_element_value(file_node, PCMK__XA_PATH)); -+ assert_int_equal(pcmk__xml_first_child(file_node)->type, XML_CDATA_SECTION_NODE); -+ -+ file_node = pcmk__xml_next(file_node); -+ i++; -+ } -+ -+ g_list_free_full(already_included, free); -+ free_xml(parent); -+} -+ -+PCMK__UNIT_TEST(setup, teardown, -+ cmocka_unit_test(invalid_name), -+ cmocka_unit_test(single_schema), -+ cmocka_unit_test(multiple_schemas)) --- -2.41.0 - -From 036eb9f59326962ed2d1f2f4af88b20755a046d5 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Thu, 14 Sep 2023 10:02:16 -0400 -Subject: [PATCH 09/15] Feature: daemons: Add a new fetch_schemas CIB command. - -This command wraps pcmk__schema_files_later_than and -pcmk__build_schema_xml_node to produce a new CIB command that takes in a -minimum schema version and returns a big XML message containing all the -schema files after that version. ---- - daemons/based/based_messages.c | 43 +++++++++++++++++++++++++++++++++ - daemons/based/based_operation.c | 1 + - daemons/based/pacemaker-based.h | 4 +++ - include/crm/cib/cib_types.h | 3 +++ - include/crm/cib/internal.h | 2 ++ - lib/cib/cib_client.c | 15 ++++++++++++ - lib/cib/cib_ops.c | 3 +++ - 7 files changed, 71 insertions(+) - -diff --git a/daemons/based/based_messages.c b/daemons/based/based_messages.c -index 35d639a89..a87d9ac2e 100644 ---- a/daemons/based/based_messages.c -+++ b/daemons/based/based_messages.c -@@ -478,3 +478,46 @@ cib_process_commit_transaction(const char *op, int options, const char *section, - } - return pcmk_rc2legacy(rc); - } -+ -+int -+cib_process_schemas(const char *op, int options, const char *section, xmlNode *req, -+ xmlNode *input, xmlNode *existing_cib, xmlNode **result_cib, -+ xmlNode **answer) -+{ -+ xmlNode *data = NULL; -+ const char *after_ver = NULL; -+ GList *schemas = NULL; -+ GList *already_included = NULL; -+ -+ *answer = create_xml_node(NULL, PCMK__XA_SCHEMAS); -+ -+ data = get_message_xml(req, F_CIB_CALLDATA); -+ if (data == NULL) { -+ crm_warn("No data specified in request"); -+ return -EPROTO; -+ } -+ -+ after_ver = crm_element_value(data, XML_ATTR_VERSION); -+ if (after_ver == NULL) { -+ crm_warn("No version specified in request"); -+ return -EPROTO; -+ } -+ -+ /* The client requested all schemas after the latest one we know about, which -+ * means the client is fully up-to-date. Return a properly formatted reply -+ * with no schemas. -+ */ -+ if (pcmk__str_eq(after_ver, xml_latest_schema(), pcmk__str_none)) { -+ return pcmk_ok; -+ } -+ -+ schemas = pcmk__schema_files_later_than(after_ver); -+ -+ for (GList *iter = schemas; iter != NULL; iter = iter->next) { -+ pcmk__build_schema_xml_node(*answer, iter->data, &already_included); -+ } -+ -+ g_list_free_full(schemas, free); -+ g_list_free_full(already_included, free); -+ return pcmk_ok; -+} -diff --git a/daemons/based/based_operation.c b/daemons/based/based_operation.c -index 736d425e3..8dd07af93 100644 ---- a/daemons/based/based_operation.c -+++ b/daemons/based/based_operation.c -@@ -35,6 +35,7 @@ static const cib__op_fn_t cib_op_functions[] = { - [cib__op_sync_all] = cib_process_sync, - [cib__op_sync_one] = cib_process_sync_one, - [cib__op_upgrade] = cib_process_upgrade_server, -+ [cib__op_schemas] = cib_process_schemas, - }; - - /*! -diff --git a/daemons/based/pacemaker-based.h b/daemons/based/pacemaker-based.h -index 33c7642c5..de24779ac 100644 ---- a/daemons/based/pacemaker-based.h -+++ b/daemons/based/pacemaker-based.h -@@ -122,6 +122,10 @@ int cib_process_commit_transaction(const char *op, int options, - const char *section, xmlNode *req, - xmlNode *input, xmlNode *existing_cib, - xmlNode **result_cib, xmlNode **answer); -+int cib_process_schemas(const char *op, int options, const char *section, -+ xmlNode *req, xmlNode *input, xmlNode *existing_cib, -+ xmlNode **result_cib, xmlNode **answer); -+ - void send_sync_request(const char *host); - int sync_our_cib(xmlNode *request, gboolean all); - -diff --git a/include/crm/cib/cib_types.h b/include/crm/cib/cib_types.h -index a803311c2..bebe770ed 100644 ---- a/include/crm/cib/cib_types.h -+++ b/include/crm/cib/cib_types.h -@@ -324,6 +324,9 @@ typedef struct cib_api_operations_s { - * processing requests - */ - void (*set_user)(cib_t *cib, const char *user); -+ -+ int (*fetch_schemas)(cib_t *cib, xmlNode **output_data, const char *after_ver, -+ int call_options); - } cib_api_operations_t; - - struct cib_s { -diff --git a/include/crm/cib/internal.h b/include/crm/cib/internal.h -index 20059ec7e..9d54d52b7 100644 ---- a/include/crm/cib/internal.h -+++ b/include/crm/cib/internal.h -@@ -32,6 +32,7 @@ - #define PCMK__CIB_REQUEST_NOOP "noop" - #define PCMK__CIB_REQUEST_SHUTDOWN "cib_shutdown_req" - #define PCMK__CIB_REQUEST_COMMIT_TRANSACT "cib_commit_transact" -+#define PCMK__CIB_REQUEST_SCHEMAS "cib_schemas" - - # define F_CIB_CLIENTID "cib_clientid" - # define F_CIB_CALLOPTS "cib_callopt" -@@ -110,6 +111,7 @@ enum cib__op_type { - cib__op_sync_all, - cib__op_sync_one, - cib__op_upgrade, -+ cib__op_schemas, - }; - - gboolean cib_diff_version_details(xmlNode * diff, int *admin_epoch, int *epoch, int *updates, -diff --git a/lib/cib/cib_client.c b/lib/cib/cib_client.c -index 32e1f83c5..a2fcabbca 100644 ---- a/lib/cib/cib_client.c -+++ b/lib/cib/cib_client.c -@@ -451,6 +451,19 @@ cib_client_end_transaction(cib_t *cib, bool commit, int call_options) - return rc; - } - -+static int -+cib_client_fetch_schemas(cib_t *cib, xmlNode **output_data, const char *after_ver, -+ int call_options) -+{ -+ xmlNode *data = create_xml_node(NULL, PCMK__XA_SCHEMA); -+ -+ crm_xml_add(data, XML_ATTR_VERSION, after_ver); -+ -+ return cib_internal_op(cib, PCMK__CIB_REQUEST_SCHEMAS, NULL, NULL, data, -+ output_data, call_options, NULL); -+ -+} -+ - static void - cib_client_set_user(cib_t *cib, const char *user) - { -@@ -736,6 +749,8 @@ cib_new_variant(void) - - new_cib->cmds->set_user = cib_client_set_user; - -+ new_cib->cmds->fetch_schemas = cib_client_fetch_schemas; -+ - return new_cib; - } - -diff --git a/lib/cib/cib_ops.c b/lib/cib/cib_ops.c -index c324304b9..2165d8af3 100644 ---- a/lib/cib/cib_ops.c -+++ b/lib/cib/cib_ops.c -@@ -127,6 +127,9 @@ static const cib__operation_t cib_ops[] = { - |cib__op_attr_writes_through - |cib__op_attr_transaction - }, -+ { -+ PCMK__CIB_REQUEST_SCHEMAS, cib__op_schemas, cib__op_attr_local -+ } - }; - - /*! --- -2.41.0 - -From e8076b4a387ee758508f0683739b3e880f79db47 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Thu, 14 Sep 2023 15:48:31 -0400 -Subject: [PATCH 10/15] Refactor: libcrmcommon: Add - pcmk__load_schemas_from_dir. - -This just moves the bulk of crm_schema_init out into its own function, -allowing us to call it multiple times as other schema directories -appear. - -There is no similar function for unloading schemas from a directory. If -you want to do that, you'll have to use crm_schema_cleanup to unload -everything and start over. - -This function has not been tested for the possibility that the same -schema files exist in multiple directories. It is assumed that it won't -be used like that. ---- - include/crm/common/internal.h | 1 + - lib/common/schemas.c | 83 +++++++++++++++++++---------------- - 2 files changed, 45 insertions(+), 39 deletions(-) - -diff --git a/include/crm/common/internal.h b/include/crm/common/internal.h -index 318003efe..542d0a67c 100644 ---- a/include/crm/common/internal.h -+++ b/include/crm/common/internal.h -@@ -134,6 +134,7 @@ bool pcmk__procfs_has_pids(void); - void crm_schema_init(void); - void crm_schema_cleanup(void); - -+void pcmk__load_schemas_from_dir(const char *dir); - GList *pcmk__schema_files_later_than(const char *name); - void pcmk__build_schema_xml_node(xmlNode *parent, const char *name, - GList **already_included); -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index 1bcdff031..a0c844131 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -406,6 +406,49 @@ wrap_libxslt(bool finalize) - } - } - -+void -+pcmk__load_schemas_from_dir(const char *dir) -+{ -+ int lpc, max; -+ struct dirent **namelist = NULL; -+ -+ max = scandir(dir, &namelist, schema_filter, schema_cmp_directory); -+ if (max < 0) { -+ crm_warn("Could not load schemas from %s: %s", dir, strerror(errno)); -+ return; -+ } -+ -+ for (lpc = 0; lpc < max; lpc++) { -+ bool transform_expected = false; -+ pcmk__schema_version_t version = SCHEMA_ZERO; -+ -+ if (!version_from_filename(namelist[lpc]->d_name, &version)) { -+ // Shouldn't be possible, but makes static analysis happy -+ crm_warn("Skipping schema '%s': could not parse version", -+ namelist[lpc]->d_name); -+ continue; -+ } -+ if ((lpc + 1) < max) { -+ pcmk__schema_version_t next_version = SCHEMA_ZERO; -+ -+ if (version_from_filename(namelist[lpc+1]->d_name, &next_version) -+ && (version.v[0] < next_version.v[0])) { -+ transform_expected = true; -+ } -+ } -+ -+ if (add_schema_by_version(&version, transform_expected) != pcmk_rc_ok) { -+ break; -+ } -+ } -+ -+ for (lpc = 0; lpc < max; lpc++) { -+ free(namelist[lpc]); -+ } -+ -+ free(namelist); -+} -+ - /*! - * \internal - * \brief Load pacemaker schemas into cache -@@ -416,50 +459,12 @@ wrap_libxslt(bool finalize) - void - crm_schema_init(void) - { -- int lpc, max; - char *base = pcmk__xml_artefact_root(pcmk__xml_artefact_ns_legacy_rng); -- struct dirent **namelist = NULL; - const pcmk__schema_version_t zero = SCHEMA_ZERO; - - wrap_libxslt(false); - -- max = scandir(base, &namelist, schema_filter, schema_cmp_directory); -- if (max < 0) { -- crm_notice("scandir(%s) failed: %s (%d)", base, strerror(errno), errno); -- free(base); -- -- } else { -- free(base); -- for (lpc = 0; lpc < max; lpc++) { -- bool transform_expected = FALSE; -- pcmk__schema_version_t version = SCHEMA_ZERO; -- -- if (!version_from_filename(namelist[lpc]->d_name, &version)) { -- // Shouldn't be possible, but makes static analysis happy -- crm_err("Skipping schema '%s': could not parse version", -- namelist[lpc]->d_name); -- continue; -- } -- if ((lpc + 1) < max) { -- pcmk__schema_version_t next_version = SCHEMA_ZERO; -- -- if (version_from_filename(namelist[lpc+1]->d_name, &next_version) -- && (version.v[0] < next_version.v[0])) { -- transform_expected = TRUE; -- } -- } -- -- if (add_schema_by_version(&version, transform_expected) -- == ENOENT) { -- break; -- } -- } -- -- for (lpc = 0; lpc < max; lpc++) { -- free(namelist[lpc]); -- } -- free(namelist); -- } -+ pcmk__load_schemas_from_dir(base); - - // @COMPAT: Deprecated since 2.1.5 - add_schema(pcmk__schema_validator_rng, &zero, "pacemaker-next", --- -2.41.0 - -From 5b40f0227b33edd6be65515dcc1af4eb656b78e7 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Thu, 14 Sep 2023 11:24:36 -0400 -Subject: [PATCH 11/15] Feature: daemons: Download newer schema files to a - remote executor. - -If the remote executor supports an older version of the CIB schema than -the rest of the cluster, various operations could fail due to the schema -not validating on the remote node. - -Instead, ask the CIB manager for the updated schema files, store them in -a separate directory on the remote node, and add them to the list of -known schema files. These can then be used just like the packaged -schema files. - -Note that while this is a fairly large patch, it's really just a lot of -boring IO and file management code, which requires a lot of error -handling. - -Fixes T182 ---- - daemons/execd/Makefile.am | 4 +- - daemons/execd/execd_commands.c | 6 + - daemons/execd/pacemaker-execd.h | 1 + - daemons/execd/remoted_schemas.c | 282 ++++++++++++++++++++++++++++++++ - 4 files changed, 292 insertions(+), 1 deletion(-) - create mode 100644 daemons/execd/remoted_schemas.c - -diff --git a/daemons/execd/Makefile.am b/daemons/execd/Makefile.am -index ab8544f9d..ce0e16126 100644 ---- a/daemons/execd/Makefile.am -+++ b/daemons/execd/Makefile.am -@@ -44,12 +44,14 @@ pacemaker_remoted_LDFLAGS = $(LDFLAGS_HARDENED_EXE) - - pacemaker_remoted_LDADD = $(top_builddir)/lib/fencing/libstonithd.la - pacemaker_remoted_LDADD += $(top_builddir)/lib/services/libcrmservice.la -+pacemaker_remoted_LDADD += $(top_builddir)/lib/cib/libcib.la - pacemaker_remoted_LDADD += $(top_builddir)/lib/lrmd/liblrmd.la - pacemaker_remoted_LDADD += $(top_builddir)/lib/common/libcrmcommon.la - pacemaker_remoted_SOURCES = $(pacemaker_execd_SOURCES) \ - remoted_tls.c \ - remoted_pidone.c \ -- remoted_proxy.c -+ remoted_proxy.c \ -+ remoted_schemas.c - endif - - cts_exec_helper_LDADD = $(top_builddir)/lib/pengine/libpe_status.la -diff --git a/daemons/execd/execd_commands.c b/daemons/execd/execd_commands.c -index cf4503a25..1601efb0b 100644 ---- a/daemons/execd/execd_commands.c -+++ b/daemons/execd/execd_commands.c -@@ -1493,9 +1493,15 @@ process_lrmd_signon(pcmk__client_t *client, xmlNode *request, int call_id, - if ((client->remote != NULL) - && pcmk_is_set(client->flags, - pcmk__client_tls_handshake_complete)) { -+ const char *op = crm_element_value(request, F_LRMD_OPERATION); - - // This is a remote connection from a cluster node's controller - ipc_proxy_add_provider(client); -+ -+ /* If this was a register operation, also ask for new schema files. */ -+ if (pcmk__str_eq(op, CRM_OP_REGISTER, pcmk__str_none)) { -+ remoted_request_cib_schema_files(); -+ } - } else { - rc = -EACCES; - } -diff --git a/daemons/execd/pacemaker-execd.h b/daemons/execd/pacemaker-execd.h -index 9c1d173f5..6fb8ef440 100644 ---- a/daemons/execd/pacemaker-execd.h -+++ b/daemons/execd/pacemaker-execd.h -@@ -101,6 +101,7 @@ void ipc_proxy_forward_client(pcmk__client_t *client, xmlNode *xml); - pcmk__client_t *ipc_proxy_get_provider(void); - int ipc_proxy_shutdown_req(pcmk__client_t *ipc_proxy); - void remoted_spawn_pidone(int argc, char **argv, char **envp); -+void remoted_request_cib_schema_files(void); - #endif - - int process_lrmd_alert_exec(pcmk__client_t *client, uint32_t id, -diff --git a/daemons/execd/remoted_schemas.c b/daemons/execd/remoted_schemas.c -new file mode 100644 -index 000000000..d501fa495 ---- /dev/null -+++ b/daemons/execd/remoted_schemas.c -@@ -0,0 +1,282 @@ -+/* -+ * 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 Lesser General Public License -+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. -+ */ -+ -+#include -+ -+#include -+#include -+#include -+ -+#include -+#include -+#include -+#include -+#include -+#include -+ -+#include "pacemaker-execd.h" -+ -+static pid_t schema_fetch_pid = 0; -+ -+static int -+rm_files(const char *pathname, const struct stat *sbuf, int type, struct FTW *ftwb) -+{ -+ /* Don't delete PCMK__REMOTE_SCHEMA_DIR . */ -+ if (ftwb->level == 0) { -+ return 0; -+ } -+ -+ if (remove(pathname) != 0) { -+ int rc = errno; -+ crm_err("Could not remove %s: %s", pathname, pcmk_rc_str(rc)); -+ return -1; -+ } -+ -+ return 0; -+} -+ -+static void -+clean_up_extra_schema_files(void) -+{ -+ const char *remote_schema_dir = pcmk__remote_schema_dir(); -+ struct stat sb; -+ int rc; -+ -+ rc = stat(remote_schema_dir, &sb); -+ -+ if (rc == -1) { -+ if (errno == ENOENT) { -+ /* If the directory doesn't exist, try to make it first. */ -+ if (mkdir(remote_schema_dir, 0755) != 0) { -+ rc = errno; -+ crm_err("Could not create directory for schemas: %s", -+ pcmk_rc_str(rc)); -+ } -+ -+ } else { -+ rc = errno; -+ crm_err("Could not create directory for schemas: %s", -+ pcmk_rc_str(rc)); -+ } -+ -+ } else if (!S_ISDIR(sb.st_mode)) { -+ /* If something exists with the same name that's not a directory, that's -+ * an error. -+ */ -+ crm_err("%s already exists but is not a directory", remote_schema_dir); -+ -+ } else { -+ /* It's a directory - clear it out so we can download potentially new -+ * schema files. -+ */ -+ rc = nftw(remote_schema_dir, rm_files, 10, FTW_DEPTH|FTW_MOUNT|FTW_PHYS); -+ -+ if (rc != 0) { -+ crm_err("Could not remove %s: %s", remote_schema_dir, pcmk_rc_str(rc)); -+ } -+ } -+} -+ -+static void -+write_extra_schema_file(xmlNode *xml, void *user_data) -+{ -+ const char *remote_schema_dir = pcmk__remote_schema_dir(); -+ const char *file = NULL; -+ char *path = NULL; -+ int rc; -+ -+ file = crm_element_value(xml, PCMK__XA_PATH); -+ if (file == NULL) { -+ crm_warn("No destination path given in schema request"); -+ return; -+ } -+ -+ path = crm_strdup_printf("%s/%s", remote_schema_dir, file); -+ -+ /* The schema is a CDATA node, which is a child of the node. Traverse -+ * all children and look for the first CDATA child. There can't be more than -+ * one because we only have one file attribute on the parent. -+ */ -+ for (xmlNode *child = xml->children; child != NULL; child = child->next) { -+ FILE *stream = NULL; -+ -+ if (child->type != XML_CDATA_SECTION_NODE) { -+ continue; -+ } -+ -+ stream = fopen(path, "w+"); -+ if (stream == NULL) { -+ crm_warn("Could not write schema file %s: %s", path, strerror(errno)); -+ } else { -+ rc = fprintf(stream, "%s", child->content); -+ -+ if (rc < 0) { -+ crm_warn("Could not write schema file %s: %s", path, strerror(errno)); -+ } -+ -+ fclose(stream); -+ } -+ -+ break; -+ } -+ -+ free(path); -+} -+ -+static void -+get_schema_files(void) -+{ -+ int rc = pcmk_rc_ok; -+ cib_t *cib = NULL; -+ xmlNode *reply; -+ -+ cib = cib_new(); -+ if (cib == NULL) { -+ _exit(ENOTCONN); -+ } -+ -+ rc = cib->cmds->signon(cib, crm_system_name, cib_query); -+ if (rc != pcmk_ok) { -+ crm_err("Could not connect to the CIB manager: %s", pcmk_strerror(rc)); -+ _exit(pcmk_rc2exitc(rc)); -+ } -+ -+ rc = cib->cmds->fetch_schemas(cib, &reply, xml_latest_schema(), cib_sync_call); -+ if (rc != pcmk_ok) { -+ crm_err("Could not get schema files: %s", pcmk_strerror(rc)); -+ rc = pcmk_legacy2rc(rc); -+ -+ } else if (reply->children != NULL) { -+ /* The returned document looks something like this: -+ * -+ * -+ * -+ * -+ * -+ * -+ * -+ * -+ * ... -+ * -+ * -+ * -+ * ... -+ * -+ * -+ * -+ * -+ * All the and tags are really just there for organizing -+ * the XML a little better. What we really care about are the nodes, -+ * and specifically the path attributes and the CDATA children (not shown) -+ * of each. We can use an xpath query to reach down and get all the -+ * nodes at once. -+ * -+ * If we already have the latest schema version, or we asked for one later -+ * than what the cluster supports, we'll get back an empty node, -+ * so all this will continue to work. It just won't do anything. -+ */ -+ crm_foreach_xpath_result(reply, "//" PCMK__XA_FILE, write_extra_schema_file, NULL); -+ } -+ -+ cib__clean_up_connection(&cib); -+ _exit(pcmk_rc2exitc(rc)); -+} -+ -+/* Load any additional schema files when the child is finished fetching and -+ * saving them to disk. -+ */ -+static void -+get_schema_files_complete(mainloop_child_t *p, pid_t pid, int core, int signo, int exitcode) -+{ -+ const char *errmsg = "Could not load additional schema files"; -+ -+ if ((signo == 0) && (exitcode == 0)) { -+ const char *remote_schema_dir = pcmk__remote_schema_dir(); -+ -+ /* Don't just crm_schema_init here because that will load the base -+ * schemas again too. Instead just load the things we fetched. -+ */ -+ pcmk__load_schemas_from_dir(remote_schema_dir); -+ crm_info("Fetching extra schema files completed successfully"); -+ -+ } else { -+ if (signo == 0) { -+ crm_err("%s: process %d exited %d", errmsg, (int) pid, exitcode); -+ -+ } else { -+ crm_err("%s: process %d terminated with signal %d (%s)%s", -+ errmsg, (int) pid, signo, strsignal(signo), -+ (core? " and dumped core" : "")); -+ } -+ -+ /* Clean up any incomplete schema data we might have been downloading when -+ * the process timed out or crashed. We don't need to do any extra cleanup -+ * because we never loaded the extra schemas, and we don't need to call -+ * crm_schema_init because that was called in remoted_request_cib_schema_files -+ * before this function. -+ */ -+ clean_up_extra_schema_files(); -+ } -+} -+ -+void -+remoted_request_cib_schema_files(void) -+{ -+ pid_t pid; -+ int rc; -+ -+ /* If a previous schema-fetch process is still running when we're called -+ * again, it's hung. Attempt to kill it before cleaning up the extra -+ * directory. -+ */ -+ if (schema_fetch_pid != 0) { -+ if (mainloop_child_kill(schema_fetch_pid) == FALSE) { -+ crm_warn("Unable to kill pre-existing schema-fetch process"); -+ return; -+ } -+ -+ schema_fetch_pid = 0; -+ } -+ -+ /* Clean up any extra schema files we downloaded from a previous cluster -+ * connection. After the files are gone, we need to wipe them from -+ * known_schemas, but there's no opposite operation for add_schema(). -+ * -+ * Instead, unload all the schemas. This means we'll also forget about all -+ * the installed schemas as well, which means that xml_latest_schema() will -+ * fail. So we need to load the base schemas right now. -+ */ -+ clean_up_extra_schema_files(); -+ crm_schema_cleanup(); -+ crm_schema_init(); -+ -+ crm_info("Fetching extra schema files from cluster"); -+ pid = fork(); -+ -+ switch (pid) { -+ case -1: { -+ rc = errno; -+ crm_warn("Could not spawn process to get schema files: %s", pcmk_rc_str(rc)); -+ break; -+ } -+ -+ case 0: -+ /* child */ -+ get_schema_files(); -+ break; -+ -+ default: -+ /* parent */ -+ schema_fetch_pid = pid; -+ mainloop_child_add_with_flags(pid, 5 * 60 * 1000, "schema-fetch", NULL, -+ mainloop_leave_pid_group, -+ get_schema_files_complete); -+ break; -+ } -+} --- -2.41.0 - -From b05fef32cf1f9063e01db5108f95386be329d778 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Wed, 29 Nov 2023 10:04:57 -0500 -Subject: [PATCH 12/15] Feature: libcrmcommon: Load additional schema files in - crm_schema_init. - -If the /var/lib/pacemaker/schemas directory exists, load any extra -schemas from it when we init. This makes them available for command -line programs to use. ---- - lib/common/schemas.c | 2 ++ - 1 file changed, 2 insertions(+) - -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index a0c844131..68d79cfc7 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -459,12 +459,14 @@ pcmk__load_schemas_from_dir(const char *dir) - void - crm_schema_init(void) - { -+ const char *remote_schema_dir = pcmk__remote_schema_dir(); - char *base = pcmk__xml_artefact_root(pcmk__xml_artefact_ns_legacy_rng); - const pcmk__schema_version_t zero = SCHEMA_ZERO; - - wrap_libxslt(false); - - pcmk__load_schemas_from_dir(base); -+ pcmk__load_schemas_from_dir(remote_schema_dir); - - // @COMPAT: Deprecated since 2.1.5 - add_schema(pcmk__schema_validator_rng, &zero, "pacemaker-next", --- -2.41.0 - -From 7454a8400238301848cc6694f5413fdb19d5834e Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Fri, 29 Sep 2023 10:41:51 -0400 -Subject: [PATCH 13/15] Refactor: daemons: Remove redundant includes from - remoted_tls.c. - ---- - daemons/execd/remoted_tls.c | 2 -- - 1 file changed, 2 deletions(-) - -diff --git a/daemons/execd/remoted_tls.c b/daemons/execd/remoted_tls.c -index 23a2dcf45..978edfdd3 100644 ---- a/daemons/execd/remoted_tls.c -+++ b/daemons/execd/remoted_tls.c -@@ -12,8 +12,6 @@ - #include - #include - --#include --#include - #include - #include - #include --- -2.41.0 - -From c6b5f73a013515d6acd818d348d011e38f3d8e0e Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Fri, 27 Oct 2023 15:23:41 -0400 -Subject: [PATCH 14/15] Refactor: daemons: Keep XML schemas sorted. - -Now that we can add schemas multiple times (including after next/none -have been added in the initial set), we've broken the assumption that -schemas are always sorted by scandir. - -This introduces a function that can be called wherever to keep them -sorted. It should be called pretty much whenever -pcmk__load_schemas_from_dir is called. ---- - daemons/execd/remoted_schemas.c | 1 + - include/crm/common/xml_internal.h | 1 + - lib/common/schemas.c | 40 +++++++++++++++++++++++++++++++ - 3 files changed, 42 insertions(+) - -diff --git a/daemons/execd/remoted_schemas.c b/daemons/execd/remoted_schemas.c -index d501fa495..eed43dfa9 100644 ---- a/daemons/execd/remoted_schemas.c -+++ b/daemons/execd/remoted_schemas.c -@@ -203,6 +203,7 @@ get_schema_files_complete(mainloop_child_t *p, pid_t pid, int core, int signo, i - * schemas again too. Instead just load the things we fetched. - */ - pcmk__load_schemas_from_dir(remote_schema_dir); -+ pcmk__sort_schemas(); - crm_info("Fetching extra schema files completed successfully"); - - } else { -diff --git a/include/crm/common/xml_internal.h b/include/crm/common/xml_internal.h -index cb27ec6b2..9b50f4e72 100644 ---- a/include/crm/common/xml_internal.h -+++ b/include/crm/common/xml_internal.h -@@ -448,5 +448,6 @@ gboolean pcmk__validate_xml(xmlNode *xml_blob, const char *validation, - - void pcmk__log_known_schemas(void); - const char *pcmk__remote_schema_dir(void); -+void pcmk__sort_schemas(void); - - #endif // PCMK__XML_INTERNAL__H -diff --git a/lib/common/schemas.c b/lib/common/schemas.c -index 68d79cfc7..fd6202f62 100644 ---- a/lib/common/schemas.c -+++ b/lib/common/schemas.c -@@ -449,6 +449,41 @@ pcmk__load_schemas_from_dir(const char *dir) - free(namelist); - } - -+static gint -+schema_sort_GCompareFunc(gconstpointer a, gconstpointer b) -+{ -+ const pcmk__schema_t *schema_a = a; -+ const pcmk__schema_t *schema_b = b; -+ -+ if (pcmk__str_eq(schema_a->name, "pacemaker-next", pcmk__str_none)) { -+ if (pcmk__str_eq(schema_b->name, "none", pcmk__str_none)) { -+ return -1; -+ } else { -+ return 1; -+ } -+ } else if (pcmk__str_eq(schema_a->name, "none", pcmk__str_none)) { -+ return 1; -+ } else if (pcmk__str_eq(schema_b->name, "pacemaker-next", pcmk__str_none)) { -+ return -1; -+ } else { -+ return schema_cmp(schema_a->version, schema_b->version); -+ } -+} -+ -+/*! -+ * \internal -+ * \brief Sort the list of known schemas such that all pacemaker-X.Y are in -+ * version order, then pacemaker-next, then none -+ * -+ * This function should be called whenever additional schemas are loaded using -+ * pcmk__load_schemas_from_dir(), after the initial sets in crm_schema_init(). -+ */ -+void -+pcmk__sort_schemas(void) -+{ -+ known_schemas = g_list_sort(known_schemas, schema_sort_GCompareFunc); -+} -+ - /*! - * \internal - * \brief Load pacemaker schemas into cache -@@ -474,6 +509,11 @@ crm_schema_init(void) - - add_schema(pcmk__schema_validator_none, &zero, PCMK__VALUE_NONE, - NULL, NULL, FALSE); -+ -+ /* This shouldn't be strictly necessary, but we'll do it here just in case -+ * there's anything in PCMK__REMOTE_SCHEMA_DIR that messes up the order. -+ */ -+ pcmk__sort_schemas(); - } - - static gboolean --- -2.41.0 - -From d6a535ca43db202bd01a2e085b58722ed1abcdb0 Mon Sep 17 00:00:00 2001 -From: Chris Lumens -Date: Mon, 30 Oct 2023 12:59:13 -0400 -Subject: [PATCH 15/15] Feature: daemons: Only ask for schemas if supported by - the server - -We can use LRMD_PROTOCOL_VERSION in the handshake to determine what the -server supports, similar to what is being done in attrd. Add a macro to -compare the version we were given with the known minimum version that -supports the schema transfer command. - -Additionally, introduce LRMD_COMPATIBLE_PROTOCOL which is just the major -version number required for the connection to succeed. This gets rid of -the need for LRMD_MIN_PROTOCOL_VERSION, which can now be deprecated. - -And then since I wasn't sure compare_version would work if you give it a -full version number and just a major version, add a unit test for that. ---- - daemons/execd/execd_commands.c | 11 +++++---- - include/crm/lrmd.h | 23 +++++++++++++++---- - lib/common/tests/utils/compare_version_test.c | 5 +++- - 3 files changed, 30 insertions(+), 9 deletions(-) - -diff --git a/daemons/execd/execd_commands.c b/daemons/execd/execd_commands.c -index 1601efb0b..4ec4d03d6 100644 ---- a/daemons/execd/execd_commands.c -+++ b/daemons/execd/execd_commands.c -@@ -1482,9 +1482,9 @@ process_lrmd_signon(pcmk__client_t *client, xmlNode *request, int call_id, - const char *protocol_version = crm_element_value(request, F_LRMD_PROTOCOL_VERSION); - const char *start_state = pcmk__env_option(PCMK__ENV_NODE_START_STATE); - -- if (compare_version(protocol_version, LRMD_MIN_PROTOCOL_VERSION) < 0) { -+ if (compare_version(protocol_version, LRMD_COMPATIBLE_PROTOCOL) < 0) { - crm_err("Cluster API version must be greater than or equal to %s, not %s", -- LRMD_MIN_PROTOCOL_VERSION, protocol_version); -+ LRMD_COMPATIBLE_PROTOCOL, protocol_version); - rc = -EPROTO; - } - -@@ -1498,8 +1498,11 @@ process_lrmd_signon(pcmk__client_t *client, xmlNode *request, int call_id, - // This is a remote connection from a cluster node's controller - ipc_proxy_add_provider(client); - -- /* If this was a register operation, also ask for new schema files. */ -- if (pcmk__str_eq(op, CRM_OP_REGISTER, pcmk__str_none)) { -+ /* If this was a register operation, also ask for new schema files but -+ * only if it's supported by the protocol version. -+ */ -+ if (pcmk__str_eq(op, CRM_OP_REGISTER, pcmk__str_none) && -+ LRMD_SUPPORTS_SCHEMA_XFER(protocol_version)) { - remoted_request_cib_schema_files(); - } - } else { -diff --git a/include/crm/lrmd.h b/include/crm/lrmd.h -index 0c5a40b62..948a3b1fc 100644 ---- a/include/crm/lrmd.h -+++ b/include/crm/lrmd.h -@@ -33,12 +33,27 @@ typedef struct lrmd_key_value_s { - struct lrmd_key_value_s *next; - } lrmd_key_value_t; - --/* This should be bumped every time there is an incompatible change that -- * prevents older clients from connecting to this version of the server. -+/* The major version should be bumped every time there is an incompatible -+ * change that prevents older clients from connecting to this version of -+ * the server. The minor version indicates feature support. -+ * -+ * Protocol Pacemaker Significant changes -+ * -------- --------- ------------------- -+ * 1.2 2.1.7 PCMK__CIB_REQUEST_SCHEMAS - */ --#define LRMD_PROTOCOL_VERSION "1.1" -+#define LRMD_PROTOCOL_VERSION "1.2" -+ -+#define LRMD_SUPPORTS_SCHEMA_XFER(x) (compare_version((x), "1.2") >= 0) - --/* This is the version that the client version will actually be compared -+/* The major protocol version the client and server both need to support for -+ * the connection to be successful. This should only ever be the major -+ * version - not a complete version number. -+ */ -+#define LRMD_COMPATIBLE_PROTOCOL "1" -+ -+/* \deprecated Do not use (will be removed in a future release) -+ * -+ * This is the version that the client version will actually be compared - * against. This should be identical to LRMD_PROTOCOL_VERSION. However, we - * accidentally bumped LRMD_PROTOCOL_VERSION in 6424a647 (1.1.15) when we didn't - * need to, so for now it's different. If we ever have a truly incompatible -diff --git a/lib/common/tests/utils/compare_version_test.c b/lib/common/tests/utils/compare_version_test.c -index 35ebb63c6..d191f4abb 100644 ---- a/lib/common/tests/utils/compare_version_test.c -+++ b/lib/common/tests/utils/compare_version_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. - * -@@ -46,6 +46,9 @@ shorter_versions(void **state) - { - assert_int_equal(compare_version("1.0", "1.0.1"), -1); - assert_int_equal(compare_version("1.0.1", "1.0"), 1); -+ assert_int_equal(compare_version("1.0", "1"), 0); -+ assert_int_equal(compare_version("1", "1.2"), -1); -+ assert_int_equal(compare_version("1.2", "1"), 1); - } - - PCMK__UNIT_TEST(NULL, NULL, --- -2.41.0 - diff --git a/Add_replace_for_PCMK__REMOTE_SCHEMA_DIR.patch b/Add_replace_for_PCMK__REMOTE_SCHEMA_DIR.patch deleted file mode 100644 index 293e50f2cfac7d9af79bd5e726fad8dac4863c5c..0000000000000000000000000000000000000000 --- a/Add_replace_for_PCMK__REMOTE_SCHEMA_DIR.patch +++ /dev/null @@ -1,42 +0,0 @@ -From a3bffc7c66bf6f796f977cffd44f223635b008c5 Mon Sep 17 00:00:00 2001 -From: Reid Wahl -Date: Wed, 20 Dec 2023 13:33:47 -0800 -Subject: [PATCH] Doc: Pacemaker Explained: Add replace for - PCMK__REMOTE_SCHEMA_DIR - -So that the existing use in local-options.rst expands correctly. - -Signed-off-by: Reid Wahl ---- - doc/sphinx/Makefile.am | 1 + - doc/sphinx/conf.py.in | 1 + - 3 files changed, 2 insertions(+) - create mode 100644 doc/sphinx/conf.py.in.rej - -diff --git a/doc/sphinx/Makefile.am b/doc/sphinx/Makefile.am -index e48e19a..d0309ff 100644 ---- a/doc/sphinx/Makefile.am -+++ b/doc/sphinx/Makefile.am -@@ -134,6 +134,7 @@ $(BOOKS:%=%/conf.py): conf.py.in - -e 's#%CRM_SCHEMA_DIRECTORY%#@CRM_SCHEMA_DIRECTORY@#g' \ - -e 's#%PACEMAKER_CONFIG_DIR%#@PACEMAKER_CONFIG_DIR@#g' \ - -e 's#%PCMK_GNUTLS_PRIORITIES%#@PCMK_GNUTLS_PRIORITIES@#g' \ -+ -e 's#%PCMK__REMOTE_SCHEMA_DIR%#@PCMK__REMOTE_SCHEMA_DIR@#g' \ - $(<) > "$@" - - $(BOOK)/_build: $(STATIC_FILES) $(BOOK)/conf.py $(DEPS_$(BOOK)) $(wildcard $(srcdir)/$(BOOK)/*.rst) -diff --git a/doc/sphinx/conf.py.in b/doc/sphinx/conf.py.in -index 556eb72..511f029 100644 ---- a/doc/sphinx/conf.py.in -+++ b/doc/sphinx/conf.py.in -@@ -40,6 +40,7 @@ rst_prolog=""" - .. |PCMK_INIT_ENV_FILE| replace:: ``%PACEMAKER_CONFIG_DIR%/pcmk-init.env`` - .. |PCMK_LOG_FILE| replace:: %CRM_LOG_DIR%/pacemaker.log - .. |PCMK_GNUTLS_PRIORITIES| replace:: %PCMK_GNUTLS_PRIORITIES% -+.. |PCMK__REMOTE_SCHEMA_DIR| replace:: %PCMK__REMOTE_SCHEMA_DIR% - .. |REMOTE_DISTRO| replace:: AlmaLinux - .. |REMOTE_DISTRO_VER| replace:: 9 - """ --- -2.31.1 - diff --git a/Doc-HealthSMART-fix-the-description-of-temp_lower.patch b/Doc-HealthSMART-fix-the-description-of-temp_lower.patch deleted file mode 100644 index 4abc518bc28ea39d9d74293cbf70aeeb1f03ddac..0000000000000000000000000000000000000000 --- a/Doc-HealthSMART-fix-the-description-of-temp_lower.patch +++ /dev/null @@ -1,25 +0,0 @@ -From d40544c0a1d50645b171f366ccae8b2a1090cf6a Mon Sep 17 00:00:00 2001 -From: bixiaoyan1 -Date: Wed, 20 Mar 2024 10:12:56 +0800 -Subject: [PATCH] Doc: HealthSMART:fix the description of temp_lower_limit - ---- - agents/ocf/HealthSMART.in | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/agents/ocf/HealthSMART.in b/agents/ocf/HealthSMART.in -index 0871ab91a..f688f7381 100755 ---- a/agents/ocf/HealthSMART.in -+++ b/agents/ocf/HealthSMART.in -@@ -80,7 +80,7 @@ The device type(s) to assume for the drive(s) being tested as a SPACE separated - - - --Lower limit of the temperature in deg C of the drive(s). Below this limit the status will be red. -+Lower limit of the temperature in deg C of the drive(s). Below this limit the status of #health-smart will be red. - - Lower limit for the red smart attribute - --- -2.25.1 - diff --git a/Fix-cibsecret-Use-ps-axww-to-avoid-truncating-issue.patch b/Fix-cibsecret-Use-ps-axww-to-avoid-truncating-issue.patch deleted file mode 100644 index 1b9c9b1c82dbfd6864e1ac5d2abbff3fb8646ece..0000000000000000000000000000000000000000 --- a/Fix-cibsecret-Use-ps-axww-to-avoid-truncating-issue.patch +++ /dev/null @@ -1,37 +0,0 @@ -From 581e1bf3850a5e6a972ea02198bbbf2d99b29873 Mon Sep 17 00:00:00 2001 -From: xin liang -Date: Wed, 6 Mar 2024 17:07:16 +0800 -Subject: [PATCH] Fix: cibsecret: Use 'ps axww' to avoid truncating issue - -When python program calling cibsecret with a small terminal width, -the command `ps -ef | grep '[p]acemaker-controld'` will return 1, see - ->>> cmd = "ps -ef | grep '[p]acemaker-controld' >/dev/null" ->>> # When terminal width is small ->>> subprocess.call(cmd, shell=True) -1 ->>> # When terminal is big enough ->>> subprocess.call(cmd, shell=True) -0 - -Use 'ps axww' can avoid this issue, also for BSD environment. ---- - tools/cibsecret.in | 2 +- - 1 file changed, 1 insertion(+), 1 deletion(-) - -diff --git a/tools/cibsecret.in b/tools/cibsecret.in -index 4569863af..9df420126 100644 ---- a/tools/cibsecret.in -+++ b/tools/cibsecret.in -@@ -171,7 +171,7 @@ check_env() { - else - fatal $CRM_EX_NOT_INSTALLED "please install pssh, pdsh, or ssh to run $PROG" - fi -- ps -ef | grep '[p]acemaker-controld' >/dev/null || -+ ps axww | grep '[p]acemaker-controld' >/dev/null || - fatal $CRM_EX_UNAVAILABLE "pacemaker not running? $PROG needs pacemaker" - } - --- -2.25.1 - diff --git a/Fix-libcrmcommon-avoid-file-descriptor-leak-in-IPC-c.patch b/Fix-libcrmcommon-avoid-file-descriptor-leak-in-IPC-c.patch deleted file mode 100644 index 3ff1b52f4a9e9f9342de889b18042acad9cd6c1b..0000000000000000000000000000000000000000 --- a/Fix-libcrmcommon-avoid-file-descriptor-leak-in-IPC-c.patch +++ /dev/null @@ -1,48 +0,0 @@ -From 47d6055bf418f7049fc716745be95374f465eb77 Mon Sep 17 00:00:00 2001 -From: "Gao,Yan" -Date: Wed, 7 Feb 2024 11:21:23 +0100 -Subject: [PATCH] Fix: libcrmcommon: avoid file descriptor leak in IPC client - with async connection - -Previously if qb_ipcc_connect_async() succeeded but the following poll() -failed, the file descriptor would leak. - -In that case, given that disconnect function is not registered yet, -qb_ipcc_disconnect() won't clean up the socket. In any case, call -qb_ipcc_connect_continue() here so that it may fail and do the cleanup -for us. - -Issue introduced in 2.1.3 by 4b60aa100. ---- - lib/common/ipc_client.c | 12 ++++++++---- - 1 file changed, 8 insertions(+), 4 deletions(-) - -diff --git a/lib/common/ipc_client.c b/lib/common/ipc_client.c -index 4635d38d8..df6697cee 100644 ---- a/lib/common/ipc_client.c -+++ b/lib/common/ipc_client.c -@@ -1623,13 +1623,17 @@ pcmk__ipc_is_authentic_process_active(const char *name, uid_t refuid, - do { - poll_rc = poll(&pollfd, 1, 2000); - } while ((poll_rc == -1) && (errno == EINTR)); -- if ((poll_rc <= 0) || (qb_ipcc_connect_continue(c) != 0)) { -+ -+ /* If poll() failed, given that disconnect function is not registered yet, -+ * qb_ipcc_disconnect() won't clean up the socket. In any case, call -+ * qb_ipcc_connect_continue() here so that it may fail and do the cleanup -+ * for us. -+ */ -+ if (qb_ipcc_connect_continue(c) != 0) { - crm_info("Could not connect to %s IPC: %s", name, - (poll_rc == 0)?"timeout":strerror(errno)); - rc = pcmk_rc_ipc_unresponsive; -- if (poll_rc > 0) { -- c = NULL; // qb_ipcc_connect_continue cleaned up for us -- } -+ c = NULL; // qb_ipcc_connect_continue cleaned up for us - goto bail; - } - #endif --- -2.25.1 - diff --git a/Fix-tools-crm_mon-segfaults-when-fencer-connection-is-lost.patch b/Fix-tools-crm_mon-segfaults-when-fencer-connection-is-lost.patch deleted file mode 100644 index 4d9c536ef543dcebf9bdeac7294c7ad8d082b08a..0000000000000000000000000000000000000000 --- a/Fix-tools-crm_mon-segfaults-when-fencer-connection-is-lost.patch +++ /dev/null @@ -1,84 +0,0 @@ -From 401f5d971f12db7792971aeec3aaba9f52d67626 Mon Sep 17 00:00:00 2001 -From: Reid Wahl -Date: Thu, 18 Jan 2024 00:11:17 -0800 -Subject: [PATCH] Fix: tools: crm_mon segfaults when fencer connection is lost - -This is easiest to observe when Pacemaker is stopping. - -When crm_mon is running in interactive mode (the default) and the -cluster is stopped, crm_mon crashes with a segmentation fault. This is a -regression that was introduced in Pacemaker 2.1.0 by commit bc91cc5. -However, for some reason the crash doesn't happen on all platforms. In -particular, I can reproduce the crash on Fedora 38 and 39, but not on -RHEL 9.3 or Fedora 37. This is independent of the Pacemaker version. - -The cause is a use-after-free. In detail, it is as follows: -1. crm_mon registers a notification via its stonith API client for - disconnect events. This notification will call either - mon_st_callback_event() or mon_st_callback_display(), depending on - the CLI options. Both of these callbacks call - mon_cib_connection_destroy() for disconnect notifications, so it - doesn't matter which one is used. -2. When the fencer connection is lost, the mainloop calls the stonith - API client's destroy callback (stonith_connection_destroy()). -3. stonith_connection_destroy() sets the state to stonith_disconnected - and calls foreach_notify_entry(..., stonith_send_notification, blob), - where blob contains a disconnect notification. -4. foreach_notify_entry() loops over all the registered notify entries, - calling stonith_send_notification(entry, blob) for each notify entry. -5. For each notify client that's subscribed to disconnect notifications, - stonith_send_notification() calls the registered callback function. -6. Based on the registration in step (1), stonith_send_notification() - synchronously calls mon_st_callback_event()/display() for crm_mon. -7. mon_st_callback_event()/display() calls mon_cib_connection_destroy(). -8. mon_cib_connection_destroy() calls stonith_api_delete(), which frees - the stonith API client and its members, including the notification - table. -9. Control returns to stonith_send_notification() and then back to - foreach_notify_entry(). -10. foreach_notify_entry() moves to the next entry in the list. But the - entire list was freed in step (8). So when it tries to access a - member of one of the entries, we get a segmentation fault. - -Commit bc91cc5 introduced the regression by deleting the stonith API -client in mon_cib_connection_destroy(). Prior to that, -mon_cib_connection_destroy() only disconnected the client and marked its -notify entries for removal. - -I audited the other uses of stonith_api_delete() in crm_mon and -elsewhere, and I believe they're safe in the sense that they're never -called while we're processing stonith notify callbacks. A function -should never be allowed to call stonith_api_delete() if the stonith API -client might be sending out notifications. If there are more -notifications in the table, attempts to access them will be a -use-after-free. - -Fixes T751 - -Signed-off-by: Reid Wahl ---- - tools/crm_mon.c | 8 ++++++-- - 1 file changed, 6 insertions(+), 2 deletions(-) - -diff --git a/tools/crm_mon.c b/tools/crm_mon.c -index 7789bfebf..19a2ead89 100644 ---- a/tools/crm_mon.c -+++ b/tools/crm_mon.c -@@ -854,8 +854,12 @@ mon_cib_connection_destroy(gpointer user_data) - /* the client API won't properly reconnect notifications if they are still - * in the table - so remove them - */ -- stonith_api_delete(st); -- st = NULL; -+ if (st != NULL) { -+ if (st->state != stonith_disconnected) { -+ st->cmds->disconnect(st); -+ } -+ st->cmds->remove_notification(st, NULL); -+ } - - if (cib) { - cib->cmds->signoff(cib); --- -2.25.1 - diff --git a/Improve-pacemaker-attrd-cache-management-and-logging.patch b/Improve-pacemaker-attrd-cache-management-and-logging.patch deleted file mode 100644 index dd617c44ddc2cc113d9b0c36f8424b5fafac0650..0000000000000000000000000000000000000000 --- a/Improve-pacemaker-attrd-cache-management-and-logging.patch +++ /dev/null @@ -1,1443 +0,0 @@ -From 543a1e9b6f22f13956a8ef22b20c8fe93dad7ae9 Mon Sep 17 00:00:00 2001 -From: Ken Gaillot -Date: Tue, 12 Dec 2023 16:08:44 -0600 -Subject: [PATCH 01/12] Refactor: libcrmcommon: support attrd purge requests - without clearing cache - -Nothing uses the new capability yet ---- - daemons/attrd/attrd_corosync.c | 4 +++- - daemons/attrd/attrd_messages.c | 8 +++++++- - daemons/attrd/pacemaker-attrd.h | 3 ++- - daemons/controld/controld_attrd.c | 2 +- - include/crm/common/ipc_attrd_internal.h | 7 ++++--- - include/crm_internal.h | 1 + - lib/common/ipc_attrd.c | 3 ++- - lib/common/ipc_client.c | 1 + - 8 files changed, 21 insertions(+), 8 deletions(-) - -diff --git a/daemons/attrd/attrd_corosync.c b/daemons/attrd/attrd_corosync.c -index 86dc67b04..e6cd07f65 100644 ---- a/daemons/attrd/attrd_corosync.c -+++ b/daemons/attrd/attrd_corosync.c -@@ -540,7 +540,9 @@ attrd_peer_remove(const char *host, bool uncache, const char *source) - GHashTableIter aIter; - - CRM_CHECK(host != NULL, return); -- crm_notice("Removing all %s attributes for peer %s", host, source); -+ crm_notice("Removing all %s attributes for node %s " -+ CRM_XS " %s reaping node from cache", -+ host, source, (uncache? "and" : "without")); - - g_hash_table_iter_init(&aIter, attributes); - while (g_hash_table_iter_next(&aIter, NULL, (gpointer *) & a)) { -diff --git a/daemons/attrd/attrd_messages.c b/daemons/attrd/attrd_messages.c -index 89da6d894..ac32e18af 100644 ---- a/daemons/attrd/attrd_messages.c -+++ b/daemons/attrd/attrd_messages.c -@@ -148,7 +148,13 @@ handle_remove_request(pcmk__request_t *request) - { - if (request->peer != NULL) { - const char *host = crm_element_value(request->xml, PCMK__XA_ATTR_NODE_NAME); -- attrd_peer_remove(host, true, request->peer); -+ bool reap = false; -+ -+ if (pcmk__xe_get_bool_attr(request->xml, PCMK__XA_REAP, -+ &reap) != pcmk_rc_ok) { -+ reap = true; // Default to true for backward compatibility -+ } -+ attrd_peer_remove(host, reap, request->peer); - pcmk__set_result(&request->result, CRM_EX_OK, PCMK_EXEC_DONE, NULL); - return NULL; - } else { -diff --git a/daemons/attrd/pacemaker-attrd.h b/daemons/attrd/pacemaker-attrd.h -index b8929a7f7..70e2cb41b 100644 ---- a/daemons/attrd/pacemaker-attrd.h -+++ b/daemons/attrd/pacemaker-attrd.h -@@ -42,8 +42,9 @@ - * 4 2.1.5 Multiple attributes can be updated in a single IPC - * message - * 5 2.1.5 Peers can request confirmation of a sent message -+ * 6 2.1.7 PCMK__ATTRD_CMD_PEER_REMOVE supports PCMK__XA_REAP - */ --#define ATTRD_PROTOCOL_VERSION "5" -+#define ATTRD_PROTOCOL_VERSION "6" - - #define ATTRD_SUPPORTS_MULTI_MESSAGE(x) ((x) >= 4) - #define ATTRD_SUPPORTS_CONFIRMATION(x) ((x) >= 5) -diff --git a/daemons/controld/controld_attrd.c b/daemons/controld/controld_attrd.c -index 923abb92d..958dc2f14 100644 ---- a/daemons/controld/controld_attrd.c -+++ b/daemons/controld/controld_attrd.c -@@ -117,7 +117,7 @@ update_attrd_remote_node_removed(const char *host, const char *user_name) - if (rc == pcmk_rc_ok) { - crm_trace("Asking attribute manager to purge Pacemaker Remote node %s", - host); -- rc = pcmk__attrd_api_purge(attrd_api, host); -+ rc = pcmk__attrd_api_purge(attrd_api, host, true); - } - if (rc != pcmk_rc_ok) { - crm_err("Could not purge Pacemaker Remote node %s " -diff --git a/include/crm/common/ipc_attrd_internal.h b/include/crm/common/ipc_attrd_internal.h -index b1b7584bd..39a55ad1d 100644 ---- a/include/crm/common/ipc_attrd_internal.h -+++ b/include/crm/common/ipc_attrd_internal.h -@@ -89,10 +89,11 @@ int pcmk__attrd_api_delete(pcmk_ipc_api_t *api, const char *node, const char *na - - /*! - * \internal -- * \brief Purge a node from pacemaker-attrd -+ * \brief Request removal of a node's transient attributes - * - * \param[in,out] api pacemaker-attrd IPC object -- * \param[in] node Node to remove -+ * \param[in] node Node whose attributes should be purged -+ * \param[in] reap If true, also request removal from node caches - * - * \note If \p api is NULL, a new temporary connection will be created - * just for this operation and destroyed afterwards. If \p api is -@@ -102,7 +103,7 @@ int pcmk__attrd_api_delete(pcmk_ipc_api_t *api, const char *node, const char *na - * - * \return Standard Pacemaker return code - */ --int pcmk__attrd_api_purge(pcmk_ipc_api_t *api, const char *node); -+int pcmk__attrd_api_purge(pcmk_ipc_api_t *api, const char *node, bool reap); - - /*! - * \internal -diff --git a/include/crm_internal.h b/include/crm_internal.h -index 3bc8d096a..f800ab0cc 100644 ---- a/include/crm_internal.h -+++ b/include/crm_internal.h -@@ -92,6 +92,7 @@ - #define PCMK__XA_MODE "mode" - #define PCMK__XA_NODE_START_STATE "node_start_state" - #define PCMK__XA_PATH "path" -+#define PCMK__XA_REAP "reap" - #define PCMK__XA_SCHEMA "schema" - #define PCMK__XA_SCHEMAS "schemas" - #define PCMK__XA_TASK "task" -diff --git a/lib/common/ipc_attrd.c b/lib/common/ipc_attrd.c -index 9caaabec0..56cdb5aba 100644 ---- a/lib/common/ipc_attrd.c -+++ b/lib/common/ipc_attrd.c -@@ -277,7 +277,7 @@ pcmk__attrd_api_delete(pcmk_ipc_api_t *api, const char *node, const char *name, - } - - int --pcmk__attrd_api_purge(pcmk_ipc_api_t *api, const char *node) -+pcmk__attrd_api_purge(pcmk_ipc_api_t *api, const char *node, bool reap) - { - int rc = pcmk_rc_ok; - xmlNode *request = NULL; -@@ -291,6 +291,7 @@ pcmk__attrd_api_purge(pcmk_ipc_api_t *api, const char *node) - request = create_attrd_op(NULL); - - crm_xml_add(request, PCMK__XA_TASK, PCMK__ATTRD_CMD_PEER_REMOVE); -+ pcmk__xe_set_bool_attr(request, PCMK__XA_REAP, reap); - pcmk__xe_add_node(request, node, 0); - - if (api == NULL) { -diff --git a/lib/common/ipc_client.c b/lib/common/ipc_client.c -index 0d3865095..5e64e2324 100644 ---- a/lib/common/ipc_client.c -+++ b/lib/common/ipc_client.c -@@ -759,6 +759,7 @@ create_purge_node_request(const pcmk_ipc_api_t *api, const char *node_name, - crm_xml_add(request, F_TYPE, T_ATTRD); - crm_xml_add(request, F_ORIG, crm_system_name); - crm_xml_add(request, PCMK__XA_TASK, PCMK__ATTRD_CMD_PEER_REMOVE); -+ pcmk__xe_set_bool_attr(request, PCMK__XA_REAP, true); - pcmk__xe_add_node(request, node_name, nodeid); - break; - --- -2.41.0 - -From adc1d8ef587913e5505494e0205bd77a8e0a878e Mon Sep 17 00:00:00 2001 -From: Ken Gaillot -Date: Wed, 13 Dec 2023 09:24:28 -0600 -Subject: [PATCH 02/12] Log: attrd: improve messages for CIB wipe - -Also, expose attrd_erase_attrs() as attrd_cib_erase_transient_attrs() and make -it take the node name as an argument, for future reuse. ---- - daemons/attrd/attrd_cib.c | 60 ++++++++++++++++++++------------- - daemons/attrd/pacemaker-attrd.h | 1 + - 2 files changed, 37 insertions(+), 24 deletions(-) - -diff --git a/daemons/attrd/attrd_cib.c b/daemons/attrd/attrd_cib.c -index 80e5580d9..ca1c5b9e0 100644 ---- a/daemons/attrd/attrd_cib.c -+++ b/daemons/attrd/attrd_cib.c -@@ -153,41 +153,44 @@ static void - attrd_erase_cb(xmlNode *msg, int call_id, int rc, xmlNode *output, - void *user_data) - { -- do_crm_log_unlikely(((rc != pcmk_ok)? LOG_NOTICE : LOG_DEBUG), -- "Cleared transient attributes: %s " -- CRM_XS " xpath=%s rc=%d", -- pcmk_strerror(rc), (char *) user_data, rc); -+ const char *node = pcmk__s((const char *) user_data, "a node"); -+ -+ if (rc == pcmk_ok) { -+ crm_info("Cleared transient node attributes for %s from CIB", node); -+ } else { -+ crm_err("Unable to clear transient node attributes for %s from CIB: %s", -+ node, pcmk_strerror(rc)); -+ } - } - - #define XPATH_TRANSIENT "//node_state[@uname='%s']/" XML_TAG_TRANSIENT_NODEATTRS - - /*! - * \internal -- * \brief Wipe all transient attributes for this node from the CIB -+ * \brief Wipe all transient node attributes for a node from the CIB - * -- * Clear any previous transient node attributes from the CIB. This is -- * normally done by the DC's controller when this node leaves the cluster, but -- * this handles the case where the node restarted so quickly that the -- * cluster layer didn't notice. -- * -- * \todo If pacemaker-attrd respawns after crashing (see PCMK_ENV_RESPAWNED), -- * ideally we'd skip this and sync our attributes from the writer. -- * However, currently we reject any values for us that the writer has, in -- * attrd_peer_update(). -+ * \param[in] node Node to clear attributes for - */ --static void --attrd_erase_attrs(void) -+void -+attrd_cib_erase_transient_attrs(const char *node) - { - int call_id = 0; -- char *xpath = crm_strdup_printf(XPATH_TRANSIENT, attrd_cluster->uname); -+ char *xpath = NULL; -+ -+ CRM_CHECK(node != NULL, return); -+ -+ xpath = crm_strdup_printf(XPATH_TRANSIENT, node); - -- crm_info("Clearing transient attributes from CIB " CRM_XS " xpath=%s", -- xpath); -+ crm_debug("Clearing transient node attributes for %s from CIB using %s", -+ node, xpath); - - call_id = the_cib->cmds->remove(the_cib, xpath, NULL, cib_xpath); -- the_cib->cmds->register_callback_full(the_cib, call_id, 120, FALSE, xpath, -- "attrd_erase_cb", attrd_erase_cb, -- free); -+ free(xpath); -+ -+ // strdup() is just for logging here, so ignore failure -+ the_cib->cmds->register_callback_full(the_cib, call_id, 120, FALSE, -+ strdup(node), "attrd_erase_cb", -+ attrd_erase_cb, free); - } - - /*! -@@ -197,8 +200,17 @@ attrd_erase_attrs(void) - void - attrd_cib_init(void) - { -- // We have no attribute values in memory, wipe the CIB to match -- attrd_erase_attrs(); -+ /* We have no attribute values in memory, so wipe the CIB to match. This is -+ * normally done by the DC's controller when this node leaves the cluster, but -+ * this handles the case where the node restarted so quickly that the -+ * cluster layer didn't notice. -+ * -+ * \todo If pacemaker-attrd respawns after crashing (see PCMK_ENV_RESPAWNED), -+ * ideally we'd skip this and sync our attributes from the writer. -+ * However, currently we reject any values for us that the writer has, in -+ * attrd_peer_update(). -+ */ -+ attrd_cib_erase_transient_attrs(attrd_cluster->uname); - - // Set a trigger for reading the CIB (for the alerts section) - attrd_config_read = mainloop_add_trigger(G_PRIORITY_HIGH, attrd_read_options, NULL); -diff --git a/daemons/attrd/pacemaker-attrd.h b/daemons/attrd/pacemaker-attrd.h -index 70e2cb41b..62637d1d7 100644 ---- a/daemons/attrd/pacemaker-attrd.h -+++ b/daemons/attrd/pacemaker-attrd.h -@@ -66,6 +66,7 @@ void attrd_ipc_fini(void); - int attrd_cib_connect(int max_retry); - void attrd_cib_disconnect(void); - void attrd_cib_init(void); -+void attrd_cib_erase_transient_attrs(const char *node); - - bool attrd_value_needs_expansion(const char *value); - int attrd_expand_value(const char *value, const char *old_value); --- -2.41.0 - -From 9be38897eaa683ad7920503d9c9fd7db7a20a8ec Mon Sep 17 00:00:00 2001 -From: Ken Gaillot -Date: Wed, 13 Dec 2023 11:20:07 -0600 -Subject: [PATCH 03/12] Refactor: attrd: convert value booleans to flags - ---- - daemons/attrd/attrd_attributes.c | 7 +++--- - daemons/attrd/attrd_corosync.c | 38 +++++++++++++++++--------------- - daemons/attrd/pacemaker-attrd.h | 21 ++++++++++++++++-- - 3 files changed, 42 insertions(+), 24 deletions(-) - -diff --git a/daemons/attrd/attrd_attributes.c b/daemons/attrd/attrd_attributes.c -index 388c181d7..8f32988be 100644 ---- a/daemons/attrd/attrd_attributes.c -+++ b/daemons/attrd/attrd_attributes.c -@@ -1,5 +1,5 @@ - /* -- * Copyright 2013-2022 the Pacemaker project contributors -+ * Copyright 2013-2023 the Pacemaker project contributors - * - * The version control history for this file may have further details. - * -@@ -143,7 +143,7 @@ attrd_add_value_xml(xmlNode *parent, const attribute_t *a, - crm_xml_add(xml, PCMK__XA_ATTR_UUID, a->uuid); - crm_xml_add(xml, PCMK__XA_ATTR_USER, a->user); - pcmk__xe_add_node(xml, v->nodename, v->nodeid); -- if (v->is_remote != 0) { -+ if (pcmk_is_set(v->flags, attrd_value_remote)) { - crm_xml_add_int(xml, PCMK__XA_ATTR_IS_REMOTE, 1); - } - crm_xml_add(xml, PCMK__XA_ATTR_VALUE, v->current); -@@ -166,8 +166,7 @@ attrd_clear_value_seen(void) - while (g_hash_table_iter_next(&aIter, NULL, (gpointer *) & a)) { - g_hash_table_iter_init(&vIter, a->values); - while (g_hash_table_iter_next(&vIter, NULL, (gpointer *) & v)) { -- v->seen = FALSE; -- crm_trace("Clear seen flag %s[%s] = %s.", a->id, v->nodename, v->current); -+ attrd_clear_value_flags(v, attrd_value_from_peer); - } - } - } -diff --git a/daemons/attrd/attrd_corosync.c b/daemons/attrd/attrd_corosync.c -index e6cd07f65..ca20bdc0f 100644 ---- a/daemons/attrd/attrd_corosync.c -+++ b/daemons/attrd/attrd_corosync.c -@@ -192,34 +192,35 @@ cache_remote_node(const char *node_name) - - /*! - * \internal -- * \brief Return host's hash table entry (creating one if needed) -+ * \brief Return a node's value from hash table (creating one if needed) - * -- * \param[in,out] values Hash table of values -- * \param[in] host Name of peer to look up -- * \param[in] xml XML describing the attribute -+ * \param[in,out] values Hash table of values -+ * \param[in] node_name Name of node to look up -+ * \param[in] xml XML describing the attribute - * - * \return Pointer to new or existing hash table entry - */ - static attribute_value_t * --attrd_lookup_or_create_value(GHashTable *values, const char *host, -+attrd_lookup_or_create_value(GHashTable *values, const char *node_name, - const xmlNode *xml) - { -- attribute_value_t *v = g_hash_table_lookup(values, host); -+ attribute_value_t *v = g_hash_table_lookup(values, node_name); - int is_remote = 0; - -- crm_element_value_int(xml, PCMK__XA_ATTR_IS_REMOTE, &is_remote); -- if (is_remote) { -- cache_remote_node(host); -- } -- - if (v == NULL) { - v = calloc(1, sizeof(attribute_value_t)); - CRM_ASSERT(v != NULL); - -- pcmk__str_update(&v->nodename, host); -- v->is_remote = is_remote; -+ pcmk__str_update(&v->nodename, node_name); - g_hash_table_replace(values, v->nodename, v); - } -+ -+ crm_element_value_int(xml, PCMK__XA_ATTR_IS_REMOTE, &is_remote); -+ if (is_remote) { -+ attrd_set_value_flags(v, attrd_value_remote); -+ cache_remote_node(node_name); -+ } -+ - return(v); - } - -@@ -344,11 +345,11 @@ update_attr_on_host(attribute_t *a, const crm_node_t *peer, const xmlNode *xml, - } - } - -- /* Set the seen flag for attribute processing held only in the own node. */ -- v->seen = TRUE; -+ // This allows us to later detect local values that peer doesn't know about -+ attrd_set_value_flags(v, attrd_value_from_peer); - - /* If this is a cluster node whose node ID we are learning, remember it */ -- if ((v->nodeid == 0) && (v->is_remote == FALSE) -+ if ((v->nodeid == 0) && !pcmk_is_set(v->flags, attrd_value_remote) - && (crm_element_value_int(xml, PCMK__XA_ATTR_NODE_ID, - (int*)&v->nodeid) == 0) && (v->nodeid > 0)) { - record_peer_nodeid(v, host); -@@ -414,8 +415,9 @@ broadcast_unseen_local_values(void) - while (g_hash_table_iter_next(&aIter, NULL, (gpointer *) & a)) { - g_hash_table_iter_init(&vIter, a->values); - while (g_hash_table_iter_next(&vIter, NULL, (gpointer *) & v)) { -- if (!(v->seen) && pcmk__str_eq(v->nodename, attrd_cluster->uname, -- pcmk__str_casei)) { -+ if (!pcmk_is_set(v->flags, attrd_value_from_peer) -+ && pcmk__str_eq(v->nodename, attrd_cluster->uname, -+ pcmk__str_casei)) { - if (sync == NULL) { - sync = create_xml_node(NULL, __func__); - crm_xml_add(sync, PCMK__XA_TASK, PCMK__ATTRD_CMD_SYNC_RESPONSE); -diff --git a/daemons/attrd/pacemaker-attrd.h b/daemons/attrd/pacemaker-attrd.h -index 62637d1d7..738418857 100644 ---- a/daemons/attrd/pacemaker-attrd.h -+++ b/daemons/attrd/pacemaker-attrd.h -@@ -140,15 +140,32 @@ typedef struct attribute_s { - - } attribute_t; - -+enum attrd_value_flags { -+ attrd_value_none = 0U, -+ attrd_value_remote = (1U << 0), // Value is for Pacemaker Remote node -+ attrd_value_from_peer = (1U << 1), // Value is from peer sync response -+}; -+ - typedef struct attribute_value_s { - uint32_t nodeid; -- gboolean is_remote; - char *nodename; - char *current; - char *requested; -- gboolean seen; -+ uint32_t flags; // Group of attrd_value_flags - } attribute_value_t; - -+#define attrd_set_value_flags(attr_value, flags_to_set) do { \ -+ (attr_value)->flags = pcmk__set_flags_as(__func__, __LINE__, \ -+ LOG_TRACE, "Value for node", (attr_value)->nodename, \ -+ (attr_value)->flags, (flags_to_set), #flags_to_set); \ -+ } while (0) -+ -+#define attrd_clear_value_flags(attr_value, flags_to_clear) do { \ -+ (attr_value)->flags = pcmk__clear_flags_as(__func__, __LINE__, \ -+ LOG_TRACE, "Value for node", (attr_value)->nodename, \ -+ (attr_value)->flags, (flags_to_clear), #flags_to_clear); \ -+ } while (0) -+ - extern crm_cluster_t *attrd_cluster; - extern GHashTable *attributes; - extern GHashTable *peer_protocol_vers; --- -2.41.0 - -From 922c79f4e39dc9501ff7c0136df8043081b771cb Mon Sep 17 00:00:00 2001 -From: Ken Gaillot -Date: Wed, 13 Dec 2023 16:51:39 -0600 -Subject: [PATCH 04/12] Log: attrd: improve logging of CIB write result - -When attrd requests a write-out of a changed attribute value, it saves the new -value in attribute_value_t:requested so it can be used in a log when the write -completes (which may occur after the value has already changed again, so we -can't log the current value at that time). - -Previously, the log call relied on libqb mapping a NULL pointer to "(null)". -To be safer, do that explicitly. - -Also, it previously erased "requested" after the write completed, even if the -write failed and would be reattempted. Leave the value alone in this case so -the result of the reattempt can be logged correctly. ---- - daemons/attrd/attrd_cib.c | 18 ++++++++---------- - 1 file changed, 8 insertions(+), 10 deletions(-) - -diff --git a/daemons/attrd/attrd_cib.c b/daemons/attrd/attrd_cib.c -index ca1c5b9e0..ae6564856 100644 ---- a/daemons/attrd/attrd_cib.c -+++ b/daemons/attrd/attrd_cib.c -@@ -274,11 +274,12 @@ attrd_cib_callback(xmlNode *msg, int call_id, int rc, xmlNode *output, void *use - - g_hash_table_iter_init(&iter, a->values); - while (g_hash_table_iter_next(&iter, (gpointer *) & peer, (gpointer *) & v)) { -- do_crm_log(level, "* %s[%s]=%s", a->id, peer, v->requested); -- free(v->requested); -- v->requested = NULL; -- if (rc != pcmk_ok) { -- a->changed = true; /* Attempt write out again */ -+ do_crm_log(level, "* %s[%s]=%s", -+ a->id, peer, pcmk__s(v->requested, "(null)")); -+ if (rc == pcmk_ok) { -+ pcmk__str_update(&(v->requested), NULL); -+ } else { -+ a->changed = true; // Reattempt write below if we are still writer - } - } - -@@ -605,11 +606,8 @@ write_attribute(attribute_t *a, bool ignore_delay) - /* Preservation of the attribute to transmit alert */ - set_alert_attribute_value(alert_attribute_value, v); - -- free(v->requested); -- v->requested = NULL; -- if (v->current) { -- v->requested = strdup(v->current); -- } -+ // Save this value so we can log it when write completes -+ pcmk__str_update(&(v->requested), v->current); - } - - if (private_updates) { --- -2.41.0 - -From fa2830b1c4acf061faa40490620eb63c48a56a2b Mon Sep 17 00:00:00 2001 -From: Ken Gaillot -Date: Wed, 13 Dec 2023 17:01:01 -0600 -Subject: [PATCH 05/12] Low: libcrmcluster: avoid use-after-free in trace log - ---- - lib/cluster/membership.c | 16 ++++++++++++++-- - 1 file changed, 14 insertions(+), 2 deletions(-) - -diff --git a/lib/cluster/membership.c b/lib/cluster/membership.c -index f856ccaca..6958e65f2 100644 ---- a/lib/cluster/membership.c -+++ b/lib/cluster/membership.c -@@ -143,11 +143,23 @@ crm_remote_peer_get(const char *node_name) - return node; - } - -+/*! -+ * \brief Remove a node from the Pacemaker Remote node cache -+ * -+ * \param[in] node_name Name of node to remove from cache -+ * -+ * \note The caller must be careful not to use \p node_name after calling this -+ * function if it might be a pointer into the cache entry being removed. -+ */ - void - crm_remote_peer_cache_remove(const char *node_name) - { -- if (g_hash_table_remove(crm_remote_peer_cache, node_name)) { -- crm_trace("removed %s from remote peer cache", node_name); -+ /* Do a lookup first, because node_name could be a pointer within the entry -+ * being removed -- we can't log it *after* removing it. -+ */ -+ if (g_hash_table_lookup(crm_remote_peer_cache, node_name) != NULL) { -+ crm_trace("Removing %s from Pacemaker Remote node cache", node_name); -+ g_hash_table_remove(crm_remote_peer_cache, node_name); - } - } - --- -2.41.0 - -From 14a7449a413f3f10eb80634c607386007d264475 Mon Sep 17 00:00:00 2001 -From: Ken Gaillot -Date: Thu, 14 Dec 2023 09:24:38 -0600 -Subject: [PATCH 06/12] Refactor: libcrmcluster,attrd: functionize removing - node from both caches - -This future-proofs against a potential use-after-free (not possible with -current code) and isolates cache management better. ---- - daemons/attrd/attrd_corosync.c | 3 +-- - include/crm/cluster/internal.h | 9 +++---- - lib/cluster/membership.c | 44 ++++++++++++++++++++++++++++++++++ - 3 files changed, 50 insertions(+), 6 deletions(-) - -diff --git a/daemons/attrd/attrd_corosync.c b/daemons/attrd/attrd_corosync.c -index ca20bdc0f..aa94a078e 100644 ---- a/daemons/attrd/attrd_corosync.c -+++ b/daemons/attrd/attrd_corosync.c -@@ -554,8 +554,7 @@ attrd_peer_remove(const char *host, bool uncache, const char *source) - } - - if (uncache) { -- crm_remote_peer_cache_remove(host); -- reap_crm_member(0, host); -+ pcmk__purge_node_from_cache(host, 0); - } - } - -diff --git a/include/crm/cluster/internal.h b/include/crm/cluster/internal.h -index e20ee4c59..c71069be2 100644 ---- a/include/crm/cluster/internal.h -+++ b/include/crm/cluster/internal.h -@@ -1,5 +1,5 @@ - /* -- * Copyright 2004-2021 the Pacemaker project contributors -+ * Copyright 2004-2023 the Pacemaker project contributors - * - * The version control history for this file may have further details. - * -@@ -7,8 +7,8 @@ - * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. - */ - --#ifndef CRM_CLUSTER_INTERNAL__H --# define CRM_CLUSTER_INTERNAL__H -+#ifndef PCMK__CRM_CLUSTER_INTERNAL__H -+# define PCMK__CRM_CLUSTER_INTERNAL__H - - # include // uint32_t, uint64_t - # include -@@ -126,6 +126,7 @@ crm_node_t *pcmk__search_node_caches(unsigned int id, const char *uname, - uint32_t flags); - crm_node_t *pcmk__search_cluster_node_cache(unsigned int id, const char *uname, - const char *uuid); -+void pcmk__purge_node_from_cache(const char *node_name, uint32_t node_id); - - void pcmk__refresh_node_caches_from_cib(xmlNode *cib); - crm_node_t *pcmk__search_known_node_cache(unsigned int id, const char *uname, -@@ -136,4 +137,4 @@ crm_node_t *pcmk__get_peer(unsigned int id, const char *uname, - crm_node_t *pcmk__get_peer_full(unsigned int id, const char *uname, - const char *uuid, int flags); - --#endif -+#endif // PCMK__CRM_CLUSTER_INTERNAL__H -diff --git a/lib/cluster/membership.c b/lib/cluster/membership.c -index 6958e65f2..173aaaa17 100644 ---- a/lib/cluster/membership.c -+++ b/lib/cluster/membership.c -@@ -341,6 +341,9 @@ crm_reap_dead_member(gpointer key, gpointer value, gpointer user_data) - * \param[in] name Uname of node to remove (or NULL to ignore) - * - * \return Number of cache entries removed -+ * -+ * \note The caller must be careful not to use \p name after calling this -+ * function if it might be a pointer into the cache entry being removed. - */ - guint - reap_crm_member(uint32_t id, const char *name) -@@ -564,6 +567,47 @@ pcmk__get_peer_full(unsigned int id, const char *uname, const char *uuid, - return node; - } - -+/*! -+ * \internal -+ * \brief Purge a node from cache (both cluster and Pacemaker Remote) -+ * -+ * \param[in] node_name If not NULL, purge only nodes with this name -+ * \param[in] node_id If not 0, purge cluster nodes only if they have this ID -+ * -+ * \note If \p node_name is NULL and \p node_id is 0, no nodes will be purged. -+ * If \p node_name is not NULL and \p node_id is not 0, Pacemaker Remote -+ * nodes that match \p node_name will be purged, and cluster nodes that -+ * match both \p node_name and \p node_id will be purged. -+ * \note The caller must be careful not to use \p node_name after calling this -+ * function if it might be a pointer into a cache entry being removed. -+ */ -+void -+pcmk__purge_node_from_cache(const char *node_name, uint32_t node_id) -+{ -+ char *node_name_copy = NULL; -+ -+ if ((node_name == NULL) && (node_id == 0U)) { -+ return; -+ } -+ -+ // Purge from Pacemaker Remote node cache -+ if ((node_name != NULL) -+ && (g_hash_table_lookup(crm_remote_peer_cache, node_name) != NULL)) { -+ /* node_name could be a pointer into the cache entry being purged, -+ * so reassign it to a copy before the original gets freed -+ */ -+ node_name_copy = strdup(node_name); -+ CRM_ASSERT(node_name_copy != NULL); -+ node_name = node_name_copy; -+ -+ crm_trace("Purging %s from Pacemaker Remote node cache", node_name); -+ g_hash_table_remove(crm_remote_peer_cache, node_name); -+ } -+ -+ reap_crm_member(node_id, node_name); -+ free(node_name_copy); -+} -+ - /*! - * \brief Get a node cache entry (cluster or Pacemaker Remote) - * --- -2.41.0 - -From 8d552c1b582a95f9879b15e2dd991a7f995e7eca Mon Sep 17 00:00:00 2001 -From: Ken Gaillot -Date: Thu, 14 Dec 2023 09:51:37 -0600 -Subject: [PATCH 07/12] Fix: pacemaker-attrd,libcrmcluster: avoid - use-after-free when remote node in cluster node cache - -Previously, pacemaker-attrd removed any conflicting entry from the cluster node -cache before adding a node to the remote node cache. However, if the name used -was a pointer into the cluster node cache entry being freed, it would be reused -to create the remote node cache entry. - -This avoids that and also moves the functionality into libcrmcluster for better -isolation of cache management. It also corrects mistakenly setting errno to a -negative value. ---- - daemons/attrd/attrd_corosync.c | 26 ++------------------------ - lib/cluster/membership.c | 30 ++++++++++++++++++++++++++++-- - 2 files changed, 30 insertions(+), 26 deletions(-) - -diff --git a/daemons/attrd/attrd_corosync.c b/daemons/attrd/attrd_corosync.c -index aa94a078e..1d0f87f04 100644 ---- a/daemons/attrd/attrd_corosync.c -+++ b/daemons/attrd/attrd_corosync.c -@@ -166,28 +166,6 @@ broadcast_local_value(const attribute_t *a) - return v; - } - --/*! -- * \internal -- * \brief Ensure a Pacemaker Remote node is in the correct peer cache -- * -- * \param[in] node_name Name of Pacemaker Remote node to check -- */ --static void --cache_remote_node(const char *node_name) --{ -- /* If we previously assumed this node was an unseen cluster node, -- * remove its entry from the cluster peer cache. -- */ -- crm_node_t *dup = pcmk__search_cluster_node_cache(0, node_name, NULL); -- -- if (dup && (dup->uuid == NULL)) { -- reap_crm_member(0, node_name); -- } -- -- // Ensure node is in the remote peer cache -- CRM_ASSERT(crm_remote_peer_get(node_name) != NULL); --} -- - #define state_text(state) pcmk__s((state), "in unknown state") - - /*! -@@ -218,7 +196,7 @@ attrd_lookup_or_create_value(GHashTable *values, const char *node_name, - crm_element_value_int(xml, PCMK__XA_ATTR_IS_REMOTE, &is_remote); - if (is_remote) { - attrd_set_value_flags(v, attrd_value_remote); -- cache_remote_node(node_name); -+ CRM_ASSERT(crm_remote_peer_get(node_name) != NULL); - } - - return(v); -@@ -273,7 +251,7 @@ attrd_peer_change_cb(enum crm_status_type kind, crm_node_t *peer, const void *da - - // Ensure remote nodes that come up are in the remote node cache - } else if (!gone && is_remote) { -- cache_remote_node(peer->uname); -+ CRM_ASSERT(crm_remote_peer_get(peer->uname) != NULL); - } - } - -diff --git a/lib/cluster/membership.c b/lib/cluster/membership.c -index 173aaaa17..a653617fa 100644 ---- a/lib/cluster/membership.c -+++ b/lib/cluster/membership.c -@@ -102,26 +102,50 @@ crm_remote_peer_cache_size(void) - * \note When creating a new entry, this will leave the node state undetermined, - * so the caller should also call pcmk__update_peer_state() if the state - * is known. -+ * \note Because this can add and remove cache entries, callers should not -+ * assume any previously obtained cache entry pointers remain valid. - */ - crm_node_t * - crm_remote_peer_get(const char *node_name) - { - crm_node_t *node; -+ char *node_name_copy = NULL; - - if (node_name == NULL) { -- errno = -EINVAL; -+ errno = EINVAL; - return NULL; - } - -+ /* It's theoretically possible that the node was added to the cluster peer -+ * cache before it was known to be a Pacemaker Remote node. Remove that -+ * entry unless it has a node ID, which means the name actually is -+ * associated with a cluster node. (@TODO return an error in that case?) -+ */ -+ node = pcmk__search_cluster_node_cache(0, node_name, NULL); -+ if ((node != NULL) && (node->uuid == NULL)) { -+ /* node_name could be a pointer into the cache entry being removed, so -+ * reassign it to a copy before the original gets freed -+ */ -+ node_name_copy = strdup(node_name); -+ if (node_name_copy == NULL) { -+ errno = ENOMEM; -+ return NULL; -+ } -+ node_name = node_name_copy; -+ reap_crm_member(0, node_name); -+ } -+ - /* Return existing cache entry if one exists */ - node = g_hash_table_lookup(crm_remote_peer_cache, node_name); - if (node) { -+ free(node_name_copy); - return node; - } - - /* Allocate a new entry */ - node = calloc(1, sizeof(crm_node_t)); - if (node == NULL) { -+ free(node_name_copy); - return NULL; - } - -@@ -130,7 +154,8 @@ crm_remote_peer_get(const char *node_name) - node->uuid = strdup(node_name); - if (node->uuid == NULL) { - free(node); -- errno = -ENOMEM; -+ errno = ENOMEM; -+ free(node_name_copy); - return NULL; - } - -@@ -140,6 +165,7 @@ crm_remote_peer_get(const char *node_name) - - /* Update the entry's uname, ensuring peer status callbacks are called */ - update_peer_uname(node, node_name); -+ free(node_name_copy); - return node; - } - --- -2.41.0 - -From 17ac8f0409021cbcd3e03a1b70518ab7abd9b259 Mon Sep 17 00:00:00 2001 -From: Ken Gaillot -Date: Thu, 14 Dec 2023 10:03:05 -0600 -Subject: [PATCH 08/12] Refactor: attrd: remove dead code - -The peer change callback can't be called for a Pacemaker Remote node unless the -node is already in the remote node cache, so don't bother trying to add it. -Modifying the peer caches is forbidden in peer change callbacks anyway since it -could lead to use-after-free issues in libcrmcluster. ---- - daemons/attrd/attrd_corosync.c | 4 ---- - 1 file changed, 4 deletions(-) - -diff --git a/daemons/attrd/attrd_corosync.c b/daemons/attrd/attrd_corosync.c -index 1d0f87f04..eba734c3a 100644 ---- a/daemons/attrd/attrd_corosync.c -+++ b/daemons/attrd/attrd_corosync.c -@@ -248,10 +248,6 @@ attrd_peer_change_cb(enum crm_status_type kind, crm_node_t *peer, const void *da - attrd_remove_voter(peer); - attrd_remove_peer_protocol_ver(peer->uname); - attrd_do_not_expect_from_peer(peer->uname); -- -- // Ensure remote nodes that come up are in the remote node cache -- } else if (!gone && is_remote) { -- CRM_ASSERT(crm_remote_peer_get(peer->uname) != NULL); - } - } - --- -2.41.0 - -From 221c4d697edc0481817c206ce8fdd878afd98ca1 Mon Sep 17 00:00:00 2001 -From: Ken Gaillot -Date: Thu, 14 Dec 2023 17:17:32 -0600 -Subject: [PATCH 09/12] Low: libcrmcommon: handle disconnected attrd API - connections consistently - -Drop send_attrd_request() in favor of using connect_and_send_attrd_request(), -since pcmk__connect_ipc() will return pcmk_rc_ok immediately if the API is -already connected. - -All the attribute manager IPC APIs attempted the connection if not already -connected except for pcmk__attrd_api_query(). Now that it uses -connect_and_send_attrd_request(), they are all consistent. ---- - lib/common/ipc_attrd.c | 28 +++++----------------------- - 1 file changed, 5 insertions(+), 23 deletions(-) - -diff --git a/lib/common/ipc_attrd.c b/lib/common/ipc_attrd.c -index 56cdb5aba..e36b42cbc 100644 ---- a/lib/common/ipc_attrd.c -+++ b/lib/common/ipc_attrd.c -@@ -190,12 +190,6 @@ connect_and_send_attrd_request(pcmk_ipc_api_t *api, const xmlNode *request) - return pcmk_rc_ok; - } - --static int --send_attrd_request(pcmk_ipc_api_t *api, const xmlNode *request) --{ -- return pcmk__send_ipc_request(api, request); --} -- - int - pcmk__attrd_api_clear_failures(pcmk_ipc_api_t *api, const char *node, - const char *resource, const char *operation, -@@ -229,11 +223,8 @@ pcmk__attrd_api_clear_failures(pcmk_ipc_api_t *api, const char *node, - rc = connect_and_send_attrd_request(api, request); - destroy_api(api); - -- } else if (!pcmk_ipc_is_connected(api)) { -- rc = connect_and_send_attrd_request(api, request); -- - } else { -- rc = send_attrd_request(api, request); -+ rc = connect_and_send_attrd_request(api, request); - } - - free_xml(request); -@@ -303,11 +294,8 @@ pcmk__attrd_api_purge(pcmk_ipc_api_t *api, const char *node, bool reap) - rc = connect_and_send_attrd_request(api, request); - destroy_api(api); - -- } else if (!pcmk_ipc_is_connected(api)) { -- rc = connect_and_send_attrd_request(api, request); -- - } else { -- rc = send_attrd_request(api, request); -+ rc = connect_and_send_attrd_request(api, request); - } - - free_xml(request); -@@ -346,7 +334,7 @@ pcmk__attrd_api_query(pcmk_ipc_api_t *api, const char *node, const char *name, - crm_xml_add(request, PCMK__XA_TASK, PCMK__ATTRD_CMD_QUERY); - pcmk__xe_add_node(request, node, 0); - -- rc = send_attrd_request(api, request); -+ rc = connect_and_send_attrd_request(api, request); - free_xml(request); - - if (node) { -@@ -386,11 +374,8 @@ pcmk__attrd_api_refresh(pcmk_ipc_api_t *api, const char *node) - rc = connect_and_send_attrd_request(api, request); - destroy_api(api); - -- } else if (!pcmk_ipc_is_connected(api)) { -- rc = connect_and_send_attrd_request(api, request); -- - } else { -- rc = send_attrd_request(api, request); -+ rc = connect_and_send_attrd_request(api, request); - } - - free_xml(request); -@@ -479,11 +464,8 @@ pcmk__attrd_api_update(pcmk_ipc_api_t *api, const char *node, const char *name, - rc = connect_and_send_attrd_request(api, request); - destroy_api(api); - -- } else if (!pcmk_ipc_is_connected(api)) { -- rc = connect_and_send_attrd_request(api, request); -- - } else { -- rc = send_attrd_request(api, request); -+ rc = connect_and_send_attrd_request(api, request); - } - - free_xml(request); --- -2.41.0 - -From 85502a405c384fdf0331e43ec161910ee1d14973 Mon Sep 17 00:00:00 2001 -From: Ken Gaillot -Date: Thu, 14 Dec 2023 17:29:11 -0600 -Subject: [PATCH 10/12] Low: libcrmcommon: handle NULL attribute manager IPC - API connections consistently - -Previously, all attribute manager IPC APIs except pcmk__attrd_api_query() would -create a temporary connection if passed a NULL argument for one. Now, -connect_and_send_attrd_request() does this itself, reducing code duplication and -making the handling consistent across all APIs. ---- - lib/common/ipc_attrd.c | 116 +++++++++-------------------------------- - 1 file changed, 25 insertions(+), 91 deletions(-) - -diff --git a/lib/common/ipc_attrd.c b/lib/common/ipc_attrd.c -index e36b42cbc..68975c7b6 100644 ---- a/lib/common/ipc_attrd.c -+++ b/lib/common/ipc_attrd.c -@@ -148,46 +148,39 @@ create_attrd_op(const char *user_name) - return attrd_op; - } - --static int --create_api(pcmk_ipc_api_t **api) --{ -- int rc = pcmk_new_ipc_api(api, pcmk_ipc_attrd); -- -- if (rc != pcmk_rc_ok) { -- crm_err("Could not connect to attrd: %s", pcmk_rc_str(rc)); -- } -- -- return rc; --} -- --static void --destroy_api(pcmk_ipc_api_t *api) --{ -- pcmk_disconnect_ipc(api); -- pcmk_free_ipc_api(api); -- api = NULL; --} -- - static int - connect_and_send_attrd_request(pcmk_ipc_api_t *api, const xmlNode *request) - { - int rc = pcmk_rc_ok; -+ bool created_api = false; -+ -+ if (api == NULL) { -+ rc = pcmk_new_ipc_api(&api, pcmk_ipc_attrd); -+ if (rc != pcmk_rc_ok) { -+ crm_err("Could not connect to attribute manager: %s", -+ pcmk_rc_str(rc)); -+ return rc; -+ } -+ created_api = true; -+ } - - 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; -- } - -- 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; -+ } else { -+ 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 pcmk_rc_ok; -+ if (created_api) { -+ pcmk_free_ipc_api(api); -+ } -+ return rc; - } - - int -@@ -214,18 +207,7 @@ pcmk__attrd_api_clear_failures(pcmk_ipc_api_t *api, const char *node, - crm_xml_add_int(request, PCMK__XA_ATTR_IS_REMOTE, - pcmk_is_set(options, pcmk__node_attr_remote)); - -- if (api == NULL) { -- rc = create_api(&api); -- if (rc != pcmk_rc_ok) { -- return rc; -- } -- -- rc = connect_and_send_attrd_request(api, request); -- destroy_api(api); -- -- } else { -- rc = connect_and_send_attrd_request(api, request); -- } -+ rc = connect_and_send_attrd_request(api, request); - - free_xml(request); - -@@ -285,18 +267,7 @@ pcmk__attrd_api_purge(pcmk_ipc_api_t *api, const char *node, bool reap) - pcmk__xe_set_bool_attr(request, PCMK__XA_REAP, reap); - pcmk__xe_add_node(request, node, 0); - -- if (api == NULL) { -- rc = create_api(&api); -- if (rc != pcmk_rc_ok) { -- return rc; -- } -- -- rc = connect_and_send_attrd_request(api, request); -- destroy_api(api); -- -- } else { -- rc = connect_and_send_attrd_request(api, request); -- } -+ rc = connect_and_send_attrd_request(api, request); - - free_xml(request); - -@@ -365,18 +336,7 @@ pcmk__attrd_api_refresh(pcmk_ipc_api_t *api, const char *node) - crm_xml_add(request, PCMK__XA_TASK, PCMK__ATTRD_CMD_REFRESH); - pcmk__xe_add_node(request, node, 0); - -- if (api == NULL) { -- rc = create_api(&api); -- if (rc != pcmk_rc_ok) { -- return rc; -- } -- -- rc = connect_and_send_attrd_request(api, request); -- destroy_api(api); -- -- } else { -- rc = connect_and_send_attrd_request(api, request); -- } -+ rc = connect_and_send_attrd_request(api, request); - - free_xml(request); - -@@ -455,18 +415,7 @@ pcmk__attrd_api_update(pcmk_ipc_api_t *api, const char *node, const char *name, - request = create_attrd_op(user_name); - populate_update_op(request, node, name, value, dampen, set, options); - -- if (api == NULL) { -- rc = create_api(&api); -- if (rc != pcmk_rc_ok) { -- return rc; -- } -- -- rc = connect_and_send_attrd_request(api, request); -- destroy_api(api); -- -- } else { -- rc = connect_and_send_attrd_request(api, request); -- } -+ rc = connect_and_send_attrd_request(api, request); - - free_xml(request); - -@@ -547,23 +496,8 @@ pcmk__attrd_api_update_list(pcmk_ipc_api_t *api, GList *attrs, const char *dampe - * request. Do that now, creating and destroying the API object if needed. - */ - if (pcmk__is_daemon) { -- bool created_api = false; -- -- if (api == NULL) { -- rc = create_api(&api); -- if (rc != pcmk_rc_ok) { -- return rc; -- } -- -- created_api = true; -- } -- - rc = connect_and_send_attrd_request(api, request); - free_xml(request); -- -- if (created_api) { -- destroy_api(api); -- } - } - - return rc; --- -2.41.0 - -From 4b25e2e2cf52e6c772805309e1f3dd6bb7ce8fab Mon Sep 17 00:00:00 2001 -From: Ken Gaillot -Date: Thu, 14 Dec 2023 18:11:14 -0600 -Subject: [PATCH 11/12] Log: controld,libcrmcommon: improve attrd IPC API - messages - -Previously, connect_and_send_attrd_request() would log error messages for -failures, attrd IPC APIs would log debug messages with the result whether -success or failure, and then callers would log or output failures again. - -Now, connect_and_send_attrd_request() does not log, the attrd IPC APIs log a -debug message before sending the request, and the callers log or output -failures. ---- - daemons/controld/controld_attrd.c | 22 ++++----- - lib/common/ipc_attrd.c | 76 ++++++++++++------------------- - 2 files changed, 38 insertions(+), 60 deletions(-) - -diff --git a/daemons/controld/controld_attrd.c b/daemons/controld/controld_attrd.c -index 958dc2f14..24c1e7068 100644 ---- a/daemons/controld/controld_attrd.c -+++ b/daemons/controld/controld_attrd.c -@@ -1,5 +1,5 @@ - /* -- * Copyright 2006-2022 the Pacemaker project contributors -+ * Copyright 2006-2023 the Pacemaker project contributors - * - * The version control history for this file may have further details. - * -@@ -136,25 +136,23 @@ update_attrd_clear_failures(const char *host, const char *rsc, const char *op, - rc = pcmk_new_ipc_api(&attrd_api, pcmk_ipc_attrd); - } - if (rc == pcmk_rc_ok) { -- const char *op_desc = pcmk__s(op, "operations"); -- const char *interval_desc = "all"; - uint32_t attrd_opts = pcmk__node_attr_none; - -- if (op != NULL) { -- interval_desc = pcmk__s(interval_spec, "nonrecurring"); -- } - if (is_remote_node) { - pcmk__set_node_attr_flags(attrd_opts, pcmk__node_attr_remote); - } -- crm_info("Asking attribute manager to clear failure of %s %s for %s " -- "on %s node %s", interval_desc, op_desc, rsc, -- node_type(is_remote_node), host); - rc = pcmk__attrd_api_clear_failures(attrd_api, host, rsc, op, - interval_spec, NULL, attrd_opts); - } - if (rc != pcmk_rc_ok) { -- crm_err("Could not clear failure attributes for %s on %s node %s%s: %s " -- CRM_XS " rc=%d", pcmk__s(rsc, "all resources"), -- node_type(is_remote_node), host, when(), pcmk_rc_str(rc), rc); -+ const char *interval_desc = "all"; -+ -+ if (op != NULL) { -+ interval_desc = pcmk__s(interval_spec, "nonrecurring"); -+ } -+ crm_err("Could not clear failure of %s %s for %s on %s node %s%s: %s " -+ CRM_XS " rc=%d", interval_desc, pcmk__s(op, "operations"), -+ pcmk__s(rsc, "all resources"), node_type(is_remote_node), host, -+ when(), pcmk_rc_str(rc), rc); - } - } -diff --git a/lib/common/ipc_attrd.c b/lib/common/ipc_attrd.c -index 68975c7b6..3951bd3df 100644 ---- a/lib/common/ipc_attrd.c -+++ b/lib/common/ipc_attrd.c -@@ -157,24 +157,14 @@ connect_and_send_attrd_request(pcmk_ipc_api_t *api, const xmlNode *request) - if (api == NULL) { - rc = pcmk_new_ipc_api(&api, pcmk_ipc_attrd); - if (rc != pcmk_rc_ok) { -- crm_err("Could not connect to attribute manager: %s", -- pcmk_rc_str(rc)); - return rc; - } - created_api = true; - } - - 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)); -- -- } else { -+ if (rc == pcmk_rc_ok) { - 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)); -- } - } - - if (created_api) { -@@ -199,6 +189,17 @@ pcmk__attrd_api_clear_failures(pcmk_ipc_api_t *api, const char *node, - node = target; - } - -+ if (operation) { -+ interval_desc = pcmk__s(interval_spec, "nonrecurring"); -+ op_desc = operation; -+ } else { -+ interval_desc = "all"; -+ op_desc = "operations"; -+ } -+ crm_debug("Asking %s to clear failure of %s %s for %s on %s", -+ pcmk_ipc_name(api, true), interval_desc, op_desc, -+ pcmk__s(resource, "all resources"), pcmk__s(node, "all nodes")); -+ - crm_xml_add(request, PCMK__XA_TASK, PCMK__ATTRD_CMD_CLEAR_FAILURE); - pcmk__xe_add_node(request, node, 0); - crm_xml_add(request, PCMK__XA_ATTR_RESOURCE, resource); -@@ -210,19 +211,6 @@ pcmk__attrd_api_clear_failures(pcmk_ipc_api_t *api, const char *node, - rc = connect_and_send_attrd_request(api, request); - - free_xml(request); -- -- if (operation) { -- interval_desc = interval_spec? interval_spec : "nonrecurring"; -- op_desc = operation; -- } else { -- interval_desc = "all"; -- op_desc = "operations"; -- } -- -- crm_debug("Asked pacemaker-attrd to clear failure of %s %s for %s on %s: %s (%d)", -- interval_desc, op_desc, (resource? resource : "all resources"), -- (node? node : "all nodes"), pcmk_rc_str(rc), rc); -- - return rc; - } - -@@ -254,13 +242,17 @@ pcmk__attrd_api_purge(pcmk_ipc_api_t *api, const char *node, bool reap) - { - int rc = pcmk_rc_ok; - xmlNode *request = NULL; -- const char *display_host = (node ? node : "localhost"); - const char *target = pcmk__node_attr_target(node); - - if (target != NULL) { - node = target; - } - -+ crm_debug("Asking %s to purge transient attributes%s for %s", -+ pcmk_ipc_name(api, true), -+ (reap? " and node cache entries" : ""), -+ pcmk__s(node, "local node")); -+ - request = create_attrd_op(NULL); - - crm_xml_add(request, PCMK__XA_TASK, PCMK__ATTRD_CMD_PEER_REMOVE); -@@ -270,10 +262,6 @@ pcmk__attrd_api_purge(pcmk_ipc_api_t *api, const char *node, bool reap) - rc = connect_and_send_attrd_request(api, request); - - free_xml(request); -- -- crm_debug("Asked pacemaker-attrd to purge %s: %s (%d)", -- display_host, pcmk_rc_str(rc), rc); -- - return rc; - } - -@@ -299,6 +287,10 @@ pcmk__attrd_api_query(pcmk_ipc_api_t *api, const char *node, const char *name, - } - } - -+ crm_debug("Querying %s for value of '%s'%s%s", -+ pcmk_ipc_name(api, true), name, -+ ((node == NULL)? "" : " on "), pcmk__s(node, "")); -+ - request = create_attrd_op(NULL); - - crm_xml_add(request, PCMK__XA_ATTR_NAME, name); -@@ -307,15 +299,6 @@ pcmk__attrd_api_query(pcmk_ipc_api_t *api, const char *node, const char *name, - - rc = connect_and_send_attrd_request(api, request); - free_xml(request); -- -- if (node) { -- crm_debug("Queried pacemaker-attrd for %s on %s: %s (%d)", -- name, node, pcmk_rc_str(rc), rc); -- } else { -- crm_debug("Queried pacemaker-attrd for %s: %s (%d)", -- name, pcmk_rc_str(rc), rc); -- } -- - return rc; - } - -@@ -324,13 +307,15 @@ pcmk__attrd_api_refresh(pcmk_ipc_api_t *api, const char *node) - { - int rc = pcmk_rc_ok; - xmlNode *request = NULL; -- const char *display_host = (node ? node : "localhost"); - const char *target = pcmk__node_attr_target(node); - - if (target != NULL) { - node = target; - } - -+ crm_debug("Asking %s to write all transient attributes for %s to CIB", -+ pcmk_ipc_name(api, true), pcmk__s(node, "local node")); -+ - request = create_attrd_op(NULL); - - crm_xml_add(request, PCMK__XA_TASK, PCMK__ATTRD_CMD_REFRESH); -@@ -339,10 +324,6 @@ pcmk__attrd_api_refresh(pcmk_ipc_api_t *api, const char *node) - rc = connect_and_send_attrd_request(api, request); - - free_xml(request); -- -- crm_debug("Asked pacemaker-attrd to refresh %s: %s (%d)", -- display_host, pcmk_rc_str(rc), rc); -- - return rc; - } - -@@ -399,7 +380,6 @@ pcmk__attrd_api_update(pcmk_ipc_api_t *api, const char *node, const char *name, - { - int rc = pcmk_rc_ok; - xmlNode *request = NULL; -- const char *display_host = (node ? node : "localhost"); - const char *target = NULL; - - if (name == NULL) { -@@ -412,16 +392,16 @@ pcmk__attrd_api_update(pcmk_ipc_api_t *api, const char *node, const char *name, - node = target; - } - -+ crm_debug("Asking %s to update '%s' to '%s' for %s", -+ pcmk_ipc_name(api, true), name, pcmk__s(value, "(null)"), -+ pcmk__s(node, "local node")); -+ - request = create_attrd_op(user_name); - populate_update_op(request, node, name, value, dampen, set, options); - - rc = connect_and_send_attrd_request(api, request); - - free_xml(request); -- -- crm_debug("Asked pacemaker-attrd to update %s on %s: %s (%d)", -- name, display_host, pcmk_rc_str(rc), rc); -- - return rc; - } - --- -2.41.0 - -From e5d22ef2a6b130768bd59ab5b7d8cd1155bb02a5 Mon Sep 17 00:00:00 2001 -From: Ken Gaillot -Date: Thu, 14 Dec 2023 17:54:01 -0600 -Subject: [PATCH 12/12] Log: libcrmcommon: use log-friendly name in pacemakerd - IPC logs - ---- - lib/common/ipc_pacemakerd.c | 15 ++++++++------- - 1 file changed, 8 insertions(+), 7 deletions(-) - -diff --git a/lib/common/ipc_pacemakerd.c b/lib/common/ipc_pacemakerd.c -index 2f0370974..6d6f6d6bf 100644 ---- a/lib/common/ipc_pacemakerd.c -+++ b/lib/common/ipc_pacemakerd.c -@@ -210,15 +210,16 @@ dispatch(pcmk_ipc_api_t *api, xmlNode *reply) - value = crm_element_value(reply, F_CRM_MSG_TYPE); - if (pcmk__str_empty(value) - || !pcmk__str_eq(value, XML_ATTR_RESPONSE, pcmk__str_none)) { -- crm_info("Unrecognizable message from pacemakerd: " -+ crm_info("Unrecognizable message from %s: " - "message type '%s' not '" XML_ATTR_RESPONSE "'", -- pcmk__s(value, "")); -+ pcmk_ipc_name(api, true), pcmk__s(value, "")); - status = CRM_EX_PROTOCOL; - goto done; - } - - if (pcmk__str_empty(crm_element_value(reply, XML_ATTR_REFERENCE))) { -- crm_info("Unrecognizable message from pacemakerd: no reference"); -+ crm_info("Unrecognizable message from %s: no reference", -+ pcmk_ipc_name(api, true)); - status = CRM_EX_PROTOCOL; - goto done; - } -@@ -244,8 +245,8 @@ dispatch(pcmk_ipc_api_t *api, xmlNode *reply) - reply_data.reply_type = pcmk_pacemakerd_reply_shutdown; - reply_data.data.shutdown.status = atoi(crm_element_value(msg_data, XML_LRM_ATTR_OPSTATUS)); - } else { -- crm_info("Unrecognizable message from pacemakerd: " -- "unknown command '%s'", pcmk__s(value, "")); -+ crm_info("Unrecognizable message from %s: unknown command '%s'", -+ pcmk_ipc_name(api, true), pcmk__s(value, "")); - status = CRM_EX_PROTOCOL; - goto done; - } -@@ -292,8 +293,8 @@ do_pacemakerd_api_call(pcmk_ipc_api_t *api, const char *ipc_name, const char *ta - if (cmd) { - rc = pcmk__send_ipc_request(api, cmd); - if (rc != pcmk_rc_ok) { -- crm_debug("Couldn't send request to pacemakerd: %s rc=%d", -- pcmk_rc_str(rc), rc); -+ crm_debug("Couldn't send request to %s: %s rc=%d", -+ pcmk_ipc_name(api, true), pcmk_rc_str(rc), rc); - } - free_xml(cmd); - } else { --- -2.41.0 - diff --git a/pacemaker-0f7f88312.tar.gz b/pacemaker-39ef08240.tar.gz similarity index 44% rename from pacemaker-0f7f88312.tar.gz rename to pacemaker-39ef08240.tar.gz index 3fed18409e0948ff0c8faa0a329629fd86062e96..687792dbd7056381612872ceaa5f98eecbc3e041 100644 Binary files a/pacemaker-0f7f88312.tar.gz and b/pacemaker-39ef08240.tar.gz differ diff --git a/pacemaker.spec b/pacemaker.spec index df075373aa0a813a6cf55b21e048bbc9f56d3d78..1295f56935fe0177b89ded0b3bde13d11e61b3f0 100644 --- a/pacemaker.spec +++ b/pacemaker.spec @@ -1,4 +1,6 @@ -# Globals and defines to control package behavior (configure these as desired) + +# User-configurable globals and defines to control package behavior +# (these should not test {with X} values, which are declared later) ## User and group to use for nonprivileged services %global uname hacluster @@ -16,11 +18,11 @@ ## Upstream pacemaker version, and its package version (specversion ## can be incremented to build packages reliably considered "newer" ## than previously built packages with the same pcmkversion) -%global pcmkversion 2.1.7 -%global specversion 11 +%global pcmkversion 2.1.8 +%global specversion 1.rc1 ## Upstream commit (full commit ID, abbreviated commit ID, or tag) to build -%global commit 0f7f88312f7a1ccedee60bf768aba79ee13d41e0 +%global commit 39ef082405a19b22554ffd6efaa75c39a586640f ## Since git v2.11, the extent of abbreviation is autoscaled by default ## (used to be constant of 7), so we need to convey it for non-tags, too. @@ -149,14 +151,7 @@ Url: https://www.clusterlabs.org/ # You can use "spectool -s 0 pacemaker.spec" (rpmdevtools) to show final URL. Source0: https://codeload.github.com/%{github_owner}/%{name}/tar.gz/%{archive_github_url} Source1: https://codeload.github.com/%{github_owner}/%{nagios_name}/tar.gz/%{nagios_archive_github_url} -Patch0: Add_replace_for_PCMK__REMOTE_SCHEMA_DIR.patch -Patch1: 001-schema-glib.patch -Patch2: Doc-HealthSMART-fix-the-description-of-temp_lower.patch -Patch3: 002-schema-transfer.patch -Patch4: Improve-pacemaker-attrd-cache-management-and-logging.patch -Patch5: Fix-cibsecret-Use-ps-axww-to-avoid-truncating-issue.patch -Patch6: Fix-tools-crm_mon-segfaults-when-fencer-connection-is-lost.patch -Patch7: Fix-libcrmcommon-avoid-file-descriptor-leak-in-IPC-c.patch +Source2: pacemaker.sysusers Requires: resource-agents Requires: %{pkgname_pcmk_libs} = %{version}-%{release} @@ -218,6 +213,16 @@ BuildRequires: corosync-devel >= 2.0.0 BuildRequires: %{pkgname_glue_libs}-devel %endif +%if %{with doc} +BuildRequires: asciidoc +BuildRequires: inkscape +BuildRequires: %{python_name}-sphinx +%endif + +# Creation of Users / Groups +BuildRequires: systemd-rpm-macros +%{?sysusers_requires_compat} + # Booth requires this Provides: pacemaker-ticket-support = 2.0 Provides: pcmk-cluster-manager = %{version}-%{release} @@ -496,6 +501,8 @@ find %{buildroot} -name '*.la' -type f -print0 | xargs -0 rm -f rm -f %{buildroot}/%{_sbindir}/fence_legacy rm -f %{buildroot}/%{_mandir}/man8/fence_legacy.* +install -p -D -m 0644 %{SOURCE2} %{buildroot}%{_sysusersdir}/pacemaker.conf + %post %systemd_post pacemaker.service @@ -557,6 +564,7 @@ fi %systemd_postun_with_restart crm_mon.service %pre -n %{pkgname_pcmk_libs} +%sysusers_create_compat %{SOURCE2} # @TODO Use sysusers.d: # https://fedoraproject.org/wiki/Changes/Adopting_sysusers.d_format getent group %{gname} >/dev/null || groupadd -r %{gname} -g %{hacluster_id} @@ -583,6 +591,7 @@ exit 0 %{_sbindir}/fence_watchdog +%doc %{_mandir}/man7/pacemaker-based.* %doc %{_mandir}/man7/pacemaker-controld.* %doc %{_mandir}/man7/pacemaker-schedulerd.* %doc %{_mandir}/man7/pacemaker-fenced.* @@ -647,6 +656,7 @@ exit 0 %{ocf_root}/resource.d/pacemaker %doc %{_mandir}/man7/*pacemaker* +%exclude %{_mandir}/man7/pacemaker-based.* %exclude %{_mandir}/man7/pacemaker-controld.* %exclude %{_mandir}/man7/pacemaker-schedulerd.* %exclude %{_mandir}/man7/pacemaker-fenced.* @@ -676,6 +686,7 @@ exit 0 %dir %attr (770, %{uname}, %{gname}) %{_var}/log/pacemaker/bundles %files -n %{pkgname_pcmk_libs} %{?with_nls:-f %{name}.lang} +%{_sysusersdir}/pacemaker.conf %{_libdir}/libcib.so.* %{_libdir}/liblrmd.so.* %{_libdir}/libcrmservice.so.* @@ -764,6 +775,9 @@ exit 0 %license %{nagios_name}-%{nagios_hash}/COPYING %changelog +* Fri Jun 07 2024 liupei - 2.1.8-1.rc1 +- update to 2.1.8-1.rc1 + * Mon Apr 29 2024 bixiaoyan - 2.1.7-11 - Fix: libcrmcommon: avoid file descriptor leak in IPC client with async connection @@ -853,3 +867,4 @@ exit 0 * Wed Apr 15 2020 houjian - 2.0.2-3.2 - Init pacemaker project +