Fix ifdefs to make it possible to use time.h in standards compilant code.
[dragonfly.git] / usr.sbin / tzsetup / tzsetup.c
blobb479bad5b4266c547b564607b4da408610017ad7
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.7 2008/03/02 14:28:37 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 set_zone_menu(&contp->menu[0]);
111 /* It's amazing how much good grammar really matters... */
112 if (!isocean)
113 snprintf(title, sizeof title, "Countries in %s",
114 continent->title);
115 else
116 snprintf(title, sizeof title, "Islands and groups in the %s",
117 continent->title);
119 menulen = contp->nitems < 16 ? contp->nitems : 16;
120 rv = dialog_menu(title, (isocean ? "Select an island or group"
121 : "Select a country"), -1, -1, menulen,
122 -contp->nitems, contp->menu, 0, &contp->ch,
123 &contp->sc);
124 if (rv == 0)
125 return DITEM_LEAVE_MENU;
126 return DITEM_RECREATE;
129 static struct continent *
130 find_continent(const char *name)
132 int i;
134 for (i = 0; i < NCONTINENTS; i++) {
135 if (strcmp(name, continent_names[i].name) == 0)
136 return continent_names[i].continent;
138 return 0;
141 struct country {
142 char *name;
143 char *tlc;
144 int nzones;
145 char *filename; /* use iff nzones < 0 */
146 struct continent *continent; /* use iff nzones < 0 */
147 TAILQ_HEAD(, zone) zones; /* use iff nzones > 0 */
148 dialogMenuItem *submenu; /* use iff nzones > 0 */
151 struct zone {
152 TAILQ_ENTRY(zone) link;
153 char *descr;
154 char *filename;
155 struct continent *continent;
159 * This is the easiest organization... we use ISO 3166 country codes,
160 * of the two-letter variety, so we just size this array to suit.
161 * Beats worrying about dynamic allocation.
163 #define NCOUNTRIES (26*26)
164 static struct country countries[NCOUNTRIES];
165 #define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A'))
168 * Read the ISO 3166 country code database in _PATH_ISO3166
169 * (/usr/share/misc/iso3166). On error, exit via err(3).
171 static void
172 read_iso3166_table(void)
174 FILE *fp;
175 char *s, *t, *name;
176 size_t len;
177 int lineno;
178 struct country *cp;
180 fp = fopen(_PATH_ISO3166, "r");
181 if (!fp)
182 err(1, _PATH_ISO3166);
183 lineno = 0;
185 while ((s = fgetln(fp, &len)) != 0) {
186 lineno++;
187 if (s[len - 1] != '\n')
188 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
189 s[len - 1] = '\0';
190 if (s[0] == '#' || strspn(s, " \t") == len - 1)
191 continue;
193 /* Isolate the two-letter code. */
194 t = strsep(&s, "\t");
195 if (t == 0 || strlen(t) != 2)
196 errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
197 if (t[0] < 'A' || t[0] > 'Z' || t[1] < 'A' || t[1] > 'Z')
198 errx(1, _PATH_ISO3166 ":%d: invalid code `%s'",
199 lineno, t);
201 name = s;
203 cp = &countries[CODE2INT(t)];
204 if (cp->name)
205 errx(1, _PATH_ISO3166
206 ":%d: country code `%s' multiply defined: %s",
207 lineno, t, cp->name);
208 cp->name = strdup(name);
209 if (cp->name == NULL)
210 errx(1, "malloc failed");
211 cp->tlc = strdup(t);
212 if (cp->tlc == NULL)
213 errx(1, "malloc failed");
216 fclose(fp);
219 static void
220 add_zone_to_country(int lineno, const char *tlc, const char *descr,
221 const char *file, struct continent *cont)
223 struct zone *zp;
224 struct country *cp;
226 if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
227 errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid",
228 lineno, tlc);
230 cp = &countries[CODE2INT(tlc)];
231 if (cp->name == 0)
232 errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown",
233 lineno, tlc);
235 if (descr) {
236 if (cp->nzones < 0)
237 errx(1, _PATH_ZONETAB
238 ":%d: conflicting zone definition", lineno);
240 zp = malloc(sizeof *zp);
241 if (zp == 0)
242 errx(1, "malloc(%lu)", (unsigned long)sizeof *zp);
244 if (cp->nzones == 0)
245 TAILQ_INIT(&cp->zones);
247 zp->descr = strdup(descr);
248 if (zp->descr == NULL)
249 errx(1, "malloc failed");
250 zp->filename = strdup(file);
251 if (zp->filename == NULL)
252 errx(1, "malloc failed");
253 zp->continent = cont;
254 TAILQ_INSERT_TAIL(&cp->zones, zp, link);
255 cp->nzones++;
256 } else {
257 if (cp->nzones > 0)
258 errx(1, _PATH_ZONETAB
259 ":%d: zone must have description", lineno);
260 if (cp->nzones < 0)
261 errx(1, _PATH_ZONETAB
262 ":%d: zone multiply defined", lineno);
263 cp->nzones = -1;
264 cp->filename = strdup(file);
265 if (cp->filename == NULL)
266 errx(1, "malloc failed");
267 cp->continent = cont;
272 * This comparison function intentionally sorts all of the null-named
273 * ``countries''---i.e., the codes that don't correspond to a real
274 * country---to the end. Everything else is lexical by country name.
276 static int
277 compare_countries(const void *xa, const void *xb)
279 const struct country *a = xa, *b = xb;
281 if (a->name == 0 && b->name == 0)
282 return 0;
283 if (a->name == 0 && b->name != 0)
284 return 1;
285 if (b->name == 0)
286 return -1;
288 return strcmp(a->name, b->name);
292 * This must be done AFTER all zone descriptions are read, since it breaks
293 * CODE2INT().
295 static void
296 sort_countries(void)
298 qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
301 static void
302 read_zones(void)
304 FILE *fp;
305 char *line;
306 size_t len;
307 int lineno;
308 char *tlc, *coord, *file, *descr, *p;
309 char contbuf[16];
310 struct continent *cont;
312 fp = fopen(_PATH_ZONETAB, "r");
313 if (!fp)
314 err(1, _PATH_ZONETAB);
315 lineno = 0;
317 while ((line = fgetln(fp, &len)) != 0) {
318 lineno++;
319 if (line[len - 1] != '\n')
320 errx(1, _PATH_ZONETAB ":%d: invalid format", lineno);
321 line[len - 1] = '\0';
322 if (line[0] == '#')
323 continue;
325 tlc = strsep(&line, "\t");
326 if (strlen(tlc) != 2)
327 errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'",
328 lineno, tlc);
329 coord = strsep(&line, "\t");
330 file = strsep(&line, "\t");
331 p = strchr(file, '/');
332 if (p == 0)
333 errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'",
334 lineno, file);
335 contbuf[0] = '\0';
336 strncat(contbuf, file, p - file);
337 cont = find_continent(contbuf);
338 if (!cont)
339 errx(1, _PATH_ZONETAB ":%d: invalid region `%s'",
340 lineno, contbuf);
342 descr = (line && *line) ? line : 0;
344 add_zone_to_country(lineno, tlc, descr, file, cont);
346 fclose(fp);
349 static void
350 make_menus(void)
352 struct country *cp;
353 struct zone *zp, *zp2;
354 struct continent *cont;
355 dialogMenuItem *dmi;
356 int i;
359 * First, count up all the countries in each continent/ocean.
360 * Be careful to count those countries which have multiple zones
361 * only once for each. NB: some countries are in multiple
362 * continents/oceans.
364 for (cp = countries; cp->name; cp++) {
365 if (cp->nzones == 0)
366 continue;
367 if (cp->nzones < 0) {
368 cp->continent->nitems++;
369 } else {
370 for (zp = cp->zones.tqh_first; zp;
371 zp = zp->link.tqe_next) {
372 cont = zp->continent;
373 for (zp2 = cp->zones.tqh_first;
374 zp2->continent != cont;
375 zp2 = zp2->link.tqe_next)
377 if (zp2 == zp)
378 zp->continent->nitems++;
384 * Now allocate memory for the country menus. We set
385 * nitems back to zero so that we can use it for counting
386 * again when we actually build the menus.
388 for (i = 0; i < NCONTINENTS; i++) {
389 continent_names[i].continent->menu =
390 malloc(sizeof(dialogMenuItem) *
391 continent_names[i].continent->nitems);
392 if (continent_names[i].continent->menu == 0)
393 errx(1, "malloc for continent menu");
394 continent_names[i].continent->nitems = 0;
398 * Now that memory is allocated, create the menu items for
399 * each continent. For multiple-zone countries, also create
400 * the country's zone submenu.
402 for (cp = countries; cp->name; cp++) {
403 if (cp->nzones == 0)
404 continue;
405 if (cp->nzones < 0) {
406 dmi = &cp->continent->menu[cp->continent->nitems];
407 memset(dmi, 0, sizeof *dmi);
408 asprintf(&dmi->prompt, "%d",
409 ++cp->continent->nitems);
410 dmi->title = cp->name;
411 dmi->checked = 0;
412 dmi->fire = set_zone_whole_country;
413 dmi->selected = 0;
414 dmi->data = cp;
415 } else {
416 cp->submenu = malloc(cp->nzones * sizeof *dmi);
417 if (cp->submenu == 0)
418 errx(1, "malloc for submenu");
419 cp->nzones = 0;
420 for (zp = cp->zones.tqh_first; zp;
421 zp = zp->link.tqe_next) {
422 cont = zp->continent;
423 dmi = &cp->submenu[cp->nzones];
424 memset(dmi, 0, sizeof *dmi);
425 asprintf(&dmi->prompt, "%d",
426 ++cp->nzones);
427 dmi->title = zp->descr;
428 dmi->checked = 0;
429 dmi->fire = set_zone_multi;
430 dmi->selected = 0;
431 dmi->data = zp;
433 for (zp2 = cp->zones.tqh_first;
434 zp2->continent != cont;
435 zp2 = zp2->link.tqe_next)
437 if (zp2 != zp)
438 continue;
440 dmi = &cont->menu[cont->nitems];
441 memset(dmi, 0, sizeof *dmi);
442 asprintf(&dmi->prompt, "%d", ++cont->nitems);
443 dmi->title = cp->name;
444 dmi->checked = 0;
445 dmi->fire = set_zone_menu;
446 dmi->selected = 0;
447 dmi->data = cp;
453 static int
454 set_zone_menu(dialogMenuItem *dmi)
456 int rv;
457 char buf[256];
458 struct country *cp = dmi->data;
459 int menulen;
461 snprintf(buf, sizeof buf, "%s Time Zones", cp->name);
462 menulen = cp->nzones < 16 ? cp->nzones : 16;
463 rv = dialog_menu(buf, "Select a zone which observes the same time as "
464 "your locality.", -1, -1, menulen, -cp->nzones,
465 cp->submenu, 0, 0, 0);
466 if (rv != 0)
467 return DITEM_RECREATE;
468 return DITEM_LEAVE_MENU;
471 static int
472 install_zone_file(const char *filename)
474 struct stat sb;
475 int fd1, fd2;
476 int copymode;
477 char *msg;
478 ssize_t len;
479 char buf[1024];
481 if (lstat(_PATH_LOCALTIME, &sb) < 0)
482 /* Nothing there yet... */
483 copymode = 1;
484 else if(S_ISLNK(sb.st_mode))
485 copymode = 0;
486 else
487 copymode = 1;
489 #ifdef VERBOSE
490 if (copymode)
491 asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename);
492 else
493 asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME
494 " to %s", filename);
496 dialog_notify(msg);
497 free(msg);
498 #endif
500 if (reallydoit) {
501 if (copymode) {
502 fd1 = open(filename, O_RDONLY, 0);
503 if (fd1 < 0) {
504 asprintf(&msg, "Could not open %s: %s",
505 filename, strerror(errno));
506 dialog_mesgbox("Error", msg, 8, 72);
507 free(msg);
508 return DITEM_FAILURE | DITEM_RECREATE;
511 unlink(_PATH_LOCALTIME);
512 fd2 = open(_PATH_LOCALTIME,
513 O_CREAT|O_EXCL|O_WRONLY,
514 S_IRUSR|S_IRGRP|S_IROTH);
515 if (fd2 < 0) {
516 asprintf(&msg, "Could not open "
517 _PATH_LOCALTIME ": %s",
518 strerror(errno));
519 dialog_mesgbox("Error", msg, 8, 72);
520 free(msg);
521 return DITEM_FAILURE | DITEM_RECREATE;
524 while ((len = read(fd1, buf, sizeof buf)) > 0)
525 len = write(fd2, buf, len);
527 if (len == -1) {
528 asprintf(&msg, "Error copying %s to "
529 _PATH_LOCALTIME ": %s",
530 filename, strerror(errno));
531 dialog_mesgbox("Error", msg, 8, 72);
532 free(msg);
533 /* Better to leave none than a corrupt one. */
534 unlink(_PATH_LOCALTIME);
535 return DITEM_FAILURE | DITEM_RECREATE;
537 close(fd1);
538 close(fd2);
539 } else {
540 if (access(filename, R_OK) != 0) {
541 asprintf(&msg, "Cannot access %s: %s",
542 filename, strerror(errno));
543 dialog_mesgbox("Error", msg, 8, 72);
544 free(msg);
545 return DITEM_FAILURE | DITEM_RECREATE;
547 unlink(_PATH_LOCALTIME);
548 if (symlink(filename, _PATH_LOCALTIME) < 0) {
549 asprintf(&msg, "Cannot create symbolic link "
550 _PATH_LOCALTIME " to %s: %s",
551 filename, strerror(errno));
552 dialog_mesgbox("Error", msg, 8, 72);
553 free(msg);
554 return DITEM_FAILURE | DITEM_RECREATE;
559 #ifdef VERBOSE
560 if (copymode)
561 asprintf(&msg, "Copied timezone file from %s to "
562 _PATH_LOCALTIME, filename);
563 else
564 asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME
565 " to %s", filename);
567 dialog_mesgbox("Done", msg, 8, 72);
568 free(msg);
569 #endif
570 return DITEM_LEAVE_MENU;
573 static int
574 confirm_zone(const char *filename)
576 char *msg;
577 struct tm *tm;
578 time_t t = time(0);
579 int rv;
581 if (setenv("TZ", filename, 1) == -1)
582 err(1, "setenv: cannot set TZ=%s", filename);
583 tzset();
584 tm = localtime(&t);
586 asprintf(&msg, "Does the abbreviation `%s' look reasonable?",
587 tm->tm_zone);
588 rv = !dialog_yesno("Confirmation", msg, 4, 72);
589 free(msg);
590 return rv;
593 static int
594 set_zone_multi(dialogMenuItem *dmi)
596 char *fn;
597 struct zone *zp = dmi->data;
598 int rv;
600 if (!confirm_zone(zp->filename))
601 return DITEM_FAILURE | DITEM_RECREATE;
603 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename);
604 rv = install_zone_file(fn);
605 free(fn);
606 return rv;
609 static int
610 set_zone_whole_country(dialogMenuItem *dmi)
612 char *fn;
613 struct country *cp = dmi->data;
614 int rv;
616 if (!confirm_zone(cp->filename))
617 return DITEM_FAILURE | DITEM_RECREATE;
619 asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename);
620 rv = install_zone_file(fn);
621 free(fn);
622 return rv;
625 static void
626 usage(void)
628 fprintf(stderr, "usage: tzsetup [-n]\n");
629 exit(1);
633 main(int argc, char **argv)
635 int c, fd;
636 int (*dialog_utc)(unsigned char *, unsigned char *, int, int);
638 dialog_utc = dialog_noyes;
640 while ((c = getopt(argc, argv, "n")) != -1) {
641 switch(c) {
642 case 'n':
643 reallydoit = 0;
644 break;
646 default:
647 usage();
651 if (argc - optind > 1)
652 usage();
654 /* Override the user-supplied umask. */
655 umask(S_IWGRP|S_IWOTH);
657 read_iso3166_table();
658 read_zones();
659 sort_countries();
660 make_menus();
662 init_dialog();
663 if (!dialog_utc("Select local or UTC (Greenwich Mean Time) clock",
664 "Is this machine's CMOS clock set to UTC? If it is set to local time,\n"
665 "or you don't know, please choose NO here!", 7, 72)) {
666 if (reallydoit)
667 unlink(_PATH_WALL_CMOS_CLOCK);
668 } else {
669 if (reallydoit) {
670 fd = open(_PATH_WALL_CMOS_CLOCK,
671 O_WRONLY|O_CREAT|O_TRUNC,
672 S_IRUSR|S_IRGRP|S_IROTH);
673 if (fd < 0)
674 err(1, "create %s", _PATH_WALL_CMOS_CLOCK);
675 close(fd);
678 dialog_clear_norefresh();
679 if (optind == argc - 1) {
680 char *msg;
681 asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]);
682 if (!dialog_yesno("Default timezone provided", msg, 7, 72)) {
683 install_zone_file(argv[optind]);
684 dialog_clear();
685 end_dialog();
686 return 0;
688 free(msg);
689 dialog_clear_norefresh();
691 dialog_menu("Time Zone Selector", "Select a region", -1, -1,
692 NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);
694 dialog_clear();
695 end_dialog();
696 return 0;