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
36static const char rcsid[] =
37 "$Id: tzsetup.c,v 1.12 1999/02/02 20:26:31 wollman 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
55static int reallydoit = 1;
56
57static int continent_country_menu(dialogMenuItem *);
58static int set_zone_multi(dialogMenuItem *);
59static int set_zone_whole_country(dialogMenuItem *);
60static int set_zone_menu(dialogMenuItem *);
61
62struct continent {
63 dialogMenuItem *menu;
64 int nitems;
65 int ch;
66 int sc;
67};
68
69static struct continent africa, america, antarctica, arctic, asia, atlantic;
70static struct continent australia, europe, indian, pacific;
71
72static 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
83static 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
99static int
100continent_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
131static struct continent *
132find_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
143struct 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
153struct 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)
166static 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 */
173static void
174read_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
225static void
226add_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 */
276static int
277compare_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 */
295static void
296sort_countries(void)
297{
298 qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
299}
300
301static void
302read_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
349static void
350make_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
453static int
454set_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
471static int
472install_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
573static int
574confirm_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
592static int
593set_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
608static int
609set_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
624static void
625usage()
626{
627 fprintf(stderr, "usage: tzsetup [-n]\n");
628 exit(1);
629}
630
631int
632main(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 (argc - optind > 1)
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 "or you don't know, 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 if (optind == argc - 1) {
672 char *msg;
673 asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]);
674 if (!dialog_yesno("Default timezone provided", msg, 7, 72)) {
675 install_zone_file(argv[optind]);
676 dialog_clear();
677 end_dialog();
678 return 0;
679 }
680 free(msg);
681 dialog_clear_norefresh();
682 }
683 dialog_menu("Time Zone Selector", "Select a region", -1, -1,
684 NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);
685
686 dialog_clear();
687 end_dialog();
688 return 0;
689}
690
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
36static const char rcsid[] =
37 "$Id: tzsetup.c,v 1.12 1999/02/02 20:26:31 wollman 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
55static int reallydoit = 1;
56
57static int continent_country_menu(dialogMenuItem *);
58static int set_zone_multi(dialogMenuItem *);
59static int set_zone_whole_country(dialogMenuItem *);
60static int set_zone_menu(dialogMenuItem *);
61
62struct continent {
63 dialogMenuItem *menu;
64 int nitems;
65 int ch;
66 int sc;
67};
68
69static struct continent africa, america, antarctica, arctic, asia, atlantic;
70static struct continent australia, europe, indian, pacific;
71
72static 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
83static 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
99static int
100continent_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
131static struct continent *
132find_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
143struct 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
153struct 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)
166static 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 */
173static void
174read_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
225static void
226add_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 */
276static int
277compare_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 */
295static void
296sort_countries(void)
297{
298 qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
299}
300
301static void
302read_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
349static void
350make_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
453static int
454set_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
471static int
472install_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
573static int
574confirm_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
592static int
593set_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
608static int
609set_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
624static void
625usage()
626{
627 fprintf(stderr, "usage: tzsetup [-n]\n");
628 exit(1);
629}
630
631int
632main(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 (argc - optind > 1)
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 "or you don't know, 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 if (optind == argc - 1) {
672 char *msg;
673 asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]);
674 if (!dialog_yesno("Default timezone provided", msg, 7, 72)) {
675 install_zone_file(argv[optind]);
676 dialog_clear();
677 end_dialog();
678 return 0;
679 }
680 free(msg);
681 dialog_clear_norefresh();
682 }
683 dialog_menu("Time Zone Selector", "Select a region", -1, -1,
684 NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);
685
686 dialog_clear();
687 end_dialog();
688 return 0;
689}
690