Skip to content

Commit c4e8f14

Browse files
committed
Refactor: libcrmcommon: New pcmk__time_parse_duration()
To replace crm_time_parse_duration(). Signed-off-by: Reid Wahl <nrwahl@protonmail.com>
1 parent dede932 commit c4e8f14

10 files changed

Lines changed: 148 additions & 126 deletions

File tree

cts/cli/regression.dates.exp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ iso8601: Invalid interval specified: P1Y/2019-02-29 00:00:00Z
6464
=#=#=#= End test: Invalid period - [P1Y/2019-02-29 00:00:00Z] - Invalid parameter (2) =#=#=#=
6565
* Passed: iso8601 - Invalid period - [P1Y/2019-02-29 00:00:00Z]
6666
=#=#=#= Begin test: Invalid period - [2019-01-01 00:00:00Z/P] =#=#=#=
67-
crm_time_parse_duration error: 'P' is not a valid ISO 8601 time duration because nothing follows 'P'
67+
pcmk__time_parse_duration error: 'P' is not a valid ISO 8601 time duration because nothing follows 'P'
6868
iso8601: Invalid interval specified: 2019-01-01 00:00:00Z/P
6969
=#=#=#= End test: Invalid period - [2019-01-01 00:00:00Z/P] - Invalid parameter (2) =#=#=#=
7070
* Passed: iso8601 - Invalid period - [2019-01-01 00:00:00Z/P]

cts/cts-cli.in

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -251,7 +251,6 @@ def sanitize_output(s):
251251
(r'(<cib.*) cib-last-written="[^"]*"', r'\1'),
252252
(r'crm_feature_set="[^"]*" ', r''),
253253
(r'@crm_feature_set=[0-9.]+, ', r''),
254-
(r'\(crm_time_parse_duration@.*\.c:[0-9]+\)', r'crm_time_parse_duration'),
255254
(r'\(parse_hms@.*\.c:[0-9]+\)', r'parse_hms'),
256255
(re.escape(cts_cli_data), r'CTS_CLI_DATA'),
257256
(r' default="[^"]*"', r' default=""'),

include/crm/common/iso8601_internal.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ char *pcmk__time_format_hr(const char *format, const crm_time_t *dt, int usec);
3434
char *pcmk__epoch2str(const time_t *source, uint32_t flags);
3535
char *pcmk__timespec2str(const struct timespec *ts, uint32_t flags);
3636
const char *pcmk__readable_interval(guint interval_ms);
37+
38+
crm_time_t *pcmk__time_parse_duration(const char *period_s);
3739
crm_time_t *pcmk__copy_timet(time_t source_sec);
3840

3941
void pcmk__time_log_as(const char *file, const char *function, int line,

lib/common/iso8601.c

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1282,18 +1282,19 @@ parse_duration_element(const char **element, const char *duration_s,
12821282
}
12831283

12841284
/*!
1285+
* \internal
12851286
* \brief Parse a time duration from an ISO 8601 duration specification
12861287
*
12871288
* \param[in] period_s ISO 8601 duration specification (optionally followed by
12881289
* whitespace, after which the rest of the string will be
12891290
* ignored)
12901291
*
1291-
* \return New time object on success, NULL (and set errno) otherwise
1292-
* \note It is the caller's responsibility to return the result using
1293-
* crm_time_free().
1292+
* \return New time object on success, or \cNULL (and set \c errno) otherwise
1293+
* \note It is the caller's responsibility to free the result using
1294+
* \c crm_time_free().
12941295
*/
12951296
crm_time_t *
1296-
crm_time_parse_duration(const char *period_s)
1297+
pcmk__time_parse_duration(const char *period_s)
12971298
{
12981299
bool is_time = false;
12991300
crm_time_t *diff = NULL;
@@ -1344,15 +1345,35 @@ crm_time_parse_duration(const char *period_s)
13441345
goto invalid;
13451346
}
13461347

1347-
diff->duration = TRUE;
1348+
diff->duration = true;
13481349
return diff;
13491350

13501351
invalid:
1352+
/* @COMPAT Setting errno is required only for backward compatibility with
1353+
* crm_time_parse_duration()
1354+
*/
13511355
crm_time_free(diff);
13521356
errno = EINVAL;
13531357
return NULL;
13541358
}
13551359

