xref: /dragonfly/usr.sbin/tzsetup/tzsetup.c (revision ed5d5720)
1 /*
2  * Copyright 1996 Massachusetts Institute of Technology
3  *
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.
15  *
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.
28  *
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 $
31  */
32 
33 /*
34  * Second attempt at a `tzmenu' program, using the separate description
35  * files provided in newer tzdata releases.
36  */
37 
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>
46 
47 #include <sys/fcntl.h>
48 #include <sys/queue.h>
49 #include <sys/stat.h>
50 
51 #include "paths.h"
52 
53 static int reallydoit = 1;
54 
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 *);
59 
60 struct continent {
61 	dialogMenuItem *menu;
62 	int nitems;
63 	int ch;
64 	int sc;
65 };
66 
67 static struct continent africa, america, antarctica, arctic, asia, atlantic;
68 static struct continent australia, europe, indian, pacific;
69 
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 }
79 };
80 
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 }
93 };
94 #define NCONTINENTS ((sizeof continents)/(sizeof continents[0]))
95 #define OCEANP(x) ((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
96 
97 static int
98 continent_country_menu(dialogMenuItem *continent)
99 {
100 	int rv;
101 	struct continent *contp = continent->data;
102 	char title[256];
103 	int isocean = OCEANP(continent - continents);
104 	int menulen;
105 
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]));
109 
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);
117 
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;
126 }
127 
128 static struct continent *
129 find_continent(const char *name)
130 {
131 	int i;
132 
133 	for (i = 0; i < NCONTINENTS; i++) {
134 		if (strcmp(name, continent_names[i].name) == 0)
135 			return continent_names[i].continent;
136 	}
137 	return 0;
138 }
139 
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 */
148 };
149 
150 struct zone {
151 	TAILQ_ENTRY(zone) link;
152 	char *descr;
153 	char *filename;
154 	struct continent *continent;
155 };
156 
157 /*
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.
161  */
162 #define NCOUNTRIES	(26*26)
163 static struct country countries[NCOUNTRIES];
164 #define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A'))
165 
166 /*
167  * Read the ISO 3166 country code database in _PATH_ISO3166
168  * (/usr/share/misc/iso3166).  On error, exit via err(3).
169  */
170 static void
171 read_iso3166_table(void)
172 {
173 	FILE *fp;
174 	char *s, *t, *name;
175 	size_t len;
176 	int lineno;
177 	struct country *cp;
178 
179 	fp = fopen(_PATH_ISO3166, "r");
180 	if (!fp)
181 		err(1, _PATH_ISO3166);
182 	lineno = 0;
183 
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;
191 
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);
199 
200 		name = s;
201 
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");
213 	}
214 
215 	fclose(fp);
216 }
217 
218 static void
219 add_zone_to_country(int lineno, const char *tlc, const char *descr,
220 		    const char *file, struct continent *cont)
221 {
222 	struct zone *zp;
223 	struct country *cp;
224 
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);
228 
229 	cp = &countries[CODE2INT(tlc)];
230 	if (cp->name == 0)
231 		errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown",
232 		     lineno, tlc);
233 
234 	if (descr) {
235 		if (cp->nzones < 0)
236 			errx(1, _PATH_ZONETAB
237 			     ":%d: conflicting zone definition", lineno);
238 
239 		zp = malloc(sizeof *zp);
240 		if (zp == 0)
241 			errx(1, "malloc(%lu)", (unsigned long)sizeof *zp);
242 
243 		if (cp->nzones == 0)
244 			TAILQ_INIT(&cp->zones);
245 
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;
267 	}
268 }
269 
270 /*
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.
274  */
275 static int
276 compare_countries(const void *xa, const void *xb)
277 {
278 	const struct country *a = xa, *b = xb;
279 
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;
286 
287 	return strcmp(a->name, b->name);
288 }
289 
290 /*
291  * This must be done AFTER all zone descriptions are read, since it breaks
292  * CODE2INT().
293  */
294 static void
295 sort_countries(void)
296 {
297 	qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
298 }
299 
300 static void
301 read_zones(void)
302 {
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;
310 
311 	fp = fopen(_PATH_ZONETAB, "r");
312 	if (!fp)
313 		err(1, _PATH_ZONETAB);
314 	lineno = 0;
315 
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;
323 
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);
340 
341 		descr = (line && *line) ? line : 0;
342 
343 		add_zone_to_country(lineno, tlc, descr, file, cont);
344 	}
345 	fclose(fp);
346 }
347 
348 static void
349 make_menus(void)
350 {
351 	struct country *cp;
352 	struct zone *zp, *zp2;
353 	struct continent *cont;
354 	dialogMenuItem *dmi;
355 	int i;
356 
357 	/*
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.
362 	 */
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)
375 					;
376 				if (zp2 == zp)
377 					zp->continent->nitems++;
378 			}
379 		}
380 	}
381 
382 	/*
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.
386 	 */
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;
394 	}
395 
396 	/*
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.
400 	 */
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;
431 
432 				for (zp2 = cp->zones.tqh_first;
433 				     zp2->continent != cont;
434 				     zp2 = zp2->link.tqe_next)
435 					;
436 				if (zp2 != zp)
437 					continue;
438 
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;
447 			}
448 		}
449 	}
450 }
451 
452 static int
453 set_zone_menu(dialogMenuItem *dmi)
454 {
455 	int rv;
456 	char buf[256];
457 	struct country *cp = dmi->data;
458 	int menulen;
459 
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;
468 }
469 
470 static int
471 install_zone_file(const char *filename)
472 {
473 	struct stat sb;
474 	int fd1, fd2;
475 	int copymode;
476 	char *msg;
477 	ssize_t len;
478 	char buf[1024];
479 
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;
487 
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);
494 
495 	dialog_notify(msg);
496 	free(msg);
497 #endif
498 
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;
508 			}
509 
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;
521 			}
522 
523 			while ((len = read(fd1, buf, sizeof buf)) > 0)
524 				len = write(fd2, buf, len);
525 
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;
535 			}
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;
545 			}
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;
554 			}
555 		}
556 	}
557 
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);
565 
566 	dialog_mesgbox("Done", msg, 8, 72);
567 	free(msg);
568 #endif
569 	return DITEM_LEAVE_MENU;
570 }
571 
572 static int
573 confirm_zone(const char *filename)
574 {
575 	char *msg;
576 	struct tm *tm;
577 	time_t t = time(0);
578 	int rv;
579 
580 	if (setenv("TZ", filename, 1) == -1)
581 		err(1, "setenv: cannot set TZ=%s", filename);
582 	tzset();
583 	tm = localtime(&t);
584 
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;
590 }
591 
592 static int
593 set_zone_multi(dialogMenuItem *dmi)
594 {
595 	char *fn;
596 	struct zone *zp = dmi->data;
597 	int rv;
598 
599 	if (!confirm_zone(zp->filename))
600 		return DITEM_FAILURE | DITEM_RECREATE;
601 
602 	asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename);
603 	rv = install_zone_file(fn);
604 	free(fn);
605 	return rv;
606 }
607 
608 static int
609 set_zone_whole_country(dialogMenuItem *dmi)
610 {
611 	char *fn;
612 	struct country *cp = dmi->data;
613 	int rv;
614 
615 	if (!confirm_zone(cp->filename))
616 		return DITEM_FAILURE | DITEM_RECREATE;
617 
618 	asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename);
619 	rv = install_zone_file(fn);
620 	free(fn);
621 	return rv;
622 }
623 
624 static void
625 usage(void)
626 {
627 	fprintf(stderr, "usage: tzsetup [-n]\n");
628 	exit(1);
629 }
630 
631 int
632 main(int argc, char **argv)
633 {
634 	int c, fd;
635 	int (*dialog_utc)(unsigned char *, unsigned char *, int, int);
636 
637 	dialog_utc = dialog_noyes;
638 
639 	while ((c = getopt(argc, argv, "n")) != -1) {
640 		switch(c) {
641 		case 'n':
642 			reallydoit = 0;
643 			break;
644 
645 		default:
646 			usage();
647 		}
648 	}
649 
650 	if (argc - optind > 1)
651 		usage();
652 
653 	/* Override the user-supplied umask. */
654 	umask(S_IWGRP|S_IWOTH);
655 
656 	read_iso3166_table();
657 	read_zones();
658 	sort_countries();
659 	make_menus();
660 
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);
675 		}
676 	}
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;
686 		}
687 		free(msg);
688 		dialog_clear_norefresh();
689 	}
690 	dialog_menu("Time Zone Selector", "Select a region", -1, -1,
691 		    NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);
692 
693 	dialog_clear();
694 	end_dialog();
695 	return 0;
696 }
697 
698