calendarspec.c revision a095315b3c31f7a419baceac82c26c3c5ac0cd12
/*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
/***
This file is part of systemd.
Copyright 2012 Lennart Poettering
under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation; either version 2.1 of the License, or
(at your option) any later version.
systemd is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with systemd; If not, see <http://www.gnu.org/licenses/>.
***/
#include <stdlib.h>
#include <string.h>
#include "calendarspec.h"
#define BITS_WEEKDAYS 127
static void free_chain(CalendarComponent *c) {
while (c) {
n = c->next;
free(c);
c = n;
}
}
void calendar_spec_free(CalendarSpec *c) {
if (!c)
return;
free_chain(c->year);
free_chain(c->month);
free_chain(c->day);
free_chain(c->hour);
free_chain(c->minute);
free_chain(c->second);
free(c);
}
return -1;
return 1;
return -1;
return 1;
return 0;
}
static void sort_chain(CalendarComponent **c) {
unsigned n = 0, k;
CalendarComponent **b, *i, **j, *next;
assert(c);
for (i = *c; i; i = i->next)
n++;
if (n <= 1)
return;
j = b = alloca(sizeof(CalendarComponent*) * n);
for (i = *c; i; i = i->next)
*(j++) = i;
next = b[n-1];
/* Drop non-unique entries */
for (k = n-1; k > 0; k--) {
free(b[k-1]);
continue;
}
next = b[k-1];
}
*c = next;
}
static void fix_year(CalendarComponent *c) {
/* Turns 12 → 2012, 89 → 1989 */
while(c) {
CalendarComponent *n = c->next;
c->value += 2000;
c->value += 1900;
c = n;
}
}
int calendar_spec_normalize(CalendarSpec *c) {
assert(c);
c->weekdays_bits = -1;
sort_chain(&c->year);
sort_chain(&c->month);
sort_chain(&c->day);
sort_chain(&c->hour);
sort_chain(&c->minute);
sort_chain(&c->second);
return 0;
}
if (!c)
return true;
return false;
return false;
if (c->next)
return true;
}
assert(c);
if (c->weekdays_bits > BITS_WEEKDAYS)
return false;
return false;
return false;
return false;
return false;
return false;
return false;
return true;
}
static const char *const days[] = {
"Mon",
"Tue",
"Wed",
"Thu",
"Fri",
"Sat",
"Sun"
};
int l, x;
bool need_colon = false;
assert(f);
assert(c);
if (c->weekdays_bits & (1 << x)) {
if (l < 0) {
if (need_colon)
fputc(',', f);
else
need_colon = true;
l = x;
}
} else if (l >= 0) {
if (x > l + 1) {
}
l = -1;
}
}
if (l >= 0 && x > l + 1) {
}
}
assert(f);
if (!c) {
fputc('*', f);
return;
}
if (c->repeat > 0)
if (c->next) {
fputc(',', f);
}
}
int calendar_spec_to_string(const CalendarSpec *c, char **p) {
FILE *f;
assert(c);
assert(p);
if (!f)
return -ENOMEM;
format_weekdays(f, c);
fputc(' ', f);
}
fputc('-', f);
fputc('-', f);
fputc(' ', f);
fputc(':', f);
fputc(':', f);
fflush(f);
if (ferror(f)) {
fclose(f);
return -ENOMEM;
}
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 (;;) {
unsigned i;
if (!first && **p == ' ')
return 0;
for (i = 0; i < ELEMENTSOF(day_nr); i++) {
continue;
if ((*p)[skip] != '-' &&
(*p)[skip] != ',' &&
(*p)[skip] != ' ' &&
(*p)[skip] != 0)
return -EINVAL;
if (l >= 0) {
int j;
return -EINVAL;
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))
/* 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;
}
if (**p == '-') {
if (l >= 0)
return -EINVAL;
} else
l = -1;
*p += 1;
first = false;
}
}
static int prepend_component(const char **p, CalendarComponent **c) {
assert(p);
assert(c);
errno = 0;
if (errno > 0)
return -errno;
if (e == *p)
return -EINVAL;
return -ERANGE;
if (*e == '/') {
if (errno > 0)
return -errno;
if (ee == e+1)
return -EINVAL;
return -ERANGE;
if (repeat <= 0)
return -ERANGE;
e = ee;
}
if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':')
return -EINVAL;
if (!cc)
return -ENOMEM;
*p = e;
*c = cc;
if (*e ==',') {
*p += 1;
return prepend_component(p, c);
}
return 0;
}
static int parse_chain(const char **p, CalendarComponent **c) {
const char *t;
int r;
assert(p);
assert(c);
t = *p;
if (t[0] == '*') {
*p = t + 1;
*c = NULL;
return 0;
}
r = prepend_component(&t, &cc);
if (r < 0) {
free_chain(cc);
return r;
}
*p = t;
*c = cc;
return 0;
}
assert(c);
if (!cc)
return -ENOMEM;
*c = cc;
return 0;
}
static int parse_date(const char **p, CalendarSpec *c) {
const char *t;
int r;
assert(p);
assert(*p);
assert(c);
t = *p;
if (*t == 0)
return 0;
r = parse_chain(&t, &first);
if (r < 0)
return r;
/* Already the end? A ':' as separator? In that case this was a time, not a date */
if (*t == 0 || *t == ':') {
return 0;
}
if (*t != '-') {
return -EINVAL;
}
t++;
r = parse_chain(&t, &second);
if (r < 0) {
return r;
}
/* Got two parts, hence it's month and day */
if (*t == ' ' || *t == 0) {
*p = t + strspn(t, " ");
return 0;
}
if (*t != '-') {
return -EINVAL;
}
t++;
r = parse_chain(&t, &third);
if (r < 0) {
return r;
}
/* Got tree parts, hence it is year, month and day */
if (*t == ' ' || *t == 0) {
*p = t + strspn(t, " ");
return 0;
}
return -EINVAL;
}
static int parse_time(const char **p, CalendarSpec *c) {
const char *t;
int r;
assert(p);
assert(*p);
assert(c);
t = *p;
if (*t == 0) {
/* If no time is specified at all, but a date of some
* kind, then this means 00:00:00 */
if (c->day || c->weekdays_bits > 0)
goto null_hour;
goto finish;
}
r = parse_chain(&t, &h);
if (r < 0)
goto fail;
if (*t != ':') {
r = -EINVAL;
goto fail;
}
t++;
r = parse_chain(&t, &m);
if (r < 0)
goto fail;
/* Already at the end? Then it's hours and minutes, and seconds are 0 */
if (*t == 0) {
if (m != NULL)
goto null_second;
goto finish;
}
if (*t != ':') {
r = -EINVAL;
goto fail;
}
t++;
r = parse_chain(&t, &s);
if (r < 0)
goto fail;
/* At the end? Then it's hours, minutes and seconds */
if (*t == 0)
goto finish;
r = -EINVAL;
goto fail;
r = const_chain(0, &h);
if (r < 0)
goto fail;
r = const_chain(0, &m);
if (r < 0)
goto fail;
r = const_chain(0, &s);
if (r < 0)
goto fail;
*p = t;
c->hour = h;
c->minute = m;
c->second = s;
return 0;
fail:
free_chain(h);
free_chain(m);
free_chain(s);
return r;
}
CalendarSpec *c;
int r;
assert(p);
if (isempty(p))
return -EINVAL;
if (!c)
return -ENOMEM;
if (strcaseeq(p, "minutely")) {
r = const_chain(0, &c->second);
if (r < 0)
goto fail;
} else if (strcaseeq(p, "hourly")) {
r = const_chain(0, &c->minute);
if (r < 0)
goto fail;
r = const_chain(0, &c->second);
if (r < 0)
goto fail;
} else if (strcaseeq(p, "daily")) {
r = const_chain(0, &c->hour);
if (r < 0)
goto fail;
r = const_chain(0, &c->minute);
if (r < 0)
goto fail;
r = const_chain(0, &c->second);
if (r < 0)
goto fail;
} else if (strcaseeq(p, "monthly")) {
if (r < 0)
goto fail;
r = const_chain(0, &c->hour);
if (r < 0)
goto fail;
r = const_chain(0, &c->minute);
if (r < 0)
goto fail;
r = const_chain(0, &c->second);
if (r < 0)
goto fail;
} else if (strcaseeq(p, "annually") ||
strcaseeq(p, "yearly") ||
if (r < 0)
goto fail;
if (r < 0)
goto fail;
r = const_chain(0, &c->hour);
if (r < 0)
goto fail;
r = const_chain(0, &c->minute);
if (r < 0)
goto fail;
r = const_chain(0, &c->second);
if (r < 0)
goto fail;
} else if (strcaseeq(p, "weekly")) {
c->weekdays_bits = 1;
r = const_chain(0, &c->hour);
if (r < 0)
goto fail;
r = const_chain(0, &c->minute);
if (r < 0)
goto fail;
r = const_chain(0, &c->second);
if (r < 0)
goto fail;
} else if (strcaseeq(p, "quarterly")) {
if (r < 0)
goto fail;
if (r < 0)
goto fail;
if (r < 0)
goto fail;
if (r < 0)
goto fail;
if (r < 0)
goto fail;
r = const_chain(0, &c->hour);
if (r < 0)
goto fail;
r = const_chain(0, &c->minute);
if (r < 0)
goto fail;
r = const_chain(0, &c->second);
if (r < 0)
goto fail;
} else if (strcaseeq(p, "biannually") ||
strcaseeq(p, "bi-annually") ||
strcaseeq(p, "semiannually") ||
strcaseeq(p, "semi-annually")) {
if (r < 0)
goto fail;
if (r < 0)
goto fail;
if (r < 0)
goto fail;
r = const_chain(0, &c->hour);
if (r < 0)
goto fail;
r = const_chain(0, &c->minute);
if (r < 0)
goto fail;
r = const_chain(0, &c->second);
if (r < 0)
goto fail;
} else {
r = parse_weekdays(&p, c);
if (r < 0)
goto fail;
r = parse_date(&p, c);
if (r < 0)
goto fail;
r = parse_time(&p, c);
if (r < 0)
goto fail;
if (*p != 0) {
r = -EINVAL;
goto fail;
}
}
r = calendar_spec_normalize(c);
if (r < 0)
goto fail;
if (!calendar_spec_valid(c)) {
r = -EINVAL;
goto fail;
}
*spec = c;
return 0;
fail:
return r;
}
const CalendarComponent *n;
int d = -1;
bool d_set = false;
int r;
if (!c)
return 0;
while (c) {
n = c->next;
d = c->value;
d_set = true;
}
} else if (c->repeat > 0) {
int k;
if (!d_set || k < d) {
d = k;
d_set = true;
}
}
c = n;
}
if (!d_set)
return -ENOENT;
r = *val != d;
*val = d;
return r;
}
struct tm t;
t = *tm;
return true;
/* Did any normalization take place? If so, it was out of bounds before */
return
}
struct tm t;
int k;
return true;
t = *tm;
return false;
return (weekdays_bits & (1 << k));
}
struct tm c;
int r;
c = *tm;
for (;;) {
/* Normalize the current date */
mktime(&c);
c.tm_isdst = -1;
c.tm_year += 1900;
c.tm_year -= 1900;
if (r > 0) {
c.tm_mon = 0;
c.tm_mday = 1;
}
if (r < 0 || tm_out_of_bounds(&c))
return r;
c.tm_mon += 1;
c.tm_mon -= 1;
if (r > 0) {
c.tm_mday = 1;
}
if (r < 0 || tm_out_of_bounds(&c)) {
c.tm_year ++;
c.tm_mon = 0;
c.tm_mday = 1;
continue;
}
if (r > 0)
if (r < 0 || tm_out_of_bounds(&c)) {
c.tm_mon ++;
c.tm_mday = 1;
continue;
}
c.tm_mday++;
continue;
}
if (r > 0)
if (r < 0 || tm_out_of_bounds(&c)) {
c.tm_mday ++;
continue;
}
if (r > 0)
c.tm_sec = 0;
if (r < 0 || tm_out_of_bounds(&c)) {
c.tm_hour ++;
continue;
}
if (r < 0 || tm_out_of_bounds(&c)) {
c.tm_min ++;
c.tm_sec = 0;
continue;
}
*tm = c;
return 0;
}
}
time_t t;
int r;
if (r < 0)
return r;
if (t == (time_t) -1)
return -EINVAL;
return 0;
}