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 $
33 * Second attempt at a `tzmenu' program, using the separate description
34 * files provided in newer tzdata releases.
37 #include <sys/types.h>
46 #include <sys/fcntl.h>
47 #include <sys/queue.h>
52 static int reallydoit = 1;
54 static int continent_country_menu(dialogMenuItem *);
55 static int set_zone_multi(dialogMenuItem *);
56 static int set_zone_whole_country(dialogMenuItem *);
57 static int set_zone_menu(dialogMenuItem *);
66 static struct continent africa, america, antarctica, arctic, asia, atlantic;
67 static struct continent australia, europe, indian, pacific;
69 static struct continent_names {
71 struct continent *continent;
72 } continent_names[] = {
73 { "Africa", &africa }, { "America", &america },
74 { "Antarctica", &antarctica }, { "Arctic", &arctic },
76 { "Atlantic", &atlantic }, { "Australia", &australia },
77 { "Europe", &europe }, { "Indian", &indian }, { "Pacific", &pacific }
80 static dialogMenuItem continents[] = {
81 { "1", "Africa", 0, continent_country_menu, 0, &africa },
82 { "2", "America -- North and South", 0, continent_country_menu, 0,
84 { "3", "Antarctica", 0, continent_country_menu, 0, &antarctica },
85 { "4", "Arctic Ocean", 0, continent_country_menu, 0, &arctic },
86 { "5", "Asia", 0, continent_country_menu, 0, &asia },
87 { "6", "Atlantic Ocean", 0, continent_country_menu, 0, &atlantic },
88 { "7", "Australia", 0, continent_country_menu, 0, &australia },
89 { "8", "Europe", 0, continent_country_menu, 0, &europe },
90 { "9", "Indian Ocean", 0, continent_country_menu, 0, &indian },
91 { "0", "Pacific Ocean", 0, continent_country_menu, 0, &pacific }
93 #define NCONTINENTS (int)((sizeof continents)/(sizeof continents[0]))
94 #define OCEANP(x) ((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
97 continent_country_menu(dialogMenuItem *continent)
100 struct continent *contp = continent->data;
102 int isocean = OCEANP(continent - continents);
105 /* Short cut -- if there's only one country, don't post a menu. */
106 if (contp->nitems == 1)
107 return (contp->menu[0].fire(&contp->menu[0]));
109 /* It's amazing how much good grammar really matters... */
111 snprintf(title, sizeof title, "Countries in %s",
114 snprintf(title, sizeof title, "Islands and groups in the %s",
117 menulen = contp->nitems < 16 ? contp->nitems : 16;
118 rv = dialog_menu(title, (isocean ? "Select an island or group"
119 : "Select a country"), -1, -1, menulen,
120 -contp->nitems, contp->menu, 0, &contp->ch,
123 return DITEM_LEAVE_MENU;
124 return DITEM_RECREATE;
127 static struct continent *
128 find_continent(const char *name)
132 for (i = 0; i < NCONTINENTS; i++) {
133 if (strcmp(name, continent_names[i].name) == 0)
134 return continent_names[i].continent;
143 char *filename; /* use iff nzones < 0 */
144 struct continent *continent; /* use iff nzones < 0 */
145 TAILQ_HEAD(, zone) zones; /* use iff nzones > 0 */
146 dialogMenuItem *submenu; /* use iff nzones > 0 */
150 TAILQ_ENTRY(zone) link;
153 struct continent *continent;
157 * This is the easiest organization... we use ISO 3166 country codes,
158 * of the two-letter variety, so we just size this array to suit.
159 * Beats worrying about dynamic allocation.
161 #define NCOUNTRIES (26*26)
162 static struct country countries[NCOUNTRIES];
163 #define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A'))
166 * Read the ISO 3166 country code database in _PATH_ISO3166
167 * (/usr/share/misc/iso3166). On error, exit via err(3).
170 read_iso3166_table(void)
178 fp = fopen(_PATH_ISO3166, "r");
180 err(1, _PATH_ISO3166);
183 while ((s = fgetln(fp, &len)) != NULL) {
185 if (s[len - 1] != '\n')
186 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
188 if (s[0] == '#' || strspn(s, " \t") == len - 1)
191 /* Isolate the two-letter code. */
192 t = strsep(&s, "\t");
193 if (t == NULL || strlen(t) != 2)
194 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
195 if (t[0] < 'A' || t[0] > 'Z' || t[1] < 'A' || t[1] > 'Z')
196 errx(1, _PATH_ISO3166 ":%d: invalid code `%s'",
201 cp = &countries[CODE2INT(t)];
203 errx(1, _PATH_ISO3166
204 ":%d: country code `%s' multiply defined: %s",
205 lineno, t, cp->name);
206 cp->name = strdup(name);
207 if (cp->name == NULL)
208 errx(1, "malloc failed");
211 errx(1, "malloc failed");
218 add_zone_to_country(int lineno, const char *tlc, const char *descr,
219 const char *file, struct continent *cont)
224 if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
225 errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid",
228 cp = &countries[CODE2INT(tlc)];
230 errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown",
235 errx(1, _PATH_ZONETAB
236 ":%d: conflicting zone definition", lineno);
238 zp = malloc(sizeof *zp);
240 errx(1, "malloc(%lu)", (unsigned long)sizeof *zp);
243 TAILQ_INIT(&cp->zones);
245 zp->descr = strdup(descr);
246 if (zp->descr == NULL)
247 errx(1, "malloc failed");
248 zp->filename = strdup(file);
249 if (zp->filename == NULL)
250 errx(1, "malloc failed");
251 zp->continent = cont;
252 TAILQ_INSERT_TAIL(&cp->zones, zp, link);
256 errx(1, _PATH_ZONETAB
257 ":%d: zone must have description", lineno);
259 errx(1, _PATH_ZONETAB
260 ":%d: zone multiply defined", lineno);
262 cp->filename = strdup(file);
263 if (cp->filename == NULL)
264 errx(1, "malloc failed");
265 cp->continent = cont;
270 * This comparison function intentionally sorts all of the null-named
271 * ``countries''---i.e., the codes that don't correspond to a real
272 * country---to the end. Everything else is lexical by country name.
275 compare_countries(const void *xa, const void *xb)
277 const struct country *a = xa, *b = xb;
279 if (a->name == 0 && b->name == 0)
281 if (a->name == 0 && b->name != 0)
286 return strcmp(a->name, b->name);
290 * This must be done AFTER all zone descriptions are read, since it breaks
296 qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
306 char *tlc, *coord, *file, *descr, *p;
308 struct continent *cont;
310 fp = fopen(_PATH_ZONETAB, "r");
312 err(1, _PATH_ZONETAB);
315 while ((line = fgetln(fp, &len)) != NULL) {
317 if (line[len - 1] != '\n')
318 errx(1, _PATH_ZONETAB ":%d: invalid format", lineno);
319 line[len - 1] = '\0';
323 tlc = strsep(&line, "\t");
324 if (strlen(tlc) != 2)
325 errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'",
327 coord = strsep(&line, "\t");
328 file = strsep(&line, "\t");
329 p = strchr(file, '/');
331 errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'",
334 strncat(contbuf, file, p - file);
335 cont = find_continent(contbuf);
337 errx(1, _PATH_ZONETAB ":%d: invalid region `%s'",
340 descr = (line && *line) ? line : 0;
342 add_zone_to_country(lineno, tlc, descr, file, cont);
351 struct zone *zp, *zp2;
352 struct continent *cont;
357 * First, count up all the countries in each continent/ocean.
358 * Be careful to count those countries which have multiple zones
359 * only once for each. NB: some countries are in multiple
362 for (cp = countries; cp->name; cp++) {
365 if (cp->nzones < 0) {
366 cp->continent->nitems++;
368 for (zp = cp->zones.tqh_first; zp;
369 zp = zp->link.tqe_next) {
370 cont = zp->continent;
371 for (zp2 = cp->zones.tqh_first;
372 zp2->continent != cont;
373 zp2 = zp2->link.tqe_next)
376 zp->continent->nitems++;
382 * Now allocate memory for the country menus. We set
383 * nitems back to zero so that we can use it for counting
384 * again when we actually build the menus.
386 for (i = 0; i < NCONTINENTS; i++) {
387 continent_names[i].continent->menu =
388 malloc(sizeof(dialogMenuItem) *
389 continent_names[i].continent->nitems);
390 if (continent_names[i].continent->menu == NULL)
391 errx(1, "malloc for continent menu");
392 continent_names[i].continent->nitems = 0;
396 * Now that memory is allocated, create the menu items for
397 * each continent. For multiple-zone countries, also create
398 * the country's zone submenu.
400 for (cp = countries; cp->name; cp++) {
403 if (cp->nzones < 0) {
404 dmi = &cp->continent->menu[cp->continent->nitems];
405 memset(dmi, 0, sizeof *dmi);
406 asprintf(&dmi->prompt, "%d",
407 ++cp->continent->nitems);
408 dmi->title = cp->name;
410 dmi->fire = set_zone_whole_country;
414 cp->submenu = malloc(cp->nzones * sizeof *dmi);
415 if (cp->submenu == 0)
416 errx(1, "malloc for submenu");
418 for (zp = cp->zones.tqh_first; zp;
419 zp = zp->link.tqe_next) {
420 cont = zp->continent;
421 dmi = &cp->submenu[cp->nzones];
422 memset(dmi, 0, sizeof *dmi);
423 asprintf(&dmi->prompt, "%d",
425 dmi->title = zp->descr;
427 dmi->fire = set_zone_multi;
431 for (zp2 = cp->zones.tqh_first;
432 zp2->continent != cont;
433 zp2 = zp2->link.tqe_next)
438 dmi = &cont->menu[cont->nitems];
439 memset(dmi, 0, sizeof *dmi);
440 asprintf(&dmi->prompt, "%d", ++cont->nitems);
441 dmi->title = cp->name;
443 dmi->fire = set_zone_menu;
452 set_zone_menu(dialogMenuItem *dmi)
456 struct country *cp = dmi->data;
459 snprintf(buf, sizeof buf, "%s Time Zones", cp->name);
460 menulen = cp->nzones < 16 ? cp->nzones : 16;
461 rv = dialog_menu(buf, "Select a zone which observes the same time as "
462 "your locality.", -1, -1, menulen, -cp->nzones,
463 cp->submenu, 0, 0, 0);
465 return DITEM_RECREATE;
466 return DITEM_LEAVE_MENU;
470 install_zone_file(const char *filename)
479 if (lstat(_PATH_LOCALTIME, &sb) < 0)
480 /* Nothing there yet... */
482 else if(S_ISLNK(sb.st_mode))
489 asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename);
491 asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME
500 fd1 = open(filename, O_RDONLY, 0);
502 asprintf(&msg, "Could not open %s: %s",
503 filename, strerror(errno));
504 dialog_mesgbox("Error", msg, 8, 72);
506 return DITEM_FAILURE | DITEM_RECREATE;
509 unlink(_PATH_LOCALTIME);
510 fd2 = open(_PATH_LOCALTIME,
511 O_CREAT|O_EXCL|O_WRONLY,
512 S_IRUSR|S_IRGRP|S_IROTH);
514 asprintf(&msg, "Could not open "
515 _PATH_LOCALTIME ": %s",
517 dialog_mesgbox("Error", msg, 8, 72);
519 return DITEM_FAILURE | DITEM_RECREATE;
522 while ((len = read(fd1, buf, sizeof buf)) > 0)
523 len = write(fd2, buf, len);
526 asprintf(&msg, "Error copying %s to "
527 _PATH_LOCALTIME ": %s",
528 filename, strerror(errno));
529 dialog_mesgbox("Error", msg, 8, 72);
531 /* Better to leave none than a corrupt one. */
532 unlink(_PATH_LOCALTIME);
533 return DITEM_FAILURE | DITEM_RECREATE;
538 if (access(filename, R_OK) != 0) {
539 asprintf(&msg, "Cannot access %s: %s",
540 filename, strerror(errno));
541 dialog_mesgbox("Error", msg, 8, 72);
543 return DITEM_FAILURE | DITEM_RECREATE;
545 unlink(_PATH_LOCALTIME);
546 if (symlink(filename, _PATH_LOCALTIME) < 0) {
547 asprintf(&msg, "Cannot create symbolic link "
548 _PATH_LOCALTIME " to %s: %s",
549 filename, strerror(errno));
550 dialog_mesgbox("Error", msg, 8, 72);
552 return DITEM_FAILURE | DITEM_RECREATE;
559 asprintf(&msg, "Copied timezone file from %s to "
560 _PATH_LOCALTIME, filename);
562 asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME
565 dialog_mesgbox("Done", msg, 8, 72);
568 return DITEM_LEAVE_MENU;
572 confirm_zone(const char *filename)
579 if (setenv("TZ", filename, 1) == -1)
580 err(1, "setenv: cannot set TZ=%s", filename);
584 asprintf(&msg, "Does the abbreviation `%s' look reasonable?",
586 rv = !dialog_yesno("Confirmation", msg, 4, 72);
592 set_zone_multi(dialogMenuItem *dmi)
595 struct zone *zp = dmi->data;
598 if (!confirm_zone(zp->filename))
599 return DITEM_FAILURE | DITEM_RECREATE;
601 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename);
602 rv = install_zone_file(fn);
608 set_zone_whole_country(dialogMenuItem *dmi)
611 struct country *cp = dmi->data;
614 if (!confirm_zone(cp->filename))
615 return DITEM_FAILURE | DITEM_RECREATE;
617 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename);
618 rv = install_zone_file(fn);
626 fprintf(stderr, "usage: tzsetup [-n]\n");
631 main(int argc, char **argv)
634 int (*dialog_utc)(unsigned char *, unsigned char *, int, int);
636 dialog_utc = dialog_noyes;
638 while ((c = getopt(argc, argv, "n")) != -1) {
649 if (argc - optind > 1)
652 /* Override the user-supplied umask. */
653 umask(S_IWGRP|S_IWOTH);
655 read_iso3166_table();
661 if (!dialog_utc("Select local or UTC (Greenwich Mean Time) clock",
662 "Is this machine's CMOS clock set to UTC? If it is set to local time,\n"
663 "or you don't know, please choose NO here!", 7, 72)) {
665 unlink(_PATH_WALL_CMOS_CLOCK);
668 fd = open(_PATH_WALL_CMOS_CLOCK,
669 O_WRONLY|O_CREAT|O_TRUNC,
670 S_IRUSR|S_IRGRP|S_IROTH);
672 err(1, "create %s", _PATH_WALL_CMOS_CLOCK);
676 dialog_clear_norefresh();
677 if (optind == argc - 1) {
679 asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]);
680 if (!dialog_yesno("Default timezone provided", msg, 7, 72)) {
681 install_zone_file(argv[optind]);
687 dialog_clear_norefresh();
689 dialog_menu("Time Zone Selector", "Select a region", -1, -1,
690 NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);