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
29 * $FreeBSD: src/usr.sbin/tzsetup/tzsetup.c,v 1.16.2.2 2002/03/06 06:17:41 obrien Exp $
30 * $DragonFly: src/usr.sbin/tzsetup/tzsetup.c,v 1.8 2008/06/03 09:33:27 swildner Exp $
34 * Second attempt at a `tzmenu' program, using the separate description
35 * files provided in newer tzdata releases.
38 #include <sys/types.h>
47 #include <sys/fcntl.h>
48 #include <sys/queue.h>
53 static int reallydoit = 1;
55 static int continent_country_menu(dialogMenuItem *);
56 static int set_zone_multi(dialogMenuItem *);
57 static int set_zone_whole_country(dialogMenuItem *);
58 static int set_zone_menu(dialogMenuItem *);
67 static struct continent africa, america, antarctica, arctic, asia, atlantic;
68 static struct continent australia, europe, indian, pacific;
70 static struct continent_names {
72 struct continent *continent;
73 } continent_names[] = {
74 { "Africa", &africa }, { "America", &america },
75 { "Antarctica", &antarctica }, { "Arctic", &arctic },
77 { "Atlantic", &atlantic }, { "Australia", &australia },
78 { "Europe", &europe }, { "Indian", &indian }, { "Pacific", &pacific }
81 static dialogMenuItem continents[] = {
82 { "1", "Africa", 0, continent_country_menu, 0, &africa },
83 { "2", "America -- North and South", 0, continent_country_menu, 0,
85 { "3", "Antarctica", 0, continent_country_menu, 0, &antarctica },
86 { "4", "Arctic Ocean", 0, continent_country_menu, 0, &arctic },
87 { "5", "Asia", 0, continent_country_menu, 0, &asia },
88 { "6", "Atlantic Ocean", 0, continent_country_menu, 0, &atlantic },
89 { "7", "Australia", 0, continent_country_menu, 0, &australia },
90 { "8", "Europe", 0, continent_country_menu, 0, &europe },
91 { "9", "Indian Ocean", 0, continent_country_menu, 0, &indian },
92 { "0", "Pacific Ocean", 0, continent_country_menu, 0, &pacific }
94 #define NCONTINENTS ((sizeof continents)/(sizeof continents[0]))
95 #define OCEANP(x) ((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
98 continent_country_menu(dialogMenuItem *continent)
101 struct continent *contp = continent->data;
103 int isocean = OCEANP(continent - continents);
106 /* Short cut -- if there's only one country, don't post a menu. */
107 if (contp->nitems == 1)
108 return (contp->menu[0].fire(&contp->menu[0]));
110 /* It's amazing how much good grammar really matters... */
112 snprintf(title, sizeof title, "Countries in %s",
115 snprintf(title, sizeof title, "Islands and groups in the %s",
118 menulen = contp->nitems < 16 ? contp->nitems : 16;
119 rv = dialog_menu(title, (isocean ? "Select an island or group"
120 : "Select a country"), -1, -1, menulen,
121 -contp->nitems, contp->menu, 0, &contp->ch,
124 return DITEM_LEAVE_MENU;
125 return DITEM_RECREATE;
128 static struct continent *
129 find_continent(const char *name)
133 for (i = 0; i < NCONTINENTS; i++) {
134 if (strcmp(name, continent_names[i].name) == 0)
135 return continent_names[i].continent;
144 char *filename; /* use iff nzones < 0 */
145 struct continent *continent; /* use iff nzones < 0 */
146 TAILQ_HEAD(, zone) zones; /* use iff nzones > 0 */
147 dialogMenuItem *submenu; /* use iff nzones > 0 */
151 TAILQ_ENTRY(zone) link;
154 struct continent *continent;
158 * This is the easiest organization... we use ISO 3166 country codes,
159 * of the two-letter variety, so we just size this array to suit.
160 * Beats worrying about dynamic allocation.
162 #define NCOUNTRIES (26*26)
163 static struct country countries[NCOUNTRIES];
164 #define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A'))
167 * Read the ISO 3166 country code database in _PATH_ISO3166
168 * (/usr/share/misc/iso3166). On error, exit via err(3).
171 read_iso3166_table(void)
179 fp = fopen(_PATH_ISO3166, "r");
181 err(1, _PATH_ISO3166);
184 while ((s = fgetln(fp, &len)) != 0) {
186 if (s[len - 1] != '\n')
187 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
189 if (s[0] == '#' || strspn(s, " \t") == len - 1)
192 /* Isolate the two-letter code. */
193 t = strsep(&s, "\t");
194 if (t == 0 || strlen(t) != 2)
195 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
196 if (t[0] < 'A' || t[0] > 'Z' || t[1] < 'A' || t[1] > 'Z')
197 errx(1, _PATH_ISO3166 ":%d: invalid code `%s'",
202 cp = &countries[CODE2INT(t)];
204 errx(1, _PATH_ISO3166
205 ":%d: country code `%s' multiply defined: %s",
206 lineno, t, cp->name);
207 cp->name = strdup(name);
208 if (cp->name == NULL)
209 errx(1, "malloc failed");
212 errx(1, "malloc failed");
219 add_zone_to_country(int lineno, const char *tlc, const char *descr,
220 const char *file, struct continent *cont)
225 if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
226 errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid",
229 cp = &countries[CODE2INT(tlc)];
231 errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown",
236 errx(1, _PATH_ZONETAB
237 ":%d: conflicting zone definition", lineno);
239 zp = malloc(sizeof *zp);
241 errx(1, "malloc(%lu)", (unsigned long)sizeof *zp);
244 TAILQ_INIT(&cp->zones);
246 zp->descr = strdup(descr);
247 if (zp->descr == NULL)
248 errx(1, "malloc failed");
249 zp->filename = strdup(file);
250 if (zp->filename == NULL)
251 errx(1, "malloc failed");
252 zp->continent = cont;
253 TAILQ_INSERT_TAIL(&cp->zones, zp, link);
257 errx(1, _PATH_ZONETAB
258 ":%d: zone must have description", lineno);
260 errx(1, _PATH_ZONETAB
261 ":%d: zone multiply defined", lineno);
263 cp->filename = strdup(file);
264 if (cp->filename == NULL)
265 errx(1, "malloc failed");
266 cp->continent = cont;
271 * This comparison function intentionally sorts all of the null-named
272 * ``countries''---i.e., the codes that don't correspond to a real
273 * country---to the end. Everything else is lexical by country name.
276 compare_countries(const void *xa, const void *xb)
278 const struct country *a = xa, *b = xb;
280 if (a->name == 0 && b->name == 0)
282 if (a->name == 0 && b->name != 0)
287 return strcmp(a->name, b->name);
291 * This must be done AFTER all zone descriptions are read, since it breaks
297 qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
307 char *tlc, *coord, *file, *descr, *p;
309 struct continent *cont;
311 fp = fopen(_PATH_ZONETAB, "r");
313 err(1, _PATH_ZONETAB);
316 while ((line = fgetln(fp, &len)) != 0) {
318 if (line[len - 1] != '\n')
319 errx(1, _PATH_ZONETAB ":%d: invalid format", lineno);
320 line[len - 1] = '\0';
324 tlc = strsep(&line, "\t");
325 if (strlen(tlc) != 2)
326 errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'",
328 coord = strsep(&line, "\t");
329 file = strsep(&line, "\t");
330 p = strchr(file, '/');
332 errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'",
335 strncat(contbuf, file, p - file);
336 cont = find_continent(contbuf);
338 errx(1, _PATH_ZONETAB ":%d: invalid region `%s'",
341 descr = (line && *line) ? line : 0;
343 add_zone_to_country(lineno, tlc, descr, file, cont);
352 struct zone *zp, *zp2;
353 struct continent *cont;
358 * First, count up all the countries in each continent/ocean.
359 * Be careful to count those countries which have multiple zones
360 * only once for each. NB: some countries are in multiple
363 for (cp = countries; cp->name; cp++) {
366 if (cp->nzones < 0) {
367 cp->continent->nitems++;
369 for (zp = cp->zones.tqh_first; zp;
370 zp = zp->link.tqe_next) {
371 cont = zp->continent;
372 for (zp2 = cp->zones.tqh_first;
373 zp2->continent != cont;
374 zp2 = zp2->link.tqe_next)
377 zp->continent->nitems++;
383 * Now allocate memory for the country menus. We set
384 * nitems back to zero so that we can use it for counting
385 * again when we actually build the menus.
387 for (i = 0; i < NCONTINENTS; i++) {
388 continent_names[i].continent->menu =
389 malloc(sizeof(dialogMenuItem) *
390 continent_names[i].continent->nitems);
391 if (continent_names[i].continent->menu == 0)
392 errx(1, "malloc for continent menu");
393 continent_names[i].continent->nitems = 0;
397 * Now that memory is allocated, create the menu items for
398 * each continent. For multiple-zone countries, also create
399 * the country's zone submenu.
401 for (cp = countries; cp->name; cp++) {
404 if (cp->nzones < 0) {
405 dmi = &cp->continent->menu[cp->continent->nitems];
406 memset(dmi, 0, sizeof *dmi);
407 asprintf(&dmi->prompt, "%d",
408 ++cp->continent->nitems);
409 dmi->title = cp->name;
411 dmi->fire = set_zone_whole_country;
415 cp->submenu = malloc(cp->nzones * sizeof *dmi);
416 if (cp->submenu == 0)
417 errx(1, "malloc for submenu");
419 for (zp = cp->zones.tqh_first; zp;
420 zp = zp->link.tqe_next) {
421 cont = zp->continent;
422 dmi = &cp->submenu[cp->nzones];
423 memset(dmi, 0, sizeof *dmi);
424 asprintf(&dmi->prompt, "%d",
426 dmi->title = zp->descr;
428 dmi->fire = set_zone_multi;
432 for (zp2 = cp->zones.tqh_first;
433 zp2->continent != cont;
434 zp2 = zp2->link.tqe_next)
439 dmi = &cont->menu[cont->nitems];
440 memset(dmi, 0, sizeof *dmi);
441 asprintf(&dmi->prompt, "%d", ++cont->nitems);
442 dmi->title = cp->name;
444 dmi->fire = set_zone_menu;
453 set_zone_menu(dialogMenuItem *dmi)
457 struct country *cp = dmi->data;
460 snprintf(buf, sizeof buf, "%s Time Zones", cp->name);
461 menulen = cp->nzones < 16 ? cp->nzones : 16;
462 rv = dialog_menu(buf, "Select a zone which observes the same time as "
463 "your locality.", -1, -1, menulen, -cp->nzones,
464 cp->submenu, 0, 0, 0);
466 return DITEM_RECREATE;
467 return DITEM_LEAVE_MENU;
471 install_zone_file(const char *filename)
480 if (lstat(_PATH_LOCALTIME, &sb) < 0)
481 /* Nothing there yet... */
483 else if(S_ISLNK(sb.st_mode))
490 asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename);
492 asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME
501 fd1 = open(filename, O_RDONLY, 0);
503 asprintf(&msg, "Could not open %s: %s",
504 filename, strerror(errno));
505 dialog_mesgbox("Error", msg, 8, 72);
507 return DITEM_FAILURE | DITEM_RECREATE;
510 unlink(_PATH_LOCALTIME);
511 fd2 = open(_PATH_LOCALTIME,
512 O_CREAT|O_EXCL|O_WRONLY,
513 S_IRUSR|S_IRGRP|S_IROTH);
515 asprintf(&msg, "Could not open "
516 _PATH_LOCALTIME ": %s",
518 dialog_mesgbox("Error", msg, 8, 72);
520 return DITEM_FAILURE | DITEM_RECREATE;
523 while ((len = read(fd1, buf, sizeof buf)) > 0)
524 len = write(fd2, buf, len);
527 asprintf(&msg, "Error copying %s to "
528 _PATH_LOCALTIME ": %s",
529 filename, strerror(errno));
530 dialog_mesgbox("Error", msg, 8, 72);
532 /* Better to leave none than a corrupt one. */
533 unlink(_PATH_LOCALTIME);
534 return DITEM_FAILURE | DITEM_RECREATE;
539 if (access(filename, R_OK) != 0) {
540 asprintf(&msg, "Cannot access %s: %s",
541 filename, strerror(errno));
542 dialog_mesgbox("Error", msg, 8, 72);
544 return DITEM_FAILURE | DITEM_RECREATE;
546 unlink(_PATH_LOCALTIME);
547 if (symlink(filename, _PATH_LOCALTIME) < 0) {
548 asprintf(&msg, "Cannot create symbolic link "
549 _PATH_LOCALTIME " to %s: %s",
550 filename, strerror(errno));
551 dialog_mesgbox("Error", msg, 8, 72);
553 return DITEM_FAILURE | DITEM_RECREATE;
560 asprintf(&msg, "Copied timezone file from %s to "
561 _PATH_LOCALTIME, filename);
563 asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME
566 dialog_mesgbox("Done", msg, 8, 72);
569 return DITEM_LEAVE_MENU;
573 confirm_zone(const char *filename)
580 if (setenv("TZ", filename, 1) == -1)
581 err(1, "setenv: cannot set TZ=%s", filename);
585 asprintf(&msg, "Does the abbreviation `%s' look reasonable?",
587 rv = !dialog_yesno("Confirmation", msg, 4, 72);
593 set_zone_multi(dialogMenuItem *dmi)
596 struct zone *zp = dmi->data;
599 if (!confirm_zone(zp->filename))
600 return DITEM_FAILURE | DITEM_RECREATE;
602 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename);
603 rv = install_zone_file(fn);
609 set_zone_whole_country(dialogMenuItem *dmi)
612 struct country *cp = dmi->data;
615 if (!confirm_zone(cp->filename))
616 return DITEM_FAILURE | DITEM_RECREATE;
618 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename);
619 rv = install_zone_file(fn);
627 fprintf(stderr, "usage: tzsetup [-n]\n");
632 main(int argc, char **argv)
635 int (*dialog_utc)(unsigned char *, unsigned char *, int, int);
637 dialog_utc = dialog_noyes;
639 while ((c = getopt(argc, argv, "n")) != -1) {
650 if (argc - optind > 1)
653 /* Override the user-supplied umask. */
654 umask(S_IWGRP|S_IWOTH);
656 read_iso3166_table();
662 if (!dialog_utc("Select local or UTC (Greenwich Mean Time) clock",
663 "Is this machine's CMOS clock set to UTC? If it is set to local time,\n"
664 "or you don't know, please choose NO here!", 7, 72)) {
666 unlink(_PATH_WALL_CMOS_CLOCK);
669 fd = open(_PATH_WALL_CMOS_CLOCK,
670 O_WRONLY|O_CREAT|O_TRUNC,
671 S_IRUSR|S_IRGRP|S_IROTH);
673 err(1, "create %s", _PATH_WALL_CMOS_CLOCK);
677 dialog_clear_norefresh();
678 if (optind == argc - 1) {
680 asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]);
681 if (!dialog_yesno("Default timezone provided", msg, 7, 72)) {
682 install_zone_file(argv[optind]);
688 dialog_clear_norefresh();
690 dialog_menu("Time Zone Selector", "Select a region", -1, -1,
691 NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);