1 /*
   2  * CDDL HEADER START
   3  *
   4  * The contents of this file are subject to the terms of the
   5  * Common Development and Distribution License, Version 1.0 only
   6  * (the "License").  You may not use this file except in compliance
   7  * with the License.
   8  *
   9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
  10  * or http://www.opensolaris.org/os/licensing.
  11  * See the License for the specific language governing permissions
  12  * and limitations under the License.
  13  *
  14  * When distributing Covered Code, include this CDDL HEADER in each
  15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
  16  * If applicable, add the following below this CDDL HEADER, with the
  17  * fields enclosed by brackets "[]" replaced with your own identifying
  18  * information: Portions Copyright [yyyy] [name of copyright owner]
  19  *
  20  * CDDL HEADER END
  21  */
  22 /*
  23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
  24  * Use is subject to license terms.
  25  */
  26 
  27 #pragma ident   "%Z%%M% %I%     %E% SMI"
  28 
  29 #include <stdlib.h>
  30 #include <stdio.h>
  31 #include <string.h>
  32 #include <unistd.h>
  33 #include <sys/param.h>
  34 #include <sys/types.h>
  35 #include <sys/stat.h>
  36 #include <tzfile.h>
  37 #include <fcntl.h>
  38 #include <regex.h>
  39 #include <errno.h>
  40 #include <libintl.h>
  41 #include <libzoneinfo.h>
  42 
  43 #define DEFINIT         "/etc/default/init"
  44 #define ZONEINFOTABDIR  "/usr/share/lib/zoneinfo/tab/"
  45 #define CONTINENT_TAB   ZONEINFOTABDIR "continent.tab"
  46 #define COUNTRY_TAB     ZONEINFOTABDIR "country.tab"
  47 #define ZONE_SUN_TAB    ZONEINFOTABDIR "zone_sun.tab"
  48 
  49 #define NEWLINE         "\n"
  50 #define SLASH           "/"
  51 #define WHITESPACE      "\t "
  52 #define WHITESPACE_NL   "\t \n"
  53 #define DIGITS          "0123456789"
  54 #define BUFFLEN         1024
  55 
  56 #define CCLEN           2               /* country code length */
  57 
  58 #define GMT_MAX         (12*60*60)      /* The maximum GMT offset */
  59 #define GMT_MIN         (-13*60*60)     /* The minimum GMT offset */
  60 #define GMT_FMT_Q       "<GMT%c%d>%c%d"
  61 #define GMT_FMT_Q_LEN   (11)            /* "<GMT+dd>+dd" - maximum 11 chars */
  62 #define GMT0_FMT        "GMT0"          /* backwards compatibility name */
  63 #define GMT_FMT_ZONE    ":Etc/GMT%c%d"  /* ":Etc/GMT+dd" */
  64 #define GMT_FMT_ZONE_LEN        (11)    /* ":Etc/GMT+dd" - maximum 11 chars */
  65 
  66 #define TZ_FMT          "TZ=%s\n"       /* format TZ entry init file */
  67 #define TZ_FMT_Q        "TZ=\"%s\"\n"   /* format quoted TZ entry init file */
  68 
  69 #define COORD_FMTLEN1   (sizeof ("+DDMM+DDDMM") - 1)
  70 #define COORD_FMTLEN2   (sizeof ("+DDMMSS+DDDMMSS") - 1)
  71 #define COORD_FMT1              (1)     /* flag for format 1 */
  72 #define COORD_FMT2              (2)     /* flag for format 2 */
  73 #define COORD_DLEN_LAT          (2)     /* length of DD for latitude */
  74 #define COORD_DLEN_LONG         (3)     /* length of DDD for longtitude */
  75 #define COORD_MLEN              (2)     /* length of MM */
  76 #define COORD_SLEN              (2)     /* length of SS */
  77 
  78 #define TRAILER         "/XXXXXX"
  79 #define TR_LEN          (sizeof (TRAILER) -1)
  80 
  81 /* Internal Declarations */
  82 static char *skipwhite(char *);
  83 static int skipline(char *);
  84 static int trav_link(char **);
  85 static void remove_component(char *);
  86 static void strip_quotes(char *, char *);
  87 static int compar(struct tz_country *, struct tz_country *);
  88 static int get_coord(struct tz_timezone *, char *, size_t);
  89 static int _tz_match(const char *, const char *);
  90 static char *_conv_gmt_zoneinfo(int);
  91 static char *_conv_gmt_posix(int);
  92 
  93 /*
  94  * get_tz_continents() reads the continent.tab file, and
  95  * returns a list of continents.
  96  */
  97 int
  98 get_tz_continents(struct tz_continent **cont)
  99 {
 100         FILE *fp;
 101         char buff[BUFFLEN];
 102         char *lp;               /* line pointer */
 103         char *lptr, *ptr;       /* temp pointer */
 104         struct tz_continent *head = NULL, *lcp, *prev = NULL;
 105         int sav_errno = 0, ncount, status;
 106         size_t len;
 107 
 108         /* open continents file */
 109         if ((fp = fopen(CONTINENT_TAB, "r")) == NULL) {
 110                 /* fopen() sets errno */
 111                 return (-1);
 112         }
 113         /* read and count continents */
 114         ncount = 0;
 115         /*CONSTANTCONDITION*/
 116         while (1) {
 117                 if (fgets(buff, sizeof (buff), fp) == NULL) {
 118                         if (feof(fp) == 0) {
 119                                 /* fgets() sets errno */
 120                                 sav_errno = errno;
 121                                 ncount = -1;
 122                         }
 123                         break;
 124                 }
 125                 /* Skip comments or blank/whitespace lines */
 126                 if ((status = skipline(buff)) != 0) {
 127                         if (status == 1)
 128                                 continue;
 129                         else {
 130                                 sav_errno = EINVAL;
 131                                 ncount = -1;
 132                                 break;
 133                         }
 134                 }
 135                 /* Get continent name */
 136                 lp = skipwhite(&buff[0]);
 137                 if ((len = strcspn(lp, WHITESPACE)) > _TZBUFLEN -1) {
 138                         sav_errno = ENAMETOOLONG;
 139                         ncount = -1;
 140                         break;
 141                 }
 142                 /* create continent struct */
 143                 if ((lcp = (struct tz_continent *)
 144                         calloc(1, sizeof (struct tz_continent))) == NULL) {
 145                         sav_errno = ENOMEM;
 146                         ncount = -1;
 147                         break;
 148                 }
 149                 (void) strncpy(lcp->ctnt_name, lp, len);
 150                 lcp->ctnt_name[len] = '\0';
 151 
 152                 /* Get continent description */
 153                 lp = skipwhite(lp + len);
 154                 len = strcspn(lp, NEWLINE);
 155                 if ((ptr = malloc(len + 1)) == NULL) {
 156                         (void) free_tz_continents(lcp);
 157                         sav_errno = ENOMEM;
 158                         ncount = -1;
 159                         break;
 160                 }
 161                 (void) strncpy(ptr, lp, len);
 162                 *(ptr + len) = '\0';
 163                 lcp->ctnt_id_desc = ptr;
 164 
 165                 /* Get localized continent description */
 166                 lptr = dgettext(TEXT_DOMAIN, lcp->ctnt_id_desc);
 167                 if ((ptr = strdup(lptr)) == NULL) {
 168                         (void) free_tz_continents(lcp);
 169                         sav_errno = ENOMEM;
 170                         ncount = -1;
 171                         break;
 172                 }
 173                 lcp->ctnt_display_desc = ptr;
 174 
 175                 if (head == NULL) {
 176                         head = lcp;
 177                 } else {
 178                         prev->ctnt_next = lcp;
 179                 }
 180                 prev = lcp;
 181                 ncount++;
 182         }
 183         (void) fclose(fp);
 184         if (ncount == -1) {
 185                 if (head != NULL) {
 186                         (void) free_tz_continents(head);
 187                 }
 188                 if (sav_errno)
 189                         errno = sav_errno;
 190         } else {
 191                 *cont = head;
 192         }
 193         return (ncount);
 194 }
 195 
 196 /*
 197  * get_tz_countries() finds the list of countries from the zone_sun.tab
 198  * file, for the input continent, and retrieves the country
 199  * names from the country.tab file.  It also retrieves the localized
 200  * country names.  The returned list of countries is sorted by the
 201  * countries' localized name fields.
 202  */
 203 int
 204 get_tz_countries(struct tz_country **country, struct tz_continent *cont)
 205 {
 206         FILE *fp_zone, *fp_cc;
 207         char buff[BUFFLEN], ccbuf[_CCBUFLEN], *ptr;
 208         char *lp, *lptr, *lp_coord, *lp_cc, *lp_tz;     /* line pointer */
 209         struct tz_country *head = NULL, *prev = NULL, *next, *cp, *cp2;
 210         int sav_errno = 0, ncount, i;
 211         int cmp, status;
 212         size_t len, len_coord, len_ctnt;
 213 
 214         if (cont->ctnt_name == NULL) {
 215                 errno = EINVAL;
 216                 return (-1);
 217         }
 218         len_ctnt = strlen(cont->ctnt_name);
 219         ccbuf[0] = '\0';
 220 
 221         /* open zone_sun.tab and country.tab files */
 222         if ((fp_zone = fopen(ZONE_SUN_TAB, "r")) == NULL) {
 223                 /* fopen() sets errno */
 224                 return (-1);
 225         }
 226         if ((fp_cc = fopen(COUNTRY_TAB, "r")) == NULL) {
 227                 /* fopen() sets errno */
 228                 (void) fclose(fp_zone);
 229                 return (-1);
 230         }
 231 
 232         /* read timezones to match continents, and get countries */
 233         ncount = 0;
 234         /*CONSTANTCONDITION*/
 235         while (1) {
 236                 if (fgets(buff, sizeof (buff), fp_zone) == NULL) {
 237                         if (feof(fp_zone) == 0) {
 238                                 /* fgets() error - errno set */
 239                                 sav_errno = errno;
 240                                 ncount = -1;
 241                         }
 242                         break;
 243                 }
 244                 /* Skip comments or blank/whitespace lines */
 245                 if ((status = skipline(buff)) != 0) {
 246                         if (status == 1)
 247                                 continue;
 248                         else {
 249                                 sav_errno = EINVAL;
 250                                 ncount = -1;
 251                                 break;
 252                         }
 253                 }
 254                 /*
 255                  * If country matches previously *matched* country, skip
 256                  * entry, since zone.tab is alphabetized by country code
 257                  * (It should be a *matched* country, because the same country
 258                  * can be in different continents.)
 259                  */
 260                 /* Get country code */
 261                 lp_cc = skipwhite(&buff[0]);
 262                 if (strcspn(lp_cc, WHITESPACE) != CCLEN) {
 263                         ncount = -1;
 264                         sav_errno = EINVAL;
 265                         break;
 266                 }
 267                 /* Check country code cache; skip if already found */
 268                 if (strncmp(ccbuf, lp_cc, CCLEN) == 0) {
 269                         continue;
 270                 }
 271                 /* Get coordinates */
 272                 lp_coord = skipwhite(lp_cc + CCLEN);
 273                 if (((len_coord = strcspn(lp_coord, WHITESPACE)) !=
 274                                 COORD_FMTLEN1) &&
 275                                 (len_coord != COORD_FMTLEN2)) {
 276                         ncount = -1;
 277                         sav_errno = EINVAL;
 278                         break;
 279                 }
 280 
 281                 /* Get timezone name (Skip timezone description) */
 282                 lp_tz = skipwhite(lp_coord + len_coord);
 283                 if ((len = strcspn(lp_tz, SLASH)) == 0) {
 284                         ncount = -1;
 285                         sav_errno = EINVAL;
 286                         break;
 287                 }
 288                 /* If continents match, allocate a country struct */
 289                 if ((len == len_ctnt) &&
 290                                 (strncmp(cont->ctnt_name, lp_tz, len) == 0)) {
 291                         if ((cp = (struct tz_country *)
 292                             calloc(1, sizeof (struct tz_country))) == NULL) {
 293                                 sav_errno = ENOMEM;
 294                                 ncount = -1;
 295                                 break;
 296                         }
 297                         /* Copy and save country code (len already checked) */
 298                         (void) strncpy(cp->ctry_code, lp_cc, CCLEN);
 299                         cp->ctry_code[CCLEN] = '\0';
 300                         (void) strncpy(ccbuf, lp_cc, CCLEN);
 301                         ccbuf[CCLEN] = '\0';
 302 
 303                         /* Create linked list */
 304                         if (head == NULL) {
 305                                 head = cp;
 306                         } else {
 307                                 prev->ctry_next = cp;
 308                         };
 309                         prev = cp;
 310                         ncount++;
 311                 }
 312         }       /* while */
 313 
 314         if (ncount == -1)
 315                 goto error;
 316 
 317         /* Get country name from country.tab; get localized country name */
 318         /* Read country list, match country codes to process entry */
 319         cp = head;
 320         /*CONSTANTCONDITION*/
 321         while (1) {
 322                 if (fgets(buff, sizeof (buff), fp_cc) == NULL) {
 323                         if (feof(fp_cc) == 0) {
 324                                 /* fgets() sets errno */
 325                                 ncount = -1;
 326                                 sav_errno = errno;
 327                         }
 328                         break;
 329                 }
 330                 /* Skip comments or blank/whitespace lines */
 331                 if ((status = skipline(buff)) != 0) {
 332                         if (status == 1)
 333                                 continue;
 334                         else {
 335                                 sav_errno = EINVAL;
 336                                 ncount = -1;
 337                                 break;
 338                         }
 339                 }
 340                 /* Match country codes */
 341                 if ((len = strcspn(buff, WHITESPACE)) != CCLEN) {
 342                         sav_errno = EINVAL;
 343                         ncount = -1;
 344                         break;
 345                 }
 346                 if ((cmp = strncmp(cp->ctry_code, buff, CCLEN)) == 0) {
 347                         /* Get country description, and localized desc. */
 348                         /* Skip to country description */
 349                         lp = &buff[CCLEN];
 350                         if ((len = strspn(lp, WHITESPACE)) == 0) {
 351                                 sav_errno = EINVAL;
 352                                 ncount = -1;
 353                                 break;
 354                         }
 355                         lp += len;              /* lp points to country desc. */
 356                         len = strcspn(lp, NEWLINE);
 357                         if ((ptr = calloc(len + 1, 1)) == NULL) {
 358                                 ncount = -1;
 359                                 errno = ENOMEM;
 360                                 break;
 361                         }
 362                         (void) strncpy(ptr, lp, len);
 363                         *(ptr + len) = '\0';
 364                         cp->ctry_id_desc = ptr;
 365 
 366                         /* Get localized country description */
 367                         lptr = dgettext(TEXT_DOMAIN, ptr);
 368                         if ((ptr = strdup(lptr)) == NULL) {
 369                                 ncount = -1;
 370                                 errno = ENOMEM;
 371                                 break;
 372                         }
 373                         cp->ctry_display_desc = ptr;
 374                 } else if (cmp > 0) {
 375                         /* Keep searching country.tab */
 376                         continue;
 377                 } else {
 378                         /* Not found - should not happen */
 379                         ncount = -1;
 380                         errno = EILSEQ;
 381                         break;
 382                 }
 383                 if (cp->ctry_next == NULL) {
 384                         /* done with countries list */
 385                         break;
 386                 } else {
 387                         cp = cp->ctry_next;
 388                 }
 389         }               /* while */
 390 
 391         /* Now sort the list by ctry_display_desc field */
 392         if ((ncount != -1) &&
 393                 ((cp2 = calloc(ncount, sizeof (struct tz_country))) != NULL)) {
 394                 /*
 395                  * First copy list to a static array for qsort() to use.
 396                  * Use the cnt_next field to point back to original structure.
 397                  */
 398                 cp = head;
 399                 for (i = 0; i < ncount; i++) {
 400                         next = cp->ctry_next;
 401                         cp->ctry_next = cp;
 402                         (void) memcpy(&cp2[i], cp, sizeof (struct tz_country));
 403                         cp = next;
 404                 }
 405 
 406                 /* Next, call qsort() using strcoll() to order */
 407                 qsort(cp2, ncount, sizeof (struct tz_country),
 408                         (int (*)(const void *, const void *))compar);
 409 
 410                 /* Rearrange the country list according to qsort order */
 411                 head = cp2->ctry_next; /* ctry_next is pointer to orig struct */
 412                 cp = head;
 413                 for (i = 0; i < ncount; i++) {
 414                         prev = cp;
 415                         cp = cp2[i].ctry_next;
 416                         prev->ctry_next = cp;
 417                 }
 418                 cp->ctry_next = NULL;
 419 
 420                 /* Last, free the static buffer */
 421                 free(cp2);
 422 
 423         } else {
 424                 if (ncount != -1)
 425                         ncount = -1;
 426         }
 427 
 428 error:
 429         (void) fclose(fp_zone);
 430         (void) fclose(fp_cc);
 431         if (ncount == -1) {
 432                 /* free the linked list */
 433                 if (head != NULL)
 434                         (void) free_tz_countries(head);
 435                 if (sav_errno)
 436                         errno = sav_errno;
 437         } else {
 438                 *country = head;
 439         }
 440         return (ncount);
 441 }
 442 
 443 /*
 444  * get_timezones_by_country() finds the list of timezones from the
 445  * zone_sun.tab file, for the input country.
 446  */
 447 int
 448 get_timezones_by_country(struct tz_timezone **tmzone,
 449         struct tz_country *country)
 450 {
 451         FILE *fp_zone;          /* zone.tab */
 452         int match = 0, ncount = 0, sav_errno = 0, status;
 453         char buff[1024];
 454         char *lp_cc, *lp_tz, *lp_otz, *lp_coord, *lp_tzdesc, *ptr, *lptr;
 455         size_t len_tz, len_otz, len_coord, len_tzdesc;
 456         struct tz_timezone *head = NULL, *prev = NULL, *tp;
 457 
 458         /* open zone.tab file */
 459         if ((fp_zone = fopen(ZONE_SUN_TAB, "r")) == NULL)
 460                 return (-1);
 461 
 462         /* Read through zone.tab until countries match */
 463         /*CONSTANTCONDITION*/
 464         while (1) {
 465                 if (fgets(buff, sizeof (buff), fp_zone) == NULL) {
 466                         if (feof(fp_zone)) {
 467                                 break;
 468                         } else {
 469                                 /* fgets() sets errno */
 470                                 ncount = -1;
 471                                 sav_errno = errno;
 472                                 break;
 473                         }
 474                 }
 475                 /* Skip comments or blank/whitespace lines */
 476                 if ((status = skipline(buff)) != 0) {
 477                         if (status == 1)
 478                                 continue;
 479                         else {
 480                                 sav_errno = EINVAL;
 481                                 ncount = -1;
 482                                 break;
 483                         }
 484                 }
 485                 /*
 486                  * Find country entries, or detect if no country matches.
 487                  */
 488                 lp_cc = skipwhite(&buff[0]);
 489                 if (strcspn(lp_cc, WHITESPACE) != CCLEN) {
 490                         sav_errno = EINVAL;
 491                         ncount = -1;
 492                         break;
 493                 }
 494                 if (strncmp(country->ctry_code, lp_cc, CCLEN) == 0) {
 495                         match = 1;
 496 
 497                         /* Get coordinates */
 498                         lp_coord = skipwhite(lp_cc + CCLEN);
 499                         if (((len_coord = strcspn(lp_coord, WHITESPACE)) !=
 500                                         COORD_FMTLEN1) &&
 501                                         (len_coord != COORD_FMTLEN2)) {
 502                                 ncount = -1;
 503                                 sav_errno = EINVAL;
 504                                 break;
 505                         }
 506                         /* Get Olson timezone name */
 507                         lp_otz = skipwhite(lp_coord + len_coord);
 508                         len_otz = strcspn(lp_otz, WHITESPACE);
 509 
 510                         /* Get Solaris compatible timezone name */
 511                         lp_tz = skipwhite(lp_otz + len_otz);
 512                         len_tz = strcspn(lp_tz, WHITESPACE_NL);
 513                         if (*(lp_tz + len_tz - 1) == '\n') {
 514                                 /* No timezone description */
 515                                 len_tz--;
 516                                 lp_tzdesc = NULL;
 517                                 len_tzdesc = 0;
 518                         } else {
 519                                 /* Get timezone description */
 520                                 lp_tzdesc = skipwhite(lp_tz +
 521                                         len_tz);
 522                                 len_tzdesc = strcspn(lp_tzdesc,
 523                                         NEWLINE);
 524                         }
 525                         /*
 526                          * Check tz name lengths.  This check assumes the
 527                          * tz_oname and tz_name fields are the same size.
 528                          * (since tz_name may be written with lp_otz, if
 529                          * lp_tz is "-".)
 530                          */
 531                         if ((len_otz > _TZBUFLEN - 1) ||
 532                                 (len_tz > _TZBUFLEN - 1)) {
 533                                 sav_errno = ENAMETOOLONG;
 534                                 ncount = -1;
 535                                 break;
 536                         }
 537                         /* Create timezone struct */
 538                         if ((tp =  (struct tz_timezone *)
 539                                 calloc(1, sizeof (struct tz_timezone))) ==
 540                                         NULL) {
 541                                 sav_errno = ENOMEM;
 542                                 ncount = -1;
 543                                 break;
 544                         }
 545                         /*
 546                          * Copy the timezone names - use the Solaris
 547                          * compatible timezone name if one exists,
 548                          * otherwise use the current Olson timezone
 549                          * name.
 550                          */
 551                         (void) strncpy(tp->tz_oname, lp_otz, len_otz);
 552                         tp->tz_oname[len_otz] = '\0';
 553                         if (strncmp("-", lp_tz, len_tz) == 0) {
 554                                 lp_tz = lp_otz;
 555                                 len_tz = len_otz;
 556                         }
 557                         /* If name has numeric digits, prefix ':' */
 558                         if (strcspn(lp_tz, DIGITS) < len_tz) {
 559                                 if (len_tz > _TZBUFLEN - 2) {
 560                                         free(tp);
 561                                         sav_errno = ENAMETOOLONG;
 562                                         ncount = -1;
 563                                         break;
 564                                 }
 565                                 tp->tz_name[0] = ':';
 566                                 (void) strncpy(tp->tz_name + 1, lp_tz, len_tz);
 567                                 tp->tz_name[len_tz + 1] = '\0';
 568                         } else {
 569                                 (void) strncpy(tp->tz_name, lp_tz, len_tz);
 570                                 tp->tz_name[len_tz] = '\0';
 571                         }
 572                         /* Process timezone description, if one exists */
 573                         if ((lp_tzdesc != NULL) && (*lp_tzdesc != '\n')) {
 574                                 if ((ptr = calloc(1, len_tzdesc + 1))
 575                                                 == NULL) {
 576                                         sav_errno = ENOMEM;
 577                                         ncount = -1;
 578                                         (void) free_timezones(tp);
 579                                         break;
 580                                 }
 581                                 (void) strncpy(ptr, lp_tzdesc, len_tzdesc);
 582                                 *(ptr + len_tzdesc) = '\0';
 583                                 tp->tz_id_desc = ptr;
 584 
 585                                 /* Get localized country description */
 586                                 lptr = dgettext(TEXT_DOMAIN, ptr);
 587                                 if ((ptr = strdup(lptr)) == NULL) {
 588                                         sav_errno = ENOMEM;
 589                                         ncount = -1;
 590                                         (void) free_timezones(tp);
 591                                         break;
 592                                 }
 593                                 tp->tz_display_desc = ptr;
 594 
 595                         } else {
 596                                 tp->tz_id_desc = NULL;
 597                                 tp->tz_display_desc = NULL;
 598                         }
 599                         /* Get coordinate information */
 600                         if (get_coord(tp, lp_coord, len_coord) == -1) {
 601                                 sav_errno = EILSEQ;
 602                                 ncount = -1;
 603                                 (void) free_timezones(tp);
 604                                 break;
 605                         }
 606                         /* Store timezone struct in a linked list */
 607                         if (head == NULL) {
 608                                 head = tp;
 609                         } else {
 610                                 prev->tz_next = tp;
 611                         }
 612                         prev = tp;
 613                         ncount++;
 614                 } else {
 615                         if (match == 1) {
 616                                 /*
 617                                  * At this point, since zone_sun.tab is ordered,
 618                                  * if we've already found timezone entries for
 619                                  * the input country, then we've found all of
 620                                  * the desired timezone entries (since we will
 621                                  * be past that country's section in
 622                                  * zone_sun.tab), and we are done.
 623                                  */
 624                                 break;
 625                         }
 626                 }
 627         }
 628 
 629         /* Finish up */
 630         (void) fclose(fp_zone);
 631         if (ncount == -1) {
 632                 if (head != NULL)
 633                         (void) free_timezones(head);
 634                 if (sav_errno)
 635                         errno = sav_errno;
 636         } else {
 637                 *tmzone = head;
 638         }
 639         return (ncount);
 640 }
 641 
 642 int
 643 free_tz_continents(struct tz_continent *cont)
 644 {
 645         struct tz_continent *cptr, *cprev;
 646 
 647         cptr = cont;
 648         while (cptr != NULL) {
 649                 if (cptr->ctnt_id_desc != NULL)
 650                         free(cptr->ctnt_id_desc);
 651                 if (cptr->ctnt_display_desc != NULL)
 652                         free(cptr->ctnt_display_desc);
 653                 cprev = cptr;
 654                 cptr = cptr->ctnt_next;
 655                 free(cprev);
 656         }
 657         return (0);
 658 }
 659 
 660 int
 661 free_tz_countries(struct tz_country *country)
 662 {
 663         struct tz_country *cptr, *cprev;
 664 
 665         cptr = country;
 666         while (cptr != NULL) {
 667                 if (cptr->ctry_id_desc != NULL)
 668                         free(cptr->ctry_id_desc);
 669                 if (cptr->ctry_display_desc != NULL)
 670                         free(cptr->ctry_display_desc);
 671                 cprev = cptr;
 672                 cptr = cptr->ctry_next;
 673                 free(cprev);
 674         }
 675         return (0);
 676 }
 677 
 678 int
 679 free_timezones(struct tz_timezone *timezone)
 680 {
 681         struct tz_timezone *tzptr, *tzprev;
 682 
 683         tzptr = timezone;
 684         while (tzptr != NULL) {
 685                 if (tzptr->tz_id_desc != NULL)
 686                         free(tzptr->tz_id_desc);
 687                 if (tzptr->tz_display_desc != NULL)
 688                         free(tzptr->tz_display_desc);
 689                 tzprev = tzptr;
 690                 tzptr = tzptr->tz_next;
 691                 free(tzprev);
 692         }
 693         return (0);
 694 }
 695 
 696 /*
 697  *  conv_gmt() returns a GMT-offset style timezone
 698  *    If flag = 0, return Quoted POSIX timezone like: <GMT+8>+8
 699  *    If flag = 1, return zoneinfo timezone like:  :Etc/GMT+8
 700  */
 701 char *
 702 conv_gmt(int seconds, int flag)
 703 {
 704         int hour;
 705         char *cp;
 706 
 707         if ((seconds < _GMT_MIN) || (seconds > _GMT_MAX)) {
 708                 errno = EINVAL;
 709                 return (NULL);
 710         }
 711         hour = (seconds / 60) / 60;
 712 
 713         if (flag == 0) {
 714                 cp = _conv_gmt_posix(hour);
 715         } else if (flag == 1) {
 716                 cp = _conv_gmt_zoneinfo(hour);
 717         } else {
 718                 errno = EINVAL;
 719                 return (NULL);
 720         }
 721         return (cp);
 722 }
 723 
 724 static char *
 725 _conv_gmt_posix(int hour)
 726 {
 727         char *cp;
 728         char xsign;
 729 
 730         if (hour == 0) {
 731                 if ((cp = strdup(GMT0_FMT)) == NULL) {
 732                         errno = ENOMEM;
 733                         return (NULL);
 734                 }
 735         } else {
 736                 if (hour < 0) {
 737                         xsign = '-';
 738                         /* make hour positive for snprintf() */
 739                         hour = -hour;
 740                 } else {
 741                         xsign = '+';
 742                 }
 743                 if ((cp = malloc(GMT_FMT_Q_LEN + 1)) == NULL) {
 744                         errno = ENOMEM;
 745                         return (NULL);
 746                 }
 747                 (void) snprintf(cp, GMT_FMT_Q_LEN + 1, GMT_FMT_Q,
 748                         xsign, hour, xsign, hour);
 749         }
 750         return (cp);
 751 }
 752 
 753 static char *
 754 _conv_gmt_zoneinfo(int hour)
 755 {
 756         char *cp;
 757         char xsign;
 758 
 759         if (hour < 0) {
 760                 xsign = '-';
 761                 /* make hour positive for snprintf() */
 762                 hour = -hour;
 763         } else {
 764                 xsign = '+';
 765         }
 766         if ((cp = malloc(GMT_FMT_ZONE_LEN + 1)) == NULL) {
 767                 errno = ENOMEM;
 768                 return (NULL);
 769         }
 770         (void) snprintf(cp, GMT_FMT_ZONE_LEN + 1, GMT_FMT_ZONE,
 771                 xsign, hour);
 772         return (cp);
 773 }
 774 
 775 /* Regular expression for POSIX GMT-offset timezone */
 776 #define _GMT_EXPR       "(" _GMT_EXPR_U "|" _GMT_EXPR_Q ")"
 777 #define _GMT_EXPR_U     "^[gG][mM][tT][-+]?[0-2]?[0-9]$"
 778 #define _GMT_EXPR_Q     "^<[gG][mM][tT][-+]?[0-2]?[0-9]>[-+]?[0-2]?[0-9]$"
 779 
 780 /*
 781  * Regular expression for quoted POSIX timezone.
 782  */
 783 /* Avoid alphabetic ranges (eg, a-z) due to effect of LC_COLLATE */
 784 #define _ALPHA  "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
 785 #define _NUM    "0123456789"    /* for safe */
 786 #define _STD_Q_ELM      "[-+" _ALPHA _NUM "]"
 787 #define _STD_Q          "<" _STD_Q_ELM _STD_Q_ELM _STD_Q_ELM "+>"
 788 
 789 /* Regular expression for unquoted POSIX timezone */
 790 #define _STD_U_ELM_1    "[^-+,<" _NUM "]"
 791 #define _STD_U_ELM      "[^-+,"  _NUM "]"
 792 #define _STD_U          _STD_U_ELM_1 _STD_U_ELM _STD_U_ELM "+"
 793 
 794 /* Regular expression for POSIX timezone */
 795 #define _STD            "(" _STD_U "|" _STD_Q ")"
 796 #define _DST            _STD
 797 #define _OFFSET         "[-+]?" _TIME
 798 #define _START          "(" _DATEJ "|" _DATEn "|" _DATEM ")"
 799 #define _DATEJ          "J(([0-2]?[0-9]?[0-9])|3[0-5][0-9]|36[0-5])"
 800 #define _DATEn          "(([0-2]?[0-9]?[0-9])|3[0-5][0-9]|36[0-5])"
 801 #define _DATEM          "M([0-9]|10|11|12)\\.[1-5]\\.[0-6]"
 802 #define _END            _START
 803 #define _TIME           _HH "(:" _MM "(:" _SS ")?" ")?"
 804 #define _HH             "(([0-1]?[0-9])|20|21|22|23|24)"
 805 #define _MM             "[0-5]?[0-9]"
 806 #define _SS             _MM
 807 #define _POSIX_EXPR     "^" _STD _OFFSET "(" _DST "(" _OFFSET ")?" \
 808                                 "(," _START "(/" _TIME ")?" \
 809                                 "," _END "(/" _TIME ")?" ")?" ")?" "$"
 810 
 811 #define LEN_TZDIR       (sizeof (TZDIR) - 1)
 812 
 813 /*
 814  *  isvalid_tz() checks if timezone is a valid POSIX or zoneinfo
 815  *  timezone, depending on the value of flag.  For flag = _VTZ_INSTALL,
 816  *  isvalid_tz() behaves according to the behavior of Solaris Install
 817  *  in Solaris 9 and earlier, where timezones under /usr/share/lib/zoneinfo
 818  *  were validated.  isvalid_tz() has a special check for GMT+-* timezones
 819  *  because Solaris Install validated /usr/share/lib/zoneinfo/GMT+-*.
 820  *  However, when /usr/share/lib/zoneinfo/GMT+-* are EOF'd, that check
 821  *  no longer works.
 822  *
 823  *  isvalid_tz() returns 1 if a valid timezone is detected.
 824  */
 825 int
 826 isvalid_tz(char *timezone, char *root, int flag)
 827 {
 828         char path[MAXPATHLEN];
 829         char buf[sizeof (struct tzhead)];
 830         int fid, ret;
 831 
 832         if ((timezone == NULL) || (*timezone == '\0')) {
 833                 return (0);
 834         }
 835 
 836         /* First check if timezone is a valid POSIX timezone */
 837         switch (flag) {
 838         case _VTZ_INSTALL:
 839                 /*
 840                  * Special check for POSIX GMT timezone.
 841                  * If no match, check for zoneinfo timezone below
 842                  */
 843                 if (_tz_match(_GMT_EXPR, timezone) == 0) {
 844                         /* Valid GMT timezone */
 845                         return (1);
 846                 }
 847                 break;
 848         case _VTZ_POSIX:
 849                 /* Check for generic POSIX timezone */
 850                 if (_tz_match(_POSIX_EXPR, timezone) == 0) {
 851                         /* Valid POSIX timezone */
 852                         return (1);
 853                 }
 854                 /* Invalid POSIX timezone */
 855                 return (0);
 856         case _VTZ_ALL:
 857                 /* Check for generic POSIX timezone */
 858                 if (_tz_match(_POSIX_EXPR, timezone) == 0) {
 859                         /* Valid POSIX timezone */
 860                         return (1);
 861                 }
 862                 break;
 863         case _VTZ_ZONEINFO:
 864                 break;
 865         default:
 866                 return (0);
 867         }
 868 
 869         /*
 870          * Check for valid zoneinfo timezone -
 871          * open zoneinfo file and check for magic number
 872          */
 873 
 874         /* skip prepended ':' if one exists */
 875         if (*timezone == ':') {
 876                 timezone++;
 877         }
 878         /* Construct full zoneinfo pathname */
 879         if ((root != NULL) && (*root != '\0')) {
 880                 ret = snprintf(path, sizeof (path),
 881                     "%s%s/%s", root, TZDIR, timezone);
 882                 if (ret >= sizeof (path)) {
 883                         /* too long */
 884                         return (0);
 885                 }
 886         } else {
 887                 ret = snprintf(path, sizeof (path),
 888                     "%s/%s", TZDIR, timezone);
 889                 if (ret >= sizeof (path)) {
 890                         /* too long */
 891                         return (0);
 892                 }
 893         }
 894         if ((fid = open(path, O_RDONLY)) == -1) {
 895                 return (0);
 896         }
 897         if (read(fid, buf, sizeof (struct tzhead)) !=
 898             sizeof (struct tzhead)) {
 899                 (void) close(fid);
 900                 return (0);
 901         }
 902         if (strncmp(buf, TZ_MAGIC, sizeof (TZ_MAGIC) - 1) != 0) {
 903                 (void) close(fid);
 904                 return (0);
 905         }
 906         if (close(fid) == -1) {
 907                 return (0);
 908         }
 909         /* Valid zoneinfo timezone */
 910         return (1);
 911 }
 912 
 913 #define N_MATCH         1
 914 
 915 int
 916 _tz_match(const char *expr, const char *string)
 917 {
 918         regex_t reg;
 919         regmatch_t pmatch[N_MATCH];
 920         int ret;
 921 
 922         ret = regcomp(&reg, expr, REG_EXTENDED);
 923         if (ret != 0) {
 924                 return (-1);
 925         }
 926 
 927         ret = regexec((const regex_t *)&reg, string, N_MATCH, pmatch, 0);
 928         if (ret == 0) {
 929 #ifdef DEBUG
 930                 printf("OK matched - %s\n", string);
 931 #endif
 932                 regfree(&reg);
 933                 return (0);
 934         }
 935 #ifdef DEBUG
 936         printf("NOT matched - %s\n", string);
 937 #endif
 938         regfree(&reg);
 939         return (-1);
 940 }
 941 
 942 char *
 943 get_system_tz(char *root)
 944 {
 945         FILE *ifp;
 946         char buff[512];
 947         int serrno, ret;
 948         char *sp, *ptr, *p;
 949         char fname[MAXPATHLEN];
 950 
 951         if ((ret = snprintf(fname, sizeof (fname), "%s/%s", root, DEFINIT)) >=
 952                         sizeof (fname)) {
 953                 errno = ENAMETOOLONG;
 954                 return (NULL);
 955         } else if (ret < 0) {
 956                 return (NULL);
 957         }
 958         if ((ifp = fopen(fname, "r")) == NULL)
 959                 return (NULL);
 960         while (fgets(buff, sizeof (buff), ifp) != NULL) {
 961                 if (strncmp(buff, "TZ=", 3) == 0) {
 962                         (void) fclose(ifp);
 963                         p = &buff[3];
 964                         if ((sp = strchr(p, ';')) != NULL) {
 965                                 *sp = '\0';
 966                         } else if ((sp = strchr(p, '\n')) != NULL) {
 967                                 *sp = '\0';
 968                         }
 969                         if (strpbrk(p, "\"'") != NULL) {
 970                                 strip_quotes(p, p);
 971                         }
 972                         ptr = strdup(p);
 973                         if (ptr == NULL) {
 974                                 errno = ENOMEM;
 975                                 return (NULL);
 976                         }
 977                         return (ptr);
 978                 }
 979         }
 980 
 981         /* Either reached EOF with no TZ= entry, or got fgets() error */
 982         serrno = errno;
 983         if (feof(ifp) != 0) {
 984                 /* No "TZ=" entry found */
 985                 serrno = EINVAL;
 986         }
 987         (void) fclose(ifp);
 988         errno = serrno;
 989         return (NULL);
 990 }
 991 
 992 int
 993 set_system_tz(char *tz, char *root)
 994 {
 995         FILE *ifp, *ofp;        /* Input & output files */
 996         char *tmpdir, *tmp;     /* Temp file name and location */
 997         char buff[1024];
 998         int replaced = 0, ret, serrno;
 999         char *tdb;
1000         struct stat sb;
1001         char fname[MAXPATHLEN];
1002         const char *tzfmt;
1003         int len, fd;
1004 
1005         if (tz == NULL || root == NULL)
1006                 return (-1);
1007 
1008         if (strchr(tz, '<')) {
1009                 tzfmt = TZ_FMT_Q;
1010         } else {
1011                 tzfmt = TZ_FMT;
1012         }
1013 
1014         if ((ret = snprintf(fname, sizeof (fname), "%s/%s", root, DEFINIT)) >=
1015                         sizeof (fname)) {
1016                 errno = ENAMETOOLONG;
1017                 return (-1);
1018         } else if (ret < 0) {
1019                 return (-1);
1020         }
1021 
1022         /*
1023          * Generate temporary file name to use.  We make sure it's in the same
1024          * directory as the db we're processing so that we can use rename to
1025          * do the replace later.  Otherwise we run the risk of being on the
1026          * wrong filesystem and having rename() fail for that reason.
1027          */
1028         tdb = fname;
1029         if (trav_link(&tdb) == -1)
1030                 return (-1);
1031         if ((tmpdir = strdup(tdb)) == NULL) {
1032                 errno = ENOMEM;
1033                 return (-1);
1034         }
1035         remove_component(tmpdir);
1036         if ((len = strlen(tmpdir)) == 0) {
1037                 (void) strcpy(tmpdir, ".");
1038                 len = 1;
1039         }
1040 
1041         if ((tmp = malloc(len + TR_LEN + 1)) == NULL) {
1042                 free(tmpdir);
1043                 errno = ENOMEM;
1044                 return (-1);
1045         }
1046         (void) strcpy(tmp, tmpdir);
1047         (void) strcpy(tmp + len, TRAILER);
1048         free(tmpdir);
1049         if ((fd = mkstemp(tmp)) == -1) {
1050                 free(tmp);
1051                 return (-1);
1052         }
1053         if ((ofp = fdopen(fd, "w")) == NULL) {
1054                 serrno = errno;
1055                 (void) close(fd);
1056                 free(tmp);
1057                 errno = serrno;
1058                 return (-1);
1059         }
1060 
1061         /* Preserve permissions of current file if it exists */
1062         if (stat(tdb, &sb) == 0) {
1063                 if (fchmod(fileno(ofp), sb.st_mode) == -1) {
1064                         serrno = errno;
1065                         (void) fclose(ofp);
1066                         (void) unlink(tmp);
1067                         free(tmp);
1068                         errno = serrno;
1069                         return (-1);
1070                 }
1071                 if (fchown(fileno(ofp), sb.st_uid, sb.st_gid) == -1) {
1072                         serrno = errno;
1073                         (void) fclose(ofp);
1074                         (void) unlink(tmp);
1075                         free(tmp);
1076                         errno = serrno;
1077                         return (-1);
1078                 }
1079         } else if (errno != ENOENT) {
1080                 serrno = errno;
1081                 (void) fclose(ofp);
1082                 (void) unlink(tmp);
1083                 free(tmp);
1084                 errno = serrno;
1085                 return (-1);
1086         }
1087 
1088         if ((ifp = fopen(fname, "r+")) != NULL) {
1089                 while (fgets(buff, sizeof (buff), ifp) != NULL) {
1090                         if (!replaced && (strncmp(buff, "TZ=", 3) == 0)) {
1091                                 ret = snprintf(buff, sizeof (buff), tzfmt,
1092                                                         tz);
1093                                 if ((ret >= sizeof (buff)) || (ret < 0)) {
1094                                         if (ret >= sizeof (buff))
1095                                                 serrno = EINVAL;
1096                                         (void) fclose(ofp);
1097                                         (void) fclose(ifp);
1098                                         (void) unlink(tmp);
1099                                         free(tmp);
1100                                         errno = serrno;
1101                                         return (-1);
1102                                 }
1103                                 replaced = 1;
1104                         }
1105                         if (fputs(buff, ofp) == EOF) {
1106                                 serrno = errno;
1107                                 (void) fclose(ofp);
1108                                 (void) fclose(ifp);
1109                                 (void) unlink(tmp);
1110                                 free(tmp);
1111                                 errno = serrno;
1112                                 return (-1);
1113                         }
1114                 }
1115                 (void) fclose(ifp);
1116 
1117         } else if (errno != ENOENT) {
1118                 serrno = errno;
1119                 (void) fclose(ofp);
1120                 (void) unlink(tmp);
1121                 free(tmp);
1122                 errno = serrno;
1123                 return (-1);
1124         }
1125 
1126         /*
1127          * no $(ROOT)/etc/default/init found, or
1128          * no "TZ=" entry found in the init file.
1129          */
1130         if (!replaced &&
1131             (fprintf(ofp, tzfmt, tz) == EOF)) {
1132                 serrno = errno;
1133                 (void) fclose(ofp);
1134                 (void) unlink(tmp);
1135                 free(tmp);
1136                 errno = serrno;
1137                 return (-1);
1138         }
1139 
1140         if (fsync(fileno(ofp))) {
1141                 serrno = errno;
1142                 (void) unlink(tmp);
1143                 free(tmp);
1144                 errno = serrno;
1145                 return (-1);
1146         }
1147 
1148         (void) fclose(ofp);
1149         if (rename(tmp, tdb) != 0) {
1150                 serrno = errno;
1151                 (void) unlink(tmp);
1152                 free(tmp);
1153                 errno = serrno;
1154                 return (-1);
1155         } else {
1156                 free(tmp);
1157                 return (0);
1158         }
1159 }
1160 
1161 /*
1162  * Function to traverse a symlink path to find the real file at the end of
1163  * the rainbow.
1164  */
1165 int
1166 trav_link(char **path)
1167 {
1168         static char newpath[MAXPATHLEN];
1169         char lastpath[MAXPATHLEN];
1170         int len, ret;
1171         char *tp;
1172 
1173         (void) strcpy(lastpath, *path);
1174         while ((len = readlink(*path, newpath, sizeof (newpath))) != -1) {
1175                 newpath[len] = '\0';
1176                 if (newpath[0] != '/') {
1177                         if ((tp = strdup(newpath)) == NULL) {
1178                                 errno = ENOMEM;
1179                                 return (-1);
1180                         }
1181                         remove_component(lastpath);
1182                         ret = snprintf(newpath, sizeof (newpath),
1183                                 "%s/%s", lastpath, tp);
1184                         free(tp);
1185                         if ((ret >= sizeof (newpath)) || (ret < 0))
1186                                 return (-1);
1187                 }
1188                 (void) strcpy(lastpath, newpath);
1189                 *path = newpath;
1190         }
1191 
1192         /*
1193          * ENOENT or EINVAL is the normal exit case of the above loop.
1194          */
1195         if ((errno == ENOENT) || (errno == EINVAL))
1196                 return (0);
1197         else
1198                 return (-1);
1199 }
1200 
1201 void
1202 remove_component(char *path)
1203 {
1204         char *p;
1205 
1206         p = strrchr(path, '/');                 /* find last '/'        */
1207         if (p == NULL) {
1208                 *path = '\0';                   /* set path to null str */
1209         } else {
1210                 *p = '\0';                      /* zap it               */
1211         }
1212 }
1213 
1214 /*
1215  *  get_coord() fills in the tz_coord structure of the tz_timezone
1216  *  struct.  It returns 0 on success, or -1 on error.
1217  *  The format of p_coord is:
1218  *
1219  *      Latitude and longitude of the zone's principal location
1220  *      in ISO 6709 sign-degrees-minutes-seconds format,
1221  *      either +-DDMM+-DDDMM or +-DDMMSS+-DDDMMSS,
1222  *      first latitude (+ is north), then longitude (+ is east).
1223  */
1224 static int
1225 get_coord(struct tz_timezone *tp, char *p_coord, size_t len_coord)
1226 {
1227         int i, fmt_flag, nchar;
1228         int *signp, *degp, *minp, *secp;
1229         struct tz_coord *tcp;
1230         char buff[512], *endp;
1231 
1232         tcp = &(tp->tz_coord);
1233 
1234         /* Figure out which format to use */
1235         if (len_coord == COORD_FMTLEN1) {
1236                 /* "+-DDMM+-DDDMM" */
1237                 fmt_flag = COORD_FMT1;
1238         } else if (len_coord == COORD_FMTLEN2) {
1239                 /* "+-DDMMSS+-DDDMMSS" */
1240                 fmt_flag = COORD_FMT2;
1241         } else {
1242                 /* error */
1243                 return (-1);
1244         }
1245         /*
1246          * First time through, get values for latitude;
1247          * second time through, get values for longitude.
1248          */
1249         for (i = 0; i < 2; i++) {
1250                 /* Set up pointers */
1251                 if (i == 0) {
1252                         /* Do latitude */
1253                         nchar = COORD_DLEN_LAT;
1254                         signp = (int *)&(tcp->lat_sign);
1255                         degp = (int *)&(tcp->lat_degree);
1256                         minp = (int *)&(tcp->lat_minute);
1257                         secp = (int *)&(tcp->lat_second);
1258                 } else {
1259                         /* Do longitude */
1260                         nchar = COORD_DLEN_LONG;
1261                         signp = (int *)&(tcp->long_sign);
1262                         degp = (int *)&tcp->long_degree;
1263                         minp = (int *)&tcp->long_minute;
1264                         secp = (int *)&tcp->long_second;
1265                 }
1266                 /* Get latitude/logitude sign */
1267                 if (*p_coord == '+') {
1268                         *signp = 1;
1269                 } else if (*p_coord == '-') {
1270                         *signp = -1;
1271                 } else {
1272                         return (-1);
1273                 }
1274                 p_coord++;
1275 
1276                 /* Get DD latitude, or DDD longitude */
1277                 (void) strncpy(buff, p_coord, nchar);
1278                 buff[nchar] = '\0';
1279                 errno = 0;
1280                 *degp = (int)strtol(buff, &endp, 10);
1281                 if ((endp != &buff[nchar]) || ((*degp == 0) && (errno != 0)))
1282                         return (-1);
1283                 p_coord += nchar;
1284 
1285                 /* Get MM latitude/longitude */
1286                 (void) strncpy(buff, p_coord, COORD_MLEN);
1287                 buff[COORD_MLEN] = '\0';
1288                 errno = 0;
1289                 *minp = (int)strtol(buff, &endp, 10);
1290                 if ((endp != &buff[COORD_MLEN]) ||
1291                                 ((*degp == 0) && (errno != 0)))
1292                         return (-1);
1293                 p_coord += COORD_MLEN;
1294 
1295                 /* If FMT2, then get SS latitude/longitude */
1296                 if (fmt_flag == COORD_FMT2) {
1297                         (void) strncpy(buff, p_coord, COORD_SLEN);
1298                         buff[COORD_SLEN] = '\0';
1299                         errno = 0;
1300                         *secp = (int)strtol(buff, &endp, 10);
1301                         if ((endp != &buff[COORD_SLEN]) ||
1302                                         ((*degp == 0) && (errno != 0)))
1303                                 return (-1);
1304                         p_coord += COORD_SLEN;
1305                 } else {
1306                         *secp = 0;
1307                 }
1308         }
1309         return (0);
1310 }
1311 
1312 static char *
1313 skipwhite(char *cp)
1314 {
1315         while (*cp && ((*cp == ' ') || (*cp == '\t'))) {
1316                 cp++;
1317         }
1318 
1319         return (cp);
1320 }
1321 
1322 /*
1323  *  skipline() checks if the line begins with a comment
1324  *  comment character anywhere in the line, or if the
1325  *  line is only whitespace.
1326  *  skipline() also checks if the line read is too long to
1327  *  fit in the buffer.
1328  *  skipline() returns 1 if the line can be skipped, -1 if
1329  *  the line read is too long, and 0 if the line should not be skipped.
1330  */
1331 static int
1332 skipline(char *line)
1333 {
1334         size_t len;
1335 
1336         len = strlen(line);
1337         if (line[len - 1] != '\n')
1338                 return (-1);
1339         if (line[0] == '#' || line[0] == '\0' ||
1340                 (len = strspn(line, " \t\n")) == strlen(line) ||
1341                 strchr(line, '#') == line + len)
1342 
1343                 return (1);
1344         else
1345                 return (0);
1346 }
1347 
1348 /*
1349  * strip_quotes -- strip double (") or single (') quotes
1350  */
1351 static void
1352 strip_quotes(char *from, char *to)
1353 {
1354         char *strip_ptr = NULL;
1355 
1356         while (*from != '\0') {
1357                 if ((*from == '"') || (*from == '\'')) {
1358                         if (strip_ptr == NULL)
1359                                 strip_ptr = to;
1360                 } else {
1361                         if (strip_ptr != NULL) {
1362                                 *strip_ptr = *from;
1363                                 strip_ptr++;
1364                         } else {
1365                                 *to = *from;
1366                                 to++;
1367                         }
1368                 }
1369                 from++;
1370         }
1371         if (strip_ptr != NULL) {
1372                 *strip_ptr = '\0';
1373         } else {
1374                 *to = '\0';
1375         }
1376 }
1377 
1378 /*
1379  * Compare function used by get_tz_countries() - uses strcoll()
1380  * for locale-sensitive comparison for the localized country names.
1381  */
1382 static int
1383 compar(struct tz_country *p1, struct tz_country *p2)
1384 {
1385         int ret;
1386 
1387         ret = strcoll(p1->ctry_display_desc, p2->ctry_display_desc);
1388         return (ret);
1389 }