2017-11-18 17:09:20 +01:00
|
|
|
/* SPDX-License-Identifier: LGPL-2.1+ */
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2015-11-30 21:43:37 +01:00
|
|
|
#include <alloca.h>
|
2016-11-24 18:47:55 +01:00
|
|
|
#include <ctype.h>
|
2015-11-30 21:43:37 +01:00
|
|
|
#include <errno.h>
|
2016-12-20 22:42:12 +01:00
|
|
|
#include <limits.h>
|
2015-11-30 21:43:37 +01:00
|
|
|
#include <stddef.h>
|
|
|
|
#include <stdio.h>
|
2017-12-11 19:50:30 +01:00
|
|
|
#include <stdio_ext.h>
|
2012-11-23 21:37:58 +01:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
2017-09-06 11:56:36 +02:00
|
|
|
#include <sys/mman.h>
|
2015-11-30 21:43:37 +01:00
|
|
|
#include <time.h>
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2015-10-27 03:01:06 +01:00
|
|
|
#include "alloc-util.h"
|
2012-11-23 21:37:58 +01:00
|
|
|
#include "calendarspec.h"
|
2019-03-14 12:24:39 +01:00
|
|
|
#include "errno-util.h"
|
2015-10-26 18:05:03 +01:00
|
|
|
#include "fileio.h"
|
2015-11-30 21:43:37 +01:00
|
|
|
#include "macro.h"
|
2015-12-01 23:22:03 +01:00
|
|
|
#include "parse-util.h"
|
2018-01-11 00:39:12 +01:00
|
|
|
#include "process-util.h"
|
2019-03-13 12:14:47 +01:00
|
|
|
#include "sort-util.h"
|
2015-11-16 22:09:36 +01:00
|
|
|
#include "string-util.h"
|
2017-09-06 11:56:36 +02:00
|
|
|
#include "time-util.h"
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2016-12-16 02:02:10 +01:00
|
|
|
#define BITS_WEEKDAYS 127
|
|
|
|
#define MIN_YEAR 1970
|
|
|
|
#define MAX_YEAR 2199
|
2014-10-30 12:19:14 +01:00
|
|
|
|
2018-03-15 10:12:48 +01:00
|
|
|
/* An arbitrary limit on the length of the chains of components. We don't want to
|
|
|
|
* build a very long linked list, which would be slow to iterate over and might cause
|
|
|
|
* our stack to overflow. It's unlikely that legitimate uses require more than a few
|
|
|
|
* linked compenents anyway. */
|
|
|
|
#define CALENDARSPEC_COMPONENTS_MAX 240
|
|
|
|
|
2019-04-07 17:21:37 +02:00
|
|
|
static void chain_free(CalendarComponent *c) {
|
2012-11-23 21:37:58 +01:00
|
|
|
CalendarComponent *n;
|
|
|
|
|
|
|
|
while (c) {
|
|
|
|
n = c->next;
|
|
|
|
free(c);
|
|
|
|
c = n;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-11-17 10:32:48 +01:00
|
|
|
CalendarSpec* calendar_spec_free(CalendarSpec *c) {
|
2014-10-24 18:33:29 +02:00
|
|
|
|
|
|
|
if (!c)
|
2017-11-17 10:32:48 +01:00
|
|
|
return NULL;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2019-04-07 17:21:37 +02:00
|
|
|
chain_free(c->year);
|
|
|
|
chain_free(c->month);
|
|
|
|
chain_free(c->day);
|
|
|
|
chain_free(c->hour);
|
|
|
|
chain_free(c->minute);
|
|
|
|
chain_free(c->microsecond);
|
2017-09-06 11:56:36 +02:00
|
|
|
free(c->timezone);
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2017-11-17 10:32:48 +01:00
|
|
|
return mfree(c);
|
2012-11-23 21:37:58 +01:00
|
|
|
}
|
|
|
|
|
2018-09-18 01:39:24 +02:00
|
|
|
static int component_compare(CalendarComponent * const *a, CalendarComponent * const *b) {
|
|
|
|
int r;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2018-09-18 01:39:24 +02:00
|
|
|
r = CMP((*a)->start, (*b)->start);
|
|
|
|
if (r != 0)
|
|
|
|
return r;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2018-09-18 01:39:24 +02:00
|
|
|
r = CMP((*a)->stop, (*b)->stop);
|
|
|
|
if (r != 0)
|
|
|
|
return r;
|
2016-12-16 02:02:10 +01:00
|
|
|
|
2018-09-18 01:39:24 +02:00
|
|
|
return CMP((*a)->repeat, (*b)->repeat);
|
2012-11-23 21:37:58 +01:00
|
|
|
}
|
|
|
|
|
2016-12-16 02:02:10 +01:00
|
|
|
static void normalize_chain(CalendarComponent **c) {
|
2012-11-23 21:37:58 +01:00
|
|
|
CalendarComponent **b, *i, **j, *next;
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
size_t n = 0, k;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
assert(c);
|
|
|
|
|
2016-12-16 02:02:10 +01:00
|
|
|
for (i = *c; i; i = i->next) {
|
2012-11-23 21:37:58 +01:00
|
|
|
n++;
|
|
|
|
|
2016-12-16 02:02:10 +01:00
|
|
|
/*
|
2016-12-16 21:49:54 +01:00
|
|
|
* While we're counting the chain, also normalize `stop`
|
2016-12-16 02:02:10 +01:00
|
|
|
* so the length of the range is a multiple of `repeat`
|
|
|
|
*/
|
2016-12-20 22:42:12 +01:00
|
|
|
if (i->stop > i->start && i->repeat > 0)
|
2016-12-16 21:49:54 +01:00
|
|
|
i->stop -= (i->stop - i->start) % i->repeat;
|
2016-12-16 02:02:10 +01:00
|
|
|
|
|
|
|
}
|
|
|
|
|
2012-11-23 21:37:58 +01:00
|
|
|
if (n <= 1)
|
|
|
|
return;
|
|
|
|
|
2018-04-27 14:28:35 +02:00
|
|
|
j = b = newa(CalendarComponent*, n);
|
2012-11-23 21:37:58 +01:00
|
|
|
for (i = *c; i; i = i->next)
|
|
|
|
*(j++) = i;
|
|
|
|
|
2018-09-18 01:39:24 +02:00
|
|
|
typesafe_qsort(b, n, component_compare);
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
b[n-1]->next = NULL;
|
|
|
|
next = b[n-1];
|
|
|
|
|
|
|
|
/* Drop non-unique entries */
|
|
|
|
for (k = n-1; k > 0; k--) {
|
2017-02-12 06:39:17 +01:00
|
|
|
if (component_compare(&b[k-1], &next) == 0) {
|
2012-11-23 21:37:58 +01:00
|
|
|
free(b[k-1]);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
b[k-1]->next = next;
|
|
|
|
next = b[k-1];
|
|
|
|
}
|
|
|
|
|
|
|
|
*c = next;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void fix_year(CalendarComponent *c) {
|
|
|
|
/* Turns 12 → 2012, 89 → 1989 */
|
|
|
|
|
2016-02-23 18:52:52 +01:00
|
|
|
while (c) {
|
2016-12-16 21:49:54 +01:00
|
|
|
if (c->start >= 0 && c->start < 70)
|
|
|
|
c->start += 2000;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2016-12-16 21:49:54 +01:00
|
|
|
if (c->stop >= 0 && c->stop < 70)
|
|
|
|
c->stop += 2000;
|
2016-12-16 02:02:10 +01:00
|
|
|
|
2016-12-16 21:49:54 +01:00
|
|
|
if (c->start >= 70 && c->start < 100)
|
|
|
|
c->start += 1900;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2016-12-16 21:49:54 +01:00
|
|
|
if (c->stop >= 70 && c->stop < 100)
|
|
|
|
c->stop += 1900;
|
2016-12-16 02:02:10 +01:00
|
|
|
|
2016-12-16 18:36:15 +01:00
|
|
|
c = c->next;
|
2012-11-23 21:37:58 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int calendar_spec_normalize(CalendarSpec *c) {
|
|
|
|
assert(c);
|
|
|
|
|
2017-12-30 14:07:33 +01:00
|
|
|
if (streq_ptr(c->timezone, "UTC")) {
|
|
|
|
c->utc = true;
|
|
|
|
c->timezone = mfree(c->timezone);
|
|
|
|
}
|
|
|
|
|
2014-10-30 12:19:14 +01:00
|
|
|
if (c->weekdays_bits <= 0 || c->weekdays_bits >= BITS_WEEKDAYS)
|
2012-11-23 21:37:58 +01:00
|
|
|
c->weekdays_bits = -1;
|
|
|
|
|
2016-11-22 16:05:10 +01:00
|
|
|
if (c->end_of_month && !c->day)
|
|
|
|
c->end_of_month = false;
|
|
|
|
|
2012-11-23 21:37:58 +01:00
|
|
|
fix_year(c->year);
|
|
|
|
|
2016-12-16 02:02:10 +01:00
|
|
|
normalize_chain(&c->year);
|
|
|
|
normalize_chain(&c->month);
|
|
|
|
normalize_chain(&c->day);
|
|
|
|
normalize_chain(&c->hour);
|
|
|
|
normalize_chain(&c->minute);
|
|
|
|
normalize_chain(&c->microsecond);
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-11-24 23:47:25 +01:00
|
|
|
_pure_ static bool chain_valid(CalendarComponent *c, int from, int to, bool end_of_month) {
|
2018-03-19 09:21:02 +01:00
|
|
|
assert(to >= from);
|
|
|
|
|
2012-11-23 21:37:58 +01:00
|
|
|
if (!c)
|
|
|
|
return true;
|
|
|
|
|
2016-12-16 02:02:10 +01:00
|
|
|
/* Forbid dates more than 28 days from the end of the month */
|
|
|
|
if (end_of_month)
|
|
|
|
to -= 3;
|
|
|
|
|
2016-12-16 21:49:54 +01:00
|
|
|
if (c->start < from || c->start > to)
|
2012-11-23 21:37:58 +01:00
|
|
|
return false;
|
|
|
|
|
2018-03-19 09:21:02 +01:00
|
|
|
/* Avoid overly large values that could cause overflow */
|
|
|
|
if (c->repeat > to - from)
|
|
|
|
return false;
|
|
|
|
|
2016-11-22 16:05:10 +01:00
|
|
|
/*
|
|
|
|
* c->repeat must be short enough so at least one repetition may
|
|
|
|
* occur before the end of the interval. For dates scheduled
|
2016-12-16 21:49:54 +01:00
|
|
|
* relative to the end of the month, c->start and c->stop
|
2016-12-16 02:02:10 +01:00
|
|
|
* correspond to the Nth last day of the month.
|
2016-11-22 16:05:10 +01:00
|
|
|
*/
|
2016-12-16 21:49:54 +01:00
|
|
|
if (c->stop >= 0) {
|
|
|
|
if (c->stop < from || c ->stop > to)
|
2016-12-16 02:02:10 +01:00
|
|
|
return false;
|
2016-11-22 16:05:10 +01:00
|
|
|
|
2016-12-16 21:49:54 +01:00
|
|
|
if (c->start + c->repeat > c->stop)
|
2016-12-16 02:02:10 +01:00
|
|
|
return false;
|
|
|
|
} else {
|
2016-12-16 21:49:54 +01:00
|
|
|
if (end_of_month && c->start - c->repeat < from)
|
2016-12-16 02:02:10 +01:00
|
|
|
return false;
|
|
|
|
|
2016-12-16 21:49:54 +01:00
|
|
|
if (!end_of_month && c->start + c->repeat > to)
|
2016-12-16 02:02:10 +01:00
|
|
|
return false;
|
|
|
|
}
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
if (c->next)
|
2016-11-24 23:47:25 +01:00
|
|
|
return chain_valid(c->next, from, to, end_of_month);
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2013-05-03 04:51:50 +02:00
|
|
|
_pure_ bool calendar_spec_valid(CalendarSpec *c) {
|
2012-11-23 21:37:58 +01:00
|
|
|
assert(c);
|
|
|
|
|
2014-10-30 12:19:14 +01:00
|
|
|
if (c->weekdays_bits > BITS_WEEKDAYS)
|
2012-11-23 21:37:58 +01:00
|
|
|
return false;
|
|
|
|
|
2016-11-22 16:05:10 +01:00
|
|
|
if (!chain_valid(c->year, MIN_YEAR, MAX_YEAR, false))
|
2012-11-23 21:37:58 +01:00
|
|
|
return false;
|
|
|
|
|
2016-11-22 16:05:10 +01:00
|
|
|
if (!chain_valid(c->month, 1, 12, false))
|
2012-11-23 21:37:58 +01:00
|
|
|
return false;
|
|
|
|
|
2016-11-22 16:05:10 +01:00
|
|
|
if (!chain_valid(c->day, 1, 31, c->end_of_month))
|
2012-11-23 21:37:58 +01:00
|
|
|
return false;
|
|
|
|
|
2016-11-22 16:05:10 +01:00
|
|
|
if (!chain_valid(c->hour, 0, 23, false))
|
2012-11-23 21:37:58 +01:00
|
|
|
return false;
|
|
|
|
|
2016-11-22 16:05:10 +01:00
|
|
|
if (!chain_valid(c->minute, 0, 59, false))
|
2012-11-23 21:37:58 +01:00
|
|
|
return false;
|
|
|
|
|
2016-11-22 16:05:10 +01:00
|
|
|
if (!chain_valid(c->microsecond, 0, 60*USEC_PER_SEC-1, false))
|
2012-11-23 21:37:58 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void format_weekdays(FILE *f, const CalendarSpec *c) {
|
|
|
|
static const char *const days[] = {
|
|
|
|
"Mon",
|
|
|
|
"Tue",
|
|
|
|
"Wed",
|
|
|
|
"Thu",
|
|
|
|
"Fri",
|
|
|
|
"Sat",
|
|
|
|
"Sun"
|
|
|
|
};
|
|
|
|
|
|
|
|
int l, x;
|
2016-07-01 04:26:07 +02:00
|
|
|
bool need_comma = false;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
assert(f);
|
|
|
|
assert(c);
|
2014-10-30 12:19:14 +01:00
|
|
|
assert(c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS);
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) {
|
|
|
|
|
|
|
|
if (c->weekdays_bits & (1 << x)) {
|
|
|
|
|
|
|
|
if (l < 0) {
|
2016-07-01 04:26:07 +02:00
|
|
|
if (need_comma)
|
2017-12-11 19:50:30 +01:00
|
|
|
fputc(',', f);
|
2012-11-23 21:37:58 +01:00
|
|
|
else
|
2016-07-01 04:26:07 +02:00
|
|
|
need_comma = true;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2017-12-11 19:50:30 +01:00
|
|
|
fputs(days[x], f);
|
2012-11-23 21:37:58 +01:00
|
|
|
l = x;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (l >= 0) {
|
|
|
|
|
|
|
|
if (x > l + 1) {
|
2017-12-11 19:50:30 +01:00
|
|
|
fputs(x > l + 2 ? ".." : ",", f);
|
|
|
|
fputs(days[x-1], f);
|
2012-11-23 21:37:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
l = -1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (l >= 0 && x > l + 1) {
|
2017-12-11 19:50:30 +01:00
|
|
|
fputs(x > l + 2 ? ".." : ",", f);
|
|
|
|
fputs(days[x-1], f);
|
2012-11-23 21:37:58 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-11-16 09:15:05 +01:00
|
|
|
static void format_chain(FILE *f, int space, const CalendarComponent *c, bool usec) {
|
2016-11-25 03:44:14 +01:00
|
|
|
int d = usec ? (int) USEC_PER_SEC : 1;
|
2016-11-24 21:41:04 +01:00
|
|
|
|
2012-11-23 21:37:58 +01:00
|
|
|
assert(f);
|
|
|
|
|
|
|
|
if (!c) {
|
2017-12-11 19:50:30 +01:00
|
|
|
fputc('*', f);
|
2012-11-23 21:37:58 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-12-16 18:36:15 +01:00
|
|
|
if (usec && c->start == 0 && c->repeat == USEC_PER_SEC && !c->next) {
|
2017-12-11 19:50:30 +01:00
|
|
|
fputc('*', f);
|
2016-12-16 18:36:15 +01:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2016-12-16 21:49:54 +01:00
|
|
|
assert(c->start >= 0);
|
2016-11-25 03:44:14 +01:00
|
|
|
|
2016-12-16 21:49:54 +01:00
|
|
|
fprintf(f, "%0*i", space, c->start / d);
|
|
|
|
if (c->start % d > 0)
|
|
|
|
fprintf(f, ".%06i", c->start % d);
|
2016-11-25 03:44:14 +01:00
|
|
|
|
2016-12-16 21:49:54 +01:00
|
|
|
if (c->stop > 0)
|
|
|
|
fprintf(f, "..%0*i", space, c->stop / d);
|
|
|
|
if (c->stop % d > 0)
|
|
|
|
fprintf(f, ".%06i", c->stop % d);
|
2016-12-16 02:02:10 +01:00
|
|
|
|
2016-12-16 21:49:54 +01:00
|
|
|
if (c->repeat > 0 && !(c->stop > 0 && c->repeat == d))
|
2016-11-25 03:44:14 +01:00
|
|
|
fprintf(f, "/%i", c->repeat / d);
|
2016-12-16 02:02:10 +01:00
|
|
|
if (c->repeat % d > 0)
|
2016-11-25 03:44:14 +01:00
|
|
|
fprintf(f, ".%06i", c->repeat % d);
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2016-12-16 02:02:10 +01:00
|
|
|
if (c->next) {
|
2017-12-11 19:50:30 +01:00
|
|
|
fputc(',', f);
|
2015-11-16 09:15:05 +01:00
|
|
|
format_chain(f, space, c->next, usec);
|
2012-11-23 21:37:58 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
int calendar_spec_to_string(const CalendarSpec *c, char **p) {
|
|
|
|
char *buf = NULL;
|
|
|
|
size_t sz = 0;
|
|
|
|
FILE *f;
|
2015-07-29 20:31:07 +02:00
|
|
|
int r;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
assert(c);
|
|
|
|
assert(p);
|
|
|
|
|
|
|
|
f = open_memstream(&buf, &sz);
|
|
|
|
if (!f)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2017-12-11 19:50:30 +01:00
|
|
|
(void) __fsetlocking(f, FSETLOCKING_BYCALLER);
|
|
|
|
|
2014-10-30 12:19:14 +01:00
|
|
|
if (c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS) {
|
2012-11-23 21:37:58 +01:00
|
|
|
format_weekdays(f, c);
|
2017-12-11 19:50:30 +01:00
|
|
|
fputc(' ', f);
|
2012-11-23 21:37:58 +01:00
|
|
|
}
|
|
|
|
|
2015-11-16 09:15:05 +01:00
|
|
|
format_chain(f, 4, c->year, false);
|
2017-12-11 19:50:30 +01:00
|
|
|
fputc('-', f);
|
2015-11-16 09:15:05 +01:00
|
|
|
format_chain(f, 2, c->month, false);
|
2017-12-11 19:50:30 +01:00
|
|
|
fputc(c->end_of_month ? '~' : '-', f);
|
2015-11-16 09:15:05 +01:00
|
|
|
format_chain(f, 2, c->day, false);
|
2017-12-11 19:50:30 +01:00
|
|
|
fputc(' ', f);
|
2015-11-16 09:15:05 +01:00
|
|
|
format_chain(f, 2, c->hour, false);
|
2017-12-11 19:50:30 +01:00
|
|
|
fputc(':', f);
|
2015-11-16 09:15:05 +01:00
|
|
|
format_chain(f, 2, c->minute, false);
|
2017-12-11 19:50:30 +01:00
|
|
|
fputc(':', f);
|
2016-12-16 18:36:15 +01:00
|
|
|
format_chain(f, 2, c->microsecond, true);
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2015-10-15 01:57:59 +02:00
|
|
|
if (c->utc)
|
2017-12-11 19:50:30 +01:00
|
|
|
fputs(" UTC", f);
|
2017-09-06 11:56:36 +02:00
|
|
|
else if (c->timezone != NULL) {
|
2017-12-11 19:50:30 +01:00
|
|
|
fputc(' ', f);
|
|
|
|
fputs(c->timezone, f);
|
2017-09-06 11:56:36 +02:00
|
|
|
} else if (IN_SET(c->dst, 0, 1)) {
|
util-lib: make timestamp generation and parsing reversible (#3869)
This patch improves parsing and generation of timestamps and calendar
specifications in two ways:
- The week day is now always printed in the abbreviated English form, instead
of the locale's setting. This makes sure we can always parse the week day
again, even if the locale is changed. Given that we don't follow locale
settings for printing timestamps in any other way either (for example, we
always use 24h syntax in order to make uniform parsing possible), it only
makes sense to also stick to a generic, non-localized form for the timestamp,
too.
- When parsing a timestamp, the local timezone (in its DST or non-DST name)
may be specified, in addition to "UTC". Other timezones are still not
supported however (not because we wouldn't want to, but mostly because libc
offers no nice API for that). In itself this brings no new features, however
it ensures that any locally formatted timestamp's timezone is also parsable
again.
These two changes ensure that the output of format_timestamp() may always be
passed to parse_timestamp() and results in the original input. The related
flavours for usec/UTC also work accordingly. Calendar specifications are
extended in a similar way.
The man page is updated accordingly, in particular this removes the claim that
timestamps systemd prints wouldn't be parsable by systemd. They are now.
The man page previously showed invalid timestamps as examples. This has been
removed, as the man page shouldn't be a unit test, where such negative examples
would be useful. The man page also no longer mentions the names of internal
functions, such as format_timestamp_us() or UNIX error codes such as EINVAL.
2016-08-04 01:04:53 +02:00
|
|
|
|
|
|
|
/* If daylight saving is explicitly on or off, let's show the used timezone. */
|
|
|
|
|
|
|
|
tzset();
|
|
|
|
|
|
|
|
if (!isempty(tzname[c->dst])) {
|
2017-12-11 19:50:30 +01:00
|
|
|
fputc(' ', f);
|
|
|
|
fputs(tzname[c->dst], f);
|
util-lib: make timestamp generation and parsing reversible (#3869)
This patch improves parsing and generation of timestamps and calendar
specifications in two ways:
- The week day is now always printed in the abbreviated English form, instead
of the locale's setting. This makes sure we can always parse the week day
again, even if the locale is changed. Given that we don't follow locale
settings for printing timestamps in any other way either (for example, we
always use 24h syntax in order to make uniform parsing possible), it only
makes sense to also stick to a generic, non-localized form for the timestamp,
too.
- When parsing a timestamp, the local timezone (in its DST or non-DST name)
may be specified, in addition to "UTC". Other timezones are still not
supported however (not because we wouldn't want to, but mostly because libc
offers no nice API for that). In itself this brings no new features, however
it ensures that any locally formatted timestamp's timezone is also parsable
again.
These two changes ensure that the output of format_timestamp() may always be
passed to parse_timestamp() and results in the original input. The related
flavours for usec/UTC also work accordingly. Calendar specifications are
extended in a similar way.
The man page is updated accordingly, in particular this removes the claim that
timestamps systemd prints wouldn't be parsable by systemd. They are now.
The man page previously showed invalid timestamps as examples. This has been
removed, as the man page shouldn't be a unit test, where such negative examples
would be useful. The man page also no longer mentions the names of internal
functions, such as format_timestamp_us() or UNIX error codes such as EINVAL.
2016-08-04 01:04:53 +02:00
|
|
|
}
|
|
|
|
}
|
2015-10-15 01:57:59 +02:00
|
|
|
|
2015-07-29 20:31:07 +02:00
|
|
|
r = fflush_and_check(f);
|
|
|
|
if (r < 0) {
|
2012-11-23 21:37:58 +01:00
|
|
|
free(buf);
|
|
|
|
fclose(f);
|
2015-07-29 20:31:07 +02:00
|
|
|
return r;
|
2012-11-23 21:37:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
fclose(f);
|
|
|
|
|
|
|
|
*p = buf;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_weekdays(const char **p, CalendarSpec *c) {
|
|
|
|
static const struct {
|
|
|
|
const char *name;
|
|
|
|
const int nr;
|
|
|
|
} day_nr[] = {
|
|
|
|
{ "Monday", 0 },
|
|
|
|
{ "Mon", 0 },
|
|
|
|
{ "Tuesday", 1 },
|
|
|
|
{ "Tue", 1 },
|
|
|
|
{ "Wednesday", 2 },
|
|
|
|
{ "Wed", 2 },
|
|
|
|
{ "Thursday", 3 },
|
|
|
|
{ "Thu", 3 },
|
|
|
|
{ "Friday", 4 },
|
|
|
|
{ "Fri", 4 },
|
|
|
|
{ "Saturday", 5 },
|
|
|
|
{ "Sat", 5 },
|
|
|
|
{ "Sunday", 6 },
|
|
|
|
{ "Sun", 6 }
|
|
|
|
};
|
|
|
|
|
|
|
|
int l = -1;
|
|
|
|
bool first = true;
|
|
|
|
|
|
|
|
assert(p);
|
|
|
|
assert(*p);
|
|
|
|
assert(c);
|
|
|
|
|
|
|
|
for (;;) {
|
tree-wide: be more careful with the type of array sizes
Previously we were a bit sloppy with the index and size types of arrays,
we'd regularly use unsigned. While I don't think this ever resulted in
real issues I think we should be more careful there and follow a
stricter regime: unless there's a strong reason not to use size_t for
array sizes and indexes, size_t it should be. Any allocations we do
ultimately will use size_t anyway, and converting forth and back between
unsigned and size_t will always be a source of problems.
Note that on 32bit machines "unsigned" and "size_t" are equivalent, and
on 64bit machines our arrays shouldn't grow that large anyway, and if
they do we have a problem, however that kind of overly large allocation
we have protections for usually, but for overflows we do not have that
so much, hence let's add it.
So yeah, it's a story of the current code being already "good enough",
but I think some extra type hygiene is better.
This patch tries to be comprehensive, but it probably isn't and I missed
a few cases. But I guess we can cover that later as we notice it. Among
smaller fixes, this changes:
1. strv_length()' return type becomes size_t
2. the unit file changes array size becomes size_t
3. DNS answer and query array sizes become size_t
Fixes: https://bugs.freedesktop.org/show_bug.cgi?id=76745
2018-04-27 14:09:31 +02:00
|
|
|
size_t i;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
for (i = 0; i < ELEMENTSOF(day_nr); i++) {
|
|
|
|
size_t skip;
|
|
|
|
|
|
|
|
if (!startswith_no_case(*p, day_nr[i].name))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
skip = strlen(day_nr[i].name);
|
|
|
|
|
2017-10-04 16:01:32 +02:00
|
|
|
if (!IN_SET((*p)[skip], 0, '-', '.', ',', ' '))
|
2012-11-23 21:37:58 +01:00
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
c->weekdays_bits |= 1 << day_nr[i].nr;
|
|
|
|
|
|
|
|
if (l >= 0) {
|
|
|
|
int j;
|
|
|
|
|
|
|
|
if (l > day_nr[i].nr)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
for (j = l + 1; j < day_nr[i].nr; j++)
|
|
|
|
c->weekdays_bits |= 1 << j;
|
|
|
|
}
|
|
|
|
|
|
|
|
*p += skip;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Couldn't find this prefix, so let's assume the
|
|
|
|
weekday was not specified and let's continue with
|
|
|
|
the date */
|
|
|
|
if (i >= ELEMENTSOF(day_nr))
|
|
|
|
return first ? 0 : -EINVAL;
|
|
|
|
|
|
|
|
/* We reached the end of the string */
|
|
|
|
if (**p == 0)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
/* We reached the end of the weekday spec part */
|
|
|
|
if (**p == ' ') {
|
|
|
|
*p += strspn(*p, " ");
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-07-01 04:26:07 +02:00
|
|
|
if (**p == '.') {
|
|
|
|
if (l >= 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
if ((*p)[1] != '.')
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
l = day_nr[i].nr;
|
2016-11-24 20:04:13 +01:00
|
|
|
*p += 2;
|
2016-07-01 04:26:07 +02:00
|
|
|
|
|
|
|
/* Support ranges with "-" for backwards compatibility */
|
|
|
|
} else if (**p == '-') {
|
2012-11-23 21:37:58 +01:00
|
|
|
if (l >= 0)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
l = day_nr[i].nr;
|
2016-11-24 20:04:13 +01:00
|
|
|
*p += 1;
|
|
|
|
|
|
|
|
} else if (**p == ',') {
|
2012-11-23 21:37:58 +01:00
|
|
|
l = -1;
|
2016-11-24 20:04:13 +01:00
|
|
|
*p += 1;
|
|
|
|
}
|
|
|
|
|
2016-12-16 18:36:15 +01:00
|
|
|
/* Allow a trailing comma but not an open range */
|
2017-10-04 16:01:32 +02:00
|
|
|
if (IN_SET(**p, 0, ' ')) {
|
2016-11-24 20:04:13 +01:00
|
|
|
*p += strspn(*p, " ");
|
|
|
|
return l < 0 ? 0 : -EINVAL;
|
|
|
|
}
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
first = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-05-17 11:40:49 +02:00
|
|
|
static int parse_one_number(const char *p, const char **e, unsigned long *ret) {
|
|
|
|
char *ee = NULL;
|
|
|
|
unsigned long value;
|
|
|
|
|
|
|
|
errno = 0;
|
|
|
|
value = strtoul(p, &ee, 10);
|
|
|
|
if (errno > 0)
|
|
|
|
return -errno;
|
|
|
|
if (ee == p)
|
|
|
|
return -EINVAL;
|
|
|
|
|
|
|
|
*ret = value;
|
|
|
|
*e = ee;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-12-20 22:42:12 +01:00
|
|
|
static int parse_component_decimal(const char **p, bool usec, int *res) {
|
2015-11-16 09:15:05 +01:00
|
|
|
unsigned long value;
|
|
|
|
const char *e = NULL;
|
|
|
|
int r;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2016-11-24 18:47:55 +01:00
|
|
|
if (!isdigit(**p))
|
|
|
|
return -EINVAL;
|
|
|
|
|
2017-05-17 11:40:49 +02:00
|
|
|
r = parse_one_number(*p, &e, &value);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2015-11-16 09:15:05 +01:00
|
|
|
if (usec) {
|
|
|
|
if (value * USEC_PER_SEC / USEC_PER_SEC != value)
|
2012-11-23 21:37:58 +01:00
|
|
|
return -ERANGE;
|
|
|
|
|
2015-11-16 09:15:05 +01:00
|
|
|
value *= USEC_PER_SEC;
|
|
|
|
|
2016-12-20 22:44:01 +01:00
|
|
|
/* One "." is a decimal point, but ".." is a range separator */
|
|
|
|
if (e[0] == '.' && e[1] != '.') {
|
|
|
|
unsigned add;
|
2016-11-24 21:51:07 +01:00
|
|
|
|
2015-11-16 09:15:05 +01:00
|
|
|
e++;
|
|
|
|
r = parse_fractional_part_u(&e, 6, &add);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
if (add + value < value)
|
|
|
|
return -ERANGE;
|
|
|
|
value += add;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-20 22:42:12 +01:00
|
|
|
if (value > INT_MAX)
|
|
|
|
return -ERANGE;
|
|
|
|
|
2015-11-16 09:15:05 +01:00
|
|
|
*p = e;
|
|
|
|
*res = value;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-07-01 02:16:05 +02:00
|
|
|
static int const_chain(int value, CalendarComponent **c) {
|
|
|
|
CalendarComponent *cc = NULL;
|
|
|
|
|
|
|
|
assert(c);
|
|
|
|
|
2019-04-07 17:18:54 +02:00
|
|
|
cc = new(CalendarComponent, 1);
|
2016-07-01 02:16:05 +02:00
|
|
|
if (!cc)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2019-04-07 17:18:54 +02:00
|
|
|
*cc = (CalendarComponent) {
|
|
|
|
.start = value,
|
|
|
|
.stop = -1,
|
|
|
|
.repeat = 0,
|
|
|
|
.next = *c,
|
|
|
|
};
|
2016-07-01 02:16:05 +02:00
|
|
|
|
|
|
|
*c = cc;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2017-05-17 11:40:49 +02:00
|
|
|
static int calendarspec_from_time_t(CalendarSpec *c, time_t time) {
|
2019-04-07 17:21:37 +02:00
|
|
|
_cleanup_CalendarComponent *year = NULL, *month = NULL, *day = NULL, *hour = NULL, *minute = NULL, *us = NULL;
|
2017-05-17 11:40:49 +02:00
|
|
|
struct tm tm;
|
|
|
|
int r;
|
|
|
|
|
2018-03-13 12:51:08 +01:00
|
|
|
if (!gmtime_r(&time, &tm))
|
|
|
|
return -ERANGE;
|
2017-05-17 11:40:49 +02:00
|
|
|
|
|
|
|
r = const_chain(tm.tm_year + 1900, &year);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = const_chain(tm.tm_mon + 1, &month);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = const_chain(tm.tm_mday, &day);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = const_chain(tm.tm_hour, &hour);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = const_chain(tm.tm_min, &minute);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
r = const_chain(tm.tm_sec * USEC_PER_SEC, &us);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
c->utc = true;
|
|
|
|
c->year = year;
|
|
|
|
c->month = month;
|
|
|
|
c->day = day;
|
|
|
|
c->hour = hour;
|
|
|
|
c->minute = minute;
|
|
|
|
c->microsecond = us;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-03-15 10:12:48 +01:00
|
|
|
static int prepend_component(const char **p, bool usec, unsigned nesting, CalendarComponent **c) {
|
2016-12-20 22:42:12 +01:00
|
|
|
int r, start, stop = -1, repeat = 0;
|
2015-11-16 09:15:05 +01:00
|
|
|
CalendarComponent *cc;
|
2018-03-15 10:12:48 +01:00
|
|
|
const char *e = *p;
|
2015-11-16 09:15:05 +01:00
|
|
|
|
|
|
|
assert(p);
|
|
|
|
assert(c);
|
|
|
|
|
2018-03-15 10:12:48 +01:00
|
|
|
if (nesting > CALENDARSPEC_COMPONENTS_MAX)
|
|
|
|
return -ENOBUFS;
|
2015-11-16 09:15:05 +01:00
|
|
|
|
2016-12-16 21:49:54 +01:00
|
|
|
r = parse_component_decimal(&e, usec, &start);
|
2015-11-16 09:15:05 +01:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2016-12-16 02:02:10 +01:00
|
|
|
if (e[0] == '.' && e[1] == '.') {
|
|
|
|
e += 2;
|
2016-12-16 21:49:54 +01:00
|
|
|
r = parse_component_decimal(&e, usec, &stop);
|
2016-12-16 02:02:10 +01:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
repeat = usec ? USEC_PER_SEC : 1;
|
|
|
|
}
|
|
|
|
|
2015-11-16 09:15:05 +01:00
|
|
|
if (*e == '/') {
|
|
|
|
e++;
|
|
|
|
r = parse_component_decimal(&e, usec, &repeat);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
if (repeat == 0)
|
|
|
|
return -ERANGE;
|
2012-11-23 21:37:58 +01:00
|
|
|
}
|
|
|
|
|
2017-10-04 16:01:32 +02:00
|
|
|
if (!IN_SET(*e, 0, ' ', ',', '-', '~', ':'))
|
2012-11-23 21:37:58 +01:00
|
|
|
return -EINVAL;
|
|
|
|
|
2019-04-07 17:18:54 +02:00
|
|
|
cc = new(CalendarComponent, 1);
|
2012-11-23 21:37:58 +01:00
|
|
|
if (!cc)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
2019-04-07 17:18:54 +02:00
|
|
|
*cc = (CalendarComponent) {
|
|
|
|
.start = start,
|
|
|
|
.stop = stop,
|
|
|
|
.repeat = repeat,
|
|
|
|
.next = *c,
|
|
|
|
};
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
*p = e;
|
|
|
|
*c = cc;
|
|
|
|
|
|
|
|
if (*e ==',') {
|
|
|
|
*p += 1;
|
2018-03-15 10:12:48 +01:00
|
|
|
return prepend_component(p, usec, nesting + 1, c);
|
2012-11-23 21:37:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2015-11-16 09:15:05 +01:00
|
|
|
static int parse_chain(const char **p, bool usec, CalendarComponent **c) {
|
2012-11-23 21:37:58 +01:00
|
|
|
const char *t;
|
|
|
|
CalendarComponent *cc = NULL;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(p);
|
|
|
|
assert(c);
|
|
|
|
|
|
|
|
t = *p;
|
|
|
|
|
|
|
|
if (t[0] == '*') {
|
2015-11-16 09:15:05 +01:00
|
|
|
if (usec) {
|
|
|
|
r = const_chain(0, c);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
(*c)->repeat = USEC_PER_SEC;
|
|
|
|
} else
|
|
|
|
*c = NULL;
|
|
|
|
|
2012-11-23 21:37:58 +01:00
|
|
|
*p = t + 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-03-15 10:12:48 +01:00
|
|
|
r = prepend_component(&t, usec, 0, &cc);
|
2012-11-23 21:37:58 +01:00
|
|
|
if (r < 0) {
|
2019-04-07 17:21:37 +02:00
|
|
|
chain_free(cc);
|
2012-11-23 21:37:58 +01:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
*p = t;
|
|
|
|
*c = cc;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int parse_date(const char **p, CalendarSpec *c) {
|
|
|
|
const char *t;
|
|
|
|
int r;
|
|
|
|
CalendarComponent *first, *second, *third;
|
|
|
|
|
|
|
|
assert(p);
|
|
|
|
assert(*p);
|
|
|
|
assert(c);
|
|
|
|
|
|
|
|
t = *p;
|
|
|
|
|
|
|
|
if (*t == 0)
|
|
|
|
return 0;
|
|
|
|
|
2017-05-17 11:40:49 +02:00
|
|
|
/* @TIMESTAMP — UNIX time in seconds since the epoch */
|
|
|
|
if (*t == '@') {
|
|
|
|
unsigned long value;
|
|
|
|
time_t time;
|
|
|
|
|
|
|
|
r = parse_one_number(t + 1, &t, &value);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
time = value;
|
|
|
|
if ((unsigned long) time != value)
|
|
|
|
return -ERANGE;
|
|
|
|
|
|
|
|
r = calendarspec_from_time_t(c, time);
|
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
*p = t;
|
|
|
|
return 1; /* finito, don't parse H:M:S after that */
|
|
|
|
}
|
|
|
|
|
2015-11-16 09:15:05 +01:00
|
|
|
r = parse_chain(&t, false, &first);
|
2012-11-23 21:37:58 +01:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
|
|
|
/* Already the end? A ':' as separator? In that case this was a time, not a date */
|
2017-10-04 16:01:32 +02:00
|
|
|
if (IN_SET(*t, 0, ':')) {
|
2019-04-07 17:21:37 +02:00
|
|
|
chain_free(first);
|
2012-11-23 21:37:58 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-11-22 16:05:10 +01:00
|
|
|
if (*t == '~')
|
|
|
|
c->end_of_month = true;
|
|
|
|
else if (*t != '-') {
|
2019-04-07 17:21:37 +02:00
|
|
|
chain_free(first);
|
2012-11-23 21:37:58 +01:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
t++;
|
2015-11-16 09:15:05 +01:00
|
|
|
r = parse_chain(&t, false, &second);
|
2012-11-23 21:37:58 +01:00
|
|
|
if (r < 0) {
|
2019-04-07 17:21:37 +02:00
|
|
|
chain_free(first);
|
2012-11-23 21:37:58 +01:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Got two parts, hence it's month and day */
|
2017-10-04 16:01:32 +02:00
|
|
|
if (IN_SET(*t, 0, ' ')) {
|
2012-11-23 21:37:58 +01:00
|
|
|
*p = t + strspn(t, " ");
|
|
|
|
c->month = first;
|
|
|
|
c->day = second;
|
|
|
|
return 0;
|
2016-12-14 19:21:27 +01:00
|
|
|
} else if (c->end_of_month) {
|
2019-04-07 17:21:37 +02:00
|
|
|
chain_free(first);
|
|
|
|
chain_free(second);
|
2016-11-22 16:05:10 +01:00
|
|
|
return -EINVAL;
|
2016-12-14 19:21:27 +01:00
|
|
|
}
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2016-11-22 16:05:10 +01:00
|
|
|
if (*t == '~')
|
|
|
|
c->end_of_month = true;
|
|
|
|
else if (*t != '-') {
|
2019-04-07 17:21:37 +02:00
|
|
|
chain_free(first);
|
|
|
|
chain_free(second);
|
2012-11-23 21:37:58 +01:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
|
|
|
t++;
|
2015-11-16 09:15:05 +01:00
|
|
|
r = parse_chain(&t, false, &third);
|
2012-11-23 21:37:58 +01:00
|
|
|
if (r < 0) {
|
2019-04-07 17:21:37 +02:00
|
|
|
chain_free(first);
|
|
|
|
chain_free(second);
|
2012-11-23 21:37:58 +01:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2016-11-22 16:05:10 +01:00
|
|
|
/* Got three parts, hence it is year, month and day */
|
2017-10-04 16:01:32 +02:00
|
|
|
if (IN_SET(*t, 0, ' ')) {
|
2012-11-23 21:37:58 +01:00
|
|
|
*p = t + strspn(t, " ");
|
|
|
|
c->year = first;
|
|
|
|
c->month = second;
|
|
|
|
c->day = third;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2019-04-07 17:21:37 +02:00
|
|
|
chain_free(first);
|
|
|
|
chain_free(second);
|
|
|
|
chain_free(third);
|
2012-11-23 21:37:58 +01:00
|
|
|
return -EINVAL;
|
|
|
|
}
|
|
|
|
|
2015-11-10 16:04:37 +01:00
|
|
|
static int parse_calendar_time(const char **p, CalendarSpec *c) {
|
2012-11-23 21:37:58 +01:00
|
|
|
CalendarComponent *h = NULL, *m = NULL, *s = NULL;
|
|
|
|
const char *t;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(p);
|
|
|
|
assert(*p);
|
|
|
|
assert(c);
|
|
|
|
|
|
|
|
t = *p;
|
|
|
|
|
2016-11-24 17:50:06 +01:00
|
|
|
/* If no time is specified at all, then this means 00:00:00 */
|
|
|
|
if (*t == 0)
|
|
|
|
goto null_hour;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2015-11-16 09:15:05 +01:00
|
|
|
r = parse_chain(&t, false, &h);
|
2012-11-23 21:37:58 +01:00
|
|
|
if (r < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
if (*t != ':') {
|
|
|
|
r = -EINVAL;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
t++;
|
2015-11-16 09:15:05 +01:00
|
|
|
r = parse_chain(&t, false, &m);
|
2012-11-23 21:37:58 +01:00
|
|
|
if (r < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
/* Already at the end? Then it's hours and minutes, and seconds are 0 */
|
2016-12-06 20:41:15 +01:00
|
|
|
if (*t == 0)
|
|
|
|
goto null_second;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
if (*t != ':') {
|
|
|
|
r = -EINVAL;
|
|
|
|
goto fail;
|
|
|
|
}
|
|
|
|
|
|
|
|
t++;
|
2015-11-16 09:15:05 +01:00
|
|
|
r = parse_chain(&t, true, &s);
|
2012-11-23 21:37:58 +01:00
|
|
|
if (r < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
/* At the end? Then it's hours, minutes and seconds */
|
|
|
|
if (*t == 0)
|
|
|
|
goto finish;
|
|
|
|
|
|
|
|
r = -EINVAL;
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
null_hour:
|
|
|
|
r = const_chain(0, &h);
|
|
|
|
if (r < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
r = const_chain(0, &m);
|
|
|
|
if (r < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
null_second:
|
|
|
|
r = const_chain(0, &s);
|
|
|
|
if (r < 0)
|
|
|
|
goto fail;
|
|
|
|
|
|
|
|
finish:
|
|
|
|
*p = t;
|
|
|
|
c->hour = h;
|
|
|
|
c->minute = m;
|
2015-11-16 09:15:05 +01:00
|
|
|
c->microsecond = s;
|
|
|
|
|
2012-11-23 21:37:58 +01:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
fail:
|
2019-04-07 17:21:37 +02:00
|
|
|
chain_free(h);
|
|
|
|
chain_free(m);
|
|
|
|
chain_free(s);
|
2012-11-23 21:37:58 +01:00
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
|
|
|
int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
|
util-lib: make timestamp generation and parsing reversible (#3869)
This patch improves parsing and generation of timestamps and calendar
specifications in two ways:
- The week day is now always printed in the abbreviated English form, instead
of the locale's setting. This makes sure we can always parse the week day
again, even if the locale is changed. Given that we don't follow locale
settings for printing timestamps in any other way either (for example, we
always use 24h syntax in order to make uniform parsing possible), it only
makes sense to also stick to a generic, non-localized form for the timestamp,
too.
- When parsing a timestamp, the local timezone (in its DST or non-DST name)
may be specified, in addition to "UTC". Other timezones are still not
supported however (not because we wouldn't want to, but mostly because libc
offers no nice API for that). In itself this brings no new features, however
it ensures that any locally formatted timestamp's timezone is also parsable
again.
These two changes ensure that the output of format_timestamp() may always be
passed to parse_timestamp() and results in the original input. The related
flavours for usec/UTC also work accordingly. Calendar specifications are
extended in a similar way.
The man page is updated accordingly, in particular this removes the claim that
timestamps systemd prints wouldn't be parsable by systemd. They are now.
The man page previously showed invalid timestamps as examples. This has been
removed, as the man page shouldn't be a unit test, where such negative examples
would be useful. The man page also no longer mentions the names of internal
functions, such as format_timestamp_us() or UNIX error codes such as EINVAL.
2016-08-04 01:04:53 +02:00
|
|
|
const char *utc;
|
2018-05-10 14:04:30 +02:00
|
|
|
_cleanup_(calendar_spec_freep) CalendarSpec *c = NULL;
|
2019-02-16 22:35:46 +01:00
|
|
|
_cleanup_free_ char *p_tmp = NULL;
|
2012-11-23 21:37:58 +01:00
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(p);
|
|
|
|
assert(spec);
|
|
|
|
|
2019-04-07 17:18:54 +02:00
|
|
|
c = new(CalendarSpec, 1);
|
2012-11-23 21:37:58 +01:00
|
|
|
if (!c)
|
|
|
|
return -ENOMEM;
|
2019-04-07 17:18:54 +02:00
|
|
|
|
|
|
|
*c = (CalendarSpec) {
|
|
|
|
.dst = -1,
|
|
|
|
.timezone = NULL,
|
|
|
|
};
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2015-10-25 17:24:39 +01:00
|
|
|
utc = endswith_no_case(p, " UTC");
|
|
|
|
if (utc) {
|
|
|
|
c->utc = true;
|
2019-02-16 22:35:46 +01:00
|
|
|
p = p_tmp = strndup(p, utc - p);
|
|
|
|
if (!p)
|
|
|
|
return -ENOMEM;
|
util-lib: make timestamp generation and parsing reversible (#3869)
This patch improves parsing and generation of timestamps and calendar
specifications in two ways:
- The week day is now always printed in the abbreviated English form, instead
of the locale's setting. This makes sure we can always parse the week day
again, even if the locale is changed. Given that we don't follow locale
settings for printing timestamps in any other way either (for example, we
always use 24h syntax in order to make uniform parsing possible), it only
makes sense to also stick to a generic, non-localized form for the timestamp,
too.
- When parsing a timestamp, the local timezone (in its DST or non-DST name)
may be specified, in addition to "UTC". Other timezones are still not
supported however (not because we wouldn't want to, but mostly because libc
offers no nice API for that). In itself this brings no new features, however
it ensures that any locally formatted timestamp's timezone is also parsable
again.
These two changes ensure that the output of format_timestamp() may always be
passed to parse_timestamp() and results in the original input. The related
flavours for usec/UTC also work accordingly. Calendar specifications are
extended in a similar way.
The man page is updated accordingly, in particular this removes the claim that
timestamps systemd prints wouldn't be parsable by systemd. They are now.
The man page previously showed invalid timestamps as examples. This has been
removed, as the man page shouldn't be a unit test, where such negative examples
would be useful. The man page also no longer mentions the names of internal
functions, such as format_timestamp_us() or UNIX error codes such as EINVAL.
2016-08-04 01:04:53 +02:00
|
|
|
} else {
|
|
|
|
const char *e = NULL;
|
|
|
|
int j;
|
|
|
|
|
|
|
|
tzset();
|
|
|
|
|
|
|
|
/* Check if the local timezone was specified? */
|
|
|
|
for (j = 0; j <= 1; j++) {
|
|
|
|
if (isempty(tzname[j]))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
e = endswith_no_case(p, tzname[j]);
|
2017-05-17 11:40:49 +02:00
|
|
|
if (!e)
|
util-lib: make timestamp generation and parsing reversible (#3869)
This patch improves parsing and generation of timestamps and calendar
specifications in two ways:
- The week day is now always printed in the abbreviated English form, instead
of the locale's setting. This makes sure we can always parse the week day
again, even if the locale is changed. Given that we don't follow locale
settings for printing timestamps in any other way either (for example, we
always use 24h syntax in order to make uniform parsing possible), it only
makes sense to also stick to a generic, non-localized form for the timestamp,
too.
- When parsing a timestamp, the local timezone (in its DST or non-DST name)
may be specified, in addition to "UTC". Other timezones are still not
supported however (not because we wouldn't want to, but mostly because libc
offers no nice API for that). In itself this brings no new features, however
it ensures that any locally formatted timestamp's timezone is also parsable
again.
These two changes ensure that the output of format_timestamp() may always be
passed to parse_timestamp() and results in the original input. The related
flavours for usec/UTC also work accordingly. Calendar specifications are
extended in a similar way.
The man page is updated accordingly, in particular this removes the claim that
timestamps systemd prints wouldn't be parsable by systemd. They are now.
The man page previously showed invalid timestamps as examples. This has been
removed, as the man page shouldn't be a unit test, where such negative examples
would be useful. The man page also no longer mentions the names of internal
functions, such as format_timestamp_us() or UNIX error codes such as EINVAL.
2016-08-04 01:04:53 +02:00
|
|
|
continue;
|
|
|
|
if (e == p)
|
|
|
|
continue;
|
|
|
|
if (e[-1] != ' ')
|
|
|
|
continue;
|
|
|
|
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Found one of the two timezones specified? */
|
|
|
|
if (IN_SET(j, 0, 1)) {
|
2019-02-16 22:35:46 +01:00
|
|
|
p = p_tmp = strndup(p, e - p - 1);
|
|
|
|
if (!p)
|
|
|
|
return -ENOMEM;
|
|
|
|
|
util-lib: make timestamp generation and parsing reversible (#3869)
This patch improves parsing and generation of timestamps and calendar
specifications in two ways:
- The week day is now always printed in the abbreviated English form, instead
of the locale's setting. This makes sure we can always parse the week day
again, even if the locale is changed. Given that we don't follow locale
settings for printing timestamps in any other way either (for example, we
always use 24h syntax in order to make uniform parsing possible), it only
makes sense to also stick to a generic, non-localized form for the timestamp,
too.
- When parsing a timestamp, the local timezone (in its DST or non-DST name)
may be specified, in addition to "UTC". Other timezones are still not
supported however (not because we wouldn't want to, but mostly because libc
offers no nice API for that). In itself this brings no new features, however
it ensures that any locally formatted timestamp's timezone is also parsable
again.
These two changes ensure that the output of format_timestamp() may always be
passed to parse_timestamp() and results in the original input. The related
flavours for usec/UTC also work accordingly. Calendar specifications are
extended in a similar way.
The man page is updated accordingly, in particular this removes the claim that
timestamps systemd prints wouldn't be parsable by systemd. They are now.
The man page previously showed invalid timestamps as examples. This has been
removed, as the man page shouldn't be a unit test, where such negative examples
would be useful. The man page also no longer mentions the names of internal
functions, such as format_timestamp_us() or UNIX error codes such as EINVAL.
2016-08-04 01:04:53 +02:00
|
|
|
c->dst = j;
|
2017-09-06 11:56:36 +02:00
|
|
|
} else {
|
|
|
|
const char *last_space;
|
|
|
|
|
2017-09-17 09:10:03 +02:00
|
|
|
last_space = strrchr(p, ' ');
|
2018-05-12 21:20:13 +02:00
|
|
|
if (last_space != NULL && timezone_is_valid(last_space + 1, LOG_DEBUG)) {
|
2017-09-17 09:10:03 +02:00
|
|
|
c->timezone = strdup(last_space + 1);
|
2018-05-10 14:04:30 +02:00
|
|
|
if (!c->timezone)
|
|
|
|
return -ENOMEM;
|
2017-09-17 09:10:03 +02:00
|
|
|
|
2019-02-16 22:35:46 +01:00
|
|
|
p = p_tmp = strndup(p, last_space - p);
|
|
|
|
if (!p)
|
|
|
|
return -ENOMEM;
|
2017-09-06 11:56:36 +02:00
|
|
|
}
|
util-lib: make timestamp generation and parsing reversible (#3869)
This patch improves parsing and generation of timestamps and calendar
specifications in two ways:
- The week day is now always printed in the abbreviated English form, instead
of the locale's setting. This makes sure we can always parse the week day
again, even if the locale is changed. Given that we don't follow locale
settings for printing timestamps in any other way either (for example, we
always use 24h syntax in order to make uniform parsing possible), it only
makes sense to also stick to a generic, non-localized form for the timestamp,
too.
- When parsing a timestamp, the local timezone (in its DST or non-DST name)
may be specified, in addition to "UTC". Other timezones are still not
supported however (not because we wouldn't want to, but mostly because libc
offers no nice API for that). In itself this brings no new features, however
it ensures that any locally formatted timestamp's timezone is also parsable
again.
These two changes ensure that the output of format_timestamp() may always be
passed to parse_timestamp() and results in the original input. The related
flavours for usec/UTC also work accordingly. Calendar specifications are
extended in a similar way.
The man page is updated accordingly, in particular this removes the claim that
timestamps systemd prints wouldn't be parsable by systemd. They are now.
The man page previously showed invalid timestamps as examples. This has been
removed, as the man page shouldn't be a unit test, where such negative examples
would be useful. The man page also no longer mentions the names of internal
functions, such as format_timestamp_us() or UNIX error codes such as EINVAL.
2016-08-04 01:04:53 +02:00
|
|
|
}
|
2015-10-25 17:24:39 +01:00
|
|
|
}
|
2015-10-15 01:57:59 +02:00
|
|
|
|
2018-05-10 14:04:30 +02:00
|
|
|
if (isempty(p))
|
|
|
|
return -EINVAL;
|
2016-11-24 18:21:37 +01:00
|
|
|
|
2014-10-27 08:42:42 +01:00
|
|
|
if (strcaseeq(p, "minutely")) {
|
2015-11-16 09:15:05 +01:00
|
|
|
r = const_chain(0, &c->microsecond);
|
2014-10-27 08:42:42 +01:00
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2014-10-27 08:42:42 +01:00
|
|
|
|
|
|
|
} else if (strcaseeq(p, "hourly")) {
|
2012-11-23 21:37:58 +01:00
|
|
|
r = const_chain(0, &c->minute);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2015-11-16 09:15:05 +01:00
|
|
|
r = const_chain(0, &c->microsecond);
|
2012-11-23 21:37:58 +01:00
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2013-02-12 21:47:37 +01:00
|
|
|
} else if (strcaseeq(p, "daily")) {
|
2012-11-23 21:37:58 +01:00
|
|
|
r = const_chain(0, &c->hour);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2012-11-23 21:37:58 +01:00
|
|
|
r = const_chain(0, &c->minute);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2015-11-16 09:15:05 +01:00
|
|
|
r = const_chain(0, &c->microsecond);
|
2012-11-23 21:37:58 +01:00
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2013-02-12 21:47:37 +01:00
|
|
|
} else if (strcaseeq(p, "monthly")) {
|
2012-11-23 21:37:58 +01:00
|
|
|
r = const_chain(1, &c->day);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2012-11-23 21:37:58 +01:00
|
|
|
r = const_chain(0, &c->hour);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2012-11-23 21:37:58 +01:00
|
|
|
r = const_chain(0, &c->minute);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2015-11-16 09:15:05 +01:00
|
|
|
r = const_chain(0, &c->microsecond);
|
2012-11-23 21:37:58 +01:00
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2014-10-27 18:08:46 +01:00
|
|
|
} else if (strcaseeq(p, "annually") ||
|
|
|
|
strcaseeq(p, "yearly") ||
|
|
|
|
strcaseeq(p, "anually") /* backwards compatibility */ ) {
|
|
|
|
|
2013-11-19 01:13:42 +01:00
|
|
|
r = const_chain(1, &c->month);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2013-11-19 01:13:42 +01:00
|
|
|
r = const_chain(1, &c->day);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2013-11-19 01:13:42 +01:00
|
|
|
r = const_chain(0, &c->hour);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2013-11-19 01:13:42 +01:00
|
|
|
r = const_chain(0, &c->minute);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2015-11-16 09:15:05 +01:00
|
|
|
r = const_chain(0, &c->microsecond);
|
2013-11-19 01:13:42 +01:00
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2013-11-19 01:13:42 +01:00
|
|
|
|
2013-02-12 21:47:37 +01:00
|
|
|
} else if (strcaseeq(p, "weekly")) {
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
c->weekdays_bits = 1;
|
|
|
|
|
|
|
|
r = const_chain(0, &c->hour);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2012-11-23 21:37:58 +01:00
|
|
|
r = const_chain(0, &c->minute);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2015-11-16 09:15:05 +01:00
|
|
|
r = const_chain(0, &c->microsecond);
|
2014-10-27 18:08:46 +01:00
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2014-10-27 18:08:46 +01:00
|
|
|
|
|
|
|
} else if (strcaseeq(p, "quarterly")) {
|
|
|
|
|
|
|
|
r = const_chain(1, &c->month);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2014-10-27 18:08:46 +01:00
|
|
|
r = const_chain(4, &c->month);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2014-10-27 18:08:46 +01:00
|
|
|
r = const_chain(7, &c->month);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2014-10-27 18:08:46 +01:00
|
|
|
r = const_chain(10, &c->month);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2014-10-27 18:08:46 +01:00
|
|
|
r = const_chain(1, &c->day);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2014-10-27 18:08:46 +01:00
|
|
|
r = const_chain(0, &c->hour);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2014-10-27 18:08:46 +01:00
|
|
|
r = const_chain(0, &c->minute);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2015-11-16 09:15:05 +01:00
|
|
|
r = const_chain(0, &c->microsecond);
|
2014-10-27 18:08:46 +01:00
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2014-10-27 18:08:46 +01:00
|
|
|
|
|
|
|
} else if (strcaseeq(p, "biannually") ||
|
|
|
|
strcaseeq(p, "bi-annually") ||
|
|
|
|
strcaseeq(p, "semiannually") ||
|
|
|
|
strcaseeq(p, "semi-annually")) {
|
|
|
|
|
|
|
|
r = const_chain(1, &c->month);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2014-10-27 18:08:46 +01:00
|
|
|
r = const_chain(7, &c->month);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2014-10-27 18:08:46 +01:00
|
|
|
r = const_chain(1, &c->day);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2014-10-27 18:08:46 +01:00
|
|
|
r = const_chain(0, &c->hour);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2014-10-27 18:08:46 +01:00
|
|
|
r = const_chain(0, &c->minute);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2015-11-16 09:15:05 +01:00
|
|
|
r = const_chain(0, &c->microsecond);
|
2012-11-23 21:37:58 +01:00
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
} else {
|
|
|
|
r = parse_weekdays(&p, c);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
r = parse_date(&p, c);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2017-05-17 11:40:49 +02:00
|
|
|
if (r == 0) {
|
|
|
|
r = parse_calendar_time(&p, c);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2017-05-17 11:40:49 +02:00
|
|
|
}
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2018-05-10 14:04:30 +02:00
|
|
|
if (*p != 0)
|
|
|
|
return -EINVAL;
|
2012-11-23 21:37:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
r = calendar_spec_normalize(c);
|
|
|
|
if (r < 0)
|
2018-05-10 14:04:30 +02:00
|
|
|
return r;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2018-05-10 14:04:30 +02:00
|
|
|
if (!calendar_spec_valid(c))
|
|
|
|
return -EINVAL;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2018-05-10 14:04:30 +02:00
|
|
|
*spec = TAKE_PTR(c);
|
2012-11-23 21:37:58 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2016-12-20 22:44:01 +01:00
|
|
|
static int find_end_of_month(struct tm *tm, bool utc, int day) {
|
2016-12-16 02:02:10 +01:00
|
|
|
struct tm t = *tm;
|
|
|
|
|
|
|
|
t.tm_mon++;
|
|
|
|
t.tm_mday = 1 - day;
|
|
|
|
|
2017-02-02 18:25:33 +01:00
|
|
|
if (mktime_or_timegm(&t, utc) < 0 ||
|
2016-12-16 02:02:10 +01:00
|
|
|
t.tm_mon != tm->tm_mon)
|
|
|
|
return -1;
|
|
|
|
|
|
|
|
return t.tm_mday;
|
|
|
|
}
|
|
|
|
|
2016-11-22 16:05:10 +01:00
|
|
|
static int find_matching_component(const CalendarSpec *spec, const CalendarComponent *c,
|
|
|
|
struct tm *tm, int *val) {
|
2016-12-16 18:36:15 +01:00
|
|
|
const CalendarComponent *p = c;
|
2016-12-16 21:49:54 +01:00
|
|
|
int start, stop, d = -1;
|
2012-11-23 21:37:58 +01:00
|
|
|
bool d_set = false;
|
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(val);
|
|
|
|
|
|
|
|
if (!c)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
while (c) {
|
2016-12-16 21:49:54 +01:00
|
|
|
start = c->start;
|
|
|
|
stop = c->stop;
|
2016-12-16 02:02:10 +01:00
|
|
|
|
2016-11-22 16:05:10 +01:00
|
|
|
if (spec->end_of_month && p == spec->day) {
|
2016-12-16 21:49:54 +01:00
|
|
|
start = find_end_of_month(tm, spec->utc, start);
|
|
|
|
stop = find_end_of_month(tm, spec->utc, stop);
|
2016-12-16 02:02:10 +01:00
|
|
|
|
2016-12-16 21:49:54 +01:00
|
|
|
if (stop > 0)
|
|
|
|
SWAP_TWO(start, stop);
|
2016-12-16 02:02:10 +01:00
|
|
|
}
|
2016-11-22 16:05:10 +01:00
|
|
|
|
2016-12-16 21:49:54 +01:00
|
|
|
if (start >= *val) {
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2016-12-16 21:49:54 +01:00
|
|
|
if (!d_set || start < d) {
|
|
|
|
d = start;
|
2012-11-23 21:37:58 +01:00
|
|
|
d_set = true;
|
|
|
|
}
|
|
|
|
|
|
|
|
} else if (c->repeat > 0) {
|
|
|
|
int k;
|
|
|
|
|
2018-03-20 20:36:09 +01:00
|
|
|
k = start + c->repeat * DIV_ROUND_UP(*val - start, c->repeat);
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2016-12-16 21:49:54 +01:00
|
|
|
if ((!d_set || k < d) && (stop < 0 || k <= stop)) {
|
2012-11-23 21:37:58 +01:00
|
|
|
d = k;
|
|
|
|
d_set = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-16 18:36:15 +01:00
|
|
|
c = c->next;
|
2012-11-23 21:37:58 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (!d_set)
|
|
|
|
return -ENOENT;
|
|
|
|
|
|
|
|
r = *val != d;
|
|
|
|
*val = d;
|
|
|
|
return r;
|
|
|
|
}
|
|
|
|
|
2015-10-15 01:57:59 +02:00
|
|
|
static bool tm_out_of_bounds(const struct tm *tm, bool utc) {
|
2012-11-23 21:37:58 +01:00
|
|
|
struct tm t;
|
|
|
|
assert(tm);
|
|
|
|
|
|
|
|
t = *tm;
|
|
|
|
|
2017-02-02 18:25:33 +01:00
|
|
|
if (mktime_or_timegm(&t, utc) < 0)
|
2012-11-23 21:37:58 +01:00
|
|
|
return true;
|
|
|
|
|
2016-11-22 18:57:07 +01:00
|
|
|
/*
|
|
|
|
* Set an upper bound on the year so impossible dates like "*-02-31"
|
|
|
|
* don't cause find_next() to loop forever. tm_year contains years
|
|
|
|
* since 1900, so adjust it accordingly.
|
|
|
|
*/
|
|
|
|
if (tm->tm_year + 1900 > MAX_YEAR)
|
|
|
|
return true;
|
|
|
|
|
2012-11-23 21:37:58 +01:00
|
|
|
/* Did any normalization take place? If so, it was out of bounds before */
|
|
|
|
return
|
|
|
|
t.tm_year != tm->tm_year ||
|
|
|
|
t.tm_mon != tm->tm_mon ||
|
|
|
|
t.tm_mday != tm->tm_mday ||
|
|
|
|
t.tm_hour != tm->tm_hour ||
|
|
|
|
t.tm_min != tm->tm_min ||
|
|
|
|
t.tm_sec != tm->tm_sec;
|
|
|
|
}
|
|
|
|
|
2015-10-15 01:57:59 +02:00
|
|
|
static bool matches_weekday(int weekdays_bits, const struct tm *tm, bool utc) {
|
2012-11-23 21:37:58 +01:00
|
|
|
struct tm t;
|
|
|
|
int k;
|
|
|
|
|
2014-10-30 12:19:14 +01:00
|
|
|
if (weekdays_bits < 0 || weekdays_bits >= BITS_WEEKDAYS)
|
2012-11-23 21:37:58 +01:00
|
|
|
return true;
|
|
|
|
|
|
|
|
t = *tm;
|
2017-02-02 18:25:33 +01:00
|
|
|
if (mktime_or_timegm(&t, utc) < 0)
|
2012-11-23 21:37:58 +01:00
|
|
|
return false;
|
|
|
|
|
|
|
|
k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
|
|
|
|
return (weekdays_bits & (1 << k));
|
|
|
|
}
|
|
|
|
|
2015-11-16 09:15:05 +01:00
|
|
|
static int find_next(const CalendarSpec *spec, struct tm *tm, usec_t *usec) {
|
2012-11-23 21:37:58 +01:00
|
|
|
struct tm c;
|
2015-11-16 09:15:05 +01:00
|
|
|
int tm_usec;
|
2012-11-23 21:37:58 +01:00
|
|
|
int r;
|
|
|
|
|
|
|
|
assert(spec);
|
|
|
|
assert(tm);
|
|
|
|
|
|
|
|
c = *tm;
|
2015-11-16 09:15:05 +01:00
|
|
|
tm_usec = *usec;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
/* Normalize the current date */
|
2016-02-21 23:25:38 +01:00
|
|
|
(void) mktime_or_timegm(&c, spec->utc);
|
util-lib: make timestamp generation and parsing reversible (#3869)
This patch improves parsing and generation of timestamps and calendar
specifications in two ways:
- The week day is now always printed in the abbreviated English form, instead
of the locale's setting. This makes sure we can always parse the week day
again, even if the locale is changed. Given that we don't follow locale
settings for printing timestamps in any other way either (for example, we
always use 24h syntax in order to make uniform parsing possible), it only
makes sense to also stick to a generic, non-localized form for the timestamp,
too.
- When parsing a timestamp, the local timezone (in its DST or non-DST name)
may be specified, in addition to "UTC". Other timezones are still not
supported however (not because we wouldn't want to, but mostly because libc
offers no nice API for that). In itself this brings no new features, however
it ensures that any locally formatted timestamp's timezone is also parsable
again.
These two changes ensure that the output of format_timestamp() may always be
passed to parse_timestamp() and results in the original input. The related
flavours for usec/UTC also work accordingly. Calendar specifications are
extended in a similar way.
The man page is updated accordingly, in particular this removes the claim that
timestamps systemd prints wouldn't be parsable by systemd. They are now.
The man page previously showed invalid timestamps as examples. This has been
removed, as the man page shouldn't be a unit test, where such negative examples
would be useful. The man page also no longer mentions the names of internal
functions, such as format_timestamp_us() or UNIX error codes such as EINVAL.
2016-08-04 01:04:53 +02:00
|
|
|
c.tm_isdst = spec->dst;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
c.tm_year += 1900;
|
2016-11-22 16:05:10 +01:00
|
|
|
r = find_matching_component(spec, spec->year, &c, &c.tm_year);
|
2012-11-23 21:37:58 +01:00
|
|
|
c.tm_year -= 1900;
|
|
|
|
|
|
|
|
if (r > 0) {
|
|
|
|
c.tm_mon = 0;
|
|
|
|
c.tm_mday = 1;
|
2015-11-16 09:15:05 +01:00
|
|
|
c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
|
2012-11-23 21:37:58 +01:00
|
|
|
}
|
2016-02-21 23:27:20 +01:00
|
|
|
if (r < 0)
|
2012-11-23 21:37:58 +01:00
|
|
|
return r;
|
2016-02-21 23:27:20 +01:00
|
|
|
if (tm_out_of_bounds(&c, spec->utc))
|
|
|
|
return -ENOENT;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
c.tm_mon += 1;
|
2016-11-22 16:05:10 +01:00
|
|
|
r = find_matching_component(spec, spec->month, &c, &c.tm_mon);
|
2012-11-23 21:37:58 +01:00
|
|
|
c.tm_mon -= 1;
|
|
|
|
|
|
|
|
if (r > 0) {
|
|
|
|
c.tm_mday = 1;
|
2015-11-16 09:15:05 +01:00
|
|
|
c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
|
2012-11-23 21:37:58 +01:00
|
|
|
}
|
2015-10-15 01:57:59 +02:00
|
|
|
if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
|
2016-02-23 05:32:04 +01:00
|
|
|
c.tm_year++;
|
2012-11-23 21:37:58 +01:00
|
|
|
c.tm_mon = 0;
|
|
|
|
c.tm_mday = 1;
|
2015-11-16 09:15:05 +01:00
|
|
|
c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
|
2012-11-23 21:37:58 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-11-22 16:05:10 +01:00
|
|
|
r = find_matching_component(spec, spec->day, &c, &c.tm_mday);
|
2012-11-23 21:37:58 +01:00
|
|
|
if (r > 0)
|
2015-11-16 09:15:05 +01:00
|
|
|
c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
|
2015-10-15 01:57:59 +02:00
|
|
|
if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
|
2016-02-23 05:32:04 +01:00
|
|
|
c.tm_mon++;
|
2012-11-23 21:37:58 +01:00
|
|
|
c.tm_mday = 1;
|
2015-11-16 09:15:05 +01:00
|
|
|
c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
|
2012-11-23 21:37:58 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-10-15 01:57:59 +02:00
|
|
|
if (!matches_weekday(spec->weekdays_bits, &c, spec->utc)) {
|
2012-11-23 21:37:58 +01:00
|
|
|
c.tm_mday++;
|
2015-11-16 09:15:05 +01:00
|
|
|
c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
|
2012-11-23 21:37:58 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-11-22 16:05:10 +01:00
|
|
|
r = find_matching_component(spec, spec->hour, &c, &c.tm_hour);
|
2012-11-23 21:37:58 +01:00
|
|
|
if (r > 0)
|
2016-02-22 14:02:48 +01:00
|
|
|
c.tm_min = c.tm_sec = tm_usec = 0;
|
2015-10-15 01:57:59 +02:00
|
|
|
if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
|
2016-02-23 05:32:04 +01:00
|
|
|
c.tm_mday++;
|
2015-11-16 09:15:05 +01:00
|
|
|
c.tm_hour = c.tm_min = c.tm_sec = tm_usec = 0;
|
2012-11-23 21:37:58 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2016-11-22 16:05:10 +01:00
|
|
|
r = find_matching_component(spec, spec->minute, &c, &c.tm_min);
|
2012-11-23 21:37:58 +01:00
|
|
|
if (r > 0)
|
2016-02-22 14:02:48 +01:00
|
|
|
c.tm_sec = tm_usec = 0;
|
2015-10-15 01:57:59 +02:00
|
|
|
if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
|
2016-02-23 05:32:04 +01:00
|
|
|
c.tm_hour++;
|
2015-11-16 09:15:05 +01:00
|
|
|
c.tm_min = c.tm_sec = tm_usec = 0;
|
2012-11-23 21:37:58 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2015-11-16 09:15:05 +01:00
|
|
|
c.tm_sec = c.tm_sec * USEC_PER_SEC + tm_usec;
|
2016-11-22 16:05:10 +01:00
|
|
|
r = find_matching_component(spec, spec->microsecond, &c, &c.tm_sec);
|
2015-11-16 09:15:05 +01:00
|
|
|
tm_usec = c.tm_sec % USEC_PER_SEC;
|
|
|
|
c.tm_sec /= USEC_PER_SEC;
|
|
|
|
|
2015-10-15 01:57:59 +02:00
|
|
|
if (r < 0 || tm_out_of_bounds(&c, spec->utc)) {
|
2016-02-23 05:32:04 +01:00
|
|
|
c.tm_min++;
|
2015-11-16 09:15:05 +01:00
|
|
|
c.tm_sec = tm_usec = 0;
|
2012-11-23 21:37:58 +01:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
*tm = c;
|
2015-11-16 09:15:05 +01:00
|
|
|
*usec = tm_usec;
|
2012-11-23 21:37:58 +01:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-09-06 11:56:36 +02:00
|
|
|
static int calendar_spec_next_usec_impl(const CalendarSpec *spec, usec_t usec, usec_t *next) {
|
2012-11-23 21:37:58 +01:00
|
|
|
struct tm tm;
|
|
|
|
time_t t;
|
|
|
|
int r;
|
2015-11-16 09:15:05 +01:00
|
|
|
usec_t tm_usec;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
|
|
|
assert(spec);
|
|
|
|
assert(next);
|
|
|
|
|
2017-02-02 18:30:29 +01:00
|
|
|
if (usec > USEC_TIMESTAMP_FORMATTABLE_MAX)
|
|
|
|
return -EINVAL;
|
|
|
|
|
2015-11-16 09:15:05 +01:00
|
|
|
usec++;
|
|
|
|
t = (time_t) (usec / USEC_PER_SEC);
|
2015-10-15 01:57:59 +02:00
|
|
|
assert_se(localtime_or_gmtime_r(&t, &tm, spec->utc));
|
2015-11-16 09:15:05 +01:00
|
|
|
tm_usec = usec % USEC_PER_SEC;
|
2012-11-23 21:37:58 +01:00
|
|
|
|
2015-11-16 09:15:05 +01:00
|
|
|
r = find_next(spec, &tm, &tm_usec);
|
2012-11-23 21:37:58 +01:00
|
|
|
if (r < 0)
|
|
|
|
return r;
|
|
|
|
|
2015-10-15 01:57:59 +02:00
|
|
|
t = mktime_or_timegm(&tm, spec->utc);
|
2017-02-02 18:25:33 +01:00
|
|
|
if (t < 0)
|
2012-11-23 21:37:58 +01:00
|
|
|
return -EINVAL;
|
|
|
|
|
2015-11-16 09:15:05 +01:00
|
|
|
*next = (usec_t) t * USEC_PER_SEC + tm_usec;
|
2012-11-23 21:37:58 +01:00
|
|
|
return 0;
|
|
|
|
}
|
2017-09-06 11:56:36 +02:00
|
|
|
|
|
|
|
typedef struct SpecNextResult {
|
|
|
|
usec_t next;
|
|
|
|
int return_value;
|
|
|
|
} SpecNextResult;
|
|
|
|
|
|
|
|
int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) {
|
2017-12-22 13:08:14 +01:00
|
|
|
SpecNextResult *shared, tmp;
|
2017-09-06 11:56:36 +02:00
|
|
|
int r;
|
|
|
|
|
|
|
|
if (isempty(spec->timezone))
|
|
|
|
return calendar_spec_next_usec_impl(spec, usec, next);
|
|
|
|
|
|
|
|
shared = mmap(NULL, sizeof *shared, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, -1, 0);
|
|
|
|
if (shared == MAP_FAILED)
|
|
|
|
return negative_errno();
|
|
|
|
|
2017-12-29 18:01:37 +01:00
|
|
|
r = safe_fork("(sd-calendar)", FORK_RESET_SIGNALS|FORK_CLOSE_ALL_FDS|FORK_DEATHSIG|FORK_WAIT, NULL);
|
2017-12-22 13:08:14 +01:00
|
|
|
if (r < 0) {
|
2017-09-06 11:56:36 +02:00
|
|
|
(void) munmap(shared, sizeof *shared);
|
2017-12-22 13:08:14 +01:00
|
|
|
return r;
|
2017-09-06 11:56:36 +02:00
|
|
|
}
|
2017-12-22 13:08:14 +01:00
|
|
|
if (r == 0) {
|
2017-09-06 11:56:36 +02:00
|
|
|
if (setenv("TZ", spec->timezone, 1) != 0) {
|
|
|
|
shared->return_value = negative_errno();
|
|
|
|
_exit(EXIT_FAILURE);
|
|
|
|
}
|
|
|
|
|
|
|
|
tzset();
|
|
|
|
|
|
|
|
shared->return_value = calendar_spec_next_usec_impl(spec, usec, &shared->next);
|
|
|
|
|
|
|
|
_exit(EXIT_SUCCESS);
|
|
|
|
}
|
|
|
|
|
|
|
|
tmp = *shared;
|
2017-12-29 18:01:37 +01:00
|
|
|
if (munmap(shared, sizeof *shared) < 0)
|
2017-09-06 11:56:36 +02:00
|
|
|
return negative_errno();
|
|
|
|
|
|
|
|
if (tmp.return_value == 0)
|
|
|
|
*next = tmp.next;
|
|
|
|
|
|
|
|
return tmp.return_value;
|
|
|
|
}
|