tzsetup.c revision 195339
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: head/usr.sbin/tzsetup/tzsetup.c 195339 2009-07-04 11:11:21Z attilio $");
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
57static int reallydoit = 1;
58
59static void	usage(void);
60static int	continent_country_menu(dialogMenuItem *);
61static int	set_zone_multi(dialogMenuItem *);
62static int	set_zone_whole_country(dialogMenuItem *);
63static int	set_zone_menu(dialogMenuItem *);
64
65struct continent {
66	dialogMenuItem *menu;
67	int		nitems;
68	int		ch;
69	int		sc;
70};
71
72static struct continent	africa, america, antarctica, arctic, asia, atlantic;
73static struct continent	australia, europe, indian, pacific;
74
75static 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
91static 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])))
109static dialogMenuItem continents[NCONTINENTS];
110
111#define	OCEANP(x)	((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
112
113static int
114continent_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
145static struct continent *
146find_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
156struct 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
166struct 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)
179static 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 */
187static void
188read_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
243static void
244add_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 */
300static int
301compare_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 */
319static void
320sort_countries(void)
321{
322
323	qsort(countries, NCOUNTRIES, sizeof(countries[0]), compare_countries);
324}
325
326static void
327read_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
373static void
374make_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
478static int
479set_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
497static int
498install_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
600static int
601confirm_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
619static int
620set_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
635static int
636set_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
651static void
652usage(void)
653{
654
655	fprintf(stderr, "usage: tzsetup [-ns]\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
665int
666main(int argc, char **argv)
667{
668	char		title[64], prompt[128];
669	int		c, fd, skiputc;
670
671	skiputc = 0;
672	while ((c = getopt(argc, argv, "ns")) != -1) {
673		switch(c) {
674		case 'n':
675			reallydoit = 0;
676			break;
677		case 's':
678			skiputc = 1;
679			break;
680		default:
681			usage();
682		}
683	}
684
685	if (argc - optind > 1)
686		usage();
687
688	/* Override the user-supplied umask. */
689	(void)umask(S_IWGRP | S_IWOTH);
690
691	read_iso3166_table();
692	read_zones();
693	sort_countries();
694	make_menus();
695
696	init_dialog();
697	if (skiputc == 0) {
698		snprintf(title, sizeof(title),
699		    "Select local or UTC (Greenwich Mean Time) clock");
700		snprintf(prompt, sizeof(prompt),
701		    "Is this machine's CMOS clock set to UTC?  "
702		    "If it is set to local time,\n"
703		    "or you don't know, please choose NO here!");
704		if (!DIALOG_UTC(title, prompt, 7, 72)) {
705			if (reallydoit)
706				unlink(_PATH_WALL_CMOS_CLOCK);
707		} else {
708			if (reallydoit) {
709				fd = open(_PATH_WALL_CMOS_CLOCK,
710				    O_WRONLY | O_CREAT | O_TRUNC,
711				    S_IRUSR | S_IRGRP | S_IROTH);
712				if (fd < 0)
713					err(1, "create %s",
714					    _PATH_WALL_CMOS_CLOCK);
715				close(fd);
716			}
717		}
718		dialog_clear_norefresh();
719	}
720	if (optind == argc - 1) {
721		snprintf(title, sizeof(title), "Default timezone provided");
722		snprintf(prompt, sizeof(prompt),
723		    "\nUse the default `%s' zone?", argv[optind]);
724		if (!dialog_yesno(title, prompt, 7, 72)) {
725			install_zone_file(argv[optind]);
726			dialog_clear();
727			end_dialog();
728			return (0);
729		}
730		dialog_clear_norefresh();
731	}
732	snprintf(title, sizeof(title), "Time Zone Selector");
733	snprintf(prompt, sizeof(prompt), "Select a region");
734	dialog_menu(title, prompt, -1, -1, NCONTINENTS, -NCONTINENTS,
735	    continents, 0, NULL, NULL);
736
737	dialog_clear();
738	end_dialog();
739	return (0);
740}
741