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 "$Id: tzsetup.c,v 1.10 1998/01/10 15:55:11 steve Exp $"; 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 cp->tlc = strdup(t); 220 } 221 222 fclose(fp); 223 } 224 225 static void 226 add_zone_to_country(int lineno, const char *tlc, const char *descr, 227 const char *file, struct continent *cont) 228 { 229 struct zone *zp; 230 struct country *cp; 231 232 if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z') 233 errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid", 234 lineno, tlc); 235 236 cp = &countries[CODE2INT(tlc)]; 237 if (cp->name == 0) 238 errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown", 239 lineno, tlc); 240 241 if (descr) { 242 if (cp->nzones < 0) 243 errx(1, _PATH_ZONETAB 244 ":%d: conflicting zone definition", lineno); 245 246 zp = malloc(sizeof *zp); 247 if (zp == 0) 248 err(1, "malloc(%lu)", (unsigned long)sizeof *zp); 249 250 if (cp->nzones == 0) 251 TAILQ_INIT(&cp->zones); 252 253 zp->descr = strdup(descr); 254 zp->filename = strdup(file); 255 zp->continent = cont; 256 TAILQ_INSERT_TAIL(&cp->zones, zp, link); 257 cp->nzones++; 258 } else { 259 if (cp->nzones > 0) 260 errx(1, _PATH_ZONETAB 261 ":%d: zone must have description", lineno); 262 if (cp->nzones < 0) 263 errx(1, _PATH_ZONETAB 264 ":%d: zone multiply defined", lineno); 265 cp->nzones = -1; 266 cp->filename = strdup(file); 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 err(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 err(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 0444); 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 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 setenv("TZ", filename, 1); 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() 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 636 while ((c = getopt(argc, argv, "n")) != -1) { 637 switch(c) { 638 case 'n': 639 reallydoit = 0; 640 break; 641 642 default: 643 usage(); 644 } 645 } 646 647 if (optind != argc) 648 usage(); 649 650 read_iso3166_table(); 651 read_zones(); 652 sort_countries(); 653 make_menus(); 654 655 init_dialog(); 656 if (!dialog_yesno("Select local or UTC (Greenwich Mean Time) clock", 657 "Is this machine's CMOS clock set to UTC? If it is set to local time,\n" 658 "please choose NO here!", 7, 72)) { 659 if (reallydoit) 660 unlink(_PATH_WALL_CMOS_CLOCK); 661 } else { 662 if (reallydoit) { 663 fd = open(_PATH_WALL_CMOS_CLOCK, 664 O_WRONLY|O_CREAT|O_TRUNC, 0666); 665 if (fd < 0) 666 err(1, "create %s", _PATH_WALL_CMOS_CLOCK); 667 close(fd); 668 } 669 } 670 dialog_clear_norefresh(); 671 dialog_menu("Time Zone Selector", "Select a region", -1, -1, 672 NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL); 673 dialog_clear(); 674 end_dialog(); 675 return 0; 676 } 677 678