xref: /dragonfly/usr.sbin/tzsetup/tzsetup.c (revision 7bc7e232)
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.6 2006/01/12 13:43:11 corecode 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 		/* Now skip past the three-letter and numeric codes. */
202 		name = strsep(&s, "\t"); /* 3-let */
203 		if (name == 0 || strlen(name) != 3)
204 			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
205 		name = strsep(&s, "\t"); /* numeric */
206 		if (name == 0 || strlen(name) != 3)
207 			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
208 
209 		name = s;
210 
211 		cp = &countries[CODE2INT(t)];
212 		if (cp->name)
213 			errx(1, _PATH_ISO3166
214 			     ":%d: country code `%s' multiply defined: %s",
215 			     lineno, t, cp->name);
216 		cp->name = strdup(name);
217 		if (cp->name == NULL)
218 			errx(1, "malloc failed");
219 		cp->tlc = strdup(t);
220 		if (cp->tlc == NULL)
221 			errx(1, "malloc failed");
222 	}
223 
224 	fclose(fp);
225 }
226 
227 static void
228 add_zone_to_country(int lineno, const char *tlc, const char *descr,
229 		    const char *file, struct continent *cont)
230 {
231 	struct zone *zp;
232 	struct country *cp;
233 
234 	if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
235 		errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid",
236 		     lineno, tlc);
237 
238 	cp = &countries[CODE2INT(tlc)];
239 	if (cp->name == 0)
240 		errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown",
241 		     lineno, tlc);
242 
243 	if (descr) {
244 		if (cp->nzones < 0)
245 			errx(1, _PATH_ZONETAB
246 			     ":%d: conflicting zone definition", lineno);
247 
248 		zp = malloc(sizeof *zp);
249 		if (zp == 0)
250 			errx(1, "malloc(%lu)", (unsigned long)sizeof *zp);
251 
252 		if (cp->nzones == 0)
253 			TAILQ_INIT(&cp->zones);
254 
255 		zp->descr = strdup(descr);
256 		if (zp->descr == NULL)
257 			errx(1, "malloc failed");
258 		zp->filename = strdup(file);
259 		if (zp->filename == NULL)
260 			errx(1, "malloc failed");
261 		zp->continent = cont;
262 		TAILQ_INSERT_TAIL(&cp->zones, zp, link);
263 		cp->nzones++;
264 	} else {
265 		if (cp->nzones > 0)
266 			errx(1, _PATH_ZONETAB
267 			     ":%d: zone must have description", lineno);
268 		if (cp->nzones < 0)
269 			errx(1, _PATH_ZONETAB
270 			     ":%d: zone multiply defined", lineno);
271 		cp->nzones = -1;
272 		cp->filename = strdup(file);
273 		if (cp->filename == NULL)
274 			errx(1, "malloc failed");
275 		cp->continent = cont;
276 	}
277 }
278 
279 /*
280  * This comparison function intentionally sorts all of the null-named
281  * ``countries''---i.e., the codes that don't correspond to a real
282  * country---to the end.  Everything else is lexical by country name.
283  */
284 static int
285 compare_countries(const void *xa, const void *xb)
286 {
287 	const struct country *a = xa, *b = xb;
288 
289 	if (a->name == 0 && b->name == 0)
290 		return 0;
291 	if (a->name == 0 && b->name != 0)
292 		return 1;
293 	if (b->name == 0)
294 		return -1;
295 
296 	return strcmp(a->name, b->name);
297 }
298 
299 /*
300  * This must be done AFTER all zone descriptions are read, since it breaks
301  * CODE2INT().
302  */
303 static void
304 sort_countries(void)
305 {
306 	qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
307 }
308 
309 static void
310 read_zones(void)
311 {
312 	FILE *fp;
313 	char *line;
314 	size_t len;
315 	int lineno;
316 	char *tlc, *coord, *file, *descr, *p;
317 	char contbuf[16];
318 	struct continent *cont;
319 
320 	fp = fopen(_PATH_ZONETAB, "r");
321 	if (!fp)
322 		err(1, _PATH_ZONETAB);
323 	lineno = 0;
324 
325 	while ((line = fgetln(fp, &len)) != 0) {
326 		lineno++;
327 		if (line[len - 1] != '\n')
328 			errx(1, _PATH_ZONETAB ":%d: invalid format", lineno);
329 		line[len - 1] = '\0';
330 		if (line[0] == '#')
331 			continue;
332 
333 		tlc = strsep(&line, "\t");
334 		if (strlen(tlc) != 2)
335 			errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'",
336 			     lineno, tlc);
337 		coord = strsep(&line, "\t");
338 		file = strsep(&line, "\t");
339 		p = strchr(file, '/');
340 		if (p == 0)
341 			errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'",
342 			     lineno, file);
343 		contbuf[0] = '\0';
344 		strncat(contbuf, file, p - file);
345 		cont = find_continent(contbuf);
346 		if (!cont)
347 			errx(1, _PATH_ZONETAB ":%d: invalid region `%s'",
348 			     lineno, contbuf);
349 
350 		descr = (line && *line) ? line : 0;
351 
352 		add_zone_to_country(lineno, tlc, descr, file, cont);
353 	}
354 	fclose(fp);
355 }
356 
357 static void
358 make_menus(void)
359 {
360 	struct country *cp;
361 	struct zone *zp, *zp2;
362 	struct continent *cont;
363 	dialogMenuItem *dmi;
364 	int i;
365 
366 	/*
367 	 * First, count up all the countries in each continent/ocean.
368 	 * Be careful to count those countries which have multiple zones
369 	 * only once for each.  NB: some countries are in multiple
370 	 * continents/oceans.
371 	 */
372 	for (cp = countries; cp->name; cp++) {
373 		if (cp->nzones == 0)
374 			continue;
375 		if (cp->nzones < 0) {
376 			cp->continent->nitems++;
377 		} else {
378 			for (zp = cp->zones.tqh_first; zp;
379 			     zp = zp->link.tqe_next) {
380 				cont = zp->continent;
381 				for (zp2 = cp->zones.tqh_first;
382 				     zp2->continent != cont;
383 				     zp2 = zp2->link.tqe_next)
384 					;
385 				if (zp2 == zp)
386 					zp->continent->nitems++;
387 			}
388 		}
389 	}
390 
391 	/*
392 	 * Now allocate memory for the country menus.  We set
393 	 * nitems back to zero so that we can use it for counting
394 	 * again when we actually build the menus.
395 	 */
396 	for (i = 0; i < NCONTINENTS; i++) {
397 		continent_names[i].continent->menu =
398 			malloc(sizeof(dialogMenuItem) *
399 			       continent_names[i].continent->nitems);
400 		if (continent_names[i].continent->menu == 0)
401 			errx(1, "malloc for continent menu");
402 		continent_names[i].continent->nitems = 0;
403 	}
404 
405 	/*
406 	 * Now that memory is allocated, create the menu items for
407 	 * each continent.  For multiple-zone countries, also create
408 	 * the country's zone submenu.
409 	 */
410 	for (cp = countries; cp->name; cp++) {
411 		if (cp->nzones == 0)
412 			continue;
413 		if (cp->nzones < 0) {
414 			dmi = &cp->continent->menu[cp->continent->nitems];
415 			memset(dmi, 0, sizeof *dmi);
416 			asprintf(&dmi->prompt, "%d",
417 				 ++cp->continent->nitems);
418 			dmi->title = cp->name;
419 			dmi->checked = 0;
420 			dmi->fire = set_zone_whole_country;
421 			dmi->selected = 0;
422 			dmi->data = cp;
423 		} else {
424 			cp->submenu = malloc(cp->nzones * sizeof *dmi);
425 			if (cp->submenu == 0)
426 				errx(1, "malloc for submenu");
427 			cp->nzones = 0;
428 			for (zp = cp->zones.tqh_first; zp;
429 			     zp = zp->link.tqe_next) {
430 				cont = zp->continent;
431 				dmi = &cp->submenu[cp->nzones];
432 				memset(dmi, 0, sizeof *dmi);
433 				asprintf(&dmi->prompt, "%d",
434 					 ++cp->nzones);
435 				dmi->title = zp->descr;
436 				dmi->checked = 0;
437 				dmi->fire = set_zone_multi;
438 				dmi->selected = 0;
439 				dmi->data = zp;
440 
441 				for (zp2 = cp->zones.tqh_first;
442 				     zp2->continent != cont;
443 				     zp2 = zp2->link.tqe_next)
444 					;
445 				if (zp2 != zp)
446 					continue;
447 
448 				dmi = &cont->menu[cont->nitems];
449 				memset(dmi, 0, sizeof *dmi);
450 				asprintf(&dmi->prompt, "%d", ++cont->nitems);
451 				dmi->title = cp->name;
452 				dmi->checked = 0;
453 				dmi->fire = set_zone_menu;
454 				dmi->selected = 0;
455 				dmi->data = cp;
456 			}
457 		}
458 	}
459 }
460 
461 static int
462 set_zone_menu(dialogMenuItem *dmi)
463 {
464 	int rv;
465 	char buf[256];
466 	struct country *cp = dmi->data;
467 	int menulen;
468 
469 	snprintf(buf, sizeof buf, "%s Time Zones", cp->name);
470 	menulen = cp->nzones < 16 ? cp->nzones : 16;
471 	rv = dialog_menu(buf, "Select a zone which observes the same time as "
472 			 "your locality.", -1, -1, menulen, -cp->nzones,
473 			 cp->submenu, 0, 0, 0);
474 	if (rv != 0)
475 		return DITEM_RECREATE;
476 	return DITEM_LEAVE_MENU;
477 }
478 
479 static int
480 install_zone_file(const char *filename)
481 {
482 	struct stat sb;
483 	int fd1, fd2;
484 	int copymode;
485 	char *msg;
486 	ssize_t len;
487 	char buf[1024];
488 
489 	if (lstat(_PATH_LOCALTIME, &sb) < 0)
490 		/* Nothing there yet... */
491 		copymode = 1;
492 	else if(S_ISLNK(sb.st_mode))
493 		copymode = 0;
494 	else
495 		copymode = 1;
496 
497 #ifdef VERBOSE
498 	if (copymode)
499 		asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename);
500 	else
501 		asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME
502 			 " to %s", filename);
503 
504 	dialog_notify(msg);
505 	free(msg);
506 #endif
507 
508 	if (reallydoit) {
509 		if (copymode) {
510 			fd1 = open(filename, O_RDONLY, 0);
511 			if (fd1 < 0) {
512 				asprintf(&msg, "Could not open %s: %s",
513 					 filename, strerror(errno));
514 				dialog_mesgbox("Error", msg, 8, 72);
515 				free(msg);
516 				return DITEM_FAILURE | DITEM_RECREATE;
517 			}
518 
519 			unlink(_PATH_LOCALTIME);
520 			fd2 = open(_PATH_LOCALTIME,
521 				   O_CREAT|O_EXCL|O_WRONLY,
522 				   S_IRUSR|S_IRGRP|S_IROTH);
523 			if (fd2 < 0) {
524 				asprintf(&msg, "Could not open "
525 					 _PATH_LOCALTIME ": %s",
526 					 strerror(errno));
527 				dialog_mesgbox("Error", msg, 8, 72);
528 				free(msg);
529 				return DITEM_FAILURE | DITEM_RECREATE;
530 			}
531 
532 			while ((len = read(fd1, buf, sizeof buf)) > 0)
533 				len = write(fd2, buf, len);
534 
535 			if (len == -1) {
536 				asprintf(&msg, "Error copying %s to "
537 					 _PATH_LOCALTIME ": %s",
538 					 filename, strerror(errno));
539 				dialog_mesgbox("Error", msg, 8, 72);
540 				free(msg);
541 				/* Better to leave none than a corrupt one. */
542 				unlink(_PATH_LOCALTIME);
543 				return DITEM_FAILURE | DITEM_RECREATE;
544 			}
545 			close(fd1);
546 			close(fd2);
547 		} else {
548 			if (access(filename, R_OK) != 0) {
549 				asprintf(&msg, "Cannot access %s: %s",
550 					 filename, strerror(errno));
551 				dialog_mesgbox("Error", msg, 8, 72);
552 				free(msg);
553 				return DITEM_FAILURE | DITEM_RECREATE;
554 			}
555 			unlink(_PATH_LOCALTIME);
556 			if (symlink(filename, _PATH_LOCALTIME) < 0) {
557 				asprintf(&msg, "Cannot create symbolic link "
558 					 _PATH_LOCALTIME " to %s: %s",
559 					 filename, strerror(errno));
560 				dialog_mesgbox("Error", msg, 8, 72);
561 				free(msg);
562 				return DITEM_FAILURE | DITEM_RECREATE;
563 			}
564 		}
565 	}
566 
567 #ifdef VERBOSE
568 	if (copymode)
569 		asprintf(&msg, "Copied timezone file from %s to "
570 			 _PATH_LOCALTIME, filename);
571 	else
572 		asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME
573 			 " to %s", filename);
574 
575 	dialog_mesgbox("Done", msg, 8, 72);
576 	free(msg);
577 #endif
578 	return DITEM_LEAVE_MENU;
579 }
580 
581 static int
582 confirm_zone(const char *filename)
583 {
584 	char *msg;
585 	struct tm *tm;
586 	time_t t = time(0);
587 	int rv;
588 
589 	if (setenv("TZ", filename, 1) == -1)
590 		err(1, "setenv: cannot set TZ=%s", filename);
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, 4, 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(void)
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 	dialog_utc = dialog_noyes;
647 
648 	while ((c = getopt(argc, argv, "n")) != -1) {
649 		switch(c) {
650 		case 'n':
651 			reallydoit = 0;
652 			break;
653 
654 		default:
655 			usage();
656 		}
657 	}
658 
659 	if (argc - optind > 1)
660 		usage();
661 
662 	/* Override the user-supplied umask. */
663 	umask(S_IWGRP|S_IWOTH);
664 
665 	read_iso3166_table();
666 	read_zones();
667 	sort_countries();
668 	make_menus();
669 
670 	init_dialog();
671 	if (!dialog_utc("Select local or UTC (Greenwich Mean Time) clock",
672 			"Is this machine's CMOS clock set to UTC?  If it is set to local time,\n"
673 			"or you don't know, please choose NO here!", 7, 72)) {
674 		if (reallydoit)
675 			unlink(_PATH_WALL_CMOS_CLOCK);
676 	} else {
677 		if (reallydoit) {
678 			fd = open(_PATH_WALL_CMOS_CLOCK,
679 				  O_WRONLY|O_CREAT|O_TRUNC,
680 				  S_IRUSR|S_IRGRP|S_IROTH);
681 			if (fd < 0)
682 				err(1, "create %s", _PATH_WALL_CMOS_CLOCK);
683 			close(fd);
684 		}
685 	}
686 	dialog_clear_norefresh();
687 	if (optind == argc - 1) {
688 		char *msg;
689 		asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]);
690 		if (!dialog_yesno("Default timezone provided", msg, 7, 72)) {
691 			install_zone_file(argv[optind]);
692 			dialog_clear();
693 			end_dialog();
694 			return 0;
695 		}
696 		free(msg);
697 		dialog_clear_norefresh();
698 	}
699 	dialog_menu("Time Zone Selector", "Select a region", -1, -1,
700 		    NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);
701 
702 	dialog_clear();
703 	end_dialog();
704 	return 0;
705 }
706 
707