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