Page MenuHomeClusterLabs Projects

No OneTemporary

diff --git a/lib/common/rules.c b/lib/common/rules.c
index baf5c92a52..ab88c42f7d 100644
--- a/lib/common/rules.c
+++ b/lib/common/rules.c
@@ -1,668 +1,709 @@
/*
* Copyright 2004-2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU Lesser General Public License
* version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <stdio.h> // NULL, size_t
#include <stdlib.h> // calloc()
+#include <stdbool.h> // bool
#include <ctype.h> // isdigit()
#include <regex.h> // regmatch_t
#include <stdint.h> // uint32_t
#include <inttypes.h> // PRIu32
#include <glib.h> // gboolean, FALSE
#include <libxml/tree.h> // xmlNode
#include <crm/common/scheduler.h>
#include <crm/common/iso8601_internal.h>
#include <crm/common/nvpair_internal.h>
#include <crm/common/scheduler_internal.h>
#include "crmcommon_private.h"
/*!
* \internal
* \brief Get the expression type corresponding to given expression XML
*
* \param[in] expr Rule expression XML
*
* \return Expression type corresponding to \p expr
*/
enum expression_type
pcmk__expression_type(const xmlNode *expr)
{
const char *name = NULL;
// Expression types based on element name
if (pcmk__xe_is(expr, PCMK_XE_DATE_EXPRESSION)) {
return pcmk__subexpr_datetime;
} else if (pcmk__xe_is(expr, PCMK_XE_RSC_EXPRESSION)) {
return pcmk__subexpr_resource;
} else if (pcmk__xe_is(expr, PCMK_XE_OP_EXPRESSION)) {
return pcmk__subexpr_operation;
} else if (pcmk__xe_is(expr, PCMK_XE_RULE)) {
return pcmk__subexpr_rule;
} else if (!pcmk__xe_is(expr, PCMK_XE_EXPRESSION)) {
return pcmk__subexpr_unknown;
}
// Expression types based on node attribute name
name = crm_element_value(expr, PCMK_XA_ATTRIBUTE);
if (pcmk__str_any_of(name, CRM_ATTR_UNAME, CRM_ATTR_KIND, CRM_ATTR_ID,
NULL)) {
return pcmk__subexpr_location;
}
return pcmk__subexpr_attribute;
}
/*!
* \internal
* \brief Get parent XML element's ID for logging purposes
*
* \param[in] xml XML of a subelement
*
* \return ID of \p xml's parent for logging purposes (guaranteed non-NULL)
*/
static const char *
loggable_parent_id(const xmlNode *xml)
{
// Default if called without parent (likely for unit testing)
const char *parent_id = "implied";
if ((xml != NULL) && (xml->parent != NULL)) {
parent_id = pcmk__xe_id(xml->parent);
if (parent_id == NULL) { // Not possible with schema validation enabled
parent_id = "without ID";
}
}
return parent_id;
}
/*!
* \internal
* \brief Get the moon phase corresponding to a given date/time
*
* \param[in] now Date/time to get moon phase for
*
* \return Phase of the moon corresponding to \p now, where 0 is the new moon
* and 7 is the full moon
* \deprecated This feature has been deprecated since 2.1.6.
*/
static int
phase_of_the_moon(const crm_time_t *now)
{
/* As per the nethack rules:
* - A moon period is 29.53058 days ~= 30
* - A year is 365.2422 days
* - Number of days moon phase advances on first day of year compared to
* preceding year is (365.2422 - 12 * 29.53058) ~= 11
* - Number of years until same phases fall on the same days of the month
* is 18.6 ~= 19
* - Moon phase on first day of year (epact) ~= (11 * (year%19) + 29) % 30
* (29 as initial condition)
* - Current phase in days = first day phase + days elapsed in year
* - 6 moons ~= 177 days ~= 8 reported phases * 22 (+ 11/22 for rounding)
*/
uint32_t epact, diy, goldn;
uint32_t y;
crm_time_get_ordinal(now, &y, &diy);
goldn = (y % 19) + 1;
epact = (11 * goldn + 18) % 30;
if (((epact == 25) && (goldn > 11)) || (epact == 24)) {
epact++;
}
return (((((diy + epact) * 6) + 11) % 177) / 22) & 7;
}
/*!
* \internal
* \brief Check an integer value against a range from a date specification
*
* \param[in] date_spec XML of PCMK_XE_DATE_SPEC element to check
* \param[in] id XML ID for logging purposes
* \param[in] attr Name of XML attribute with range to check against
* \param[in] value Value to compare against range
*
* \return Standard Pacemaker return code (specifically, pcmk_rc_before_range,
* pcmk_rc_after_range, or pcmk_rc_ok to indicate that result is either
* within range or undetermined)
* \note We return pcmk_rc_ok for an undetermined result so we can continue
* checking the next range attribute.
*/
static int
check_range(const xmlNode *date_spec, const char *id, const char *attr,
uint32_t value)
{
int rc = pcmk_rc_ok;
const char *range = crm_element_value(date_spec, attr);
long long low, high;
if (range == NULL) { // Attribute not present
goto bail;
}
if (pcmk__parse_ll_range(range, &low, &high) != pcmk_rc_ok) {
// Invalid range
/* @COMPAT When we can break behavioral backward compatibility, treat
* the entire rule as not passing.
*/
pcmk__config_err("Ignoring " PCMK_XE_DATE_SPEC
" %s attribute %s because '%s' is not a valid range",
id, attr, range);
} else if ((low != -1) && (value < low)) {
rc = pcmk_rc_before_range;
} else if ((high != -1) && (value > high)) {
rc = pcmk_rc_after_range;
}
bail:
crm_trace("Checked " PCMK_XE_DATE_SPEC " %s %s='%s' for %" PRIu32 ": %s",
id, attr, pcmk__s(range, ""), value, pcmk_rc_str(rc));
return rc;
}
/*!
* \internal
* \brief Evaluate a date specification for a given date/time
*
* \param[in] date_spec XML of PCMK_XE_DATE_SPEC element to evaluate
* \param[in] now Time to check
*
* \return Standard Pacemaker return code (specifically, EINVAL for NULL
* arguments, pcmk_rc_ok if time matches specification, or
* pcmk_rc_before_range, pcmk_rc_after_range, or pcmk_rc_op_unsatisfied
* as appropriate to how time relates to specification)
*/
int
pcmk__evaluate_date_spec(const xmlNode *date_spec, const crm_time_t *now)
{
const char *id = NULL;
const char *parent_id = loggable_parent_id(date_spec);
// Range attributes that can be specified for a PCMK_XE_DATE_SPEC element
struct range {
const char *attr;
uint32_t value;
} ranges[] = {
{ PCMK_XA_YEARS, 0U },
{ PCMK_XA_MONTHS, 0U },
{ PCMK_XA_MONTHDAYS, 0U },
{ PCMK_XA_HOURS, 0U },
{ PCMK_XA_MINUTES, 0U },
{ PCMK_XA_SECONDS, 0U },
{ PCMK_XA_YEARDAYS, 0U },
{ PCMK_XA_WEEKYEARS, 0U },
{ PCMK_XA_WEEKS, 0U },
{ PCMK_XA_WEEKDAYS, 0U },
{ PCMK__XA_MOON, 0U },
};
if ((date_spec == NULL) || (now == NULL)) {
return EINVAL;
}
// Get specification ID (for logging)
id = pcmk__xe_id(date_spec);
if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* fail the specification
*/
pcmk__config_warn(PCMK_XE_DATE_SPEC " subelement of "
PCMK_XE_DATE_EXPRESSION " %s has no " PCMK_XA_ID,
parent_id);
id = "without ID"; // for logging
}
// Year, month, day
crm_time_get_gregorian(now, &(ranges[0].value), &(ranges[1].value),
&(ranges[2].value));
// Hour, minute, second
crm_time_get_timeofday(now, &(ranges[3].value), &(ranges[4].value),
&(ranges[5].value));
// Year (redundant) and day of year
crm_time_get_ordinal(now, &(ranges[0].value), &(ranges[6].value));
// Week year, week of week year, day of week
crm_time_get_isoweek(now, &(ranges[7].value), &(ranges[8].value),
&(ranges[9].value));
// Moon phase (deprecated)
ranges[10].value = phase_of_the_moon(now);
if (crm_element_value(date_spec, PCMK__XA_MOON) != NULL) {
pcmk__config_warn("Support for '" PCMK__XA_MOON "' in "
PCMK_XE_DATE_SPEC " elements (such as %s) is "
"deprecated and will be removed in a future release "
"of Pacemaker", id);
}
for (int i = 0; i < PCMK__NELEM(ranges); ++i) {
int rc = check_range(date_spec, id, ranges[i].attr, ranges[i].value);
if (rc != pcmk_rc_ok) {
return rc;
}
}
// All specified ranges passed, or none were given (also considered a pass)
return pcmk_rc_ok;
}
#define ADD_COMPONENT(component) do { \
int sub_rc = pcmk__add_time_from_xml(*end, component, duration); \
if (sub_rc != pcmk_rc_ok) { \
/* @COMPAT return sub_rc when we can break compatibility */ \
pcmk__config_warn("Ignoring %s in " PCMK_XE_DURATION " %s " \
"because it is invalid", \
pcmk__time_component_attr(component), id); \
rc = sub_rc; \
} \
} while (0)
/*!
* \internal
* \brief Given a duration and a start time, calculate the end time
*
* \param[in] duration XML of PCMK_XE_DURATION element
* \param[in] start Start time
* \param[out] end Where to store end time (\p *end must be NULL
* initially)
*
* \return Standard Pacemaker return code
* \note The caller is responsible for freeing \p *end using crm_time_free().
*/
int
pcmk__unpack_duration(const xmlNode *duration, const crm_time_t *start,
crm_time_t **end)
{
int rc = pcmk_rc_ok;
const char *id = NULL;
const char *parent_id = loggable_parent_id(duration);
if ((start == NULL) || (duration == NULL)
|| (end == NULL) || (*end != NULL)) {
return EINVAL;
}
// Get duration ID (for logging)
id = pcmk__xe_id(duration);
if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error instead
*/
pcmk__config_warn(PCMK_XE_DURATION " subelement of "
PCMK_XE_DATE_EXPRESSION " %s has no " PCMK_XA_ID,
parent_id);
id = "without ID";
}
*end = pcmk_copy_time(start);
ADD_COMPONENT(pcmk__time_years);
ADD_COMPONENT(pcmk__time_months);
ADD_COMPONENT(pcmk__time_weeks);
ADD_COMPONENT(pcmk__time_days);
ADD_COMPONENT(pcmk__time_hours);
ADD_COMPONENT(pcmk__time_minutes);
ADD_COMPONENT(pcmk__time_seconds);
return rc;
}
/*!
* \internal
* \brief Evaluate a range check for a given date/time
*
* \param[in] date_expression XML of PCMK_XE_DATE_EXPRESSION element
* \param[in] id Expression ID for logging purposes
* \param[in] now Date/time to compare
* \param[in,out] next_change If not NULL, set this to when the evaluation
* will change, if known and earlier than the
* original value
*
* \return Standard Pacemaker return code
*/
static int
evaluate_in_range(const xmlNode *date_expression, const char *id,
const crm_time_t *now, crm_time_t *next_change)
{
crm_time_t *start = NULL;
crm_time_t *end = NULL;
if (pcmk__xe_get_datetime(date_expression, PCMK_XA_START,
&start) != pcmk_rc_ok) {
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Ignoring " PCMK_XA_START " in "
PCMK_XE_DATE_EXPRESSION " %s because it is invalid",
id);
}
if (pcmk__xe_get_datetime(date_expression, PCMK_XA_END,
&end) != pcmk_rc_ok) {
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Ignoring " PCMK_XA_END " in "
PCMK_XE_DATE_EXPRESSION " %s because it is invalid",
id);
}
if ((start == NULL) && (end == NULL)) {
// Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because in_range requires at least one of "
PCMK_XA_START " or " PCMK_XA_END, id);
return pcmk_rc_undetermined;
}
if (end == NULL) {
xmlNode *duration = first_named_child(date_expression,
PCMK_XE_DURATION);
if (duration != NULL) {
/* @COMPAT When we can break behavioral backward compatibility,
* return the result of this if not OK
*/
pcmk__unpack_duration(duration, start, &end);
}
}
if ((start != NULL) && (crm_time_compare(now, start) < 0)) {
pcmk__set_time_if_earlier(next_change, start);
crm_time_free(start);
crm_time_free(end);
return pcmk_rc_before_range;
}
if (end != NULL) {
if (crm_time_compare(now, end) > 0) {
crm_time_free(start);
crm_time_free(end);
return pcmk_rc_after_range;
}
// Evaluation doesn't change until second after end
if (next_change != NULL) {
crm_time_add_seconds(end, 1);
pcmk__set_time_if_earlier(next_change, end);
}
}
crm_time_free(start);
crm_time_free(end);
return pcmk_rc_within_range;
}
/*!
* \internal
* \brief Evaluate a greater-than check for a given date/time
*
* \param[in] date_expression XML of PCMK_XE_DATE_EXPRESSION element
* \param[in] id Expression ID for logging purposes
* \param[in] now Date/time to compare
* \param[in,out] next_change If not NULL, set this to when the evaluation
* will change, if known and earlier than the
* original value
*
* \return Standard Pacemaker return code
*/
static int
evaluate_gt(const xmlNode *date_expression, const char *id,
const crm_time_t *now, crm_time_t *next_change)
{
crm_time_t *start = NULL;
if (pcmk__xe_get_datetime(date_expression, PCMK_XA_START,
&start) != pcmk_rc_ok) {
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because " PCMK_XA_START " is invalid",
id);
return pcmk_rc_undetermined;
}
if (start == NULL) { // Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because " PCMK_VALUE_GT " requires "
PCMK_XA_START, id);
return pcmk_rc_undetermined;
}
if (crm_time_compare(now, start) > 0) {
crm_time_free(start);
return pcmk_rc_within_range;
}
// Evaluation doesn't change until second after start time
crm_time_add_seconds(start, 1);
pcmk__set_time_if_earlier(next_change, start);
crm_time_free(start);
return pcmk_rc_before_range;
}
/*!
* \internal
* \brief Evaluate a less-than check for a given date/time
*
* \param[in] date_expression XML of PCMK_XE_DATE_EXPRESSION element
* \param[in] id Expression ID for logging purposes
* \param[in] now Date/time to compare
* \param[in,out] next_change If not NULL, set this to when the evaluation
* will change, if known and earlier than the
* original value
*
* \return Standard Pacemaker return code
*/
static int
evaluate_lt(const xmlNode *date_expression, const char *id,
const crm_time_t *now, crm_time_t *next_change)
{
crm_time_t *end = NULL;
if (pcmk__xe_get_datetime(date_expression, PCMK_XA_END,
&end) != pcmk_rc_ok) {
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because " PCMK_XA_END " is invalid", id);
return pcmk_rc_undetermined;
}
if (end == NULL) { // Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s as not "
"passing because " PCMK_VALUE_GT " requires "
PCMK_XA_END, id);
return pcmk_rc_undetermined;
}
if (crm_time_compare(now, end) < 0) {
pcmk__set_time_if_earlier(next_change, end);
crm_time_free(end);
return pcmk_rc_within_range;
}
crm_time_free(end);
return pcmk_rc_after_range;
}
/*!
* \internal
* \brief Evaluate a rule's date expression for a given date/time
*
* \param[in] date_expression XML of a PCMK_XE_DATE_EXPRESSION element
* \param[in] now Time to use for evaluation
* \param[in,out] next_change If not NULL, set this to when the evaluation
* will change, if known and earlier than the
* original value
*
* \return Standard Pacemaker return code (unlike most other evaluation
* functions, this can return either pcmk_rc_ok or pcmk_rc_within_range
* on success)
*/
int
pcmk__evaluate_date_expression(const xmlNode *date_expression,
const crm_time_t *now, crm_time_t *next_change)
{
const char *id = NULL;
const char *op = NULL;
int rc = pcmk_rc_undetermined;
if ((date_expression == NULL) || (now == NULL)) {
return EINVAL;
}
// Get expression ID (for logging)
id = pcmk__xe_id(date_expression);
if (pcmk__str_empty(id)) { // Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn(PCMK_XE_DATE_EXPRESSION " element has no "
PCMK_XA_ID);
id = "without ID"; // for logging
}
op = crm_element_value(date_expression, PCMK_XA_OPERATION);
if (pcmk__str_eq(op, PCMK_VALUE_IN_RANGE,
pcmk__str_null_matches|pcmk__str_casei)) {
rc = evaluate_in_range(date_expression, id, now, next_change);
} else if (pcmk__str_eq(op, PCMK_VALUE_DATE_SPEC, pcmk__str_casei)) {
xmlNode *date_spec = first_named_child(date_expression,
PCMK_XE_DATE_SPEC);
if (date_spec == NULL) { // Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION " %s "
"as not passing because " PCMK_VALUE_DATE_SPEC
" operations require a " PCMK_XE_DATE_SPEC
" subelement", id);
} else {
// @TODO set next_change appropriately
rc = pcmk__evaluate_date_spec(date_spec, now);
}
} else if (pcmk__str_eq(op, PCMK_VALUE_GT, pcmk__str_casei)) {
rc = evaluate_gt(date_expression, id, now, next_change);
} else if (pcmk__str_eq(op, PCMK_VALUE_LT, pcmk__str_casei)) {
rc = evaluate_lt(date_expression, id, now, next_change);
} else { // Not possible with schema validation enabled
/* @COMPAT When we can break behavioral backward compatibility,
* return pcmk_rc_unpack_error
*/
pcmk__config_warn("Treating " PCMK_XE_DATE_EXPRESSION
" %s as not passing because '%s' is not a valid "
PCMK_XE_OPERATION, op);
}
crm_trace(PCMK_XE_DATE_EXPRESSION " %s (%s): %s (%d)",
id, op, pcmk_rc_str(rc), rc);
return rc;
}
+/*!
+ * \internal
+ * \brief Go through submatches in a string, either counting how many bytes
+ * would be needed for the expansion, or performing the expansion,
+ * as requested
+ *
+ * \param[in] string String possibly containing submatch variables
+ * \param[in] match String that matched the regular expression
+ * \param[in] submatches Regular expression submatches (as set by regexec())
+ * \param[in] nmatches Number of entries in \p submatches[]
+ * \param[out] expansion If not NULL, expand string here (must be
+ * pre-allocated to appropriate size)
+ * \param[out] nbytes If not NULL, set to size needed for expansion
+ *
+ * \return true if any expansion is needed, otherwise false
+ */
+static bool
+process_submatches(const char *string, const char *match,
+ const regmatch_t submatches[], int nmatches,
+ char *expansion, size_t *nbytes)
+{
+ bool expanded = false;
+ const char *src = string;
+
+ if (nbytes != NULL) {
+ *nbytes = 1; // Include space for terminator
+ }
+
+ while (*src != '\0') {
+ int submatch = 0;
+ size_t match_len = 0;
+
+ if ((src[0] != '%') || !isdigit(src[1])) {
+ /* src does not point to the first character of a %N sequence,
+ * so expand this character as-is
+ */
+ if (expansion != NULL) {
+ *expansion++ = *src;
+ }
+ if (nbytes != NULL) {
+ ++(*nbytes);
+ }
+ ++src;
+ continue;
+ }
+
+ submatch = src[1] - '0';
+ src += 2; // Skip over %N sequence in source string
+ expanded = true; // Expansion will be different from source
+
+ // Omit sequence from expansion unless it has a non-empty match
+ if ((nmatches <= submatch) // Not enough submatches
+ || (submatches[submatch].rm_so < 0) // Pattern did not match
+ || (submatches[submatch].rm_eo
+ <= submatches[submatch].rm_so)) { // Match was empty
+ continue;
+ }
+
+ match_len = submatches[submatch].rm_eo - submatches[submatch].rm_so;
+ if (nbytes != NULL) {
+ *nbytes += match_len;
+ }
+ if (expansion != NULL) {
+ memcpy(expansion, match + submatches[submatch].rm_so,
+ match_len);
+ expansion += match_len;
+ }
+ }
+
+ return expanded;
+}
+
/*!
* \internal
* \brief Expand any regular expression submatches (%0-%9) in a string
*
* \param[in] string String possibly containing submatch variables
* \param[in] match String that matched the regular expression
* \param[in] submatches Regular expression submatches (as set by regexec())
* \param[in] nmatches Number of entries in \p submatches[]
*
* \return Newly allocated string identical to \p string with submatches
- * expanded, or NULL if there were no matches
+ * expanded on success, or NULL if no expansions were needed
+ * \note The caller is responsible for freeing the result with free()
*/
char *
pcmk__replace_submatches(const char *string, const char *match,
const regmatch_t submatches[], int nmatches)
{
- size_t len = 0;
- int i;
- const char *p, *last_match_index;
- char *p_dst, *result = NULL;
+ size_t nbytes = 0;
+ char *result = NULL;
if (pcmk__str_empty(string)) {
- return NULL;
+ return NULL; // Nothing to expand
}
- p = last_match_index = string;
-
- while (*p) {
- if (*p == '%' && *(p + 1) && isdigit(*(p + 1))) {
- i = *(p + 1) - '0';
- if ((nmatches >= i) && (submatches[i].rm_so != -1)
- && (submatches[i].rm_eo > submatches[i].rm_so)) {
- len += p - last_match_index
- + (submatches[i].rm_eo - submatches[i].rm_so);
- last_match_index = p + 2;
- }
- p++;
- }
- p++;
+ // Calculate how much space will be needed for expanded string
+ if (!process_submatches(string, match, submatches, nmatches, NULL,
+ &nbytes)) {
+ return NULL; // No expansions needed
}
- len += p - last_match_index + 1;
- /* FIXME: Excessive? */
- if (len - 1 <= 0) {
- return NULL;
- }
-
- p_dst = result = calloc(1, len);
- p = string;
-
- while (*p) {
- if (*p == '%' && *(p + 1) && isdigit(*(p + 1))) {
- i = *(p + 1) - '0';
- if ((nmatches >= i) && (submatches[i].rm_so != -1)
- && (submatches[i].rm_eo > submatches[i].rm_so)) {
- // rm_eo can be equal to rm_so, but then there is nothing to do
- int match_len = submatches[i].rm_eo - submatches[i].rm_so;
-
- memcpy(p_dst, match + submatches[i].rm_so, match_len);
- p_dst += match_len;
- }
- p++;
- } else {
- *(p_dst) = *(p);
- p_dst++;
- }
- p++;
- }
+ // Allocate enough space for expanded string
+ result = calloc(nbytes, sizeof(char));
+ CRM_ASSERT(result != NULL);
+ // Expand submatches
+ (void) process_submatches(string, match, submatches, nmatches, result,
+ NULL);
return result;
}
diff --git a/lib/common/tests/rules/pcmk__replace_submatches_test.c b/lib/common/tests/rules/pcmk__replace_submatches_test.c
index e95f20c3e7..d404fcc855 100644
--- a/lib/common/tests/rules/pcmk__replace_submatches_test.c
+++ b/lib/common/tests/rules/pcmk__replace_submatches_test.c
@@ -1,79 +1,81 @@
/*
* Copyright 2024 the Pacemaker project contributors
*
* The version control history for this file may have further details.
*
* This source code is licensed under the GNU General Public License version 2
* or later (GPLv2+) WITHOUT ANY WARRANTY.
*/
#include <crm_internal.h>
#include <regex.h> // regmatch_t
#include <crm/common/rules_internal.h>
#include <crm/common/unittest_internal.h>
// An example matched string with submatches
static const char *match = "this is a string";
static const regmatch_t submatches[] = {
{ .rm_so = 0, .rm_eo = 16 }, // %0 = entire string
{ .rm_so = 5, .rm_eo = 7 }, // %1 = "is"
{ .rm_so = 9, .rm_eo = 9 }, // %2 = empty match
};
static const int nmatches = 3;
static void
assert_submatch(const char *string, const char *reference)
{
char *expanded = NULL;
expanded = pcmk__replace_submatches(string, match, submatches, nmatches);
if ((expanded == NULL) || (reference == NULL)) {
assert_null(expanded);
assert_null(reference);
} else {
assert_int_equal(strcmp(expanded, reference), 0);
}
free(expanded);
}
static void
no_source(void **state)
{
assert_null(pcmk__replace_submatches(NULL, NULL, NULL, 0));
assert_submatch(NULL, NULL);
assert_submatch("", NULL);
}
static void
source_has_no_variables(void **state)
{
- assert_submatch("this has no submatch variables",
- "this has no submatch variables");
- assert_submatch("this ends in a %", "this ends in a %");
- assert_submatch("%this starts with one", "%this starts with one");
+ assert_null(pcmk__replace_submatches("this has no submatch variables",
+ match, submatches, nmatches));
+ assert_null(pcmk__replace_submatches("this ends in a %",
+ match, submatches, nmatches));
+ assert_null(pcmk__replace_submatches("%this starts with one",
+ match, submatches, nmatches));
}
static void
without_matches(void **state)
{
assert_submatch("this has an empty submatch %2",
"this has an empty submatch ");
assert_submatch("this has a nonexistent submatch %3",
"this has a nonexistent submatch ");
}
static void
with_matches(void **state)
{
assert_submatch("%0", match); // %0 matches entire string
assert_submatch("this %1", "this is");
assert_submatch("%1 this %ok", "is this %ok");
}
PCMK__UNIT_TEST(NULL, NULL,
cmocka_unit_test(no_source),
cmocka_unit_test(source_has_no_variables),
cmocka_unit_test(without_matches),
cmocka_unit_test(with_matches))

File Metadata

Mime Type
text/x-diff
Expires
Thu, Oct 16, 3:09 PM (1 d, 3 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
2536488
Default Alt Text
(29 KB)

Event Timeline