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