tzsetup.c revision 21915
1162852Sdes/* 298937Sdes * Copyright 1996 Massachusetts Institute of Technology 398937Sdes * 498937Sdes * Permission to use, copy, modify, and distribute this software and 598937Sdes * its documentation for any purpose and without fee is hereby 698937Sdes * granted, provided that both the above copyright notice and this 798937Sdes * permission notice appear in all copies, that both the above 898937Sdes * copyright notice and this permission notice appear in all 998937Sdes * supporting documentation, and that the name of M.I.T. not be used 1098937Sdes * in advertising or publicity pertaining to distribution of the 1198937Sdes * software without specific, written prior permission. M.I.T. makes 1298937Sdes * no representations about the suitability of this software for any 1398937Sdes * purpose. It is provided "as is" without express or implied 1498937Sdes * warranty. 1598937Sdes * 1698937Sdes * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''. M.I.T. DISCLAIMS 1798937Sdes * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE, 1898937Sdes * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 19162852Sdes * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT 2098937Sdes * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 21157016Sdes * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 2298937Sdes * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF 23162852Sdes * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 2498937Sdes * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 2598937Sdes * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT 2698937Sdes * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 2798937Sdes * SUCH DAMAGE. 2898937Sdes * 2998937Sdes * $FreeBSD: head/usr.sbin/tzsetup/tzsetup.c 21915 1997-01-21 10:53:38Z jkh $ 3098937Sdes */ 3198937Sdes 3298937Sdes/* 3398937Sdes * Second attempt at a `tzmenu' program, using the separate description 3498937Sdes * files provided in newer tzdata releases. 3598937Sdes */ 3698937Sdes 3798937Sdes#include <sys/types.h> 3898937Sdes#include <dialog.h> 3998937Sdes#include <err.h> 4098937Sdes#include <errno.h> 4198937Sdes#include <stdio.h> 4298937Sdes#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 52static int reallydoit = 1; 53 54static int continent_country_menu(dialogMenuItem *); 55static int set_zone_multi(dialogMenuItem *); 56static int set_zone_whole_country(dialogMenuItem *); 57static int set_zone_menu(dialogMenuItem *); 58 59struct continent { 60 dialogMenuItem *menu; 61 int nitems; 62 int ch; 63 int sc; 64}; 65 66static struct continent africa, america, antarctica, arctic, asia, atlantic; 67static struct continent australia, europe, indian, pacific; 68 69static 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 80static 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 96static int 97continent_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 128static struct continent * 129find_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 140struct 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 150struct 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 */ 162static struct country countries[26*26]; 163#define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A')) 164 165/* 166 * Read the ISO 3166 country code database in _PATH_ISO3166 167 * (/usr/share/misc/iso3166). On error, exit via err(3). 168 */ 169static void 170read_iso3166_table(void) 171{ 172 FILE *fp; 173 char *s, *t, *name; 174 size_t len; 175 int lineno; 176 struct country *cp; 177 178 fp = fopen(_PATH_ISO3166, "r"); 179 if (!fp) 180 err(1, _PATH_ISO3166); 181 lineno = 0; 182 183 while ((s = fgetln(fp, &len)) != 0) { 184 lineno++; 185 if (s[len - 1] != '\n') 186 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno); 187 s[len - 1] = '\0'; 188 if (s[0] == '#') 189 continue; 190 191 /* Isolate the two-letter code. */ 192 t = strsep(&s, "\t"); 193 if (t == 0 || strlen(t) != 2) 194 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno); 195 if (t[0] < 'A' || t[0] > 'Z' || t[1] < 'A' || t[1] > 'Z') 196 errx(1, _PATH_ISO3166 ":%d: invalid code `%s'", 197 lineno, t); 198 199 /* Now skip past the three-letter and numeric codes. */ 200 name = strsep(&s, "\t"); /* 3-let */ 201 if (name == 0 || strlen(name) != 3) 202 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno); 203 name = strsep(&s, "\t"); /* numeric */ 204 if (name == 0 || strlen(name) != 3) 205 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno); 206 207 name = s; 208 209 cp = &countries[CODE2INT(t)]; 210 if (cp->name) 211 errx(1, _PATH_ISO3166 212 ":%d: country code `%s' multiply defined: %s", 213 lineno, t, cp->name); 214 cp->name = strdup(name); 215 cp->tlc = strdup(t); 216 } 217 218 fclose(fp); 219} 220 221static void 222add_zone_to_country(int lineno, const char *tlc, const char *descr, 223 const char *file, struct continent *cont) 224{ 225 struct zone *zp; 226 struct country *cp; 227 228 if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z') 229 errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid", 230 lineno, tlc); 231 232 cp = &countries[CODE2INT(tlc)]; 233 if (cp->name == 0) 234 errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown", 235 lineno, tlc); 236 237 if (descr) { 238 if (cp->nzones < 0) 239 errx(1, _PATH_ZONETAB 240 ":%d: conflicting zone definition", lineno); 241 242 zp = malloc(sizeof *zp); 243 if (zp == 0) 244 err(1, "malloc(%lu)", (unsigned long)sizeof *zp); 245 246 if (cp->nzones == 0) 247 TAILQ_INIT(&cp->zones); 248 249 zp->descr = strdup(descr); 250 zp->filename = strdup(file); 251 zp->continent = cont; 252 TAILQ_INSERT_TAIL(&cp->zones, zp, link); 253 cp->nzones++; 254 } else { 255 if (cp->nzones > 0) 256 errx(1, _PATH_ZONETAB 257 ":%d: zone must have description", lineno); 258 if (cp->nzones < 0) 259 errx(1, _PATH_ZONETAB 260 ":%d: zone multiply defined", lineno); 261 cp->nzones = -1; 262 cp->filename = strdup(file); 263 cp->continent = cont; 264 } 265} 266 267/* 268 * This comparison function intentionally sorts all of the null-named 269 * ``countries''---i.e., the codes that don't correspond to a real 270 * country---to the end. Everything else is lexical by country name. 271 */ 272static int 273compare_countries(const void *xa, const void *xb) 274{ 275 const struct country *a = xa, *b = xb; 276 277 if (a->name == 0 && b->name == 0) 278 return 0; 279 if (a->name == 0 && b->name != 0) 280 return 1; 281 if (b->name == 0) 282 return -1; 283 284 return strcmp(a->name, b->name); 285} 286 287/* 288 * This must be done AFTER all zone descriptions are read, since it breaks 289 * CODE2INT(). 290 */ 291static void 292sort_countries(void) 293{ 294 qsort(countries, 576, sizeof countries[0], compare_countries); 295} 296 297static void 298read_zones(void) 299{ 300 FILE *fp; 301 char *line; 302 size_t len; 303 int lineno; 304 char *tlc, *coord, *file, *descr, *p; 305 char contbuf[16]; 306 struct continent *cont; 307 308 fp = fopen(_PATH_ZONETAB, "r"); 309 if (!fp) 310 err(1, _PATH_ZONETAB); 311 lineno = 0; 312 313 while ((line = fgetln(fp, &len)) != 0) { 314 lineno++; 315 if (line[len - 1] != '\n') 316 errx(1, _PATH_ZONETAB ":%d: invalid format", lineno); 317 line[len - 1] = '\0'; 318 if (line[0] == '#') 319 continue; 320 321 tlc = strsep(&line, "\t"); 322 if (strlen(tlc) != 2) 323 errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'", 324 lineno, tlc); 325 coord = strsep(&line, "\t"); 326 file = strsep(&line, "\t"); 327 p = strchr(file, '/'); 328 if (p == 0) 329 errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'", 330 lineno, file); 331 contbuf[0] = '\0'; 332 strncat(contbuf, file, p - file); 333 cont = find_continent(contbuf); 334 if (!cont) 335 errx(1, _PATH_ZONETAB ":%d: invalid region `%s'", 336 lineno, contbuf); 337 338 descr = (line && *line) ? line : 0; 339 340 add_zone_to_country(lineno, tlc, descr, file, cont); 341 } 342 fclose(fp); 343} 344 345static void 346make_menus(void) 347{ 348 struct country *cp; 349 struct zone *zp, *zp2; 350 struct continent *cont; 351 dialogMenuItem *dmi; 352 int i; 353 354 /* 355 * First, count up all the countries in each continent/ocean. 356 * Be careful to count those countries which have multiple zones 357 * only once for each. NB: some countries are in multiple 358 * continents/oceans. 359 */ 360 for (cp = countries; cp->name; cp++) { 361 if (cp->nzones == 0) 362 continue; 363 if (cp->nzones < 0) { 364 cp->continent->nitems++; 365 } else { 366 for (zp = cp->zones.tqh_first; zp; 367 zp = zp->link.tqe_next) { 368 cont = zp->continent; 369 for (zp2 = cp->zones.tqh_first; 370 zp2->continent != cont; 371 zp2 = zp2->link.tqe_next) 372 ; 373 if (zp2 == zp) 374 zp->continent->nitems++; 375 } 376 } 377 } 378 379 /* 380 * Now allocate memory for the country menus. We set 381 * nitems back to zero so that we can use it for counting 382 * again when we actually build the menus. 383 */ 384 for (i = 0; i < NCONTINENTS; i++) { 385 continent_names[i].continent->menu = 386 malloc(sizeof(dialogMenuItem) * 387 continent_names[i].continent->nitems); 388 if (continent_names[i].continent->menu == 0) 389 err(1, "malloc for continent menu"); 390 continent_names[i].continent->nitems = 0; 391 } 392 393 /* 394 * Now that memory is allocated, create the menu items for 395 * each continent. For multiple-zone countries, also create 396 * the country's zone submenu. 397 */ 398 for (cp = countries; cp->name; cp++) { 399 if (cp->nzones == 0) 400 continue; 401 if (cp->nzones < 0) { 402 dmi = &cp->continent->menu[cp->continent->nitems]; 403 memset(dmi, 0, sizeof *dmi); 404 asprintf(&dmi->prompt, "%d", 405 ++cp->continent->nitems); 406 dmi->title = cp->name; 407 dmi->checked = 0; 408 dmi->fire = set_zone_whole_country; 409 dmi->selected = 0; 410 dmi->data = cp; 411 } else { 412 cp->submenu = malloc(cp->nzones * sizeof *dmi); 413 if (cp->submenu == 0) 414 err(1, "malloc for submenu"); 415 cp->nzones = 0; 416 for (zp = cp->zones.tqh_first; zp; 417 zp = zp->link.tqe_next) { 418 cont = zp->continent; 419 dmi = &cp->submenu[cp->nzones]; 420 memset(dmi, 0, sizeof *dmi); 421 asprintf(&dmi->prompt, "%d", 422 ++cp->nzones); 423 dmi->title = zp->descr; 424 dmi->checked = 0; 425 dmi->fire = set_zone_multi; 426 dmi->selected = 0; 427 dmi->data = zp; 428 429 for (zp2 = cp->zones.tqh_first; 430 zp2->continent != cont; 431 zp2 = zp2->link.tqe_next) 432 ; 433 if (zp2 != zp) 434 continue; 435 436 dmi = &cont->menu[cont->nitems]; 437 memset(dmi, 0, sizeof *dmi); 438 asprintf(&dmi->prompt, "%d", ++cont->nitems); 439 dmi->title = cp->name; 440 dmi->checked = 0; 441 dmi->fire = set_zone_menu; 442 dmi->selected = 0; 443 dmi->data = cp; 444 } 445 } 446 } 447} 448 449static int 450set_zone_menu(dialogMenuItem *dmi) 451{ 452 int rv; 453 char buf[256]; 454 struct country *cp = dmi->data; 455 int menulen; 456 457 snprintf(buf, sizeof buf, "%s Time Zones", cp->name); 458 menulen = cp->nzones < 16 ? cp->nzones : 16; 459 rv = dialog_menu(buf, "Select a zone which observes the same time as " 460 "your locality.", -1, -1, menulen, -cp->nzones, 461 cp->submenu, 0, 0, 0); 462 if (rv != 0) 463 return DITEM_RECREATE; 464 return DITEM_LEAVE_MENU; 465} 466 467static int 468install_zone_file(const char *filename) 469{ 470 struct stat sb; 471 int fd1, fd2; 472 int copymode; 473 char *msg; 474 ssize_t len; 475 char buf[1024]; 476 477 if (lstat(_PATH_LOCALTIME, &sb) < 0) 478 /* Nothing there yet... */ 479 copymode = 1; 480 else if(S_ISLNK(sb.st_mode)) 481 copymode = 0; 482 else 483 copymode = 1; 484 485#ifdef VERBOSE 486 if (copymode) 487 asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename); 488 else 489 asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME 490 " to %s", filename); 491 492 dialog_notify(msg); 493 free(msg); 494#endif 495 496 if (reallydoit) { 497 if (copymode) { 498 fd1 = open(filename, O_RDONLY, 0); 499 if (fd1 < 0) { 500 asprintf(&msg, "Could not open %s: %s", 501 filename, strerror(errno)); 502 dialog_mesgbox("Error", msg, 8, 72); 503 free(msg); 504 return DITEM_FAILURE | DITEM_RECREATE; 505 } 506 507 unlink(_PATH_LOCALTIME); 508 fd2 = open(_PATH_LOCALTIME, 509 O_CREAT | O_EXCL | O_WRONLY, 510 0444); 511 if (fd2 < 0) { 512 asprintf(&msg, "Could not open " 513 _PATH_LOCALTIME ": %s", 514 strerror(errno)); 515 dialog_mesgbox("Error", msg, 8, 72); 516 free(msg); 517 return DITEM_FAILURE | DITEM_RECREATE; 518 } 519 520 while ((len = read(fd1, buf, sizeof buf)) > 0) 521 len = write(fd2, buf, len); 522 523 if (len == -1) { 524 asprintf(&msg, "Error copying %s to " 525 _PATH_LOCALTIME ": %s", 526 strerror(errno)); 527 dialog_mesgbox("Error", msg, 8, 72); 528 free(msg); 529 /* Better to leave none than a corrupt one. */ 530 unlink(_PATH_LOCALTIME); 531 return DITEM_FAILURE | DITEM_RECREATE; 532 } 533 close(fd1); 534 close(fd2); 535 } else { 536 if (access(filename, R_OK) != 0) { 537 asprintf(&msg, "Cannot access %s: %s", 538 filename, strerror(errno)); 539 dialog_mesgbox("Error", msg, 8, 72); 540 free(msg); 541 return DITEM_FAILURE | DITEM_RECREATE; 542 } 543 unlink(_PATH_LOCALTIME); 544 if (symlink(filename, _PATH_LOCALTIME) < 0) { 545 asprintf(&msg, "Cannot create symbolic link " 546 _PATH_LOCALTIME " to %s: %s", 547 filename, strerror(errno)); 548 dialog_mesgbox("Error", msg, 8, 72); 549 free(msg); 550 return DITEM_FAILURE | DITEM_RECREATE; 551 } 552 } 553 } 554 555#ifdef VERBOSE 556 if (copymode) 557 asprintf(&msg, "Copied timezone file from %s to " 558 _PATH_LOCALTIME, filename); 559 else 560 asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME 561 " to %s", filename); 562 563 dialog_mesgbox("Done", msg, 8, 72); 564 free(msg); 565#endif 566 return DITEM_LEAVE_MENU; 567} 568 569static int 570confirm_zone(const char *filename) 571{ 572 char *msg; 573 struct tm *tm; 574 time_t t = time(0); 575 int rv; 576 577 setenv("TZ", filename, 1); 578 tzset(); 579 tm = localtime(&t); 580 581 asprintf(&msg, "Does the abbreviation `%s' look reasonable?", 582 tm->tm_zone); 583 rv = !dialog_yesno("Confirmation", msg, 4, 72); 584 free(msg); 585 return rv; 586} 587 588static int 589set_zone_multi(dialogMenuItem *dmi) 590{ 591 char *fn; 592 struct zone *zp = dmi->data; 593 int rv; 594 595 if (!confirm_zone(zp->filename)) 596 return DITEM_FAILURE | DITEM_RECREATE; 597 598 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename); 599 rv = install_zone_file(fn); 600 free(fn); 601 return rv; 602} 603 604static int 605set_zone_whole_country(dialogMenuItem *dmi) 606{ 607 char *fn; 608 struct country *cp = dmi->data; 609 int rv; 610 611 if (!confirm_zone(cp->filename)) 612 return DITEM_FAILURE | DITEM_RECREATE; 613 614 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename); 615 rv = install_zone_file(fn); 616 free(fn); 617 return rv; 618} 619 620int 621main(int argc, char **argv) 622{ 623 int c; 624 625 while ((c = getopt(argc, argv, "n")) != -1) { 626 switch(c) { 627 case 'n': 628 reallydoit = 0; 629 break; 630 631 default: 632 fprintf(stderr, "%s: usage:\n\t%s [-n]\n", argv[0], 633 argv[0]); 634 exit(1); 635 } 636 } 637 638 if (optind != argc) { 639 fprintf(stderr, "%s: usage:\n\t%s [-n]\n", argv[0], argv[0]); 640 exit(1); 641 } 642 643 read_iso3166_table(); 644 read_zones(); 645 sort_countries(); 646 make_menus(); 647 648 init_dialog(); 649 dialog_menu("Time Zone Selector", "Select a region", -1, -1, 650 NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL); 651 end_dialog(); 652 return 0; 653} 654 655