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 #include <locale.h> 55 #endif 56 57 #define _PATH_ZONETAB "/usr/share/zoneinfo/zone1970.tab" 58 #define _PATH_ISO3166 "/usr/share/misc/iso3166" 59 #define _PATH_ZONEINFO "/usr/share/zoneinfo" 60 #define _PATH_LOCALTIME "/etc/localtime" 61 #define _PATH_DB "/var/db/zoneinfo" 62 #define _PATH_WALL_CMOS_CLOCK "/etc/wall_cmos_clock" 63 64 #ifdef PATH_MAX 65 #define SILLY_BUFFER_SIZE 2*PATH_MAX 66 #else 67 #warning "Somebody needs to fix this to dynamically size this buffer." 68 #define SILLY_BUFFER_SIZE 2048 69 #endif 70 71 /* special return codes for `fire' actions */ 72 #define DITEM_FAILURE 1 73 74 /* flags - returned in upper 16 bits of return status */ 75 #define DITEM_LEAVE_MENU (1 << 16) 76 #define DITEM_RECREATE (1 << 18) 77 78 static char path_zonetab[MAXPATHLEN], path_iso3166[MAXPATHLEN], 79 path_zoneinfo[MAXPATHLEN], path_localtime[MAXPATHLEN], 80 path_db[MAXPATHLEN], path_wall_cmos_clock[MAXPATHLEN]; 81 82 static int reallydoit = 1; 83 static int reinstall = 0; 84 static char *chrootenv = NULL; 85 86 static void usage(void); 87 static int install_zoneinfo(const char *zoneinfo); 88 static int install_zoneinfo_file(const char *zoneinfo_file); 89 90 #ifdef HAVE_BSDDIALOG 91 static struct bsddialog_conf conf; 92 93 /* for use in describing more exotic behaviors */ 94 typedef struct dialogMenuItem { 95 char *prompt; 96 char *title; 97 int (*fire)(struct dialogMenuItem *self); 98 void *data; 99 } dialogMenuItem; 100 101 static int 102 xdialog_menu(char *title, char *cprompt, int height, int width, 103 int menu_height, int item_no, dialogMenuItem *ditems) 104 { 105 int i, result, choice = 0; 106 struct bsddialog_menuitem *listitems; 107 108 /* initialize list items */ 109 listitems = calloc(item_no + 1, sizeof(struct bsddialog_menuitem)); 110 if (listitems == NULL) 111 errx(1, "Failed to allocate memory in xdialog_menu"); 112 for (i = 0; i < item_no; i++) { 113 listitems[i].prefix = ""; 114 listitems[i].depth = 0; 115 listitems[i].bottomdesc = ""; 116 listitems[i].on = false; 117 listitems[i].name = ditems[i].prompt; 118 listitems[i].desc = ditems[i].title; 119 } 120 121 if (height < 0) 122 height = BSDDIALOG_AUTOSIZE; 123 124 if (width < 0) { 125 width = BSDDIALOG_AUTOSIZE; 126 conf.auto_minwidth = 24; 127 } 128 129 again: 130 conf.menu.default_item = listitems[choice].name; 131 conf.title = title; 132 result = bsddialog_menu(&conf, cprompt, height, width, 133 menu_height, item_no, listitems, NULL); 134 for (i = 0; i < item_no; i++) 135 if (listitems[i].on) 136 choice = i; 137 switch (result) { 138 case BSDDIALOG_ESC: 139 result = -1; 140 break; 141 case BSDDIALOG_OK: 142 if (ditems[choice].fire != NULL) { 143 int status; 144 145 status = ditems[choice].fire(ditems + choice); 146 if (status & DITEM_RECREATE) { 147 goto again; 148 } 149 } 150 result = 0; 151 break; 152 case BSDDIALOG_CANCEL: 153 default: 154 result = 1; 155 break; 156 } 157 158 free(listitems); 159 return (result); 160 } 161 162 static int usedialog = 1; 163 164 static int confirm_zone(const char *filename); 165 static int continent_country_menu(dialogMenuItem *); 166 static int set_zone_multi(dialogMenuItem *); 167 static int set_zone_whole_country(dialogMenuItem *); 168 static int set_zone_menu(dialogMenuItem *); 169 static int set_zone_utc(void); 170 171 struct continent { 172 dialogMenuItem *menu; 173 int nitems; 174 }; 175 176 static struct continent africa, america, antarctica, asia, atlantic; 177 static struct continent australia, europe, indian, pacific, utc; 178 179 static struct continent_names { 180 const char *name; 181 struct continent *continent; 182 } continent_names[] = { 183 { "Africa", &africa }, 184 { "America", &america }, 185 { "Antarctica", &antarctica }, 186 { "Asia", &asia }, 187 { "Atlantic", &atlantic }, 188 { "Australia", &australia }, 189 { "Europe", &europe }, 190 { "Indian", &indian }, 191 { "Pacific", &pacific }, 192 { "UTC", &utc } 193 }; 194 195 static struct continent_items { 196 char prompt[2]; 197 char title[30]; 198 } continent_items[] = { 199 { "1", "Africa" }, 200 { "2", "America -- North and South" }, 201 { "3", "Antarctica" }, 202 { "4", "Asia" }, 203 { "5", "Atlantic Ocean" }, 204 { "6", "Australia" }, 205 { "7", "Europe" }, 206 { "8", "Indian Ocean" }, 207 { "9", "Pacific Ocean" }, 208 { "0", "UTC" } 209 }; 210 211 #define NCONTINENTS \ 212 (int)((sizeof(continent_items)) / (sizeof(continent_items[0]))) 213 static dialogMenuItem continents[NCONTINENTS]; 214 215 #define OCEANP(x) ((x) == 4 || (x) == 7 || (x) == 8) 216 217 static int 218 continent_country_menu(dialogMenuItem *continent) 219 { 220 char title[64], prompt[64]; 221 struct continent *contp = continent->data; 222 int isocean = OCEANP(continent - continents); 223 int menulen; 224 int rv; 225 226 if (strcmp(continent->title, "UTC") == 0) 227 return (set_zone_utc()); 228 229 /* Short cut -- if there's only one country, don't post a menu. */ 230 if (contp->nitems == 1) 231 return (contp->menu[0].fire(&contp->menu[0])); 232 233 /* It's amazing how much good grammar really matters... */ 234 if (!isocean) { 235 snprintf(title, sizeof(title), "Countries in %s", 236 continent->title); 237 snprintf(prompt, sizeof(prompt), "Select a country or region"); 238 } else { 239 snprintf(title, sizeof(title), "Islands and groups in the %s", 240 continent->title); 241 snprintf(prompt, sizeof(prompt), "Select an island or group"); 242 } 243 244 menulen = contp->nitems < 16 ? contp->nitems : 16; 245 rv = xdialog_menu(title, prompt, -1, -1, menulen, contp->nitems, 246 contp->menu); 247 if (rv == 0) 248 return (DITEM_LEAVE_MENU); 249 return (DITEM_RECREATE); 250 } 251 252 static struct continent * 253 find_continent(const char *name) 254 { 255 int i; 256 257 for (i = 0; i < NCONTINENTS; i++) 258 if (strcmp(name, continent_names[i].name) == 0) 259 return (continent_names[i].continent); 260 return (0); 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 make_menus(void) 488 { 489 struct country *cp; 490 struct zone *zp, *zp2; 491 struct continent *cont; 492 dialogMenuItem *dmi; 493 int i; 494 495 /* 496 * First, count up all the countries in each continent/ocean. 497 * Be careful to count those countries which have multiple zones 498 * only once for each. NB: some countries are in multiple 499 * continents/oceans. 500 */ 501 for (cp = countries; cp->name; cp++) { 502 if (cp->nzones == 0) 503 continue; 504 if (cp->nzones < 0) { 505 cp->continent->nitems++; 506 } else { 507 TAILQ_FOREACH(zp, &cp->zones, link) { 508 cont = zp->continent; 509 for (zp2 = TAILQ_FIRST(&cp->zones); 510 zp2->continent != cont; 511 zp2 = TAILQ_NEXT(zp2, link)) 512 ; 513 if (zp2 == zp) 514 zp->continent->nitems++; 515 } 516 } 517 } 518 519 /* 520 * Now allocate memory for the country menus and initialize 521 * continent menus. We set nitems back to zero so that we can 522 * use it for counting again when we actually build the menus. 523 */ 524 memset(continents, 0, sizeof(continents)); 525 for (i = 0; i < NCONTINENTS; i++) { 526 continent_names[i].continent->menu = 527 malloc(sizeof(dialogMenuItem) * 528 continent_names[i].continent->nitems); 529 if (continent_names[i].continent->menu == NULL) 530 errx(1, "malloc for continent menu"); 531 continent_names[i].continent->nitems = 0; 532 continents[i].prompt = continent_items[i].prompt; 533 continents[i].title = continent_items[i].title; 534 continents[i].fire = continent_country_menu; 535 continents[i].data = continent_names[i].continent; 536 } 537 538 /* 539 * Now that memory is allocated, create the menu items for 540 * each continent. For multiple-zone countries, also create 541 * the country's zone submenu. 542 */ 543 for (cp = countries; cp->name; cp++) { 544 if (cp->nzones == 0) 545 continue; 546 if (cp->nzones < 0) { 547 dmi = &cp->continent->menu[cp->continent->nitems]; 548 memset(dmi, 0, sizeof(*dmi)); 549 asprintf(&dmi->prompt, "%d", ++cp->continent->nitems); 550 dmi->title = cp->name; 551 dmi->fire = set_zone_whole_country; 552 dmi->data = cp; 553 } else { 554 cp->submenu = malloc(cp->nzones * sizeof(*dmi)); 555 if (cp->submenu == 0) 556 errx(1, "malloc for submenu"); 557 cp->nzones = 0; 558 TAILQ_FOREACH(zp, &cp->zones, link) { 559 cont = zp->continent; 560 dmi = &cp->submenu[cp->nzones]; 561 memset(dmi, 0, sizeof(*dmi)); 562 asprintf(&dmi->prompt, "%d", ++cp->nzones); 563 dmi->title = zp->descr; 564 dmi->fire = set_zone_multi; 565 dmi->data = zp; 566 567 for (zp2 = TAILQ_FIRST(&cp->zones); 568 zp2->continent != cont; 569 zp2 = TAILQ_NEXT(zp2, link)) 570 ; 571 if (zp2 != zp) 572 continue; 573 574 dmi = &cont->menu[cont->nitems]; 575 memset(dmi, 0, sizeof(*dmi)); 576 asprintf(&dmi->prompt, "%d", ++cont->nitems); 577 dmi->title = cp->name; 578 dmi->fire = set_zone_menu; 579 dmi->data = cp; 580 } 581 } 582 } 583 } 584 585 static int 586 set_zone_menu(dialogMenuItem *dmi) 587 { 588 char title[64], prompt[64]; 589 struct country *cp = dmi->data; 590 int menulen; 591 int rv; 592 593 snprintf(title, sizeof(title), "%s Time Zones", cp->name); 594 snprintf(prompt, sizeof(prompt), 595 "Select a zone which observes the same time as your locality."); 596 menulen = cp->nzones < 16 ? cp->nzones : 16; 597 rv = xdialog_menu(title, prompt, -1, -1, menulen, cp->nzones, 598 cp->submenu); 599 if (rv != 0) 600 return (DITEM_RECREATE); 601 return (DITEM_LEAVE_MENU); 602 } 603 604 static int 605 set_zone_utc(void) 606 { 607 if (!confirm_zone("UTC")) 608 return (DITEM_FAILURE | DITEM_RECREATE); 609 610 return (install_zoneinfo("UTC")); 611 } 612 613 static int 614 confirm_zone(const char *filename) 615 { 616 char prompt[64]; 617 time_t t = time(0); 618 struct tm *tm; 619 int rv; 620 621 setenv("TZ", filename, 1); 622 tzset(); 623 tm = localtime(&t); 624 625 snprintf(prompt, sizeof(prompt), 626 "Does the abbreviation `%s' look reasonable?", tm->tm_zone); 627 conf.title = "Confirmation"; 628 rv = !bsddialog_yesno(&conf, prompt, 5, 72); 629 return (rv); 630 } 631 632 static int 633 set_zone_multi(dialogMenuItem *dmi) 634 { 635 struct zone *zp = dmi->data; 636 int rv; 637 638 if (!confirm_zone(zp->filename)) 639 return (DITEM_FAILURE | DITEM_RECREATE); 640 641 rv = install_zoneinfo(zp->filename); 642 return (rv); 643 } 644 645 static int 646 set_zone_whole_country(dialogMenuItem *dmi) 647 { 648 struct country *cp = dmi->data; 649 int rv; 650 651 if (!confirm_zone(cp->filename)) 652 return (DITEM_FAILURE | DITEM_RECREATE); 653 654 rv = install_zoneinfo(cp->filename); 655 return (rv); 656 } 657 658 #endif 659 660 static int 661 install_zoneinfo_file(const char *zoneinfo_file) 662 { 663 char buf[1024]; 664 char prompt[SILLY_BUFFER_SIZE]; 665 struct stat sb; 666 ssize_t len; 667 int fd1, fd2, copymode; 668 669 if (lstat(path_localtime, &sb) < 0) { 670 /* Nothing there yet... */ 671 copymode = 1; 672 } else if (S_ISLNK(sb.st_mode)) 673 copymode = 0; 674 else 675 copymode = 1; 676 677 #ifdef VERBOSE 678 if (copymode) 679 snprintf(prompt, sizeof(prompt), 680 "Copying %s to %s", zoneinfo_file, path_localtime); 681 else 682 snprintf(prompt, sizeof(prompt), 683 "Creating symbolic link %s to %s", 684 path_localtime, zoneinfo_file); 685 #ifdef HAVE_BSDDIALOG 686 if (usedialog) 687 conf.title = "Info"; 688 bsddialog_msgbox(&conf, prompt, 8, 72); 689 else 690 #endif 691 fprintf(stderr, "%s\n", prompt); 692 #endif 693 694 if (reallydoit) { 695 if (copymode) { 696 fd1 = open(zoneinfo_file, O_RDONLY, 0); 697 if (fd1 < 0) { 698 snprintf(prompt, sizeof(prompt), 699 "Could not open %s: %s", zoneinfo_file, 700 strerror(errno)); 701 #ifdef HAVE_BSDDIALOG 702 if (usedialog) { 703 conf.title = "Error"; 704 bsddialog_msgbox(&conf, prompt, 8, 72); 705 } else 706 #endif 707 fprintf(stderr, "%s\n", prompt); 708 return (DITEM_FAILURE | DITEM_RECREATE); 709 } 710 711 if (unlink(path_localtime) < 0 && errno != ENOENT) { 712 snprintf(prompt, sizeof(prompt), 713 "Could not delete %s: %s", 714 path_localtime, strerror(errno)); 715 #ifdef HAVE_BSDDIALOG 716 if (usedialog) { 717 conf.title = "error"; 718 bsddialog_msgbox(&conf, prompt, 8, 72); 719 } else 720 #endif 721 fprintf(stderr, "%s\n", prompt); 722 return (DITEM_FAILURE | DITEM_RECREATE); 723 } 724 725 fd2 = open(path_localtime, O_CREAT | O_EXCL | O_WRONLY, 726 S_IRUSR | S_IRGRP | S_IROTH); 727 if (fd2 < 0) { 728 snprintf(prompt, sizeof(prompt), 729 "Could not open %s: %s", 730 path_localtime, strerror(errno)); 731 #ifdef HAVE_BSDDIALOG 732 if (usedialog) { 733 conf.title = "Error"; 734 bsddialog_msgbox(&conf, prompt, 8, 72); 735 } else 736 #endif 737 fprintf(stderr, "%s\n", prompt); 738 return (DITEM_FAILURE | DITEM_RECREATE); 739 } 740 741 while ((len = read(fd1, buf, sizeof(buf))) > 0) 742 if ((len = write(fd2, buf, len)) < 0) 743 break; 744 745 if (len == -1) { 746 snprintf(prompt, sizeof(prompt), 747 "Error copying %s to %s %s", zoneinfo_file, 748 path_localtime, strerror(errno)); 749 #ifdef HAVE_BSDDIALOG 750 if (usedialog) { 751 conf.title = "Error"; 752 bsddialog_msgbox(&conf, prompt, 8, 72); 753 } else 754 #endif 755 fprintf(stderr, "%s\n", prompt); 756 /* Better to leave none than a corrupt one. */ 757 unlink(path_localtime); 758 return (DITEM_FAILURE | DITEM_RECREATE); 759 } 760 close(fd1); 761 close(fd2); 762 } else { 763 if (access(zoneinfo_file, R_OK) != 0) { 764 snprintf(prompt, sizeof(prompt), 765 "Cannot access %s: %s", zoneinfo_file, 766 strerror(errno)); 767 #ifdef HAVE_BSDDIALOG 768 if (usedialog) { 769 conf.title = "Error"; 770 bsddialog_msgbox(&conf, prompt, 8, 72); 771 } else 772 #endif 773 fprintf(stderr, "%s\n", prompt); 774 return (DITEM_FAILURE | DITEM_RECREATE); 775 } 776 if (unlink(path_localtime) < 0 && errno != ENOENT) { 777 snprintf(prompt, sizeof(prompt), 778 "Could not delete %s: %s", 779 path_localtime, strerror(errno)); 780 #ifdef HAVE_BSDDIALOG 781 if (usedialog) { 782 conf.title = "Error"; 783 bsddialog_msgbox(&conf, prompt, 8, 72); 784 } else 785 #endif 786 fprintf(stderr, "%s\n", prompt); 787 return (DITEM_FAILURE | DITEM_RECREATE); 788 } 789 if (symlink(zoneinfo_file, path_localtime) < 0) { 790 snprintf(prompt, sizeof(prompt), 791 "Cannot create symbolic link %s to %s: %s", 792 path_localtime, zoneinfo_file, 793 strerror(errno)); 794 #ifdef HAVE_BSDDIALOG 795 if (usedialog) { 796 conf.title = "Error"; 797 bsddialog_msgbox(&conf, prompt, 8, 72); 798 } else 799 #endif 800 fprintf(stderr, "%s\n", prompt); 801 return (DITEM_FAILURE | DITEM_RECREATE); 802 } 803 } 804 805 #ifdef VERBOSE 806 if (copymode) 807 snprintf(prompt, sizeof(prompt), 808 "Copied timezone file from %s to %s", 809 zoneinfo_file, path_localtime); 810 else 811 snprintf(prompt, sizeof(prompt), 812 "Created symbolic link from %s to %s", 813 zoneinfo_file, path_localtime); 814 #ifdef HAVE_BSDDIALOG 815 if (usedialog) { 816 conf.title = "Done"; 817 bsddialog_msgbox(&conf, prompt, 8, 72); 818 } else 819 #endif 820 fprintf(stderr, "%s\n", prompt); 821 #endif 822 } /* reallydoit */ 823 824 return (DITEM_LEAVE_MENU); 825 } 826 827 static int 828 install_zoneinfo(const char *zoneinfo) 829 { 830 int rv; 831 FILE *f; 832 char path_zoneinfo_file[MAXPATHLEN]; 833 834 if ((size_t)snprintf(path_zoneinfo_file, sizeof(path_zoneinfo_file), 835 "%s/%s", path_zoneinfo, zoneinfo) >= sizeof(path_zoneinfo_file)) 836 errx(1, "%s/%s name too long", path_zoneinfo, zoneinfo); 837 rv = install_zoneinfo_file(path_zoneinfo_file); 838 839 /* Save knowledge for later */ 840 if (reallydoit && (rv & DITEM_FAILURE) == 0) { 841 if ((f = fopen(path_db, "w")) != NULL) { 842 fprintf(f, "%s\n", zoneinfo); 843 fclose(f); 844 } 845 } 846 847 return (rv); 848 } 849 850 static void 851 usage(void) 852 { 853 854 fprintf(stderr, "usage: tzsetup [-nrs] [-C chroot_directory]" 855 " [zoneinfo_file | zoneinfo_name]\n"); 856 exit(1); 857 } 858 859 int 860 main(int argc, char **argv) 861 { 862 #ifdef HAVE_BSDDIALOG 863 char prompt[128]; 864 int fd; 865 #endif 866 int c, rv, skiputc; 867 char vm_guest[16] = ""; 868 size_t len = sizeof(vm_guest); 869 870 skiputc = 0; 871 872 #ifdef HAVE_BSDDIALOG 873 setlocale(LC_ALL, ""); 874 #endif 875 876 /* Default skiputc to 1 for VM guests */ 877 if (sysctlbyname("kern.vm_guest", vm_guest, &len, NULL, 0) == 0 && 878 strcmp(vm_guest, "none") != 0) 879 skiputc = 1; 880 881 while ((c = getopt(argc, argv, "C:nrs")) != -1) { 882 switch(c) { 883 case 'C': 884 chrootenv = optarg; 885 break; 886 case 'n': 887 reallydoit = 0; 888 break; 889 case 'r': 890 reinstall = 1; 891 #ifdef HAVE_BSDDIALOG 892 usedialog = 0; 893 #endif 894 break; 895 case 's': 896 skiputc = 1; 897 break; 898 default: 899 usage(); 900 } 901 } 902 903 if (argc - optind > 1) 904 usage(); 905 906 if (chrootenv == NULL) { 907 strcpy(path_zonetab, _PATH_ZONETAB); 908 strcpy(path_iso3166, _PATH_ISO3166); 909 strcpy(path_zoneinfo, _PATH_ZONEINFO); 910 strcpy(path_localtime, _PATH_LOCALTIME); 911 strcpy(path_db, _PATH_DB); 912 strcpy(path_wall_cmos_clock, _PATH_WALL_CMOS_CLOCK); 913 } else { 914 sprintf(path_zonetab, "%s/%s", chrootenv, _PATH_ZONETAB); 915 sprintf(path_iso3166, "%s/%s", chrootenv, _PATH_ISO3166); 916 sprintf(path_zoneinfo, "%s/%s", chrootenv, _PATH_ZONEINFO); 917 sprintf(path_localtime, "%s/%s", chrootenv, _PATH_LOCALTIME); 918 sprintf(path_db, "%s/%s", chrootenv, _PATH_DB); 919 sprintf(path_wall_cmos_clock, "%s/%s", chrootenv, 920 _PATH_WALL_CMOS_CLOCK); 921 } 922 923 /* Override the user-supplied umask. */ 924 (void)umask(S_IWGRP | S_IWOTH); 925 926 if (reinstall == 1) { 927 FILE *f; 928 char zoneinfo[MAXPATHLEN]; 929 930 if ((f = fopen(path_db, "r")) != NULL) { 931 if (fgets(zoneinfo, sizeof(zoneinfo), f) != NULL) { 932 zoneinfo[sizeof(zoneinfo) - 1] = 0; 933 if (strlen(zoneinfo) > 0) { 934 zoneinfo[strlen(zoneinfo) - 1] = 0; 935 rv = install_zoneinfo(zoneinfo); 936 exit(rv & ~DITEM_LEAVE_MENU); 937 } 938 errx(1, "Error reading %s.\n", path_db); 939 } 940 fclose(f); 941 errx(1, 942 "Unable to determine earlier installed zoneinfo " 943 "name. Check %s", path_db); 944 } 945 errx(1, "Cannot open %s for reading. Does it exist?", path_db); 946 } 947 948 /* 949 * If the arguments on the command-line do not specify a file, 950 * then interpret it as a zoneinfo name 951 */ 952 if (optind == argc - 1) { 953 struct stat sb; 954 955 if (stat(argv[optind], &sb) != 0) { 956 #ifdef HAVE_BSDDIALOG 957 usedialog = 0; 958 #endif 959 rv = install_zoneinfo(argv[optind]); 960 exit(rv & ~DITEM_LEAVE_MENU); 961 } 962 /* FALLTHROUGH */ 963 } 964 #ifdef HAVE_BSDDIALOG 965 966 read_iso3166_table(); 967 read_zones(); 968 sort_countries(); 969 make_menus(); 970 971 bsddialog_initconf(&conf); 972 conf.clear = true; 973 974 if (bsddialog_init() < 0) 975 return (1); 976 977 if (skiputc == 0) { 978 int yesno; 979 980 snprintf(prompt, sizeof(prompt), 981 "Is this machine's CMOS clock set to UTC? " 982 "If it is set to local time,\n" 983 "or you don't know, please choose NO here!"); 984 985 conf.button.default_cancel = false; 986 conf.title = "Select local or UTC (Greenwich Mean Time) clock"; 987 yesno = bsddialog_yesno(&conf, prompt, 7, 73); 988 if (!yesno) { 989 if (reallydoit) 990 unlink(path_wall_cmos_clock); 991 } else { 992 if (reallydoit) { 993 fd = open(path_wall_cmos_clock, 994 O_WRONLY | O_CREAT | O_TRUNC, 995 S_IRUSR | S_IRGRP | S_IROTH); 996 if (fd < 0) { 997 bsddialog_end(); 998 err(1, "create %s", 999 path_wall_cmos_clock); 1000 } 1001 close(fd); 1002 } 1003 } 1004 } 1005 if (optind == argc - 1) { 1006 snprintf(prompt, sizeof(prompt), 1007 "\nUse the default `%s' zone?", argv[optind]); 1008 conf.title = "Default timezone provided"; 1009 if (!bsddialog_yesno(&conf, prompt, 7, 72)) { 1010 rv = install_zoneinfo_file(argv[optind]); 1011 bsddialog_end(); 1012 exit(rv & ~DITEM_LEAVE_MENU); 1013 } 1014 } 1015 xdialog_menu("Time Zone Selector", "Select a region", -1, -1, 1016 NCONTINENTS, NCONTINENTS, continents); 1017 1018 bsddialog_end(); 1019 #else 1020 usage(); 1021 #endif 1022 return (0); 1023 } 1024