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