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 * $Id$ 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 ((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 set_zone_menu(&contp->menu[0]); 108 } 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] == '#') 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 /* Now skip past the three-letter and numeric codes. */ 201 name = strsep(&s, "\t"); /* 3-let */ 202 if (name == 0 || strlen(name) != 3) 203 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno); 204 name = strsep(&s, "\t"); /* numeric */ 205 if (name == 0 || strlen(name) != 3) 206 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno); 207 208 name = s; 209 210 cp = &countries[CODE2INT(t)]; 211 if (cp->name) 212 errx(1, _PATH_ISO3166 213 ":%d: country code `%s' multiply defined: %s", 214 lineno, t, cp->name); 215 cp->name = strdup(name); 216 cp->tlc = strdup(t); 217 } 218 219 fclose(fp); 220 } 221 222 static void 223 add_zone_to_country(int lineno, const char *tlc, const char *descr, 224 const char *file, struct continent *cont) 225 { 226 struct zone *zp; 227 struct country *cp; 228 229 if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z') 230 errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid", 231 lineno, tlc); 232 233 cp = &countries[CODE2INT(tlc)]; 234 if (cp->name == 0) 235 errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown", 236 lineno, tlc); 237 238 if (descr) { 239 if (cp->nzones < 0) 240 errx(1, _PATH_ZONETAB 241 ":%d: conflicting zone definition", lineno); 242 243 zp = malloc(sizeof *zp); 244 if (zp == 0) 245 err(1, "malloc(%lu)", (unsigned long)sizeof *zp); 246 247 if (cp->nzones == 0) 248 TAILQ_INIT(&cp->zones); 249 250 zp->descr = strdup(descr); 251 zp->filename = strdup(file); 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 cp->continent = cont; 265 } 266 } 267 268 /* 269 * This comparison function intentionally sorts all of the null-named 270 * ``countries''---i.e., the codes that don't correspond to a real 271 * country---to the end. Everything else is lexical by country name. 272 */ 273 static int 274 compare_countries(const void *xa, const void *xb) 275 { 276 const struct country *a = xa, *b = xb; 277 278 if (a->name == 0 && b->name == 0) 279 return 0; 280 if (a->name == 0 && b->name != 0) 281 return 1; 282 if (b->name == 0) 283 return -1; 284 285 return strcmp(a->name, b->name); 286 } 287 288 /* 289 * This must be done AFTER all zone descriptions are read, since it breaks 290 * CODE2INT(). 291 */ 292 static void 293 sort_countries(void) 294 { 295 qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries); 296 } 297 298 static void 299 read_zones(void) 300 { 301 FILE *fp; 302 char *line; 303 size_t len; 304 int lineno; 305 char *tlc, *coord, *file, *descr, *p; 306 char contbuf[16]; 307 struct continent *cont; 308 309 fp = fopen(_PATH_ZONETAB, "r"); 310 if (!fp) 311 err(1, _PATH_ZONETAB); 312 lineno = 0; 313 314 while ((line = fgetln(fp, &len)) != 0) { 315 lineno++; 316 if (line[len - 1] != '\n') 317 errx(1, _PATH_ZONETAB ":%d: invalid format", lineno); 318 line[len - 1] = '\0'; 319 if (line[0] == '#') 320 continue; 321 322 tlc = strsep(&line, "\t"); 323 if (strlen(tlc) != 2) 324 errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'", 325 lineno, tlc); 326 coord = strsep(&line, "\t"); 327 file = strsep(&line, "\t"); 328 p = strchr(file, '/'); 329 if (p == 0) 330 errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'", 331 lineno, file); 332 contbuf[0] = '\0'; 333 strncat(contbuf, file, p - file); 334 cont = find_continent(contbuf); 335 if (!cont) 336 errx(1, _PATH_ZONETAB ":%d: invalid region `%s'", 337 lineno, contbuf); 338 339 descr = (line && *line) ? line : 0; 340 341 add_zone_to_country(lineno, tlc, descr, file, cont); 342 } 343 fclose(fp); 344 } 345 346 static void 347 make_menus(void) 348 { 349 struct country *cp; 350 struct zone *zp, *zp2; 351 struct continent *cont; 352 dialogMenuItem *dmi; 353 int i; 354 355 /* 356 * First, count up all the countries in each continent/ocean. 357 * Be careful to count those countries which have multiple zones 358 * only once for each. NB: some countries are in multiple 359 * continents/oceans. 360 */ 361 for (cp = countries; cp->name; cp++) { 362 if (cp->nzones == 0) 363 continue; 364 if (cp->nzones < 0) { 365 cp->continent->nitems++; 366 } else { 367 for (zp = cp->zones.tqh_first; zp; 368 zp = zp->link.tqe_next) { 369 cont = zp->continent; 370 for (zp2 = cp->zones.tqh_first; 371 zp2->continent != cont; 372 zp2 = zp2->link.tqe_next) 373 ; 374 if (zp2 == zp) 375 zp->continent->nitems++; 376 } 377 } 378 } 379 380 /* 381 * Now allocate memory for the country menus. We set 382 * nitems back to zero so that we can use it for counting 383 * again when we actually build the menus. 384 */ 385 for (i = 0; i < NCONTINENTS; i++) { 386 continent_names[i].continent->menu = 387 malloc(sizeof(dialogMenuItem) * 388 continent_names[i].continent->nitems); 389 if (continent_names[i].continent->menu == 0) 390 err(1, "malloc for continent menu"); 391 continent_names[i].continent->nitems = 0; 392 } 393 394 /* 395 * Now that memory is allocated, create the menu items for 396 * each continent. For multiple-zone countries, also create 397 * the country's zone submenu. 398 */ 399 for (cp = countries; cp->name; cp++) { 400 if (cp->nzones == 0) 401 continue; 402 if (cp->nzones < 0) { 403 dmi = &cp->continent->menu[cp->continent->nitems]; 404 memset(dmi, 0, sizeof *dmi); 405 asprintf(&dmi->prompt, "%d", 406 ++cp->continent->nitems); 407 dmi->title = cp->name; 408 dmi->checked = 0; 409 dmi->fire = set_zone_whole_country; 410 dmi->selected = 0; 411 dmi->data = cp; 412 } else { 413 cp->submenu = malloc(cp->nzones * sizeof *dmi); 414 if (cp->submenu == 0) 415 err(1, "malloc for submenu"); 416 cp->nzones = 0; 417 for (zp = cp->zones.tqh_first; zp; 418 zp = zp->link.tqe_next) { 419 cont = zp->continent; 420 dmi = &cp->submenu[cp->nzones]; 421 memset(dmi, 0, sizeof *dmi); 422 asprintf(&dmi->prompt, "%d", 423 ++cp->nzones); 424 dmi->title = zp->descr; 425 dmi->checked = 0; 426 dmi->fire = set_zone_multi; 427 dmi->selected = 0; 428 dmi->data = zp; 429 430 for (zp2 = cp->zones.tqh_first; 431 zp2->continent != cont; 432 zp2 = zp2->link.tqe_next) 433 ; 434 if (zp2 != zp) 435 continue; 436 437 dmi = &cont->menu[cont->nitems]; 438 memset(dmi, 0, sizeof *dmi); 439 asprintf(&dmi->prompt, "%d", ++cont->nitems); 440 dmi->title = cp->name; 441 dmi->checked = 0; 442 dmi->fire = set_zone_menu; 443 dmi->selected = 0; 444 dmi->data = cp; 445 } 446 } 447 } 448 } 449 450 static int 451 set_zone_menu(dialogMenuItem *dmi) 452 { 453 int rv; 454 char buf[256]; 455 struct country *cp = dmi->data; 456 int menulen; 457 458 snprintf(buf, sizeof buf, "%s Time Zones", cp->name); 459 menulen = cp->nzones < 16 ? cp->nzones : 16; 460 rv = dialog_menu(buf, "Select a zone which observes the same time as " 461 "your locality.", -1, -1, menulen, -cp->nzones, 462 cp->submenu, 0, 0, 0); 463 if (rv != 0) 464 return DITEM_RECREATE; 465 return DITEM_LEAVE_MENU; 466 } 467 468 static int 469 install_zone_file(const char *filename) 470 { 471 struct stat sb; 472 int fd1, fd2; 473 int copymode; 474 char *msg; 475 ssize_t len; 476 char buf[1024]; 477 478 if (lstat(_PATH_LOCALTIME, &sb) < 0) 479 /* Nothing there yet... */ 480 copymode = 1; 481 else if(S_ISLNK(sb.st_mode)) 482 copymode = 0; 483 else 484 copymode = 1; 485 486 #ifdef VERBOSE 487 if (copymode) 488 asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename); 489 else 490 asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME 491 " to %s", filename); 492 493 dialog_notify(msg); 494 free(msg); 495 #endif 496 497 if (reallydoit) { 498 if (copymode) { 499 fd1 = open(filename, O_RDONLY, 0); 500 if (fd1 < 0) { 501 asprintf(&msg, "Could not open %s: %s", 502 filename, strerror(errno)); 503 dialog_mesgbox("Error", msg, 8, 72); 504 free(msg); 505 return DITEM_FAILURE | DITEM_RECREATE; 506 } 507 508 unlink(_PATH_LOCALTIME); 509 fd2 = open(_PATH_LOCALTIME, 510 O_CREAT | O_EXCL | O_WRONLY, 511 0444); 512 if (fd2 < 0) { 513 asprintf(&msg, "Could not open " 514 _PATH_LOCALTIME ": %s", 515 strerror(errno)); 516 dialog_mesgbox("Error", msg, 8, 72); 517 free(msg); 518 return DITEM_FAILURE | DITEM_RECREATE; 519 } 520 521 while ((len = read(fd1, buf, sizeof buf)) > 0) 522 len = write(fd2, buf, len); 523 524 if (len == -1) { 525 asprintf(&msg, "Error copying %s to " 526 _PATH_LOCALTIME ": %s", 527 strerror(errno)); 528 dialog_mesgbox("Error", msg, 8, 72); 529 free(msg); 530 /* Better to leave none than a corrupt one. */ 531 unlink(_PATH_LOCALTIME); 532 return DITEM_FAILURE | DITEM_RECREATE; 533 } 534 close(fd1); 535 close(fd2); 536 } else { 537 if (access(filename, R_OK) != 0) { 538 asprintf(&msg, "Cannot access %s: %s", 539 filename, strerror(errno)); 540 dialog_mesgbox("Error", msg, 8, 72); 541 free(msg); 542 return DITEM_FAILURE | DITEM_RECREATE; 543 } 544 unlink(_PATH_LOCALTIME); 545 if (symlink(filename, _PATH_LOCALTIME) < 0) { 546 asprintf(&msg, "Cannot create symbolic link " 547 _PATH_LOCALTIME " to %s: %s", 548 filename, strerror(errno)); 549 dialog_mesgbox("Error", msg, 8, 72); 550 free(msg); 551 return DITEM_FAILURE | DITEM_RECREATE; 552 } 553 } 554 } 555 556 #ifdef VERBOSE 557 if (copymode) 558 asprintf(&msg, "Copied timezone file from %s to " 559 _PATH_LOCALTIME, filename); 560 else 561 asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME 562 " to %s", filename); 563 564 dialog_mesgbox("Done", msg, 8, 72); 565 free(msg); 566 #endif 567 return DITEM_LEAVE_MENU; 568 } 569 570 static int 571 confirm_zone(const char *filename) 572 { 573 char *msg; 574 struct tm *tm; 575 time_t t = time(0); 576 int rv; 577 578 setenv("TZ", filename, 1); 579 tzset(); 580 tm = localtime(&t); 581 582 asprintf(&msg, "Does the abbreviation `%s' look reasonable?", 583 tm->tm_zone); 584 rv = !dialog_yesno("Confirmation", msg, 4, 72); 585 free(msg); 586 return rv; 587 } 588 589 static int 590 set_zone_multi(dialogMenuItem *dmi) 591 { 592 char *fn; 593 struct zone *zp = dmi->data; 594 int rv; 595 596 if (!confirm_zone(zp->filename)) 597 return DITEM_FAILURE | DITEM_RECREATE; 598 599 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename); 600 rv = install_zone_file(fn); 601 free(fn); 602 return rv; 603 } 604 605 static int 606 set_zone_whole_country(dialogMenuItem *dmi) 607 { 608 char *fn; 609 struct country *cp = dmi->data; 610 int rv; 611 612 if (!confirm_zone(cp->filename)) 613 return DITEM_FAILURE | DITEM_RECREATE; 614 615 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename); 616 rv = install_zone_file(fn); 617 free(fn); 618 return rv; 619 } 620 621 int 622 main(int argc, char **argv) 623 { 624 int c; 625 626 while ((c = getopt(argc, argv, "n")) != -1) { 627 switch(c) { 628 case 'n': 629 reallydoit = 0; 630 break; 631 632 default: 633 fprintf(stderr, "%s: usage:\n\t%s [-n]\n", argv[0], 634 argv[0]); 635 exit(1); 636 } 637 } 638 639 if (optind != argc) { 640 fprintf(stderr, "%s: usage:\n\t%s [-n]\n", argv[0], argv[0]); 641 exit(1); 642 } 643 644 read_iso3166_table(); 645 read_zones(); 646 sort_countries(); 647 make_menus(); 648 649 init_dialog(); 650 if (!dialog_yesno("Select local or UTC (Greenwich Mean Time) clock", 651 "Is this machine's CMOS clock set to UTC? If it is set to local time,\n" 652 "please choose NO here!", 7, 72)) 653 system("rm -f /etc/wall_cmos_clock"); 654 else 655 system("touch /etc/wall_cmos_clock"); 656 dialog_clear_norefresh(); 657 dialog_menu("Time Zone Selector", "Select a region", -1, -1, 658 NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL); 659 dialog_clear(); 660 end_dialog(); 661 return 0; 662 } 663 664