1360+
/*!
1361+
* \brief Parse a time duration from an ISO 8601 duration specification
1362+
*
1363+
* \param[in] period_s ISO 8601 duration specification (optionally followed by
1364+
* whitespace, after which the rest of the string will be
1365+
* ignored)
1366+
*
1367+
* \return New time object on success, NULL (and set errno) otherwise
1368+
* \note It is the caller's responsibility to return the result using
1369+
* crm_time_free().
1370+
*/
1371+
crm_time_t *
1372+
crm_time_parse_duration(const char *period_s)
1373+
{
1374+
return pcmk__time_parse_duration(period_s);
1375+
}
1376+
13561377
/*!
13571378
* \internal
13581379
* \brief Set one time object to another if the other is earlier
@@ -2329,7 +2350,7 @@ crm_time_parse_period(const char *period_str)
23292350
period = pcmk__assert_alloc(1, sizeof(crm_time_period_t));
23302351

23312352
if (period_str[0] == 'P') {
2332-
period->diff = crm_time_parse_duration(period_str);
2353+
period->diff = pcmk__time_parse_duration(period_str);
23332354
if (period->diff == NULL) {
23342355
goto invalid;
23352356
}
@@ -2350,7 +2371,7 @@ crm_time_parse_period(const char *period_str)
23502371
original);
23512372
goto invalid;
23522373
}
2353-
period->diff = crm_time_parse_duration(period_str);
2374+
period->diff = pcmk__time_parse_duration(period_str);
23542375
if (period->diff == NULL) {
23552376
goto invalid;
23562377
}

lib/common/strings.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2004-2025 the Pacemaker project contributors
2+
* Copyright 2004-2026 the Pacemaker project contributors
33
*
44
* The version control history for this file may have further details.
55
*
@@ -372,7 +372,7 @@ pcmk_parse_interval_spec(const char *input, guint *result_ms)
372372
}
373373

374374
if (input[0] == 'P') {
375-
crm_time_t *period_s = crm_time_parse_duration(input);
375+
crm_time_t *period_s = pcmk__time_parse_duration(input);
376376

377377
if (period_s != NULL) {
378378
msec = crm_time_get_seconds(period_s);

lib/common/tests/iso8601/Makefile.am

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@ include $(top_srcdir)/mk/unittest.mk
1515
check_PROGRAMS = crm_time_add_days_test
1616
check_PROGRAMS += crm_time_add_seconds_test
1717
check_PROGRAMS += crm_time_add_years_test
18-
check_PROGRAMS += crm_time_parse_duration_test
1918
check_PROGRAMS += pcmk__add_time_from_xml_test
2019
check_PROGRAMS += pcmk__readable_interval_test
2120
check_PROGRAMS += pcmk__set_time_if_earlier_test
2221
check_PROGRAMS += pcmk__time_format_hr_test
22+
check_PROGRAMS += pcmk__time_parse_duration_test
2323

2424
TESTS = $(check_PROGRAMS)

lib/common/tests/iso8601/crm_time_parse_duration_test.c

Lines changed: 0 additions & 110 deletions
This file was deleted.
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright 2024-2026 the Pacemaker project contributors
3+
*
4+
* The version control history for this file may have further details.
5+
*
6+
* This source code is licensed under the GNU General Public License version 2
7+
* or later (GPLv2+) WITHOUT ANY WARRANTY.
8+
*/
9+
10+
#include <crm_internal.h>
11+
12+
#include <crm/common/unittest_internal.h>
13+
14+
#include <crm/common/iso8601.h>
15+
#include "crmcommon_private.h"
16+
17+
static void
18+
empty_arg(void **state)
19+
{
20+
assert_null(pcmk__time_parse_duration(NULL));
21+
assert_null(pcmk__time_parse_duration(""));
22+
}
23+
24+
static void
25+
invalid_arg(void **state)
26+
{
27+
// Valid except doesn't start with P
28+
assert_null(pcmk__time_parse_duration("X3Y6M4DT12H30M5S"));
29+
30+
// Illegal character after P
31+
assert_null(pcmk__time_parse_duration("P"));
32+
assert_null(pcmk__time_parse_duration("P 3Y6M4DT12H30M5S"));
33+
assert_null(pcmk__time_parse_duration("PX3Y6M4DT12H30M5S"));
34+
35+
// Missing or invalid units
36+
assert_null(pcmk__time_parse_duration("P3Y6M4DT12H30M5"));
37+
assert_null(pcmk__time_parse_duration("P3Y6M4DT12H30M5X"));
38+
assert_null(pcmk__time_parse_duration("P3X6M4DT12H30M5S"));
39+
assert_null(pcmk__time_parse_duration("PT"));
40+
assert_null(pcmk__time_parse_duration("P/"));
41+
42+
#if 0
43+
// @TODO The current implementation treats these as valid
44+
45+
// Units out of order
46+
assert_null(pcmk__time_parse_duration("P6M3Y4DT12H30M5S"));
47+
assert_null(pcmk__time_parse_duration("P6M3DT12HY430M5S"));
48+
49+
// Same unit specified multiple times
50+
assert_null(pcmk__time_parse_duration("P6Y4M3D1MT12H30M5S"));
51+
52+
// Weeks mixed with other units
53+
assert_null(pcmk__time_parse_duration("P6Y4M3W3D1MT12H30M5S"));
54+
assert_null(pcmk__time_parse_duration("P3WT12H30M5S"));
55+
#endif
56+
}
57+
58+
static void
59+
overflow(void **state)
60+
{
61+
// Too large
62+
assert_null(pcmk__time_parse_duration("P2147483648Y6M4DT12H30M5S"));
63+
assert_null(pcmk__time_parse_duration("P3Y2147483648M4DT12H30M5S"));
64+
assert_null(pcmk__time_parse_duration("P3Y6M2147483648DT12H30M5S"));
65+
assert_null(pcmk__time_parse_duration("P3Y6M4DT2147483648H30M5S"));
66+
assert_null(pcmk__time_parse_duration("P3Y6M4DT12H2147483648M5S"));
67+
assert_null(pcmk__time_parse_duration("P3Y6M4DT12H30MP2147483648S"));
68+
69+
// Too small
70+
assert_null(pcmk__time_parse_duration("P-2147483648Y6M4DT12H30M5S"));
71+
assert_null(pcmk__time_parse_duration("P3Y-2147483648M4DT12H30M5S"));
72+
assert_null(pcmk__time_parse_duration("P3Y6M-2147483648DT12H30M5S"));
73+
assert_null(pcmk__time_parse_duration("P3Y6M4DT-2147483648H30M5S"));
74+
assert_null(pcmk__time_parse_duration("P3Y6M4DT12H-2147483648M5S"));
75+
assert_null(pcmk__time_parse_duration("P3Y6M4DT12H30MP-2147483648S"));
76+
}
77+
78+
static void
79+
valid_arg(void **state)
80+
{
81+
// @TODO Check result value
82+
assert_non_null(pcmk__time_parse_duration("P3Y6M4DT12H30M5S"));
83+
assert_non_null(pcmk__time_parse_duration("P3Y6M4DT12H30M-5S"));
84+
assert_non_null(pcmk__time_parse_duration("P3Y6M4DT12H-30M5S"));
85+
assert_non_null(pcmk__time_parse_duration("P3Y6M4DT-12H30M5S"));
86+
assert_non_null(pcmk__time_parse_duration("P3Y6M-4DT12H30M5S"));
87+
assert_non_null(pcmk__time_parse_duration("P3Y-6M4DT12H30M5S"));
88+
assert_non_null(pcmk__time_parse_duration("P3Y6M4DT12H30M"));
89+
assert_non_null(pcmk__time_parse_duration("P3Y6M4D"));
90+
assert_non_null(pcmk__time_parse_duration("P1M")); // 1 month
91+
assert_non_null(pcmk__time_parse_duration("PT1M")); // 1 minute
92+
assert_non_null(pcmk__time_parse_duration("P7W"));
93+
94+
#if 0
95+
// @TODO Current implementation can't handle these cases
96+
97+
// Fractional value for last unit
98+
assert_non_null(pcmk__time_parse_duration("P3Y6M4DT12H30.5M"));
99+
assert_non_null(pcmk__time_parse_duration("P3Y6M4DT12H30,5M"));
100+
101+
// P<YYYY>-<MM>-<DD>T<hh>:<mm>:<ss> format
102+
assert_non_null(pcmk__time_parse_duration("P0003-02-01T11:10:09");
103+
#endif
104+
}
105+
106+
PCMK__UNIT_TEST(NULL, NULL,
107+
cmocka_unit_test(empty_arg),
108+
cmocka_unit_test(invalid_arg),
109+
cmocka_unit_test(overflow),
110+
cmocka_unit_test(valid_arg));

tools/crm_resource_ban.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ parse_cli_lifetime(pcmk__output_t *out, const char *move_lifetime)
3131
return NULL;
3232
}
3333

