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