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