xref: /freebsd/usr.sbin/tzsetup/tzsetup.c (revision 1e413cf93298b5b97441a21d9a50fdcd0ee9945e)
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 (int)((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 or region"), -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 			TAILQ_FOREACH(zp, &cp->zones, link) {
382 				cont = zp->continent;
383 				for (zp2 = TAILQ_FIRST(&cp->zones);
384 				     zp2->continent != cont;
385 				     zp2 = TAILQ_NEXT(zp2, link))
386 					;
387 				if (zp2 == zp)
388 					zp->continent->nitems++;
389 			}
390 		}
391 	}
392 
393 	/*
394 	 * Now allocate memory for the country menus.  We set
395 	 * nitems back to zero so that we can use it for counting
396 	 * again when we actually build the menus.
397 	 */
398 	for (i = 0; i < NCONTINENTS; i++) {
399 		continent_names[i].continent->menu =
400 			malloc(sizeof(dialogMenuItem) *
401 			       continent_names[i].continent->nitems);
402 		if (continent_names[i].continent->menu == 0)
403 			errx(1, "malloc for continent menu");
404 		continent_names[i].continent->nitems = 0;
405 	}
406 
407 	/*
408 	 * Now that memory is allocated, create the menu items for
409 	 * each continent.  For multiple-zone countries, also create
410 	 * the country's zone submenu.
411 	 */
412 	for (cp = countries; cp->name; cp++) {
413 		if (cp->nzones == 0)
414 			continue;
415 		if (cp->nzones < 0) {
416 			dmi = &cp->continent->menu[cp->continent->nitems];
417 			memset(dmi, 0, sizeof *dmi);
418 			asprintf(&dmi->prompt, "%d",
419 				 ++cp->continent->nitems);
420 			dmi->title = cp->name;
421 			dmi->checked = 0;
422 			dmi->fire = set_zone_whole_country;
423 			dmi->selected = 0;
424 			dmi->data = cp;
425 		} else {
426 			cp->submenu = malloc(cp->nzones * sizeof *dmi);
427 			if (cp->submenu == 0)
428 				errx(1, "malloc for submenu");
429 			cp->nzones = 0;
430 			TAILQ_FOREACH(zp, &cp->zones, link) {
431 				cont = zp->continent;
432 				dmi = &cp->submenu[cp->nzones];
433 				memset(dmi, 0, sizeof *dmi);
434 				asprintf(&dmi->prompt, "%d",
435 					 ++cp->nzones);
436 				dmi->title = zp->descr;
437 				dmi->checked = 0;
438 				dmi->fire = set_zone_multi;
439 				dmi->selected = 0;
440 				dmi->data = zp;
441 
442 				for (zp2 = TAILQ_FIRST(&cp->zones);
443 				     zp2->continent != cont;
444 				     zp2 = TAILQ_NEXT(zp2, link))
445 					;
446 				if (zp2 != zp)
447 					continue;
448 
449 				dmi = &cont->menu[cont->nitems];
450 				memset(dmi, 0, sizeof *dmi);
451 				asprintf(&dmi->prompt, "%d", ++cont->nitems);
452 				dmi->title = cp->name;
453 				dmi->checked = 0;
454 				dmi->fire = set_zone_menu;
455 				dmi->selected = 0;
456 				dmi->data = cp;
457 			}
458 		}
459 	}
460 }
461 
462 static int
463 set_zone_menu(dialogMenuItem *dmi)
464 {
465 	int rv;
466 	char buf[256];
467 	struct country *cp = dmi->data;
468 	int menulen;
469 
470 	snprintf(buf, sizeof buf, "%s Time Zones", cp->name);
471 	menulen = cp->nzones < 16 ? cp->nzones : 16;
472 	rv = dialog_menu(buf, "Select a zone which observes the same time as "
473 			 "your locality.", -1, -1, menulen, -cp->nzones,
474 			 cp->submenu, 0, 0, 0);
475 	if (rv != 0)
476 		return DITEM_RECREATE;
477 	return DITEM_LEAVE_MENU;
478 }
479 
480 static int
481 install_zone_file(const char *filename)
482 {
483 	struct stat sb;
484 	int fd1, fd2;
485 	int copymode;
486 	char *msg;
487 	ssize_t len;
488 	char buf[1024];
489 
490 	if (lstat(_PATH_LOCALTIME, &sb) < 0)
491 		/* Nothing there yet... */
492 		copymode = 1;
493 	else if(S_ISLNK(sb.st_mode))
494 		copymode = 0;
495 	else
496 		copymode = 1;
497 
498 #ifdef VERBOSE
499 	if (copymode)
500 		asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename);
501 	else
502 		asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME
503 			 " to %s", filename);
504 
505 	dialog_notify(msg);
506 	free(msg);
507 #endif
508 
509 	if (reallydoit) {
510 		if (copymode) {
511 			fd1 = open(filename, O_RDONLY, 0);
512 			if (fd1 < 0) {
513 				asprintf(&msg, "Could not open %s: %s",
514 					 filename, strerror(errno));
515 				dialog_mesgbox("Error", msg, 8, 72);
516 				free(msg);
517 				return DITEM_FAILURE | DITEM_RECREATE;
518 			}
519 
520 			unlink(_PATH_LOCALTIME);
521 			fd2 = open(_PATH_LOCALTIME,
522 				   O_CREAT|O_EXCL|O_WRONLY,
523 				   S_IRUSR|S_IRGRP|S_IROTH);
524 			if (fd2 < 0) {
525 				asprintf(&msg, "Could not open "
526 					 _PATH_LOCALTIME ": %s",
527 					 strerror(errno));
528 				dialog_mesgbox("Error", msg, 8, 72);
529 				free(msg);
530 				return DITEM_FAILURE | DITEM_RECREATE;
531 			}
532 
533 			while ((len = read(fd1, buf, sizeof buf)) > 0)
534 				len = write(fd2, buf, len);
535 
536 			if (len == -1) {
537 				asprintf(&msg, "Error copying %s to "
538 					 _PATH_LOCALTIME ": %s",
539 					 filename, strerror(errno));
540 				dialog_mesgbox("Error", msg, 8, 72);
541 				free(msg);
542 				/* Better to leave none than a corrupt one. */
543 				unlink(_PATH_LOCALTIME);
544 				return DITEM_FAILURE | DITEM_RECREATE;
545 			}
546 			close(fd1);
547 			close(fd2);
548 		} else {
549 			if (access(filename, R_OK) != 0) {
550 				asprintf(&msg, "Cannot access %s: %s",
551 					 filename, strerror(errno));
552 				dialog_mesgbox("Error", msg, 8, 72);
553 				free(msg);
554 				return DITEM_FAILURE | DITEM_RECREATE;
555 			}
556 			unlink(_PATH_LOCALTIME);
557 			if (symlink(filename, _PATH_LOCALTIME) < 0) {
558 				asprintf(&msg, "Cannot create symbolic link "
559 					 _PATH_LOCALTIME " to %s: %s",
560 					 filename, strerror(errno));
561 				dialog_mesgbox("Error", msg, 8, 72);
562 				free(msg);
563 				return DITEM_FAILURE | DITEM_RECREATE;
564 			}
565 		}
566 	}
567 
568 #ifdef VERBOSE
569 	if (copymode)
570 		asprintf(&msg, "Copied timezone file from %s to "
571 			 _PATH_LOCALTIME, filename);
572 	else
573 		asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME
574 			 " to %s", filename);
575 
576 	dialog_mesgbox("Done", msg, 8, 72);
577 	free(msg);
578 #endif
579 	return DITEM_LEAVE_MENU;
580 }
581 
582 static int
583 confirm_zone(const char *filename)
584 {
585 	char *msg;
586 	struct tm *tm;
587 	time_t t = time(0);
588 	int rv;
589 
590 	setenv("TZ", filename, 1);
591 	tzset();
592 	tm = localtime(&t);
593 
594 	asprintf(&msg, "Does the abbreviation `%s' look reasonable?",
595 		 tm->tm_zone);
596 	rv = !dialog_yesno("Confirmation", msg, 5, 72);
597 	free(msg);
598 	return rv;
599 }
600 
601 static int
602 set_zone_multi(dialogMenuItem *dmi)
603 {
604 	char *fn;
605 	struct zone *zp = dmi->data;
606 	int rv;
607 
608 	if (!confirm_zone(zp->filename))
609 		return DITEM_FAILURE | DITEM_RECREATE;
610 
611 	asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename);
612 	rv = install_zone_file(fn);
613 	free(fn);
614 	return rv;
615 }
616 
617 static int
618 set_zone_whole_country(dialogMenuItem *dmi)
619 {
620 	char *fn;
621 	struct country *cp = dmi->data;
622 	int rv;
623 
624 	if (!confirm_zone(cp->filename))
625 		return DITEM_FAILURE | DITEM_RECREATE;
626 
627 	asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename);
628 	rv = install_zone_file(fn);
629 	free(fn);
630 	return rv;
631 }
632 
633 static void
634 usage()
635 {
636 	fprintf(stderr, "usage: tzsetup [-n]\n");
637 	exit(1);
638 }
639 
640 int
641 main(int argc, char **argv)
642 {
643 	int c, fd;
644 	int (*dialog_utc)(unsigned char *, unsigned char *, int, int);
645 
646 #if defined(__alpha__) || defined(__sparc64__)
647 	dialog_utc = dialog_yesno;
648 #else
649 	dialog_utc = dialog_noyes;
650 #endif
651 
652 	while ((c = getopt(argc, argv, "n")) != -1) {
653 		switch(c) {
654 		case 'n':
655 			reallydoit = 0;
656 			break;
657 
658 		default:
659 			usage();
660 		}
661 	}
662 
663 	if (argc - optind > 1)
664 		usage();
665 
666 	/* Override the user-supplied umask. */
667 	(void)umask(S_IWGRP|S_IWOTH);
668 
669 	read_iso3166_table();
670 	read_zones();
671 	sort_countries();
672 	make_menus();
673 
674 	init_dialog();
675 	if (!dialog_utc("Select local or UTC (Greenwich Mean Time) clock",
676 			"Is this machine's CMOS clock set to UTC?  If it is set to local time,\n"
677 			"or you don't know, please choose NO here!", 7, 72)) {
678 		if (reallydoit)
679 			unlink(_PATH_WALL_CMOS_CLOCK);
680 	} else {
681 		if (reallydoit) {
682 			fd = open(_PATH_WALL_CMOS_CLOCK,
683 				  O_WRONLY|O_CREAT|O_TRUNC,
684 				  S_IRUSR|S_IRGRP|S_IROTH);
685 			if (fd < 0)
686 				err(1, "create %s", _PATH_WALL_CMOS_CLOCK);
687 			close(fd);
688 		}
689 	}
690 	dialog_clear_norefresh();
691 	if (optind == argc - 1) {
692 		char *msg;
693 		asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]);
694 		if (!dialog_yesno("Default timezone provided", msg, 7, 72)) {
695 			install_zone_file(argv[optind]);
696 			dialog_clear();
697 			end_dialog();
698 			return 0;
699 		}
700 		free(msg);
701 		dialog_clear_norefresh();
702 	}
703 	dialog_menu("Time Zone Selector", "Select a region", -1, -1,
704 		    NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);
705 
706 	dialog_clear();
707 	end_dialog();
708 	return 0;
709 }
710 
711