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