2 * Copyright 1996 Massachusetts Institute of Technology
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
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
31 * Second attempt at a `tzmenu' program, using the separate description
32 * files provided in newer tzdata releases.
36 static const char rcsid[] =
40 #include <sys/types.h>
50 #include <sys/fcntl.h>
51 #include <sys/queue.h>
56 static int reallydoit = 1;
58 static int continent_country_menu(dialogMenuItem *);
59 static int set_zone_multi(dialogMenuItem *);
60 static int set_zone_whole_country(dialogMenuItem *);
61 static int set_zone_menu(dialogMenuItem *);
70 static struct continent africa, america, antarctica, arctic, asia, atlantic;
71 static struct continent australia, europe, indian, pacific;
73 static struct continent_names {
75 struct continent *continent;
76 } continent_names[] = {
77 { "Africa", &africa }, { "America", &america },
78 { "Antarctica", &antarctica }, { "Arctic", &arctic },
80 { "Atlantic", &atlantic }, { "Australia", &australia },
81 { "Europe", &europe }, { "Indian", &indian }, { "Pacific", &pacific }
84 static dialogMenuItem continents[] = {
85 { "1", "Africa", 0, continent_country_menu, 0, &africa },
86 { "2", "America -- North and South", 0, continent_country_menu, 0,
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 }
97 #define NCONTINENTS (int)((sizeof continents)/(sizeof continents[0]))
98 #define OCEANP(x) ((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
101 continent_country_menu(dialogMenuItem *continent)
104 struct continent *contp = continent->data;
106 int isocean = OCEANP(continent - continents);
109 /* Short cut -- if there's only one country, don't post a menu. */
110 if (contp->nitems == 1)
111 return (contp->menu[0].fire(&contp->menu[0]));
113 /* It's amazing how much good grammar really matters... */
115 snprintf(title, sizeof title, "Countries in %s",
118 snprintf(title, sizeof title, "Islands and groups in the %s",
121 menulen = contp->nitems < 16 ? contp->nitems : 16;
122 rv = dialog_menu(title, (isocean ? "Select an island or group"
123 : "Select a country or region"), -1, -1, menulen,
124 -contp->nitems, contp->menu, 0, &contp->ch,
127 return DITEM_LEAVE_MENU;
128 return DITEM_RECREATE;
131 static struct continent *
132 find_continent(const char *name)
136 for (i = 0; i < NCONTINENTS; i++) {
137 if (strcmp(name, continent_names[i].name) == 0)
138 return continent_names[i].continent;
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 */
154 TAILQ_ENTRY(zone) link;
157 struct continent *continent;
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.
165 #define NCOUNTRIES (26*26)
166 static struct country countries[NCOUNTRIES];
167 #define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A'))
170 * Read the ISO 3166 country code database in _PATH_ISO3166
171 * (/usr/share/misc/iso3166). On error, exit via err(3).
174 read_iso3166_table(void)
182 fp = fopen(_PATH_ISO3166, "r");
184 err(1, _PATH_ISO3166);
187 while ((s = fgetln(fp, &len)) != 0) {
189 if (s[len - 1] != '\n')
190 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
192 if (s[0] == '#' || strspn(s, " \t") == len - 1)
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'",
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);
213 cp = &countries[CODE2INT(t)];
215 errx(1, _PATH_ISO3166
216 ":%d: country code `%s' multiply defined: %s",
217 lineno, t, cp->name);
218 cp->name = strdup(name);
219 if (cp->name == NULL)
220 errx(1, "malloc failed");
223 errx(1, "malloc failed");
230 add_zone_to_country(int lineno, const char *tlc, const char *descr,
231 const char *file, struct continent *cont)
236 if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
237 errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid",
240 cp = &countries[CODE2INT(tlc)];
242 errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown",
247 errx(1, _PATH_ZONETAB
248 ":%d: conflicting zone definition", lineno);
250 zp = malloc(sizeof *zp);
252 errx(1, "malloc(%lu)", (unsigned long)sizeof *zp);
255 TAILQ_INIT(&cp->zones);
257 zp->descr = strdup(descr);
258 if (zp->descr == NULL)
259 errx(1, "malloc failed");
260 zp->filename = strdup(file);
261 if (zp->filename == NULL)
262 errx(1, "malloc failed");
263 zp->continent = cont;
264 TAILQ_INSERT_TAIL(&cp->zones, zp, link);
268 errx(1, _PATH_ZONETAB
269 ":%d: zone must have description", lineno);
271 errx(1, _PATH_ZONETAB
272 ":%d: zone multiply defined", lineno);
274 cp->filename = strdup(file);
275 if (cp->filename == NULL)
276 errx(1, "malloc failed");
277 cp->continent = cont;
282 * This comparison function intentionally sorts all of the null-named
283 * ``countries''---i.e., the codes that don't correspond to a real
284 * country---to the end. Everything else is lexical by country name.
287 compare_countries(const void *xa, const void *xb)
289 const struct country *a = xa, *b = xb;
291 if (a->name == 0 && b->name == 0)
293 if (a->name == 0 && b->name != 0)
298 return strcmp(a->name, b->name);
302 * This must be done AFTER all zone descriptions are read, since it breaks
308 qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
318 char *tlc, *coord, *file, *descr, *p;
320 struct continent *cont;
322 fp = fopen(_PATH_ZONETAB, "r");
324 err(1, _PATH_ZONETAB);
327 while ((line = fgetln(fp, &len)) != 0) {
329 if (line[len - 1] != '\n')
330 errx(1, _PATH_ZONETAB ":%d: invalid format", lineno);
331 line[len - 1] = '\0';
335 tlc = strsep(&line, "\t");
336 if (strlen(tlc) != 2)
337 errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'",
339 coord = strsep(&line, "\t");
340 file = strsep(&line, "\t");
341 p = strchr(file, '/');
343 errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'",
346 strncat(contbuf, file, p - file);
347 cont = find_continent(contbuf);
349 errx(1, _PATH_ZONETAB ":%d: invalid region `%s'",
352 descr = (line && *line) ? line : 0;
354 add_zone_to_country(lineno, tlc, descr, file, cont);
363 struct zone *zp, *zp2;
364 struct continent *cont;
369 * First, count up all the countries in each continent/ocean.
370 * Be careful to count those countries which have multiple zones
371 * only once for each. NB: some countries are in multiple
374 for (cp = countries; cp->name; cp++) {
377 if (cp->nzones < 0) {
378 cp->continent->nitems++;
380 TAILQ_FOREACH(zp, &cp->zones, link) {
381 cont = zp->continent;
382 for (zp2 = TAILQ_FIRST(&cp->zones);
383 zp2->continent != cont;
384 zp2 = TAILQ_NEXT(zp2, link))
387 zp->continent->nitems++;
393 * Now allocate memory for the country menus. We set
394 * nitems back to zero so that we can use it for counting
395 * again when we actually build the menus.
397 for (i = 0; i < NCONTINENTS; i++) {
398 continent_names[i].continent->menu =
399 malloc(sizeof(dialogMenuItem) *
400 continent_names[i].continent->nitems);
401 if (continent_names[i].continent->menu == 0)
402 errx(1, "malloc for continent menu");
403 continent_names[i].continent->nitems = 0;
407 * Now that memory is allocated, create the menu items for
408 * each continent. For multiple-zone countries, also create
409 * the country's zone submenu.
411 for (cp = countries; cp->name; cp++) {
414 if (cp->nzones < 0) {
415 dmi = &cp->continent->menu[cp->continent->nitems];
416 memset(dmi, 0, sizeof *dmi);
417 asprintf(&dmi->prompt, "%d",
418 ++cp->continent->nitems);
419 dmi->title = cp->name;
421 dmi->fire = set_zone_whole_country;
425 cp->submenu = malloc(cp->nzones * sizeof *dmi);
426 if (cp->submenu == 0)
427 errx(1, "malloc for submenu");
429 TAILQ_FOREACH(zp, &cp->zones, link) {
430 cont = zp->continent;
431 dmi = &cp->submenu[cp->nzones];
432 memset(dmi, 0, sizeof *dmi);
433 asprintf(&dmi->prompt, "%d",
435 dmi->title = zp->descr;
437 dmi->fire = set_zone_multi;
441 for (zp2 = TAILQ_FIRST(&cp->zones);
442 zp2->continent != cont;
443 zp2 = TAILQ_NEXT(zp2, link))
448 dmi = &cont->menu[cont->nitems];
449 memset(dmi, 0, sizeof *dmi);
450 asprintf(&dmi->prompt, "%d", ++cont->nitems);
451 dmi->title = cp->name;
453 dmi->fire = set_zone_menu;
462 set_zone_menu(dialogMenuItem *dmi)
466 struct country *cp = dmi->data;
469 snprintf(buf, sizeof buf, "%s Time Zones", cp->name);
470 menulen = cp->nzones < 16 ? cp->nzones : 16;
471 rv = dialog_menu(buf, "Select a zone which observes the same time as "
472 "your locality.", -1, -1, menulen, -cp->nzones,
473 cp->submenu, 0, 0, 0);
475 return DITEM_RECREATE;
476 return DITEM_LEAVE_MENU;
480 install_zone_file(const char *filename)
489 if (lstat(_PATH_LOCALTIME, &sb) < 0)
490 /* Nothing there yet... */
492 else if(S_ISLNK(sb.st_mode))
499 asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename);
501 asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME
510 fd1 = open(filename, O_RDONLY, 0);
512 asprintf(&msg, "Could not open %s: %s",
513 filename, strerror(errno));
514 dialog_mesgbox("Error", msg, 8, 72);
516 return DITEM_FAILURE | DITEM_RECREATE;
519 unlink(_PATH_LOCALTIME);
520 fd2 = open(_PATH_LOCALTIME,
521 O_CREAT|O_EXCL|O_WRONLY,
522 S_IRUSR|S_IRGRP|S_IROTH);
524 asprintf(&msg, "Could not open "
525 _PATH_LOCALTIME ": %s",
527 dialog_mesgbox("Error", msg, 8, 72);
529 return DITEM_FAILURE | DITEM_RECREATE;
532 while ((len = read(fd1, buf, sizeof buf)) > 0)
533 len = write(fd2, buf, len);
536 asprintf(&msg, "Error copying %s to "
537 _PATH_LOCALTIME ": %s",
538 filename, strerror(errno));
539 dialog_mesgbox("Error", msg, 8, 72);
541 /* Better to leave none than a corrupt one. */
542 unlink(_PATH_LOCALTIME);
543 return DITEM_FAILURE | DITEM_RECREATE;
548 if (access(filename, R_OK) != 0) {
549 asprintf(&msg, "Cannot access %s: %s",
550 filename, strerror(errno));
551 dialog_mesgbox("Error", msg, 8, 72);
553 return DITEM_FAILURE | DITEM_RECREATE;
555 unlink(_PATH_LOCALTIME);
556 if (symlink(filename, _PATH_LOCALTIME) < 0) {
557 asprintf(&msg, "Cannot create symbolic link "
558 _PATH_LOCALTIME " to %s: %s",
559 filename, strerror(errno));
560 dialog_mesgbox("Error", msg, 8, 72);
562 return DITEM_FAILURE | DITEM_RECREATE;
569 asprintf(&msg, "Copied timezone file from %s to "
570 _PATH_LOCALTIME, filename);
572 asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME
575 dialog_mesgbox("Done", msg, 8, 72);
578 return DITEM_LEAVE_MENU;
582 confirm_zone(const char *filename)
589 setenv("TZ", filename, 1);
593 asprintf(&msg, "Does the abbreviation `%s' look reasonable?",
595 rv = !dialog_yesno("Confirmation", msg, 5, 72);
601 set_zone_multi(dialogMenuItem *dmi)
604 struct zone *zp = dmi->data;
607 if (!confirm_zone(zp->filename))
608 return DITEM_FAILURE | DITEM_RECREATE;
610 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename);
611 rv = install_zone_file(fn);
617 set_zone_whole_country(dialogMenuItem *dmi)
620 struct country *cp = dmi->data;
623 if (!confirm_zone(cp->filename))
624 return DITEM_FAILURE | DITEM_RECREATE;
626 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename);
627 rv = install_zone_file(fn);
635 fprintf(stderr, "usage: tzsetup [-n]\n");
640 main(int argc, char **argv)
643 int (*dialog_utc)(unsigned char *, unsigned char *, int, int);
645 #if defined(__alpha__) || defined(__sparc64__)
646 dialog_utc = dialog_yesno;
648 dialog_utc = dialog_noyes;
651 while ((c = getopt(argc, argv, "n")) != -1) {
662 if (argc - optind > 1)
665 /* Override the user-supplied umask. */
666 (void)umask(S_IWGRP|S_IWOTH);
668 read_iso3166_table();
674 if (!dialog_utc("Select local or UTC (Greenwich Mean Time) clock",
675 "Is this machine's CMOS clock set to UTC? If it is set to local time,\n"
676 "or you don't know, please choose NO here!", 7, 72)) {
678 unlink(_PATH_WALL_CMOS_CLOCK);
681 fd = open(_PATH_WALL_CMOS_CLOCK,
682 O_WRONLY|O_CREAT|O_TRUNC,
683 S_IRUSR|S_IRGRP|S_IROTH);
685 err(1, "create %s", _PATH_WALL_CMOS_CLOCK);
689 dialog_clear_norefresh();
690 if (optind == argc - 1) {
692 asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]);
693 if (!dialog_yesno("Default timezone provided", msg, 7, 72)) {
694 install_zone_file(argv[optind]);
700 dialog_clear_norefresh();
702 dialog_menu("Time Zone Selector", "Select a region", -1, -1,
703 NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);