xref: /dragonfly/usr.sbin/tzsetup/tzsetup.c (revision 678e8cc6)
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  */
31 
32 /*
33  * Second attempt at a `tzmenu' program, using the separate description
34  * files provided in newer tzdata releases.
35  */
36 
37 #include <sys/types.h>
38 #include <dialog.h>
39 #include <err.h>
40 #include <errno.h>
41 #include <stdio.h>
42 #include <stdlib.h>
43 #include <string.h>
44 #include <unistd.h>
45 
46 #include <sys/fcntl.h>
47 #include <sys/queue.h>
48 #include <sys/stat.h>
49 
50 #include "paths.h"
51 
52 static int reallydoit = 1;
53 
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 *);
58 
59 struct continent {
60 	dialogMenuItem *menu;
61 	int nitems;
62 	int ch;
63 	int sc;
64 };
65 
66 static struct continent africa, america, antarctica, arctic, asia, atlantic;
67 static struct continent australia, europe, indian, pacific;
68 
69 static struct continent_names {
70 	char *name;
71 	struct continent *continent;
72 } continent_names[] = {
73 	{ "Africa", &africa }, { "America", &america },
74 	{ "Antarctica", &antarctica }, { "Arctic", &arctic },
75 	{ "Asia", &asia },
76 	{ "Atlantic", &atlantic }, { "Australia", &australia },
77 	{ "Europe", &europe }, { "Indian", &indian }, { "Pacific", &pacific }
78 };
79 
80 static dialogMenuItem continents[] = {
81 	{ "1", "Africa", 0, continent_country_menu, 0, &africa },
82 	{ "2", "America -- North and South", 0, continent_country_menu, 0,
83 		  &america },
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 }
92 };
93 #define NCONTINENTS (int)((sizeof continents)/(sizeof continents[0]))
94 #define OCEANP(x) ((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
95 
96 static int
97 continent_country_menu(dialogMenuItem *continent)
98 {
99 	int rv;
100 	struct continent *contp = continent->data;
101 	char title[256];
102 	int isocean = OCEANP(continent - continents);
103 	int menulen;
104 
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]));
108 
109 	/* It's amazing how much good grammar really matters... */
110 	if (!isocean)
111 		snprintf(title, sizeof title, "Countries in %s",
112 			 continent->title);
113 	else
114 		snprintf(title, sizeof title, "Islands and groups in the %s",
115 			 continent->title);
116 
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,
121 			 &contp->sc);
122 	if (rv == 0)
123 		return DITEM_LEAVE_MENU;
124 	return DITEM_RECREATE;
125 }
126 
127 static struct continent *
128 find_continent(const char *name)
129 {
130 	int i;
131 
132 	for (i = 0; i < NCONTINENTS; i++) {
133 		if (strcmp(name, continent_names[i].name) == 0)
134 			return continent_names[i].continent;
135 	}
136 	return 0;
137 }
138 
139 struct country {
140 	char *name;
141 	char *tlc;
142 	int nzones;
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 */
147 };
148 
149 struct zone {
150 	TAILQ_ENTRY(zone) link;
151 	char *descr;
152 	char *filename;
153 	struct continent *continent;
154 };
155 
156 /*
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.
160  */
161 #define NCOUNTRIES	(26*26)
162 static struct country countries[NCOUNTRIES];
163 #define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A'))
164 
165 /*
166  * Read the ISO 3166 country code database in _PATH_ISO3166
167  * (/usr/share/misc/iso3166).  On error, exit via err(3).
168  */
169 static void
170 read_iso3166_table(void)
171 {
172 	FILE *fp;
173 	char *s, *t, *name;
174 	size_t len;
175 	int lineno;
176 	struct country *cp;
177 
178 	fp = fopen(_PATH_ISO3166, "r");
179 	if (!fp)
180 		err(1, _PATH_ISO3166);
181 	lineno = 0;
182 
183 	while ((s = fgetln(fp, &len)) != NULL) {
184 		lineno++;
185 		if (s[len - 1] != '\n')
186 			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
187 		s[len - 1] = '\0';
188 		if (s[0] == '#' || strspn(s, " \t") == len - 1)
189 			continue;
190 
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'",
197 			     lineno, t);
198 
199 		name = s;
200 
201 		cp = &countries[CODE2INT(t)];
202 		if (cp->name)
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");
209 		cp->tlc = strdup(t);
210 		if (cp->tlc == NULL)
211 			errx(1, "malloc failed");
212 	}
213 
214 	fclose(fp);
215 }
216 
217 static void
218 add_zone_to_country(int lineno, const char *tlc, const char *descr,
219 		    const char *file, struct continent *cont)
220 {
221 	struct zone *zp;
222 	struct country *cp;
223 
224 	if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
225 		errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid",
226 		     lineno, tlc);
227 
228 	cp = &countries[CODE2INT(tlc)];
229 	if (cp->name == 0)
230 		errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown",
231 		     lineno, tlc);
232 
233 	if (descr) {
234 		if (cp->nzones < 0)
235 			errx(1, _PATH_ZONETAB
236 			     ":%d: conflicting zone definition", lineno);
237 
238 		zp = malloc(sizeof *zp);
239 		if (zp == NULL)
240 			errx(1, "malloc(%lu)", (unsigned long)sizeof *zp);
241 
242 		if (cp->nzones == 0)
243 			TAILQ_INIT(&cp->zones);
244 
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);
253 		cp->nzones++;
254 	} else {
255 		if (cp->nzones > 0)
256 			errx(1, _PATH_ZONETAB
257 			     ":%d: zone must have description", lineno);
258 		if (cp->nzones < 0)
259 			errx(1, _PATH_ZONETAB
260 			     ":%d: zone multiply defined", lineno);
261 		cp->nzones = -1;
262 		cp->filename = strdup(file);
263 		if (cp->filename == NULL)
264 			errx(1, "malloc failed");
265 		cp->continent = cont;
266 	}
267 }
268 
269 /*
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.
273  */
274 static int
275 compare_countries(const void *xa, const void *xb)
276 {
277 	const struct country *a = xa, *b = xb;
278 
279 	if (a->name == 0 && b->name == 0)
280 		return 0;
281 	if (a->name == 0 && b->name != 0)
282 		return 1;
283 	if (b->name == 0)
284 		return -1;
285 
286 	return strcmp(a->name, b->name);
287 }
288 
289 /*
290  * This must be done AFTER all zone descriptions are read, since it breaks
291  * CODE2INT().
292  */
293 static void
294 sort_countries(void)
295 {
296 	qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
297 }
298 
299 static void
300 read_zones(void)
301 {
302 	FILE *fp;
303 	char *line;
304 	size_t len;
305 	int lineno;
306 	char *tlc, *coord, *file, *descr, *p;
307 	char contbuf[16];
308 	struct continent *cont;
309 
310 	fp = fopen(_PATH_ZONETAB, "r");
311 	if (!fp)
312 		err(1, _PATH_ZONETAB);
313 	lineno = 0;
314 
315 	while ((line = fgetln(fp, &len)) != NULL) {
316 		lineno++;
317 		if (line[len - 1] != '\n')
318 			errx(1, _PATH_ZONETAB ":%d: invalid format", lineno);
319 		line[len - 1] = '\0';
320 		if (line[0] == '#')
321 			continue;
322 
323 		tlc = strsep(&line, "\t");
324 		if (strlen(tlc) != 2)
325 			errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'",
326 			     lineno, tlc);
327 		coord = strsep(&line, "\t");
328 		file = strsep(&line, "\t");
329 		p = strchr(file, '/');
330 		if (p == NULL)
331 			errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'",
332 			     lineno, file);
333 		contbuf[0] = '\0';
334 		strncat(contbuf, file, p - file);
335 		cont = find_continent(contbuf);
336 		if (!cont)
337 			errx(1, _PATH_ZONETAB ":%d: invalid region `%s'",
338 			     lineno, contbuf);
339 
340 		descr = (line && *line) ? line : 0;
341 
342 		add_zone_to_country(lineno, tlc, descr, file, cont);
343 	}
344 	fclose(fp);
345 }
346 
347 static void
348 make_menus(void)
349 {
350 	struct country *cp;
351 	struct zone *zp, *zp2;
352 	struct continent *cont;
353 	dialogMenuItem *dmi;
354 	int i;
355 
356 	/*
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
360 	 * continents/oceans.
361 	 */
362 	for (cp = countries; cp->name; cp++) {
363 		if (cp->nzones == 0)
364 			continue;
365 		if (cp->nzones < 0) {
366 			cp->continent->nitems++;
367 		} else {
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)
374 					;
375 				if (zp2 == zp)
376 					zp->continent->nitems++;
377 			}
378 		}
379 	}
380 
381 	/*
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.
385 	 */
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;
393 	}
394 
395 	/*
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.
399 	 */
400 	for (cp = countries; cp->name; cp++) {
401 		if (cp->nzones == 0)
402 			continue;
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;
409 			dmi->checked = 0;
410 			dmi->fire = set_zone_whole_country;
411 			dmi->selected = 0;
412 			dmi->data = cp;
413 		} else {
414 			cp->submenu = malloc(cp->nzones * sizeof *dmi);
415 			if (cp->submenu == 0)
416 				errx(1, "malloc for submenu");
417 			cp->nzones = 0;
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",
424 					 ++cp->nzones);
425 				dmi->title = zp->descr;
426 				dmi->checked = 0;
427 				dmi->fire = set_zone_multi;
428 				dmi->selected = 0;
429 				dmi->data = zp;
430 
431 				for (zp2 = cp->zones.tqh_first;
432 				     zp2->continent != cont;
433 				     zp2 = zp2->link.tqe_next)
434 					;
435 				if (zp2 != zp)
436 					continue;
437 
438 				dmi = &cont->menu[cont->nitems];
439 				memset(dmi, 0, sizeof *dmi);
440 				asprintf(&dmi->prompt, "%d", ++cont->nitems);
441 				dmi->title = cp->name;
442 				dmi->checked = 0;
443 				dmi->fire = set_zone_menu;
444 				dmi->selected = 0;
445 				dmi->data = cp;
446 			}
447 		}
448 	}
449 }
450 
451 static int
452 set_zone_menu(dialogMenuItem *dmi)
453 {
454 	int rv;
455 	char buf[256];
456 	struct country *cp = dmi->data;
457 	int menulen;
458 
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);
464 	if (rv != 0)
465 		return DITEM_RECREATE;
466 	return DITEM_LEAVE_MENU;
467 }
468 
469 static int
470 install_zone_file(const char *filename)
471 {
472 	struct stat sb;
473 	int fd1, fd2;
474 	int copymode;
475 	char *msg;
476 	ssize_t len;
477 	char buf[1024];
478 
479 	if (lstat(_PATH_LOCALTIME, &sb) < 0)
480 		/* Nothing there yet... */
481 		copymode = 1;
482 	else if(S_ISLNK(sb.st_mode))
483 		copymode = 0;
484 	else
485 		copymode = 1;
486 
487 #ifdef VERBOSE
488 	if (copymode)
489 		asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename);
490 	else
491 		asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME
492 			 " to %s", filename);
493 
494 	dialog_notify(msg);
495 	free(msg);
496 #endif
497 
498 	if (reallydoit) {
499 		if (copymode) {
500 			fd1 = open(filename, O_RDONLY, 0);
501 			if (fd1 < 0) {
502 				asprintf(&msg, "Could not open %s: %s",
503 					 filename, strerror(errno));
504 				dialog_mesgbox("Error", msg, 8, 72);
505 				free(msg);
506 				return DITEM_FAILURE | DITEM_RECREATE;
507 			}
508 
509 			unlink(_PATH_LOCALTIME);
510 			fd2 = open(_PATH_LOCALTIME,
511 				   O_CREAT|O_EXCL|O_WRONLY,
512 				   S_IRUSR|S_IRGRP|S_IROTH);
513 			if (fd2 < 0) {
514 				asprintf(&msg, "Could not open "
515 					 _PATH_LOCALTIME ": %s",
516 					 strerror(errno));
517 				dialog_mesgbox("Error", msg, 8, 72);
518 				free(msg);
519 				return DITEM_FAILURE | DITEM_RECREATE;
520 			}
521 
522 			while ((len = read(fd1, buf, sizeof buf)) > 0)
523 				len = write(fd2, buf, len);
524 
525 			if (len == -1) {
526 				asprintf(&msg, "Error copying %s to "
527 					 _PATH_LOCALTIME ": %s",
528 					 filename, strerror(errno));
529 				dialog_mesgbox("Error", msg, 8, 72);
530 				free(msg);
531 				/* Better to leave none than a corrupt one. */
532 				unlink(_PATH_LOCALTIME);
533 				return DITEM_FAILURE | DITEM_RECREATE;
534 			}
535 			close(fd1);
536 			close(fd2);
537 		} else {
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);
542 				free(msg);
543 				return DITEM_FAILURE | DITEM_RECREATE;
544 			}
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);
551 				free(msg);
552 				return DITEM_FAILURE | DITEM_RECREATE;
553 			}
554 		}
555 	}
556 
557 #ifdef VERBOSE
558 	if (copymode)
559 		asprintf(&msg, "Copied timezone file from %s to "
560 			 _PATH_LOCALTIME, filename);
561 	else
562 		asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME
563 			 " to %s", filename);
564 
565 	dialog_mesgbox("Done", msg, 8, 72);
566 	free(msg);
567 #endif
568 	return DITEM_LEAVE_MENU;
569 }
570 
571 static int
572 confirm_zone(const char *filename)
573 {
574 	char *msg;
575 	struct tm *tm;
576 	time_t t = time(0);
577 	int rv;
578 
579 	if (setenv("TZ", filename, 1) == -1)
580 		err(1, "setenv: cannot set TZ=%s", filename);
581 	tzset();
582 	tm = localtime(&t);
583 
584 	asprintf(&msg, "Does the abbreviation `%s' look reasonable?",
585 		 tm->tm_zone);
586 	rv = !dialog_yesno("Confirmation", msg, 4, 72);
587 	free(msg);
588 	return rv;
589 }
590 
591 static int
592 set_zone_multi(dialogMenuItem *dmi)
593 {
594 	char *fn;
595 	struct zone *zp = dmi->data;
596 	int rv;
597 
598 	if (!confirm_zone(zp->filename))
599 		return DITEM_FAILURE | DITEM_RECREATE;
600 
601 	asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename);
602 	rv = install_zone_file(fn);
603 	free(fn);
604 	return rv;
605 }
606 
607 static int
608 set_zone_whole_country(dialogMenuItem *dmi)
609 {
610 	char *fn;
611 	struct country *cp = dmi->data;
612 	int rv;
613 
614 	if (!confirm_zone(cp->filename))
615 		return DITEM_FAILURE | DITEM_RECREATE;
616 
617 	asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename);
618 	rv = install_zone_file(fn);
619 	free(fn);
620 	return rv;
621 }
622 
623 static void
624 usage(void)
625 {
626 	fprintf(stderr, "usage: tzsetup [-n]\n");
627 	exit(1);
628 }
629 
630 int
631 main(int argc, char **argv)
632 {
633 	int c, fd;
634 	int (*dialog_utc)(unsigned char *, unsigned char *, int, int);
635 
636 	dialog_utc = dialog_noyes;
637 
638 	while ((c = getopt(argc, argv, "n")) != -1) {
639 		switch(c) {
640 		case 'n':
641 			reallydoit = 0;
642 			break;
643 
644 		default:
645 			usage();
646 		}
647 	}
648 
649 	if (argc - optind > 1)
650 		usage();
651 
652 	/* Override the user-supplied umask. */
653 	umask(S_IWGRP|S_IWOTH);
654 
655 	read_iso3166_table();
656 	read_zones();
657 	sort_countries();
658 	make_menus();
659 
660 	init_dialog();
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)) {
664 		if (reallydoit)
665 			unlink(_PATH_WALL_CMOS_CLOCK);
666 	} else {
667 		if (reallydoit) {
668 			fd = open(_PATH_WALL_CMOS_CLOCK,
669 				  O_WRONLY|O_CREAT|O_TRUNC,
670 				  S_IRUSR|S_IRGRP|S_IROTH);
671 			if (fd < 0)
672 				err(1, "create %s", _PATH_WALL_CMOS_CLOCK);
673 			close(fd);
674 		}
675 	}
676 	dialog_clear_norefresh();
677 	if (optind == argc - 1) {
678 		char *msg;
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]);
682 			dialog_clear();
683 			end_dialog();
684 			return 0;
685 		}
686 		free(msg);
687 		dialog_clear_norefresh();
688 	}
689 	dialog_menu("Time Zone Selector", "Select a region", -1, -1,
690 		    NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);
691 
692 	dialog_clear();
693 	end_dialog();
694 	return 0;
695 }
696 
697