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