Annotation of embedaddon/ntp/sntp/libopts/parse-duration.c, revision 1.1.1.1

1.1       misho       1: /* Parse a time duration and return a seconds count
                      2:    Copyright (C) 2008-2011 Free Software Foundation, Inc.
                      3:    Written by Bruce Korb <bkorb@gnu.org>, 2008.
                      4: 
                      5:    This program is free software: you can redistribute it and/or modify
                      6:    it under the terms of the GNU General Public License as published by
                      7:    the Free Software Foundation; either version 3 of the License, or
                      8:    (at your option) any later version.
                      9: 
                     10:    This program is distributed in the hope that it will be useful,
                     11:    but WITHOUT ANY WARRANTY; without even the implied warranty of
                     12:    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
                     13:    GNU General Public License for more details.
                     14: 
                     15:    You should have received a copy of the GNU General Public License
                     16:    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
                     17: 
                     18: #include <config.h>
                     19: 
                     20: /* Specification.  */
                     21: #include "parse-duration.h"
                     22: 
                     23: #include <ctype.h>
                     24: #include <errno.h>
                     25: #include <limits.h>
                     26: #include <stdio.h>
                     27: #include <stdlib.h>
                     28: #include <string.h>
                     29: 
                     30: #ifndef NUL
                     31: #define NUL '\0'
                     32: #endif
                     33: 
                     34: #define cch_t char const
                     35: 
                     36: typedef enum {
                     37:   NOTHING_IS_DONE,
                     38:   YEAR_IS_DONE,
                     39:   MONTH_IS_DONE,
                     40:   WEEK_IS_DONE,
                     41:   DAY_IS_DONE,
                     42:   HOUR_IS_DONE,
                     43:   MINUTE_IS_DONE,
                     44:   SECOND_IS_DONE
                     45: } whats_done_t;
                     46: 
                     47: #define SEC_PER_MIN     60
                     48: #define SEC_PER_HR      (SEC_PER_MIN * 60)
                     49: #define SEC_PER_DAY     (SEC_PER_HR  * 24)
                     50: #define SEC_PER_WEEK    (SEC_PER_DAY * 7)
                     51: #define SEC_PER_MONTH   (SEC_PER_DAY * 30)
                     52: #define SEC_PER_YEAR    (SEC_PER_DAY * 365)
                     53: 
                     54: #define TIME_MAX        0x7FFFFFFF
                     55: 
                     56: /* Wrapper around strtoul that does not require a cast.  */
                     57: static unsigned long inline
                     58: str_const_to_ul (cch_t * str, cch_t ** ppz, int base)
                     59: {
                     60:   return strtoul (str, (char **)ppz, base);
                     61: }
                     62: 
                     63: /* Wrapper around strtol that does not require a cast.  */
                     64: static long inline
                     65: str_const_to_l (cch_t * str, cch_t ** ppz, int base)
                     66: {
                     67:   return strtol (str, (char **)ppz, base);
                     68: }
                     69: 
                     70: /* Returns BASE + VAL * SCALE, interpreting BASE = BAD_TIME
                     71:    with errno set as an error situation, and returning BAD_TIME
                     72:    with errno set in an error situation.  */
                     73: static time_t inline
                     74: scale_n_add (time_t base, time_t val, int scale)
                     75: {
                     76:   if (base == BAD_TIME)
                     77:     {
                     78:       if (errno == 0)
                     79:         errno = EINVAL;
                     80:       return BAD_TIME;
                     81:     }
                     82: 
                     83:   if (val > TIME_MAX / scale)
                     84:     {
                     85:       errno = ERANGE;
                     86:       return BAD_TIME;
                     87:     }
                     88: 
                     89:   val *= scale;
                     90:   if (base > TIME_MAX - val)
                     91:     {
                     92:       errno = ERANGE;
                     93:       return BAD_TIME;
                     94:     }
                     95: 
                     96:   return base + val;
                     97: }
                     98: 
                     99: /* After a number HH has been parsed, parse subsequent :MM or :MM:SS.  */
                    100: static time_t
                    101: parse_hr_min_sec (time_t start, cch_t * pz)
                    102: {
                    103:   int lpct = 0;
                    104: 
                    105:   errno = 0;
                    106: 
                    107:   /* For as long as our scanner pointer points to a colon *AND*
                    108:      we've not looped before, then keep looping.  (two iterations max) */
                    109:   while ((*pz == ':') && (lpct++ <= 1))
                    110:     {
                    111:       unsigned long v = str_const_to_ul (pz+1, &pz, 10);
                    112: 
                    113:       if (errno != 0)
                    114:         return BAD_TIME;
                    115: 
                    116:       start = scale_n_add (v, start, 60);
                    117: 
                    118:       if (errno != 0)
                    119:         return BAD_TIME;
                    120:     }
                    121: 
                    122:   /* allow for trailing spaces */
                    123:   while (isspace ((unsigned char)*pz))
                    124:     pz++;
                    125:   if (*pz != NUL)
                    126:     {
                    127:       errno = EINVAL;
                    128:       return BAD_TIME;
                    129:     }
                    130: 
                    131:   return start;
                    132: }
                    133: 
                    134: /* Parses a value and returns BASE + value * SCALE, interpreting
                    135:    BASE = BAD_TIME with errno set as an error situation, and returning
                    136:    BAD_TIME with errno set in an error situation.  */
                    137: static time_t
                    138: parse_scaled_value (time_t base, cch_t ** ppz, cch_t * endp, int scale)
                    139: {
                    140:   cch_t * pz = *ppz;
                    141:   time_t val;
                    142: 
                    143:   if (base == BAD_TIME)
                    144:     return base;
                    145: 
                    146:   errno = 0;
                    147:   val = str_const_to_ul (pz, &pz, 10);
                    148:   if (errno != 0)
                    149:     return BAD_TIME;
                    150:   while (isspace ((unsigned char)*pz))
                    151:     pz++;
                    152:   if (pz != endp)
                    153:     {
                    154:       errno = EINVAL;
                    155:       return BAD_TIME;
                    156:     }
                    157: 
                    158:   *ppz = pz;
                    159:   return scale_n_add (base, val, scale);
                    160: }
                    161: 
                    162: /* Parses the syntax YEAR-MONTH-DAY.
                    163:    PS points into the string, after "YEAR", before "-MONTH-DAY".  */
                    164: static time_t
                    165: parse_year_month_day (cch_t * pz, cch_t * ps)
                    166: {
                    167:   time_t res = 0;
                    168: 
                    169:   res = parse_scaled_value (0, &pz, ps, SEC_PER_YEAR);
                    170: 
                    171:   pz++; /* over the first '-' */
                    172:   ps = strchr (pz, '-');
                    173:   if (ps == NULL)
                    174:     {
                    175:       errno = EINVAL;
                    176:       return BAD_TIME;
                    177:     }
                    178:   res = parse_scaled_value (res, &pz, ps, SEC_PER_MONTH);
                    179: 
                    180:   pz++; /* over the second '-' */
                    181:   ps = pz + strlen (pz);
                    182:   return parse_scaled_value (res, &pz, ps, SEC_PER_DAY);
                    183: }
                    184: 
                    185: /* Parses the syntax YYYYMMDD.  */
                    186: static time_t
                    187: parse_yearmonthday (cch_t * in_pz)
                    188: {
                    189:   time_t res = 0;
                    190:   char   buf[8];
                    191:   cch_t * pz;
                    192: 
                    193:   if (strlen (in_pz) != 8)
                    194:     {
                    195:       errno = EINVAL;
                    196:       return BAD_TIME;
                    197:     }
                    198: 
                    199:   memcpy (buf, in_pz, 4);
                    200:   buf[4] = NUL;
                    201:   pz = buf;
                    202:   res = parse_scaled_value (0, &pz, buf + 4, SEC_PER_YEAR);
                    203: 
                    204:   memcpy (buf, in_pz + 4, 2);
                    205:   buf[2] = NUL;
                    206:   pz =   buf;
                    207:   res = parse_scaled_value (res, &pz, buf + 2, SEC_PER_MONTH);
                    208: 
                    209:   memcpy (buf, in_pz + 6, 2);
                    210:   buf[2] = NUL;
                    211:   pz =   buf;
                    212:   return parse_scaled_value (res, &pz, buf + 2, SEC_PER_DAY);
                    213: }
                    214: 
                    215: /* Parses the syntax yy Y mm M ww W dd D.  */
                    216: static time_t
                    217: parse_YMWD (cch_t * pz)
                    218: {
                    219:   time_t res = 0;
                    220:   cch_t * ps = strchr (pz, 'Y');
                    221:   if (ps != NULL)
                    222:     {
                    223:       res = parse_scaled_value (0, &pz, ps, SEC_PER_YEAR);
                    224:       pz++;
                    225:     }
                    226: 
                    227:   ps = strchr (pz, 'M');
                    228:   if (ps != NULL)
                    229:     {
                    230:       res = parse_scaled_value (res, &pz, ps, SEC_PER_MONTH);
                    231:       pz++;
                    232:     }
                    233: 
                    234:   ps = strchr (pz, 'W');
                    235:   if (ps != NULL)
                    236:     {
                    237:       res = parse_scaled_value (res, &pz, ps, SEC_PER_WEEK);
                    238:       pz++;
                    239:     }
                    240: 
                    241:   ps = strchr (pz, 'D');
                    242:   if (ps != NULL)
                    243:     {
                    244:       res = parse_scaled_value (res, &pz, ps, SEC_PER_DAY);
                    245:       pz++;
                    246:     }
                    247: 
                    248:   while (isspace ((unsigned char)*pz))
                    249:     pz++;
                    250:   if (*pz != NUL)
                    251:     {
                    252:       errno = EINVAL;
                    253:       return BAD_TIME;
                    254:     }
                    255: 
                    256:   return res;
                    257: }
                    258: 
                    259: /* Parses the syntax HH:MM:SS.
                    260:    PS points into the string, after "HH", before ":MM:SS".  */
                    261: static time_t
                    262: parse_hour_minute_second (cch_t * pz, cch_t * ps)
                    263: {
                    264:   time_t res = 0;
                    265: 
                    266:   res = parse_scaled_value (0, &pz, ps, SEC_PER_HR);
                    267: 
                    268:   pz++;
                    269:   ps = strchr (pz, ':');
                    270:   if (ps == NULL)
                    271:     {
                    272:       errno = EINVAL;
                    273:       return BAD_TIME;
                    274:     }
                    275: 
                    276:   res = parse_scaled_value (res, &pz, ps, SEC_PER_MIN);
                    277: 
                    278:   pz++;
                    279:   ps = pz + strlen (pz);
                    280:   return parse_scaled_value (res, &pz, ps, 1);
                    281: }
                    282: 
                    283: /* Parses the syntax HHMMSS.  */
                    284: static time_t
                    285: parse_hourminutesecond (cch_t * in_pz)
                    286: {
                    287:   time_t res = 0;
                    288:   char   buf[4];
                    289:   cch_t * pz;
                    290: 
                    291:   if (strlen (in_pz) != 6)
                    292:     {
                    293:       errno = EINVAL;
                    294:       return BAD_TIME;
                    295:     }
                    296: 
                    297:   memcpy (buf, in_pz, 2);
                    298:   buf[2] = NUL;
                    299:   pz = buf;
                    300:   res = parse_scaled_value (0, &pz, buf + 2, SEC_PER_HR);
                    301: 
                    302:   memcpy (buf, in_pz + 2, 2);
                    303:   buf[2] = NUL;
                    304:   pz =   buf;
                    305:   res = parse_scaled_value (res, &pz, buf + 2, SEC_PER_MIN);
                    306: 
                    307:   memcpy (buf, in_pz + 4, 2);
                    308:   buf[2] = NUL;
                    309:   pz =   buf;
                    310:   return parse_scaled_value (res, &pz, buf + 2, 1);
                    311: }
                    312: 
                    313: /* Parses the syntax hh H mm M ss S.  */
                    314: static time_t
                    315: parse_HMS (cch_t * pz)
                    316: {
                    317:   time_t res = 0;
                    318:   cch_t * ps = strchr (pz, 'H');
                    319:   if (ps != NULL)
                    320:     {
                    321:       res = parse_scaled_value (0, &pz, ps, SEC_PER_HR);
                    322:       pz++;
                    323:     }
                    324: 
                    325:   ps = strchr (pz, 'M');
                    326:   if (ps != NULL)
                    327:     {
                    328:       res = parse_scaled_value (res, &pz, ps, SEC_PER_MIN);
                    329:       pz++;
                    330:     }
                    331: 
                    332:   ps = strchr (pz, 'S');
                    333:   if (ps != NULL)
                    334:     {
                    335:       res = parse_scaled_value (res, &pz, ps, 1);
                    336:       pz++;
                    337:     }
                    338: 
                    339:   while (isspace ((unsigned char)*pz))
                    340:     pz++;
                    341:   if (*pz != NUL)
                    342:     {
                    343:       errno = EINVAL;
                    344:       return BAD_TIME;
                    345:     }
                    346: 
                    347:   return res;
                    348: }
                    349: 
                    350: /* Parses a time (hours, minutes, seconds) specification in either syntax.  */
                    351: static time_t
                    352: parse_time (cch_t * pz)
                    353: {
                    354:   cch_t * ps;
                    355:   time_t  res = 0;
                    356: 
                    357:   /*
                    358:    *  Scan for a hyphen
                    359:    */
                    360:   ps = strchr (pz, ':');
                    361:   if (ps != NULL)
                    362:     {
                    363:       res = parse_hour_minute_second (pz, ps);
                    364:     }
                    365: 
                    366:   /*
                    367:    *  Try for a 'H', 'M' or 'S' suffix
                    368:    */
                    369:   else if (ps = strpbrk (pz, "HMS"),
                    370:            ps == NULL)
                    371:     {
                    372:       /* Its a YYYYMMDD format: */
                    373:       res = parse_hourminutesecond (pz);
                    374:     }
                    375: 
                    376:   else
                    377:     res = parse_HMS (pz);
                    378: 
                    379:   return res;
                    380: }
                    381: 
                    382: /* Returns a substring of the given string, with spaces at the beginning and at
                    383:    the end destructively removed, per SNOBOL.  */
                    384: static char *
                    385: trim (char * pz)
                    386: {
                    387:   /* trim leading white space */
                    388:   while (isspace ((unsigned char)*pz))
                    389:     pz++;
                    390: 
                    391:   /* trim trailing white space */
                    392:   {
                    393:     char * pe = pz + strlen (pz);
                    394:     while ((pe > pz) && isspace ((unsigned char)pe[-1]))
                    395:       pe--;
                    396:     *pe = NUL;
                    397:   }
                    398: 
                    399:   return pz;
                    400: }
                    401: 
                    402: /*
                    403:  *  Parse the year/months/days of a time period
                    404:  */
                    405: static time_t
                    406: parse_period (cch_t * in_pz)
                    407: {
                    408:   char * pT;
                    409:   char * ps;
                    410:   char * pz   = strdup (in_pz);
                    411:   void * fptr = pz;
                    412:   time_t res  = 0;
                    413: 
                    414:   if (pz == NULL)
                    415:     {
                    416:       errno = ENOMEM;
                    417:       return BAD_TIME;
                    418:     }
                    419: 
                    420:   pT = strchr (pz, 'T');
                    421:   if (pT != NULL)
                    422:     {
                    423:       *(pT++) = NUL;
                    424:       pz = trim (pz);
                    425:       pT = trim (pT);
                    426:     }
                    427: 
                    428:   /*
                    429:    *  Scan for a hyphen
                    430:    */
                    431:   ps = strchr (pz, '-');
                    432:   if (ps != NULL)
                    433:     {
                    434:       res = parse_year_month_day (pz, ps);
                    435:     }
                    436: 
                    437:   /*
                    438:    *  Try for a 'Y', 'M' or 'D' suffix
                    439:    */
                    440:   else if (ps = strpbrk (pz, "YMWD"),
                    441:            ps == NULL)
                    442:     {
                    443:       /* Its a YYYYMMDD format: */
                    444:       res = parse_yearmonthday (pz);
                    445:     }
                    446: 
                    447:   else
                    448:     res = parse_YMWD (pz);
                    449: 
                    450:   if ((errno == 0) && (pT != NULL))
                    451:     {
                    452:       time_t val = parse_time (pT);
                    453:       res = scale_n_add (res, val, 1);
                    454:     }
                    455: 
                    456:   free (fptr);
                    457:   return res;
                    458: }
                    459: 
                    460: static time_t
                    461: parse_non_iso8601 (cch_t * pz)
                    462: {
                    463:   whats_done_t whatd_we_do = NOTHING_IS_DONE;
                    464: 
                    465:   time_t res = 0;
                    466: 
                    467:   do  {
                    468:     time_t val;
                    469: 
                    470:     errno = 0;
                    471:     val = str_const_to_l (pz, &pz, 10);
                    472:     if (errno != 0)
                    473:       goto bad_time;
                    474: 
                    475:     /*  IF we find a colon, then we're going to have a seconds value.
                    476:         We will not loop here any more.  We cannot already have parsed
                    477:         a minute value and if we've parsed an hour value, then the result
                    478:         value has to be less than an hour. */
                    479:     if (*pz == ':')
                    480:       {
                    481:         if (whatd_we_do >= MINUTE_IS_DONE)
                    482:           break;
                    483: 
                    484:         val = parse_hr_min_sec (val, pz);
                    485: 
                    486:         if ((whatd_we_do == HOUR_IS_DONE) && (val >= SEC_PER_HR))
                    487:           break;
                    488: 
                    489:         return scale_n_add (res, val, 1);
                    490:       }
                    491: 
                    492:     {
                    493:       unsigned int mult;
                    494: 
                    495:       /*  Skip over white space following the number we just parsed. */
                    496:       while (isspace ((unsigned char)*pz))
                    497:         pz++;
                    498: 
                    499:       switch (*pz)
                    500:         {
                    501:         default:  goto bad_time;
                    502:         case NUL:
                    503:           return scale_n_add (res, val, 1);
                    504: 
                    505:         case 'y': case 'Y':
                    506:           if (whatd_we_do >= YEAR_IS_DONE)
                    507:             goto bad_time;
                    508:           mult = SEC_PER_YEAR;
                    509:           whatd_we_do = YEAR_IS_DONE;
                    510:           break;
                    511: 
                    512:         case 'M':
                    513:           if (whatd_we_do >= MONTH_IS_DONE)
                    514:             goto bad_time;
                    515:           mult = SEC_PER_MONTH;
                    516:           whatd_we_do = MONTH_IS_DONE;
                    517:           break;
                    518: 
                    519:         case 'W':
                    520:           if (whatd_we_do >= WEEK_IS_DONE)
                    521:             goto bad_time;
                    522:           mult = SEC_PER_WEEK;
                    523:           whatd_we_do = WEEK_IS_DONE;
                    524:           break;
                    525: 
                    526:         case 'd': case 'D':
                    527:           if (whatd_we_do >= DAY_IS_DONE)
                    528:             goto bad_time;
                    529:           mult = SEC_PER_DAY;
                    530:           whatd_we_do = DAY_IS_DONE;
                    531:           break;
                    532: 
                    533:         case 'h':
                    534:           if (whatd_we_do >= HOUR_IS_DONE)
                    535:             goto bad_time;
                    536:           mult = SEC_PER_HR;
                    537:           whatd_we_do = HOUR_IS_DONE;
                    538:           break;
                    539: 
                    540:         case 'm':
                    541:           if (whatd_we_do >= MINUTE_IS_DONE)
                    542:             goto bad_time;
                    543:           mult = SEC_PER_MIN;
                    544:           whatd_we_do = MINUTE_IS_DONE;
                    545:           break;
                    546: 
                    547:         case 's':
                    548:           mult = 1;
                    549:           whatd_we_do = SECOND_IS_DONE;
                    550:           break;
                    551:         }
                    552: 
                    553:       res = scale_n_add (res, val, mult);
                    554: 
                    555:       pz++;
                    556:       while (isspace ((unsigned char)*pz))
                    557:         pz++;
                    558:       if (*pz == NUL)
                    559:         return res;
                    560: 
                    561:       if (! isdigit ((unsigned char)*pz))
                    562:         break;
                    563:     }
                    564: 
                    565:   } while (whatd_we_do < SECOND_IS_DONE);
                    566: 
                    567:  bad_time:
                    568:   errno = EINVAL;
                    569:   return BAD_TIME;
                    570: }
                    571: 
                    572: time_t
                    573: parse_duration (char const * pz)
                    574: {
                    575:   while (isspace ((unsigned char)*pz))
                    576:     pz++;
                    577: 
                    578:   switch (*pz)
                    579:     {
                    580:     case 'P':
                    581:       return parse_period (pz + 1);
                    582: 
                    583:     case 'T':
                    584:       return parse_time (pz + 1);
                    585: 
                    586:     default:
                    587:       if (isdigit ((unsigned char)*pz))
                    588:         return parse_non_iso8601 (pz);
                    589: 
                    590:       errno = EINVAL;
                    591:       return BAD_TIME;
                    592:     }
                    593: }
                    594: 
                    595: /*
                    596:  * Local Variables:
                    597:  * mode: C
                    598:  * c-file-style: "gnu"
                    599:  * indent-tabs-mode: nil
                    600:  * End:
                    601:  * end of parse-duration.c */

FreeBSD-CVSweb <freebsd-cvsweb@FreeBSD.org>