34-
duration = crm_time_parse_duration(move_lifetime);
34+
duration = pcmk__time_parse_duration(move_lifetime);
3535
if (duration == NULL) {
3636
out->err(out, "Invalid duration specified: %s\n"
3737
"Please refer to https://en.wikipedia.org/wiki/ISO_8601#Durations "

tools/iso8601.c

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -295,7 +295,7 @@ parse_period(const char *period_str, crm_time_t **start, crm_time_t **end)
295295
tzset();
296296

297297
if (period_str[0] == 'P') {
298-
diff = crm_time_parse_duration(period_str);
298+
diff = pcmk__time_parse_duration(period_str);
299299
if (diff == NULL) {
300300
goto invalid;
301301
}
@@ -315,7 +315,7 @@ parse_period(const char *period_str, crm_time_t **start, crm_time_t **end)
315315
"has two durations", original);
316316
goto invalid;
317317
}
318-
diff = crm_time_parse_duration(period_str);
318+
diff = pcmk__time_parse_duration(period_str);
319319
if (diff == NULL) {
320320
goto invalid;
321321
}
@@ -467,7 +467,7 @@ main(int argc, char **argv)
467467
}
468468

469469
if (options.duration_s) {
470-
duration = crm_time_parse_duration(options.duration_s);
470+
duration = pcmk__time_parse_duration(options.duration_s);
471471

472472
if (duration == NULL) {
473473
exit_code = CRM_EX_INVALID_PARAM;

0 commit comments

Comments
 (0)