xref: /dragonfly/usr.sbin/tzsetup/tzsetup.c (revision 62f7f702)
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.7 2008/03/02 14:28:37 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 set_zone_menu(&contp->menu[0]);
109 	}
110 
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);
118 
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;
127 }
128 
129 static struct continent *
130 find_continent(const char *name)
131 {
132 	int i;
133 
134 	for (i = 0; i < NCONTINENTS; i++) {
135 		if (strcmp(name, continent_names[i].name) == 0)
136 			return continent_names[i].continent;
137 	}
138 	return 0;
139 }
140 
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 */
149 };
150 
151 struct zone {
152 	TAILQ_ENTRY(zone) link;
153 	char *descr;
154 	char *filename;
155 	struct continent *continent;
156 };
157 
158 /*
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.
162  */
163 #define NCOUNTRIES	(26*26)
164 static struct country countries[NCOUNTRIES];
165 #define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A'))
166 
167 /*
168  * Read the ISO 3166 country code database in _PATH_ISO3166
169  * (/usr/share/misc/iso3166).  On error, exit via err(3).
170  */
171 static void
172 read_iso3166_table(void)
173 {
174 	FILE *fp;
175 	char *s, *t, *name;
176 	size_t len;
177 	int lineno;
178 	struct country *cp;
179 
180 	fp = fopen(_PATH_ISO3166, "r");
181 	if (!fp)
182 		err(1, _PATH_ISO3166);
183 	lineno = 0;
184 
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;
192 
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);
200 
201 		name = s;
202 
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");
214 	}
215 
216 	fclose(fp);
217 }
218 
219 static void
220 add_zone_to_country(int lineno, const char *tlc, const char *descr,
221 		    const char *file, struct continent *cont)
222 {
223 	struct zone *zp;
224 	struct country *cp;
225 
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);
229 
230 	cp = &countries[CODE2INT(tlc)];
231 	if (cp->name == 0)
232 		errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown",
233 		     lineno, tlc);
234 
235 	if (descr) {
236 		if (cp->nzones < 0)
237 			errx(1, _PATH_ZONETAB
238 			     ":%d: conflicting zone definition", lineno);
239 
240 		zp = malloc(sizeof *zp);
241 		if (zp == 0)
242 			errx(1, "malloc(%lu)", (unsigned long)sizeof *zp);
243 
244 		if (cp->nzones == 0)
245 			TAILQ_INIT(&cp->zones);
246 
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;
268 	}
269 }
270 
271 /*
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.
275  */
276 static int
277 compare_countries(const void *xa, const void *xb)
278 {
279 	const struct country *a = xa, *b = xb;
280 
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;
287 
288 	return strcmp(a->name, b->name);
289 }
290 
291 /*
292  * This must be done AFTER all zone descriptions are read, since it breaks
293  * CODE2INT().
294  */
295 static void
296 sort_countries(void)
297 {
298 	qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
299 }
300 
301 static void
302 read_zones(void)
303 {
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;
311 
312 	fp = fopen(_PATH_ZONETAB, "r");
313 	if (!fp)
314 		err(1, _PATH_ZONETAB);
315 	lineno = 0;
316 
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;
324 
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);
341 
342 		descr = (line && *line) ? line : 0;
343 
344 		add_zone_to_country(lineno, tlc, descr, file, cont);
345 	}
346 	fclose(fp);
347 }
348 
349 static void
350 make_menus(void)
351 {
352 	struct country *cp;
353 	struct zone *zp, *zp2;
354 	struct continent *cont;
355 	dialogMenuItem *dmi;
356 	int i;
357 
358 	/*
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.
363 	 */
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)
376 					;
377 				if (zp2 == zp)
378 					zp->continent->nitems++;
379 			}
380 		}
381 	}
382 
383 	/*
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.
387 	 */
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;
395 	}
396 
397 	/*
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.
401 	 */
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;
432 
433 				for (zp2 = cp->zones.tqh_first;
434 				     zp2->continent != cont;
435 				     zp2 = zp2->link.tqe_next)
436 					;
437 				if (zp2 != zp)
438 					continue;
439 
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;
448 			}
449 		}
450 	}
451 }
452 
453 static int
454 set_zone_menu(dialogMenuItem *dmi)
455 {
456 	int rv;
457 	char buf[256];
458 	struct country *cp = dmi->data;
459 	int menulen;
460 
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;
469 }
470 
471 static int
472 install_zone_file(const char *filename)
473 {
474 	struct stat sb;
475 	int fd1, fd2;
476 	int copymode;
477 	char *msg;
478 	ssize_t len;
479 	char buf[1024];
480 
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;
488 
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);
495 
496 	dialog_notify(msg);
497 	free(msg);
498 #endif
499 
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;
509 			}
510 
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;
522 			}
523 
524 			while ((len = read(fd1, buf, sizeof buf)) > 0)
525 				len = write(fd2, buf, len);
526 
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;
536 			}
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;
546 			}
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;
555 			}
556 		}
557 	}
558 
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);
566 
567 	dialog_mesgbox("Done", msg, 8, 72);
568 	free(msg);
569 #endif
570 	return DITEM_LEAVE_MENU;
571 }
572 
573 static int
574 confirm_zone(const char *filename)
575 {
576 	char *msg;
577 	struct tm *tm;
578 	time_t t = time(0);
579 	int rv;
580 
581 	if (setenv("TZ", filename, 1) == -1)
582 		err(1, "setenv: cannot set TZ=%s", filename);
583 	tzset();
584 	tm = localtime(&t);
585 
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;
591 }
592 
593 static int
594 set_zone_multi(dialogMenuItem *dmi)
595 {
596 	char *fn;
597 	struct zone *zp = dmi->data;
598 	int rv;
599 
600 	if (!confirm_zone(zp->filename))
601 		return DITEM_FAILURE | DITEM_RECREATE;
602 
603 	asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename);
604 	rv = install_zone_file(fn);
605 	free(fn);
606 	return rv;
607 }
608 
609 static int
610 set_zone_whole_country(dialogMenuItem *dmi)
611 {
612 	char *fn;
613 	struct country *cp = dmi->data;
614 	int rv;
615 
616 	if (!confirm_zone(cp->filename))
617 		return DITEM_FAILURE | DITEM_RECREATE;
618 
619 	asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename);
620 	rv = install_zone_file(fn);
621 	free(fn);
622 	return rv;
623 }
624 
625 static void
626 usage(void)
627 {
628 	fprintf(stderr, "usage: tzsetup [-n]\n");
629 	exit(1);
630 }
631 
632 int
633 main(int argc, char **argv)
634 {
635 	int c, fd;
636 	int (*dialog_utc)(unsigned char *, unsigned char *, int, int);
637 
638 	dialog_utc = dialog_noyes;
639 
640 	while ((c = getopt(argc, argv, "n")) != -1) {
641 		switch(c) {
642 		case 'n':
643 			reallydoit = 0;
644 			break;
645 
646 		default:
647 			usage();
648 		}
649 	}
650 
651 	if (argc - optind > 1)
652 		usage();
653 
654 	/* Override the user-supplied umask. */
655 	umask(S_IWGRP|S_IWOTH);
656 
657 	read_iso3166_table();
658 	read_zones();
659 	sort_countries();
660 	make_menus();
661 
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);
676 		}
677 	}
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;
687 		}
688 		free(msg);
689 		dialog_clear_norefresh();
690 	}
691 	dialog_menu("Time Zone Selector", "Select a region", -1, -1,
692 		    NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);
693 
694 	dialog_clear();
695 	end_dialog();
696 	return 0;
697 }
698 
699