1 /*
   2  * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
   3  * Use is subject to license terms.
   4  */
   5 
   6 /*
   7  * Copyright (c) 2018, Joyent, Inc.
   8  */
   9 
  10 /*
  11  * zdump 7.24
  12  * Taken from elsie.nci.nih.gov to replace the existing Solaris zdump,
  13  * which was based on an earlier version of the elsie code.
  14  *
  15  * For zdump 7.24, the following changes were made to the elsie code:
  16  *   locale/textdomain/messages to match existing Solaris style.
  17  *   Solaris verbose mode is documented to display the current time first.
  18  *   cstyle cleaned code.
  19  *   removed old locale/textdomain code.
  20  */
  21 
  22 static char     elsieid[] = "@(#)zdump.c        7.74";
  23 
  24 /*
  25  * This code has been made independent of the rest of the time
  26  * conversion package to increase confidence in the verification it provides.
  27  * You can use this code to help in verifying other implementations.
  28  */
  29 
  30 #include "stdio.h"      /* for stdout, stderr, perror */
  31 #include "string.h"     /* for strcpy */
  32 #include "sys/types.h"  /* for time_t */
  33 #include "time.h"       /* for struct tm */
  34 #include "stdlib.h"     /* for exit, malloc, atoi */
  35 #include "locale.h"     /* for setlocale, textdomain */
  36 #include "libintl.h"
  37 #include <ctype.h>
  38 #include "tzfile.h"     /* for defines */
  39 #include <limits.h>
  40 
  41 #ifndef ZDUMP_LO_YEAR
  42 #define ZDUMP_LO_YEAR   (-500)
  43 #endif /* !defined ZDUMP_LO_YEAR */
  44 
  45 #ifndef ZDUMP_HI_YEAR
  46 #define ZDUMP_HI_YEAR   2500
  47 #endif /* !defined ZDUMP_HI_YEAR */
  48 
  49 #ifndef MAX_STRING_LENGTH
  50 #define MAX_STRING_LENGTH       1024
  51 #endif /* !defined MAX_STRING_LENGTH */
  52 
  53 #ifndef TRUE
  54 #define TRUE            1
  55 #endif /* !defined TRUE */
  56 
  57 #ifndef FALSE
  58 #define FALSE           0
  59 #endif /* !defined FALSE */
  60 
  61 #ifndef isleap_sum
  62 /*
  63  * See tzfile.h for details on isleap_sum.
  64  */
  65 #define isleap_sum(a, b)        isleap((a) % 400 + (b) % 400)
  66 #endif /* !defined isleap_sum */
  67 
  68 #ifndef SECSPERDAY
  69 #define SECSPERDAY      ((long)SECSPERHOUR * HOURSPERDAY)
  70 #endif
  71 #define SECSPERNYEAR    (SECSPERDAY * DAYSPERNYEAR)
  72 #define SECSPERLYEAR    (SECSPERNYEAR + SECSPERDAY)
  73 
  74 #ifndef GNUC_or_lint
  75 #ifdef lint
  76 #define GNUC_or_lint
  77 #else /* !defined lint */
  78 #ifdef __GNUC__
  79 #define GNUC_or_lint
  80 #endif /* defined __GNUC__ */
  81 #endif /* !defined lint */
  82 #endif /* !defined GNUC_or_lint */
  83 
  84 #ifndef INITIALIZE
  85 #ifdef  GNUC_or_lint
  86 #define INITIALIZE(x)   ((x) = 0)
  87 #else /* !defined GNUC_or_lint */
  88 #define INITIALIZE(x)
  89 #endif /* !defined GNUC_or_lint */
  90 #endif /* !defined INITIALIZE */
  91 
  92 static time_t   absolute_min_time;
  93 static time_t   absolute_max_time;
  94 static size_t   longest;
  95 static char     *progname;
  96 static int      warned;
  97 
  98 static char     *abbr(struct tm *);
  99 static void     abbrok(const char *, const char *);
 100 static long     delta(struct tm *, struct tm *);
 101 static void     dumptime(const struct tm *);
 102 static time_t   hunt(char *, time_t, time_t);
 103 static void     setabsolutes(void);
 104 static void     show(char *, time_t, int);
 105 static void     usage(void);
 106 static const char       *tformat(void);
 107 static time_t   yeartot(long y);
 108 
 109 #ifndef TYPECHECK
 110 #define my_localtime    localtime
 111 #else /* !defined TYPECHECK */
 112 static struct tm *
 113 my_localtime(tp)
 114 time_t *tp;
 115 {
 116         register struct tm *tmp;
 117 
 118         tmp = localtime(tp);
 119         if (tp != NULL && tmp != NULL) {
 120                 struct tm       tm;
 121                 register time_t t;
 122 
 123                 tm = *tmp;
 124                 t = mktime(&tm);
 125                 if (t - *tp >= 1 || *tp - t >= 1) {
 126                         (void) fflush(stdout);
 127                         (void) fprintf(stderr, "\n%s: ", progname);
 128                         (void) fprintf(stderr, tformat(), *tp);
 129                         (void) fprintf(stderr, " ->");
 130                         (void) fprintf(stderr, " year=%d", tmp->tm_year);
 131                         (void) fprintf(stderr, " mon=%d", tmp->tm_mon);
 132                         (void) fprintf(stderr, " mday=%d", tmp->tm_mday);
 133                         (void) fprintf(stderr, " hour=%d", tmp->tm_hour);
 134                         (void) fprintf(stderr, " min=%d", tmp->tm_min);
 135                         (void) fprintf(stderr, " sec=%d", tmp->tm_sec);
 136                         (void) fprintf(stderr, " isdst=%d", tmp->tm_isdst);
 137                         (void) fprintf(stderr, " -> ");
 138                         (void) fprintf(stderr, tformat(), t);
 139                         (void) fprintf(stderr, "\n");
 140                 }
 141         }
 142         return (tmp);
 143 }
 144 #endif /* !defined TYPECHECK */
 145 
 146 static void
 147 abbrok(const char * const abbrp, const char * const zone)
 148 {
 149         register const char *cp;
 150         int error = 0;
 151 
 152         if (warned)
 153                 return;
 154         cp = abbrp;
 155         while (isalpha(*cp) || isdigit(*cp) || *cp == '-' || *cp == '+')
 156                 ++cp;
 157         (void) fflush(stdout);
 158         if (cp - abbrp < 3) {
 159                 (void) fprintf(stderr, gettext("%s: warning: zone \"%s\" "
 160                     "abbreviation \"%s\" has fewer than 3 alphabetics\n"),
 161                     progname, zone, abbrp);
 162                 error = 1;
 163         } else if (cp - abbrp > 6) {
 164                 (void) fprintf(stderr, gettext("%s: warning: zone \"%s\" "
 165                     "abbreviation \"%s\" has more than 6 characters\n"),
 166                     progname, zone, abbrp);
 167                 error = 1;
 168         } else if (*cp != '\0') {
 169                 (void) fprintf(stderr, gettext("%s: warning: zone \"%s\" "
 170                     "abbreviation \"%s\" has characters other than "
 171                     "alphanumerics\n"), progname, zone, abbrp);
 172                 error = 1;
 173         }
 174         if (error)
 175                 warned = TRUE;
 176 }
 177 
 178 int
 179 main(argc, argv)
 180 int     argc;
 181 char    *argv[];
 182 {
 183         register int            i;
 184         register int            c;
 185         register int            vflag;
 186         register char           *cutarg;
 187         register long           cutloyear = ZDUMP_LO_YEAR;
 188         register long           cuthiyear = ZDUMP_HI_YEAR;
 189         register time_t         cutlotime;
 190         register time_t         cuthitime;
 191         time_t                  now;
 192         time_t                  t;
 193         time_t                  newt;
 194         struct tm               tm;
 195         struct tm               newtm;
 196         register struct tm      *tmp;
 197         register struct tm      *newtmp;
 198 
 199         INITIALIZE(cutlotime);
 200         INITIALIZE(cuthitime);
 201 
 202         (void) setlocale(LC_ALL, "");
 203 #if !defined(TEXT_DOMAIN)               /* Should be defined by cc -D */
 204 #define TEXT_DOMAIN     "SYS_TEST"      /* Use this only if it weren't */
 205 #endif
 206         (void) textdomain(TEXT_DOMAIN);
 207 
 208         progname = argv[0];
 209         for (i = 1; i < argc; ++i)
 210                 if (strcmp(argv[i], "--version") == 0) {
 211                         (void) printf("%s\n", elsieid);
 212                         exit(EXIT_SUCCESS);
 213                 }
 214         vflag = 0;
 215         cutarg = NULL;
 216         while ((c = getopt(argc, argv, "c:v")) == 'c' || c == 'v')
 217                 if (c == 'v')
 218                         vflag = 1;
 219                 else    cutarg = optarg;
 220         if (c != EOF ||
 221                 (optind == argc - 1 && strcmp(argv[optind], "=") == 0)) {
 222                         usage();
 223                         /* NOTREACHED */
 224         }
 225         if (vflag) {
 226                 if (cutarg != NULL) {
 227                         long    lo;
 228                         long    hi;
 229                         char    dummy;
 230 
 231                         if (sscanf(cutarg, "%ld%c", &hi, &dummy) == 1) {
 232                                 cuthiyear = hi;
 233                         } else if (sscanf(cutarg, "%ld,%ld%c",
 234                                 &lo, &hi, &dummy) == 2) {
 235                                         cutloyear = lo;
 236                                         cuthiyear = hi;
 237                         } else {
 238                                 (void) fprintf(stderr,
 239                                     gettext("%s: wild -c argument %s\n"),
 240                                     progname, cutarg);
 241                                 exit(EXIT_FAILURE);
 242                         }
 243                 }
 244                 setabsolutes();
 245                 cutlotime = yeartot(cutloyear);
 246                 cuthitime = yeartot(cuthiyear);
 247         }
 248         (void) time(&now);
 249         longest = 0;
 250         for (i = optind; i < argc; ++i)
 251                 if (strlen(argv[i]) > longest)
 252                         longest = strlen(argv[i]);
 253 
 254         for (i = optind; i < argc; ++i) {
 255                 static char     buf[MAX_STRING_LENGTH];
 256                 static char     *tzp = NULL;
 257 
 258                 (void) unsetenv("TZ");
 259                 if (tzp != NULL)
 260                         free(tzp);
 261                 if ((tzp = malloc(3 + strlen(argv[i]) + 1)) == NULL) {
 262                         perror(progname);
 263                         exit(EXIT_FAILURE);
 264                 }
 265                 (void) strcpy(tzp, "TZ=");
 266                 (void) strcat(tzp, argv[i]);
 267                 if (putenv(tzp) != 0) {
 268                         perror(progname);
 269                         exit(EXIT_FAILURE);
 270                 }
 271                 if (!vflag) {
 272                         show(argv[i], now, FALSE);
 273                         continue;
 274                 }
 275 
 276 #if defined(sun)
 277                 /*
 278                  * We show the current time first, probably because we froze
 279                  * the behavior of zdump some time ago and then it got
 280                  * changed.
 281                  */
 282                 show(argv[i], now, TRUE);
 283 #endif
 284                 warned = FALSE;
 285                 t = absolute_min_time;
 286                 show(argv[i], t, TRUE);
 287                 t += SECSPERHOUR * HOURSPERDAY;
 288                 show(argv[i], t, TRUE);
 289                 if (t < cutlotime)
 290                         t = cutlotime;
 291                 tmp = my_localtime(&t);
 292                 if (tmp != NULL) {
 293                         tm = *tmp;
 294                         (void) strncpy(buf, abbr(&tm), sizeof (buf) - 1);
 295                 }
 296                 for (;;) {
 297                         if (t >= cuthitime)
 298                                 break;
 299                         /* check if newt will overrun maximum time_t value */
 300                         if (t > LONG_MAX - (SECSPERHOUR * 12))
 301                                 break;
 302                         newt = t + SECSPERHOUR * 12;
 303                         if (newt >= cuthitime)
 304                                 break;
 305                         newtmp = localtime(&newt);
 306                         if (newtmp != NULL)
 307                                 newtm = *newtmp;
 308                         if ((tmp == NULL || newtmp == NULL) ? (tmp != newtmp) :
 309                                 (delta(&newtm, &tm) != (newt - t) ||
 310                                 newtm.tm_isdst != tm.tm_isdst ||
 311                                 strcmp(abbr(&newtm), buf) != 0)) {
 312                                         newt = hunt(argv[i], t, newt);
 313                                         newtmp = localtime(&newt);
 314                                         if (newtmp != NULL) {
 315                                                 newtm = *newtmp;
 316                                                 (void) strncpy(buf,
 317                                                         abbr(&newtm),
 318                                                         sizeof (buf) - 1);
 319                                         }
 320                         }
 321                         t = newt;
 322                         tm = newtm;
 323                         tmp = newtmp;
 324                 }
 325                 t = absolute_max_time;
 326 #if defined(sun)
 327                 show(argv[i], t, TRUE);
 328                 t -= SECSPERHOUR * HOURSPERDAY;
 329                 show(argv[i], t, TRUE);
 330 #else /* !defined(sun) */
 331                 t -= SECSPERHOUR * HOURSPERDAY;
 332                 show(argv[i], t, TRUE);
 333                 t += SECSPERHOUR * HOURSPERDAY;
 334                 show(argv[i], t, TRUE);
 335 #endif /* !defined(sun) */
 336         }
 337         if (fflush(stdout) || ferror(stdout)) {
 338                 (void) fprintf(stderr, "%s: ", progname);
 339                 (void) perror(gettext("Error writing standard output"));
 340                 exit(EXIT_FAILURE);
 341         }
 342         return (EXIT_SUCCESS);
 343 }
 344 
 345 static void
 346 setabsolutes()
 347 {
 348 #if defined(sun)
 349         absolute_min_time = LONG_MIN;
 350         absolute_max_time = LONG_MAX;
 351 #else
 352         if (0.5 == (time_t)0.5) {
 353                 /*
 354                  * time_t is floating.
 355                  */
 356                 if (sizeof (time_t) == sizeof (float)) {
 357                         absolute_min_time = (time_t)-FLT_MAX;
 358                         absolute_max_time = (time_t)FLT_MAX;
 359                 } else if (sizeof (time_t) == sizeof (double)) {
 360                         absolute_min_time = (time_t)-DBL_MAX;
 361                         absolute_max_time = (time_t)DBL_MAX;
 362                 } else {
 363                         (void) fprintf(stderr, gettext("%s: use of -v on "
 364                             "system with floating time_t other than float "
 365                             "or double\n"), progname);
 366                         exit(EXIT_FAILURE);
 367                 }
 368         } else
 369         /*CONSTANTCONDITION*/
 370         if (0 > (time_t)-1) {
 371                 /*
 372                  * time_t is signed.
 373                  */
 374                 register time_t hibit;
 375 
 376                 for (hibit = 1; (hibit * 2) != 0; hibit *= 2)
 377                         continue;
 378                 absolute_min_time = hibit;
 379                 absolute_max_time = -(hibit + 1);
 380         } else {
 381                 /*
 382                  * time_t is unsigned.
 383                  */
 384                 absolute_min_time = 0;
 385                 absolute_max_time = absolute_min_time - 1;
 386         }
 387 #endif
 388 }
 389 
 390 static time_t
 391 yeartot(y)
 392 const long      y;
 393 {
 394         register long   myy;
 395         register long   seconds;
 396         register time_t t;
 397 
 398         myy = EPOCH_YEAR;
 399         t = 0;
 400         while (myy != y) {
 401                 if (myy < y) {
 402                         seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR;
 403                         ++myy;
 404                         if (t > absolute_max_time - seconds) {
 405                                 t = absolute_max_time;
 406                                 break;
 407                         }
 408                         t += seconds;
 409                 } else {
 410                         --myy;
 411                         seconds = isleap(myy) ? SECSPERLYEAR : SECSPERNYEAR;
 412                         if (t < absolute_min_time + seconds) {
 413                                 t = absolute_min_time;
 414                                 break;
 415                         }
 416                         t -= seconds;
 417                 }
 418         }
 419         return (t);
 420 }
 421 
 422 static time_t
 423 hunt(name, lot, hit)
 424 char    *name;
 425 time_t  lot;
 426 time_t  hit;
 427 {
 428         time_t                  t;
 429         long                    diff;
 430         struct tm               lotm;
 431         register struct tm      *lotmp;
 432         struct tm               tm;
 433         register struct tm      *tmp;
 434         char                    loab[MAX_STRING_LENGTH];
 435 
 436         lotmp = my_localtime(&lot);
 437         if (lotmp != NULL) {
 438                 lotm = *lotmp;
 439                 (void) strncpy(loab, abbr(&lotm), sizeof (loab) - 1);
 440         }
 441         for (;;) {
 442                 diff = (long)(hit - lot);
 443                 if (diff < 2)
 444                         break;
 445                 t = lot;
 446                 t += diff / 2;
 447                 if (t <= lot)
 448                         ++t;
 449                 else if (t >= hit)
 450                         --t;
 451                 tmp = my_localtime(&t);
 452                 if (tmp != NULL)
 453                         tm = *tmp;
 454                 if ((lotmp == NULL || tmp == NULL) ? (lotmp == tmp) :
 455                         (delta(&tm, &lotm) == (t - lot) &&
 456                         tm.tm_isdst == lotm.tm_isdst &&
 457                         strcmp(abbr(&tm), loab) == 0)) {
 458                                 lot = t;
 459                                 lotm = tm;
 460                                 lotmp = tmp;
 461                 } else  hit = t;
 462         }
 463         show(name, lot, TRUE);
 464         show(name, hit, TRUE);
 465         return (hit);
 466 }
 467 
 468 /*
 469  * Thanks to Paul Eggert for logic used in delta.
 470  */
 471 
 472 static long
 473 delta(newp, oldp)
 474 struct tm       *newp;
 475 struct tm       *oldp;
 476 {
 477         register long   result;
 478         register int    tmy;
 479 
 480         if (newp->tm_year < oldp->tm_year)
 481                 return (-delta(oldp, newp));
 482         result = 0;
 483         for (tmy = oldp->tm_year; tmy < newp->tm_year; ++tmy)
 484                 result += DAYSPERNYEAR + isleap_sum(tmy, TM_YEAR_BASE);
 485         result += newp->tm_yday - oldp->tm_yday;
 486         result *= HOURSPERDAY;
 487         result += newp->tm_hour - oldp->tm_hour;
 488         result *= MINSPERHOUR;
 489         result += newp->tm_min - oldp->tm_min;
 490         result *= SECSPERMIN;
 491         result += newp->tm_sec - oldp->tm_sec;
 492         return (result);
 493 }
 494 
 495 static void
 496 show(zone, t, v)
 497 char    *zone;
 498 time_t  t;
 499 int     v;
 500 {
 501         register struct tm      *tmp;
 502 
 503         (void) printf("%-*s  ", (int)longest, zone);
 504         if (v) {
 505                 tmp = gmtime(&t);
 506                 if (tmp == NULL) {
 507                         (void) printf(tformat(), t);
 508                 } else {
 509                         dumptime(tmp);
 510                         (void) printf(" UTC");
 511                 }
 512                 (void) printf(" = ");
 513         }
 514         tmp = my_localtime(&t);
 515         dumptime(tmp);
 516         if (tmp != NULL) {
 517                 if (*abbr(tmp) != '\0')
 518                         (void) printf(" %s", abbr(tmp));
 519                 if (v) {
 520                         (void) printf(" isdst=%d", tmp->tm_isdst);
 521 #ifdef TM_GMTOFF
 522                         (void) printf(" gmtoff=%ld", tmp->TM_GMTOFF);
 523 #endif /* defined TM_GMTOFF */
 524                 }
 525         }
 526         (void) printf("\n");
 527         if (tmp != NULL && *abbr(tmp) != '\0')
 528                 abbrok(abbr(tmp), zone);
 529 }
 530 
 531 static char *
 532 abbr(tmp)
 533 struct tm       *tmp;
 534 {
 535         register char   *result;
 536         static char     nada;
 537 
 538         if (tmp->tm_isdst != 0 && tmp->tm_isdst != 1)
 539                 return (&nada);
 540         result = tzname[tmp->tm_isdst];
 541         return ((result == NULL) ? &nada : result);
 542 }
 543 
 544 /*
 545  * The code below can fail on certain theoretical systems;
 546  * it works on all known real-world systems as of 2004-12-30.
 547  */
 548 
 549 static const char *
 550 tformat()
 551 {
 552 #if defined(sun)
 553         /* time_t is signed long */
 554         return ("%ld");
 555 #else
 556         /*CONSTANTCONDITION*/
 557         if (0.5 == (time_t)0.5) {       /* floating */
 558                 /*CONSTANTCONDITION*/
 559                 if (sizeof (time_t) > sizeof (double))
 560                         return ("%Lg");
 561                 return ("%g");
 562         }
 563         /*CONSTANTCONDITION*/
 564         if (0 > (time_t)-1) {                /* signed */
 565                 /*CONSTANTCONDITION*/
 566                 if (sizeof (time_t) > sizeof (long))
 567                         return ("%lld");
 568                 /*CONSTANTCONDITION*/
 569                 if (sizeof (time_t) > sizeof (int))
 570                         return ("%ld");
 571                 return ("%d");
 572         }
 573         /*CONSTANTCONDITION*/
 574         if (sizeof (time_t) > sizeof (unsigned long))
 575                 return ("%llu");
 576         /*CONSTANTCONDITION*/
 577         if (sizeof (time_t) > sizeof (unsigned int))
 578                 return ("%lu");
 579         return ("%u");
 580 #endif
 581 }
 582 
 583 static void
 584 dumptime(timeptr)
 585 register const struct tm        *timeptr;
 586 {
 587         static const char       wday_name[][3] = {
 588                 "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"
 589         };
 590         static const char       mon_name[][3] = {
 591                 "Jan", "Feb", "Mar", "Apr", "May", "Jun",
 592                 "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"
 593         };
 594         register const char     *wn;
 595         register const char     *mn;
 596         register int            lead;
 597         register int            trail;
 598 
 599         if (timeptr == NULL) {
 600                 (void) printf("NULL");
 601                 return;
 602         }
 603         /*
 604          * The packaged versions of localtime and gmtime never put out-of-range
 605          * values in tm_wday or tm_mon, but since this code might be compiled
 606          * with other (perhaps experimental) versions, paranoia is in order.
 607          */
 608         if (timeptr->tm_wday < 0 || timeptr->tm_wday >=
 609                 (int)(sizeof (wday_name) / sizeof (wday_name[0])))
 610                         wn = "???";
 611         else            wn = wday_name[timeptr->tm_wday];
 612         if (timeptr->tm_mon < 0 || timeptr->tm_mon >=
 613                 (int)(sizeof (mon_name) / sizeof (mon_name[0])))
 614                         mn = "???";
 615         else            mn = mon_name[timeptr->tm_mon];
 616         (void) printf("%.3s %.3s%3d %.2d:%.2d:%.2d ",
 617                 wn, mn,
 618                 timeptr->tm_mday, timeptr->tm_hour,
 619                 timeptr->tm_min, timeptr->tm_sec);
 620 #define DIVISOR 10
 621         trail = timeptr->tm_year % DIVISOR + TM_YEAR_BASE % DIVISOR;
 622         lead = timeptr->tm_year / DIVISOR + TM_YEAR_BASE / DIVISOR +
 623                 trail / DIVISOR;
 624         trail %= DIVISOR;
 625         if (trail < 0 && lead > 0) {
 626                 trail += DIVISOR;
 627                 --lead;
 628         } else if (lead < 0 && trail > 0) {
 629                 trail -= DIVISOR;
 630                 ++lead;
 631         }
 632         if (lead == 0)
 633                 (void) printf("%d", trail);
 634         else
 635                 (void) printf("%d%d", lead, ((trail < 0) ? -trail : trail));
 636 }
 637 
 638 static void
 639 usage()
 640 {
 641         (void) fprintf(stderr, gettext(
 642             "%s: [ --version ] [ -v ] [ -c [loyear,]hiyear ] zonename ...\n"),
 643                 progname);
 644         exit(EXIT_FAILURE);
 645         /* NOTREACHED */
 646 }