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 "$FreeBSD: head/usr.sbin/tzsetup/tzsetup.c 66907 2000-10-10 01:50:26Z wollman $";
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();
670 if (!dialog_yesno("Select local or UTC (Greenwich Mean Time) clock",
671 "Is this machine's CMOS clock set to UTC? If it is set to local time,\n"
672 "or you don't know, please choose NO here!", 7, 72)) {
673 if (reallydoit)
674 unlink(_PATH_WALL_CMOS_CLOCK);
675 } else {
676 if (reallydoit) {
677 fd = open(_PATH_WALL_CMOS_CLOCK,
678 O_WRONLY|O_CREAT|O_TRUNC,
679 S_IRUSR|S_IRGRP|S_IROTH);
680 if (fd < 0)
681 err(1, "create %s", _PATH_WALL_CMOS_CLOCK);
682 close(fd);
683 }
684 }
685 dialog_clear_norefresh();
686 if (optind == argc - 1) {
687 char *msg;
688 asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]);
689 if (!dialog_yesno("Default timezone provided", msg, 7, 72)) {
690 install_zone_file(argv[optind]);
691 dialog_clear();
692 end_dialog();
693 return 0;
694 }
695 free(msg);
696 dialog_clear_norefresh();
697 }
698 dialog_menu("Time Zone Selector", "Select a region", -1, -1,
699 NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);
700
701 dialog_clear();
702 end_dialog();
703 return 0;
704}
705
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 "$FreeBSD: head/usr.sbin/tzsetup/tzsetup.c 66907 2000-10-10 01:50:26Z wollman $";
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();
670 if (!dialog_yesno("Select local or UTC (Greenwich Mean Time) clock",
671 "Is this machine's CMOS clock set to UTC? If it is set to local time,\n"
672 "or you don't know, please choose NO here!", 7, 72)) {
673 if (reallydoit)
674 unlink(_PATH_WALL_CMOS_CLOCK);
675 } else {
676 if (reallydoit) {
677 fd = open(_PATH_WALL_CMOS_CLOCK,
678 O_WRONLY|O_CREAT|O_TRUNC,
679 S_IRUSR|S_IRGRP|S_IROTH);
680 if (fd < 0)
681 err(1, "create %s", _PATH_WALL_CMOS_CLOCK);
682 close(fd);
683 }
684 }
685 dialog_clear_norefresh();
686 if (optind == argc - 1) {
687 char *msg;
688 asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]);
689 if (!dialog_yesno("Default timezone provided", msg, 7, 72)) {
690 install_zone_file(argv[optind]);
691 dialog_clear();
692 end_dialog();
693 return 0;
694 }
695 free(msg);
696 dialog_clear_norefresh();
697 }
698 dialog_menu("Time Zone Selector", "Select a region", -1, -1,
699 NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);
700
701 dialog_clear();
702 end_dialog();
703 return 0;
704}
705