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