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