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 set_zone_menu(&contp->menu[0]);
114 /* It's amazing how much good grammar really matters... */
116 snprintf(title, sizeof title, "Countries in %s",
119 snprintf(title, sizeof title, "Islands and groups in the %s",
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,
128 return DITEM_LEAVE_MENU;
129 return DITEM_RECREATE;
132 static struct continent *
133 find_continent(const char *name)
137 for (i = 0; i < NCONTINENTS; i++) {
138 if (strcmp(name, continent_names[i].name) == 0)
139 return continent_names[i].continent;
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 */
155 TAILQ_ENTRY(zone) link;
158 struct continent *continent;
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.
166 #define NCOUNTRIES (26*26)
167 static struct country countries[NCOUNTRIES];
168 #define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A'))
171 * Read the ISO 3166 country code database in _PATH_ISO3166
172 * (/usr/share/misc/iso3166). On error, exit via err(3).
175 read_iso3166_table(void)
183 fp = fopen(_PATH_ISO3166, "r");
185 err(1, _PATH_ISO3166);
188 while ((s = fgetln(fp, &len)) != 0) {
190 if (s[len - 1] != '\n')
191 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
193 if (s[0] == '#' || strspn(s, " \t") == len - 1)
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'",
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);
214 cp = &countries[CODE2INT(t)];
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");
224 errx(1, "malloc failed");
231 add_zone_to_country(int lineno, const char *tlc, const char *descr,
232 const char *file, struct continent *cont)
237 if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
238 errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid",
241 cp = &countries[CODE2INT(tlc)];
243 errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown",
248 errx(1, _PATH_ZONETAB
249 ":%d: conflicting zone definition", lineno);
251 zp = malloc(sizeof *zp);
253 errx(1, "malloc(%lu)", (unsigned long)sizeof *zp);
256 TAILQ_INIT(&cp->zones);
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);
269 errx(1, _PATH_ZONETAB
270 ":%d: zone must have description", lineno);
272 errx(1, _PATH_ZONETAB
273 ":%d: zone multiply defined", lineno);
275 cp->filename = strdup(file);
276 if (cp->filename == NULL)
277 errx(1, "malloc failed");
278 cp->continent = cont;
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.
288 compare_countries(const void *xa, const void *xb)
290 const struct country *a = xa, *b = xb;
292 if (a->name == 0 && b->name == 0)
294 if (a->name == 0 && b->name != 0)
299 return strcmp(a->name, b->name);
303 * This must be done AFTER all zone descriptions are read, since it breaks
309 qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
319 char *tlc, *coord, *file, *descr, *p;
321 struct continent *cont;
323 fp = fopen(_PATH_ZONETAB, "r");
325 err(1, _PATH_ZONETAB);
328 while ((line = fgetln(fp, &len)) != 0) {
330 if (line[len - 1] != '\n')
331 errx(1, _PATH_ZONETAB ":%d: invalid format", lineno);
332 line[len - 1] = '\0';
336 tlc = strsep(&line, "\t");
337 if (strlen(tlc) != 2)
338 errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'",
340 coord = strsep(&line, "\t");
341 file = strsep(&line, "\t");
342 p = strchr(file, '/');
344 errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'",
347 strncat(contbuf, file, p - file);
348 cont = find_continent(contbuf);
350 errx(1, _PATH_ZONETAB ":%d: invalid region `%s'",
353 descr = (line && *line) ? line : 0;
355 add_zone_to_country(lineno, tlc, descr, file, cont);
364 struct zone *zp, *zp2;
365 struct continent *cont;
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
375 for (cp = countries; cp->name; cp++) {
378 if (cp->nzones < 0) {
379 cp->continent->nitems++;
381 TAILQ_FOREACH(zp, &cp->zones, link) {
382 cont = zp->continent;
383 for (zp2 = TAILQ_FIRST(&cp->zones);
384 zp2->continent != cont;
385 zp2 = TAILQ_NEXT(zp2, link))
388 zp->continent->nitems++;
394 * Now allocate memory for the country menus. We set
395 * nitems back to zero so that we can use it for counting
396 * again when we actually build the menus.
398 for (i = 0; i < NCONTINENTS; i++) {
399 continent_names[i].continent->menu =
400 malloc(sizeof(dialogMenuItem) *
401 continent_names[i].continent->nitems);
402 if (continent_names[i].continent->menu == 0)
403 errx(1, "malloc for continent menu");
404 continent_names[i].continent->nitems = 0;
408 * Now that memory is allocated, create the menu items for
409 * each continent. For multiple-zone countries, also create
410 * the country's zone submenu.
412 for (cp = countries; cp->name; cp++) {
415 if (cp->nzones < 0) {
416 dmi = &cp->continent->menu[cp->continent->nitems];
417 memset(dmi, 0, sizeof *dmi);
418 asprintf(&dmi->prompt, "%d",
419 ++cp->continent->nitems);
420 dmi->title = cp->name;
422 dmi->fire = set_zone_whole_country;
426 cp->submenu = malloc(cp->nzones * sizeof *dmi);
427 if (cp->submenu == 0)
428 errx(1, "malloc for submenu");
430 TAILQ_FOREACH(zp, &cp->zones, link) {
431 cont = zp->continent;
432 dmi = &cp->submenu[cp->nzones];
433 memset(dmi, 0, sizeof *dmi);
434 asprintf(&dmi->prompt, "%d",
436 dmi->title = zp->descr;
438 dmi->fire = set_zone_multi;
442 for (zp2 = TAILQ_FIRST(&cp->zones);
443 zp2->continent != cont;
444 zp2 = TAILQ_NEXT(zp2, link))
449 dmi = &cont->menu[cont->nitems];
450 memset(dmi, 0, sizeof *dmi);
451 asprintf(&dmi->prompt, "%d", ++cont->nitems);
452 dmi->title = cp->name;
454 dmi->fire = set_zone_menu;
463 set_zone_menu(dialogMenuItem *dmi)
467 struct country *cp = dmi->data;
470 snprintf(buf, sizeof buf, "%s Time Zones", cp->name);
471 menulen = cp->nzones < 16 ? cp->nzones : 16;
472 rv = dialog_menu(buf, "Select a zone which observes the same time as "
473 "your locality.", -1, -1, menulen, -cp->nzones,
474 cp->submenu, 0, 0, 0);
476 return DITEM_RECREATE;
477 return DITEM_LEAVE_MENU;
481 install_zone_file(const char *filename)
490 if (lstat(_PATH_LOCALTIME, &sb) < 0)
491 /* Nothing there yet... */
493 else if(S_ISLNK(sb.st_mode))
500 asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename);
502 asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME
511 fd1 = open(filename, O_RDONLY, 0);
513 asprintf(&msg, "Could not open %s: %s",
514 filename, strerror(errno));
515 dialog_mesgbox("Error", msg, 8, 72);
517 return DITEM_FAILURE | DITEM_RECREATE;
520 unlink(_PATH_LOCALTIME);
521 fd2 = open(_PATH_LOCALTIME,
522 O_CREAT|O_EXCL|O_WRONLY,
523 S_IRUSR|S_IRGRP|S_IROTH);
525 asprintf(&msg, "Could not open "
526 _PATH_LOCALTIME ": %s",
528 dialog_mesgbox("Error", msg, 8, 72);
530 return DITEM_FAILURE | DITEM_RECREATE;
533 while ((len = read(fd1, buf, sizeof buf)) > 0)
534 len = write(fd2, buf, len);
537 asprintf(&msg, "Error copying %s to "
538 _PATH_LOCALTIME ": %s",
539 filename, strerror(errno));
540 dialog_mesgbox("Error", msg, 8, 72);
542 /* Better to leave none than a corrupt one. */
543 unlink(_PATH_LOCALTIME);
544 return DITEM_FAILURE | DITEM_RECREATE;
549 if (access(filename, R_OK) != 0) {
550 asprintf(&msg, "Cannot access %s: %s",
551 filename, strerror(errno));
552 dialog_mesgbox("Error", msg, 8, 72);
554 return DITEM_FAILURE | DITEM_RECREATE;
556 unlink(_PATH_LOCALTIME);
557 if (symlink(filename, _PATH_LOCALTIME) < 0) {
558 asprintf(&msg, "Cannot create symbolic link "
559 _PATH_LOCALTIME " to %s: %s",
560 filename, strerror(errno));
561 dialog_mesgbox("Error", msg, 8, 72);
563 return DITEM_FAILURE | DITEM_RECREATE;
570 asprintf(&msg, "Copied timezone file from %s to "
571 _PATH_LOCALTIME, filename);
573 asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME
576 dialog_mesgbox("Done", msg, 8, 72);
579 return DITEM_LEAVE_MENU;
583 confirm_zone(const char *filename)
590 setenv("TZ", filename, 1);
594 asprintf(&msg, "Does the abbreviation `%s' look reasonable?",
596 rv = !dialog_yesno("Confirmation", msg, 4, 72);
602 set_zone_multi(dialogMenuItem *dmi)
605 struct zone *zp = dmi->data;
608 if (!confirm_zone(zp->filename))
609 return DITEM_FAILURE | DITEM_RECREATE;
611 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename);
612 rv = install_zone_file(fn);
618 set_zone_whole_country(dialogMenuItem *dmi)
621 struct country *cp = dmi->data;
624 if (!confirm_zone(cp->filename))
625 return DITEM_FAILURE | DITEM_RECREATE;
627 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename);
628 rv = install_zone_file(fn);
636 fprintf(stderr, "usage: tzsetup [-n]\n");
641 main(int argc, char **argv)
644 int (*dialog_utc)(unsigned char *, unsigned char *, int, int);
646 #if defined(__alpha__) || defined(__sparc64__)
647 dialog_utc = dialog_yesno;
649 dialog_utc = dialog_noyes;
652 while ((c = getopt(argc, argv, "n")) != -1) {
663 if (argc - optind > 1)
666 /* Override the user-supplied umask. */
667 (void)umask(S_IWGRP|S_IWOTH);
669 read_iso3166_table();
675 if (!dialog_utc("Select local or UTC (Greenwich Mean Time) clock",
676 "Is this machine's CMOS clock set to UTC? If it is set to local time,\n"
677 "or you don't know, please choose NO here!", 7, 72)) {
679 unlink(_PATH_WALL_CMOS_CLOCK);
682 fd = open(_PATH_WALL_CMOS_CLOCK,
683 O_WRONLY|O_CREAT|O_TRUNC,
684 S_IRUSR|S_IRGRP|S_IROTH);
686 err(1, "create %s", _PATH_WALL_CMOS_CLOCK);
690 dialog_clear_norefresh();
691 if (optind == argc - 1) {
693 asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]);
694 if (!dialog_yesno("Default timezone provided", msg, 7, 72)) {
695 install_zone_file(argv[optind]);
701 dialog_clear_norefresh();
703 dialog_menu("Time Zone Selector", "Select a region", -1, -1,
704 NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);