diff --git a/daemons/attrd/attrd_alerts.c b/daemons/attrd/attrd_alerts.c index 495e18f60a8..005c444051e 100644 --- a/daemons/attrd/attrd_alerts.c +++ b/daemons/attrd/attrd_alerts.c @@ -101,8 +101,8 @@ config_query_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void return; } - pe_free_alert_list(attrd_alert_list); - attrd_alert_list = pe_unpack_alerts(crmalerts); + pe__free_alert_list(attrd_alert_list); + attrd_alert_list = pe__unpack_alerts(crmalerts); } gboolean diff --git a/daemons/controld/controld_alerts.c b/daemons/controld/controld_alerts.c index 27a5ce27589..56d1b7c64f1 100644 --- a/daemons/controld/controld_alerts.c +++ b/daemons/controld/controld_alerts.c @@ -26,8 +26,8 @@ static GList *crmd_alert_list = NULL; void crmd_unpack_alerts(xmlNode *alerts) { - pe_free_alert_list(crmd_alert_list); - crmd_alert_list = pe_unpack_alerts(alerts); + pe__free_alert_list(crmd_alert_list); + crmd_alert_list = pe__unpack_alerts(alerts); } void diff --git a/doc/sphinx/Pacemaker_Explained/rules.rst b/doc/sphinx/Pacemaker_Explained/rules.rst index e9d85e083a1..f0955eeb637 100644 --- a/doc/sphinx/Pacemaker_Explained/rules.rst +++ b/doc/sphinx/Pacemaker_Explained/rules.rst @@ -254,21 +254,27 @@ A ``date_expression`` element may optionally contain a ``date_spec`` or | | pair: start; date_expression | | | | | | A date/time conforming to the | - | | `ISO8601 `_ | + | | `ISO 8601 `_ | | | specification. May be used when ``operation`` is | | | ``in_range`` (in which case at least one of ``start`` or | | | ``end`` must be specified) or ``gt`` (in which case | | | ``start`` is required). | + | | | + | | Parsing of ISO 8601 timezones in date/time strings is | + | | currently incomplete and should not be relied upon. | +---------------+-----------------------------------------------------------+ | end | .. index:: | | | pair: end; date_expression | | | | | | A date/time conforming to the | - | | `ISO8601 `_ | + | | `ISO 8601 `_ | | | specification. May be used when ``operation`` is | | | ``in_range`` (in which case at least one of ``start`` or | | | ``end`` must be specified) or ``lt`` (in which case | | | ``end`` is required). | + | | | + | | Parsing of ISO 8601 timezones in date/time strings is | + | | currently incomplete and should not be relied upon. | +---------------+-----------------------------------------------------------+ | operation | .. index:: | | | pair: operation; date_expression | @@ -288,10 +294,27 @@ A ``date_expression`` element may optionally contain a ``date_spec`` or | | the specification given in the contained ``date_spec`` | | | element (described below) | +---------------+-----------------------------------------------------------+ + | timezone | .. index:: | + | | pair: timezone; date_expression | + | | | + | | A timezone conforming to the format specified in the | + | | ``tzset(3)`` man page. The ``date_expression`` is | + | | evaluated based on the current time in this timezone. | + | | Defaults to the existing value of the ``TZ`` environment | + | | variable if set, or to the system timezone otherwise. As | + | | with ``TZ``, if the value is set but empty or cannot be | + | | interpreted, UTC is used. | + +---------------+-----------------------------------------------------------+ .. note:: There is no ``eq``, ``neq``, ``gte``, or ``lte`` operation, since they would be valid only for a single second. +.. note:: Pacemaker will try to adjust ``start`` and ``end`` to ``timezone`` if + specified, or to the local timezone otherwise, for correct comparison + to the current time. This adjustment takes place after parsing and + applying the timezone (if present) from the ``start`` or ``end`` + string. As noted above, this parsing is incomplete and should not be + relied upon. .. index:: diff --git a/include/crm/common/Makefile.am b/include/crm/common/Makefile.am index f54b30903cf..a77889c43ff 100644 --- a/include/crm/common/Makefile.am +++ b/include/crm/common/Makefile.am @@ -21,6 +21,7 @@ header_HEADERS = acl.h \ ipc_pacemakerd.h \ ipc_schedulerd.h \ iso8601.h \ + iso8601_compat.h \ logging.h \ logging_compat.h \ mainloop.h \ diff --git a/include/crm/common/iso8601.h b/include/crm/common/iso8601.h index 78f530babb0..a0c499363ab 100644 --- a/include/crm/common/iso8601.h +++ b/include/crm/common/iso8601.h @@ -1,5 +1,5 @@ /* - * Copyright 2005-2020 the Pacemaker project contributors + * Copyright 2005-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -85,7 +85,6 @@ 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_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m); 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); @@ -123,6 +122,10 @@ int crm_time_days_in_month(int month, int year); bool crm_time_leapyear(int year); bool crm_time_check(const crm_time_t *dt); +#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) +#include +#endif // !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) + #ifdef __cplusplus } #endif diff --git a/include/crm/common/iso8601_compat.h b/include/crm/common/iso8601_compat.h new file mode 100644 index 00000000000..ce0aef45f05 --- /dev/null +++ b/include/crm/common/iso8601_compat.h @@ -0,0 +1,37 @@ +/* + * Copyright 2005-2023 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#ifndef PCMK__CRM_COMMON_ISO8601_COMPAT__H +#define PCMK__CRM_COMMON_ISO8601_COMPAT__H + +#include // uint32_t + +#include // crm_time_t + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \file + * \brief Deprecated Pacemaker ISO 8601 API + * \ingroup core + * \deprecated Do not include this header directly. The ISO 8601 APIs in this + * header, and the header itself, will be removed in a future + * release. + */ + +//! \deprecated Do not use Pacemaker for general-purpose date and time +int crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m); + +#ifdef __cplusplus +} +#endif + +#endif // PCMK__CRM_COMMON_ISO8601_COMPAT__H diff --git a/include/crm/common/iso8601_internal.h b/include/crm/common/iso8601_internal.h index f924d8a076f..f4b8e970d6b 100644 --- a/include/crm/common/iso8601_internal.h +++ b/include/crm/common/iso8601_internal.h @@ -1,5 +1,5 @@ /* - * Copyright 2015-2022 the Pacemaker project contributors + * Copyright 2015-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -17,6 +17,7 @@ typedef struct pcmk__time_us pcmk__time_hr_t; +void pcmk__time_get_timezone(const crm_time_t *dt, int *hours, int *minutes); pcmk__time_hr_t *pcmk__time_hr_convert(pcmk__time_hr_t *target, const crm_time_t *dt); void pcmk__time_set_hr_dt(crm_time_t *target, const pcmk__time_hr_t *hr_dt); @@ -28,6 +29,7 @@ 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__copy_timet(time_t source); +void pcmk__time_set_timezone(crm_time_t *dt, int hours, int minutes); struct pcmk__time_us { int years; diff --git a/include/crm/crm.h b/include/crm/crm.h index aecfcc8a8c7..9615465a02e 100644 --- a/include/crm/crm.h +++ b/include/crm/crm.h @@ -67,7 +67,7 @@ extern "C" { * >=3.2.0: DC supports PCMK_EXEC_INVALID and PCMK_EXEC_NOT_CONNECTED * >=3.19.0: DC supports PCMK__CIB_REQUEST_COMMIT_TRANSACT */ -# define CRM_FEATURE_SET "3.19.0" +#define CRM_FEATURE_SET "3.19.1" /* Pacemaker's CPG protocols use fixed-width binary fields for the sender and * recipient of a CPG message. This imposes an arbitrary limit on cluster node diff --git a/include/crm/msg_xml.h b/include/crm/msg_xml.h index c61618235da..4649d171492 100644 --- a/include/crm/msg_xml.h +++ b/include/crm/msg_xml.h @@ -56,6 +56,7 @@ extern "C" { #define PCMK_XA_PROMOTED_MAX_LEGACY "master-max" #define PCMK_XA_PROMOTED_NODE_MAX_LEGACY "master-node-max" +#define PCMK_XA_TIME_ZONE "timezone" /* * Meta attributes diff --git a/include/crm/pengine/rules_internal.h b/include/crm/pengine/rules_internal.h index 9b819636e56..c823188a362 100644 --- a/include/crm/pengine/rules_internal.h +++ b/include/crm/pengine/rules_internal.h @@ -1,5 +1,5 @@ /* - * Copyright 2015-2022 the Pacemaker project contributors + * Copyright 2015-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -16,8 +16,8 @@ #include #include -GList *pe_unpack_alerts(const xmlNode *alerts); -void pe_free_alert_list(GList *alert_list); +GList *pe__unpack_alerts(const xmlNode *alerts); +void pe__free_alert_list(GList *alert_list); gboolean pe__eval_attr_expr(const xmlNode *expr, const pe_rule_eval_data_t *rule_data); @@ -31,6 +31,6 @@ gboolean pe__eval_role_expr(const xmlNode *expr, gboolean pe__eval_rsc_expr(const xmlNode *expr, const pe_rule_eval_data_t *rule_data); -int pe_cron_range_satisfied(const crm_time_t *now, const xmlNode *cron_spec); +int pe__cron_range_satisfied(const crm_time_t *now, const xmlNode *cron_spec); #endif diff --git a/lib/common/iso8601.c b/lib/common/iso8601.c index 3e000e1beac..d0b39bbca59 100644 --- a/lib/common/iso8601.c +++ b/lib/common/iso8601.c @@ -1,5 +1,5 @@ /* - * Copyright 2005-2022 the Pacemaker project contributors + * Copyright 2005-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -303,13 +303,71 @@ crm_time_get_timeofday(const crm_time_t *dt, uint32_t *h, uint32_t *m, return TRUE; } -int -crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m) +/*! + * \internal + * \brief Get a date/time object's timezone as offset relative to UTC + * + * \param[in] dt Date/time object whose timezone to get + * \param[out] hours Where to store hours offset from UTC + * \param[out] minutes Where to store remaining minutes offset from UTC + * + * \note If the offset is negative, both \p hours and \p minutes will be set to + * nonpositive values. Make note of this if formatting text. Typically, a + * negative sign should precede only the hours field. + */ +void +pcmk__time_get_timezone(const crm_time_t *dt, int *hours, int *minutes) { - uint32_t s; + // Be paranoid and don't cast (int *) to (uint32_t *) + uint32_t hours_u = 0; + uint32_t minutes_u = 0; + uint32_t seconds_u = 0; - crm_time_get_sec(dt->seconds, h, m, &s); - return TRUE; + CRM_ASSERT((dt != NULL) && (hours != NULL) && (minutes != NULL)); + + crm_time_get_sec(dt->offset, &hours_u, &minutes_u, &seconds_u); + + if (dt->offset < 0) { + *hours = -hours_u; + *minutes = -minutes_u; + } else { + *hours = hours_u; + *minutes = minutes_u; + } +} + +/*! + * \internal + * \brief Set a date/time object's timezone relative to UTC + * + * \param[in,out] dt Date/time object whose timezone to set + * \param[in] hours Hours offset from UTC + * \param[in] minutes Minutes offset from UTC + * + * \note \p hours and \p minutes are summed. If \p hours is negative, then + * \p minutes typically should also be nonpositive, and vice-versa. + */ +void +pcmk__time_set_timezone(crm_time_t *dt, int hours, int minutes) +{ + // Desired offset from UTC in seconds + int seconds = (hours * HOUR_SECONDS) + (minutes * 60); + + // Current offsets from UTC + int hours_current = 0; + int minutes_current = 0; + int seconds_current = 0; + + CRM_ASSERT(dt != NULL); + + pcmk__time_get_timezone(dt, &hours_current, &minutes_current); + seconds_current = (hours_current * HOUR_SECONDS) + (minutes_current * 60); + + // Adjust the object's time + crm_time_add_seconds(dt, seconds - seconds_current); + + // Set the object's timezone + dt->offset = seconds; } long long @@ -1968,3 +2026,20 @@ pcmk__readable_interval(guint interval_ms) } return str; } + +// Deprecated functions kept only for backward API compatibility +// LCOV_EXCL_START + +#include + +int +crm_time_get_timezone(const crm_time_t *dt, uint32_t *h, uint32_t *m) +{ + uint32_t s; + + crm_time_get_sec(dt->offset, h, m, &s); + return TRUE; +} + +// LCOV_EXCL_STOP +// End deprecated API diff --git a/lib/pengine/common.c b/lib/pengine/common.c index e43891f08b6..4745a51fdc9 100644 --- a/lib/pengine/common.c +++ b/lib/pengine/common.c @@ -91,6 +91,9 @@ static pcmk__cluster_option_t pe_opts[] = { "passed since the shutdown was initiated, even if the node has not " "rejoined.") }, + { + PCMK_XA_TIME_ZONE, NULL, "string", NULL, + }, // Fencing-related options { diff --git a/lib/pengine/rules.c b/lib/pengine/rules.c index 50f9f64b4aa..ea5164331f5 100644 --- a/lib/pengine/rules.c +++ b/lib/pengine/rules.c @@ -234,7 +234,7 @@ check_passes(int rc) { } while (0) int -pe_cron_range_satisfied(const crm_time_t *now, const xmlNode *cron_spec) +pe__cron_range_satisfied(const crm_time_t *now, const xmlNode *cron_spec) { uint32_t h, m, s, y, d, w; @@ -1083,7 +1083,7 @@ pe__eval_date_expr(const xmlNode *expr, const pe_rule_eval_data_t *rule_data, } } else if (pcmk__str_eq(op, "date_spec", pcmk__str_casei)) { - rc = pe_cron_range_satisfied(rule_data->now, date_spec); + rc = pe__cron_range_satisfied(rule_data->now, date_spec); // @TODO set next_change appropriately } else if (pcmk__str_eq(op, "gt", pcmk__str_casei)) { diff --git a/lib/pengine/rules_alerts.c b/lib/pengine/rules_alerts.c index 9eed7ff890f..4659643a032 100644 --- a/lib/pengine/rules_alerts.c +++ b/lib/pengine/rules_alerts.c @@ -205,7 +205,7 @@ unpack_alert(xmlNode *alert, pcmk__alert_t *entry, guint *max_timeout) * but is supplied for use by daemons that need to send alerts. */ GList * -pe_unpack_alerts(const xmlNode *alerts) +pe__unpack_alerts(const xmlNode *alerts) { xmlNode *alert; pcmk__alert_t *entry; @@ -281,12 +281,12 @@ pe_unpack_alerts(const xmlNode *alerts) /*! * \internal - * \brief Free an alert list generated by pe_unpack_alerts() + * \brief Free an alert list generated by pe__unpack_alerts() * * \param[in,out] alert_list Alert list to free */ void -pe_free_alert_list(GList *alert_list) +pe__free_alert_list(GList *alert_list) { if (alert_list) { g_list_free_full(alert_list, (GDestroyNotify) pcmk__free_alert); diff --git a/lib/pengine/tests/rules/Makefile.am b/lib/pengine/tests/rules/Makefile.am index 261ec16e677..52a85f09862 100644 --- a/lib/pengine/tests/rules/Makefile.am +++ b/lib/pengine/tests/rules/Makefile.am @@ -13,6 +13,6 @@ include $(top_srcdir)/mk/unittest.mk LDADD += $(top_builddir)/lib/pengine/libpe_rules_test.la # Add "_test" to the end of all test program names to simplify .gitignore. -check_PROGRAMS = pe_cron_range_satisfied_test +check_PROGRAMS = pe__cron_range_satisfied_test TESTS = $(check_PROGRAMS) diff --git a/lib/pengine/tests/rules/pe_cron_range_satisfied_test.c b/lib/pengine/tests/rules/pe__cron_range_satisfied_test.c similarity index 94% rename from lib/pengine/tests/rules/pe_cron_range_satisfied_test.c rename to lib/pengine/tests/rules/pe__cron_range_satisfied_test.c index a8ba6cfe56b..7fe2ff12192 100644 --- a/lib/pengine/tests/rules/pe_cron_range_satisfied_test.c +++ b/lib/pengine/tests/rules/pe__cron_range_satisfied_test.c @@ -1,5 +1,5 @@ /* - * Copyright 2020-2022 the Pacemaker project contributors + * Copyright 2020-2023 the Pacemaker project contributors * * The version control history for this file may have further details. * @@ -21,7 +21,7 @@ run_one_test(const char *t, const char *x, int expected) { crm_time_t *tm = crm_time_new(t); xmlNodePtr xml = string2xml(x); - assert_int_equal(pe_cron_range_satisfied(tm, xml), expected); + assert_int_equal(pe__cron_range_satisfied(tm, xml), expected); crm_time_free(tm); free_xml(xml); @@ -29,14 +29,15 @@ run_one_test(const char *t, const char *x, int expected) { static void no_time_given(void **state) { - assert_int_equal(pe_cron_range_satisfied(NULL, NULL), pcmk_rc_op_unsatisfied); + assert_int_equal(pe__cron_range_satisfied(NULL, NULL), + pcmk_rc_op_unsatisfied); } static void any_time_satisfies_empty_spec(void **state) { crm_time_t *tm = crm_time_new(NULL); - assert_int_equal(pe_cron_range_satisfied(tm, NULL), pcmk_rc_ok); + assert_int_equal(pe__cron_range_satisfied(tm, NULL), pcmk_rc_ok); crm_time_free(tm); }