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