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