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 { "UTC", &utc }, 175 { "Africa", &africa }, 176 { "America", &america }, 177 { "Antarctica", &antarctica }, 178 { "Arctic", &arctic }, 179 { "Asia", &asia }, 180 { "Atlantic", &atlantic }, 181 { "Australia", &australia }, 182 { "Europe", &europe }, 183 { "Indian", &indian }, 184 { "Pacific", &pacific }, 185 }; 186 187 static struct continent_items { 188 char prompt[3]; 189 char title[30]; 190 } continent_items[] = { 191 { "0", "UTC" }, 192 { "1", "Africa" }, 193 { "2", "America -- North and South" }, 194 { "3", "Antarctica" }, 195 { "4", "Arctic Ocean" }, 196 { "5", "Asia" }, 197 { "6", "Atlantic Ocean" }, 198 { "7", "Australia" }, 199 { "8", "Europe" }, 200 { "9", "Indian Ocean" }, 201 { "10", "Pacific Ocean" }, 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) == 4 || (x) == 6 || (x) == 9 || (x) == 10) 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 prompt[SILLY_BUFFER_SIZE]; 748 749 #ifdef VERBOSE 750 snprintf(prompt, sizeof(prompt), "Creating symbolic link %s to %s", 751 path_localtime, zoneinfo_file); 752 message_zoneinfo_file("Info", prompt); 753 #endif 754 755 if (reallydoit) { 756 if (access(zoneinfo_file, R_OK) != 0) { 757 snprintf(prompt, sizeof(prompt), 758 "Cannot access %s: %s", zoneinfo_file, 759 strerror(errno)); 760 message_zoneinfo_file("Error", prompt); 761 return (DITEM_FAILURE | DITEM_RECREATE); 762 } 763 if (unlink(path_localtime) < 0 && errno != ENOENT) { 764 snprintf(prompt, sizeof(prompt), 765 "Could not delete %s: %s", 766 path_localtime, strerror(errno)); 767 message_zoneinfo_file("Error", prompt); 768 return (DITEM_FAILURE | DITEM_RECREATE); 769 } 770 if (symlink(zoneinfo_file, path_localtime) < 0) { 771 snprintf(prompt, sizeof(prompt), 772 "Cannot create symbolic link %s to %s: %s", 773 path_localtime, zoneinfo_file, 774 strerror(errno)); 775 message_zoneinfo_file("Error", prompt); 776 return (DITEM_FAILURE | DITEM_RECREATE); 777 } 778 779 #ifdef VERBOSE 780 snprintf(prompt, sizeof(prompt), 781 "Created symbolic link from %s to %s", zoneinfo_file, 782 path_localtime); 783 message_zoneinfo_file("Done", prompt); 784 #endif 785 } /* reallydoit */ 786 787 return (DITEM_LEAVE_MENU); 788 } 789 790 static int 791 install_zoneinfo(const char *zoneinfo) 792 { 793 int rv; 794 FILE *f; 795 char path_zoneinfo_file[MAXPATHLEN]; 796 797 if ((size_t)snprintf(path_zoneinfo_file, sizeof(path_zoneinfo_file), 798 "%s/%s", path_zoneinfo, zoneinfo) >= sizeof(path_zoneinfo_file)) 799 errx(1, "%s/%s name too long", path_zoneinfo, zoneinfo); 800 rv = install_zoneinfo_file(path_zoneinfo_file); 801 802 /* Save knowledge for later */ 803 if (reallydoit && (rv & DITEM_FAILURE) == 0) { 804 if ((f = fopen(path_db, "w")) != NULL) { 805 fprintf(f, "%s\n", zoneinfo); 806 fclose(f); 807 } 808 } 809 810 return (rv); 811 } 812 813 static void 814 usage(void) 815 { 816 817 fprintf(stderr, "usage: tzsetup [-nrs] [-C chroot_directory]" 818 " [zoneinfo_file | zoneinfo_name]\n"); 819 exit(1); 820 } 821 822 int 823 main(int argc, char **argv) 824 { 825 #ifdef HAVE_BSDDIALOG 826 char prompt[128]; 827 int fd; 828 #endif 829 int c, rv, skiputc; 830 char vm_guest[16] = ""; 831 size_t len = sizeof(vm_guest); 832 char *dztpath; 833 834 dztpath = NULL; 835 skiputc = 0; 836 837 #ifdef HAVE_BSDDIALOG 838 setlocale(LC_ALL, ""); 839 #endif 840 841 /* Default skiputc to 1 for VM guests */ 842 if (sysctlbyname("kern.vm_guest", vm_guest, &len, NULL, 0) == 0 && 843 strcmp(vm_guest, "none") != 0) 844 skiputc = 1; 845 846 while ((c = getopt(argc, argv, "C:d:nrs")) != -1) { 847 switch (c) { 848 case 'C': 849 chrootenv = optarg; 850 break; 851 case 'd': 852 dztpath = optarg; 853 break; 854 case 'n': 855 reallydoit = 0; 856 break; 857 case 'r': 858 reinstall = 1; 859 #ifdef HAVE_BSDDIALOG 860 usedialog = 0; 861 #endif 862 break; 863 case 's': 864 skiputc = 1; 865 break; 866 default: 867 usage(); 868 } 869 } 870 871 if (argc - optind > 1) 872 usage(); 873 874 if (chrootenv == NULL) { 875 if (dztpath == NULL) 876 strcpy(path_zonetab, _PATH_ZONETAB); 877 else 878 strlcpy(path_zonetab, dztpath, sizeof(path_zonetab)); 879 strcpy(path_iso3166, _PATH_ISO3166); 880 strcpy(path_zoneinfo, _PATH_ZONEINFO); 881 strcpy(path_localtime, _PATH_LOCALTIME); 882 strcpy(path_db, _PATH_DB); 883 strcpy(path_wall_cmos_clock, _PATH_WALL_CMOS_CLOCK); 884 } else { 885 sprintf(path_zonetab, "%s/%s", chrootenv, _PATH_ZONETAB); 886 sprintf(path_iso3166, "%s/%s", chrootenv, _PATH_ISO3166); 887 sprintf(path_zoneinfo, "%s/%s", chrootenv, _PATH_ZONEINFO); 888 sprintf(path_localtime, "%s/%s", chrootenv, _PATH_LOCALTIME); 889 sprintf(path_db, "%s/%s", chrootenv, _PATH_DB); 890 sprintf(path_wall_cmos_clock, "%s/%s", chrootenv, 891 _PATH_WALL_CMOS_CLOCK); 892 } 893 894 /* Override the user-supplied umask. */ 895 (void)umask(S_IWGRP | S_IWOTH); 896 897 if (reinstall == 1) { 898 FILE *f; 899 char zoneinfo[MAXPATHLEN]; 900 901 if ((f = fopen(path_db, "r")) != NULL) { 902 if (fgets(zoneinfo, sizeof(zoneinfo), f) != NULL) { 903 zoneinfo[sizeof(zoneinfo) - 1] = 0; 904 if (strlen(zoneinfo) > 0) { 905 zoneinfo[strlen(zoneinfo) - 1] = 0; 906 rv = install_zoneinfo(zoneinfo); 907 exit(rv & ~DITEM_LEAVE_MENU); 908 } 909 errx(1, "Error reading %s.\n", path_db); 910 } 911 fclose(f); 912 errx(1, 913 "Unable to determine earlier installed zoneinfo " 914 "name. Check %s", path_db); 915 } 916 errx(1, "Cannot open %s for reading. Does it exist?", path_db); 917 } 918 919 /* 920 * If the arguments on the command-line do not specify a file, 921 * then interpret it as a zoneinfo name 922 */ 923 if (optind == argc - 1) { 924 struct stat sb; 925 926 if (stat(argv[optind], &sb) != 0) { 927 #ifdef HAVE_BSDDIALOG 928 usedialog = 0; 929 #endif 930 rv = install_zoneinfo(argv[optind]); 931 exit(rv & ~DITEM_LEAVE_MENU); 932 } 933 /* FALLTHROUGH */ 934 } 935 #ifdef HAVE_BSDDIALOG 936 937 read_iso3166_table(); 938 read_zones(); 939 sort_countries(); 940 if (dztpath != NULL) { 941 dump_zonetab(); 942 return (0); 943 } 944 make_menus(); 945 946 bsddialog_initconf(&conf); 947 conf.clear = true; 948 conf.auto_minwidth = 24; 949 conf.key.enable_esc = true; 950 951 if (bsddialog_init() == BSDDIALOG_ERROR) 952 errx(1, "Error bsddialog: %s\n", bsddialog_geterror()); 953 954 if (skiputc == 0) { 955 snprintf(prompt, sizeof(prompt), 956 "Is this machine's CMOS clock set to UTC? " 957 "If it is set to local time,\n" 958 "or you don't know, please choose NO here!"); 959 960 conf.title = "Select local or UTC (Greenwich Mean Time) clock"; 961 if (bsddialog_yesno(&conf, prompt, 7, 73) == BSDDIALOG_YES) { 962 if (reallydoit) 963 unlink(path_wall_cmos_clock); 964 } else { 965 if (reallydoit) { 966 fd = open(path_wall_cmos_clock, 967 O_WRONLY | O_CREAT | O_TRUNC, 968 S_IRUSR | S_IRGRP | S_IROTH); 969 if (fd < 0) { 970 bsddialog_end(); 971 err(1, "create %s", 972 path_wall_cmos_clock); 973 } 974 close(fd); 975 } 976 } 977 } 978 if (optind == argc - 1) { 979 snprintf(prompt, sizeof(prompt), 980 "\nUse the default `%s' zone?", argv[optind]); 981 conf.title = "Default timezone provided"; 982 if (bsddialog_yesno(&conf, prompt, 7, 72) == BSDDIALOG_YES) { 983 rv = install_zoneinfo_file(argv[optind]); 984 bsddialog_end(); 985 exit(rv & ~DITEM_LEAVE_MENU); 986 } 987 } 988 xdialog_menu("Time Zone Selector", "Select a region", NCONTINENTS, 989 continents); 990 991 bsddialog_end(); 992 #else 993 usage(); 994 #endif 995 return (0); 996 } 997