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(®, expr, REG_EXTENDED); 923 if (ret != 0) { 924 return (-1); 925 } 926 927 ret = regexec((const regex_t *)®, string, N_MATCH, pmatch, 0); 928 if (ret == 0) { 929 #ifdef DEBUG 930 printf("OK matched - %s\n", string); 931 #endif 932 regfree(®); 933 return (0); 934 } 935 #ifdef DEBUG 936 printf("NOT matched - %s\n", string); 937 #endif 938 regfree(®); 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 }