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