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