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[] =
37 "$FreeBSD: src/usr.sbin/tzsetup/tzsetup.c,v 1.16.2.2 2002/03/06 06:17:41 obrien Exp $";
40 #include <sys/types.h>
49 #include <sys/fcntl.h>
50 #include <sys/queue.h>
55 static int reallydoit = 1;
57 static int continent_country_menu(dialogMenuItem *);
58 static int set_zone_multi(dialogMenuItem *);
59 static int set_zone_whole_country(dialogMenuItem *);
60 static int set_zone_menu(dialogMenuItem *);
69 static struct continent africa, america, antarctica, arctic, asia, atlantic;
70 static struct continent australia, europe, indian, pacific;
72 static struct continent_names {
74 struct continent *continent;
75 } continent_names[] = {
76 { "Africa", &africa }, { "America", &america },
77 { "Antarctica", &antarctica }, { "Arctic", &arctic },
79 { "Atlantic", &atlantic }, { "Australia", &australia },
80 { "Europe", &europe }, { "Indian", &indian }, { "Pacific", &pacific }
83 static dialogMenuItem continents[] = {
84 { "1", "Africa", 0, continent_country_menu, 0, &africa },
85 { "2", "America -- North and South", 0, continent_country_menu, 0,
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 }
96 #define NCONTINENTS ((sizeof continents)/(sizeof continents[0]))
97 #define OCEANP(x) ((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
100 continent_country_menu(dialogMenuItem *continent)
103 struct continent *contp = continent->data;
105 int isocean = OCEANP(continent - continents);
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]);
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"), -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 for (zp = cp->zones.tqh_first; zp;
381 zp = zp->link.tqe_next) {
382 cont = zp->continent;
383 for (zp2 = cp->zones.tqh_first;
384 zp2->continent != cont;
385 zp2 = zp2->link.tqe_next)
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 for (zp = cp->zones.tqh_first; zp;
431 zp = zp->link.tqe_next) {
432 cont = zp->continent;
433 dmi = &cp->submenu[cp->nzones];
434 memset(dmi, 0, sizeof *dmi);
435 asprintf(&dmi->prompt, "%d",
437 dmi->title = zp->descr;
439 dmi->fire = set_zone_multi;
443 for (zp2 = cp->zones.tqh_first;
444 zp2->continent != cont;
445 zp2 = zp2->link.tqe_next)
450 dmi = &cont->menu[cont->nitems];
451 memset(dmi, 0, sizeof *dmi);
452 asprintf(&dmi->prompt, "%d", ++cont->nitems);
453 dmi->title = cp->name;
455 dmi->fire = set_zone_menu;
464 set_zone_menu(dialogMenuItem *dmi)
468 struct country *cp = dmi->data;
471 snprintf(buf, sizeof buf, "%s Time Zones", cp->name);
472 menulen = cp->nzones < 16 ? cp->nzones : 16;
473 rv = dialog_menu(buf, "Select a zone which observes the same time as "
474 "your locality.", -1, -1, menulen, -cp->nzones,
475 cp->submenu, 0, 0, 0);
477 return DITEM_RECREATE;
478 return DITEM_LEAVE_MENU;
482 install_zone_file(const char *filename)
491 if (lstat(_PATH_LOCALTIME, &sb) < 0)
492 /* Nothing there yet... */
494 else if(S_ISLNK(sb.st_mode))
501 asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename);
503 asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME
512 fd1 = open(filename, O_RDONLY, 0);
514 asprintf(&msg, "Could not open %s: %s",
515 filename, strerror(errno));
516 dialog_mesgbox("Error", msg, 8, 72);
518 return DITEM_FAILURE | DITEM_RECREATE;
521 unlink(_PATH_LOCALTIME);
522 fd2 = open(_PATH_LOCALTIME,
523 O_CREAT|O_EXCL|O_WRONLY,
524 S_IRUSR|S_IRGRP|S_IROTH);
526 asprintf(&msg, "Could not open "
527 _PATH_LOCALTIME ": %s",
529 dialog_mesgbox("Error", msg, 8, 72);
531 return DITEM_FAILURE | DITEM_RECREATE;
534 while ((len = read(fd1, buf, sizeof buf)) > 0)
535 len = write(fd2, buf, len);
538 asprintf(&msg, "Error copying %s to "
539 _PATH_LOCALTIME ": %s",
540 filename, strerror(errno));
541 dialog_mesgbox("Error", msg, 8, 72);
543 /* Better to leave none than a corrupt one. */
544 unlink(_PATH_LOCALTIME);
545 return DITEM_FAILURE | DITEM_RECREATE;
550 if (access(filename, R_OK) != 0) {
551 asprintf(&msg, "Cannot access %s: %s",
552 filename, strerror(errno));
553 dialog_mesgbox("Error", msg, 8, 72);
555 return DITEM_FAILURE | DITEM_RECREATE;
557 unlink(_PATH_LOCALTIME);
558 if (symlink(filename, _PATH_LOCALTIME) < 0) {
559 asprintf(&msg, "Cannot create symbolic link "
560 _PATH_LOCALTIME " to %s: %s",
561 filename, strerror(errno));
562 dialog_mesgbox("Error", msg, 8, 72);
564 return DITEM_FAILURE | DITEM_RECREATE;
571 asprintf(&msg, "Copied timezone file from %s to "
572 _PATH_LOCALTIME, filename);
574 asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME
577 dialog_mesgbox("Done", msg, 8, 72);
580 return DITEM_LEAVE_MENU;
584 confirm_zone(const char *filename)
591 setenv("TZ", filename, 1);
595 asprintf(&msg, "Does the abbreviation `%s' look reasonable?",
597 rv = !dialog_yesno("Confirmation", msg, 4, 72);
603 set_zone_multi(dialogMenuItem *dmi)
606 struct zone *zp = dmi->data;
609 if (!confirm_zone(zp->filename))
610 return DITEM_FAILURE | DITEM_RECREATE;
612 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename);
613 rv = install_zone_file(fn);
619 set_zone_whole_country(dialogMenuItem *dmi)
622 struct country *cp = dmi->data;
625 if (!confirm_zone(cp->filename))
626 return DITEM_FAILURE | DITEM_RECREATE;
628 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename);
629 rv = install_zone_file(fn);
637 fprintf(stderr, "usage: tzsetup [-n]\n");
642 main(int argc, char **argv)
645 int (*dialog_utc)(unsigned char *, unsigned char *, int, int);
647 #if defined(__alpha__)
648 dialog_utc = dialog_yesno;
650 dialog_utc = dialog_noyes;
653 while ((c = getopt(argc, argv, "n")) != -1) {
664 if (argc - optind > 1)
667 /* Override the user-supplied umask. */
668 (void)umask(S_IWGRP|S_IWOTH);
670 read_iso3166_table();
676 if (!dialog_utc("Select local or UTC (Greenwich Mean Time) clock",
677 "Is this machine's CMOS clock set to UTC? If it is set to local time,\n"
678 "or you don't know, please choose NO here!", 7, 72)) {
680 unlink(_PATH_WALL_CMOS_CLOCK);
683 fd = open(_PATH_WALL_CMOS_CLOCK,
684 O_WRONLY|O_CREAT|O_TRUNC,
685 S_IRUSR|S_IRGRP|S_IROTH);
687 err(1, "create %s", _PATH_WALL_CMOS_CLOCK);
691 dialog_clear_norefresh();
692 if (optind == argc - 1) {
694 asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]);
695 if (!dialog_yesno("Default timezone provided", msg, 7, 72)) {
696 install_zone_file(argv[optind]);
702 dialog_clear_norefresh();
704 dialog_menu("Time Zone Selector", "Select a region", -1, -1,
705 NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);