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 #ifndef lint 36 static const char rcsid[] = 37 "$FreeBSD$"; 38 #endif /* not lint */ 39 40 #include <sys/types.h> 41 #include <dialog.h> 42 #include <err.h> 43 #include <errno.h> 44 #include <stdio.h> 45 #include <stdlib.h> 46 #include <string.h> 47 #include <time.h> 48 #include <unistd.h> 49 50 #include <sys/fcntl.h> 51 #include <sys/queue.h> 52 #include <sys/stat.h> 53 54 #include "paths.h" 55 56 static int reallydoit = 1; 57 58 static int continent_country_menu(dialogMenuItem *); 59 static int set_zone_multi(dialogMenuItem *); 60 static int set_zone_whole_country(dialogMenuItem *); 61 static int set_zone_menu(dialogMenuItem *); 62 63 struct continent { 64 dialogMenuItem *menu; 65 int nitems; 66 int ch; 67 int sc; 68 }; 69 70 static struct continent africa, america, antarctica, arctic, asia, atlantic; 71 static struct continent australia, europe, indian, pacific; 72 73 static struct continent_names { 74 char *name; 75 struct continent *continent; 76 } continent_names[] = { 77 { "Africa", &africa }, { "America", &america }, 78 { "Antarctica", &antarctica }, { "Arctic", &arctic }, 79 { "Asia", &asia }, 80 { "Atlantic", &atlantic }, { "Australia", &australia }, 81 { "Europe", &europe }, { "Indian", &indian }, { "Pacific", &pacific } 82 }; 83 84 static dialogMenuItem continents[] = { 85 { "1", "Africa", 0, continent_country_menu, 0, &africa }, 86 { "2", "America -- North and South", 0, continent_country_menu, 0, 87 &america }, 88 { "3", "Antarctica", 0, continent_country_menu, 0, &antarctica }, 89 { "4", "Arctic Ocean", 0, continent_country_menu, 0, &arctic }, 90 { "5", "Asia", 0, continent_country_menu, 0, &asia }, 91 { "6", "Atlantic Ocean", 0, continent_country_menu, 0, &atlantic }, 92 { "7", "Australia", 0, continent_country_menu, 0, &australia }, 93 { "8", "Europe", 0, continent_country_menu, 0, &europe }, 94 { "9", "Indian Ocean", 0, continent_country_menu, 0, &indian }, 95 { "0", "Pacific Ocean", 0, continent_country_menu, 0, &pacific } 96 }; 97 #define NCONTINENTS (int)((sizeof continents)/(sizeof continents[0])) 98 #define OCEANP(x) ((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9) 99 100 static int 101 continent_country_menu(dialogMenuItem *continent) 102 { 103 int rv; 104 struct continent *contp = continent->data; 105 char title[256]; 106 int isocean = OCEANP(continent - continents); 107 int menulen; 108 109 /* Short cut -- if there's only one country, don't post a menu. */ 110 if (contp->nitems == 1) { 111 return set_zone_menu(&contp->menu[0]); 112 } 113 114 /* It's amazing how much good grammar really matters... */ 115 if (!isocean) 116 snprintf(title, sizeof title, "Countries in %s", 117 continent->title); 118 else 119 snprintf(title, sizeof title, "Islands and groups in the %s", 120 continent->title); 121 122 menulen = contp->nitems < 16 ? contp->nitems : 16; 123 rv = dialog_menu(title, (isocean ? "Select an island or group" 124 : "Select a country"), -1, -1, menulen, 125 -contp->nitems, contp->menu, 0, &contp->ch, 126 &contp->sc); 127 if (rv == 0) 128 return DITEM_LEAVE_MENU; 129 return DITEM_RECREATE; 130 } 131 132 static struct continent * 133 find_continent(const char *name) 134 { 135 int i; 136 137 for (i = 0; i < NCONTINENTS; i++) { 138 if (strcmp(name, continent_names[i].name) == 0) 139 return continent_names[i].continent; 140 } 141 return 0; 142 } 143 144 struct country { 145 char *name; 146 char *tlc; 147 int nzones; 148 char *filename; /* use iff nzones < 0 */ 149 struct continent *continent; /* use iff nzones < 0 */ 150 TAILQ_HEAD(, zone) zones; /* use iff nzones > 0 */ 151 dialogMenuItem *submenu; /* use iff nzones > 0 */ 152 }; 153 154 struct zone { 155 TAILQ_ENTRY(zone) link; 156 char *descr; 157 char *filename; 158 struct continent *continent; 159 }; 160 161 /* 162 * This is the easiest organization... we use ISO 3166 country codes, 163 * of the two-letter variety, so we just size this array to suit. 164 * Beats worrying about dynamic allocation. 165 */ 166 #define NCOUNTRIES (26*26) 167 static struct country countries[NCOUNTRIES]; 168 #define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A')) 169 170 /* 171 * Read the ISO 3166 country code database in _PATH_ISO3166 172 * (/usr/share/misc/iso3166). On error, exit via err(3). 173 */ 174 static void 175 read_iso3166_table(void) 176 { 177 FILE *fp; 178 char *s, *t, *name; 179 size_t len; 180 int lineno; 181 struct country *cp; 182 183 fp = fopen(_PATH_ISO3166, "r"); 184 if (!fp) 185 err(1, _PATH_ISO3166); 186 lineno = 0; 187 188 while ((s = fgetln(fp, &len)) != 0) { 189 lineno++; 190 if (s[len - 1] != '\n') 191 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno); 192 s[len - 1] = '\0'; 193 if (s[0] == '#' || strspn(s, " \t") == len - 1) 194 continue; 195 196 /* Isolate the two-letter code. */ 197 t = strsep(&s, "\t"); 198 if (t == 0 || strlen(t) != 2) 199 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno); 200 if (t[0] < 'A' || t[0] > 'Z' || t[1] < 'A' || t[1] > 'Z') 201 errx(1, _PATH_ISO3166 ":%d: invalid code `%s'", 202 lineno, t); 203 204 /* Now skip past the three-letter and numeric codes. */ 205 name = strsep(&s, "\t"); /* 3-let */ 206 if (name == 0 || strlen(name) != 3) 207 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno); 208 name = strsep(&s, "\t"); /* numeric */ 209 if (name == 0 || strlen(name) != 3) 210 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno); 211 212 name = s; 213 214 cp = &countries[CODE2INT(t)]; 215 if (cp->name) 216 errx(1, _PATH_ISO3166 217 ":%d: country code `%s' multiply defined: %s", 218 lineno, t, cp->name); 219 cp->name = strdup(name); 220 if (cp->name == NULL) 221 errx(1, "malloc failed"); 222 cp->tlc = strdup(t); 223 if (cp->tlc == NULL) 224 errx(1, "malloc failed"); 225 } 226 227 fclose(fp); 228 } 229 230 static void 231 add_zone_to_country(int lineno, const char *tlc, const char *descr, 232 const char *file, struct continent *cont) 233 { 234 struct zone *zp; 235 struct country *cp; 236 237 if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z') 238 errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid", 239 lineno, tlc); 240 241 cp = &countries[CODE2INT(tlc)]; 242 if (cp->name == 0) 243 errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown", 244 lineno, tlc); 245 246 if (descr) { 247 if (cp->nzones < 0) 248 errx(1, _PATH_ZONETAB 249 ":%d: conflicting zone definition", lineno); 250 251 zp = malloc(sizeof *zp); 252 if (zp == 0) 253 errx(1, "malloc(%lu)", (unsigned long)sizeof *zp); 254 255 if (cp->nzones == 0) 256 TAILQ_INIT(&cp->zones); 257 258 zp->descr = strdup(descr); 259 if (zp->descr == NULL) 260 errx(1, "malloc failed"); 261 zp->filename = strdup(file); 262 if (zp->filename == NULL) 263 errx(1, "malloc failed"); 264 zp->continent = cont; 265 TAILQ_INSERT_TAIL(&cp->zones, zp, link); 266 cp->nzones++; 267 } else { 268 if (cp->nzones > 0) 269 errx(1, _PATH_ZONETAB 270 ":%d: zone must have description", lineno); 271 if (cp->nzones < 0) 272 errx(1, _PATH_ZONETAB 273 ":%d: zone multiply defined", lineno); 274 cp->nzones = -1; 275 cp->filename = strdup(file); 276 if (cp->filename == NULL) 277 errx(1, "malloc failed"); 278 cp->continent = cont; 279 } 280 } 281 282 /* 283 * This comparison function intentionally sorts all of the null-named 284 * ``countries''---i.e., the codes that don't correspond to a real 285 * country---to the end. Everything else is lexical by country name. 286 */ 287 static int 288 compare_countries(const void *xa, const void *xb) 289 { 290 const struct country *a = xa, *b = xb; 291 292 if (a->name == 0 && b->name == 0) 293 return 0; 294 if (a->name == 0 && b->name != 0) 295 return 1; 296 if (b->name == 0) 297 return -1; 298 299 return strcmp(a->name, b->name); 300 } 301 302 /* 303 * This must be done AFTER all zone descriptions are read, since it breaks 304 * CODE2INT(). 305 */ 306 static void 307 sort_countries(void) 308 { 309 qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries); 310 } 311 312 static void 313 read_zones(void) 314 { 315 FILE *fp; 316 char *line; 317 size_t len; 318 int lineno; 319 char *tlc, *coord, *file, *descr, *p; 320 char contbuf[16]; 321 struct continent *cont; 322 323 fp = fopen(_PATH_ZONETAB, "r"); 324 if (!fp) 325 err(1, _PATH_ZONETAB); 326 lineno = 0; 327 328 while ((line = fgetln(fp, &len)) != 0) { 329 lineno++; 330 if (line[len - 1] != '\n') 331 errx(1, _PATH_ZONETAB ":%d: invalid format", lineno); 332 line[len - 1] = '\0'; 333 if (line[0] == '#') 334 continue; 335 336 tlc = strsep(&line, "\t"); 337 if (strlen(tlc) != 2) 338 errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'", 339 lineno, tlc); 340 coord = strsep(&line, "\t"); 341 file = strsep(&line, "\t"); 342 p = strchr(file, '/'); 343 if (p == 0) 344 errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'", 345 lineno, file); 346 contbuf[0] = '\0'; 347 strncat(contbuf, file, p - file); 348 cont = find_continent(contbuf); 349 if (!cont) 350 errx(1, _PATH_ZONETAB ":%d: invalid region `%s'", 351 lineno, contbuf); 352 353 descr = (line && *line) ? line : 0; 354 355 add_zone_to_country(lineno, tlc, descr, file, cont); 356 } 357 fclose(fp); 358 } 359 360 static void 361 make_menus(void) 362 { 363 struct country *cp; 364 struct zone *zp, *zp2; 365 struct continent *cont; 366 dialogMenuItem *dmi; 367 int i; 368 369 /* 370 * First, count up all the countries in each continent/ocean. 371 * Be careful to count those countries which have multiple zones 372 * only once for each. NB: some countries are in multiple 373 * continents/oceans. 374 */ 375 for (cp = countries; cp->name; cp++) { 376 if (cp->nzones == 0) 377 continue; 378 if (cp->nzones < 0) { 379 cp->continent->nitems++; 380 } else { 381 TAILQ_FOREACH(zp, &cp->zones, link) { 382 cont = zp->continent; 383 for (zp2 = TAILQ_FIRST(&cp->zones); 384 zp2->continent != cont; 385 zp2 = TAILQ_NEXT(zp2, link)) 386 ; 387 if (zp2 == zp) 388 zp->continent->nitems++; 389 } 390 } 391 } 392 393 /* 394 * Now allocate memory for the country menus. We set 395 * nitems back to zero so that we can use it for counting 396 * again when we actually build the menus. 397 */ 398 for (i = 0; i < NCONTINENTS; i++) { 399 continent_names[i].continent->menu = 400 malloc(sizeof(dialogMenuItem) * 401 continent_names[i].continent->nitems); 402 if (continent_names[i].continent->menu == 0) 403 errx(1, "malloc for continent menu"); 404 continent_names[i].continent->nitems = 0; 405 } 406 407 /* 408 * Now that memory is allocated, create the menu items for 409 * each continent. For multiple-zone countries, also create 410 * the country's zone submenu. 411 */ 412 for (cp = countries; cp->name; cp++) { 413 if (cp->nzones == 0) 414 continue; 415 if (cp->nzones < 0) { 416 dmi = &cp->continent->menu[cp->continent->nitems]; 417 memset(dmi, 0, sizeof *dmi); 418 asprintf(&dmi->prompt, "%d", 419 ++cp->continent->nitems); 420 dmi->title = cp->name; 421 dmi->checked = 0; 422 dmi->fire = set_zone_whole_country; 423 dmi->selected = 0; 424 dmi->data = cp; 425 } else { 426 cp->submenu = malloc(cp->nzones * sizeof *dmi); 427 if (cp->submenu == 0) 428 errx(1, "malloc for submenu"); 429 cp->nzones = 0; 430 TAILQ_FOREACH(zp, &cp->zones, link) { 431 cont = zp->continent; 432 dmi = &cp->submenu[cp->nzones]; 433 memset(dmi, 0, sizeof *dmi); 434 asprintf(&dmi->prompt, "%d", 435 ++cp->nzones); 436 dmi->title = zp->descr; 437 dmi->checked = 0; 438 dmi->fire = set_zone_multi; 439 dmi->selected = 0; 440 dmi->data = zp; 441 442 for (zp2 = TAILQ_FIRST(&cp->zones); 443 zp2->continent != cont; 444 zp2 = TAILQ_NEXT(zp2, link)) 445 ; 446 if (zp2 != zp) 447 continue; 448 449 dmi = &cont->menu[cont->nitems]; 450 memset(dmi, 0, sizeof *dmi); 451 asprintf(&dmi->prompt, "%d", ++cont->nitems); 452 dmi->title = cp->name; 453 dmi->checked = 0; 454 dmi->fire = set_zone_menu; 455 dmi->selected = 0; 456 dmi->data = cp; 457 } 458 } 459 } 460 } 461 462 static int 463 set_zone_menu(dialogMenuItem *dmi) 464 { 465 int rv; 466 char buf[256]; 467 struct country *cp = dmi->data; 468 int menulen; 469 470 snprintf(buf, sizeof buf, "%s Time Zones", cp->name); 471 menulen = cp->nzones < 16 ? cp->nzones : 16; 472 rv = dialog_menu(buf, "Select a zone which observes the same time as " 473 "your locality.", -1, -1, menulen, -cp->nzones, 474 cp->submenu, 0, 0, 0); 475 if (rv != 0) 476 return DITEM_RECREATE; 477 return DITEM_LEAVE_MENU; 478 } 479 480 static int 481 install_zone_file(const char *filename) 482 { 483 struct stat sb; 484 int fd1, fd2; 485 int copymode; 486 char *msg; 487 ssize_t len; 488 char buf[1024]; 489 490 if (lstat(_PATH_LOCALTIME, &sb) < 0) 491 /* Nothing there yet... */ 492 copymode = 1; 493 else if(S_ISLNK(sb.st_mode)) 494 copymode = 0; 495 else 496 copymode = 1; 497 498 #ifdef VERBOSE 499 if (copymode) 500 asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename); 501 else 502 asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME 503 " to %s", filename); 504 505 dialog_notify(msg); 506 free(msg); 507 #endif 508 509 if (reallydoit) { 510 if (copymode) { 511 fd1 = open(filename, O_RDONLY, 0); 512 if (fd1 < 0) { 513 asprintf(&msg, "Could not open %s: %s", 514 filename, strerror(errno)); 515 dialog_mesgbox("Error", msg, 8, 72); 516 free(msg); 517 return DITEM_FAILURE | DITEM_RECREATE; 518 } 519 520 unlink(_PATH_LOCALTIME); 521 fd2 = open(_PATH_LOCALTIME, 522 O_CREAT|O_EXCL|O_WRONLY, 523 S_IRUSR|S_IRGRP|S_IROTH); 524 if (fd2 < 0) { 525 asprintf(&msg, "Could not open " 526 _PATH_LOCALTIME ": %s", 527 strerror(errno)); 528 dialog_mesgbox("Error", msg, 8, 72); 529 free(msg); 530 return DITEM_FAILURE | DITEM_RECREATE; 531 } 532 533 while ((len = read(fd1, buf, sizeof buf)) > 0) 534 len = write(fd2, buf, len); 535 536 if (len == -1) { 537 asprintf(&msg, "Error copying %s to " 538 _PATH_LOCALTIME ": %s", 539 filename, strerror(errno)); 540 dialog_mesgbox("Error", msg, 8, 72); 541 free(msg); 542 /* Better to leave none than a corrupt one. */ 543 unlink(_PATH_LOCALTIME); 544 return DITEM_FAILURE | DITEM_RECREATE; 545 } 546 close(fd1); 547 close(fd2); 548 } else { 549 if (access(filename, R_OK) != 0) { 550 asprintf(&msg, "Cannot access %s: %s", 551 filename, strerror(errno)); 552 dialog_mesgbox("Error", msg, 8, 72); 553 free(msg); 554 return DITEM_FAILURE | DITEM_RECREATE; 555 } 556 unlink(_PATH_LOCALTIME); 557 if (symlink(filename, _PATH_LOCALTIME) < 0) { 558 asprintf(&msg, "Cannot create symbolic link " 559 _PATH_LOCALTIME " to %s: %s", 560 filename, strerror(errno)); 561 dialog_mesgbox("Error", msg, 8, 72); 562 free(msg); 563 return DITEM_FAILURE | DITEM_RECREATE; 564 } 565 } 566 } 567 568 #ifdef VERBOSE 569 if (copymode) 570 asprintf(&msg, "Copied timezone file from %s to " 571 _PATH_LOCALTIME, filename); 572 else 573 asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME 574 " to %s", filename); 575 576 dialog_mesgbox("Done", msg, 8, 72); 577 free(msg); 578 #endif 579 return DITEM_LEAVE_MENU; 580 } 581 582 static int 583 confirm_zone(const char *filename) 584 { 585 char *msg; 586 struct tm *tm; 587 time_t t = time(0); 588 int rv; 589 590 setenv("TZ", filename, 1); 591 tzset(); 592 tm = localtime(&t); 593 594 asprintf(&msg, "Does the abbreviation `%s' look reasonable?", 595 tm->tm_zone); 596 rv = !dialog_yesno("Confirmation", msg, 4, 72); 597 free(msg); 598 return rv; 599 } 600 601 static int 602 set_zone_multi(dialogMenuItem *dmi) 603 { 604 char *fn; 605 struct zone *zp = dmi->data; 606 int rv; 607 608 if (!confirm_zone(zp->filename)) 609 return DITEM_FAILURE | DITEM_RECREATE; 610 611 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename); 612 rv = install_zone_file(fn); 613 free(fn); 614 return rv; 615 } 616 617 static int 618 set_zone_whole_country(dialogMenuItem *dmi) 619 { 620 char *fn; 621 struct country *cp = dmi->data; 622 int rv; 623 624 if (!confirm_zone(cp->filename)) 625 return DITEM_FAILURE | DITEM_RECREATE; 626 627 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename); 628 rv = install_zone_file(fn); 629 free(fn); 630 return rv; 631 } 632 633 static void 634 usage() 635 { 636 fprintf(stderr, "usage: tzsetup [-n]\n"); 637 exit(1); 638 } 639 640 int 641 main(int argc, char **argv) 642 { 643 int c, fd; 644 int (*dialog_utc)(unsigned char *, unsigned char *, int, int); 645 646 #if defined(__alpha__) || defined(__sparc64__) 647 dialog_utc = dialog_yesno; 648 #else 649 dialog_utc = dialog_noyes; 650 #endif 651 652 while ((c = getopt(argc, argv, "n")) != -1) { 653 switch(c) { 654 case 'n': 655 reallydoit = 0; 656 break; 657 658 default: 659 usage(); 660 } 661 } 662 663 if (argc - optind > 1) 664 usage(); 665 666 /* Override the user-supplied umask. */ 667 (void)umask(S_IWGRP|S_IWOTH); 668 669 read_iso3166_table(); 670 read_zones(); 671 sort_countries(); 672 make_menus(); 673 674 init_dialog(); 675 if (!dialog_utc("Select local or UTC (Greenwich Mean Time) clock", 676 "Is this machine's CMOS clock set to UTC? If it is set to local time,\n" 677 "or you don't know, please choose NO here!", 7, 72)) { 678 if (reallydoit) 679 unlink(_PATH_WALL_CMOS_CLOCK); 680 } else { 681 if (reallydoit) { 682 fd = open(_PATH_WALL_CMOS_CLOCK, 683 O_WRONLY|O_CREAT|O_TRUNC, 684 S_IRUSR|S_IRGRP|S_IROTH); 685 if (fd < 0) 686 err(1, "create %s", _PATH_WALL_CMOS_CLOCK); 687 close(fd); 688 } 689 } 690 dialog_clear_norefresh(); 691 if (optind == argc - 1) { 692 char *msg; 693 asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]); 694 if (!dialog_yesno("Default timezone provided", msg, 7, 72)) { 695 install_zone_file(argv[optind]); 696 dialog_clear(); 697 end_dialog(); 698 return 0; 699 } 700 free(msg); 701 dialog_clear_norefresh(); 702 } 703 dialog_menu("Time Zone Selector", "Select a region", -1, -1, 704 NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL); 705 706 dialog_clear(); 707 end_dialog(); 708 return 0; 709 } 710 711