1 /* 2 * Copyright 1996 Massachusetts Institute of Technology 3 * 4 * Permission to use, copy, modify, and distribute this software and 5 * its documentation for any purpose and without fee is hereby 6 * granted, provided that both the above copyright notice and this 7 * permission notice appear in all copies, that both the above 8 * copyright notice and this permission notice appear in all 9 * supporting documentation, and that the name of M.I.T. not be used 10 * in advertising or publicity pertaining to distribution of the 11 * software without specific, written prior permission. M.I.T. makes 12 * no representations about the suitability of this software for any 13 * purpose. It is provided "as is" without express or implied 14 * warranty. 15 * 16 * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS 17 * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, 18 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT 20 * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 23 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 25 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 26 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 27 * SUCH DAMAGE. 28 */ 29 30 /* 31 * Second attempt at a `tzmenu' program, using the separate description 32 * files provided in newer tzdata releases. 33 */ 34 35 /* 36 * When making changes to parser code, run baseline target, check that there are 37 * no unintended changes and commit updated file. 38 */ 39 40 #include <sys/cdefs.h> 41 #include <err.h> 42 #include <errno.h> 43 #include <stdio.h> 44 #include <stdlib.h> 45 #include <string.h> 46 #include <time.h> 47 #include <unistd.h> 48 49 #include <sys/fcntl.h> 50 #include <sys/param.h> 51 #include <sys/queue.h> 52 #include <sys/stat.h> 53 #include <sys/sysctl.h> 54 55 #ifdef HAVE_BSDDIALOG 56 #include <bsddialog.h> 57 #include <locale.h> 58 #endif 59 60 #define _PATH_ZONETAB "/usr/share/zoneinfo/zone1970.tab" 61 #define _PATH_ISO3166 "/usr/share/misc/iso3166" 62 #define _PATH_ZONEINFO "/usr/share/zoneinfo" 63 #define _PATH_LOCALTIME "/etc/localtime" 64 #define _PATH_DB "/var/db/zoneinfo" 65 #define _PATH_WALL_CMOS_CLOCK "/etc/wall_cmos_clock" 66 67 #ifdef PATH_MAX 68 #define SILLY_BUFFER_SIZE (2 * PATH_MAX) 69 #else 70 #warning "Somebody needs to fix this to dynamically size this buffer." 71 #define SILLY_BUFFER_SIZE 2048 72 #endif 73 74 /* special return codes for `fire' actions */ 75 #define DITEM_FAILURE 1 76 77 /* flags - returned in upper 16 bits of return status */ 78 #define DITEM_LEAVE_MENU (1 << 16) 79 #define DITEM_RECREATE (1 << 18) 80 81 static char path_zonetab[MAXPATHLEN], path_iso3166[MAXPATHLEN], 82 path_zoneinfo[MAXPATHLEN], path_localtime[MAXPATHLEN], 83 path_db[MAXPATHLEN], path_wall_cmos_clock[MAXPATHLEN]; 84 85 static int reallydoit = 1; 86 static int reinstall = 0; 87 static char *chrootenv = NULL; 88 89 static void usage(void); 90 static int install_zoneinfo(const char *zoneinfo); 91 static void message_zoneinfo_file(const char *title, char *prompt); 92 static int install_zoneinfo_file(const char *zoneinfo_file); 93 94 #ifdef HAVE_BSDDIALOG 95 static struct bsddialog_conf conf; 96 97 /* for use in describing more exotic behaviors */ 98 typedef struct dialogMenuItem { 99 char *prompt; 100 char *title; 101 int (*fire)(struct dialogMenuItem *self); 102 void *data; 103 } dialogMenuItem; 104 105 static int 106 xdialog_menu(char *title, char *cprompt, int item_no, dialogMenuItem *ditems) 107 { 108 int i, result, menurows, choice = 0; 109 struct bsddialog_menuitem *listitems; 110 111 /* initialize list items */ 112 listitems = calloc(item_no + 1, sizeof(struct bsddialog_menuitem)); 113 if (listitems == NULL) 114 errx(1, "Failed to allocate memory in xdialog_menu"); 115 for (i = 0; i < item_no; i++) { 116 listitems[i].prefix = ""; 117 listitems[i].depth = 0; 118 listitems[i].bottomdesc = ""; 119 listitems[i].on = false; 120 listitems[i].name = ditems[i].prompt; 121 listitems[i].desc = ditems[i].title; 122 } 123 124 again: 125 conf.title = title; 126 menurows = item_no < 16 ? item_no : 16; 127 result = bsddialog_menu(&conf, cprompt, BSDDIALOG_AUTOSIZE, 128 BSDDIALOG_AUTOSIZE, menurows, item_no, listitems, &choice); 129 switch (result) { 130 case BSDDIALOG_ESC: 131 result = -1; 132 break; 133 case BSDDIALOG_OK: 134 if (ditems[choice].fire != NULL) { 135 int status; 136 137 status = ditems[choice].fire(ditems + choice); 138 if (status & DITEM_RECREATE) { 139 goto again; 140 } 141 } 142 result = 0; 143 break; 144 case BSDDIALOG_CANCEL: 145 default: 146 result = 1; 147 break; 148 } 149 150 free(listitems); 151 return (result); 152 } 153 154 static int usedialog = 1; 155 156 static int confirm_zone(const char *filename); 157 static int continent_country_menu(dialogMenuItem *); 158 static int set_zone(dialogMenuItem *); 159 static int set_zone_menu(dialogMenuItem *); 160 static int set_zone_utc(void); 161 162 struct continent { 163 dialogMenuItem *menu; 164 int nitems; 165 }; 166 167 static struct continent africa, america, antarctica, arctic, asia, atlantic; 168 static struct continent australia, europe, indian, pacific, utc; 169 170 static struct continent_names { 171 const char *name; 172 struct continent *continent; 173 } continent_names[] = { 174 { "Africa", &africa }, 175 { "America", &america }, 176 { "Antarctica", &antarctica }, 177 { "Arctic", &arctic }, 178 { "Asia", &asia }, 179 { "Atlantic", &atlantic }, 180 { "Australia", &australia }, 181 { "Europe", &europe }, 182 { "Indian", &indian }, 183 { "Pacific", &pacific }, 184 { "UTC", &utc } 185 }; 186 187 static struct continent_items { 188 char prompt[3]; 189 char title[30]; 190 } continent_items[] = { 191 { "1", "Africa" }, 192 { "2", "America -- North and South" }, 193 { "3", "Antarctica" }, 194 { "4", "Arctic Ocean" }, 195 { "5", "Asia" }, 196 { "6", "Atlantic Ocean" }, 197 { "7", "Australia" }, 198 { "8", "Europe" }, 199 { "9", "Indian Ocean" }, 200 { "10", "Pacific Ocean" }, 201 { "11", "UTC" } 202 }; 203 204 #define NCONTINENTS \ 205 (int)((sizeof(continent_items)) / (sizeof(continent_items[0]))) 206 static dialogMenuItem continents[NCONTINENTS]; 207 208 #define OCEANP(x) ((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9) 209 210 static int 211 continent_country_menu(dialogMenuItem *continent) 212 { 213 char title[64], prompt[64]; 214 struct continent *contp = continent->data; 215 int isocean = OCEANP(continent - continents); 216 int rv; 217 218 if (strcmp(continent->title, "UTC") == 0) 219 return (set_zone_utc()); 220 221 /* It's amazing how much good grammar really matters... */ 222 if (!isocean) { 223 snprintf(title, sizeof(title), "Countries in %s", 224 continent->title); 225 snprintf(prompt, sizeof(prompt), "Select a country or region"); 226 } else { 227 snprintf(title, sizeof(title), "Islands and groups in the %s", 228 continent->title); 229 snprintf(prompt, sizeof(prompt), "Select an island or group"); 230 } 231 232 rv = xdialog_menu(title, prompt, contp->nitems, contp->menu); 233 return (rv == 0 ? DITEM_LEAVE_MENU : DITEM_RECREATE); 234 } 235 236 static struct continent * 237 find_continent(int lineno, const char *name) 238 { 239 char *cname, *cp; 240 int i; 241 242 /* 243 * Both normal (the ones in zone filename, e.g. Europe/Andorra) and 244 * override (e.g. Atlantic/) entries should contain '/'. 245 */ 246 cp = strdup(name); 247 if (cp == NULL) 248 err(1, "strdup"); 249 cname = strsep(&cp, "/"); 250 if (cp == NULL) 251 errx(1, "%s:%d: invalid entry `%s'", path_zonetab, lineno, 252 cname); 253 254 for (i = 0; i < NCONTINENTS; i++) 255 if (strcmp(cname, continent_names[i].name) == 0) { 256 free(cname); 257 return (continent_names[i].continent); 258 } 259 260 errx(1, "%s:%d: continent `%s' unknown", path_zonetab, lineno, cname); 261 } 262 263 static const char * 264 find_continent_name(struct continent *cont) 265 { 266 int i; 267 268 for (i = 0; i < NCONTINENTS; i++) 269 if (cont == continent_names[i].continent) 270 return (continent_names[i].name); 271 return ("Unknown"); 272 } 273 274 struct country { 275 char *name; 276 char *tlc; 277 int nzones; 278 struct continent *override; /* continent override */ 279 struct continent *alternate; /* extra continent */ 280 TAILQ_HEAD(, zone) zones; 281 dialogMenuItem *submenu; 282 }; 283 284 struct zone { 285 TAILQ_ENTRY(zone) link; 286 char *descr; 287 char *filename; 288 struct continent *continent; 289 }; 290 291 /* 292 * This is the easiest organization... we use ISO 3166 country codes, 293 * of the two-letter variety, so we just size this array to suit. 294 * Beats worrying about dynamic allocation. 295 */ 296 #define NCOUNTRIES (26 * 26) 297 static struct country countries[NCOUNTRIES]; 298 299 #define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A')) 300 301 /* 302 * Read the ISO 3166 country code database in _PATH_ISO3166 303 * (/usr/share/misc/iso3166). On error, exit via err(3). 304 */ 305 static void 306 read_iso3166_table(void) 307 { 308 FILE *fp; 309 struct country *cp; 310 size_t len; 311 char *s, *t, *name; 312 int lineno; 313 314 fp = fopen(path_iso3166, "r"); 315 if (!fp) 316 err(1, "%s", path_iso3166); 317 lineno = 0; 318 319 while ((s = fgetln(fp, &len)) != NULL) { 320 lineno++; 321 if (s[len - 1] != '\n') 322 errx(1, "%s:%d: invalid format", path_iso3166, lineno); 323 s[len - 1] = '\0'; 324 if (s[0] == '#' || strspn(s, " \t") == len - 1) 325 continue; 326 327 /* Isolate the two-letter code. */ 328 t = strsep(&s, "\t"); 329 if (t == NULL || strlen(t) != 2) 330 errx(1, "%s:%d: invalid format", path_iso3166, lineno); 331 if (t[0] < 'A' || t[0] > 'Z' || t[1] < 'A' || t[1] > 'Z') 332 errx(1, "%s:%d: invalid code `%s'", path_iso3166, 333 lineno, t); 334 335 /* Now skip past the three-letter and numeric codes. */ 336 name = strsep(&s, "\t"); /* 3-let */ 337 if (name == NULL || strlen(name) != 3) 338 errx(1, "%s:%d: invalid format", path_iso3166, lineno); 339 name = strsep(&s, "\t"); /* numeric */ 340 if (name == NULL || strlen(name) != 3) 341 errx(1, "%s:%d: invalid format", path_iso3166, lineno); 342 343 name = s; 344 345 cp = &countries[CODE2INT(t)]; 346 if (cp->name) 347 errx(1, "%s:%d: country code `%s' multiply defined: %s", 348 path_iso3166, lineno, t, cp->name); 349 cp->name = strdup(name); 350 if (cp->name == NULL) 351 errx(1, "malloc failed"); 352 cp->tlc = strdup(t); 353 if (cp->tlc == NULL) 354 errx(1, "malloc failed"); 355 } 356 357 fclose(fp); 358 } 359 360 static struct country * 361 find_country(int lineno, const char *tlc) 362 { 363 struct country *cp; 364 365 if (strlen(tlc) != 2 || 366 tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z') 367 errx(1, "%s:%d: country code `%s' invalid", path_zonetab, 368 lineno, tlc); 369 370 cp = &countries[CODE2INT(tlc)]; 371 if (cp->name == NULL) 372 errx(1, "%s:%d: country code `%s' unknown", path_zonetab, 373 lineno, tlc); 374 375 return (cp); 376 } 377 378 static void 379 add_cont_to_country(struct country *cp, struct continent *cont) 380 { 381 struct zone *zp; 382 383 TAILQ_FOREACH(zp, &cp->zones, link) { 384 if (zp->continent == cont) 385 return; 386 } 387 cp->alternate = cont; 388 } 389 390 static void 391 add_zone_to_country(int lineno, struct country *cp, const char *descr, 392 const char *file, struct continent *cont) 393 { 394 struct zone *zp; 395 396 zp = malloc(sizeof(*zp)); 397 if (zp == NULL) 398 errx(1, "malloc(%zu)", sizeof(*zp)); 399 400 if (cp->nzones == 0) 401 TAILQ_INIT(&cp->zones); 402 403 if (descr != NULL) { 404 zp->descr = strdup(descr); 405 if (zp->descr == NULL) 406 errx(1, "malloc failed"); 407 } else { 408 zp->descr = NULL; 409 } 410 zp->filename = strdup(file); 411 if (zp->filename == NULL) 412 errx(1, "malloc failed"); 413 zp->continent = cp->override != NULL ? cp->override : cont; 414 TAILQ_INSERT_TAIL(&cp->zones, zp, link); 415 cp->nzones++; 416 } 417 418 /* 419 * This comparison function intentionally sorts all of the null-named 420 * ``countries''---i.e., the codes that don't correspond to a real 421 * country---to the end. Everything else is lexical by country name. 422 */ 423 static int 424 compare_countries(const void *xa, const void *xb) 425 { 426 const struct country *a = xa, *b = xb; 427 428 if (a->name == 0 && b->name == 0) 429 return (0); 430 if (a->name == 0 && b->name != 0) 431 return (1); 432 if (b->name == 0) 433 return (-1); 434 435 return (strcmp(a->name, b->name)); 436 } 437 438 /* 439 * This must be done AFTER all zone descriptions are read, since it breaks 440 * CODE2INT(). 441 */ 442 static void 443 sort_countries(void) 444 { 445 446 qsort(countries, NCOUNTRIES, sizeof(countries[0]), compare_countries); 447 } 448 449 static void 450 read_zones(void) 451 { 452 FILE *fp; 453 struct continent *cont; 454 struct country *cp; 455 size_t len; 456 char *line, *country_list, *tlc, *file, *descr; 457 char *p, *q; 458 int lineno; 459 int pass = 1; 460 461 fp = fopen(path_zonetab, "r"); 462 if (!fp) 463 err(1, "%s", path_zonetab); 464 465 again: 466 lineno = 0; 467 while ((line = fgetln(fp, &len)) != NULL) { 468 lineno++; 469 if (line[len - 1] != '\n') 470 errx(1, "%s:%d: invalid format", path_zonetab, lineno); 471 line[len - 1] = '\0'; 472 473 switch (pass) 474 { 475 case 1: 476 /* 477 * First pass: collect overrides, only looking for 478 * single continent ones for the moment. 479 * 480 * zone1970.tab introduced continent overrides in the 481 * following format: 482 * 483 * #@TLC[,TLC...]<tab>CONTINENT/[,CONTINENT/...] 484 */ 485 if (strncmp(line, "#@", strlen("#@")) != 0) 486 continue; 487 line += 2; 488 country_list = strsep(&line, "\t"); 489 /* Skip multi-continent overrides */ 490 if (strchr(line, ',') != NULL) 491 continue; 492 cont = find_continent(lineno, line); 493 /* Parse and store overrides */ 494 while (country_list != NULL) { 495 tlc = strsep(&country_list, ","); 496 cp = find_country(lineno, tlc); 497 cp->override = cont; 498 } 499 break; 500 case 2: 501 /* Second pass: parse actual data */ 502 if (line[0] == '#') 503 continue; 504 505 country_list = strsep(&line, "\t"); 506 /* coord = */ strsep(&line, "\t"); /* Unused */ 507 file = strsep(&line, "\t"); 508 cont = find_continent(lineno, file); 509 descr = (line != NULL && *line != '\0') ? line : NULL; 510 511 while (country_list != NULL) { 512 tlc = strsep(&country_list, ","); 513 cp = find_country(lineno, tlc); 514 add_zone_to_country(lineno, cp, descr, file, 515 cont); 516 } 517 break; 518 case 3: 519 /* Third pass: collect multi-continent overrides */ 520 if (strncmp(line, "#@", strlen("#@")) != 0) 521 continue; 522 line += 2; 523 country_list = strsep(&line, "\t"); 524 /* Skip single-continent overrides */ 525 if (strchr(line, ',') == NULL) 526 continue; 527 while (line != NULL) { 528 cont = find_continent(lineno, line); 529 p = q = strdup(country_list); 530 if (p == NULL) 531 errx(1, "malloc failed"); 532 while (q != NULL) { 533 tlc = strsep(&q, ","); 534 cp = find_country(lineno, tlc); 535 add_cont_to_country(cp, cont); 536 } 537 free(p); 538 strsep(&line, ","); 539 } 540 break; 541 } 542 } 543 544 if (pass++ < 3) { 545 errno = 0; 546 rewind(fp); 547 if (errno != 0) 548 err(1, "failed to rewind %s", path_zonetab); 549 goto again; 550 } 551 fclose(fp); 552 } 553 554 static void 555 dump_zonetab(void) 556 { 557 struct country *cp; 558 struct zone *zp; 559 const char *cont; 560 561 for (cp = countries; cp->name != NULL; cp++) { 562 printf("%s:%s\n", cp->tlc, cp->name); 563 TAILQ_FOREACH(zp, &cp->zones, link) { 564 cont = find_continent_name(zp->continent); 565 printf(" %s:%s\n", cont, zp->filename); 566 } 567 } 568 } 569 570 static void 571 make_menus(void) 572 { 573 struct country *cp; 574 struct zone *zp, *zp2; 575 struct continent *cont; 576 dialogMenuItem *dmi; 577 int i; 578 579 /* 580 * First, count up all the countries in each continent/ocean. 581 * Be careful to count those countries which have multiple zones 582 * only once for each. NB: some countries are in multiple 583 * continents/oceans. 584 */ 585 for (cp = countries; cp->name; cp++) { 586 if (cp->nzones == 0) 587 continue; 588 TAILQ_FOREACH(zp, &cp->zones, link) { 589 cont = zp->continent; 590 for (zp2 = TAILQ_FIRST(&cp->zones); 591 zp2->continent != cont; 592 zp2 = TAILQ_NEXT(zp2, link)) 593 ; 594 if (zp2 == zp) 595 zp->continent->nitems++; 596 } 597 598 for (i = 0; i < NCONTINENTS; i++) { 599 if (cp->alternate == continent_names[i].continent) { 600 continent_names[i].continent->nitems++; 601 } 602 } 603 } 604 605 /* 606 * Now allocate memory for the country menus and initialize 607 * continent menus. We set nitems back to zero so that we can 608 * use it for counting again when we actually build the menus. 609 */ 610 memset(continents, 0, sizeof(continents)); 611 for (i = 0; i < NCONTINENTS; i++) { 612 continent_names[i].continent->menu = 613 malloc(sizeof(dialogMenuItem) * 614 continent_names[i].continent->nitems); 615 if (continent_names[i].continent->menu == NULL) 616 errx(1, "malloc for continent menu"); 617 continent_names[i].continent->nitems = 0; 618 continents[i].prompt = continent_items[i].prompt; 619 continents[i].title = continent_items[i].title; 620 continents[i].fire = continent_country_menu; 621 continents[i].data = continent_names[i].continent; 622 } 623 624 /* 625 * Now that memory is allocated, create the menu items for 626 * each continent. For multiple-zone countries, also create 627 * the country's zone submenu. 628 */ 629 for (cp = countries; cp->name; cp++) { 630 if (cp->nzones == 0) 631 continue; 632 cp->submenu = malloc(cp->nzones * sizeof(*dmi)); 633 if (cp->submenu == 0) 634 errx(1, "malloc for submenu"); 635 cp->nzones = 0; 636 TAILQ_FOREACH(zp, &cp->zones, link) { 637 cont = zp->continent; 638 dmi = &cp->submenu[cp->nzones]; 639 memset(dmi, 0, sizeof(*dmi)); 640 asprintf(&dmi->prompt, "%d", ++cp->nzones); 641 dmi->title = zp->descr; 642 dmi->fire = set_zone; 643 dmi->data = zp; 644 645 for (zp2 = TAILQ_FIRST(&cp->zones); 646 zp2->continent != cont; 647 zp2 = TAILQ_NEXT(zp2, link)) 648 ; 649 if (zp2 != zp) 650 continue; 651 652 dmi = &cont->menu[cont->nitems]; 653 memset(dmi, 0, sizeof(*dmi)); 654 asprintf(&dmi->prompt, "%d", ++cont->nitems); 655 dmi->title = cp->name; 656 dmi->fire = set_zone_menu; 657 dmi->data = cp; 658 } 659 660 if (cp->alternate != NULL) { 661 cont = cp->alternate; 662 dmi = &cont->menu[cont->nitems]; 663 memset(dmi, 0, sizeof(*dmi)); 664 asprintf(&dmi->prompt, "%d", ++cont->nitems); 665 dmi->title = cp->name; 666 dmi->fire = set_zone_menu; 667 dmi->data = cp; 668 } 669 } 670 } 671 672 static int 673 set_zone_menu(dialogMenuItem *dmi) 674 { 675 char title[64], prompt[64]; 676 struct country *cp = dmi->data; 677 int rv; 678 679 /* Short cut -- if there's only one zone, don't post a menu. */ 680 if (cp->nzones == 1) 681 return (cp->submenu[0].fire(&cp->submenu[0])); 682 683 snprintf(title, sizeof(title), "%s Time Zones", cp->name); 684 snprintf(prompt, sizeof(prompt), 685 "Select a zone which observes the same time as your locality."); 686 rv = xdialog_menu(title, prompt, cp->nzones, cp->submenu); 687 return (rv != 0 ? DITEM_RECREATE : DITEM_LEAVE_MENU); 688 } 689 690 static int 691 set_zone_utc(void) 692 { 693 if (!confirm_zone("UTC")) 694 return (DITEM_FAILURE | DITEM_RECREATE); 695 696 return (install_zoneinfo("UTC")); 697 } 698 699 static int 700 confirm_zone(const char *filename) 701 { 702 char prompt[64]; 703 time_t t = time(0); 704 struct tm *tm; 705 int rv; 706 707 setenv("TZ", filename, 1); 708 tzset(); 709 tm = localtime(&t); 710 711 snprintf(prompt, sizeof(prompt), 712 "Does the abbreviation `%s' look reasonable?", tm->tm_zone); 713 conf.title = "Confirmation"; 714 rv = (bsddialog_yesno(&conf, prompt, 5, 72) == BSDDIALOG_YES); 715 return (rv); 716 } 717 718 static int 719 set_zone(dialogMenuItem *dmi) 720 { 721 struct zone *zp = dmi->data; 722 int rv; 723 724 if (!confirm_zone(zp->filename)) 725 return (DITEM_FAILURE | DITEM_RECREATE); 726 727 rv = install_zoneinfo(zp->filename); 728 return (rv); 729 } 730 731 #endif 732 733 static void message_zoneinfo_file(const char *title, char *prompt) 734 { 735 #ifdef HAVE_BSDDIALOG 736 if (usedialog) { 737 conf.title = title; 738 bsddialog_msgbox(&conf, prompt, 8, 72); 739 } else 740 #endif 741 fprintf(stderr, "%s: %s\n", title, prompt); 742 } 743 744 static int 745 install_zoneinfo_file(const char *zoneinfo_file) 746 { 747 char buf[1024]; 748 char prompt[SILLY_BUFFER_SIZE]; 749 struct stat sb; 750 ssize_t len; 751 int fd1, fd2, copymode; 752 753 if (lstat(path_localtime, &sb) < 0) { 754 /* Nothing there yet... */ 755 copymode = 1; 756 } else if (S_ISLNK(sb.st_mode)) 757 copymode = 0; 758 else 759 copymode = 1; 760 761 #ifdef VERBOSE 762 if (copymode) 763 snprintf(prompt, sizeof(prompt), 764 "Copying %s to %s", zoneinfo_file, path_localtime); 765 else 766 snprintf(prompt, sizeof(prompt), 767 "Creating symbolic link %s to %s", 768 path_localtime, zoneinfo_file); 769 message_zoneinfo_file("Info", prompt); 770 #endif 771 772 if (reallydoit) { 773 if (copymode) { 774 fd1 = open(zoneinfo_file, O_RDONLY, 0); 775 if (fd1 < 0) { 776 snprintf(prompt, sizeof(prompt), 777 "Could not open %s: %s", zoneinfo_file, 778 strerror(errno)); 779 message_zoneinfo_file("Error", prompt); 780 return (DITEM_FAILURE | DITEM_RECREATE); 781 } 782 783 if (unlink(path_localtime) < 0 && errno != ENOENT) { 784 snprintf(prompt, sizeof(prompt), 785 "Could not delete %s: %s", 786 path_localtime, strerror(errno)); 787 message_zoneinfo_file("Error", prompt); 788 return (DITEM_FAILURE | DITEM_RECREATE); 789 } 790 791 fd2 = open(path_localtime, O_CREAT | O_EXCL | O_WRONLY, 792 S_IRUSR | S_IRGRP | S_IROTH); 793 if (fd2 < 0) { 794 snprintf(prompt, sizeof(prompt), 795 "Could not open %s: %s", 796 path_localtime, strerror(errno)); 797 message_zoneinfo_file("Error", prompt); 798 return (DITEM_FAILURE | DITEM_RECREATE); 799 } 800 801 while ((len = read(fd1, buf, sizeof(buf))) > 0) 802 if ((len = write(fd2, buf, len)) < 0) 803 break; 804 805 if (len == -1) { 806 snprintf(prompt, sizeof(prompt), 807 "Error copying %s to %s %s", zoneinfo_file, 808 path_localtime, strerror(errno)); 809 message_zoneinfo_file("Error", prompt); 810 /* Better to leave none than a corrupt one. */ 811 unlink(path_localtime); 812 return (DITEM_FAILURE | DITEM_RECREATE); 813 } 814 close(fd1); 815 close(fd2); 816 } else { 817 if (access(zoneinfo_file, R_OK) != 0) { 818 snprintf(prompt, sizeof(prompt), 819 "Cannot access %s: %s", zoneinfo_file, 820 strerror(errno)); 821 message_zoneinfo_file("Error", prompt); 822 return (DITEM_FAILURE | DITEM_RECREATE); 823 } 824 if (unlink(path_localtime) < 0 && errno != ENOENT) { 825 snprintf(prompt, sizeof(prompt), 826 "Could not delete %s: %s", 827 path_localtime, strerror(errno)); 828 message_zoneinfo_file("Error", prompt); 829 return (DITEM_FAILURE | DITEM_RECREATE); 830 } 831 if (symlink(zoneinfo_file, path_localtime) < 0) { 832 snprintf(prompt, sizeof(prompt), 833 "Cannot create symbolic link %s to %s: %s", 834 path_localtime, zoneinfo_file, 835 strerror(errno)); 836 message_zoneinfo_file("Error", prompt); 837 return (DITEM_FAILURE | DITEM_RECREATE); 838 } 839 } 840 841 #ifdef VERBOSE 842 if (copymode) 843 snprintf(prompt, sizeof(prompt), 844 "Copied timezone file from %s to %s", 845 zoneinfo_file, path_localtime); 846 else 847 snprintf(prompt, sizeof(prompt), 848 "Created symbolic link from %s to %s", 849 zoneinfo_file, path_localtime); 850 message_zoneinfo_file("Done", prompt); 851 #endif 852 } /* reallydoit */ 853 854 return (DITEM_LEAVE_MENU); 855 } 856 857 static int 858 install_zoneinfo(const char *zoneinfo) 859 { 860 int rv; 861 FILE *f; 862 char path_zoneinfo_file[MAXPATHLEN]; 863 864 if ((size_t)snprintf(path_zoneinfo_file, sizeof(path_zoneinfo_file), 865 "%s/%s", path_zoneinfo, zoneinfo) >= sizeof(path_zoneinfo_file)) 866 errx(1, "%s/%s name too long", path_zoneinfo, zoneinfo); 867 rv = install_zoneinfo_file(path_zoneinfo_file); 868 869 /* Save knowledge for later */ 870 if (reallydoit && (rv & DITEM_FAILURE) == 0) { 871 if ((f = fopen(path_db, "w")) != NULL) { 872 fprintf(f, "%s\n", zoneinfo); 873 fclose(f); 874 } 875 } 876 877 return (rv); 878 } 879 880 static void 881 usage(void) 882 { 883 884 fprintf(stderr, "usage: tzsetup [-nrs] [-C chroot_directory]" 885 " [zoneinfo_file | zoneinfo_name]\n"); 886 exit(1); 887 } 888 889 int 890 main(int argc, char **argv) 891 { 892 #ifdef HAVE_BSDDIALOG 893 char prompt[128]; 894 int fd; 895 #endif 896 int c, rv, skiputc; 897 char vm_guest[16] = ""; 898 size_t len = sizeof(vm_guest); 899 char *dztpath; 900 901 dztpath = NULL; 902 skiputc = 0; 903 904 #ifdef HAVE_BSDDIALOG 905 setlocale(LC_ALL, ""); 906 #endif 907 908 /* Default skiputc to 1 for VM guests */ 909 if (sysctlbyname("kern.vm_guest", vm_guest, &len, NULL, 0) == 0 && 910 strcmp(vm_guest, "none") != 0) 911 skiputc = 1; 912 913 while ((c = getopt(argc, argv, "C:d:nrs")) != -1) { 914 switch (c) { 915 case 'C': 916 chrootenv = optarg; 917 break; 918 case 'd': 919 dztpath = optarg; 920 break; 921 case 'n': 922 reallydoit = 0; 923 break; 924 case 'r': 925 reinstall = 1; 926 #ifdef HAVE_BSDDIALOG 927 usedialog = 0; 928 #endif 929 break; 930 case 's': 931 skiputc = 1; 932 break; 933 default: 934 usage(); 935 } 936 } 937 938 if (argc - optind > 1) 939 usage(); 940 941 if (chrootenv == NULL) { 942 if (dztpath == NULL) 943 strcpy(path_zonetab, _PATH_ZONETAB); 944 else 945 strlcpy(path_zonetab, dztpath, sizeof(path_zonetab)); 946 strcpy(path_iso3166, _PATH_ISO3166); 947 strcpy(path_zoneinfo, _PATH_ZONEINFO); 948 strcpy(path_localtime, _PATH_LOCALTIME); 949 strcpy(path_db, _PATH_DB); 950 strcpy(path_wall_cmos_clock, _PATH_WALL_CMOS_CLOCK); 951 } else { 952 sprintf(path_zonetab, "%s/%s", chrootenv, _PATH_ZONETAB); 953 sprintf(path_iso3166, "%s/%s", chrootenv, _PATH_ISO3166); 954 sprintf(path_zoneinfo, "%s/%s", chrootenv, _PATH_ZONEINFO); 955 sprintf(path_localtime, "%s/%s", chrootenv, _PATH_LOCALTIME); 956 sprintf(path_db, "%s/%s", chrootenv, _PATH_DB); 957 sprintf(path_wall_cmos_clock, "%s/%s", chrootenv, 958 _PATH_WALL_CMOS_CLOCK); 959 } 960 961 /* Override the user-supplied umask. */ 962 (void)umask(S_IWGRP | S_IWOTH); 963 964 if (reinstall == 1) { 965 FILE *f; 966 char zoneinfo[MAXPATHLEN]; 967 968 if ((f = fopen(path_db, "r")) != NULL) { 969 if (fgets(zoneinfo, sizeof(zoneinfo), f) != NULL) { 970 zoneinfo[sizeof(zoneinfo) - 1] = 0; 971 if (strlen(zoneinfo) > 0) { 972 zoneinfo[strlen(zoneinfo) - 1] = 0; 973 rv = install_zoneinfo(zoneinfo); 974 exit(rv & ~DITEM_LEAVE_MENU); 975 } 976 errx(1, "Error reading %s.\n", path_db); 977 } 978 fclose(f); 979 errx(1, 980 "Unable to determine earlier installed zoneinfo " 981 "name. Check %s", path_db); 982 } 983 errx(1, "Cannot open %s for reading. Does it exist?", path_db); 984 } 985 986 /* 987 * If the arguments on the command-line do not specify a file, 988 * then interpret it as a zoneinfo name 989 */ 990 if (optind == argc - 1) { 991 struct stat sb; 992 993 if (stat(argv[optind], &sb) != 0) { 994 #ifdef HAVE_BSDDIALOG 995 usedialog = 0; 996 #endif 997 rv = install_zoneinfo(argv[optind]); 998 exit(rv & ~DITEM_LEAVE_MENU); 999 } 1000 /* FALLTHROUGH */ 1001 } 1002 #ifdef HAVE_BSDDIALOG 1003 1004 read_iso3166_table(); 1005 read_zones(); 1006 sort_countries(); 1007 if (dztpath != NULL) { 1008 dump_zonetab(); 1009 return (0); 1010 } 1011 make_menus(); 1012 1013 bsddialog_initconf(&conf); 1014 conf.clear = true; 1015 conf.auto_minwidth = 24; 1016 conf.key.enable_esc = true; 1017 1018 if (bsddialog_init() == BSDDIALOG_ERROR) 1019 errx(1, "Error bsddialog: %s\n", bsddialog_geterror()); 1020 1021 if (skiputc == 0) { 1022 snprintf(prompt, sizeof(prompt), 1023 "Is this machine's CMOS clock set to UTC? " 1024 "If it is set to local time,\n" 1025 "or you don't know, please choose NO here!"); 1026 1027 conf.title = "Select local or UTC (Greenwich Mean Time) clock"; 1028 if (bsddialog_yesno(&conf, prompt, 7, 73) == BSDDIALOG_YES) { 1029 if (reallydoit) 1030 unlink(path_wall_cmos_clock); 1031 } else { 1032 if (reallydoit) { 1033 fd = open(path_wall_cmos_clock, 1034 O_WRONLY | O_CREAT | O_TRUNC, 1035 S_IRUSR | S_IRGRP | S_IROTH); 1036 if (fd < 0) { 1037 bsddialog_end(); 1038 err(1, "create %s", 1039 path_wall_cmos_clock); 1040 } 1041 close(fd); 1042 } 1043 } 1044 } 1045 if (optind == argc - 1) { 1046 snprintf(prompt, sizeof(prompt), 1047 "\nUse the default `%s' zone?", argv[optind]); 1048 conf.title = "Default timezone provided"; 1049 if (bsddialog_yesno(&conf, prompt, 7, 72) == BSDDIALOG_YES) { 1050 rv = install_zoneinfo_file(argv[optind]); 1051 bsddialog_end(); 1052 exit(rv & ~DITEM_LEAVE_MENU); 1053 } 1054 } 1055 xdialog_menu("Time Zone Selector", "Select a region", NCONTINENTS, 1056 continents); 1057 1058 bsddialog_end(); 1059 #else 1060 usage(); 1061 #endif 1062 return (0); 1063 } 1064