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