Merge branch 'vendor/OPENSSL'
[dragonfly.git] / usr.sbin / tzsetup / tzsetup.c
blob3d1a5d8847681d954b83b6706b5fb9eadec2563e
1 /*
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
14 * warranty.
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
27 * SUCH DAMAGE.
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>
39 #include <dialog.h>
40 #include <err.h>
41 #include <errno.h>
42 #include <stdio.h>
43 #include <stdlib.h>
44 #include <string.h>
45 #include <unistd.h>
47 #include <sys/fcntl.h>
48 #include <sys/queue.h>
49 #include <sys/stat.h>
51 #include "paths.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 *);
60 struct continent {
61 dialogMenuItem *menu;
62 int nitems;
63 int ch;
64 int sc;
67 static struct continent africa, america, antarctica, arctic, asia, atlantic;
68 static struct continent australia, europe, indian, pacific;
70 static struct continent_names {
71 char *name;
72 struct continent *continent;
73 } continent_names[] = {
74 { "Africa", &africa }, { "America", &america },
75 { "Antarctica", &antarctica }, { "Arctic", &arctic },
76 { "Asia", &asia },
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,
84 &america },
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)
97 static int
98 continent_country_menu(dialogMenuItem *continent)
100 int rv;
101 struct continent *contp = continent->data;
102 char title[256];
103 int isocean = OCEANP(continent - continents);
104 int menulen;
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... */
111 if (!isocean)
112 snprintf(title, sizeof title, "Countries in %s",
113 continent->title);
114 else
115 snprintf(title, sizeof title, "Islands and groups in the %s",
116 continent->title);
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,
122 &contp->sc);
123 if (rv == 0)
124 return DITEM_LEAVE_MENU;
125 return DITEM_RECREATE;
128 static struct continent *
129 find_continent(const char *name)
131 int i;
133 for (i = 0; i < NCONTINENTS; i++) {
134 if (strcmp(name, continent_names[i].name) == 0)
135 return continent_names[i].continent;
137 return 0;
140 struct country {
141 char *name;
142 char *tlc;
143 int nzones;
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 */
150 struct zone {
151 TAILQ_ENTRY(zone) link;
152 char *descr;
153 char *filename;
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).
170 static void
171 read_iso3166_table(void)
173 FILE *fp;
174 char *s, *t, *name;
175 size_t len;
176 int lineno;
177 struct country *cp;
179 fp = fopen(_PATH_ISO3166, "r");
180 if (!fp)
181 err(1, _PATH_ISO3166);
182 lineno = 0;
184 while ((s = fgetln(fp, &len)) != 0) {
185 lineno++;
186 if (s[len - 1] != '\n')
187 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
188 s[len - 1] = '\0';
189 if (s[0] == '#' || strspn(s, " \t") == len - 1)
190 continue;
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'",
198 lineno, t);
200 name = s;
202 cp = &countries[CODE2INT(t)];
203 if (cp->name)
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");
210 cp->tlc = strdup(t);
211 if (cp->tlc == NULL)
212 errx(1, "malloc failed");
215 fclose(fp);
218 static void
219 add_zone_to_country(int lineno, const char *tlc, const char *descr,
220 const char *file, struct continent *cont)
222 struct zone *zp;
223 struct country *cp;
225 if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
226 errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid",
227 lineno, tlc);
229 cp = &countries[CODE2INT(tlc)];
230 if (cp->name == 0)
231 errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown",
232 lineno, tlc);
234 if (descr) {
235 if (cp->nzones < 0)
236 errx(1, _PATH_ZONETAB
237 ":%d: conflicting zone definition", lineno);
239 zp = malloc(sizeof *zp);
240 if (zp == 0)
241 errx(1, "malloc(%lu)", (unsigned long)sizeof *zp);
243 if (cp->nzones == 0)
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);
254 cp->nzones++;
255 } else {
256 if (cp->nzones > 0)
257 errx(1, _PATH_ZONETAB
258 ":%d: zone must have description", lineno);
259 if (cp->nzones < 0)
260 errx(1, _PATH_ZONETAB
261 ":%d: zone multiply defined", lineno);
262 cp->nzones = -1;
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.
275 static int
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)
281 return 0;
282 if (a->name == 0 && b->name != 0)
283 return 1;
284 if (b->name == 0)
285 return -1;
287 return strcmp(a->name, b->name);
291 * This must be done AFTER all zone descriptions are read, since it breaks
292 * CODE2INT().
294 static void
295 sort_countries(void)
297 qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
300 static void
301 read_zones(void)
303 FILE *fp;
304 char *line;
305 size_t len;
306 int lineno;
307 char *tlc, *coord, *file, *descr, *p;
308 char contbuf[16];
309 struct continent *cont;
311 fp = fopen(_PATH_ZONETAB, "r");
312 if (!fp)
313 err(1, _PATH_ZONETAB);
314 lineno = 0;
316 while ((line = fgetln(fp, &len)) != 0) {
317 lineno++;
318 if (line[len - 1] != '\n')
319 errx(1, _PATH_ZONETAB ":%d: invalid format", lineno);
320 line[len - 1] = '\0';
321 if (line[0] == '#')
322 continue;
324 tlc = strsep(&line, "\t");
325 if (strlen(tlc) != 2)
326 errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'",
327 lineno, tlc);
328 coord = strsep(&line, "\t");
329 file = strsep(&line, "\t");
330 p = strchr(file, '/');
331 if (p == 0)
332 errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'",
333 lineno, file);
334 contbuf[0] = '\0';
335 strncat(contbuf, file, p - file);
336 cont = find_continent(contbuf);
337 if (!cont)
338 errx(1, _PATH_ZONETAB ":%d: invalid region `%s'",
339 lineno, contbuf);
341 descr = (line && *line) ? line : 0;
343 add_zone_to_country(lineno, tlc, descr, file, cont);
345 fclose(fp);
348 static void
349 make_menus(void)
351 struct country *cp;
352 struct zone *zp, *zp2;
353 struct continent *cont;
354 dialogMenuItem *dmi;
355 int i;
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
361 * continents/oceans.
363 for (cp = countries; cp->name; cp++) {
364 if (cp->nzones == 0)
365 continue;
366 if (cp->nzones < 0) {
367 cp->continent->nitems++;
368 } else {
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)
376 if (zp2 == zp)
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++) {
402 if (cp->nzones == 0)
403 continue;
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;
410 dmi->checked = 0;
411 dmi->fire = set_zone_whole_country;
412 dmi->selected = 0;
413 dmi->data = cp;
414 } else {
415 cp->submenu = malloc(cp->nzones * sizeof *dmi);
416 if (cp->submenu == 0)
417 errx(1, "malloc for submenu");
418 cp->nzones = 0;
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",
425 ++cp->nzones);
426 dmi->title = zp->descr;
427 dmi->checked = 0;
428 dmi->fire = set_zone_multi;
429 dmi->selected = 0;
430 dmi->data = zp;
432 for (zp2 = cp->zones.tqh_first;
433 zp2->continent != cont;
434 zp2 = zp2->link.tqe_next)
436 if (zp2 != zp)
437 continue;
439 dmi = &cont->menu[cont->nitems];
440 memset(dmi, 0, sizeof *dmi);
441 asprintf(&dmi->prompt, "%d", ++cont->nitems);
442 dmi->title = cp->name;
443 dmi->checked = 0;
444 dmi->fire = set_zone_menu;
445 dmi->selected = 0;
446 dmi->data = cp;
452 static int
453 set_zone_menu(dialogMenuItem *dmi)
455 int rv;
456 char buf[256];
457 struct country *cp = dmi->data;
458 int menulen;
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);
465 if (rv != 0)
466 return DITEM_RECREATE;
467 return DITEM_LEAVE_MENU;
470 static int
471 install_zone_file(const char *filename)
473 struct stat sb;
474 int fd1, fd2;
475 int copymode;
476 char *msg;
477 ssize_t len;
478 char buf[1024];
480 if (lstat(_PATH_LOCALTIME, &sb) < 0)
481 /* Nothing there yet... */
482 copymode = 1;
483 else if(S_ISLNK(sb.st_mode))
484 copymode = 0;
485 else
486 copymode = 1;
488 #ifdef VERBOSE
489 if (copymode)
490 asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename);
491 else
492 asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME
493 " to %s", filename);
495 dialog_notify(msg);
496 free(msg);
497 #endif
499 if (reallydoit) {
500 if (copymode) {
501 fd1 = open(filename, O_RDONLY, 0);
502 if (fd1 < 0) {
503 asprintf(&msg, "Could not open %s: %s",
504 filename, strerror(errno));
505 dialog_mesgbox("Error", msg, 8, 72);
506 free(msg);
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);
514 if (fd2 < 0) {
515 asprintf(&msg, "Could not open "
516 _PATH_LOCALTIME ": %s",
517 strerror(errno));
518 dialog_mesgbox("Error", msg, 8, 72);
519 free(msg);
520 return DITEM_FAILURE | DITEM_RECREATE;
523 while ((len = read(fd1, buf, sizeof buf)) > 0)
524 len = write(fd2, buf, len);
526 if (len == -1) {
527 asprintf(&msg, "Error copying %s to "
528 _PATH_LOCALTIME ": %s",
529 filename, strerror(errno));
530 dialog_mesgbox("Error", msg, 8, 72);
531 free(msg);
532 /* Better to leave none than a corrupt one. */
533 unlink(_PATH_LOCALTIME);
534 return DITEM_FAILURE | DITEM_RECREATE;
536 close(fd1);
537 close(fd2);
538 } else {
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);
543 free(msg);
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);
552 free(msg);
553 return DITEM_FAILURE | DITEM_RECREATE;
558 #ifdef VERBOSE
559 if (copymode)
560 asprintf(&msg, "Copied timezone file from %s to "
561 _PATH_LOCALTIME, filename);
562 else
563 asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME
564 " to %s", filename);
566 dialog_mesgbox("Done", msg, 8, 72);
567 free(msg);
568 #endif
569 return DITEM_LEAVE_MENU;
572 static int
573 confirm_zone(const char *filename)
575 char *msg;
576 struct tm *tm;
577 time_t t = time(0);
578 int rv;
580 if (setenv("TZ", filename, 1) == -1)
581 err(1, "setenv: cannot set TZ=%s", filename);
582 tzset();
583 tm = localtime(&t);
585 asprintf(&msg, "Does the abbreviation `%s' look reasonable?",
586 tm->tm_zone);
587 rv = !dialog_yesno("Confirmation", msg, 4, 72);
588 free(msg);
589 return rv;
592 static int
593 set_zone_multi(dialogMenuItem *dmi)
595 char *fn;
596 struct zone *zp = dmi->data;
597 int rv;
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);
604 free(fn);
605 return rv;
608 static int
609 set_zone_whole_country(dialogMenuItem *dmi)
611 char *fn;
612 struct country *cp = dmi->data;
613 int rv;
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);
620 free(fn);
621 return rv;
624 static void
625 usage(void)
627 fprintf(stderr, "usage: tzsetup [-n]\n");
628 exit(1);
632 main(int argc, char **argv)
634 int c, fd;
635 int (*dialog_utc)(unsigned char *, unsigned char *, int, int);
637 dialog_utc = dialog_noyes;
639 while ((c = getopt(argc, argv, "n")) != -1) {
640 switch(c) {
641 case 'n':
642 reallydoit = 0;
643 break;
645 default:
646 usage();
650 if (argc - optind > 1)
651 usage();
653 /* Override the user-supplied umask. */
654 umask(S_IWGRP|S_IWOTH);
656 read_iso3166_table();
657 read_zones();
658 sort_countries();
659 make_menus();
661 init_dialog();
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)) {
665 if (reallydoit)
666 unlink(_PATH_WALL_CMOS_CLOCK);
667 } else {
668 if (reallydoit) {
669 fd = open(_PATH_WALL_CMOS_CLOCK,
670 O_WRONLY|O_CREAT|O_TRUNC,
671 S_IRUSR|S_IRGRP|S_IROTH);
672 if (fd < 0)
673 err(1, "create %s", _PATH_WALL_CMOS_CLOCK);
674 close(fd);
677 dialog_clear_norefresh();
678 if (optind == argc - 1) {
679 char *msg;
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]);
683 dialog_clear();
684 end_dialog();
685 return 0;
687 free(msg);
688 dialog_clear_norefresh();
690 dialog_menu("Time Zone Selector", "Select a region", -1, -1,
691 NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);
693 dialog_clear();
694 end_dialog();
695 return 0;