diff --git a/cts/cli/regression.dates.exp b/cts/cli/regression.dates.exp index a3b4a9c07d4..bb0378ab81d 100644 --- a/cts/cli/regression.dates.exp +++ b/cts/cli/regression.dates.exp @@ -1,10 +1,10 @@ =#=#=#= Begin test: Invalid period - [] =#=#=#= -crm_time_parse_period error: No ISO 8601 time period given +parse_period error: No ISO 8601 time period given iso8601: Invalid interval specified: =#=#=#= End test: Invalid period - [] - Invalid parameter (2) =#=#=#= * Passed: iso8601 - Invalid period - [] =#=#=#= Begin test: Invalid period - [2019-01-01 00:00:00Z] =#=#=#= -crm_time_parse_period error: '2019-01-01 00:00:00Z' is not a valid ISO 8601 time period because it has no duration or ending time +parse_period error: '2019-01-01 00:00:00Z' is not a valid ISO 8601 time period because it has no duration or ending time iso8601: Invalid interval specified: 2019-01-01 00:00:00Z =#=#=#= End test: Invalid period - [2019-01-01 00:00:00Z] - Invalid parameter (2) =#=#=#= * Passed: iso8601 - Invalid period - [2019-01-01 00:00:00Z] @@ -14,7 +14,7 @@ iso8601: Invalid interval specified: 2019-01-01 00:00:00Z/ =#=#=#= End test: Invalid period - [2019-01-01 00:00:00Z/] - Invalid parameter (2) =#=#=#= * Passed: iso8601 - Invalid period - [2019-01-01 00:00:00Z/] =#=#=#= Begin test: Invalid period - [PT2S/P1M] =#=#=#= -crm_time_parse_period error: 'PT2S/P1M' is not a valid ISO 8601 time period because it has two durations +parse_period error: 'PT2S/P1M' is not a valid ISO 8601 time period because it has two durations iso8601: Invalid interval specified: PT2S/P1M =#=#=#= End test: Invalid period - [PT2S/P1M] - Invalid parameter (2) =#=#=#= * Passed: iso8601 - Invalid period - [PT2S/P1M] @@ -64,20 +64,35 @@ iso8601: Invalid interval specified: P1Y/2019-02-29 00:00:00Z =#=#=#= End test: Invalid period - [P1Y/2019-02-29 00:00:00Z] - Invalid parameter (2) =#=#=#= * Passed: iso8601 - Invalid period - [P1Y/2019-02-29 00:00:00Z] =#=#=#= Begin test: Invalid period - [2019-01-01 00:00:00Z/P] =#=#=#= -crm_time_parse_duration error: 'P' is not a valid ISO 8601 time duration because nothing follows 'P' +pcmk__time_parse_duration error: 'P' is not a valid ISO 8601 time duration because nothing follows 'P' iso8601: Invalid interval specified: 2019-01-01 00:00:00Z/P =#=#=#= End test: Invalid period - [2019-01-01 00:00:00Z/P] - Invalid parameter (2) =#=#=#= * Passed: iso8601 - Invalid period - [2019-01-01 00:00:00Z/P] =#=#=#= Begin test: Invalid period - [P1Z/2019-02-20 00:00:00Z] =#=#=#= -crm_time_parse_duration error: 'P1Z/2019-02-20 00:00:00Z' is not a valid ISO 8601 time duration because 'Z' is not a valid time unit +parse_duration_element error: 'P1Z/2019-02-20 00:00:00Z' is not a valid ISO 8601 duration because 'Z' is not a valid time unit iso8601: Invalid interval specified: P1Z/2019-02-20 00:00:00Z =#=#=#= End test: Invalid period - [P1Z/2019-02-20 00:00:00Z] - Invalid parameter (2) =#=#=#= * Passed: iso8601 - Invalid period - [P1Z/2019-02-20 00:00:00Z] =#=#=#= Begin test: Invalid period - [P1YM/2019-02-20 00:00:00Z] =#=#=#= -crm_time_parse_duration error: 'P1YM/2019-02-20 00:00:00Z' is not a valid ISO 8601 time duration because no valid integer at 'M/2019-02-20 00:00:00Z' +parse_duration_element error: 'P1YM/2019-02-20 00:00:00Z' is not a valid ISO 8601 duration because no valid integer at 'M/2019-02-20 00:00:00Z' iso8601: Invalid interval specified: P1YM/2019-02-20 00:00:00Z =#=#=#= End test: Invalid period - [P1YM/2019-02-20 00:00:00Z] - Invalid parameter (2) =#=#=#= * Passed: iso8601 - Invalid period - [P1YM/2019-02-20 00:00:00Z] +=#=#=#= Begin test: Invalid period - [0000-10-01T00:00:00Z/P1M] =#=#=#= +parse_date error: '0000-10-01T00:00:00Z/P1M' is not a valid ISO 8601 date/time specification because '0' is not a valid year +iso8601: Invalid interval specified: 0000-10-01T00:00:00Z/P1M +=#=#=#= End test: Invalid period - [0000-10-01T00:00:00Z/P1M] - Invalid parameter (2) =#=#=#= +* Passed: iso8601 - Invalid period - [0000-10-01T00:00:00Z/P1M] +=#=#=#= Begin test: Invalid period - [-0001-10-01T00:00:00Z/P1M] =#=#=#= +parse_date error: '-0001-10-01T00:00:00Z/P1M' is not a valid ISO 8601 date/time specification because '4294967295' is not a valid year +iso8601: Invalid interval specified: -0001-10-01T00:00:00Z/P1M +=#=#=#= End test: Invalid period - [-0001-10-01T00:00:00Z/P1M] - Invalid parameter (2) =#=#=#= +* Passed: iso8601 - Invalid period - [-0001-10-01T00:00:00Z/P1M] +=#=#=#= Begin test: Invalid period - [10000-10-01T00:00:00Z/P1M] =#=#=#= +parse_period error: 'P1M' is not a valid ISO 8601 time period because the start is invalid (must be between 0001-01-01T00:00:00 and 9999-12-31T23:59:59) +iso8601: Invalid interval specified: 10000-10-01T00:00:00Z/P1M +=#=#=#= End test: Invalid period - [10000-10-01T00:00:00Z/P1M] - Invalid parameter (2) =#=#=#= +* Passed: iso8601 - Invalid period - [10000-10-01T00:00:00Z/P1M] =#=#=#= Begin test: '2005-040/2005-043' period =#=#=#= Period: 2005-02-09 00:00:00Z to 2005-02-12 00:00:00Z =#=#=#= End test: '2005-040/2005-043' period - OK (0) =#=#=#= diff --git a/cts/cts-cli.in b/cts/cts-cli.in index eadb6a69ba1..b111a6055b7 100644 --- a/cts/cts-cli.in +++ b/cts/cts-cli.in @@ -9,7 +9,7 @@ # We know this is a very long file. # pylint: disable=too-many-lines -__copyright__ = "Copyright 2024-2025 the Pacemaker project contributors" +__copyright__ = "Copyright 2024-2026 the Pacemaker project contributors" __license__ = "GNU General Public License version 2 or later (GPLv2+) WITHOUT ANY WARRANTY" import argparse @@ -251,8 +251,6 @@ def sanitize_output(s): (r'( // pcmk_unpack_nvpair_blocks #include // PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS #include // pcmk_ok, pcmk_rc_*, pcmk_strerror -#include // pcmk_rule_input_t #include // PCMK_XA_*, PCMK_XE_*, etc. #include // crm_system_name #include // lrmd_t @@ -244,7 +243,7 @@ remote_config_check(xmlNode *msg, int call_id, int rc, xmlNode *output, lrmd_t *lrmd = user_data; GHashTable *config_hash = NULL; crm_time_t *now = NULL; - pcmk_rule_input_t rule_input = { NULL, }; + pcmk__rule_input_t rule_input = { NULL, }; if (rc != pcmk_ok) { pcmk__err("Query resulted in an error: %s", pcmk_strerror(rc)); @@ -272,7 +271,7 @@ remote_config_check(xmlNode *msg, int call_id, int rc, xmlNode *output, lrmd__validate_remote_settings(lrmd, config_hash); g_hash_table_destroy(config_hash); - crm_time_free(now); + free(now); } /*! diff --git a/include/crm/common/Makefile.am b/include/crm/common/Makefile.am index cf66a79f240..06e6bdf2bdd 100644 --- a/include/crm/common/Makefile.am +++ b/include/crm/common/Makefile.am @@ -39,6 +39,7 @@ header_HEADERS += results.h header_HEADERS += results_compat.h header_HEADERS += roles.h header_HEADERS += rules.h +header_HEADERS += rules_compat.h header_HEADERS += scheduler.h header_HEADERS += scheduler_types.h header_HEADERS += schemas.h diff --git a/include/crm/common/iso8601.h b/include/crm/common/iso8601.h index 8d3939603c5..caf807b08f6 100644 --- a/include/crm/common/iso8601.h +++ b/include/crm/common/iso8601.h @@ -10,11 +10,6 @@ #ifndef PCMK__CRM_COMMON_ISO8601__H #define PCMK__CRM_COMMON_ISO8601__H -#include -#include // bool -#include // uint32_t -#include - #ifdef __cplusplus extern "C" { #endif @@ -38,16 +33,6 @@ extern "C" { */ typedef struct crm_time_s crm_time_t; -/*! - * \deprecated Use \c crm_time_period_t instead of - * struct crm_time_period_s. - */ -typedef struct crm_time_period_s { - crm_time_t *start; - crm_time_t *end; - crm_time_t *diff; -} crm_time_period_t; - /* Creates a new date/time object conforming to ISO 8601, for example: * Ordinal: 2010-01 12:00:00 +10:00 * Gregorian: 2010-01-01 12:00:00 +10:00 @@ -62,56 +47,6 @@ typedef struct crm_time_period_s { * A timezone of 'Z' denotes UTC time */ crm_time_t *crm_time_new(const char *string); -crm_time_t *crm_time_new_undefined(void); -void crm_time_free(crm_time_t * dt); - -bool crm_time_is_defined(const crm_time_t *t); -char *crm_time_as_string(const crm_time_t *dt, int flags); - -#define crm_time_log_date 0x001 -#define crm_time_log_timeofday 0x002 -#define crm_time_log_with_timezone 0x004 -#define crm_time_log_duration 0x008 - -#define crm_time_ordinal 0x010 -#define crm_time_weeks 0x020 -#define crm_time_seconds 0x100 -#define crm_time_epoch 0x200 -#define crm_time_usecs 0x400 - -crm_time_t *crm_time_parse_duration(const char *duration_str); -crm_time_t *crm_time_calculate_duration(const crm_time_t *dt, - const crm_time_t *value); -crm_time_period_t *crm_time_parse_period(const char *period_str); -void crm_time_free_period(crm_time_period_t *period); - -int crm_time_compare(const crm_time_t *a, const crm_time_t *b); - -int crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m, - uint32_t *s); -int crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m, - uint32_t *d); -int crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d); - -/* Time in seconds since 0000-01-01 00:00:00Z */ -long long crm_time_get_seconds(const crm_time_t *dt); - -/* Time in seconds since 1970-01-01 00:00:00Z */ -long long crm_time_get_seconds_since_epoch(const crm_time_t *dt); - -/* Returns a new time object */ -crm_time_t *pcmk_copy_time(const crm_time_t *source); -crm_time_t *crm_time_add(const crm_time_t *dt, const crm_time_t *value); -crm_time_t *crm_time_subtract(const crm_time_t *dt, const crm_time_t *value); - -/* All crm_time_add_... functions support negative values */ -void crm_time_add_seconds(crm_time_t * dt, int value); -void crm_time_add_minutes(crm_time_t * dt, int value); -void crm_time_add_hours(crm_time_t * dt, int value); -void crm_time_add_days(crm_time_t * dt, int value); -void crm_time_add_weeks(crm_time_t * dt, int value); -void crm_time_add_months(crm_time_t * dt, int value); -void crm_time_add_years(crm_time_t * dt, int value); #ifdef __cplusplus } diff --git a/include/crm/common/iso8601_compat.h b/include/crm/common/iso8601_compat.h index 69d551ee451..5ef6f6915b2 100644 --- a/include/crm/common/iso8601_compat.h +++ b/include/crm/common/iso8601_compat.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -29,6 +29,40 @@ extern "C" { * release. */ +//! \deprecated Do not use +#define crm_time_log_date 0x001 + +//! \deprecated Do not use +#define crm_time_log_timeofday 0x002 + +//! \deprecated Do not use +#define crm_time_log_with_timezone 0x004 + +//! \deprecated Do not use +#define crm_time_log_duration 0x008 + +//! \deprecated Do not use +#define crm_time_ordinal 0x010 + +//! \deprecated Do not use +#define crm_time_weeks 0x020 + +//! \deprecated Do not use +#define crm_time_seconds 0x100 + +//! \deprecated Do not use +#define crm_time_epoch 0x200 + +//! \deprecated Do not use +#define crm_time_usecs 0x400 + +//! \deprecated Do not use +typedef struct crm_time_period_s { + crm_time_t *start; + crm_time_t *end; + crm_time_t *diff; +} crm_time_period_t; + //! \deprecated Do not use bool crm_time_leapyear(int year); @@ -66,6 +100,81 @@ void crm_time_log_alias(int log_level, const char *file, const char *function, int line, const char *prefix, const crm_time_t *date_time, int flags); +//! \deprecated Do not use +void crm_time_free_period(crm_time_period_t *period); + +//! \deprecated Do not use +crm_time_period_t *crm_time_parse_period(const char *period_str); + +//! \deprecated Do not use +crm_time_t *crm_time_calculate_duration(const crm_time_t *dt, + const crm_time_t *value); + +//! \deprecated Do not use +crm_time_t *crm_time_parse_duration(const char *duration_str); + +//! \deprecated Do not use +crm_time_t *crm_time_new_undefined(void); + +//! \deprecated Do not use +bool crm_time_is_defined(const crm_time_t *t); + +//! \deprecated Do not use +char *crm_time_as_string(const crm_time_t *dt, int flags); + +//! \deprecated Do not use +int crm_time_compare(const crm_time_t *a, const crm_time_t *b); + +//! \deprecated Do not use +int crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m, + uint32_t *s); + +//! \deprecated Do not use +int crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m, + uint32_t *d); + +//! \deprecated Do not use +int crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d); + +//! \deprecated Do not use +long long crm_time_get_seconds(const crm_time_t *dt); + +//! \deprecated Do not use +long long crm_time_get_seconds_since_epoch(const crm_time_t *dt); + +//! \deprecated Use \c free() instead +void crm_time_free(crm_time_t *dt); + +//! \deprecated Do not use +crm_time_t *pcmk_copy_time(const crm_time_t *source); + +//! \deprecated Do not use +crm_time_t *crm_time_add(const crm_time_t *dt, const crm_time_t *value); + +//! \deprecated Do not use +crm_time_t *crm_time_subtract(const crm_time_t *dt, const crm_time_t *value); + +//! \deprecated Do not use +void crm_time_add_seconds(crm_time_t *dt, int value); + +//! \deprecated Do not use +void crm_time_add_minutes(crm_time_t *dt, int value); + +//! \deprecated Do not use +void crm_time_add_hours(crm_time_t *dt, int value); + +//! \deprecated Do not use +void crm_time_add_days(crm_time_t *dt, int value); + +//! \deprecated Do not use +void crm_time_add_weeks(crm_time_t *dt, int value); + +//! \deprecated Do not use +void crm_time_add_months(crm_time_t *dt, int value); + +//! \deprecated Do not use +void crm_time_add_years(crm_time_t *dt, int value); + #ifdef __cplusplus } #endif diff --git a/include/crm/common/iso8601_internal.h b/include/crm/common/iso8601_internal.h index f8817ff0d5c..b902b3af36f 100644 --- a/include/crm/common/iso8601_internal.h +++ b/include/crm/common/iso8601_internal.h @@ -27,13 +27,51 @@ extern "C" { #endif -void pcmk__time_get_ywd(const crm_time_t *dt, uint32_t *y, uint32_t *w, - uint32_t *d); +/* @COMPAT These must be kept in line with the deprecated crm_time_* flags until + * those are removed + */ +enum pcmk__time_fmt_flags { + pcmk__time_fmt_date = (UINT32_C(1) << 0), + pcmk__time_fmt_time = (UINT32_C(1) << 1), + pcmk__time_fmt_timezone = (UINT32_C(1) << 2), + + // @COMPAT Nothing sets this internally except tools/iso8601.c (deprecated) + pcmk__time_fmt_duration = (UINT32_C(1) << 3), + + // @COMPAT Nothing sets this internally except tools/iso8601.c (deprecated) + pcmk__time_fmt_ordinal = (UINT32_C(1) << 4), + + // @COMPAT Nothing sets this internally except tools/iso8601.c (deprecated) + pcmk__time_fmt_weeks = (UINT32_C(1) << 5), + + // @COMPAT Nothing sets this internally except tools/iso8601.c (deprecated) + pcmk__time_fmt_seconds = (UINT32_C(1) << 8), + + // @COMPAT Nothing sets this internally except tools/iso8601.c (deprecated) + pcmk__time_fmt_epoch = (UINT32_C(1) << 9), + + pcmk__time_fmt_usecs = (UINT32_C(1) << 10), +}; + +bool pcmk__time_valid_year(int year); + +crm_time_t *pcmk__time_copy(const crm_time_t *source); + +bool pcmk__time_is_initialized(const crm_time_t *dt); +long long pcmk__time_get_seconds(const crm_time_t *dt); +long long pcmk__time_to_unix(const crm_time_t *dt); +char *pcmk__time_text(const crm_time_t *dt, int flags); char *pcmk__time_format_hr(const char *format, const crm_time_t *dt, int usec); char *pcmk__epoch2str(const time_t *source, uint32_t flags); char *pcmk__timespec2str(const struct timespec *ts, uint32_t flags); const char *pcmk__readable_interval(guint interval_ms); + +crm_time_t *pcmk__time_parse_duration(const char *period_s); crm_time_t *pcmk__copy_timet(time_t source_sec); +crm_time_t *pcmk__time_add(const crm_time_t *dt, const crm_time_t *value); +crm_time_t *pcmk__time_subtract(const crm_time_t *dt, const crm_time_t *value); + +int pcmk__time_compare(const crm_time_t *a, const crm_time_t *b); void pcmk__time_log_as(const char *file, const char *function, int line, uint8_t level, const char *prefix, const crm_time_t *dt, @@ -63,6 +101,8 @@ struct crm_time_s { bool duration; }; +bool valid_time(const crm_time_t *dt); + #ifdef __cplusplus } #endif diff --git a/include/crm/common/nvpair_compat.h b/include/crm/common/nvpair_compat.h index 88c4fd250b5..db9510f0d5f 100644 --- a/include/crm/common/nvpair_compat.h +++ b/include/crm/common/nvpair_compat.h @@ -10,11 +10,11 @@ #ifndef PCMK__CRM_COMMON_NVPAIR_COMPAT__H #define PCMK__CRM_COMMON_NVPAIR_COMPAT__H -#include // GHashTable, gpointer, GSList -#include // xmlNode +#include // GHashTable, gpointer, GSList +#include // xmlNode -#include // crm_time_t -#include // pcmk_rule_input_t +#include // crm_time_t +#include // pcmk_rule_input_t #ifdef __cplusplus extern "C" { diff --git a/include/crm/common/nvpair_internal.h b/include/crm/common/nvpair_internal.h index d0582ad953f..f334a2d2a07 100644 --- a/include/crm/common/nvpair_internal.h +++ b/include/crm/common/nvpair_internal.h @@ -18,8 +18,8 @@ #include // gboolean, gpointer, GHashTable #include // xmlNode -#include // pcmk_rule_input_t #include // crm_time_t +#include // pcmk__rule_input_t #include // pcmk__str_eq(), etc. #ifdef __cplusplus @@ -31,7 +31,7 @@ typedef struct { GHashTable *values; // Where to put name/value pairs const char *first_id; // Block with this XML ID should sort first xmlDoc *doc; // XML document to use for resolving IDREFs - pcmk_rule_input_t rule_input; // Data used to evaluate rules + pcmk__rule_input_t rule_input; // Data used to evaluate rules /* Whether each block's values should overwrite any existing ones * @@ -51,7 +51,7 @@ void pcmk__unpack_nvpair_block(gpointer data, gpointer user_data); void pcmk__unpack_nvpair_blocks(const xmlNode *xml, const char *element_name, const char *first_id, - const pcmk_rule_input_t *rule_input, + const pcmk__rule_input_t *rule_input, GHashTable *values, crm_time_t *next_change, xmlDoc *doc); diff --git a/include/crm/common/rules.h b/include/crm/common/rules.h index ec311011878..15f432e1bc3 100644 --- a/include/crm/common/rules.h +++ b/include/crm/common/rules.h @@ -13,7 +13,6 @@ #include // regmatch_t #include // guint, GHashTable -#include // xmlNode #include // crm_time_t @@ -55,65 +54,12 @@ enum expression_type { }; //!@} -/*! - * \brief Data used to evaluate a rule (any \c NULL items are ignored) - * - * \deprecated Use \c pcmk_rule_input_t instead of - * struct pcmk_rule_input. - */ -typedef struct pcmk_rule_input { - // Used to evaluate date expressions - const crm_time_t *now; //!< Current time for rule evaluation purposes - - // Used to evaluate resource type expressions - const char *rsc_standard; //!< Resource standard that rule applies to - const char *rsc_provider; //!< Resource provider that rule applies to - const char *rsc_agent; //!< Resource agent that rule applies to - - // Used to evaluate operation type expressions - const char *op_name; //!< Operation name that rule applies to - guint op_interval_ms; //!< Operation interval that rule applies to - - // Remaining members are used to evaluate node attribute expressions - - /*! - * Node attributes for rule evaluation purposes - * - * \note Though not const, this is used only with g_hash_table_lookup(). - */ - GHashTable *node_attrs; - - // Remaining members are used only within location constraint rules - - /*! - * Resource parameters that can be used as the reference value source - * - * \note Though not const, this is used only with g_hash_table_lookup(). - */ - GHashTable *rsc_params; - - /*! - * Resource meta-attributes that can be used as the reference value source - * - * \note Though not const, this is used only with g_hash_table_lookup(). - */ - GHashTable *rsc_meta; - - //! Resource ID to compare against a location constraint's resource pattern - const char *rsc_id; - - //! Resource pattern submatches (as set by regexec()) for rsc_id - const regmatch_t *rsc_id_submatches; - - //! Number of entries in rsc_id_submatches - int rsc_id_nmatches; -} pcmk_rule_input_t; - -int pcmk_evaluate_rule(xmlNode *rule, const pcmk_rule_input_t *rule_input, - crm_time_t *next_change); - #ifdef __cplusplus } #endif +#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) +#include +#endif + #endif // PCMK__CRM_COMMON_RULES__H diff --git a/include/crm/common/rules_compat.h b/include/crm/common/rules_compat.h new file mode 100644 index 00000000000..a3deb76ea20 --- /dev/null +++ b/include/crm/common/rules_compat.h @@ -0,0 +1,53 @@ +/* + * Copyright 2004-2026 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. + */ + +#ifndef PCMK__CRM_COMMON_RULES_COMPAT__H +#define PCMK__CRM_COMMON_RULES_COMPAT__H + +#include // xmlNode + +#include // crm_time_t + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \file + * \brief Deprecated Pacemaker rules API + * \ingroup core + * \deprecated Do not include this header directly. The nvpair APIs in this + * header, and the header itself, will be removed in a future + * release. + */ + +typedef struct pcmk_rule_input { + const crm_time_t *now; + const char *rsc_standard; + const char *rsc_provider; + const char *rsc_agent; + const char *op_name; + guint op_interval_ms; + GHashTable *node_attrs; + GHashTable *rsc_params; + GHashTable *rsc_meta; + const char *rsc_id; + const regmatch_t *rsc_id_submatches; + int rsc_id_nmatches; +} pcmk_rule_input_t; + +//! \deprecated Do not use +int pcmk_evaluate_rule(xmlNode *rule, const pcmk_rule_input_t *rule_input, + crm_time_t *next_change); + +#ifdef __cplusplus +} +#endif + +#endif // PCMK__CRM_COMMON_RULES_COMPAT__H diff --git a/include/crm/common/rules_internal.h b/include/crm/common/rules_internal.h index 91ea703febf..8eccc37572f 100644 --- a/include/crm/common/rules_internal.h +++ b/include/crm/common/rules_internal.h @@ -15,10 +15,12 @@ #define PCMK__CRM_COMMON_RULES_INTERNAL__H #include // regmatch_t + +#include // GHashTable #include // xmlNode -#include // enum expression_type, etc. #include // crm_time_t +#include // enum expression_type, etc. #ifdef __cplusplus extern "C" { @@ -30,6 +32,58 @@ enum pcmk__combine { pcmk__combine_or, }; +/*! + * \internal + * \brief Data used to evaluate a rule (any \c NULL items are ignored) + */ +typedef struct { + // Used to evaluate date expressions + const crm_time_t *now; //!< Current time to use for rule evaluation + + // Used to evaluate resource type expressions + const char *rsc_standard; //!< Resource standard that rule applies to + const char *rsc_provider; //!< Resource provider that rule applies to + const char *rsc_agent; //!< Resource agent that rule applies to + + // Used to evaluate operation type expressions + const char *op_name; //!< Operation name that rule applies to + unsigned int op_interval_ms; //!< Operation interval that rule applies to + + // Remaining members are used to evaluate node attribute expressions + + /*! + * Node attributes for rule evaluation purposes + * + * \note Though not const, this is used only with \c g_hash_table_lookup(). + */ + GHashTable *node_attrs; + + // Remaining members are used only within location constraint rules + + /*! + * Resource parameters that can be used as the reference value source + * + * \note Though not const, this is used only with \c g_hash_table_lookup(). + */ + GHashTable *rsc_params; + + /*! + * Resource meta-attributes that can be used as the reference value source + * + * \note Though not const, this is used only with \c g_hash_table_lookup(). + */ + GHashTable *rsc_meta; + + //! Resource ID to compare against a location constraint's resource pattern + const char *rsc_id; + + //! Resource pattern submatches (as set by \c regexec()) for \c rsc_id + const regmatch_t *rsc_id_submatches; + + //! Number of entries in rsc_id_submatches + int rsc_id_nmatches; +} pcmk__rule_input_t; + enum expression_type pcmk__condition_type(const xmlNode *condition); char *pcmk__replace_submatches(const char *string, const char *match, const regmatch_t submatches[], int nmatches); @@ -38,9 +92,19 @@ enum pcmk__combine pcmk__parse_combine(const char *combine); int pcmk__evaluate_date_expression(const xmlNode *date_expression, const crm_time_t *now, crm_time_t *next_change); -int pcmk__evaluate_condition(xmlNode *expr, const pcmk_rule_input_t *rule_input, +int pcmk__evaluate_condition(xmlNode *expr, + const pcmk__rule_input_t *rule_input, crm_time_t *next_change); +int pcmk__evaluate_rule(xmlNode *rule, const pcmk__rule_input_t *rule_input, + crm_time_t *next_change); + +// Redeclare because the definition is in rules_compat.h +typedef struct pcmk_rule_input pcmk_rule_input_t; + +void pcmk__rule_input_convert(const pcmk_rule_input_t *source, + pcmk__rule_input_t *target); + #ifdef __cplusplus } #endif diff --git a/include/crm/common/scheduler.h b/include/crm/common/scheduler.h index 5e347ff00bf..b01f970a32b 100644 --- a/include/crm/common/scheduler.h +++ b/include/crm/common/scheduler.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -10,19 +10,13 @@ #ifndef PCMK__CRM_COMMON_SCHEDULER__H #define PCMK__CRM_COMMON_SCHEDULER__H -#include -#include // time_t -#include // xmlNode -#include // guint, GList, GHashTable +#include // bool +#include // uint64_t -#include // crm_time_t +#include // GList +#include // xmlNode -#include -#include -#include -#include -#include -#include +#include // pcmk_node_t, pcmk_scheduler_t #ifdef __cplusplus extern "C" { diff --git a/include/crm/pengine/internal.h b/include/crm/pengine/internal.h index e27d41cc0cd..e749d5b249e 100644 --- a/include/crm/pengine/internal.h +++ b/include/crm/pengine/internal.h @@ -338,7 +338,7 @@ bool pe__shutdown_requested(const pcmk_node_t *node); void pe__register_messages(pcmk__output_t *out); void pe__unpack_dataset_nvpairs(const xmlNode *xml_obj, const char *set_name, - const pcmk_rule_input_t *rule_input, + const pcmk__rule_input_t *rule_input, GHashTable *hash, const char *always_first, pcmk_scheduler_t *scheduler); diff --git a/include/crm/pengine/rules_compat.h b/include/crm/pengine/rules_compat.h index 4112075f33e..036bf0b4d89 100644 --- a/include/crm/pengine/rules_compat.h +++ b/include/crm/pengine/rules_compat.h @@ -30,7 +30,7 @@ extern "C" { */ // @COMPAT sbd's configure script checks for this (as of at least 1.5.2) -//! \deprecated Use pcmk_evaluate_rule() instead +//! \deprecated Do not use gboolean test_rule(xmlNode *rule, GHashTable *node_hash, enum rsc_role_e role, crm_time_t *now); diff --git a/include/crm/pengine/status.h b/include/crm/pengine/status.h index 8d7722c72b0..d46428b6870 100644 --- a/include/crm/pengine/status.h +++ b/include/crm/pengine/status.h @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -12,7 +12,7 @@ # include // gboolean # include // bool -# include +# include // pe_find # include // pcmk_node_t, pcmk_resource_t, etc. # include diff --git a/include/pacemaker.h b/include/pacemaker.h index b2bbff4b047..299cf73b698 100644 --- a/include/pacemaker.h +++ b/include/pacemaker.h @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 the Pacemaker project contributors + * Copyright 2019-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -16,6 +16,7 @@ # include # include +# include // crm_time_t # include # include diff --git a/lib/cib/cib_utils.c b/lib/cib/cib_utils.c index 709a799c538..076ed67434e 100644 --- a/lib/cib/cib_utils.c +++ b/lib/cib/cib_utils.c @@ -133,7 +133,7 @@ static void read_config(GHashTable *options, xmlNode *current_cib) { crm_time_t *now = NULL; - pcmk_rule_input_t rule_input = { 0, }; + pcmk__rule_input_t rule_input = { NULL, }; xmlNode *config = pcmk_find_cib_element(current_cib, PCMK_XE_CRM_CONFIG); if (config == NULL) { @@ -146,7 +146,7 @@ read_config(GHashTable *options, xmlNode *current_cib) pcmk__unpack_nvpair_blocks(config, PCMK_XE_CLUSTER_PROPERTY_SET, PCMK_VALUE_CIB_BOOTSTRAP_OPTIONS, &rule_input, options, NULL, config->doc); - crm_time_free(now); + free(now); } static bool diff --git a/lib/common/alerts.c b/lib/common/alerts.c index 6906c74059d..77e5bbbd8d3 100644 --- a/lib/common/alerts.c +++ b/lib/common/alerts.c @@ -149,13 +149,13 @@ unpack_alert_options(xmlNode *xml, pcmk__alert_t *entry, guint *max_timeout) const char *value = NULL; int rc = pcmk_rc_ok; - pcmk_rule_input_t rule_input = { + pcmk__rule_input_t rule_input = { .now = now, }; pcmk__unpack_nvpair_blocks(xml, PCMK_XE_META_ATTRIBUTES, NULL, &rule_input, config_hash, NULL, xml->doc); - crm_time_free(now); + free(now); value = g_hash_table_lookup(config_hash, PCMK_META_ENABLED); if ((value != NULL) && !pcmk__is_true(value)) { diff --git a/lib/common/crmcommon_private.h b/lib/common/crmcommon_private.h index 58add461801..cfffcaf977c 100644 --- a/lib/common/crmcommon_private.h +++ b/lib/common/crmcommon_private.h @@ -28,7 +28,6 @@ #include // crm_time_t #include // mainloop_io_t #include // crm_exit_t -#include // pcmk_rule_input_t #ifdef __cplusplus extern "C" { @@ -203,6 +202,24 @@ enum pcmk__time_component { pcmk__time_seconds, }; +G_GNUC_INTERNAL +void pcmk__time_add_days(crm_time_t *dt, int value); + +G_GNUC_INTERNAL +void pcmk__time_add_seconds(crm_time_t *dt, int value); + +G_GNUC_INTERNAL +void pcmk__time_get_timeofday(const crm_time_t *dt, uint32_t *hour, + uint32_t *minute, uint32_t *second); + +G_GNUC_INTERNAL +void pcmk__time_get_ymd(const crm_time_t *dt, uint32_t *year, uint32_t *month, + uint32_t *day); + +G_GNUC_INTERNAL +void pcmk__time_get_ywd(const crm_time_t *dt, uint32_t *y, uint32_t *w, + uint32_t *d); + G_GNUC_INTERNAL const char *pcmk__time_component_attr(enum pcmk__time_component component); @@ -213,6 +230,9 @@ int pcmk__add_time_from_xml(crm_time_t *t, enum pcmk__time_component component, G_GNUC_INTERNAL void pcmk__set_time_if_earlier(crm_time_t *target, const crm_time_t *source); +G_GNUC_INTERNAL +void pcmk__time_add_years(crm_time_t *dt, int value); + /* * IPC @@ -427,15 +447,15 @@ int pcmk__evaluate_date_spec(const xmlNode *date_spec, const crm_time_t *now); G_GNUC_INTERNAL int pcmk__evaluate_attr_expression(const xmlNode *expression, - const pcmk_rule_input_t *rule_input); + const pcmk__rule_input_t *rule_input); G_GNUC_INTERNAL int pcmk__evaluate_rsc_expression(const xmlNode *expr, - const pcmk_rule_input_t *rule_input); + const pcmk__rule_input_t *rule_input); G_GNUC_INTERNAL int pcmk__evaluate_op_expression(const xmlNode *expr, - const pcmk_rule_input_t *rule_input); + const pcmk__rule_input_t *rule_input); /* diff --git a/lib/common/fuzzers/iso8601_fuzzer.c b/lib/common/fuzzers/iso8601_fuzzer.c index 668f74a7d00..60b93d67eee 100644 --- a/lib/common/fuzzers/iso8601_fuzzer.c +++ b/lib/common/fuzzers/iso8601_fuzzer.c @@ -1,5 +1,5 @@ /* - * Copyright 2024-2025 the Pacemaker project contributors + * Copyright 2024-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -21,8 +21,6 @@ int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { char *ns = NULL; - crm_time_period_t *period = NULL; - struct timespec tv = { 0, }; crm_time_t *now = NULL; char *result = NULL; @@ -34,14 +32,11 @@ LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) ns = pcmk__assert_alloc(size + 1, sizeof(char)); memcpy(ns, data, size); - period = crm_time_parse_period(ns); - crm_time_free_period(period); - qb_util_timespec_from_epoch_get(&tv); now = pcmk__copy_timet(tv.tv_sec); result = pcmk__time_format_hr(ns, now, (int) (tv.tv_nsec / QB_TIME_NS_IN_USEC)); - crm_time_free(now); + free(now); free(result); free(ns); diff --git a/lib/common/iso8601.c b/lib/common/iso8601.c index f7a37384c44..e5eeb1114e0 100644 --- a/lib/common/iso8601.c +++ b/lib/common/iso8601.c @@ -55,6 +55,9 @@ #define HOURS_IN_DAY 24 #define SECONDS_IN_DAY (SECONDS_IN_HOUR * HOURS_IN_DAY) +#define BEGIN_VALID_RANGE_S "0001-01-01T00:00:00" +#define END_VALID_RANGE_S "9999-12-31T23:59:59" + /*! * \internal * \brief Validate a seconds/microseconds tuple @@ -72,32 +75,36 @@ && (((sec) == 0) || ((usec) == 0) || (((sec) < 0) == ((usec) < 0)))) /*! - * \brief Allocate memory for an uninitialized time object + * \internal + * \brief Check whether a year is positive and representable by four digits * - * \return Newly allocated time object (guaranteed not to be \c NULL) + * \param[in] year Year * - * \note The caller is responsible for freeing the return value using - * \c crm_time_free(). + * \return \c true if \p year is between 1 and 9999 (inclusive), or \c false + * otherwise */ -crm_time_t * -crm_time_new_undefined(void) +bool +pcmk__time_valid_year(int year) { - return pcmk__assert_alloc(1, sizeof(crm_time_t)); + return (year >= 1) && (year <= 9999); } static bool is_leap_year(int year) { - return ((year % 4) == 0) - && (((year % 100) != 0) || (year % 400 == 0)); -} + /* @COMPAT Remove this fallback when we can ensure that the year argument is + * always in the range 1 to 9999. + */ + if (!pcmk__time_valid_year(year)) { + return ((year % 4) == 0) + && (((year % 100) != 0) || (year % 400 == 0)); + } -// Jan-Dec plus Feb of leap years -static int month_days[13] = { - 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31, 29 -}; + return g_date_is_leap_year(year); +} /*! + * \internal * \brief Return number of days in given month of given year * * \param[in] month Ordinal month (1-12) @@ -108,39 +115,58 @@ static int month_days[13] = { static int days_in_month_year(int month, int year) { - if ((month < 1) || (month > 12) || (year < 1)) { + if (!g_date_valid_month(month)) { + return 0; + } + + if (year < 1) { return 0; } - if ((month == 2) && is_leap_year(year)) { - month = 13; + + /* @COMPAT Remove this fallback when we can ensure that the year argument is + * always in the range 1 to 9999. g_date_get_days_in_month() takes a + * GDateYear, which is defined as guint16. + */ + if (year > UINT16_MAX) { + static const int month_days[12] = { + 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 + }; + + if ((month == 2) && is_leap_year(year)) { + return month_days[1] + 1; + } + + return month_days[month - 1]; } - return month_days[month - 1]; + + return g_date_get_days_in_month(month, year); } /*! * \internal * \brief Get ordinal day number of year corresponding to given date * - * \param[in] y Year - * \param[in] m Month (1-12) - * \param[in] d Day of month (1-31) + * \param[in] year Year + * \param[in] month Month (1-12) + * \param[in] day Day of month (1-31) * - * \return Day number of year \p y corresponding to month \p m and day \p d, - * or 0 for invalid arguments + * \return Day number of year \p year corresponding to month \p month and day + * \p day, or 0 for invalid arguments */ static int -get_ordinal_days(uint32_t y, uint32_t m, uint32_t d) +get_ordinal_days(uint32_t year, uint32_t month, uint32_t day) { - int result = 0; + int prev_month_days = 0; - CRM_CHECK((y > 0) && (y <= INT_MAX) && (m >= 1) && (m <= 12) - && (d >= 1) && (d <= 31), return 0); + CRM_CHECK((year >= 1) && (year <= INT_MAX) + && (month >= 1) && (month <= 12) + && (day >= 1) && (day <= 31), return 0); - result = d; - for (int lpc = 1; lpc < m; lpc++) { - result += days_in_month_year(lpc, y); + for (int i = 1; i < month; i++) { + prev_month_days += days_in_month_year(i, year); } - return result; + + return prev_month_days + day; } static int @@ -299,21 +325,73 @@ parse_offset(const char *offset_str, int *offset) return true; } +/*! + * \internal + * \brief Convert seconds to hours, minutes, and seconds + * + * The resulting minutes and seconds are in the range [0, 59]. Accordingly, the + * number of hours is \p seconds_i divided by \c SECONDS_IN_HOUR. + * + * \param[in] seconds_i Seconds to convert + * \param[out] hours Where to store hours + * \param[out] minutes Where to store minutes + * \param[out] seconds If not \c NULL, where to store seconds + */ static void -seconds_to_hms(int seconds, uint32_t *h, uint32_t *m, uint32_t *s) +seconds_to_hms(int seconds_i, uint32_t *hours, uint32_t *minutes, + uint32_t *seconds) { - int hours = 0; - int minutes = 0; + int hours_i = 0; + int minutes_i = 0; + + hours_i = seconds_i / SECONDS_IN_HOUR; + seconds_i %= SECONDS_IN_HOUR; + + minutes_i = seconds_i / SECONDS_IN_MINUTE; + seconds_i %= SECONDS_IN_MINUTE; + + *hours = (uint32_t) QB_ABS(hours_i); + *minutes = (uint32_t) QB_ABS(minutes_i); + if (seconds != NULL) { + *seconds = (uint32_t) QB_ABS(seconds_i); + } +} - hours = seconds / SECONDS_IN_HOUR; - seconds %= SECONDS_IN_HOUR; +/*! + * \internal + * \brief Add days to a date/time + * + * \param[in,out] dt Time to modify + * \param[in] value Number of days to add (may be negative to subtract) + */ +void +pcmk__time_add_days(crm_time_t *dt, int value) +{ + pcmk__assert(dt != NULL); - minutes = seconds / SECONDS_IN_MINUTE; - seconds %= SECONDS_IN_MINUTE; + if (value > 0) { + while ((dt->days + (long long) value) > year_days(dt->years)) { + if (dt->years == INT_MAX) { + // Clip to latest we can handle + dt->days = year_days(dt->years); + return; + } + value -= year_days(dt->years); + dt->years++; + } + } else if (value < 0) { + const int min_days = dt->duration? 0 : 1; - *h = (uint32_t) QB_ABS(hours); - *m = (uint32_t) QB_ABS(minutes); - *s = (uint32_t) QB_ABS(seconds); + while ((dt->days + (long long) value) < min_days) { + if (dt->years <= 1) { + dt->days = 1; // Clip to earliest we can handle (no BCE) + return; + } + dt->years--; + value += year_days(dt->years); + } + } + dt->days += value; } /*! @@ -329,7 +407,8 @@ seconds_to_hms(int seconds, uint32_t *h, uint32_t *m, uint32_t *s) static bool parse_time(const char *time_str, crm_time_t *a_time) { - uint32_t h, m, s; + uint32_t h = 0; + uint32_t m = 0; const char *offset_s = NULL; tzset(); @@ -357,14 +436,14 @@ parse_time(const char *time_str, crm_time_t *a_time) return false; } - seconds_to_hms(a_time->offset, &h, &m, &s); + seconds_to_hms(a_time->offset, &h, &m, NULL); pcmk__trace("Got tz: %c%2." PRIu32 ":%.2" PRIu32, (a_time->offset < 0)? '-' : '+', h, m); if (a_time->seconds == SECONDS_IN_DAY) { // 24:00:00 == 00:00:00 of next day a_time->seconds = 0; - crm_time_add_days(a_time, 1); + pcmk__time_add_days(a_time, 1); } return true; } @@ -378,7 +457,7 @@ parse_time(const char *time_str, crm_time_t *a_time) * \return \c true if days and seconds are valid given the year, or \c false * otherwise */ -static bool +bool valid_time(const crm_time_t *dt) { return (dt != NULL) @@ -398,7 +477,7 @@ valid_time(const crm_time_t *dt) static crm_time_t * parse_date(const char *date_str) { - const uint32_t flags = crm_time_log_date|crm_time_log_timeofday; + const uint32_t flags = pcmk__time_fmt_date|pcmk__time_fmt_time; const char *time_s = NULL; crm_time_t *dt = NULL; @@ -426,7 +505,7 @@ parse_date(const char *date_str) goto parse_time_segment; } - dt = crm_time_new_undefined(); + dt = pcmk__assert_alloc(1, sizeof(crm_time_t)); if ((strncasecmp(PCMK__VALUE_EPOCH, date_str, 5) == 0) && ((date_str[5] == '\0') @@ -529,15 +608,15 @@ parse_date(const char *date_str) year, jan1, week, day, date_str); dt->years = year; - crm_time_add_days(dt, (week - 1) * 7); + pcmk__time_add_days(dt, (week - 1) * 7); if (jan1 <= 4) { - crm_time_add_days(dt, 1 - jan1); + pcmk__time_add_days(dt, 1 - jan1); } else { - crm_time_add_days(dt, 8 - jan1); + pcmk__time_add_days(dt, 8 - jan1); } - crm_time_add_days(dt, day); + pcmk__time_add_days(dt, day); } goto parse_time_segment; } @@ -568,30 +647,59 @@ parse_date(const char *date_str) return dt; invalid: - crm_time_free(dt); + free(dt); errno = EINVAL; return NULL; } +/*! + * \internal + * \brief Add a given number of seconds to a date/time or duration + * + * \param[in,out] dt Date/time or duration to add seconds to + * \param[in] value Number of seconds to add + */ +void +pcmk__time_add_seconds(crm_time_t *dt, int value) +{ + int days = value / SECONDS_IN_DAY; + + pcmk__assert(dt != NULL); + + dt->seconds += value % SECONDS_IN_DAY; + + // Check whether the addition crossed a day boundary + if (dt->seconds > SECONDS_IN_DAY) { + ++days; + dt->seconds -= SECONDS_IN_DAY; + + } else if (dt->seconds < 0) { + --days; + dt->seconds += SECONDS_IN_DAY; + } + + pcmk__time_add_days(dt, days); +} + // Return value is guaranteed not to be NULL static crm_time_t * copy_time_to_utc(const crm_time_t *dt) { - const uint32_t flags = crm_time_log_date - |crm_time_log_timeofday - |crm_time_log_with_timezone; + const uint32_t flags = pcmk__time_fmt_date + |pcmk__time_fmt_time + |pcmk__time_fmt_timezone; crm_time_t *utc = NULL; pcmk__assert(dt != NULL); - utc = crm_time_new_undefined(); + utc = pcmk__assert_alloc(1, sizeof(crm_time_t)); utc->years = dt->years; utc->days = dt->days; utc->seconds = dt->seconds; utc->offset = 0; if (dt->offset != 0) { - crm_time_add_seconds(utc, -dt->offset); + pcmk__time_add_seconds(utc, -dt->offset); } else { // Durations (the only things that can include months) never have a TZ @@ -613,28 +721,33 @@ crm_time_new(const char *date_time) return parse_date(date_time); } +crm_time_t * +pcmk__time_copy(const crm_time_t *source) +{ + crm_time_t *target = pcmk__assert_alloc(1, sizeof(crm_time_t)); + + *target = *source; + return target; +} + /*! * \brief Check whether a time object has been initialized yet * - * \param[in] t Time object to check + * \param[in] dt Time object to check * - * \return \c true if time object has been initialized, \c false otherwise + * \return \c true if time object has been initialized, or \c false otherwise */ bool -crm_time_is_defined(const crm_time_t *t) +pcmk__time_is_initialized(const crm_time_t *dt) { - // Any nonzero member indicates something has been done to t - return (t != NULL) && (t->years || t->months || t->days || t->seconds - || t->offset || t->duration); -} - -void -crm_time_free(crm_time_t * dt) -{ - if (dt == NULL) { - return; - } - free(dt); + // Any nonzero member indicates something has been done to dt + return (dt != NULL) + && ((dt->years != 0) + || (dt->months != 0) + || (dt->days != 0) + || (dt->seconds != 0) + || (dt->offset != 0) + || (dt->duration)); } void @@ -642,7 +755,7 @@ pcmk__time_log_as(const char *file, const char *function, int line, uint8_t level, const char *prefix, const crm_time_t *dt, uint32_t flags) { - char *date_s = crm_time_as_string(dt, flags); + char *date_s = pcmk__time_text(dt, flags); if (prefix != NULL) { char *old = date_s; @@ -659,19 +772,23 @@ pcmk__time_log_as(const char *file, const char *function, int line, free(date_s); } -int -crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m, - uint32_t *s) +void +pcmk__time_get_timeofday(const crm_time_t *dt, uint32_t *hour, + uint32_t *minute, uint32_t *second) { - seconds_to_hms(dt->seconds, h, m, s); - return TRUE; + pcmk__assert((dt != NULL) && (hour != NULL) && (minute != NULL) + && (second != NULL)); + + seconds_to_hms(dt->seconds, hour, minute, second); } +// Time in seconds since 0000-01-01 00:00:00Z long long -crm_time_get_seconds(const crm_time_t *dt) +pcmk__time_get_seconds(const crm_time_t *dt) { crm_time_t *utc = NULL; - long long in_seconds = 0; + long long days = 0; + long long seconds = 0; if (dt == NULL) { return 0; @@ -682,45 +799,51 @@ crm_time_get_seconds(const crm_time_t *dt) dt = utc; } - // @TODO We should probably use <= if dt is a duration - for (int i = 1; i < dt->years; i++) { - long long dmax = year_days(i); + if (dt->duration) { + /* Assume 365-day years and 30-day months. The correct number of days in + * years and months varies depending on the start date to which the + * duration will be applied, which is unknown. + */ + days = (365 * (long long) dt->years) + + (30 * (long long) dt->months) + + dt->days; - in_seconds += SECONDS_IN_DAY * dmax; - } + } else { + // The months field can be set only for durations, so ignore it here + for (int i = 1; i < dt->years; i++) { + days += year_days(i); + } - /* utc->months can be set only for durations. By definition, the value - * varies depending on the (unknown) start date to which the duration will - * be applied. Assume 30-day months so that something vaguely sane happens - * in this case. - */ - if (dt->months > 0) { - in_seconds += SECONDS_IN_DAY * 30 * (long long) (dt->months); + // This is probably always true + if (dt->days > 0) { + days += dt->days - 1; + } } - if (dt->days > 0) { - in_seconds += SECONDS_IN_DAY * (long long) (dt->days - 1); - } - in_seconds += dt->seconds; + seconds = dt->seconds + (SECONDS_IN_DAY * days); - crm_time_free(utc); - return in_seconds; + free(utc); + return seconds; } -#define EPOCH_SECONDS 62135596800ULL /* Calculated using crm_time_get_seconds() */ +#define EPOCH_SECONDS 62135596800ULL // Calculated using pcmk__time_get_seconds +// Time in seconds since 1970-01-01 00:00:00Z long long -crm_time_get_seconds_since_epoch(const crm_time_t *dt) +pcmk__time_to_unix(const crm_time_t *dt) { - return (dt == NULL)? 0 : (crm_time_get_seconds(dt) - EPOCH_SECONDS); + return (dt == NULL)? 0 : (pcmk__time_get_seconds(dt) - EPOCH_SECONDS); } -int -crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m, - uint32_t *d) +void +pcmk__time_get_ymd(const crm_time_t *dt, uint32_t *year, uint32_t *month, + uint32_t *day) { int months = 0; int days = dt->days; + pcmk__assert((dt != NULL) && (year != NULL) && (month != NULL) + && (day != NULL)); + if(dt->years != 0) { for (months = 1; months <= 12 && days > 0; months++) { int mdays = days_in_month_year(months, dt->years); @@ -740,20 +863,9 @@ crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m, /* This is a duration not including months, still don't convert the days field */ } - *y = dt->years; - *m = months; - *d = days; - pcmk__trace("%.4d-%.3d -> %.4d-%.2d-%.2d", dt->years, dt->days, dt->years, - months, days); - return TRUE; -} - -int -crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d) -{ - *y = dt->years; - *d = dt->days; - return TRUE; + *year = dt->years; + *month = months; + *day = days; } void @@ -761,13 +873,17 @@ pcmk__time_get_ywd(const crm_time_t *dt, uint32_t *y, uint32_t *w, uint32_t *d) { // Based on ISO week date: https://en.wikipedia.org/wiki/ISO_week_date int year_num = 0; - int jan1 = jan1_day_of_week(dt->years); + int jan1 = 0; int h = -1; + pcmk__assert((dt != NULL) && (y != NULL) && (w != NULL) && (d != NULL)); + if (dt->days <= 0) { return; } + jan1 = jan1_day_of_week(dt->years); + /* 6. Find the Weekday for Y M D */ h = dt->days + jan1 - 1; *d = 1 + ((h - 1) % 7); @@ -932,7 +1048,7 @@ time_as_string_common(const crm_time_t *dt, int usec, uint32_t flags) GString *buf = NULL; char *result = NULL; - if (!crm_time_is_defined(dt)) { + if (!pcmk__time_is_initialized(dt)) { return pcmk__str_copy(""); } @@ -944,21 +1060,23 @@ time_as_string_common(const crm_time_t *dt, int usec, uint32_t flags) * These never depend on time zone. */ - if (pcmk__is_set(flags, crm_time_log_duration)) { - duration_as_string(dt, usec, pcmk__is_set(flags, crm_time_usecs), buf); + if (pcmk__is_set(flags, pcmk__time_fmt_duration)) { + duration_as_string(dt, usec, pcmk__is_set(flags, pcmk__time_fmt_usecs), + buf); goto done; } - if (pcmk__any_flags_set(flags, crm_time_seconds|crm_time_epoch)) { + if (pcmk__any_flags_set(flags, + pcmk__time_fmt_seconds|pcmk__time_fmt_epoch)) { long long seconds = 0; - if (pcmk__is_set(flags, crm_time_seconds)) { - seconds = crm_time_get_seconds(dt); + if (pcmk__is_set(flags, pcmk__time_fmt_seconds)) { + seconds = pcmk__time_get_seconds(dt); } else { - seconds = crm_time_get_seconds_since_epoch(dt); + seconds = pcmk__time_to_unix(dt); } - if (pcmk__is_set(flags, crm_time_usecs)) { + if (pcmk__is_set(flags, pcmk__time_fmt_usecs)) { sec_usec_as_string(seconds, usec, buf); } else { g_string_append_printf(buf, "%lld", seconds); @@ -967,15 +1085,15 @@ time_as_string_common(const crm_time_t *dt, int usec, uint32_t flags) } // Convert to UTC if local timezone was not requested - if ((dt->offset != 0) && !pcmk__is_set(flags, crm_time_log_with_timezone)) { + if ((dt->offset != 0) && !pcmk__is_set(flags, pcmk__time_fmt_timezone)) { utc = copy_time_to_utc(dt); dt = utc; } // As readable string - if (pcmk__is_set(flags, crm_time_log_date)) { - if (pcmk__is_set(flags, crm_time_weeks)) { // YYYY-WW-D + if (pcmk__is_set(flags, pcmk__time_fmt_date)) { + if (pcmk__is_set(flags, pcmk__time_fmt_weeks)) { // YYYY-WW-D if (dt->days > 0) { uint32_t y = 0; uint32_t w = 0; @@ -987,48 +1105,38 @@ time_as_string_common(const crm_time_t *dt, int usec, uint32_t flags) y, w, d); } - } else if (pcmk__is_set(flags, crm_time_ordinal)) { // YYYY-DDD - uint32_t y = 0; - uint32_t d = 0; - - if (crm_time_get_ordinal(dt, &y, &d)) { - g_string_append_printf(buf, "%" PRIu32 "-%.3" PRIu32, y, d); - } + } else if (pcmk__is_set(flags, pcmk__time_fmt_ordinal)) { // YYYY-DDD + g_string_append_printf(buf, "%d-%.3d", dt->years, dt->days); } else { // YYYY-MM-DD uint32_t y = 0; uint32_t m = 0; uint32_t d = 0; - if (crm_time_get_gregorian(dt, &y, &m, &d)) { - g_string_append_printf(buf, - "%.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32, - y, m, d); - } + pcmk__time_get_ymd(dt, &y, &m, &d); + g_string_append_printf(buf, + "%.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32, + y, m, d); } } - if (pcmk__is_set(flags, crm_time_log_timeofday)) { + if (pcmk__is_set(flags, pcmk__time_fmt_time)) { uint32_t h = 0, m = 0, s = 0; if (buf->len > 0) { g_string_append_c(buf, ' '); } - if (crm_time_get_timeofday(dt, &h, &m, &s)) { - g_string_append_printf(buf, - "%.2" PRIu32 ":%.2" PRIu32 ":%.2" PRIu32, - h, m, s); + pcmk__time_get_timeofday(dt, &h, &m, &s); + g_string_append_printf(buf, "%.2" PRIu32 ":%.2" PRIu32 ":%.2" PRIu32, + h, m, s); - if (pcmk__is_set(flags, crm_time_usecs)) { - g_string_append_printf(buf, ".%06" PRIu32, QB_ABS(usec)); - } + if (pcmk__is_set(flags, pcmk__time_fmt_usecs)) { + g_string_append_printf(buf, ".%06" PRIu32, QB_ABS(usec)); } - if (pcmk__is_set(flags, crm_time_log_with_timezone) - && (dt->offset != 0)) { - - seconds_to_hms(dt->offset, &h, &m, &s); + if (pcmk__is_set(flags, pcmk__time_fmt_timezone) && (dt->offset != 0)) { + seconds_to_hms(dt->offset, &h, &m, NULL); g_string_append_printf(buf, " %c%.2" PRIu32 ":%.2" PRIu32, ((dt->offset < 0)? '-' : '+'), h, m); @@ -1038,22 +1146,23 @@ time_as_string_common(const crm_time_t *dt, int usec, uint32_t flags) } done: - crm_time_free(utc); + free(utc); result = pcmk__str_copy(buf->str); g_string_free(buf, TRUE); return result; } /*! - * \brief Get a string representation of a \p crm_time_t object + * \internal + * \brief Get a string representation of a \c crm_time_t object * - * \param[in] dt Time to convert to string - * \param[in] flags Group of \p crm_time_* string format options + * \param[in] dt Time to convert to string + * \param[in] flags Group of \c crm_time_* string format options * - * \note The caller is responsible for freeing the return value using \p free(). + * \note The caller is responsible for freeing the return value using \c free(). */ char * -crm_time_as_string(const crm_time_t *dt, int flags) +pcmk__time_text(const crm_time_t *dt, int flags) { return time_as_string_common(dt, 0, flags); } @@ -1063,25 +1172,25 @@ static int parse_int(const char *str, int *result) { unsigned int lpc; - int offset = (str[0] == 'T')? 1 : 0; + int offset = 0; bool negate = false; *result = 0; // @TODO This cannot handle combinations of these characters - switch (str[offset]) { + switch (str[0]) { case '.': case ',': return 0; // Fractions are not supported case '-': negate = true; - offset++; + offset = 1; break; case '+': case ':': - offset++; + offset = 1; break; default: @@ -1097,24 +1206,134 @@ parse_int(const char *str, int *result) *result = *result * 10 + digit; } if (negate) { - *result = 0 - *result; + *result = -*result; } return (lpc > 0)? offset : 0; } /*! + * \internal + * \brief Parse an element of an ISO 8601 duration string + * + * \param[in,out] element Element to parse (within \p duration_s) + * \param[in] duration_s Full duration string (for logging only) + * \param[in,out] duration Where to add result of parsing \p element + * \param[in] as_time If \c true, \c 'M' indicates minutes; otherwise, + * it indicates months + * + * \return Standard Pacemaker return code + * + * \note On successful return, \p element points to the unit designator of the + * element just parsed. This is a bit confusing but will suffice for now. + * \note \p as_time is set to \c true if the caller has encountered a \c 'T' + * already while parsing \p duration_s. + */ +static int +parse_duration_element(const char **element, const char *duration_s, + crm_time_t *duration, bool as_time) +{ + int value = 0; + int consumed = 0; + long long result = 0; + const char *start = *element; + + // Component must begin with an integer + consumed = parse_int(*element, &value); + if (consumed == 0) { + pcmk__err("'%s' is not a valid ISO 8601 duration because no valid " + "integer at '%s'", duration_s, *element); + return pcmk_rc_bad_input; + } + + *element += consumed; + + // A unit designator must be next (we're not strict about the order) + switch (**element) { + case 'Y': + duration->years = value; + return pcmk_rc_ok; + + case 'M': + if (!as_time) { // Months + duration->months = value; + return pcmk_rc_ok; + } + + // Minutes + result = duration->seconds + (value * 60LL); + if ((result < INT_MIN) || (result > INT_MAX)) { + break; + } + + duration->seconds = (int) result; + return pcmk_rc_ok; + + case 'W': + result = duration->days + (value * 7LL); + if ((result < INT_MIN) || (result > INT_MAX)) { + break; + } + + duration->days = (int) result; + return pcmk_rc_ok; + + case 'D': + result = duration->days + (long long) value; + if ((result < INT_MIN) || (result > INT_MAX)) { + break; + } + + duration->days = (int) result; + return pcmk_rc_ok; + + case 'H': + result = duration->seconds + ((long long) value * SECONDS_IN_HOUR); + if ((result < INT_MIN) || (result > INT_MAX)) { + break; + } + + duration->seconds = (int) result; + return pcmk_rc_ok; + + case 'S': + result = duration->seconds + (long long) value; + if ((result < INT_MIN) || (result > INT_MAX)) { + break; + } + + duration->seconds = (int) result; + return pcmk_rc_ok; + + case '\0': + pcmk__err("'%s' is not a valid ISO 8601 duration because no units " + "after %s", duration_s, start); + return pcmk_rc_bad_input; + + default: + pcmk__err("'%s' is not a valid ISO 8601 duration because '%c' is " + "not a valid time unit", duration_s, **element); + return pcmk_rc_bad_input; + } + + pcmk__err("'%s' could not be parsed as an ISO 8601 duration because the " + "the parsed value for one or more time units is too large", + duration_s); + return pcmk_rc_bad_input; +} + +/*! + * \internal * \brief Parse a time duration from an ISO 8601 duration specification * * \param[in] period_s ISO 8601 duration specification (optionally followed by * whitespace, after which the rest of the string will be * ignored) * - * \return New time object on success, NULL (and set errno) otherwise - * \note It is the caller's responsibility to return the result using - * crm_time_free(). + * \return New time object on success, or \cNULL (and set \c errno) otherwise + * \note It is the caller's responsibility to free the result using \c free(). */ crm_time_t * -crm_time_parse_duration(const char *period_s) +pcmk__time_parse_duration(const char *period_s) { bool is_time = false; crm_time_t *diff = NULL; @@ -1136,15 +1355,12 @@ crm_time_parse_duration(const char *period_s) goto invalid; } - diff = crm_time_new_undefined(); + diff = pcmk__assert_alloc(1, sizeof(crm_time_t)); for (const char *current = period_s + 1; current[0] && (current[0] != '/') && !isspace(current[0]); ++current) { - int an_int = 0, rc; - long long result = 0LL; - if (current[0] == 'T') { /* A 'T' separates year/month/day from hour/minute/seconds. We don't * require it strictly, but just use it to differentiate month from @@ -1154,266 +1370,73 @@ crm_time_parse_duration(const char *period_s) continue; } - // An integer must be next - rc = parse_int(current, &an_int); - if (rc == 0) { - pcmk__err("'%s' is not a valid ISO 8601 time duration because no " - "valid integer at '%s'", - period_s, current); + // current points to last character of current element on success + if (parse_duration_element(¤t, period_s, diff, + is_time) != pcmk_rc_ok) { goto invalid; } - current += rc; - - // A time unit must be next (we're not strict about the order) - switch (current[0]) { - case 'Y': - diff->years = an_int; - break; - - case 'M': - if (!is_time) { // Months - diff->months = an_int; - } else { // Minutes - result = diff->seconds + an_int * 60LL; - if ((result < INT_MIN) || (result > INT_MAX)) { - pcmk__err("'%s' is not a valid ISO 8601 time duration " - "because integer at '%s' is too %s", - period_s, (current - rc), - ((result > 0)? "large" : "small")); - goto invalid; - } else { - diff->seconds = (int) result; - } - } - - break; - - case 'W': - result = diff->days + an_int * 7LL; - if ((result < INT_MIN) || (result > INT_MAX)) { - pcmk__err("'%s' is not a valid ISO 8601 time duration " - "because integer at '%s' is too %s", - period_s, (current - rc), - ((result > 0)? "large" : "small")); - goto invalid; - } else { - diff->days = (int) result; - } - break; - - case 'D': - result = diff->days + (long long) an_int; - if ((result < INT_MIN) || (result > INT_MAX)) { - pcmk__err("'%s' is not a valid ISO 8601 time duration " - "because integer at '%s' is too %s", - period_s, (current - rc), - ((result > 0)? "large" : "small")); - goto invalid; - } else { - diff->days = (int) result; - } - break; - - case 'H': - result = diff->seconds + ((long long) an_int * SECONDS_IN_HOUR); - if ((result < INT_MIN) || (result > INT_MAX)) { - pcmk__err("'%s' is not a valid ISO 8601 time duration " - "because integer at '%s' is too %s", - period_s, (current - rc), - ((result > 0)? "large" : "small")); - goto invalid; - } else { - diff->seconds = (int) result; - } - break; - - case 'S': - result = diff->seconds + (long long) an_int; - if ((result < INT_MIN) || (result > INT_MAX)) { - pcmk__err("'%s' is not a valid ISO 8601 time duration " - "because integer at '%s' is too %s", - period_s, (current - rc), - ((result > 0)? "large" : "small")); - goto invalid; - } else { - diff->seconds = (int) result; - } - break; - - case '\0': - pcmk__err("'%s' is not a valid ISO 8601 time duration because " - "no units after %d", - period_s, an_int); - goto invalid; - - default: - pcmk__err("'%s' is not a valid ISO 8601 time duration because " - "'%c' is not a valid time unit", - period_s, current[0]); - goto invalid; - } } - if (!crm_time_is_defined(diff)) { + if (!pcmk__time_is_initialized(diff)) { pcmk__err("'%s' is not a valid ISO 8601 time duration because no " "amounts and units given", period_s); goto invalid; } - diff->duration = TRUE; + diff->duration = true; return diff; invalid: - crm_time_free(diff); + /* @COMPAT Setting errno is required only for backward compatibility with + * crm_time_parse_duration() + */ + free(diff); errno = EINVAL; return NULL; } /*! - * \brief Parse a time period from an ISO 8601 interval specification - * - * \param[in] period_str ISO 8601 interval specification (start/end, - * start/duration, or duration/end) + * \internal + * \brief Set one time object to another if the other is earlier * - * \return New time period object on success, NULL (and set errno) otherwise - * \note The caller is responsible for freeing the result using - * crm_time_free_period(). + * \param[in,out] target Time object to set + * \param[in] source Time object to use if earlier */ -crm_time_period_t * -crm_time_parse_period(const char *period_str) +void +pcmk__set_time_if_earlier(crm_time_t *target, const crm_time_t *source) { - const char *original = period_str; - crm_time_period_t *period = NULL; + const int flags = pcmk__time_fmt_date + |pcmk__time_fmt_time + |pcmk__time_fmt_timezone; - if (pcmk__str_empty(period_str)) { - pcmk__err("No ISO 8601 time period given"); - goto invalid; + if ((target == NULL) + || (source == NULL) + || (pcmk__time_is_initialized(target) + && (pcmk__time_compare(source, target) >= 0))) { + + return; } - tzset(); - period = pcmk__assert_alloc(1, sizeof(crm_time_period_t)); + *target = *source; + pcmk__time_log(LOG_TRACE, "source", source, flags); + pcmk__time_log(LOG_TRACE, "target", target, flags); +} - if (period_str[0] == 'P') { - period->diff = crm_time_parse_duration(period_str); - if (period->diff == NULL) { - goto invalid; - } - } else { - period->start = parse_date(period_str); - if (period->start == NULL) { - goto invalid; - } - } +void +pcmk__time_add_years(crm_time_t *dt, int value) +{ + pcmk__assert(dt != NULL); - period_str = strchr(original, '/'); - if (period_str != NULL) { - ++period_str; - if (period_str[0] == 'P') { - if (period->diff != NULL) { - pcmk__err("'%s' is not a valid ISO 8601 time period because it " - "has two durations", - original); - goto invalid; - } - period->diff = crm_time_parse_duration(period_str); - if (period->diff == NULL) { - goto invalid; - } - } else { - period->end = parse_date(period_str); - if (period->end == NULL) { - goto invalid; - } - } + if ((value > 0) && ((dt->years + (long long) value) > INT_MAX)) { + dt->years = INT_MAX; - } else if (period->diff != NULL) { - // Only duration given, assume start is now - period->start = pcmk__copy_timet(time(NULL)); + } else if ((value < 0) && ((dt->years + (long long) value) < 1)) { + dt->years = 1; // Clip to earliest we can handle (no BCE) } else { - // Only start given - pcmk__err("'%s' is not a valid ISO 8601 time period because it has no " - "duration or ending time", - original); - goto invalid; - } - - if (period->start == NULL) { - period->start = crm_time_subtract(period->end, period->diff); - - } else if (period->end == NULL) { - period->end = crm_time_add(period->start, period->diff); + dt->years += value; } - - if (!valid_time(period->start)) { - pcmk__err("'%s' is not a valid ISO 8601 time period because the start " - "is invalid", period_str); - goto invalid; - } - if (!valid_time(period->end)) { - pcmk__err("'%s' is not a valid ISO 8601 time period because the end is " - "invalid", period_str); - goto invalid; - } - return period; - -invalid: - errno = EINVAL; - crm_time_free_period(period); - return NULL; -} - -/*! - * \brief Free a dynamically allocated time period object - * - * \param[in,out] period Time period to free - */ -void -crm_time_free_period(crm_time_period_t *period) -{ - if (period) { - crm_time_free(period->start); - crm_time_free(period->end); - crm_time_free(period->diff); - free(period); - } -} - -/*! - * \internal - * \brief Set one time object to another if the other is earlier - * - * \param[in,out] target Time object to set - * \param[in] source Time object to use if earlier - */ -void -pcmk__set_time_if_earlier(crm_time_t *target, const crm_time_t *source) -{ - const int flags = crm_time_log_date - |crm_time_log_timeofday - |crm_time_log_with_timezone; - - if ((target == NULL) - || (source == NULL) - || (crm_time_is_defined(target) - && (crm_time_compare(source, target) >= 0))) { - - return; - } - - *target = *source; - pcmk__time_log(LOG_TRACE, "source", source, flags); - pcmk__time_log(LOG_TRACE, "target", target, flags); -} - -crm_time_t * -pcmk_copy_time(const crm_time_t *source) -{ - crm_time_t *target = crm_time_new_undefined(); - - *target = *source; - return target; } /*! @@ -1424,14 +1447,13 @@ pcmk_copy_time(const crm_time_t *source) * * \return Newly allocated \c crm_time_t object representing \p source_sec * - * \note The caller is responsible for freeing the return value using - * \c crm_time_free(). + * \note The caller is responsible for freeing the return value using \c free(). */ crm_time_t * pcmk__copy_timet(time_t source_sec) { const struct tm *source = localtime(&source_sec); - crm_time_t *target = crm_time_new_undefined(); + crm_time_t *target = pcmk__assert_alloc(1, sizeof(crm_time_t)); int h_offset = 0; int m_offset = 0; @@ -1439,7 +1461,7 @@ pcmk__copy_timet(time_t source_sec) if (source->tm_year > 0) { // Years since 1900 target->years = 1900; - crm_time_add_years(target, source->tm_year); + pcmk__time_add_years(target, source->tm_year); } if (source->tm_yday >= 0) { @@ -1472,26 +1494,62 @@ pcmk__copy_timet(time_t source_sec) return target; } +static void +add_months(crm_time_t *dt, int value) +{ + uint32_t year = 0; + uint32_t month = 0; + uint32_t day = 0; + int days_in_month = 0; + + pcmk__time_get_ymd(dt, &year, &month, &day); + + if (value > 0) { + for (int i = value; i > 0; i--) { + month++; + if (month == 13) { + month = 1; + year++; + } + } + } else { + for (int i = value; i < 0; i++) { + month--; + if (month == 0) { + month = 12; + year--; + } + } + } + + days_in_month = days_in_month_year(month, year); + + if (days_in_month < day) { + // Preserve day-of-month unless the month doesn't have enough days + day = days_in_month; + } + + dt->years = year; + dt->days = get_ordinal_days(year, month, day); +} + crm_time_t * -crm_time_add(const crm_time_t *dt, const crm_time_t *value) +pcmk__time_add(const crm_time_t *dt, const crm_time_t *value) { crm_time_t *utc = NULL; crm_time_t *answer = NULL; - if ((dt == NULL) || (value == NULL)) { - errno = EINVAL; - return NULL; - } + pcmk__assert((dt != NULL) && (value != NULL)); - answer = pcmk_copy_time(dt); + answer = pcmk__time_copy(dt); utc = copy_time_to_utc(value); - crm_time_add_years(answer, utc->years); - crm_time_add_months(answer, utc->months); - crm_time_add_days(answer, utc->days); - crm_time_add_seconds(answer, utc->seconds); + pcmk__time_add_years(answer, utc->years); + add_months(answer, utc->months); + pcmk__time_add_days(answer, utc->days); + pcmk__time_add_seconds(answer, utc->seconds); - crm_time_free(utc); + free(utc); return answer; } @@ -1534,6 +1592,24 @@ pcmk__time_component_attr(enum pcmk__time_component component) } } +static void +add_weeks(crm_time_t *dt, int value) +{ + pcmk__time_add_days(dt, value * 7); +} + +static void +add_hours(crm_time_t *dt, int value) +{ + pcmk__time_add_seconds(dt, value * SECONDS_IN_HOUR); +} + +static void +add_minutes(crm_time_t *dt, int value) +{ + pcmk__time_add_seconds(dt, value * SECONDS_IN_MINUTE); +} + typedef void (*component_fn_t)(crm_time_t *, int); /*! @@ -1549,30 +1625,29 @@ component_fn(enum pcmk__time_component component) { switch (component) { case pcmk__time_years: - return crm_time_add_years; + return pcmk__time_add_years; case pcmk__time_months: - return crm_time_add_months; + return add_months; case pcmk__time_weeks: - return crm_time_add_weeks; + return add_weeks; case pcmk__time_days: - return crm_time_add_days; + return pcmk__time_add_days; case pcmk__time_hours: - return crm_time_add_hours; + return add_hours; case pcmk__time_minutes: - return crm_time_add_minutes; + return add_minutes; case pcmk__time_seconds: - return crm_time_add_seconds; + return pcmk__time_add_seconds; default: return NULL; } - } /*! @@ -1621,12 +1696,9 @@ subtract_time(const crm_time_t *dt1, const crm_time_t *dt2, bool as_duration) crm_time_t *result = NULL; crm_time_t *utc = NULL; - if ((dt1 == NULL) || (dt2 == NULL)) { - errno = EINVAL; - return NULL; - } + pcmk__assert((dt1 != NULL) && (dt2 != NULL)); - result = (as_duration? copy_time_to_utc(dt1) : pcmk_copy_time(dt1)); + result = (as_duration? copy_time_to_utc(dt1) : pcmk__time_copy(dt1)); result->duration = as_duration; utc = copy_time_to_utc(dt2); @@ -1634,41 +1706,35 @@ subtract_time(const crm_time_t *dt1, const crm_time_t *dt2, bool as_duration) // Avoid overflow when negating INT_MIN in calculations below if (utc->years == INT_MIN) { - crm_time_add_years(result, -1); + pcmk__time_add_years(result, -1); utc->years++; } - crm_time_add_years(result, -utc->years); + pcmk__time_add_years(result, -utc->years); if (utc->months == INT_MIN) { - crm_time_add_months(result, -1); + add_months(result, -1); utc->months++; } - crm_time_add_months(result, -utc->months); + add_months(result, -utc->months); if (utc->days == INT_MIN) { - crm_time_add_days(result, -1); + pcmk__time_add_days(result, -1); utc->days++; } - crm_time_add_days(result, -utc->days); + pcmk__time_add_days(result, -utc->days); if (utc->seconds == INT_MIN) { - crm_time_add_seconds(result, -1); + pcmk__time_add_seconds(result, -1); utc->seconds++; } - crm_time_add_seconds(result, -utc->seconds); + pcmk__time_add_seconds(result, -utc->seconds); - crm_time_free(utc); + free(utc); return result; } crm_time_t * -crm_time_calculate_duration(const crm_time_t *dt, const crm_time_t *value) -{ - return subtract_time(dt, value, true); -} - -crm_time_t * -crm_time_subtract(const crm_time_t *dt, const crm_time_t *value) +pcmk__time_subtract(const crm_time_t *dt, const crm_time_t *value) { return subtract_time(dt, value, false); } @@ -1687,7 +1753,7 @@ crm_time_subtract(const crm_time_t *dt, const crm_time_t *value) } int -crm_time_compare(const crm_time_t *a, const crm_time_t *b) +pcmk__time_compare(const crm_time_t *a, const crm_time_t *b) { int rc = 0; crm_time_t *t1 = NULL; @@ -1710,156 +1776,11 @@ crm_time_compare(const crm_time_t *a, const crm_time_t *b) do_cmp_field(t1, t2, days); do_cmp_field(t1, t2, seconds); - crm_time_free(t1); - crm_time_free(t2); + free(t1); + free(t2); return rc; } -/*! - * \brief Add a given number of seconds to a date/time or duration - * - * \param[in,out] a_time Date/time or duration to add seconds to - * \param[in] extra Number of seconds to add - */ -void -crm_time_add_seconds(crm_time_t *a_time, int extra) -{ - int days = extra / SECONDS_IN_DAY; - - pcmk__assert(a_time != NULL); - - pcmk__trace("Adding %d seconds (including %d whole day%s) to %d", extra, - days, pcmk__plural_s(days), a_time->seconds); - - a_time->seconds += extra % SECONDS_IN_DAY; - - // Check whether the addition crossed a day boundary - if (a_time->seconds > SECONDS_IN_DAY) { - ++days; - a_time->seconds -= SECONDS_IN_DAY; - - } else if (a_time->seconds < 0) { - --days; - a_time->seconds += SECONDS_IN_DAY; - } - - crm_time_add_days(a_time, days); -} - -/*! - * \brief Add days to a date/time - * - * \param[in,out] a_time Time to modify - * \param[in] extra Number of days to add (may be negative to subtract) - */ -void -crm_time_add_days(crm_time_t *a_time, int extra) -{ - pcmk__assert(a_time != NULL); - - pcmk__trace("Adding %d days to %.4d-%.3d", extra, a_time->years, - a_time->days); - - if (extra > 0) { - while ((a_time->days + (long long) extra) > year_days(a_time->years)) { - if (a_time->years == INT_MAX) { - // Clip to latest we can handle - a_time->days = year_days(a_time->years); - return; - } - extra -= year_days(a_time->years); - a_time->years++; - } - } else if (extra < 0) { - const int min_days = a_time->duration? 0 : 1; - - while ((a_time->days + (long long) extra) < min_days) { - if (a_time->years <= 1) { - a_time->days = 1; // Clip to earliest we can handle (no BCE) - return; - } - a_time->years--; - extra += year_days(a_time->years); - } - } - a_time->days += extra; -} - -void -crm_time_add_months(crm_time_t * a_time, int extra) -{ - int lpc; - uint32_t y, m, d, dmax; - - crm_time_get_gregorian(a_time, &y, &m, &d); - pcmk__trace("Adding %d months to %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32, - extra, y, m, d); - - if (extra > 0) { - for (lpc = extra; lpc > 0; lpc--) { - m++; - if (m == 13) { - m = 1; - y++; - } - } - } else { - for (lpc = extra; lpc < 0; lpc++) { - m--; - if (m == 0) { - m = 12; - y--; - } - } - } - - dmax = days_in_month_year(m, y); - if (dmax < d) { - /* Preserve day-of-month unless the month doesn't have enough days */ - d = dmax; - } - - pcmk__trace("Calculated %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32, y, m, d); - - a_time->years = y; - a_time->days = get_ordinal_days(y, m, d); - - crm_time_get_gregorian(a_time, &y, &m, &d); - pcmk__trace("Got %.4" PRIu32 "-%.2" PRIu32 "-%.2" PRIu32, y, m, d); -} - -void -crm_time_add_minutes(crm_time_t * a_time, int extra) -{ - crm_time_add_seconds(a_time, extra * SECONDS_IN_MINUTE); -} - -void -crm_time_add_hours(crm_time_t * a_time, int extra) -{ - crm_time_add_seconds(a_time, extra * SECONDS_IN_HOUR); -} - -void -crm_time_add_weeks(crm_time_t * a_time, int extra) -{ - crm_time_add_days(a_time, extra * 7); -} - -void -crm_time_add_years(crm_time_t * a_time, int extra) -{ - pcmk__assert(a_time != NULL); - - if ((extra > 0) && ((a_time->years + (long long) extra) > INT_MAX)) { - a_time->years = INT_MAX; - } else if ((extra < 0) && ((a_time->years + (long long) extra) < 1)) { - a_time->years = 1; // Clip to earliest we can handle (no BCE) - } else { - a_time->years += extra; - } -} - static void ha_get_tm_time(struct tm *target, const crm_time_t *source) { @@ -1884,6 +1805,21 @@ ha_get_tm_time(struct tm *target, const crm_time_t *source) mktime(target); } +static char * +offset_text(int offset) +{ + uint32_t hours = 0; + uint32_t minutes = 0; + + // If offset is out of range, default to NULL + CRM_CHECK(QB_ABS(offset) <= SECONDS_IN_DAY, return NULL); + + seconds_to_hms(offset, &hours, &minutes, NULL); + + return pcmk__assert_asprintf("%c%02" PRIu32 ":%02" PRIu32, + ((offset >= 0)? '+' : '-'), hours, minutes); +} + /*! * \internal * \brief Convert a struct tm to a \c GDateTime @@ -1901,44 +1837,25 @@ static GDateTime * get_g_date_time(const struct tm *tm, int offset) { // Accept an offset argument in case tm lacks a tm_gmtoff member - char buf[sizeof("+hh:mm")] = { '\0', }; - const char *offset_s = NULL; - + char *offset_s = offset_text(offset); GTimeZone *tz = NULL; GDateTime *dt = NULL; - if (QB_ABS(offset) <= SECONDS_IN_DAY) { - uint32_t hours = 0; - uint32_t minutes = 0; - uint32_t seconds = 0; - int rc = 0; - - seconds_to_hms(offset, &hours, &minutes, &seconds); - - rc = snprintf(buf, sizeof(buf), "%c%02" PRIu32 ":%02" PRIu32, - ((offset >= 0)? '+' : '-'), hours, minutes); - pcmk__assert(rc == (sizeof(buf) - 1)); - offset_s = buf; - - } else { - // offset out of range; use NULL as offset_s - CRM_LOG_ASSERT(QB_ABS(offset) <= SECONDS_IN_DAY); - } - - /* @FIXME @COMPAT As of glib 2.68, g_time_zone_new() is deprecated in favor - * of g_time_zone_new_identifier(). However, calling - * g_time_zone_new_identifier() results in compiler warnings, even on a - * system with glib 2.84 installed. It is unclear why. - * - * The *_new_identifier() function was added (and the *_new() function - * deprecated) in version 2.68. They have the same signature. Ideally, we - * would choose which function to call here and below based the installed - * glib version using a CPP guard. - */ + // @COMPAT Starting in GLib 2.58, we can use g_time_zone_new_offset() tz = g_time_zone_new(offset_s); + if (tz == NULL) { + goto done; + } + dt = g_date_time_new(tz, tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec); - g_time_zone_unref(tz); + +done: + free(offset_s); + + if (tz != NULL) { + g_time_zone_unref(tz); + } return dt; } @@ -2143,9 +2060,9 @@ pcmk__epoch2str(const time_t *source, uint32_t flags) } dt = pcmk__copy_timet(epoch_time); - result = crm_time_as_string(dt, flags); + result = pcmk__time_text(dt, flags); - crm_time_free(dt); + free(dt); return result; } @@ -2154,8 +2071,8 @@ pcmk__epoch2str(const time_t *source, uint32_t flags) * \brief Return a human-friendly string corresponding to seconds-and- * nanoseconds value * - * Time is shown with microsecond resolution if \p crm_time_usecs is in \p - * flags. + * Time is shown with microsecond resolution if \c pcmk__time_fmt_usecs is in + * \p flags. * * \param[in] ts Time in seconds and nanoseconds (or \p NULL for current * time) @@ -2181,7 +2098,7 @@ pcmk__timespec2str(const struct timespec *ts, uint32_t flags) dt = pcmk__copy_timet(ts->tv_sec); result = time_as_string_common(dt, ts->tv_nsec / QB_TIME_NS_IN_USEC, flags); - crm_time_free(dt); + free(dt); return result; } @@ -2266,9 +2183,7 @@ crm_time_days_in_month(int month, int year) int crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m) { - uint32_t s; - - seconds_to_hms(dt->seconds, h, m, &s); + seconds_to_hms(dt->seconds, h, m, NULL); return TRUE; } @@ -2287,9 +2202,9 @@ crm_time_january1_weekday(int year) void crm_time_set(crm_time_t *target, const crm_time_t *source) { - const uint32_t flags = crm_time_log_date - |crm_time_log_timeofday - |crm_time_log_with_timezone; + const uint32_t flags = pcmk__time_fmt_date + |pcmk__time_fmt_time + |pcmk__time_fmt_timezone; pcmk__trace("target=%p, source=%p", target, source); @@ -2322,13 +2237,15 @@ crm_time_set_timet(crm_time_t *target, const time_t *source_sec) source = pcmk__copy_timet(*source_sec); *target = *source; - crm_time_free(source); + free(source); } int crm_time_get_isoweek(const crm_time_t *dt, uint32_t *y, uint32_t *w, uint32_t *d) { + pcmk__assert((dt != NULL) && (y != NULL) && (w != NULL) && (d != NULL)); + CRM_CHECK(dt->days > 0, return FALSE); pcmk__time_get_ywd(dt, y, w, d); return TRUE; @@ -2343,5 +2260,263 @@ crm_time_log_alias(int log_level, const char *file, const char *function, prefix, date_time, flags); } +void +crm_time_free_period(crm_time_period_t *period) +{ + if (period) { + free(period->start); + free(period->end); + free(period->diff); + free(period); + } +} + +crm_time_period_t * +crm_time_parse_period(const char *period_str) +{ + const char *original = period_str; + crm_time_period_t *period = NULL; + + if (pcmk__str_empty(period_str)) { + pcmk__err("No ISO 8601 time period given"); + goto invalid; + } + + tzset(); + period = pcmk__assert_alloc(1, sizeof(crm_time_period_t)); + + if (period_str[0] == 'P') { + period->diff = pcmk__time_parse_duration(period_str); + if (period->diff == NULL) { + goto invalid; + } + } else { + period->start = parse_date(period_str); + if (period->start == NULL) { + goto invalid; + } + } + + period_str = strchr(original, '/'); + if (period_str != NULL) { + ++period_str; + if (period_str[0] == 'P') { + if (period->diff != NULL) { + pcmk__err("'%s' is not a valid ISO 8601 time period because it " + "has two durations", + original); + goto invalid; + } + period->diff = pcmk__time_parse_duration(period_str); + if (period->diff == NULL) { + goto invalid; + } + } else { + period->end = parse_date(period_str); + if (period->end == NULL) { + goto invalid; + } + } + + } else if (period->diff != NULL) { + // Only duration given, assume start is now + period->start = pcmk__copy_timet(time(NULL)); + + } else { + // Only start given + pcmk__err("'%s' is not a valid ISO 8601 time period because it has no " + "duration or ending time", + original); + goto invalid; + } + + if (period->start == NULL) { + period->start = pcmk__time_subtract(period->end, period->diff); + + } else if (period->end == NULL) { + period->end = pcmk__time_add(period->start, period->diff); + } + + if (!pcmk__time_valid_year(period->start->years) + || !valid_time(period->start)) { + + pcmk__err("'%s' is not a valid ISO 8601 time period because the start " + "is invalid (must be between " BEGIN_VALID_RANGE_S " and " + END_VALID_RANGE_S ")", period_str); + goto invalid; + } + + if (!pcmk__time_valid_year(period->end->years) + || !valid_time(period->end)) { + + pcmk__err("'%s' is not a valid ISO 8601 time period because the end is " + "invalid (must be between " BEGIN_VALID_RANGE_S " and " + END_VALID_RANGE_S ")", period_str); + goto invalid; + } + + return period; + +invalid: + errno = EINVAL; + crm_time_free_period(period); + return NULL; +} + +crm_time_t * +crm_time_calculate_duration(const crm_time_t *dt, const crm_time_t *value) +{ + if ((dt == NULL) || (value == NULL)) { + errno = EINVAL; + return NULL; + } + + return subtract_time(dt, value, true); +} + +crm_time_t * +crm_time_parse_duration(const char *period_s) +{ + return pcmk__time_parse_duration(period_s); +} + +crm_time_t * +crm_time_new_undefined(void) +{ + return pcmk__assert_alloc(1, sizeof(crm_time_t)); +} + +bool +crm_time_is_defined(const crm_time_t *t) +{ + return pcmk__time_is_initialized(t); +} + +char * +crm_time_as_string(const crm_time_t *dt, int flags) +{ + return pcmk__time_text(dt, flags); +} + +int +crm_time_compare(const crm_time_t *a, const crm_time_t *b) +{ + return pcmk__time_compare(a, b); +} + +int +crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m, + uint32_t *s) +{ + pcmk__time_get_timeofday(dt, h, m, s); + return TRUE; +} + +int +crm_time_get_gregorian(const crm_time_t *dt, uint32_t *y, uint32_t *m, + uint32_t *d) +{ + pcmk__time_get_ymd(dt, y, m, d); + return TRUE; +} + +int +crm_time_get_ordinal(const crm_time_t *dt, uint32_t *y, uint32_t *d) +{ + pcmk__assert((dt != NULL) && (y != NULL) && (d != NULL)); + + *y = dt->years; + *d = dt->days; + return TRUE; +} + +long long +crm_time_get_seconds(const crm_time_t *dt) +{ + return pcmk__time_get_seconds(dt); +} + +long long +crm_time_get_seconds_since_epoch(const crm_time_t *dt) +{ + return pcmk__time_to_unix(dt); +} + +void +crm_time_free(crm_time_t *dt) +{ + free(dt); +} + +crm_time_t * +pcmk_copy_time(const crm_time_t *source) +{ + return pcmk__time_copy(source); +} + +crm_time_t * +crm_time_add(const crm_time_t *dt, const crm_time_t *value) +{ + if ((dt == NULL) || (value == NULL)) { + errno = EINVAL; + return NULL; + } + + return pcmk__time_add(dt, value); +} + +crm_time_t * +crm_time_subtract(const crm_time_t *dt, const crm_time_t *value) +{ + if ((dt == NULL) || (value == NULL)) { + errno = EINVAL; + return NULL; + } + + return pcmk__time_subtract(dt, value); +} + +void +crm_time_add_seconds(crm_time_t *dt, int value) +{ + pcmk__time_add_seconds(dt, value); +} + +void +crm_time_add_minutes(crm_time_t *dt, int value) +{ + add_minutes(dt, value); +} + +void +crm_time_add_hours(crm_time_t *dt, int value) +{ + add_hours(dt, value); +} + +void +crm_time_add_days(crm_time_t *dt, int value) +{ + pcmk__time_add_days(dt, value); +} + +void +crm_time_add_weeks(crm_time_t *dt, int value) +{ + add_weeks(dt, value); +} + +void +crm_time_add_months(crm_time_t *dt, int value) +{ + add_months(dt, value); +} + +void +crm_time_add_years(crm_time_t *dt, int value) +{ + pcmk__time_add_years(dt, value); +} + // LCOV_EXCL_STOP // End deprecated API diff --git a/lib/common/nvpair.c b/lib/common/nvpair.c index b1510b93e12..dd3a0479a82 100644 --- a/lib/common/nvpair.c +++ b/lib/common/nvpair.c @@ -456,8 +456,8 @@ pcmk__unpack_nvpair_block(gpointer data, gpointer user_data) rule_xml = pcmk__xe_first_child(pair, PCMK_XE_RULE, NULL, NULL); if ((rule_xml != NULL) - && (pcmk_evaluate_rule(rule_xml, &(unpack_data->rule_input), - unpack_data->next_change) != pcmk_rc_ok)) { + && (pcmk__evaluate_rule(rule_xml, &unpack_data->rule_input, + unpack_data->next_change) != pcmk_rc_ok)) { return; } @@ -487,7 +487,7 @@ pcmk__unpack_nvpair_block(gpointer data, gpointer user_data) void pcmk__unpack_nvpair_blocks(const xmlNode *xml, const char *element_name, const char *first_id, - const pcmk_rule_input_t *rule_input, + const pcmk__rule_input_t *rule_input, GHashTable *values, crm_time_t *next_change, xmlDoc *doc) { @@ -665,6 +665,7 @@ pcmk__cmp_nvpair_blocks(gconstpointer a, gconstpointer b, gpointer user_data) // LCOV_EXCL_START #include +#include // pcmk_rule_input_t static gint pcmk__compare_nvpair(gconstpointer a, gconstpointer b) @@ -738,11 +739,17 @@ pcmk_unpack_nvpair_blocks(const xmlNode *xml, const char *element_name, const pcmk_rule_input_t *rule_input, GHashTable *values, crm_time_t *next_change) { + pcmk__rule_input_t new_input = { NULL, }; + if (xml == NULL) { return; } - pcmk__unpack_nvpair_blocks(xml, element_name, first_id, rule_input, values, + if (rule_input != NULL) { + pcmk__rule_input_convert(rule_input, &new_input); + } + + pcmk__unpack_nvpair_blocks(xml, element_name, first_id, &new_input, values, next_change, xml->doc); } diff --git a/lib/common/rules.c b/lib/common/rules.c index 9a7bff73ae5..afb5074cc4f 100644 --- a/lib/common/rules.c +++ b/lib/common/rules.c @@ -184,15 +184,15 @@ pcmk__evaluate_date_spec(const xmlNode *date_spec, const crm_time_t *now) } // Year, month, day - crm_time_get_gregorian(now, &(ranges[0].value), &(ranges[1].value), - &(ranges[2].value)); + pcmk__time_get_ymd(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)); + pcmk__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)); + // Day of year + ranges[6].value = now->days; // Week year, week of week year, day of week pcmk__time_get_ywd(now, &(ranges[7].value), &(ranges[8].value), @@ -220,7 +220,7 @@ pcmk__evaluate_date_spec(const xmlNode *date_spec, const crm_time_t *now) parent_id, id, \ pcmk__time_component_attr(component), \ pcmk_rc_str(rc)); \ - g_clear_pointer(end, crm_time_free); \ + g_clear_pointer(end, free); \ return rc; \ } \ } while (0) @@ -235,7 +235,7 @@ pcmk__evaluate_date_spec(const xmlNode *date_spec, const crm_time_t *now) * initially) * * \return Standard Pacemaker return code - * \note The caller is responsible for freeing \p *end using crm_time_free(). + * \note The caller is responsible for freeing \p *end using \c free(). */ int pcmk__unpack_duration(const xmlNode *duration, const crm_time_t *start, @@ -258,7 +258,7 @@ pcmk__unpack_duration(const xmlNode *duration, const crm_time_t *start, return pcmk_rc_unpack_error; } - *end = pcmk_copy_time(start); + *end = pcmk__time_copy(start); ADD_COMPONENT(pcmk__time_years); ADD_COMPONENT(pcmk__time_months); @@ -302,7 +302,7 @@ evaluate_in_range(const xmlNode *date_expression, const char *id, &end) != pcmk_rc_ok) { pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not " "passing because " PCMK_XA_END " is invalid", id); - crm_time_free(start); + free(start); return pcmk_rc_unpack_error; } @@ -326,35 +326,35 @@ evaluate_in_range(const xmlNode *date_expression, const char *id, pcmk__config_err("Treating " PCMK_XE_DATE_EXPRESSION " %s as not passing because duration " "is invalid", id); - crm_time_free(start); + free(start); return rc; } } } - if ((start != NULL) && (crm_time_compare(now, start) < 0)) { + if ((start != NULL) && (pcmk__time_compare(now, start) < 0)) { pcmk__set_time_if_earlier(next_change, start); - crm_time_free(start); - crm_time_free(end); + free(start); + 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); + if (pcmk__time_compare(now, end) > 0) { + free(start); + 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__time_add_seconds(end, 1); pcmk__set_time_if_earlier(next_change, end); } } - crm_time_free(start); - crm_time_free(end); + free(start); + free(end); return pcmk_rc_within_range; } @@ -392,15 +392,15 @@ evaluate_gt(const xmlNode *date_expression, const char *id, return pcmk_rc_unpack_error; } - if (crm_time_compare(now, start) > 0) { - crm_time_free(start); + if (pcmk__time_compare(now, start) > 0) { + free(start); return pcmk_rc_within_range; } // Evaluation doesn't change until second after start time - crm_time_add_seconds(start, 1); + pcmk__time_add_seconds(start, 1); pcmk__set_time_if_earlier(next_change, start); - crm_time_free(start); + free(start); return pcmk_rc_before_range; } @@ -437,13 +437,13 @@ evaluate_lt(const xmlNode *date_expression, const char *id, return pcmk_rc_unpack_error; } - if (crm_time_compare(now, end) < 0) { + if (pcmk__time_compare(now, end) < 0) { pcmk__set_time_if_earlier(next_change, end); - crm_time_free(end); + free(end); return pcmk_rc_within_range; } - crm_time_free(end); + free(end); return pcmk_rc_after_range; } @@ -915,7 +915,7 @@ evaluate_attr_comparison(const char *actual, const char *reference, */ static const char * value_from_source(const char *value, enum pcmk__reference_source source, - const pcmk_rule_input_t *rule_input) + const pcmk__rule_input_t *rule_input) { GHashTable *table = NULL; @@ -953,7 +953,7 @@ value_from_source(const char *value, enum pcmk__reference_source source, */ int pcmk__evaluate_attr_expression(const xmlNode *expression, - const pcmk_rule_input_t *rule_input) + const pcmk__rule_input_t *rule_input) { const char *id = NULL; const char *op = NULL; @@ -1107,7 +1107,7 @@ pcmk__evaluate_attr_expression(const xmlNode *expression, */ int pcmk__evaluate_rsc_expression(const xmlNode *rsc_expression, - const pcmk_rule_input_t *rule_input) + const pcmk__rule_input_t *rule_input) { const char *id = NULL; const char *standard = NULL; @@ -1176,7 +1176,7 @@ pcmk__evaluate_rsc_expression(const xmlNode *rsc_expression, */ int pcmk__evaluate_op_expression(const xmlNode *op_expression, - const pcmk_rule_input_t *rule_input) + const pcmk__rule_input_t *rule_input) { const char *id = NULL; const char *name = NULL; @@ -1249,17 +1249,16 @@ pcmk__evaluate_op_expression(const xmlNode *op_expression, */ int pcmk__evaluate_condition(xmlNode *condition, - const pcmk_rule_input_t *rule_input, + const pcmk__rule_input_t *rule_input, crm_time_t *next_change) { - if ((condition == NULL) || (rule_input == NULL)) { return EINVAL; } switch (pcmk__condition_type(condition)) { case pcmk__condition_rule: - return pcmk_evaluate_rule(condition, rule_input, next_change); + return pcmk__evaluate_rule(condition, rule_input, next_change); case pcmk__condition_attribute: case pcmk__condition_location: @@ -1284,12 +1283,13 @@ pcmk__evaluate_condition(xmlNode *condition, pcmk__config_err("Treating rule condition %s as not passing " "because %s is not a valid condition type", pcmk__s(pcmk__xe_id(condition), "without ID"), - (const char *) condition->name); + condition->name); return pcmk_rc_unpack_error; } } /*! + * \internal * \brief Evaluate a single rule, including all its conditions * * \param[in,out] rule XML containing a rule definition or its id-ref @@ -1300,8 +1300,8 @@ pcmk__evaluate_condition(xmlNode *condition, * satisfied, some other value if it is not) */ int -pcmk_evaluate_rule(xmlNode *rule, const pcmk_rule_input_t *rule_input, - crm_time_t *next_change) +pcmk__evaluate_rule(xmlNode *rule, const pcmk__rule_input_t *rule_input, + crm_time_t *next_change) { bool empty = true; int rc = pcmk_rc_ok; @@ -1373,3 +1373,49 @@ pcmk_evaluate_rule(xmlNode *rule, const pcmk_rule_input_t *rule_input, ((rc == pcmk_rc_ok)? "" : "not ")); return rc; } + +// Deprecated functions kept only for backward API compatibility +// LCOV_EXCL_START + +#include // pcmk_rule_input_t + +void +pcmk__rule_input_convert(const pcmk_rule_input_t *source, + pcmk__rule_input_t *target) +{ + /* @COMPAT Drop this function when pcmk_rule_input_t is dropped. It exists + * purely for as a helper for deprecated API. + */ + pcmk__assert((source != NULL) && (target != NULL)); + + // Non-numeric fields are const and are shared between source and target + target->now = source->now; + target->rsc_standard = source->rsc_standard; + target->rsc_provider = source->rsc_provider; + target->rsc_agent = source->rsc_agent; + target->op_name = source->op_name; + target->op_interval_ms = source->op_interval_ms; + target->node_attrs = source->node_attrs; + target->rsc_params = source->rsc_params; + target->rsc_meta = source->rsc_meta; + target->rsc_id = source->rsc_id; + target->rsc_id_submatches = source->rsc_id_submatches; + target->rsc_id_nmatches = source->rsc_id_nmatches; +} + +int +pcmk_evaluate_rule(xmlNode *rule, const pcmk_rule_input_t *rule_input, + crm_time_t *next_change) +{ + pcmk__rule_input_t new_input = { NULL, }; + + if (rule_input == NULL) { + return EINVAL; + } + + pcmk__rule_input_convert(rule_input, &new_input); + return pcmk__evaluate_rule(rule, &new_input, next_change); +} + +// LCOV_EXCL_STOP +// End deprecated API diff --git a/lib/common/scheduler.c b/lib/common/scheduler.c index 44d14df455e..ce798d4ef2f 100644 --- a/lib/common/scheduler.c +++ b/lib/common/scheduler.c @@ -105,7 +105,7 @@ pcmk_reset_scheduler(pcmk_scheduler_t *scheduler) // Do not reset local_node_name or out - g_clear_pointer(&scheduler->priv->now, crm_time_free); + g_clear_pointer(&scheduler->priv->now, free); g_clear_pointer(&scheduler->priv->options, g_hash_table_destroy); scheduler->priv->fence_action = NULL; @@ -285,7 +285,7 @@ pcmk__scheduler_epoch_time(pcmk_scheduler_t *scheduler) pcmk__trace("Scheduler 'now' set to current time"); scheduler->priv->now = crm_time_new(NULL); } - return crm_time_get_seconds_since_epoch(scheduler->priv->now); + return pcmk__time_to_unix(scheduler->priv->now); } /*! diff --git a/lib/common/strings.c b/lib/common/strings.c index a920a899e3c..db014ad44ed 100644 --- a/lib/common/strings.c +++ b/lib/common/strings.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -372,12 +372,12 @@ pcmk_parse_interval_spec(const char *input, guint *result_ms) } if (input[0] == 'P') { - crm_time_t *period_s = crm_time_parse_duration(input); + crm_time_t *period_s = pcmk__time_parse_duration(input); if (period_s != NULL) { - msec = crm_time_get_seconds(period_s); + msec = pcmk__time_get_seconds(period_s); msec = QB_MIN(msec, G_MAXUINT / 1000) * 1000; - crm_time_free(period_s); + free(period_s); } } else { diff --git a/lib/common/tests/iso8601/Makefile.am b/lib/common/tests/iso8601/Makefile.am index 92ac3b5b273..77e947fa7bf 100644 --- a/lib/common/tests/iso8601/Makefile.am +++ b/lib/common/tests/iso8601/Makefile.am @@ -12,13 +12,13 @@ include $(top_srcdir)/mk/tap.mk include $(top_srcdir)/mk/unittest.mk # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = crm_time_add_days_test -check_PROGRAMS += crm_time_add_seconds_test -check_PROGRAMS += crm_time_add_years_test -check_PROGRAMS += crm_time_parse_duration_test -check_PROGRAMS += pcmk__add_time_from_xml_test +check_PROGRAMS = pcmk__add_time_from_xml_test check_PROGRAMS += pcmk__readable_interval_test check_PROGRAMS += pcmk__set_time_if_earlier_test +check_PROGRAMS += pcmk__time_add_days_test +check_PROGRAMS += pcmk__time_add_seconds_test +check_PROGRAMS += pcmk__time_add_years_test check_PROGRAMS += pcmk__time_format_hr_test +check_PROGRAMS += pcmk__time_parse_duration_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/iso8601/crm_time_parse_duration_test.c b/lib/common/tests/iso8601/crm_time_parse_duration_test.c deleted file mode 100644 index 58015e82422..00000000000 --- a/lib/common/tests/iso8601/crm_time_parse_duration_test.c +++ /dev/null @@ -1,110 +0,0 @@ -/* - * 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 - -#include - -#include -#include "crmcommon_private.h" - -static void -empty_arg(void **state) -{ - assert_null(crm_time_parse_duration(NULL)); - assert_null(crm_time_parse_duration("")); -} - -static void -invalid_arg(void **state) -{ - // Valid except doesn't start with P - assert_null(crm_time_parse_duration("X3Y6M4DT12H30M5S")); - - // Illegal character after P - assert_null(crm_time_parse_duration("P")); - assert_null(crm_time_parse_duration("P 3Y6M4DT12H30M5S")); - assert_null(crm_time_parse_duration("PX3Y6M4DT12H30M5S")); - - // Missing or invalid units - assert_null(crm_time_parse_duration("P3Y6M4DT12H30M5")); - assert_null(crm_time_parse_duration("P3Y6M4DT12H30M5X")); - assert_null(crm_time_parse_duration("P3X6M4DT12H30M5S")); - assert_null(crm_time_parse_duration("PT")); - assert_null(crm_time_parse_duration("P/")); - -#if 0 - // @TODO The current implementation treats these as valid - - // Units out of order - assert_null(crm_time_parse_duration("P6M3Y4DT12H30M5S")); - assert_null(crm_time_parse_duration("P6M3DT12HY430M5S")); - - // Same unit specified multiple times - assert_null(crm_time_parse_duration("P6Y4M3D1MT12H30M5S")); - - // Weeks mixed with other units - assert_null(crm_time_parse_duration("P6Y4M3W3D1MT12H30M5S")); - assert_null(crm_time_parse_duration("P3WT12H30M5S")); -#endif -} - -static void -overflow(void **state) -{ - // Too large - assert_null(crm_time_parse_duration("P2147483648Y6M4DT12H30M5S")); - assert_null(crm_time_parse_duration("P3Y2147483648M4DT12H30M5S")); - assert_null(crm_time_parse_duration("P3Y6M2147483648DT12H30M5S")); - assert_null(crm_time_parse_duration("P3Y6M4DT2147483648H30M5S")); - assert_null(crm_time_parse_duration("P3Y6M4DT12H2147483648M5S")); - assert_null(crm_time_parse_duration("P3Y6M4DT12H30MP2147483648S")); - - // Too small - assert_null(crm_time_parse_duration("P-2147483648Y6M4DT12H30M5S")); - assert_null(crm_time_parse_duration("P3Y-2147483648M4DT12H30M5S")); - assert_null(crm_time_parse_duration("P3Y6M-2147483648DT12H30M5S")); - assert_null(crm_time_parse_duration("P3Y6M4DT-2147483648H30M5S")); - assert_null(crm_time_parse_duration("P3Y6M4DT12H-2147483648M5S")); - assert_null(crm_time_parse_duration("P3Y6M4DT12H30MP-2147483648S")); -} - -static void -valid_arg(void **state) -{ - // @TODO Check result value - assert_non_null(crm_time_parse_duration("P3Y6M4DT12H30M5S")); - assert_non_null(crm_time_parse_duration("P3Y6M4DT12H30M-5S")); - assert_non_null(crm_time_parse_duration("P3Y6M4DT12H-30M5S")); - assert_non_null(crm_time_parse_duration("P3Y6M4DT-12H30M5S")); - assert_non_null(crm_time_parse_duration("P3Y6M-4DT12H30M5S")); - assert_non_null(crm_time_parse_duration("P3Y-6M4DT12H30M5S")); - assert_non_null(crm_time_parse_duration("P3Y6M4DT12H30M")); - assert_non_null(crm_time_parse_duration("P3Y6M4D")); - assert_non_null(crm_time_parse_duration("P1M")); // 1 month - assert_non_null(crm_time_parse_duration("PT1M")); // 1 minute - assert_non_null(crm_time_parse_duration("P7W")); - -#if 0 - // @TODO Current implementation can't handle these cases - - // Fractional value for last unit - assert_non_null(crm_time_parse_duration("P3Y6M4DT12H30.5M")); - assert_non_null(crm_time_parse_duration("P3Y6M4DT12H30,5M")); - - // P--
T:: format - assert_non_null(crm_time_parse_duration("P0003-02-01T11:10:09"); -#endif -} - -PCMK__UNIT_TEST(NULL, NULL, - cmocka_unit_test(empty_arg), - cmocka_unit_test(invalid_arg), - cmocka_unit_test(overflow), - cmocka_unit_test(valid_arg)); diff --git a/lib/common/tests/iso8601/pcmk__add_time_from_xml_test.c b/lib/common/tests/iso8601/pcmk__add_time_from_xml_test.c index fa7f6ab183e..0f9be77060d 100644 --- a/lib/common/tests/iso8601/pcmk__add_time_from_xml_test.c +++ b/lib/common/tests/iso8601/pcmk__add_time_from_xml_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2024 the Pacemaker project contributors + * Copyright 2024-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -41,14 +41,14 @@ static void null_xml_ok(void **state) { crm_time_t *t = crm_time_new("2024-01-01 15:00:00"); - crm_time_t *reference = pcmk_copy_time(t); + crm_time_t *reference = pcmk__time_copy(t); assert_int_equal(pcmk__add_time_from_xml(t, pcmk__time_years, NULL), pcmk_rc_ok); - assert_int_equal(crm_time_compare(t, reference), 0); + assert_int_equal(pcmk__time_compare(t, reference), 0); - crm_time_free(t); - crm_time_free(reference); + free(t); + free(reference); } static void @@ -65,15 +65,15 @@ static void missing_attr(void **state) { crm_time_t *t = crm_time_new("2024-01-01 15:00:00"); - crm_time_t *reference = pcmk_copy_time(t); + crm_time_t *reference = pcmk__time_copy(t); xmlNode *xml = pcmk__xml_parse(YEARS_INVALID); assert_int_equal(pcmk__add_time_from_xml(t, pcmk__time_months, xml), pcmk_rc_ok); - assert_int_equal(crm_time_compare(t, reference), 0); + assert_int_equal(pcmk__time_compare(t, reference), 0); - crm_time_free(t); - crm_time_free(reference); + free(t); + free(reference); pcmk__xml_free(xml); } @@ -81,15 +81,15 @@ static void invalid_attr(void **state) { crm_time_t *t = crm_time_new("2024-01-01 15:00:00"); - crm_time_t *reference = pcmk_copy_time(t); + crm_time_t *reference = pcmk__time_copy(t); xmlNode *xml = pcmk__xml_parse(YEARS_INVALID); assert_int_equal(pcmk__add_time_from_xml(t, pcmk__time_years, xml), pcmk_rc_unpack_error); - assert_int_equal(crm_time_compare(t, reference), 0); + assert_int_equal(pcmk__time_compare(t, reference), 0); - crm_time_free(t); - crm_time_free(reference); + free(t); + free(reference); pcmk__xml_free(xml); } @@ -97,21 +97,21 @@ static void out_of_range_attr(void **state) { crm_time_t *t = crm_time_new("2024-01-01 15:00:00"); - crm_time_t *reference = pcmk_copy_time(t); + crm_time_t *reference = pcmk__time_copy(t); xmlNode *xml = NULL; xml = pcmk__xml_parse(YEARS_TOO_BIG); assert_int_equal(pcmk__add_time_from_xml(t, pcmk__time_years, xml), ERANGE); - assert_int_equal(crm_time_compare(t, reference), 0); + assert_int_equal(pcmk__time_compare(t, reference), 0); pcmk__xml_free(xml); xml = pcmk__xml_parse(YEARS_TOO_SMALL); assert_int_equal(pcmk__add_time_from_xml(t, pcmk__time_years, xml), ERANGE); - assert_int_equal(crm_time_compare(t, reference), 0); + assert_int_equal(pcmk__time_compare(t, reference), 0); pcmk__xml_free(xml); - crm_time_free(t); - crm_time_free(reference); + free(t); + free(reference); } static void @@ -123,10 +123,10 @@ add_years(void **state) assert_int_equal(pcmk__add_time_from_xml(t, pcmk__time_years, xml), pcmk_rc_ok); - assert_int_equal(crm_time_compare(t, reference), 0); + assert_int_equal(pcmk__time_compare(t, reference), 0); - crm_time_free(t); - crm_time_free(reference); + free(t); + free(reference); pcmk__xml_free(xml); } @@ -139,10 +139,10 @@ add_months(void **state) assert_int_equal(pcmk__add_time_from_xml(t, pcmk__time_months, xml), pcmk_rc_ok); - assert_int_equal(crm_time_compare(t, reference), 0); + assert_int_equal(pcmk__time_compare(t, reference), 0); - crm_time_free(t); - crm_time_free(reference); + free(t); + free(reference); pcmk__xml_free(xml); } @@ -155,10 +155,10 @@ add_weeks(void **state) assert_int_equal(pcmk__add_time_from_xml(t, pcmk__time_weeks, xml), pcmk_rc_ok); - assert_int_equal(crm_time_compare(t, reference), 0); + assert_int_equal(pcmk__time_compare(t, reference), 0); - crm_time_free(t); - crm_time_free(reference); + free(t); + free(reference); pcmk__xml_free(xml); } @@ -171,10 +171,10 @@ add_days(void **state) assert_int_equal(pcmk__add_time_from_xml(t, pcmk__time_days, xml), pcmk_rc_ok); - assert_int_equal(crm_time_compare(t, reference), 0); + assert_int_equal(pcmk__time_compare(t, reference), 0); - crm_time_free(t); - crm_time_free(reference); + free(t); + free(reference); pcmk__xml_free(xml); } @@ -187,10 +187,10 @@ add_hours(void **state) assert_int_equal(pcmk__add_time_from_xml(t, pcmk__time_hours, xml), pcmk_rc_ok); - assert_int_equal(crm_time_compare(t, reference), 0); + assert_int_equal(pcmk__time_compare(t, reference), 0); - crm_time_free(t); - crm_time_free(reference); + free(t); + free(reference); pcmk__xml_free(xml); } @@ -203,10 +203,10 @@ add_minutes(void **state) assert_int_equal(pcmk__add_time_from_xml(t, pcmk__time_minutes, xml), pcmk_rc_ok); - assert_int_equal(crm_time_compare(t, reference), 0); + assert_int_equal(pcmk__time_compare(t, reference), 0); - crm_time_free(t); - crm_time_free(reference); + free(t); + free(reference); pcmk__xml_free(xml); } @@ -219,10 +219,10 @@ add_seconds(void **state) assert_int_equal(pcmk__add_time_from_xml(t, pcmk__time_seconds, xml), pcmk_rc_ok); - assert_int_equal(crm_time_compare(t, reference), 0); + assert_int_equal(pcmk__time_compare(t, reference), 0); - crm_time_free(t); - crm_time_free(reference); + free(t); + free(reference); pcmk__xml_free(xml); } diff --git a/lib/common/tests/iso8601/pcmk__set_time_if_earlier_test.c b/lib/common/tests/iso8601/pcmk__set_time_if_earlier_test.c index c4bf0140825..27b48fb9c5b 100644 --- a/lib/common/tests/iso8601/pcmk__set_time_if_earlier_test.c +++ b/lib/common/tests/iso8601/pcmk__set_time_if_earlier_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2024 the Pacemaker project contributors + * Copyright 2024-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -18,7 +18,7 @@ static void null_ok(void **state) { crm_time_t *target = crm_time_new("2024-01-01 00:30:00 +01:00"); - crm_time_t *target_copy = pcmk_copy_time(target); + crm_time_t *target_copy = pcmk__time_copy(target); // Should do nothing (just checking it doesn't assert or crash) pcmk__set_time_if_earlier(NULL, NULL); @@ -26,23 +26,23 @@ null_ok(void **state) // Shouldn't assert, crash, or change target pcmk__set_time_if_earlier(target, NULL); - assert_int_equal(crm_time_compare(target, target_copy), 0); + assert_int_equal(pcmk__time_compare(target, target_copy), 0); - crm_time_free(target); - crm_time_free(target_copy); + free(target); + free(target_copy); } static void target_undefined(void **state) { crm_time_t *source = crm_time_new("2024-01-01 00:29:59 +01:00"); - crm_time_t *target = crm_time_new_undefined(); + crm_time_t *target = pcmk__assert_alloc(1, sizeof(crm_time_t)); pcmk__set_time_if_earlier(target, source); - assert_int_equal(crm_time_compare(target, source), 0); + assert_int_equal(pcmk__time_compare(target, source), 0); - crm_time_free(source); - crm_time_free(target); + free(source); + free(target); } static void @@ -52,10 +52,10 @@ source_earlier(void **state) crm_time_t *target = crm_time_new("2024-01-01 00:30:00 +01:00"); pcmk__set_time_if_earlier(target, source); - assert_int_equal(crm_time_compare(target, source), 0); + assert_int_equal(pcmk__time_compare(target, source), 0); - crm_time_free(source); - crm_time_free(target); + free(source); + free(target); } static void @@ -63,14 +63,14 @@ source_later(void **state) { crm_time_t *source = crm_time_new("2024-01-01 00:31:00 +01:00"); crm_time_t *target = crm_time_new("2024-01-01 00:30:00 +01:00"); - crm_time_t *target_copy = pcmk_copy_time(target); + crm_time_t *target_copy = pcmk__time_copy(target); pcmk__set_time_if_earlier(target, source); - assert_int_equal(crm_time_compare(target, target_copy), 0); + assert_int_equal(pcmk__time_compare(target, target_copy), 0); - crm_time_free(source); - crm_time_free(target); - crm_time_free(target_copy); + free(source); + free(target); + free(target_copy); } PCMK__UNIT_TEST(NULL, NULL, diff --git a/lib/common/tests/iso8601/crm_time_add_days_test.c b/lib/common/tests/iso8601/pcmk__time_add_days_test.c similarity index 95% rename from lib/common/tests/iso8601/crm_time_add_days_test.c rename to lib/common/tests/iso8601/pcmk__time_add_days_test.c index c9ad3616d78..6efaab17d43 100644 --- a/lib/common/tests/iso8601/crm_time_add_days_test.c +++ b/lib/common/tests/iso8601/pcmk__time_add_days_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2024-2025 the Pacemaker project contributors + * Copyright 2024-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -16,6 +16,8 @@ #include +#include "crmcommon_private.h" // pcmk__time_add_days + static void assert_add_days(const char *orig_date_time, int days, const char *expected_date_time) @@ -26,17 +28,17 @@ assert_add_days(const char *orig_date_time, int days, assert_non_null(orig); assert_non_null(expected); - crm_time_add_days(orig, days); - assert_int_equal(crm_time_compare(orig, expected), 0); + pcmk__time_add_days(orig, days); + assert_int_equal(pcmk__time_compare(orig, expected), 0); - crm_time_free(orig); - crm_time_free(expected); + free(orig); + free(expected); } static void invalid_argument(void **state) { - pcmk__assert_asserts(crm_time_add_days(NULL, 1)); + pcmk__assert_asserts(pcmk__time_add_days(NULL, 1)); } static void diff --git a/lib/common/tests/iso8601/crm_time_add_seconds_test.c b/lib/common/tests/iso8601/pcmk__time_add_seconds_test.c similarity index 95% rename from lib/common/tests/iso8601/crm_time_add_seconds_test.c rename to lib/common/tests/iso8601/pcmk__time_add_seconds_test.c index 4315d7dcb5d..9cf91ffb641 100644 --- a/lib/common/tests/iso8601/crm_time_add_seconds_test.c +++ b/lib/common/tests/iso8601/pcmk__time_add_seconds_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2024 the Pacemaker project contributors + * Copyright 2024-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -16,6 +16,8 @@ #include +#include "crmcommon_private.h" // pcmk__time_add_seconds + static void assert_add_seconds(const char *orig_date_time, int seconds, const char *expected_date_time) @@ -26,17 +28,17 @@ assert_add_seconds(const char *orig_date_time, int seconds, assert_non_null(orig); assert_non_null(expected); - crm_time_add_seconds(orig, seconds); - assert_int_equal(crm_time_compare(orig, expected), 0); + pcmk__time_add_seconds(orig, seconds); + assert_int_equal(pcmk__time_compare(orig, expected), 0); - crm_time_free(orig); - crm_time_free(expected); + free(orig); + free(expected); } static void invalid_argument(void **state) { - pcmk__assert_asserts(crm_time_add_seconds(NULL, 1)); + pcmk__assert_asserts(pcmk__time_add_seconds(NULL, 1)); } static void diff --git a/lib/common/tests/iso8601/crm_time_add_years_test.c b/lib/common/tests/iso8601/pcmk__time_add_years_test.c similarity index 85% rename from lib/common/tests/iso8601/crm_time_add_years_test.c rename to lib/common/tests/iso8601/pcmk__time_add_years_test.c index 8ea6b4d1c46..c4110c04c69 100644 --- a/lib/common/tests/iso8601/crm_time_add_years_test.c +++ b/lib/common/tests/iso8601/pcmk__time_add_years_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2024-2025 the Pacemaker project contributors + * Copyright 2024-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -16,6 +16,8 @@ #include +#include "crmcommon_private.h" // pcmk__time_add_days + static void assert_add_years(const char *orig_date_time, int years, const char *expected_date_time) @@ -26,17 +28,17 @@ assert_add_years(const char *orig_date_time, int years, assert_non_null(orig); assert_non_null(expected); - crm_time_add_years(orig, years); - assert_int_equal(crm_time_compare(orig, expected), 0); + pcmk__time_add_years(orig, years); + assert_int_equal(pcmk__time_compare(orig, expected), 0); - crm_time_free(orig); - crm_time_free(expected); + free(orig); + free(expected); } static void invalid_argument(void **state) { - pcmk__assert_asserts(crm_time_add_years(NULL, 1)); + pcmk__assert_asserts(pcmk__time_add_years(NULL, 1)); } static void diff --git a/lib/common/tests/iso8601/pcmk__time_parse_duration_test.c b/lib/common/tests/iso8601/pcmk__time_parse_duration_test.c new file mode 100644 index 00000000000..76acc590741 --- /dev/null +++ b/lib/common/tests/iso8601/pcmk__time_parse_duration_test.c @@ -0,0 +1,110 @@ +/* + * Copyright 2024-2026 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 void +empty_arg(void **state) +{ + assert_null(pcmk__time_parse_duration(NULL)); + assert_null(pcmk__time_parse_duration("")); +} + +static void +invalid_arg(void **state) +{ + // Valid except doesn't start with P + assert_null(pcmk__time_parse_duration("X3Y6M4DT12H30M5S")); + + // Illegal character after P + assert_null(pcmk__time_parse_duration("P")); + assert_null(pcmk__time_parse_duration("P 3Y6M4DT12H30M5S")); + assert_null(pcmk__time_parse_duration("PX3Y6M4DT12H30M5S")); + + // Missing or invalid units + assert_null(pcmk__time_parse_duration("P3Y6M4DT12H30M5")); + assert_null(pcmk__time_parse_duration("P3Y6M4DT12H30M5X")); + assert_null(pcmk__time_parse_duration("P3X6M4DT12H30M5S")); + assert_null(pcmk__time_parse_duration("PT")); + assert_null(pcmk__time_parse_duration("P/")); + +#if 0 + // @TODO The current implementation treats these as valid + + // Units out of order + assert_null(pcmk__time_parse_duration("P6M3Y4DT12H30M5S")); + assert_null(pcmk__time_parse_duration("P6M3DT12HY430M5S")); + + // Same unit specified multiple times + assert_null(pcmk__time_parse_duration("P6Y4M3D1MT12H30M5S")); + + // Weeks mixed with other units + assert_null(pcmk__time_parse_duration("P6Y4M3W3D1MT12H30M5S")); + assert_null(pcmk__time_parse_duration("P3WT12H30M5S")); +#endif +} + +static void +overflow(void **state) +{ + // Too large + assert_null(pcmk__time_parse_duration("P2147483648Y6M4DT12H30M5S")); + assert_null(pcmk__time_parse_duration("P3Y2147483648M4DT12H30M5S")); + assert_null(pcmk__time_parse_duration("P3Y6M2147483648DT12H30M5S")); + assert_null(pcmk__time_parse_duration("P3Y6M4DT2147483648H30M5S")); + assert_null(pcmk__time_parse_duration("P3Y6M4DT12H2147483648M5S")); + assert_null(pcmk__time_parse_duration("P3Y6M4DT12H30MP2147483648S")); + + // Too small + assert_null(pcmk__time_parse_duration("P-2147483648Y6M4DT12H30M5S")); + assert_null(pcmk__time_parse_duration("P3Y-2147483648M4DT12H30M5S")); + assert_null(pcmk__time_parse_duration("P3Y6M-2147483648DT12H30M5S")); + assert_null(pcmk__time_parse_duration("P3Y6M4DT-2147483648H30M5S")); + assert_null(pcmk__time_parse_duration("P3Y6M4DT12H-2147483648M5S")); + assert_null(pcmk__time_parse_duration("P3Y6M4DT12H30MP-2147483648S")); +} + +static void +valid_arg(void **state) +{ + // @TODO Check result value + assert_non_null(pcmk__time_parse_duration("P3Y6M4DT12H30M5S")); + assert_non_null(pcmk__time_parse_duration("P3Y6M4DT12H30M-5S")); + assert_non_null(pcmk__time_parse_duration("P3Y6M4DT12H-30M5S")); + assert_non_null(pcmk__time_parse_duration("P3Y6M4DT-12H30M5S")); + assert_non_null(pcmk__time_parse_duration("P3Y6M-4DT12H30M5S")); + assert_non_null(pcmk__time_parse_duration("P3Y-6M4DT12H30M5S")); + assert_non_null(pcmk__time_parse_duration("P3Y6M4DT12H30M")); + assert_non_null(pcmk__time_parse_duration("P3Y6M4D")); + assert_non_null(pcmk__time_parse_duration("P1M")); // 1 month + assert_non_null(pcmk__time_parse_duration("PT1M")); // 1 minute + assert_non_null(pcmk__time_parse_duration("P7W")); + +#if 0 + // @TODO Current implementation can't handle these cases + + // Fractional value for last unit + assert_non_null(pcmk__time_parse_duration("P3Y6M4DT12H30.5M")); + assert_non_null(pcmk__time_parse_duration("P3Y6M4DT12H30,5M")); + + // P--
T:: format + assert_non_null(pcmk__time_parse_duration("P0003-02-01T11:10:09"); +#endif +} + +PCMK__UNIT_TEST(NULL, NULL, + cmocka_unit_test(empty_arg), + cmocka_unit_test(invalid_arg), + cmocka_unit_test(overflow), + cmocka_unit_test(valid_arg)); diff --git a/lib/common/tests/nvpair/pcmk__unpack_nvpair_block_test.c b/lib/common/tests/nvpair/pcmk__unpack_nvpair_block_test.c index bfb34e03378..8198cef841b 100644 --- a/lib/common/tests/nvpair/pcmk__unpack_nvpair_block_test.c +++ b/lib/common/tests/nvpair/pcmk__unpack_nvpair_block_test.c @@ -127,7 +127,7 @@ with_rules(void **state) assert_unpack_nvpair_block("" XML_NVPAIRS_2 XML_FAILING_RULE "", &unpack_data, 2, "1", "1", NULL); - crm_time_free(now); + free(now); g_hash_table_destroy(unpack_data.values); } diff --git a/lib/common/tests/nvpair/pcmk__unpack_nvpair_blocks_test.c b/lib/common/tests/nvpair/pcmk__unpack_nvpair_blocks_test.c index 6bea483f134..5adb15296e1 100644 --- a/lib/common/tests/nvpair/pcmk__unpack_nvpair_blocks_test.c +++ b/lib/common/tests/nvpair/pcmk__unpack_nvpair_blocks_test.c @@ -65,7 +65,7 @@ null_xml(void **state) GHashTable *values = pcmk__strkey_table(free, free); crm_time_t *now = crm_time_new("2024-01-01 15:00:00"); crm_time_t *next_change = crm_time_new("2024-01-01 20:00:00"); - pcmk_rule_input_t rule_input = { + pcmk__rule_input_t rule_input = { .now = now, }; @@ -74,8 +74,8 @@ null_xml(void **state) &rule_input, values, next_change, NULL); assert_int_equal(g_hash_table_size(values), 0); g_hash_table_destroy(values); - crm_time_free(now); - crm_time_free(next_change); + free(now); + free(next_change); } static void @@ -84,7 +84,7 @@ null_table(void **state) xmlNode *xml = pcmk__xml_parse(XML_BLOCKS); crm_time_t *now = crm_time_new("2024-01-01 15:00:00"); crm_time_t *next_change = crm_time_new("2024-01-01 20:00:00"); - pcmk_rule_input_t rule_input = { + pcmk__rule_input_t rule_input = { .now = now, }; @@ -94,8 +94,8 @@ null_table(void **state) "id1", &rule_input, NULL, next_change, xml->doc)); pcmk__xml_free(xml); - crm_time_free(next_change); - crm_time_free(now); + free(next_change); + free(now); } static void @@ -105,7 +105,7 @@ rule_passes(void **state) crm_time_t *now = crm_time_new("2024-11-06 15:00:00"); crm_time_t *next_change = crm_time_new("2024-11-06 20:00:00"); GHashTable *values = pcmk__strkey_table(free, free); - pcmk_rule_input_t rule_input = { + pcmk__rule_input_t rule_input = { .now = now, }; @@ -118,8 +118,8 @@ rule_passes(void **state) assert_string_equal(g_hash_table_lookup(values, "name3"), "3"); pcmk__xml_free(xml); - crm_time_free(next_change); - crm_time_free(now); + free(next_change); + free(now); g_hash_table_destroy(values); } @@ -131,7 +131,7 @@ rule_fails(void **state) crm_time_t *next_change = crm_time_new("2024-11-05 20:00:00"); crm_time_t *expected_next_change = crm_time_new("2024-11-05 00:00:01"); GHashTable *values = pcmk__strkey_table(free, free); - pcmk_rule_input_t rule_input = { + pcmk__rule_input_t rule_input = { .now = now, }; @@ -144,12 +144,12 @@ rule_fails(void **state) assert_string_equal(g_hash_table_lookup(values, "name1"), "3"); assert_string_equal(g_hash_table_lookup(values, "name2"), "3"); assert_string_equal(g_hash_table_lookup(values, "name3"), "3"); - assert_int_equal(crm_time_compare(next_change, expected_next_change), 0); + assert_int_equal(pcmk__time_compare(next_change, expected_next_change), 0); pcmk__xml_free(xml); - crm_time_free(now); - crm_time_free(next_change); - crm_time_free(expected_next_change); + free(now); + free(next_change); + free(expected_next_change); g_hash_table_destroy(values); } @@ -159,7 +159,7 @@ element_name(void **state) xmlNode *xml = pcmk__xml_parse(XML_BLOCKS); crm_time_t *now = crm_time_new("2024-11-06 15:00:00"); GHashTable *values = pcmk__strkey_table(free, free); - pcmk_rule_input_t rule_input = { + pcmk__rule_input_t rule_input = { .now = now, }; @@ -185,7 +185,7 @@ element_name(void **state) assert_string_equal(g_hash_table_lookup(values, "name3"), "3"); pcmk__xml_free(xml); - crm_time_free(now); + free(now); g_hash_table_destroy(values); } diff --git a/lib/common/tests/rules/Makefile.am b/lib/common/tests/rules/Makefile.am index 57fbd067ba3..343960f35ad 100644 --- a/lib/common/tests/rules/Makefile.am +++ b/lib/common/tests/rules/Makefile.am @@ -19,12 +19,12 @@ check_PROGRAMS += pcmk__evaluate_date_spec_test check_PROGRAMS += pcmk__evaluate_condition_test check_PROGRAMS += pcmk__evaluate_op_expression_test check_PROGRAMS += pcmk__evaluate_rsc_expression_test +check_PROGRAMS += pcmk__evaluate_rule_test check_PROGRAMS += pcmk__parse_combine_test check_PROGRAMS += pcmk__parse_comparison_test check_PROGRAMS += pcmk__parse_source_test check_PROGRAMS += pcmk__parse_type_test check_PROGRAMS += pcmk__replace_submatches_test check_PROGRAMS += pcmk__unpack_duration_test -check_PROGRAMS += pcmk_evaluate_rule_test TESTS = $(check_PROGRAMS) diff --git a/lib/common/tests/rules/pcmk__evaluate_attr_expression_test.c b/lib/common/tests/rules/pcmk__evaluate_attr_expression_test.c index b229893aa59..01813dd0d9f 100644 --- a/lib/common/tests/rules/pcmk__evaluate_attr_expression_test.c +++ b/lib/common/tests/rules/pcmk__evaluate_attr_expression_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2024 the Pacemaker project contributors + * Copyright 2024-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -10,10 +10,13 @@ #include #include + #include -#include +#include // pcmk__rule_*, pcmk__xml_*, etc. #include +#include // PCMK_XA_*, PCMK_XE_*, etc. + #include "crmcommon_private.h" /* @@ -27,7 +30,7 @@ static const regmatch_t submatches[] = { { .rm_so = 7, .rm_eo = 12 }, // %1 = "north" }; -static pcmk_rule_input_t rule_input = { +static pcmk__rule_input_t rule_input = { // These are the only members used to evaluate attribute expressions // Used to replace submatches in attribute name diff --git a/lib/common/tests/rules/pcmk__evaluate_condition_test.c b/lib/common/tests/rules/pcmk__evaluate_condition_test.c index be0931a0267..40965ee32d8 100644 --- a/lib/common/tests/rules/pcmk__evaluate_condition_test.c +++ b/lib/common/tests/rules/pcmk__evaluate_condition_test.c @@ -19,7 +19,7 @@ * Shared data */ -static pcmk_rule_input_t rule_input = { +static pcmk__rule_input_t rule_input = { .rsc_standard = PCMK_RESOURCE_CLASS_OCF, .rsc_provider = "heartbeat", .rsc_agent = "IPaddr2", @@ -42,7 +42,7 @@ static void null_invalid(void **state) { xmlNode *xml = NULL; - crm_time_t *next_change = crm_time_new_undefined(); + crm_time_t *next_change = pcmk__assert_alloc(1, sizeof(crm_time_t)); assert_int_equal(pcmk__evaluate_condition(NULL, NULL, next_change), EINVAL); @@ -53,7 +53,7 @@ null_invalid(void **state) assert_int_equal(pcmk__evaluate_condition(NULL, &rule_input, next_change), EINVAL); - crm_time_free(next_change); + free(next_change); } @@ -63,12 +63,12 @@ static void invalid_expression(void **state) { xmlNode *xml = pcmk__xml_parse(EXPR_INVALID); - crm_time_t *next_change = crm_time_new_undefined(); + crm_time_t *next_change = pcmk__assert_alloc(1, sizeof(crm_time_t)); assert_int_equal(pcmk__evaluate_condition(xml, &rule_input, next_change), pcmk_rc_unpack_error); - crm_time_free(next_change); + free(next_change); pcmk__xml_free(xml); } @@ -130,12 +130,12 @@ date_expression(void **state) rule_input.now = now; assert_int_equal(pcmk__evaluate_condition(xml, &rule_input, next_change), pcmk_rc_before_range); - assert_int_equal(crm_time_compare(next_change, reference), 0); + assert_int_equal(pcmk__time_compare(next_change, reference), 0); rule_input.now = NULL; - crm_time_free(reference); - crm_time_free(next_change); - crm_time_free(now); + free(reference); + free(next_change); + free(now); } #define EXPR_RESOURCE \ diff --git a/lib/common/tests/rules/pcmk__evaluate_date_expression_test.c b/lib/common/tests/rules/pcmk__evaluate_date_expression_test.c index cfb6bdbf7f5..042fcaabad6 100644 --- a/lib/common/tests/rules/pcmk__evaluate_date_expression_test.c +++ b/lib/common/tests/rules/pcmk__evaluate_date_expression_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2024-2025 the Pacemaker project contributors + * Copyright 2024-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -47,14 +47,14 @@ assert_date_expression(const xmlNode *xml, const char *now_s, now = crm_time_new(now_s); assert_int_equal(pcmk__evaluate_date_expression(xml, now, next_change), reference_rc); - crm_time_free(now); + free(now); if (check_next_change) { crm_time_t *reference = crm_time_new(reference_s); - assert_int_equal(crm_time_compare(next_change, reference), 0); - crm_time_free(reference); - crm_time_free(next_change); + assert_int_equal(pcmk__time_compare(next_change, reference), 0); + free(reference); + free(next_change); } } @@ -73,7 +73,7 @@ null_invalid(void **state) assert_int_equal(pcmk__evaluate_date_expression(xml, NULL, NULL), EINVAL); assert_int_equal(pcmk__evaluate_date_expression(NULL, t, NULL), EINVAL); - crm_time_free(t); + free(t); pcmk__xml_free(xml); } @@ -237,7 +237,7 @@ range_missing(void **state) assert_int_equal(pcmk__evaluate_date_expression(xml, t, NULL), pcmk_rc_unpack_error); - crm_time_free(t); + free(t); pcmk__xml_free(xml); } diff --git a/lib/common/tests/rules/pcmk__evaluate_date_spec_test.c b/lib/common/tests/rules/pcmk__evaluate_date_spec_test.c index 8522f30be8b..08d9ff3a5d2 100644 --- a/lib/common/tests/rules/pcmk__evaluate_date_spec_test.c +++ b/lib/common/tests/rules/pcmk__evaluate_date_spec_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2020-2024 the Pacemaker project contributors + * Copyright 2020-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -24,7 +24,7 @@ run_one_test(const char *t, const char *x, int expected) assert_int_equal(pcmk__evaluate_date_spec(xml, tm), expected); - crm_time_free(tm); + free(tm); pcmk__xml_free(xml); } @@ -40,7 +40,7 @@ null_invalid(void **state) assert_int_equal(pcmk__evaluate_date_spec(xml, NULL), EINVAL); assert_int_equal(pcmk__evaluate_date_spec(NULL, tm), EINVAL); - crm_time_free(tm); + free(tm); pcmk__xml_free(xml); } diff --git a/lib/common/tests/rules/pcmk__evaluate_op_expression_test.c b/lib/common/tests/rules/pcmk__evaluate_op_expression_test.c index e04497f22f2..40de0f30e00 100644 --- a/lib/common/tests/rules/pcmk__evaluate_op_expression_test.c +++ b/lib/common/tests/rules/pcmk__evaluate_op_expression_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2024 the Pacemaker project contributors + * Copyright 2024-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -20,7 +20,7 @@ * Shared data */ -static pcmk_rule_input_t rule_input = { +static pcmk__rule_input_t rule_input = { // These are the only members used to evaluate operation expressions .op_name = PCMK_ACTION_MONITOR, .op_interval_ms = 10000, diff --git a/lib/common/tests/rules/pcmk__evaluate_rsc_expression_test.c b/lib/common/tests/rules/pcmk__evaluate_rsc_expression_test.c index e1d24853e5c..025aad58e0b 100644 --- a/lib/common/tests/rules/pcmk__evaluate_rsc_expression_test.c +++ b/lib/common/tests/rules/pcmk__evaluate_rsc_expression_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2024 the Pacemaker project contributors + * Copyright 2024-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -20,7 +20,7 @@ * Shared data */ -static pcmk_rule_input_t rule_input = { +static pcmk__rule_input_t rule_input = { // These are the only members used to evaluate resource expressions .rsc_standard = PCMK_RESOURCE_CLASS_OCF, .rsc_provider = "heartbeat", diff --git a/lib/common/tests/rules/pcmk_evaluate_rule_test.c b/lib/common/tests/rules/pcmk__evaluate_rule_test.c similarity index 84% rename from lib/common/tests/rules/pcmk_evaluate_rule_test.c rename to lib/common/tests/rules/pcmk__evaluate_rule_test.c index 0a028076c58..96c53fc3266 100644 --- a/lib/common/tests/rules/pcmk_evaluate_rule_test.c +++ b/lib/common/tests/rules/pcmk__evaluate_rule_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2024-2025 the Pacemaker project contributors + * Copyright 2024-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -19,7 +19,7 @@ * Shared data */ -static pcmk_rule_input_t rule_input = { +static pcmk__rule_input_t rule_input = { .rsc_standard = PCMK_RESOURCE_CLASS_OCF, .rsc_provider = "heartbeat", .rsc_agent = "IPaddr2", @@ -43,19 +43,18 @@ static void null_invalid(void **state) { xmlNode *xml = NULL; - crm_time_t *next_change = crm_time_new_undefined(); + crm_time_t *next_change = pcmk__assert_alloc(1, sizeof(crm_time_t)); - assert_int_equal(pcmk_evaluate_rule(NULL, NULL, next_change), - EINVAL); + assert_int_equal(pcmk__evaluate_rule(NULL, NULL, next_change), EINVAL); xml = pcmk__xml_parse(RULE_OP); - assert_int_equal(pcmk_evaluate_rule(xml, NULL, next_change), EINVAL); + assert_int_equal(pcmk__evaluate_rule(xml, NULL, next_change), EINVAL); pcmk__xml_free(xml); - assert_int_equal(pcmk_evaluate_rule(NULL, &rule_input, next_change), + assert_int_equal(pcmk__evaluate_rule(NULL, &rule_input, next_change), EINVAL); - crm_time_free(next_change); + free(next_change); } #define RULE_OP_MISSING_ID \ @@ -69,12 +68,12 @@ static void id_missing(void **state) { xmlNode *xml = pcmk__xml_parse(RULE_OP_MISSING_ID); - crm_time_t *next_change = crm_time_new_undefined(); + crm_time_t *next_change = pcmk__assert_alloc(1, sizeof(crm_time_t)); - assert_int_equal(pcmk_evaluate_rule(xml, &rule_input, next_change), + assert_int_equal(pcmk__evaluate_rule(xml, &rule_input, next_change), pcmk_rc_unpack_error); - crm_time_free(next_change); + free(next_change); pcmk__xml_free(xml); } @@ -85,13 +84,13 @@ good_idref(void **state) { xmlNode *parent_xml = pcmk__xml_parse(RULE_IDREF_PARENT); xmlNode *rule_xml = pcmk__xe_create(parent_xml, PCMK_XE_RULE); - crm_time_t *next_change = crm_time_new_undefined(); + crm_time_t *next_change = pcmk__assert_alloc(1, sizeof(crm_time_t)); pcmk__xe_set(rule_xml, PCMK_XA_ID_REF, "r"); - assert_int_equal(pcmk_evaluate_rule(rule_xml, &rule_input, next_change), + assert_int_equal(pcmk__evaluate_rule(rule_xml, &rule_input, next_change), pcmk_rc_ok); - crm_time_free(next_change); + free(next_change); pcmk__xml_free(parent_xml); } @@ -100,13 +99,13 @@ bad_idref(void **state) { xmlNode *parent_xml = pcmk__xml_parse(RULE_IDREF_PARENT); xmlNode *rule_xml = pcmk__xe_create(parent_xml, PCMK_XE_RULE); - crm_time_t *next_change = crm_time_new_undefined(); + crm_time_t *next_change = pcmk__assert_alloc(1, sizeof(crm_time_t)); pcmk__xe_set(rule_xml, PCMK_XA_ID_REF, "x"); - assert_int_equal(pcmk_evaluate_rule(rule_xml, &rule_input, next_change), + assert_int_equal(pcmk__evaluate_rule(rule_xml, &rule_input, next_change), pcmk_rc_unpack_error); - crm_time_free(next_change); + free(next_change); pcmk__xml_free(parent_xml); } @@ -118,8 +117,7 @@ empty_default(void **state) // Currently acceptable xmlNode *xml = pcmk__xml_parse(RULE_EMPTY); - assert_int_equal(pcmk_evaluate_rule(xml, &rule_input, NULL), - pcmk_rc_ok); + assert_int_equal(pcmk__evaluate_rule(xml, &rule_input, NULL), pcmk_rc_ok); pcmk__xml_free(xml); } @@ -134,8 +132,7 @@ empty_and(void **state) // Currently acceptable xmlNode *xml = pcmk__xml_parse(RULE_EMPTY_AND); - assert_int_equal(pcmk_evaluate_rule(xml, &rule_input, NULL), - pcmk_rc_ok); + assert_int_equal(pcmk__evaluate_rule(xml, &rule_input, NULL), pcmk_rc_ok); pcmk__xml_free(xml); } @@ -149,8 +146,7 @@ empty_or(void **state) { xmlNode *xml = pcmk__xml_parse(RULE_EMPTY_OR); - assert_int_equal(pcmk_evaluate_rule(xml, &rule_input, NULL), - pcmk_rc_ok); + assert_int_equal(pcmk__evaluate_rule(xml, &rule_input, NULL), pcmk_rc_ok); pcmk__xml_free(xml); } @@ -170,7 +166,7 @@ default_boolean_op(void **state) // Defaults to PCMK_VALUE_AND xmlNode *xml = pcmk__xml_parse(RULE_DEFAULT_BOOLEAN_OP); - assert_int_equal(pcmk_evaluate_rule(xml, &rule_input, NULL), + assert_int_equal(pcmk__evaluate_rule(xml, &rule_input, NULL), pcmk_rc_op_unsatisfied); pcmk__xml_free(xml); @@ -191,7 +187,7 @@ invalid_boolean_op(void **state) { xmlNode *xml = pcmk__xml_parse(RULE_INVALID_BOOLEAN_OP); - assert_int_equal(pcmk_evaluate_rule(xml, &rule_input, NULL), + assert_int_equal(pcmk__evaluate_rule(xml, &rule_input, NULL), pcmk_rc_unpack_error); pcmk__xml_free(xml); @@ -212,7 +208,7 @@ and_passes(void **state) { xmlNode *xml = pcmk__xml_parse(RULE_AND_PASSES); - assert_int_equal(pcmk_evaluate_rule(xml, &rule_input, NULL), pcmk_rc_ok); + assert_int_equal(pcmk__evaluate_rule(xml, &rule_input, NULL), pcmk_rc_ok); pcmk__xml_free(xml); } @@ -229,7 +225,7 @@ lonely_and_passes(void **state) { xmlNode *xml = pcmk__xml_parse(RULE_LONELY_AND); - assert_int_equal(pcmk_evaluate_rule(xml, &rule_input, NULL), pcmk_rc_ok); + assert_int_equal(pcmk__evaluate_rule(xml, &rule_input, NULL), pcmk_rc_ok); pcmk__xml_free(xml); } @@ -249,7 +245,7 @@ and_one_fails(void **state) { xmlNode *xml = pcmk__xml_parse(RULE_AND_ONE_FAILS); - assert_int_equal(pcmk_evaluate_rule(xml, &rule_input, NULL), + assert_int_equal(pcmk__evaluate_rule(xml, &rule_input, NULL), pcmk_rc_op_unsatisfied); pcmk__xml_free(xml); @@ -270,7 +266,7 @@ and_two_fail(void **state) { xmlNode *xml = pcmk__xml_parse(RULE_AND_TWO_FAIL); - assert_int_equal(pcmk_evaluate_rule(xml, &rule_input, NULL), + assert_int_equal(pcmk__evaluate_rule(xml, &rule_input, NULL), pcmk_rc_op_unsatisfied); pcmk__xml_free(xml); @@ -291,7 +287,7 @@ or_one_passes(void **state) { xmlNode *xml = pcmk__xml_parse(RULE_OR_ONE_PASSES); - assert_int_equal(pcmk_evaluate_rule(xml, &rule_input, NULL), pcmk_rc_ok); + assert_int_equal(pcmk__evaluate_rule(xml, &rule_input, NULL), pcmk_rc_ok); pcmk__xml_free(xml); } @@ -311,7 +307,7 @@ or_two_pass(void **state) { xmlNode *xml = pcmk__xml_parse(RULE_OR_TWO_PASS); - assert_int_equal(pcmk_evaluate_rule(xml, &rule_input, NULL), pcmk_rc_ok); + assert_int_equal(pcmk__evaluate_rule(xml, &rule_input, NULL), pcmk_rc_ok); pcmk__xml_free(xml); } @@ -329,7 +325,7 @@ lonely_or_passes(void **state) { xmlNode *xml = pcmk__xml_parse(RULE_LONELY_OR); - assert_int_equal(pcmk_evaluate_rule(xml, &rule_input, NULL), pcmk_rc_ok); + assert_int_equal(pcmk__evaluate_rule(xml, &rule_input, NULL), pcmk_rc_ok); pcmk__xml_free(xml); } @@ -349,7 +345,7 @@ or_fails(void **state) { xmlNode *xml = pcmk__xml_parse(RULE_OR_FAILS); - assert_int_equal(pcmk_evaluate_rule(xml, &rule_input, NULL), + assert_int_equal(pcmk__evaluate_rule(xml, &rule_input, NULL), pcmk_rc_op_unsatisfied); pcmk__xml_free(xml); diff --git a/lib/common/tests/rules/pcmk__unpack_duration_test.c b/lib/common/tests/rules/pcmk__unpack_duration_test.c index 2eba68e24e7..fba62996741 100644 --- a/lib/common/tests/rules/pcmk__unpack_duration_test.c +++ b/lib/common/tests/rules/pcmk__unpack_duration_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2024 the Pacemaker project contributors + * Copyright 2024-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -42,7 +42,7 @@ null_invalid(void **state) assert_int_equal(pcmk__unpack_duration(NULL, start, &end), EINVAL); assert_int_equal(pcmk__unpack_duration(NULL, NULL, &end), EINVAL); - crm_time_free(start); + free(start); pcmk__xml_free(duration); } @@ -55,8 +55,8 @@ nonnull_end_invalid(void **state) assert_int_equal(pcmk__unpack_duration(duration, start, &end), EINVAL); - crm_time_free(start); - crm_time_free(end); + free(start); + free(end); pcmk__xml_free(duration); } @@ -71,7 +71,7 @@ no_id(void **state) pcmk_rc_unpack_error); assert_null(end); - crm_time_free(start); + free(start); pcmk__xml_free(duration); } @@ -86,7 +86,7 @@ years_invalid(void **state) pcmk_rc_unpack_error); assert_null(end); - crm_time_free(start); + free(start); pcmk__xml_free(duration); } @@ -99,11 +99,11 @@ all_valid(void **state) crm_time_t *reference = crm_time_new("2025-03-21 16:01:01"); assert_int_equal(pcmk__unpack_duration(duration, start, &end), pcmk_rc_ok); - assert_int_equal(crm_time_compare(end, reference), 0); + assert_int_equal(pcmk__time_compare(end, reference), 0); - crm_time_free(start); - crm_time_free(end); - crm_time_free(reference); + free(start); + free(end); + free(reference); pcmk__xml_free(duration); } diff --git a/lib/common/tests/xml_element/pcmk__xe_get_datetime_test.c b/lib/common/tests/xml_element/pcmk__xe_get_datetime_test.c index bad68bffbf1..9c2b60b6ad9 100644 --- a/lib/common/tests/xml_element/pcmk__xe_get_datetime_test.c +++ b/lib/common/tests/xml_element/pcmk__xe_get_datetime_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2024 the Pacemaker project contributors + * Copyright 2024-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -51,11 +51,11 @@ static void nonnull_time_invalid(void **state) { xmlNode *xml = pcmk__xml_parse(REFERENCE_XML); - crm_time_t *t = crm_time_new_undefined(); + crm_time_t *t = pcmk__assert_alloc(1, sizeof(crm_time_t)); assert_int_equal(pcmk__xe_get_datetime(xml, ATTR_PRESENT, &t), EINVAL); - crm_time_free(t); + free(t); pcmk__xml_free(xml); } @@ -79,10 +79,10 @@ attr_valid(void **state) crm_time_t *reference = crm_time_new(REFERENCE_ISO8601); assert_int_equal(pcmk__xe_get_datetime(xml, ATTR_PRESENT, &t), pcmk_rc_ok); - assert_int_equal(crm_time_compare(t, reference), 0); + assert_int_equal(pcmk__time_compare(t, reference), 0); - crm_time_free(t); - crm_time_free(reference); + free(t); + free(reference); pcmk__xml_free(xml); } diff --git a/lib/common/tls.c b/lib/common/tls.c index babb8b9b20e..19906924aff 100644 --- a/lib/common/tls.c +++ b/lib/common/tls.c @@ -27,7 +27,7 @@ #include // QB_XS #include -#include // crm_time_free, crm_time_log_date +#include // crm_time_* #include // CRM_CHECK #include // pcmk_rc_* @@ -466,7 +466,7 @@ pcmk__tls_check_cert_expiration(gnutls_session_t session) expiry = gnutls_x509_crt_get_expiration_time(cert); - if (expiry != -1) { + if (expiry != (time_t) -1) { time_t now = time(NULL); /* If the cert is going to expire within ~ one month (30 days), log it */ @@ -474,8 +474,8 @@ pcmk__tls_check_cert_expiration(gnutls_session_t session) crm_time_t *expiry_t = pcmk__copy_timet(expiry); pcmk__time_log(LOG_WARNING, "TLS certificate will expire on", - expiry_t, crm_time_log_date|crm_time_log_timeofday); - crm_time_free(expiry_t); + expiry_t, pcmk__time_fmt_date|pcmk__time_fmt_time); + free(expiry_t); } } diff --git a/lib/common/xml_element.c b/lib/common/xml_element.c index 91adf14ff80..c8aeba83689 100644 --- a/lib/common/xml_element.c +++ b/lib/common/xml_element.c @@ -1455,7 +1455,7 @@ pcmk__xe_set_timeval(xmlNode *xml, const char *sec_attr, const char *usec_attr, * (\p *t must be NULL initially) * * \return Standard Pacemaker return code - * \note The caller is responsible for freeing \p *t using crm_time_free(). + * \note The caller is responsible for freeing \p *t using \c free(). */ int pcmk__xe_get_datetime(const xmlNode *xml, const char *attr, crm_time_t **t) diff --git a/lib/fencing/st_output.c b/lib/fencing/st_output.c index 0f68ed2ad74..d779b993a65 100644 --- a/lib/fencing/st_output.c +++ b/lib/fencing/st_output.c @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 the Pacemaker project contributors + * Copyright 2019-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -41,10 +41,10 @@ timespec_string(time_t sec, long nsec, bool show_usec) { }; return pcmk__timespec2str(&ts, - crm_time_log_date - |crm_time_log_timeofday - |crm_time_log_with_timezone - |(show_usec? crm_time_usecs : 0)); + pcmk__time_fmt_date + |pcmk__time_fmt_time + |pcmk__time_fmt_timezone + |(show_usec? pcmk__time_fmt_usecs : 0)); } /*! diff --git a/lib/lrmd/lrmd_alerts.c b/lib/lrmd/lrmd_alerts.c index 3fba9d47bd1..eeea294d0a5 100644 --- a/lib/lrmd/lrmd_alerts.c +++ b/lib/lrmd/lrmd_alerts.c @@ -1,5 +1,5 @@ /* - * Copyright 2015-2025 the Pacemaker project contributors + * Copyright 2015-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -184,7 +184,7 @@ exec_alert_list(lrmd_t *lrmd, const GList *alert_list, } } - crm_time_free(now_dt); + free(now_dt); if (any_failure) { return (any_success? -1 : -2); diff --git a/lib/pacemaker/pcmk_output.c b/lib/pacemaker/pcmk_output.c index 48e13538eb8..9095754e9f6 100644 --- a/lib/pacemaker/pcmk_output.c +++ b/lib/pacemaker/pcmk_output.c @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 the Pacemaker project contributors + * Copyright 2019-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -736,9 +736,9 @@ pacemakerd_health(pcmk__output_t *out, va_list args) if (last_updated != 0) { last_updated_s = pcmk__epoch2str(&last_updated, - crm_time_log_date - |crm_time_log_timeofday - |crm_time_log_with_timezone); + pcmk__time_fmt_date + |pcmk__time_fmt_time + |pcmk__time_fmt_timezone); } rc = out->info(out, "Status of %s: '%s' (last updated %s)", @@ -777,9 +777,9 @@ pacemakerd_health_html(pcmk__output_t *out, va_list args) if (last_updated != 0) { last_updated_s = pcmk__epoch2str(&last_updated, - crm_time_log_date - |crm_time_log_timeofday - |crm_time_log_with_timezone); + pcmk__time_fmt_date + |pcmk__time_fmt_time + |pcmk__time_fmt_timezone); } msg = pcmk__assert_asprintf("Status of %s: '%s' (last updated %s)", @@ -841,9 +841,9 @@ pacemakerd_health_xml(pcmk__output_t *out, va_list args) if (last_updated != 0) { last_updated_s = pcmk__epoch2str(&last_updated, - crm_time_log_date - |crm_time_log_timeofday - |crm_time_log_with_timezone); + pcmk__time_fmt_date + |pcmk__time_fmt_time + |pcmk__time_fmt_timezone); } pcmk__output_create_xml_node(out, PCMK_XE_PACEMAKERD, diff --git a/lib/pacemaker/pcmk_sched_location.c b/lib/pacemaker/pcmk_sched_location.c index 8415dc2c131..b5da4bb859c 100644 --- a/lib/pacemaker/pcmk_sched_location.c +++ b/lib/pacemaker/pcmk_sched_location.c @@ -70,7 +70,7 @@ parse_location_role(const char *role_spec, enum rsc_role_e *role) */ static const char * score_attribute_name(const xmlNode *rule_xml, char **allocated, - const pcmk_rule_input_t *rule_input) + const pcmk__rule_input_t *rule_input) { const char *name = NULL; @@ -191,7 +191,8 @@ score_from_attr(const char *constraint_id, const char *attr_name, static bool generate_location_rule(pcmk_resource_t *rsc, xmlNode *rule_xml, const char *discovery, crm_time_t *next_change, - pcmk_rule_input_t *rule_input, const char *constraint_id) + pcmk__rule_input_t *rule_input, + const char *constraint_id) { const char *rule_id = NULL; const char *score_attr = NULL; @@ -269,8 +270,8 @@ generate_location_rule(pcmk_resource_t *rsc, xmlNode *rule_xml, rule_input->rsc_params = pe_rsc_params(rsc, node, rsc->priv->scheduler); - if (pcmk_evaluate_rule(rule_xml, rule_input, - next_change) != pcmk_rc_ok) { + if (pcmk__evaluate_rule(rule_xml, rule_input, + next_change) != pcmk_rc_ok) { continue; } @@ -362,10 +363,10 @@ unpack_rsc_location(xmlNode *xml_obj, pcmk_resource_t *rsc, location->role_filter = role; } else { - crm_time_t *next_change = crm_time_new_undefined(); + crm_time_t *next_change = pcmk__assert_alloc(1, sizeof(crm_time_t)); xmlNode *rule_xml = pcmk__xe_first_child(xml_obj, PCMK_XE_RULE, NULL, NULL); - pcmk_rule_input_t rule_input = { + pcmk__rule_input_t rule_input = { .now = rsc->priv->scheduler->priv->now, .rsc_meta = rsc->priv->meta, .rsc_id = rsc_id_match, @@ -379,13 +380,13 @@ unpack_rsc_location(xmlNode *xml_obj, pcmk_resource_t *rsc, /* If there is a point in the future when the evaluation of a rule will * change, make sure the scheduler is re-run by that time. */ - if (crm_time_is_defined(next_change)) { - time_t t = (time_t) crm_time_get_seconds_since_epoch(next_change); + if (pcmk__time_is_initialized(next_change)) { + time_t t = (time_t) pcmk__time_to_unix(next_change); pcmk__update_recheck_time(t, rsc->priv->scheduler, "location rule evaluation"); } - crm_time_free(next_change); + free(next_change); } } diff --git a/lib/pacemaker/pcmk_scheduler.c b/lib/pacemaker/pcmk_scheduler.c index c0d5ccd9e4e..2368485c6a9 100644 --- a/lib/pacemaker/pcmk_scheduler.c +++ b/lib/pacemaker/pcmk_scheduler.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -834,8 +834,8 @@ pcmk__init_scheduler(pcmk__output_t *out, xmlNodePtr input, const crm_time_t *da // Make our own copy of the given crm_time_t object; otherwise // cluster_status() populates with the current time if (date != NULL) { - // pcmk_copy_time() guarantees non-NULL - new_scheduler->priv->now = pcmk_copy_time(date); + // pcmk__time_copy() guarantees non-NULL + new_scheduler->priv->now = pcmk__time_copy(date); } // Unpack everything diff --git a/lib/pacemaker/pcmk_simulate.c b/lib/pacemaker/pcmk_simulate.c index 647c2342257..7f318b3cfcb 100644 --- a/lib/pacemaker/pcmk_simulate.c +++ b/lib/pacemaker/pcmk_simulate.c @@ -498,31 +498,33 @@ static void set_effective_date(pcmk_scheduler_t *scheduler, bool print_original, const char *use_date) { + static const uint32_t flags = pcmk__time_fmt_date|pcmk__time_fmt_time; + pcmk__output_t *out = scheduler->priv->out; time_t original_date = 0; pcmk__assert(out != NULL); - pcmk__xe_get_time(scheduler->input, PCMK_XA_EXECUTION_DATE, - &original_date); - if (use_date) { scheduler->priv->now = crm_time_new(use_date); out->info(out, "Setting effective cluster time: %s", use_date); pcmk__time_log(LOG_NOTICE, "Pretending 'now' is", scheduler->priv->now, - crm_time_log_date|crm_time_log_timeofday); + flags); + return; + } - } else if (original_date != 0) { - scheduler->priv->now = pcmk__copy_timet(original_date); + if (pcmk__xe_get_time(scheduler->input, PCMK_XA_EXECUTION_DATE, + &original_date) != pcmk_rc_ok) { + return; + } - if (print_original) { - char *when = crm_time_as_string(scheduler->priv->now, - crm_time_log_date - |crm_time_log_timeofday); + scheduler->priv->now = pcmk__copy_timet(original_date); - out->info(out, "Using the original execution date of: %s", when); - free(when); - } + if (print_original) { + char *when = pcmk__time_text(scheduler->priv->now, flags); + + out->info(out, "Using the original execution date of: %s", when); + free(when); } } diff --git a/lib/pengine/complex.c b/lib/pengine/complex.c index 60c97f9572c..fe6b89544a8 100644 --- a/lib/pengine/complex.c +++ b/lib/pengine/complex.c @@ -132,7 +132,7 @@ dup_attr(gpointer key, gpointer value, gpointer user_data) static void expand_parents_fixed_nvpairs(const pcmk_resource_t *rsc, - const pcmk_rule_input_t *rule_input, + const pcmk__rule_input_t *rule_input, GHashTable *meta_hash, pcmk_scheduler_t *scheduler) { GHashTable *parent_orig_meta = pcmk__strkey_table(free, free); @@ -168,7 +168,7 @@ void get_meta_attributes(GHashTable *meta_hash, const pcmk_resource_t *rsc, pcmk_node_t *node, pcmk_scheduler_t *scheduler) { - pcmk_rule_input_t rule_input = { NULL, }; + pcmk__rule_input_t rule_input = { NULL, }; CRM_CHECK((meta_hash != NULL) && (rsc != NULL) && (scheduler != NULL), return); @@ -227,7 +227,7 @@ void get_rsc_attributes(GHashTable *instance_attrs, const pcmk_resource_t *rsc, const pcmk_node_t *node, pcmk_scheduler_t *scheduler) { - pcmk_rule_input_t rule_input = { + pcmk__rule_input_t rule_input = { .now = NULL, }; @@ -681,7 +681,7 @@ pe__unpack_resource(xmlNode *xml_obj, pcmk_resource_t **rsc, bool remote_node = false; pcmk__resource_private_t *rsc_private = NULL; - pcmk_rule_input_t rule_input = { + pcmk__rule_input_t rule_input = { .now = NULL, }; diff --git a/lib/pengine/pe_actions.c b/lib/pengine/pe_actions.c index d71c800cf04..ac66567dc07 100644 --- a/lib/pengine/pe_actions.c +++ b/lib/pengine/pe_actions.c @@ -249,7 +249,7 @@ pcmk__unpack_action_rsc_params(const xmlNode *action_xml, { GHashTable *params = pcmk__strkey_table(free, free); - const pcmk_rule_input_t rule_input = { + const pcmk__rule_input_t rule_input = { .now = scheduler->priv->now, .node_attrs = node_attrs, }; @@ -572,8 +572,8 @@ unpack_interval_origin(const char *value, const xmlNode *xml_obj, } // Get seconds since origin (negative if origin is in the future) - result = crm_time_get_seconds(now) - crm_time_get_seconds(origin); - crm_time_free(origin); + result = pcmk__time_get_seconds(now) - pcmk__time_get_seconds(origin); + free(origin); // Calculate seconds from closest interval to now result = result % interval_sec; @@ -685,7 +685,7 @@ pcmk__unpack_action_meta(pcmk_resource_t *rsc, const pcmk_node_t *node, const char *timeout_spec = NULL; const char *str = NULL; - const pcmk_rule_input_t rule_input = { + const pcmk__rule_input_t rule_input = { /* Node attributes are not set because node expressions are not allowed * for meta-attributes */ diff --git a/lib/pengine/pe_output.c b/lib/pengine/pe_output.c index 7b43314937e..ff2828a765e 100644 --- a/lib/pengine/pe_output.c +++ b/lib/pengine/pe_output.c @@ -1648,9 +1648,9 @@ failed_action_xml(pcmk__output_t *out, va_list args) { guint interval_ms = 0; char *interval_ms_s = NULL; char *rc_change = pcmk__epoch2str(&epoch, - crm_time_log_date - |crm_time_log_timeofday - |crm_time_log_with_timezone); + pcmk__time_fmt_date + |pcmk__time_fmt_time + |pcmk__time_fmt_timezone); pcmk__xe_get_guint(xml_op, PCMK_META_INTERVAL, &interval_ms); interval_ms_s = pcmk__assert_asprintf("%u", interval_ms); diff --git a/lib/pengine/rules_compat.c b/lib/pengine/rules_compat.c index 43fce159391..02057759eba 100644 --- a/lib/pengine/rules_compat.c +++ b/lib/pengine/rules_compat.c @@ -14,27 +14,27 @@ #include // xmlNode #include +#include // pcmk__evaluate_rule, etc. #include // crm_time_t #include // enum rsc_role_e - #include -#include // pcmk_rule_input_t, etc. // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START +#include // pcmk_rule_input_t #include #include gboolean test_rule(xmlNode * rule, GHashTable * node_hash, enum rsc_role_e role, crm_time_t * now) { - pcmk_rule_input_t rule_input = { + pcmk__rule_input_t rule_input = { .node_attrs = node_hash, .now = now, }; - return pcmk_evaluate_rule(rule, &rule_input, NULL) == pcmk_rc_ok; + return pcmk__evaluate_rule(rule, &rule_input, NULL) == pcmk_rc_ok; } /*! @@ -79,6 +79,7 @@ pe_eval_nvpairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name, crm_time_t *next_change) { GList *pairs = NULL; + pcmk_rule_input_t tmp_input = { NULL, }; pcmk__nvpair_unpack_t data = { .values = hash, .first_id = always_first, @@ -96,7 +97,11 @@ pe_eval_nvpairs(xmlNode *top, const xmlNode *xml_obj, const char *set_name, } data.doc = xml_obj->doc; - map_rule_input(&(data.rule_input), rule_data); + + map_rule_input(&tmp_input, rule_data); + if (rule_data != NULL) { + pcmk__rule_input_convert(&tmp_input, &data.rule_input); + } pairs = g_list_sort_with_data(pairs, pcmk__cmp_nvpair_blocks, &data); g_list_foreach(pairs, pcmk__unpack_nvpair_block, &data); diff --git a/lib/pengine/status.c b/lib/pengine/status.c index c7da06f45ea..af33aff69b0 100644 --- a/lib/pengine/status.c +++ b/lib/pengine/status.c @@ -320,7 +320,7 @@ cleanup_calculations(pcmk_scheduler_t *scheduler) pcmk__free_param_checks(scheduler); g_list_free(scheduler->priv->stop_needed); - crm_time_free(scheduler->priv->now); + free(scheduler->priv->now); pcmk__xml_free(scheduler->input); pcmk__xml_free(scheduler->priv->failed); pcmk__xml_free(scheduler->priv->graph); diff --git a/lib/pengine/unpack.c b/lib/pengine/unpack.c index 1430a58a146..0db6448d6cc 100644 --- a/lib/pengine/unpack.c +++ b/lib/pengine/unpack.c @@ -221,7 +221,7 @@ unpack_config(xmlNode *config, pcmk_scheduler_t *scheduler) const char *value = NULL; GHashTable *config_hash = pcmk__strkey_table(free, free); - const pcmk_rule_input_t rule_input = { + const pcmk__rule_input_t rule_input = { .now = scheduler->priv->now, }; @@ -4963,7 +4963,7 @@ add_node_attrs(const xmlNode *xml_obj, pcmk_node_t *node, bool overwrite, { const char *cluster_name = NULL; const char *dc_id = pcmk__xe_get(scheduler->input, PCMK_XA_DC_UUID); - const pcmk_rule_input_t rule_input = { + const pcmk__rule_input_t rule_input = { .now = scheduler->priv->now, }; diff --git a/lib/pengine/utils.c b/lib/pengine/utils.c index 7906fbe6305..83c4d56cc7f 100644 --- a/lib/pengine/utils.c +++ b/lib/pengine/utils.c @@ -692,7 +692,7 @@ pe__shutdown_requested(const pcmk_node_t *node) */ void pe__unpack_dataset_nvpairs(const xmlNode *xml_obj, const char *set_name, - const pcmk_rule_input_t *rule_input, + const pcmk__rule_input_t *rule_input, GHashTable *hash, const char *always_first, pcmk_scheduler_t *scheduler) { @@ -709,16 +709,16 @@ pe__unpack_dataset_nvpairs(const xmlNode *xml_obj, const char *set_name, return; } - next_change = crm_time_new_undefined(); + next_change = pcmk__assert_alloc(1, sizeof(crm_time_t)); pcmk__unpack_nvpair_blocks(xml_obj, set_name, always_first, rule_input, hash, next_change, scheduler->input->doc); - if (crm_time_is_defined(next_change)) { - time_t recheck = (time_t) crm_time_get_seconds_since_epoch(next_change); + if (pcmk__time_is_initialized(next_change)) { + time_t recheck = (time_t) pcmk__time_to_unix(next_change); pcmk__update_recheck_time(recheck, scheduler, "rule evaluation"); } - crm_time_free(next_change); + free(next_change); } bool diff --git a/tools/crm_resource.c b/tools/crm_resource.c index 8e655d1f6e3..fbbfc104cdf 100644 --- a/tools/crm_resource.c +++ b/tools/crm_resource.c @@ -1445,7 +1445,7 @@ handle_get_param(pcmk_resource_t *rsc, pcmk_node_t *node, cib_t *cib_conn, value = pcmk__xe_get(rsc->priv->xml, options.prop_name); } else { - const pcmk_rule_input_t rule_input = { + const pcmk__rule_input_t rule_input = { .now = scheduler->priv->now, }; diff --git a/tools/crm_resource_ban.c b/tools/crm_resource_ban.c index e52268b4335..264c5ee1d2a 100644 --- a/tools/crm_resource_ban.c +++ b/tools/crm_resource_ban.c @@ -1,5 +1,5 @@ /* - * Copyright 2004-2025 the Pacemaker project contributors + * Copyright 2004-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -24,14 +24,14 @@ parse_cli_lifetime(pcmk__output_t *out, const char *move_lifetime) crm_time_t *now = NULL; crm_time_t *later = NULL; crm_time_t *duration = NULL; - const uint32_t duration_flags = crm_time_log_date|crm_time_log_timeofday; - const uint32_t time_flags = duration_flags|crm_time_log_with_timezone; + const uint32_t duration_flags = pcmk__time_fmt_date|pcmk__time_fmt_time; + const uint32_t time_flags = duration_flags|pcmk__time_fmt_timezone; if (move_lifetime == NULL) { return NULL; } - duration = crm_time_parse_duration(move_lifetime); + duration = pcmk__time_parse_duration(move_lifetime); if (duration == NULL) { out->err(out, "Invalid duration specified: %s\n" "Please refer to https://en.wikipedia.org/wiki/ISO_8601#Durations " @@ -40,25 +40,25 @@ parse_cli_lifetime(pcmk__output_t *out, const char *move_lifetime) } now = crm_time_new(NULL); - later = crm_time_add(now, duration); + later = pcmk__time_add(now, duration); if (later == NULL) { out->err(out, "Unable to add %s to current time\n" "Please report to " PACKAGE_BUGREPORT " as possible bug", move_lifetime); - crm_time_free(now); - crm_time_free(duration); + free(now); + free(duration); return NULL; } pcmk__time_log(LOG_INFO, "now ", now, time_flags); pcmk__time_log(LOG_INFO, "later ", later, time_flags); pcmk__time_log(LOG_INFO, "duration", duration, duration_flags); - later_s = crm_time_as_string(later, time_flags); + later_s = pcmk__time_text(later, time_flags); out->info(out, "Migration will take effect until: %s", later_s); - crm_time_free(duration); - crm_time_free(later); - crm_time_free(now); + free(duration); + free(later); + free(now); return later_s; } @@ -496,7 +496,7 @@ cli_resource_clear_all_expired(xmlNode *root, cib_t *cib_conn, const char *rsc, continue; // Treat as unexpired } - if (crm_time_compare(now, end) == 1) { + if (pcmk__time_compare(now, end) > 0) { xmlNode *fragment = NULL; xmlNode *location = NULL; @@ -516,7 +516,7 @@ cli_resource_clear_all_expired(xmlNode *root, cib_t *cib_conn, const char *rsc, pcmk__xml_free(fragment); } - crm_time_free(end); + free(end); } done: @@ -524,6 +524,6 @@ cli_resource_clear_all_expired(xmlNode *root, cib_t *cib_conn, const char *rsc, g_string_free(buf, TRUE); } xmlXPathFreeObject(xpathObj); - crm_time_free(now); + free(now); return rc; } diff --git a/tools/crm_rule.c b/tools/crm_rule.c index bf22b197c3b..b660331198b 100644 --- a/tools/crm_rule.c +++ b/tools/crm_rule.c @@ -1,5 +1,5 @@ /* - * Copyright 2019-2025 the Pacemaker project contributors + * Copyright 2019-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -157,18 +157,10 @@ main(int argc, char **argv) /* Set up some defaults. */ rule_date = crm_time_new(options.date); - if (rule_date == NULL) { - if (options.date != NULL) { - exit_code = CRM_EX_DATAERR; - g_set_error(&error, PCMK__EXITC_ERROR, exit_code, - "Invalid date specified: '%s'", options.date); - - } else { - // Should never happen - exit_code = CRM_EX_OSERR; - g_set_error(&error, PCMK__EXITC_ERROR, exit_code, - "No --date given and can't determine current date"); - } + if ((rule_date == NULL) || !pcmk__time_valid_year(rule_date->years)) { + exit_code = CRM_EX_DATAERR; + g_set_error(&error, PCMK__EXITC_ERROR, exit_code, + "Invalid date specified: '%s'", options.date); goto done; } @@ -212,7 +204,7 @@ main(int argc, char **argv) g_strfreev(processed_args); pcmk__free_arg_context(context); - crm_time_free(rule_date); + free(rule_date); pcmk__xml_free(input); pcmk__output_and_clear_error(&error, out); diff --git a/tools/iso8601.c b/tools/iso8601.c index d8d0a1a7f8a..bb91d076832 100644 --- a/tools/iso8601.c +++ b/tools/iso8601.c @@ -1,5 +1,5 @@ /* - * Copyright 2005-2025 the Pacemaker project contributors + * Copyright 2005-2026 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -34,6 +34,8 @@ struct { } options; #define INDENT " " +#define BEGIN_VALID_RANGE_S "0001-01-01T00:00:00" +#define END_VALID_RANGE_S "9999-12-31T23:59:59" static gboolean date_now_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { @@ -49,15 +51,15 @@ date_now_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError static gboolean modifier_cb(const gchar *option_name, const gchar *optarg, gpointer data, GError **error) { if (pcmk__str_any_of(option_name, "--seconds", "-s", NULL)) { - options.print_options |= crm_time_seconds; + options.print_options |= pcmk__time_fmt_seconds; } else if (pcmk__str_any_of(option_name, "--epoch", "-S", NULL)) { - options.print_options |= crm_time_epoch; + options.print_options |= pcmk__time_fmt_epoch; } else if (pcmk__str_any_of(option_name, "--local", "-L", NULL)) { - options.print_options |= crm_time_log_with_timezone; + options.print_options |= pcmk__time_fmt_timezone; } else if (pcmk__str_any_of(option_name, "--ordinal", "-O", NULL)) { - options.print_options |= crm_time_ordinal; + options.print_options |= pcmk__time_fmt_ordinal; } else if (pcmk__str_any_of(option_name, "--week", "-W", NULL)) { - options.print_options |= crm_time_weeks; + options.print_options |= pcmk__time_fmt_weeks; } return TRUE; @@ -124,8 +126,8 @@ date_default(pcmk__output_t *out, va_list args) char *date_s = NULL; - opts |= crm_time_log_date | crm_time_log_timeofday; - date_s = crm_time_as_string(date, opts); + opts |= pcmk__time_fmt_date|pcmk__time_fmt_time; + date_s = pcmk__time_text(date, opts); out->info(out, "%s: %s", prefix, date_s); @@ -143,8 +145,8 @@ date_xml(pcmk__output_t *out, va_list args) char *date_s = NULL; - opts |= crm_time_log_date | crm_time_log_timeofday; - date_s = crm_time_as_string(date, opts); + opts |= pcmk__time_fmt_date|pcmk__time_fmt_time; + date_s = pcmk__time_text(date, opts); pcmk__output_create_xml_text_node(out, PCMK_XE_DATE, date_s); free(date_s); @@ -158,7 +160,7 @@ duration_default(pcmk__output_t *out, va_list args) crm_time_t *time = va_arg(args, crm_time_t *); int opts = va_arg(args, int); - char *date_s = crm_time_as_string(time, opts | crm_time_log_duration); + char *date_s = pcmk__time_text(time, opts|pcmk__time_fmt_duration); out->info(out, "Duration: %s", date_s); @@ -173,7 +175,7 @@ duration_xml(pcmk__output_t *out, va_list args) crm_time_t *time = va_arg(args, crm_time_t *); int opts = va_arg(args, int); - char *date_s = crm_time_as_string(time, opts | crm_time_log_duration); + char *date_s = pcmk__time_text(time, opts|pcmk__time_fmt_duration); pcmk__output_create_xml_text_node(out, PCMK_XE_DURATION, date_s); free(date_s); @@ -189,8 +191,8 @@ duration_ends_default(pcmk__output_t *out, va_list args) char *date_s = NULL; - opts |= crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone; - date_s = crm_time_as_string(time, opts); + opts |= pcmk__time_fmt_date|pcmk__time_fmt_time|pcmk__time_fmt_timezone; + date_s = pcmk__time_text(time, opts); out->info(out, "Duration ends at: %s", date_s); @@ -207,74 +209,163 @@ duration_ends_xml(pcmk__output_t *out, va_list args) char *date_s = NULL; - opts |= crm_time_log_date | crm_time_log_timeofday | crm_time_log_with_timezone; - date_s = crm_time_as_string(time, opts); + opts |= pcmk__time_fmt_date|pcmk__time_fmt_time|pcmk__time_fmt_timezone; + date_s = pcmk__time_text(time, opts); pcmk__output_create_xml_text_node(out, PCMK_XE_DURATION_ENDS, date_s); free(date_s); return pcmk_rc_ok; } -PCMK__OUTPUT_ARGS("period", "crm_time_period_t *", "int") +PCMK__OUTPUT_ARGS("period", "crm_time_t *", "crm_time_t *", "int") static int period_default(pcmk__output_t *out, va_list args) { - crm_time_period_t *period = va_arg(args, crm_time_period_t *); + const crm_time_t *start = va_arg(args, crm_time_t *); + const crm_time_t *end = va_arg(args, crm_time_t *); int opts = va_arg(args, int); - char *start = NULL; - char *end = NULL; + char *start_s = NULL; + char *end_s = NULL; - opts |= crm_time_log_date | crm_time_log_timeofday; + opts |= pcmk__time_fmt_date|pcmk__time_fmt_time; - start = crm_time_as_string(period->start, opts); - if (start == NULL) { + start_s = pcmk__time_text(start, opts); + if (start_s == NULL) { return pcmk_rc_no_output; } - end = crm_time_as_string(period->end, opts); - if (end == NULL) { - free(start); + end_s = pcmk__time_text(end, opts); + if (end_s == NULL) { + free(start_s); return pcmk_rc_no_output; } - out->info(out, "Period: %s to %s", start, end); + out->info(out, "Period: %s to %s", start_s, end_s); - free(start); - free(end); + free(start_s); + free(end_s); return pcmk_rc_ok; } -PCMK__OUTPUT_ARGS("period", "crm_time_period_t *", "int") +PCMK__OUTPUT_ARGS("period", "crm_time_t *", "crm_time_t *", "int") static int period_xml(pcmk__output_t *out, va_list args) { - crm_time_period_t *period = va_arg(args, crm_time_period_t *); + const crm_time_t *start = va_arg(args, crm_time_t *); + const crm_time_t *end = va_arg(args, crm_time_t *); int opts = va_arg(args, int); - char *start = NULL; - char *end = NULL; + char *start_s = NULL; + char *end_s = NULL; - opts |= crm_time_log_date | crm_time_log_timeofday; + opts |= pcmk__time_fmt_date|pcmk__time_fmt_time; - start = crm_time_as_string(period->start, opts); - if (start == NULL) { + start_s = pcmk__time_text(start, opts); + if (start_s == NULL) { return pcmk_rc_no_output; } - end = crm_time_as_string(period->end, opts); - if (end == NULL) { - free(start); + end_s = pcmk__time_text(end, opts); + if (end_s == NULL) { + free(start_s); return pcmk_rc_no_output; } pcmk__output_xml_create_parent(out, PCMK_XE_PERIOD, NULL); - pcmk__output_create_xml_text_node(out, PCMK_XE_START, start); - pcmk__output_create_xml_text_node(out, PCMK_XE_END, end); + pcmk__output_create_xml_text_node(out, PCMK_XE_START, start_s); + pcmk__output_create_xml_text_node(out, PCMK_XE_END, end_s); + + free(start_s); + free(end_s); + return pcmk_rc_ok; +} + +static int +parse_period(const char *period_str, crm_time_t **start, crm_time_t **end) +{ + const char *original = period_str; + crm_time_t *diff = NULL; + + if (pcmk__str_empty(period_str)) { + pcmk__err("No ISO 8601 time period given"); + goto invalid; + } + + tzset(); + + if (period_str[0] == 'P') { + diff = pcmk__time_parse_duration(period_str); + if (diff == NULL) { + goto invalid; + } + } else { + *start = crm_time_new(period_str); + if (*start == NULL) { + goto invalid; + } + } + + period_str = strchr(original, '/'); + if (period_str != NULL) { + ++period_str; + if (period_str[0] == 'P') { + if (diff != NULL) { + pcmk__err("'%s' is not a valid ISO 8601 time period because it " + "has two durations", original); + goto invalid; + } + diff = pcmk__time_parse_duration(period_str); + if (diff == NULL) { + goto invalid; + } + } else { + *end = crm_time_new(period_str); + if (*end == NULL) { + goto invalid; + } + } + + } else if (diff != NULL) { + // Only duration given, assume start is now + *start = pcmk__copy_timet(time(NULL)); + + } else { + // Only start given + pcmk__err("'%s' is not a valid ISO 8601 time period because it has no " + "duration or ending time", original); + goto invalid; + } + + if (*start == NULL) { + *start = pcmk__time_subtract(*end, diff); - free(start); - free(end); + } else if (*end == NULL) { + *end = pcmk__time_add(*start, diff); + } + + if (!pcmk__time_valid_year((*start)->years) || !valid_time(*start)) { + pcmk__err("'%s' is not a valid ISO 8601 time period because the start " + "is invalid (must be between " BEGIN_VALID_RANGE_S " and " + END_VALID_RANGE_S ")", period_str); + goto invalid; + } + + if (!pcmk__time_valid_year((*end)->years) || !valid_time(*end)) { + pcmk__err("'%s' is not a valid ISO 8601 time period because the end is " + "invalid (must be between " BEGIN_VALID_RANGE_S " and " + END_VALID_RANGE_S ")", period_str); + goto invalid; + } + + free(diff); return pcmk_rc_ok; + +invalid: + free(diff); + free(*start); + free(*end); + return EINVAL; } static GOptionContext * @@ -376,7 +467,7 @@ main(int argc, char **argv) } if (options.duration_s) { - duration = crm_time_parse_duration(options.duration_s); + duration = pcmk__time_parse_duration(options.duration_s); if (duration == NULL) { exit_code = CRM_EX_INVALID_PARAM; @@ -389,21 +480,23 @@ main(int argc, char **argv) } if (options.period_s) { - crm_time_period_t *period = crm_time_parse_period(options.period_s); + crm_time_t *start = NULL; + crm_time_t *end = NULL; - if (period == NULL) { + if (parse_period(options.period_s, &start, &end) != pcmk_rc_ok) { exit_code = CRM_EX_INVALID_PARAM; g_set_error(&error, PCMK__EXITC_ERROR, exit_code, "Invalid interval specified: %s", options.period_s); goto done; } - out->message(out, "period", period, options.print_options); - crm_time_free_period(period); + out->message(out, "period", start, end, options.print_options); + free(start); + free(end); } if (date_time && duration) { - crm_time_t *later = crm_time_add(date_time, duration); + crm_time_t *later = pcmk__time_add(date_time, duration); if (later == NULL) { exit_code = CRM_EX_SOFTWARE; @@ -416,20 +509,23 @@ main(int argc, char **argv) out->message(out, "duration_ends", later, options.print_options); if (options.expected_s) { - char *dt_s = crm_time_as_string(later, - options.print_options | crm_time_log_date | - crm_time_log_timeofday); + char *dt_s = pcmk__time_text(later, + options.print_options + |pcmk__time_fmt_date + |pcmk__time_fmt_time); if (!pcmk__str_eq(options.expected_s, dt_s, pcmk__str_casei)) { exit_code = CRM_EX_ERROR; goto done; } free(dt_s); } - crm_time_free(later); + free(later); } else if (date_time && options.expected_s) { - char *dt_s = crm_time_as_string(date_time, - options.print_options | crm_time_log_date | crm_time_log_timeofday); + char *dt_s = pcmk__time_text(date_time, + options.print_options + |pcmk__time_fmt_date + |pcmk__time_fmt_time); if (!pcmk__str_eq(options.expected_s, dt_s, pcmk__str_casei)) { exit_code = CRM_EX_ERROR; @@ -439,8 +535,8 @@ main(int argc, char **argv) } done: - crm_time_free(date_time); - crm_time_free(duration); + free(date_time); + free(duration); g_strfreev(processed_args); pcmk__free_arg_context(context);