xref: /freebsd/usr.sbin/tzsetup/tzsetup.c (revision 27beb2e98db3193bd22010b9eb00cc7787bb0a2f)
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 
30 /*
31  * Second attempt at a `tzmenu' program, using the separate description
32  * files provided in newer tzdata releases.
33  */
34 
35 #ifndef lint
36 static const char rcsid[] =
37   "$FreeBSD$";
38 #endif /* not lint */
39 
40 #include <sys/types.h>
41 #include <dialog.h>
42 #include <err.h>
43 #include <errno.h>
44 #include <stdio.h>
45 #include <stdlib.h>
46 #include <string.h>
47 #include <time.h>
48 #include <unistd.h>
49 
50 #include <sys/fcntl.h>
51 #include <sys/queue.h>
52 #include <sys/stat.h>
53 
54 #include "paths.h"
55 
56 static int reallydoit = 1;
57 
58 static int continent_country_menu(dialogMenuItem *);
59 static int set_zone_multi(dialogMenuItem *);
60 static int set_zone_whole_country(dialogMenuItem *);
61 static int set_zone_menu(dialogMenuItem *);
62 
63 struct continent {
64 	dialogMenuItem *menu;
65 	int nitems;
66 	int ch;
67 	int sc;
68 };
69 
70 static struct continent africa, america, antarctica, arctic, asia, atlantic;
71 static struct continent australia, europe, indian, pacific;
72 
73 static struct continent_names {
74 	char *name;
75 	struct continent *continent;
76 } continent_names[] = {
77 	{ "Africa", &africa }, { "America", &america },
78 	{ "Antarctica", &antarctica }, { "Arctic", &arctic },
79 	{ "Asia", &asia },
80 	{ "Atlantic", &atlantic }, { "Australia", &australia },
81 	{ "Europe", &europe }, { "Indian", &indian }, { "Pacific", &pacific }
82 };
83 
84 static dialogMenuItem continents[] = {
85 	{ "1", "Africa", 0, continent_country_menu, 0, &africa },
86 	{ "2", "America -- North and South", 0, continent_country_menu, 0,
87 		  &america },
88 	{ "3", "Antarctica", 0, continent_country_menu, 0, &antarctica },
89 	{ "4", "Arctic Ocean", 0, continent_country_menu, 0, &arctic },
90 	{ "5", "Asia", 0, continent_country_menu, 0, &asia },
91 	{ "6", "Atlantic Ocean", 0, continent_country_menu, 0, &atlantic },
92 	{ "7", "Australia", 0, continent_country_menu, 0, &australia },
93 	{ "8", "Europe", 0, continent_country_menu, 0, &europe },
94 	{ "9", "Indian Ocean", 0, continent_country_menu, 0, &indian },
95 	{ "0", "Pacific Ocean", 0, continent_country_menu, 0, &pacific }
96 };
97 #define NCONTINENTS ((sizeof continents)/(sizeof continents[0]))
98 #define OCEANP(x) ((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
99 
100 static int
101 continent_country_menu(dialogMenuItem *continent)
102 {
103 	int rv;
104 	struct continent *contp = continent->data;
105 	char title[256];
106 	int isocean = OCEANP(continent - continents);
107 	int menulen;
108 
109 	/* Short cut -- if there's only one country, don't post a menu. */
110 	if (contp->nitems == 1) {
111 		return set_zone_menu(&contp->menu[0]);
112 	}
113 
114 	/* It's amazing how much good grammar really matters... */
115 	if (!isocean)
116 		snprintf(title, sizeof title, "Countries in %s",
117 			 continent->title);
118 	else
119 		snprintf(title, sizeof title, "Islands and groups in the %s",
120 			 continent->title);
121 
122 	menulen = contp->nitems < 16 ? contp->nitems : 16;
123 	rv = dialog_menu(title, (isocean ? "Select an island or group"
124                                  : "Select a country"), -1, -1, menulen,
125 			 -contp->nitems, contp->menu, 0, &contp->ch,
126 			 &contp->sc);
127 	if (rv == 0)
128 		return DITEM_LEAVE_MENU;
129 	return DITEM_RECREATE;
130 }
131 
132 static struct continent *
133 find_continent(const char *name)
134 {
135 	int i;
136 
137 	for (i = 0; i < NCONTINENTS; i++) {
138 		if (strcmp(name, continent_names[i].name) == 0)
139 			return continent_names[i].continent;
140 	}
141 	return 0;
142 }
143 
144 struct country {
145 	char *name;
146 	char *tlc;
147 	int nzones;
148 	char *filename;		/* use iff nzones < 0 */
149 	struct continent *continent; /* use iff nzones < 0 */
150 	TAILQ_HEAD(, zone) zones; /* use iff nzones > 0 */
151 	dialogMenuItem *submenu; /* use iff nzones > 0 */
152 };
153 
154 struct zone {
155 	TAILQ_ENTRY(zone) link;
156 	char *descr;
157 	char *filename;
158 	struct continent *continent;
159 };
160 
161 /*
162  * This is the easiest organization... we use ISO 3166 country codes,
163  * of the two-letter variety, so we just size this array to suit.
164  * Beats worrying about dynamic allocation.
165  */
166 #define NCOUNTRIES	(26*26)
167 static struct country countries[NCOUNTRIES];
168 #define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A'))
169 
170 /*
171  * Read the ISO 3166 country code database in _PATH_ISO3166
172  * (/usr/share/misc/iso3166).  On error, exit via err(3).
173  */
174 static void
175 read_iso3166_table(void)
176 {
177 	FILE *fp;
178 	char *s, *t, *name;
179 	size_t len;
180 	int lineno;
181 	struct country *cp;
182 
183 	fp = fopen(_PATH_ISO3166, "r");
184 	if (!fp)
185 		err(1, _PATH_ISO3166);
186 	lineno = 0;
187 
188 	while ((s = fgetln(fp, &len)) != 0) {
189 		lineno++;
190 		if (s[len - 1] != '\n')
191 			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
192 		s[len - 1] = '\0';
193 		if (s[0] == '#' || strspn(s, " \t") == len - 1)
194 			continue;
195 
196 		/* Isolate the two-letter code. */
197 		t = strsep(&s, "\t");
198 		if (t == 0 || strlen(t) != 2)
199 			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
200 		if (t[0] < 'A' || t[0] > 'Z' || t[1] < 'A' || t[1] > 'Z')
201 			errx(1, _PATH_ISO3166 ":%d: invalid code `%s'",
202 			     lineno, t);
203 
204 		/* Now skip past the three-letter and numeric codes. */
205 		name = strsep(&s, "\t"); /* 3-let */
206 		if (name == 0 || strlen(name) != 3)
207 			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
208 		name = strsep(&s, "\t"); /* numeric */
209 		if (name == 0 || strlen(name) != 3)
210 			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
211 
212 		name = s;
213 
214 		cp = &countries[CODE2INT(t)];
215 		if (cp->name)
216 			errx(1, _PATH_ISO3166
217 			     ":%d: country code `%s' multiply defined: %s",
218 			     lineno, t, cp->name);
219 		cp->name = strdup(name);
220 		if (cp->name == NULL)
221 			errx(1, "malloc failed");
222 		cp->tlc = strdup(t);
223 		if (cp->tlc == NULL)
224 			errx(1, "malloc failed");
225 	}
226 
227 	fclose(fp);
228 }
229 
230 static void
231 add_zone_to_country(int lineno, const char *tlc, const char *descr,
232 		    const char *file, struct continent *cont)
233 {
234 	struct zone *zp;
235 	struct country *cp;
236 
237 	if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
238 		errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid",
239 		     lineno, tlc);
240 
241 	cp = &countries[CODE2INT(tlc)];
242 	if (cp->name == 0)
243 		errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown",
244 		     lineno, tlc);
245 
246 	if (descr) {
247 		if (cp->nzones < 0)
248 			errx(1, _PATH_ZONETAB
249 			     ":%d: conflicting zone definition", lineno);
250 
251 		zp = malloc(sizeof *zp);
252 		if (zp == 0)
253 			errx(1, "malloc(%lu)", (unsigned long)sizeof *zp);
254 
255 		if (cp->nzones == 0)
256 			TAILQ_INIT(&cp->zones);
257 
258 		zp->descr = strdup(descr);
259 		if (zp->descr == NULL)
260 			errx(1, "malloc failed");
261 		zp->filename = strdup(file);
262 		if (zp->filename == NULL)
263 			errx(1, "malloc failed");
264 		zp->continent = cont;
265 		TAILQ_INSERT_TAIL(&cp->zones, zp, link);
266 		cp->nzones++;
267 	} else {
268 		if (cp->nzones > 0)
269 			errx(1, _PATH_ZONETAB
270 			     ":%d: zone must have description", lineno);
271 		if (cp->nzones < 0)
272 			errx(1, _PATH_ZONETAB
273 			     ":%d: zone multiply defined", lineno);
274 		cp->nzones = -1;
275 		cp->filename = strdup(file);
276 		if (cp->filename == NULL)
277 			errx(1, "malloc failed");
278 		cp->continent = cont;
279 	}
280 }
281 
282 /*
283  * This comparison function intentionally sorts all of the null-named
284  * ``countries''---i.e., the codes that don't correspond to a real
285  * country---to the end.  Everything else is lexical by country name.
286  */
287 static int
288 compare_countries(const void *xa, const void *xb)
289 {
290 	const struct country *a = xa, *b = xb;
291 
292 	if (a->name == 0 && b->name == 0)
293 		return 0;
294 	if (a->name == 0 && b->name != 0)
295 		return 1;
296 	if (b->name == 0)
297 		return -1;
298 
299 	return strcmp(a->name, b->name);
300 }
301 
302 /*
303  * This must be done AFTER all zone descriptions are read, since it breaks
304  * CODE2INT().
305  */
306 static void
307 sort_countries(void)
308 {
309 	qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
310 }
311 
312 static void
313 read_zones(void)
314 {
315 	FILE *fp;
316 	char *line;
317 	size_t len;
318 	int lineno;
319 	char *tlc, *coord, *file, *descr, *p;
320 	char contbuf[16];
321 	struct continent *cont;
322 
323 	fp = fopen(_PATH_ZONETAB, "r");
324 	if (!fp)
325 		err(1, _PATH_ZONETAB);
326 	lineno = 0;
327 
328 	while ((line = fgetln(fp, &len)) != 0) {
329 		lineno++;
330 		if (line[len - 1] != '\n')
331 			errx(1, _PATH_ZONETAB ":%d: invalid format", lineno);
332 		line[len - 1] = '\0';
333 		if (line[0] == '#')
334 			continue;
335 
336 		tlc = strsep(&line, "\t");
337 		if (strlen(tlc) != 2)
338 			errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'",
339 			     lineno, tlc);
340 		coord = strsep(&line, "\t");
341 		file = strsep(&line, "\t");
342 		p = strchr(file, '/');
343 		if (p == 0)
344 			errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'",
345 			     lineno, file);
346 		contbuf[0] = '\0';
347 		strncat(contbuf, file, p - file);
348 		cont = find_continent(contbuf);
349 		if (!cont)
350 			errx(1, _PATH_ZONETAB ":%d: invalid region `%s'",
351 			     lineno, contbuf);
352 
353 		descr = (line && *line) ? line : 0;
354 
355 		add_zone_to_country(lineno, tlc, descr, file, cont);
356 	}
357 	fclose(fp);
358 }
359 
360 static void
361 make_menus(void)
362 {
363 	struct country *cp;
364 	struct zone *zp, *zp2;
365 	struct continent *cont;
366 	dialogMenuItem *dmi;
367 	int i;
368 
369 	/*
370 	 * First, count up all the countries in each continent/ocean.
371 	 * Be careful to count those countries which have multiple zones
372 	 * only once for each.  NB: some countries are in multiple
373 	 * continents/oceans.
374 	 */
375 	for (cp = countries; cp->name; cp++) {
376 		if (cp->nzones == 0)
377 			continue;
378 		if (cp->nzones < 0) {
379 			cp->continent->nitems++;
380 		} else {
381 			for (zp = cp->zones.tqh_first; zp;
382 			     zp = zp->link.tqe_next) {
383 				cont = zp->continent;
384 				for (zp2 = cp->zones.tqh_first;
385 				     zp2->continent != cont;
386 				     zp2 = zp2->link.tqe_next)
387 					;
388 				if (zp2 == zp)
389 					zp->continent->nitems++;
390 			}
391 		}
392 	}
393 
394 	/*
395 	 * Now allocate memory for the country menus.  We set
396 	 * nitems back to zero so that we can use it for counting
397 	 * again when we actually build the menus.
398 	 */
399 	for (i = 0; i < NCONTINENTS; i++) {
400 		continent_names[i].continent->menu =
401 			malloc(sizeof(dialogMenuItem) *
402 			       continent_names[i].continent->nitems);
403 		if (continent_names[i].continent->menu == 0)
404 			errx(1, "malloc for continent menu");
405 		continent_names[i].continent->nitems = 0;
406 	}
407 
408 	/*
409 	 * Now that memory is allocated, create the menu items for
410 	 * each continent.  For multiple-zone countries, also create
411 	 * the country's zone submenu.
412 	 */
413 	for (cp = countries; cp->name; cp++) {
414 		if (cp->nzones == 0)
415 			continue;
416 		if (cp->nzones < 0) {
417 			dmi = &cp->continent->menu[cp->continent->nitems];
418 			memset(dmi, 0, sizeof *dmi);
419 			asprintf(&dmi->prompt, "%d",
420 				 ++cp->continent->nitems);
421 			dmi->title = cp->name;
422 			dmi->checked = 0;
423 			dmi->fire = set_zone_whole_country;
424 			dmi->selected = 0;
425 			dmi->data = cp;
426 		} else {
427 			cp->submenu = malloc(cp->nzones * sizeof *dmi);
428 			if (cp->submenu == 0)
429 				errx(1, "malloc for submenu");
430 			cp->nzones = 0;
431 			for (zp = cp->zones.tqh_first; zp;
432 			     zp = zp->link.tqe_next) {
433 				cont = zp->continent;
434 				dmi = &cp->submenu[cp->nzones];
435 				memset(dmi, 0, sizeof *dmi);
436 				asprintf(&dmi->prompt, "%d",
437 					 ++cp->nzones);
438 				dmi->title = zp->descr;
439 				dmi->checked = 0;
440 				dmi->fire = set_zone_multi;
441 				dmi->selected = 0;
442 				dmi->data = zp;
443 
444 				for (zp2 = cp->zones.tqh_first;
445 				     zp2->continent != cont;
446 				     zp2 = zp2->link.tqe_next)
447 					;
448 				if (zp2 != zp)
449 					continue;
450 
451 				dmi = &cont->menu[cont->nitems];
452 				memset(dmi, 0, sizeof *dmi);
453 				asprintf(&dmi->prompt, "%d", ++cont->nitems);
454 				dmi->title = cp->name;
455 				dmi->checked = 0;
456 				dmi->fire = set_zone_menu;
457 				dmi->selected = 0;
458 				dmi->data = cp;
459 			}
460 		}
461 	}
462 }
463 
464 static int
465 set_zone_menu(dialogMenuItem *dmi)
466 {
467 	int rv;
468 	char buf[256];
469 	struct country *cp = dmi->data;
470 	int menulen;
471 
472 	snprintf(buf, sizeof buf, "%s Time Zones", cp->name);
473 	menulen = cp->nzones < 16 ? cp->nzones : 16;
474 	rv = dialog_menu(buf, "Select a zone which observes the same time as "
475 			 "your locality.", -1, -1, menulen, -cp->nzones,
476 			 cp->submenu, 0, 0, 0);
477 	if (rv != 0)
478 		return DITEM_RECREATE;
479 	return DITEM_LEAVE_MENU;
480 }
481 
482 static int
483 install_zone_file(const char *filename)
484 {
485 	struct stat sb;
486 	int fd1, fd2;
487 	int copymode;
488 	char *msg;
489 	ssize_t len;
490 	char buf[1024];
491 
492 	if (lstat(_PATH_LOCALTIME, &sb) < 0)
493 		/* Nothing there yet... */
494 		copymode = 1;
495 	else if(S_ISLNK(sb.st_mode))
496 		copymode = 0;
497 	else
498 		copymode = 1;
499 
500 #ifdef VERBOSE
501 	if (copymode)
502 		asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename);
503 	else
504 		asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME
505 			 " to %s", filename);
506 
507 	dialog_notify(msg);
508 	free(msg);
509 #endif
510 
511 	if (reallydoit) {
512 		if (copymode) {
513 			fd1 = open(filename, O_RDONLY, 0);
514 			if (fd1 < 0) {
515 				asprintf(&msg, "Could not open %s: %s",
516 					 filename, strerror(errno));
517 				dialog_mesgbox("Error", msg, 8, 72);
518 				free(msg);
519 				return DITEM_FAILURE | DITEM_RECREATE;
520 			}
521 
522 			unlink(_PATH_LOCALTIME);
523 			fd2 = open(_PATH_LOCALTIME,
524 				   O_CREAT|O_EXCL|O_WRONLY,
525 				   S_IRUSR|S_IRGRP|S_IROTH);
526 			if (fd2 < 0) {
527 				asprintf(&msg, "Could not open "
528 					 _PATH_LOCALTIME ": %s",
529 					 strerror(errno));
530 				dialog_mesgbox("Error", msg, 8, 72);
531 				free(msg);
532 				return DITEM_FAILURE | DITEM_RECREATE;
533 			}
534 
535 			while ((len = read(fd1, buf, sizeof buf)) > 0)
536 				len = write(fd2, buf, len);
537 
538 			if (len == -1) {
539 				asprintf(&msg, "Error copying %s to "
540 					 _PATH_LOCALTIME ": %s",
541 					 filename, strerror(errno));
542 				dialog_mesgbox("Error", msg, 8, 72);
543 				free(msg);
544 				/* Better to leave none than a corrupt one. */
545 				unlink(_PATH_LOCALTIME);
546 				return DITEM_FAILURE | DITEM_RECREATE;
547 			}
548 			close(fd1);
549 			close(fd2);
550 		} else {
551 			if (access(filename, R_OK) != 0) {
552 				asprintf(&msg, "Cannot access %s: %s",
553 					 filename, strerror(errno));
554 				dialog_mesgbox("Error", msg, 8, 72);
555 				free(msg);
556 				return DITEM_FAILURE | DITEM_RECREATE;
557 			}
558 			unlink(_PATH_LOCALTIME);
559 			if (symlink(filename, _PATH_LOCALTIME) < 0) {
560 				asprintf(&msg, "Cannot create symbolic link "
561 					 _PATH_LOCALTIME " to %s: %s",
562 					 filename, strerror(errno));
563 				dialog_mesgbox("Error", msg, 8, 72);
564 				free(msg);
565 				return DITEM_FAILURE | DITEM_RECREATE;
566 			}
567 		}
568 	}
569 
570 #ifdef VERBOSE
571 	if (copymode)
572 		asprintf(&msg, "Copied timezone file from %s to "
573 			 _PATH_LOCALTIME, filename);
574 	else
575 		asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME
576 			 " to %s", filename);
577 
578 	dialog_mesgbox("Done", msg, 8, 72);
579 	free(msg);
580 #endif
581 	return DITEM_LEAVE_MENU;
582 }
583 
584 static int
585 confirm_zone(const char *filename)
586 {
587 	char *msg;
588 	struct tm *tm;
589 	time_t t = time(0);
590 	int rv;
591 
592 	setenv("TZ", filename, 1);
593 	tzset();
594 	tm = localtime(&t);
595 
596 	asprintf(&msg, "Does the abbreviation `%s' look reasonable?",
597 		 tm->tm_zone);
598 	rv = !dialog_yesno("Confirmation", msg, 4, 72);
599 	free(msg);
600 	return rv;
601 }
602 
603 static int
604 set_zone_multi(dialogMenuItem *dmi)
605 {
606 	char *fn;
607 	struct zone *zp = dmi->data;
608 	int rv;
609 
610 	if (!confirm_zone(zp->filename))
611 		return DITEM_FAILURE | DITEM_RECREATE;
612 
613 	asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename);
614 	rv = install_zone_file(fn);
615 	free(fn);
616 	return rv;
617 }
618 
619 static int
620 set_zone_whole_country(dialogMenuItem *dmi)
621 {
622 	char *fn;
623 	struct country *cp = dmi->data;
624 	int rv;
625 
626 	if (!confirm_zone(cp->filename))
627 		return DITEM_FAILURE | DITEM_RECREATE;
628 
629 	asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename);
630 	rv = install_zone_file(fn);
631 	free(fn);
632 	return rv;
633 }
634 
635 static void
636 usage()
637 {
638 	fprintf(stderr, "usage: tzsetup [-n]\n");
639 	exit(1);
640 }
641 
642 int
643 main(int argc, char **argv)
644 {
645 	int c, fd;
646 
647 	while ((c = getopt(argc, argv, "n")) != -1) {
648 		switch(c) {
649 		case 'n':
650 			reallydoit = 0;
651 			break;
652 
653 		default:
654 			usage();
655 		}
656 	}
657 
658 	if (argc - optind > 1)
659 		usage();
660 
661 	/* Override the user-supplied umask. */
662 	(void)umask(S_IWGRP|S_IWOTH);
663 
664 	read_iso3166_table();
665 	read_zones();
666 	sort_countries();
667 	make_menus();
668 
669 	init_dialog();
670 	if (!dialog_yesno("Select local or UTC (Greenwich Mean Time) clock",
671 			  "Is this machine's CMOS clock set to UTC?  If it is set to local time,\n"
672 			  "or you don't know, please choose NO here!", 7, 72)) {
673 		if (reallydoit)
674 			unlink(_PATH_WALL_CMOS_CLOCK);
675 	} else {
676 		if (reallydoit) {
677 			fd = open(_PATH_WALL_CMOS_CLOCK,
678 				  O_WRONLY|O_CREAT|O_TRUNC,
679 				  S_IRUSR|S_IRGRP|S_IROTH);
680 			if (fd < 0)
681 				err(1, "create %s", _PATH_WALL_CMOS_CLOCK);
682 			close(fd);
683 		}
684 	}
685 	dialog_clear_norefresh();
686 	if (optind == argc - 1) {
687 		char *msg;
688 		asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]);
689 		if (!dialog_yesno("Default timezone provided", msg, 7, 72)) {
690 			install_zone_file(argv[optind]);
691 			dialog_clear();
692 			end_dialog();
693 			return 0;
694 		}
695 		free(msg);
696 		dialog_clear_norefresh();
697 	}
698 	dialog_menu("Time Zone Selector", "Select a region", -1, -1,
699 		    NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);
700 
701 	dialog_clear();
702 	end_dialog();
703 	return 0;
704 }
705 
706