LCOV - code coverage report
Current view: top level - basic - calendarspec.c (source / functions) Hit Total Coverage
Test: systemd test coverage Lines: 523 619 84.5 %
Date: 2015-07-29 18:47:03 Functions: 23 23 100.0 %

          Line data    Source code
       1             : /*-*- Mode: C; c-basic-offset: 8; indent-tabs-mode: nil -*-*/
       2             : 
       3             : /***
       4             :   This file is part of systemd.
       5             : 
       6             :   Copyright 2012 Lennart Poettering
       7             : 
       8             :   systemd is free software; you can redistribute it and/or modify it
       9             :   under the terms of the GNU Lesser General Public License as published by
      10             :   the Free Software Foundation; either version 2.1 of the License, or
      11             :   (at your option) any later version.
      12             : 
      13             :   systemd is distributed in the hope that it will be useful, but
      14             :   WITHOUT ANY WARRANTY; without even the implied warranty of
      15             :   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
      16             :   Lesser General Public License for more details.
      17             : 
      18             :   You should have received a copy of the GNU Lesser General Public License
      19             :   along with systemd; If not, see <http://www.gnu.org/licenses/>.
      20             : ***/
      21             : 
      22             : #include <stdlib.h>
      23             : #include <string.h>
      24             : 
      25             : #include "calendarspec.h"
      26             : 
      27             : #define BITS_WEEKDAYS   127
      28             : 
      29         378 : static void free_chain(CalendarComponent *c) {
      30             :         CalendarComponent *n;
      31             : 
      32        1020 :         while (c) {
      33         264 :                 n = c->next;
      34         264 :                 free(c);
      35         264 :                 c = n;
      36             :         }
      37         378 : }
      38             : 
      39          61 : void calendar_spec_free(CalendarSpec *c) {
      40             : 
      41          61 :         if (!c)
      42           0 :                 return;
      43             : 
      44          61 :         free_chain(c->year);
      45          61 :         free_chain(c->month);
      46          61 :         free_chain(c->day);
      47          61 :         free_chain(c->hour);
      48          61 :         free_chain(c->minute);
      49          61 :         free_chain(c->second);
      50             : 
      51          61 :         free(c);
      52             : }
      53             : 
      54          34 : static int component_compare(const void *_a, const void *_b) {
      55          34 :         CalendarComponent * const *a = _a, * const *b = _b;
      56             : 
      57          34 :         if ((*a)->value < (*b)->value)
      58           6 :                 return -1;
      59          28 :         if ((*a)->value > (*b)->value)
      60          27 :                 return 1;
      61             : 
      62           1 :         if ((*a)->repeat < (*b)->repeat)
      63           0 :                 return -1;
      64           1 :         if ((*a)->repeat > (*b)->repeat)
      65           0 :                 return 1;
      66             : 
      67           1 :         return 0;
      68             : }
      69             : 
      70         354 : static void sort_chain(CalendarComponent **c) {
      71         354 :         unsigned n = 0, k;
      72             :         CalendarComponent **b, *i, **j, *next;
      73             : 
      74         354 :         assert(c);
      75             : 
      76         608 :         for (i = *c; i; i = i->next)
      77         254 :                 n++;
      78             : 
      79         354 :         if (n <= 1)
      80         340 :                 return;
      81             : 
      82          14 :         j = b = alloca(sizeof(CalendarComponent*) * n);
      83          53 :         for (i = *c; i; i = i->next)
      84          39 :                 *(j++) = i;
      85             : 
      86          14 :         qsort(b, n, sizeof(CalendarComponent*), component_compare);
      87             : 
      88          14 :         b[n-1]->next = NULL;
      89          14 :         next = b[n-1];
      90             : 
      91             :         /* Drop non-unique entries */
      92          39 :         for (k = n-1; k > 0; k--) {
      93          26 :                 if (b[k-1]->value == next->value &&
      94           1 :                     b[k-1]->repeat == next->repeat) {
      95           1 :                         free(b[k-1]);
      96           1 :                         continue;
      97             :                 }
      98             : 
      99          24 :                 b[k-1]->next = next;
     100          24 :                 next = b[k-1];
     101             :         }
     102             : 
     103          14 :         *c = next;
     104             : }
     105             : 
     106          59 : static void fix_year(CalendarComponent *c) {
     107             :         /* Turns 12 → 2012, 89 → 1989 */
     108             : 
     109         126 :         while(c) {
     110           8 :                 CalendarComponent *n = c->next;
     111             : 
     112           8 :                 if (c->value >= 0 && c->value < 70)
     113           2 :                         c->value += 2000;
     114             : 
     115           8 :                 if (c->value >= 70 && c->value < 100)
     116           0 :                         c->value += 1900;
     117             : 
     118           8 :                 c = n;
     119             :         }
     120          59 : }
     121             : 
     122          59 : int calendar_spec_normalize(CalendarSpec *c) {
     123          59 :         assert(c);
     124             : 
     125          59 :         if (c->weekdays_bits <= 0 || c->weekdays_bits >= BITS_WEEKDAYS)
     126          35 :                 c->weekdays_bits = -1;
     127             : 
     128          59 :         fix_year(c->year);
     129             : 
     130          59 :         sort_chain(&c->year);
     131          59 :         sort_chain(&c->month);
     132          59 :         sort_chain(&c->day);
     133          59 :         sort_chain(&c->hour);
     134          59 :         sort_chain(&c->minute);
     135          59 :         sort_chain(&c->second);
     136             : 
     137          59 :         return 0;
     138             : }
     139             : 
     140         376 : _pure_ static bool chain_valid(CalendarComponent *c, int from, int to) {
     141         376 :         if (!c)
     142         125 :                 return true;
     143             : 
     144         251 :         if (c->value < from || c->value > to)
     145           1 :                 return false;
     146             : 
     147         250 :         if (c->value + c->repeat > to)
     148           0 :                 return false;
     149             : 
     150         250 :         if (c->next)
     151          24 :                 return chain_valid(c->next, from, to);
     152             : 
     153         226 :         return true;
     154             : }
     155             : 
     156          59 : _pure_ bool calendar_spec_valid(CalendarSpec *c) {
     157          59 :         assert(c);
     158             : 
     159          59 :         if (c->weekdays_bits > BITS_WEEKDAYS)
     160           0 :                 return false;
     161             : 
     162          59 :         if (!chain_valid(c->year, 1970, 2199))
     163           0 :                 return false;
     164             : 
     165          59 :         if (!chain_valid(c->month, 1, 12))
     166           0 :                 return false;
     167             : 
     168          59 :         if (!chain_valid(c->day, 1, 31))
     169           0 :                 return false;
     170             : 
     171          59 :         if (!chain_valid(c->hour, 0, 23))
     172           1 :                 return false;
     173             : 
     174          58 :         if (!chain_valid(c->minute, 0, 59))
     175           0 :                 return false;
     176             : 
     177          58 :         if (!chain_valid(c->second, 0, 59))
     178           0 :                 return false;
     179             : 
     180          58 :         return true;
     181             : }
     182             : 
     183          24 : static void format_weekdays(FILE *f, const CalendarSpec *c) {
     184             :         static const char *const days[] = {
     185             :                 "Mon",
     186             :                 "Tue",
     187             :                 "Wed",
     188             :                 "Thu",
     189             :                 "Fri",
     190             :                 "Sat",
     191             :                 "Sun"
     192             :         };
     193             : 
     194             :         int l, x;
     195          24 :         bool need_colon = false;
     196             : 
     197          24 :         assert(f);
     198          24 :         assert(c);
     199          24 :         assert(c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS);
     200             : 
     201         192 :         for (x = 0, l = -1; x < (int) ELEMENTSOF(days); x++) {
     202             : 
     203         168 :                 if (c->weekdays_bits & (1 << x)) {
     204             : 
     205          52 :                         if (l < 0) {
     206          32 :                                 if (need_colon)
     207           8 :                                         fputc(',', f);
     208             :                                 else
     209          24 :                                         need_colon = true;
     210             : 
     211          32 :                                 fputs(days[x], f);
     212          32 :                                 l = x;
     213             :                         }
     214             : 
     215         116 :                 } else if (l >= 0) {
     216             : 
     217          24 :                         if (x > l + 1) {
     218           4 :                                 fputc(x > l + 2 ? '-' : ',', f);
     219           4 :                                 fputs(days[x-1], f);
     220             :                         }
     221             : 
     222          24 :                         l = -1;
     223             :                 }
     224             :         }
     225             : 
     226          24 :         if (l >= 0 && x > l + 1) {
     227           6 :                 fputc(x > l + 2 ? '-' : ',', f);
     228           6 :                 fputs(days[x-1], f);
     229             :         }
     230          24 : }
     231             : 
     232         372 : static void format_chain(FILE *f, int space, const CalendarComponent *c) {
     233         372 :         assert(f);
     234             : 
     235         372 :         if (!c) {
     236         122 :                 fputc('*', f);
     237         122 :                 return;
     238             :         }
     239             : 
     240         250 :         assert(c->value >= 0);
     241         250 :         fprintf(f, "%0*i", space, c->value);
     242             : 
     243         250 :         if (c->repeat > 0)
     244           4 :                 fprintf(f, "/%i", c->repeat);
     245             : 
     246         250 :         if (c->next) {
     247          24 :                 fputc(',', f);
     248          24 :                 format_chain(f, space, c->next);
     249             :         }
     250             : }
     251             : 
     252          58 : int calendar_spec_to_string(const CalendarSpec *c, char **p) {
     253          58 :         char *buf = NULL;
     254          58 :         size_t sz = 0;
     255             :         FILE *f;
     256             : 
     257          58 :         assert(c);
     258          58 :         assert(p);
     259             : 
     260          58 :         f = open_memstream(&buf, &sz);
     261          58 :         if (!f)
     262           0 :                 return -ENOMEM;
     263             : 
     264          58 :         if (c->weekdays_bits > 0 && c->weekdays_bits <= BITS_WEEKDAYS) {
     265          24 :                 format_weekdays(f, c);
     266          24 :                 fputc(' ', f);
     267             :         }
     268             : 
     269          58 :         format_chain(f, 4, c->year);
     270          58 :         fputc('-', f);
     271          58 :         format_chain(f, 2, c->month);
     272          58 :         fputc('-', f);
     273          58 :         format_chain(f, 2, c->day);
     274          58 :         fputc(' ', f);
     275          58 :         format_chain(f, 2, c->hour);
     276          58 :         fputc(':', f);
     277          58 :         format_chain(f, 2, c->minute);
     278          58 :         fputc(':', f);
     279          58 :         format_chain(f, 2, c->second);
     280             : 
     281          58 :         fflush(f);
     282             : 
     283          58 :         if (ferror(f)) {
     284           0 :                 free(buf);
     285           0 :                 fclose(f);
     286           0 :                 return -ENOMEM;
     287             :         }
     288             : 
     289          58 :         fclose(f);
     290             : 
     291          58 :         *p = buf;
     292          58 :         return 0;
     293             : }
     294             : 
     295          53 : static int parse_weekdays(const char **p, CalendarSpec *c) {
     296             :         static const struct {
     297             :                 const char *name;
     298             :                 const int nr;
     299             :         } day_nr[] = {
     300             :                 { "Monday",    0 },
     301             :                 { "Mon",       0 },
     302             :                 { "Tuesday",   1 },
     303             :                 { "Tue",       1 },
     304             :                 { "Wednesday", 2 },
     305             :                 { "Wed",       2 },
     306             :                 { "Thursday",  3 },
     307             :                 { "Thu",       3 },
     308             :                 { "Friday",    4 },
     309             :                 { "Fri",       4 },
     310             :                 { "Saturday",  5 },
     311             :                 { "Sat",       5 },
     312             :                 { "Sunday",    6 },
     313             :                 { "Sun",       6 }
     314             :         };
     315             : 
     316          53 :         int l = -1;
     317          53 :         bool first = true;
     318             : 
     319          53 :         assert(p);
     320          53 :         assert(*p);
     321          53 :         assert(c);
     322             : 
     323             :         for (;;) {
     324             :                 unsigned i;
     325             : 
     326          77 :                 if (!first && **p == ' ')
     327           1 :                         return 0;
     328             : 
     329        1630 :                 for (i = 0; i < ELEMENTSOF(day_nr); i++) {
     330             :                         size_t skip;
     331             : 
     332         785 :                         if (!startswith_no_case(*p, day_nr[i].name))
     333         739 :                                 continue;
     334             : 
     335          46 :                         skip = strlen(day_nr[i].name);
     336             : 
     337          86 :                         if ((*p)[skip] != '-' &&
     338          62 :                             (*p)[skip] != ',' &&
     339          23 :                             (*p)[skip] != ' ' &&
     340           1 :                             (*p)[skip] != 0)
     341           0 :                                 return -EINVAL;
     342             : 
     343          46 :                         c->weekdays_bits |= 1 << day_nr[i].nr;
     344             : 
     345          46 :                         if (l >= 0) {
     346             :                                 int j;
     347             : 
     348           6 :                                 if (l > day_nr[i].nr)
     349           0 :                                         return -EINVAL;
     350             : 
     351          14 :                                 for (j = l + 1; j < day_nr[i].nr; j++)
     352           8 :                                         c->weekdays_bits |= 1 << j;
     353             :                         }
     354             : 
     355          46 :                         *p += skip;
     356          46 :                         break;
     357             :                 }
     358             : 
     359             :                 /* Couldn't find this prefix, so let's assume the
     360             :                    weekday was not specified and let's continue with
     361             :                    the date */
     362          76 :                 if (i >= ELEMENTSOF(day_nr))
     363          30 :                         return first ? 0 : -EINVAL;
     364             : 
     365             :                 /* We reached the end of the string */
     366          46 :                 if (**p == 0)
     367           1 :                         return 0;
     368             : 
     369             :                 /* We reached the end of the weekday spec part */
     370          45 :                 if (**p == ' ') {
     371          21 :                         *p += strspn(*p, " ");
     372          21 :                         return 0;
     373             :                 }
     374             : 
     375          24 :                 if (**p == '-') {
     376           6 :                         if (l >= 0)
     377           0 :                                 return -EINVAL;
     378             : 
     379           6 :                         l = day_nr[i].nr;
     380             :                 } else
     381          18 :                         l = -1;
     382             : 
     383          24 :                 *p += 1;
     384          24 :                 first = false;
     385          24 :         }
     386             : }
     387             : 
     388         209 : static int prepend_component(const char **p, CalendarComponent **c) {
     389         209 :         unsigned long value, repeat = 0;
     390         209 :         char *e = NULL, *ee = NULL;
     391             :         CalendarComponent *cc;
     392             : 
     393         209 :         assert(p);
     394         209 :         assert(c);
     395             : 
     396         209 :         errno = 0;
     397         209 :         value = strtoul(*p, &e, 10);
     398         209 :         if (errno > 0)
     399           0 :                 return -errno;
     400         209 :         if (e == *p)
     401           1 :                 return -EINVAL;
     402         208 :         if ((unsigned long) (int) value != value)
     403           0 :                 return -ERANGE;
     404             : 
     405         208 :         if (*e == '/') {
     406           4 :                 repeat = strtoul(e+1, &ee, 10);
     407           4 :                 if (errno > 0)
     408           0 :                         return -errno;
     409           4 :                 if (ee == e+1)
     410           0 :                         return -EINVAL;
     411           4 :                 if ((unsigned long) (int) repeat != repeat)
     412           0 :                         return -ERANGE;
     413           4 :                 if (repeat <= 0)
     414           0 :                         return -ERANGE;
     415             : 
     416           4 :                 e = ee;
     417             :         }
     418             : 
     419         208 :         if (*e != 0 && *e != ' ' && *e != ',' && *e != '-' && *e != ':')
     420           0 :                 return -EINVAL;
     421             : 
     422         208 :         cc = new0(CalendarComponent, 1);
     423         208 :         if (!cc)
     424           0 :                 return -ENOMEM;
     425             : 
     426         208 :         cc->value = value;
     427         208 :         cc->repeat = repeat;
     428         208 :         cc->next = *c;
     429             : 
     430         208 :         *p = e;
     431         208 :         *c = cc;
     432             : 
     433         208 :         if (*e ==',') {
     434          24 :                 *p += 1;
     435          24 :                 return prepend_component(p, c);
     436             :         }
     437             : 
     438         184 :         return 0;
     439             : }
     440             : 
     441         261 : static int parse_chain(const char **p, CalendarComponent **c) {
     442             :         const char *t;
     443         261 :         CalendarComponent *cc = NULL;
     444             :         int r;
     445             : 
     446         261 :         assert(p);
     447         261 :         assert(c);
     448             : 
     449         261 :         t = *p;
     450             : 
     451         261 :         if (t[0] == '*') {
     452          76 :                 *p = t + 1;
     453          76 :                 *c = NULL;
     454          76 :                 return 0;
     455             :         }
     456             : 
     457         185 :         r = prepend_component(&t, &cc);
     458         185 :         if (r < 0) {
     459           1 :                 free_chain(cc);
     460           1 :                 return r;
     461             :         }
     462             : 
     463         184 :         *p = t;
     464         184 :         *c = cc;
     465         184 :         return 0;
     466             : }
     467             : 
     468          57 : static int const_chain(int value, CalendarComponent **c) {
     469          57 :         CalendarComponent *cc = NULL;
     470             : 
     471          57 :         assert(c);
     472             : 
     473          57 :         cc = new0(CalendarComponent, 1);
     474          57 :         if (!cc)
     475           0 :                 return -ENOMEM;
     476             : 
     477          57 :         cc->value = value;
     478          57 :         cc->repeat = 0;
     479          57 :         cc->next = *c;
     480             : 
     481          57 :         *c = cc;
     482             : 
     483          57 :         return 0;
     484             : }
     485             : 
     486          53 : static int parse_date(const char **p, CalendarSpec *c) {
     487             :         const char *t;
     488             :         int r;
     489             :         CalendarComponent *first, *second, *third;
     490             : 
     491          53 :         assert(p);
     492          53 :         assert(*p);
     493          53 :         assert(c);
     494             : 
     495          53 :         t = *p;
     496             : 
     497          53 :         if (*t == 0)
     498           1 :                 return 0;
     499             : 
     500          52 :         r = parse_chain(&t, &first);
     501          52 :         if (r < 0)
     502           1 :                 return r;
     503             : 
     504             :         /* Already the end? A ':' as separator? In that case this was a time, not a date */
     505          51 :         if (*t == 0 || *t == ':') {
     506           8 :                 free_chain(first);
     507           8 :                 return 0;
     508             :         }
     509             : 
     510          43 :         if (*t != '-') {
     511           0 :                 free_chain(first);
     512           0 :                 return -EINVAL;
     513             :         }
     514             : 
     515          43 :         t++;
     516          43 :         r = parse_chain(&t, &second);
     517          43 :         if (r < 0) {
     518           0 :                 free_chain(first);
     519           0 :                 return r;
     520             :         }
     521             : 
     522             :         /* Got two parts, hence it's month and day */
     523          43 :         if (*t == ' ' || *t == 0) {
     524           6 :                 *p = t + strspn(t, " ");
     525           6 :                 c->month = first;
     526           6 :                 c->day = second;
     527           6 :                 return 0;
     528             :         }
     529             : 
     530          37 :         if (*t != '-') {
     531           0 :                 free_chain(first);
     532           0 :                 free_chain(second);
     533           0 :                 return -EINVAL;
     534             :         }
     535             : 
     536          37 :         t++;
     537          37 :         r = parse_chain(&t, &third);
     538          37 :         if (r < 0) {
     539           0 :                 free_chain(first);
     540           0 :                 free_chain(second);
     541           0 :                 return r;
     542             :         }
     543             : 
     544             :         /* Got tree parts, hence it is year, month and day */
     545          37 :         if (*t == ' ' || *t == 0) {
     546          37 :                 *p = t + strspn(t, " ");
     547          37 :                 c->year = first;
     548          37 :                 c->month = second;
     549          37 :                 c->day = third;
     550          37 :                 return 0;
     551             :         }
     552             : 
     553           0 :         free_chain(first);
     554           0 :         free_chain(second);
     555           0 :         free_chain(third);
     556           0 :         return -EINVAL;
     557             : }
     558             : 
     559          52 : static int parse_time(const char **p, CalendarSpec *c) {
     560          52 :         CalendarComponent *h = NULL, *m = NULL, *s = NULL;
     561             :         const char *t;
     562             :         int r;
     563             : 
     564          52 :         assert(p);
     565          52 :         assert(*p);
     566          52 :         assert(c);
     567             : 
     568          52 :         t = *p;
     569             : 
     570          52 :         if (*t == 0) {
     571             :                 /* If no time is specified at all, but a date of some
     572             :                  * kind, then this means 00:00:00 */
     573           6 :                 if (c->day || c->weekdays_bits > 0)
     574             :                         goto null_hour;
     575             : 
     576           0 :                 goto finish;
     577             :         }
     578             : 
     579          46 :         r = parse_chain(&t, &h);
     580          46 :         if (r < 0)
     581           0 :                 goto fail;
     582             : 
     583          46 :         if (*t != ':') {
     584           1 :                 r = -EINVAL;
     585           1 :                 goto fail;
     586             :         }
     587             : 
     588          45 :         t++;
     589          45 :         r = parse_chain(&t, &m);
     590          45 :         if (r < 0)
     591           0 :                 goto fail;
     592             : 
     593             :         /* Already at the end? Then it's hours and minutes, and seconds are 0 */
     594          45 :         if (*t == 0) {
     595           7 :                 if (m != NULL)
     596           7 :                         goto null_second;
     597             : 
     598           0 :                 goto finish;
     599             :         }
     600             : 
     601          38 :         if (*t != ':') {
     602           0 :                 r = -EINVAL;
     603           0 :                 goto fail;
     604             :         }
     605             : 
     606          38 :         t++;
     607          38 :         r = parse_chain(&t, &s);
     608          38 :         if (r < 0)
     609           0 :                 goto fail;
     610             : 
     611             :         /* At the end? Then it's hours, minutes and seconds */
     612          38 :         if (*t == 0)
     613          38 :                 goto finish;
     614             : 
     615           0 :         r = -EINVAL;
     616           0 :         goto fail;
     617             : 
     618             : null_hour:
     619           6 :         r = const_chain(0, &h);
     620           6 :         if (r < 0)
     621           0 :                 goto fail;
     622             : 
     623           6 :         r = const_chain(0, &m);
     624           6 :         if (r < 0)
     625           0 :                 goto fail;
     626             : 
     627             : null_second:
     628          13 :         r = const_chain(0, &s);
     629          13 :         if (r < 0)
     630           0 :                 goto fail;
     631             : 
     632             : finish:
     633          51 :         *p = t;
     634          51 :         c->hour = h;
     635          51 :         c->minute = m;
     636          51 :         c->second = s;
     637          51 :         return 0;
     638             : 
     639             : fail:
     640           1 :         free_chain(h);
     641           1 :         free_chain(m);
     642           1 :         free_chain(s);
     643           1 :         return r;
     644             : }
     645             : 
     646          62 : int calendar_spec_from_string(const char *p, CalendarSpec **spec) {
     647             :         CalendarSpec *c;
     648             :         int r;
     649             : 
     650          62 :         assert(p);
     651          62 :         assert(spec);
     652             : 
     653          62 :         if (isempty(p))
     654           1 :                 return -EINVAL;
     655             : 
     656          61 :         c = new0(CalendarSpec, 1);
     657          61 :         if (!c)
     658           0 :                 return -ENOMEM;
     659             : 
     660          61 :         if (strcaseeq(p, "minutely")) {
     661           1 :                 r = const_chain(0, &c->second);
     662           1 :                 if (r < 0)
     663           0 :                         goto fail;
     664             : 
     665          60 :         } else if (strcaseeq(p, "hourly")) {
     666           1 :                 r = const_chain(0, &c->minute);
     667           1 :                 if (r < 0)
     668           0 :                         goto fail;
     669           1 :                 r = const_chain(0, &c->second);
     670           1 :                 if (r < 0)
     671           0 :                         goto fail;
     672             : 
     673          59 :         } else if (strcaseeq(p, "daily")) {
     674           1 :                 r = const_chain(0, &c->hour);
     675           1 :                 if (r < 0)
     676           0 :                         goto fail;
     677           1 :                 r = const_chain(0, &c->minute);
     678           1 :                 if (r < 0)
     679           0 :                         goto fail;
     680           1 :                 r = const_chain(0, &c->second);
     681           1 :                 if (r < 0)
     682           0 :                         goto fail;
     683             : 
     684          58 :         } else if (strcaseeq(p, "monthly")) {
     685           1 :                 r = const_chain(1, &c->day);
     686           1 :                 if (r < 0)
     687           0 :                         goto fail;
     688           1 :                 r = const_chain(0, &c->hour);
     689           1 :                 if (r < 0)
     690           0 :                         goto fail;
     691           1 :                 r = const_chain(0, &c->minute);
     692           1 :                 if (r < 0)
     693           0 :                         goto fail;
     694           1 :                 r = const_chain(0, &c->second);
     695           1 :                 if (r < 0)
     696           0 :                         goto fail;
     697             : 
     698         113 :         } else if (strcaseeq(p, "annually") ||
     699         112 :                    strcaseeq(p, "yearly") ||
     700          56 :                    strcaseeq(p, "anually") /* backwards compatibility */ ) {
     701             : 
     702           1 :                 r = const_chain(1, &c->month);
     703           1 :                 if (r < 0)
     704           0 :                         goto fail;
     705           1 :                 r = const_chain(1, &c->day);
     706           1 :                 if (r < 0)
     707           0 :                         goto fail;
     708           1 :                 r = const_chain(0, &c->hour);
     709           1 :                 if (r < 0)
     710           0 :                         goto fail;
     711           1 :                 r = const_chain(0, &c->minute);
     712           1 :                 if (r < 0)
     713           0 :                         goto fail;
     714           1 :                 r = const_chain(0, &c->second);
     715           2 :                 if (r < 0)
     716           0 :                         goto fail;
     717             : 
     718          56 :         } else if (strcaseeq(p, "weekly")) {
     719             : 
     720           1 :                 c->weekdays_bits = 1;
     721             : 
     722           1 :                 r = const_chain(0, &c->hour);
     723           1 :                 if (r < 0)
     724           0 :                         goto fail;
     725           1 :                 r = const_chain(0, &c->minute);
     726           1 :                 if (r < 0)
     727           0 :                         goto fail;
     728           1 :                 r = const_chain(0, &c->second);
     729           1 :                 if (r < 0)
     730           0 :                         goto fail;
     731             : 
     732          55 :         } else if (strcaseeq(p, "quarterly")) {
     733             : 
     734           1 :                 r = const_chain(1, &c->month);
     735           1 :                 if (r < 0)
     736           0 :                         goto fail;
     737           1 :                 r = const_chain(4, &c->month);
     738           1 :                 if (r < 0)
     739           0 :                         goto fail;
     740           1 :                 r = const_chain(7, &c->month);
     741           1 :                 if (r < 0)
     742           0 :                         goto fail;
     743           1 :                 r = const_chain(10, &c->month);
     744           1 :                 if (r < 0)
     745           0 :                         goto fail;
     746           1 :                 r = const_chain(1, &c->day);
     747           1 :                 if (r < 0)
     748           0 :                         goto fail;
     749           1 :                 r = const_chain(0, &c->hour);
     750           1 :                 if (r < 0)
     751           0 :                         goto fail;
     752           1 :                 r = const_chain(0, &c->minute);
     753           1 :                 if (r < 0)
     754           0 :                         goto fail;
     755           1 :                 r = const_chain(0, &c->second);
     756           1 :                 if (r < 0)
     757           0 :                         goto fail;
     758             : 
     759         108 :         } else if (strcaseeq(p, "biannually") ||
     760         108 :                    strcaseeq(p, "bi-annually") ||
     761         108 :                    strcaseeq(p, "semiannually") ||
     762          54 :                    strcaseeq(p, "semi-annually")) {
     763             : 
     764           1 :                 r = const_chain(1, &c->month);
     765           1 :                 if (r < 0)
     766           0 :                         goto fail;
     767           1 :                 r = const_chain(7, &c->month);
     768           1 :                 if (r < 0)
     769           0 :                         goto fail;
     770           1 :                 r = const_chain(1, &c->day);
     771           1 :                 if (r < 0)
     772           0 :                         goto fail;
     773           1 :                 r = const_chain(0, &c->hour);
     774           1 :                 if (r < 0)
     775           0 :                         goto fail;
     776           1 :                 r = const_chain(0, &c->minute);
     777           1 :                 if (r < 0)
     778           0 :                         goto fail;
     779           1 :                 r = const_chain(0, &c->second);
     780           2 :                 if (r < 0)
     781           0 :                         goto fail;
     782             : 
     783             :         } else {
     784          53 :                 r = parse_weekdays(&p, c);
     785          53 :                 if (r < 0)
     786           0 :                         goto fail;
     787             : 
     788          53 :                 r = parse_date(&p, c);
     789          53 :                 if (r < 0)
     790           1 :                         goto fail;
     791             : 
     792          52 :                 r = parse_time(&p, c);
     793          52 :                 if (r < 0)
     794           1 :                         goto fail;
     795             : 
     796          51 :                 if (*p != 0) {
     797           0 :                         r = -EINVAL;
     798           0 :                         goto fail;
     799             :                 }
     800             :         }
     801             : 
     802          59 :         r = calendar_spec_normalize(c);
     803          59 :         if (r < 0)
     804           0 :                 goto fail;
     805             : 
     806          59 :         if (!calendar_spec_valid(c)) {
     807           1 :                 r = -EINVAL;
     808           1 :                 goto fail;
     809             :         }
     810             : 
     811          58 :         *spec = c;
     812          58 :         return 0;
     813             : 
     814             : fail:
     815           3 :         calendar_spec_free(c);
     816           3 :         return r;
     817             : }
     818             : 
     819         427 : static int find_matching_component(const CalendarComponent *c, int *val) {
     820             :         const CalendarComponent *n;
     821         427 :         int d = -1;
     822         427 :         bool d_set = false;
     823             :         int r;
     824             : 
     825         427 :         assert(val);
     826             : 
     827         427 :         if (!c)
     828         248 :                 return 0;
     829             : 
     830         568 :         while (c) {
     831         210 :                 n = c->next;
     832             : 
     833         210 :                 if (c->value >= *val) {
     834             : 
     835         136 :                         if (!d_set || c->value < d) {
     836         124 :                                 d = c->value;
     837         124 :                                 d_set = true;
     838             :                         }
     839             : 
     840          74 :                 } else if (c->repeat > 0) {
     841             :                         int k;
     842             : 
     843           9 :                         k = c->value + c->repeat * ((*val - c->value + c->repeat -1) / c->repeat);
     844             : 
     845           9 :                         if (!d_set || k < d) {
     846           9 :                                 d = k;
     847           9 :                                 d_set = true;
     848             :                         }
     849             :                 }
     850             : 
     851         210 :                 c = n;
     852             :         }
     853             : 
     854         179 :         if (!d_set)
     855          46 :                 return -ENOENT;
     856             : 
     857         133 :         r = *val != d;
     858         133 :         *val = d;
     859         133 :         return r;
     860             : }
     861             : 
     862         381 : static bool tm_out_of_bounds(const struct tm *tm) {
     863             :         struct tm t;
     864         381 :         assert(tm);
     865             : 
     866         381 :         t = *tm;
     867             : 
     868         381 :         if (mktime(&t) == (time_t) -1)
     869           0 :                 return true;
     870             : 
     871             :         /* Did any normalization take place? If so, it was out of bounds before */
     872             :         return
     873        1142 :                 t.tm_year != tm->tm_year ||
     874         760 :                 t.tm_mon != tm->tm_mon ||
     875         760 :                 t.tm_mday != tm->tm_mday ||
     876         760 :                 t.tm_hour != tm->tm_hour ||
     877        1141 :                 t.tm_min != tm->tm_min ||
     878         380 :                 t.tm_sec != tm->tm_sec;
     879             : }
     880             : 
     881          79 : static bool matches_weekday(int weekdays_bits, const struct tm *tm) {
     882             :         struct tm t;
     883             :         int k;
     884             : 
     885          79 :         if (weekdays_bits < 0 || weekdays_bits >= BITS_WEEKDAYS)
     886          21 :                 return true;
     887             : 
     888          58 :         t = *tm;
     889          58 :         if (mktime(&t) == (time_t) -1)
     890           0 :                 return false;
     891             : 
     892          58 :         k = t.tm_wday == 0 ? 6 : t.tm_wday - 1;
     893          58 :         return (weekdays_bits & (1 << k));
     894             : }
     895             : 
     896          29 : static int find_next(const CalendarSpec *spec, struct tm *tm) {
     897             :         struct tm c;
     898             :         int r;
     899             : 
     900          29 :         assert(spec);
     901          29 :         assert(tm);
     902             : 
     903          29 :         c = *tm;
     904             : 
     905             :         for (;;) {
     906             :                 /* Normalize the current date */
     907         118 :                 mktime(&c);
     908         118 :                 c.tm_isdst = -1;
     909             : 
     910         118 :                 c.tm_year += 1900;
     911         118 :                 r = find_matching_component(spec->year, &c.tm_year);
     912         118 :                 c.tm_year -= 1900;
     913             : 
     914         118 :                 if (r > 0) {
     915           0 :                         c.tm_mon = 0;
     916           0 :                         c.tm_mday = 1;
     917           0 :                         c.tm_hour = c.tm_min = c.tm_sec = 0;
     918             :                 }
     919         118 :                 if (r < 0 || tm_out_of_bounds(&c))
     920           4 :                         return r;
     921             : 
     922         114 :                 c.tm_mon += 1;
     923         114 :                 r = find_matching_component(spec->month, &c.tm_mon);
     924         114 :                 c.tm_mon -= 1;
     925             : 
     926         114 :                 if (r > 0) {
     927           9 :                         c.tm_mday = 1;
     928           9 :                         c.tm_hour = c.tm_min = c.tm_sec = 0;
     929             :                 }
     930         114 :                 if (r < 0 || tm_out_of_bounds(&c)) {
     931           5 :                         c.tm_year ++;
     932           5 :                         c.tm_mon = 0;
     933           5 :                         c.tm_mday = 1;
     934           5 :                         c.tm_hour = c.tm_min = c.tm_sec = 0;
     935           5 :                         continue;
     936             :                 }
     937             : 
     938         109 :                 r = find_matching_component(spec->day, &c.tm_mday);
     939         109 :                 if (r > 0)
     940           7 :                         c.tm_hour = c.tm_min = c.tm_sec = 0;
     941         109 :                 if (r < 0 || tm_out_of_bounds(&c)) {
     942          30 :                         c.tm_mon ++;
     943          30 :                         c.tm_mday = 1;
     944          30 :                         c.tm_hour = c.tm_min = c.tm_sec = 0;
     945          30 :                         continue;
     946             :                 }
     947             : 
     948          79 :                 if (!matches_weekday(spec->weekdays_bits, &c)) {
     949          46 :                         c.tm_mday++;
     950          46 :                         c.tm_hour = c.tm_min = c.tm_sec = 0;
     951          46 :                         continue;
     952             :                 }
     953             : 
     954          33 :                 r = find_matching_component(spec->hour, &c.tm_hour);
     955          33 :                 if (r > 0)
     956           8 :                         c.tm_min = c.tm_sec = 0;
     957          33 :                 if (r < 0 || tm_out_of_bounds(&c)) {
     958           6 :                         c.tm_mday ++;
     959           6 :                         c.tm_hour = c.tm_min = c.tm_sec = 0;
     960           6 :                         continue;
     961             :                 }
     962             : 
     963          27 :                 r = find_matching_component(spec->minute, &c.tm_min);
     964          27 :                 if (r > 0)
     965          10 :                         c.tm_sec = 0;
     966          27 :                 if (r < 0 || tm_out_of_bounds(&c)) {
     967           1 :                         c.tm_hour ++;
     968           1 :                         c.tm_min = c.tm_sec = 0;
     969           1 :                         continue;
     970             :                 }
     971             : 
     972          26 :                 r = find_matching_component(spec->second, &c.tm_sec);
     973          26 :                 if (r < 0 || tm_out_of_bounds(&c)) {
     974           1 :                         c.tm_min ++;
     975           1 :                         c.tm_sec = 0;
     976           1 :                         continue;
     977             :                 }
     978             : 
     979             : 
     980          25 :                 *tm = c;
     981          25 :                 return 0;
     982          89 :         }
     983             : }
     984             : 
     985          29 : int calendar_spec_next_usec(const CalendarSpec *spec, usec_t usec, usec_t *next) {
     986             :         struct tm tm;
     987             :         time_t t;
     988             :         int r;
     989             : 
     990          29 :         assert(spec);
     991          29 :         assert(next);
     992             : 
     993          29 :         t = (time_t) (usec / USEC_PER_SEC) + 1;
     994          29 :         assert_se(localtime_r(&t, &tm));
     995             : 
     996          29 :         r = find_next(spec, &tm);
     997          29 :         if (r < 0)
     998           4 :                 return r;
     999             : 
    1000          25 :         t = mktime(&tm);
    1001          25 :         if (t == (time_t) -1)
    1002           0 :                 return -EINVAL;
    1003             : 
    1004          25 :         *next = (usec_t) t * USEC_PER_SEC;
    1005          25 :         return 0;
    1006             : }

Generated by: LCOV version 1.11