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