tzsetup.c revision 198267
116728Swpaul/*
216728Swpaul * Copyright 1996 Massachusetts Institute of Technology
316728Swpaul *
416728Swpaul * Permission to use, copy, modify, and distribute this software and
516728Swpaul * its documentation for any purpose and without fee is hereby
616728Swpaul * granted, provided that both the above copyright notice and this
716728Swpaul * permission notice appear in all copies, that both the above
816728Swpaul * copyright notice and this permission notice appear in all
916728Swpaul * supporting documentation, and that the name of M.I.T. not be used
1016728Swpaul * in advertising or publicity pertaining to distribution of the
1116728Swpaul * software without specific, written prior permission.  M.I.T. makes
1216728Swpaul * no representations about the suitability of this software for any
1316728Swpaul * purpose.  It is provided "as is" without express or implied
1416728Swpaul * warranty.
1516728Swpaul *
1616728Swpaul * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
1716728Swpaul * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
1816728Swpaul * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
1916728Swpaul * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
2016728Swpaul * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2116728Swpaul * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2216728Swpaul * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
2316728Swpaul * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
2416728Swpaul * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
2516728Swpaul * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
2616728Swpaul * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2716728Swpaul * SUCH DAMAGE.
2816728Swpaul */
2916728Swpaul
3016728Swpaul/*
3116728Swpaul * Second attempt at a `tzmenu' program, using the separate description
3216728Swpaul * files provided in newer tzdata releases.
3316728Swpaul */
3416728Swpaul
3516728Swpaul#include <sys/cdefs.h>
3616728Swpaul__FBSDID("$FreeBSD: head/usr.sbin/tzsetup/tzsetup.c 198267 2009-10-20 06:54:31Z edwin $");
3716728Swpaul
3816728Swpaul#include <dialog.h>
3916728Swpaul#include <err.h>
4031385Scharnier#include <errno.h>
4150476Speter#include <stdio.h>
4231385Scharnier#include <stdlib.h>
4316728Swpaul#include <string.h>
4416728Swpaul#include <time.h>
4516728Swpaul#include <unistd.h>
4616728Swpaul
4716728Swpaul#include <sys/fcntl.h>
4816728Swpaul#include <sys/param.h>
4916728Swpaul#include <sys/queue.h>
5016728Swpaul#include <sys/stat.h>
5116728Swpaul
5216728Swpaul#define	_PATH_ZONETAB		"/usr/share/zoneinfo/zone.tab"
5316728Swpaul#define	_PATH_ISO3166		"/usr/share/misc/iso3166"
5490779Simp#define	_PATH_ZONEINFO		"/usr/share/zoneinfo"
5516728Swpaul#define	_PATH_LOCALTIME		"/etc/localtime"
5690779Simp#define	_PATH_DB		"/var/db/zoneinfo"
5790779Simp#define	_PATH_WALL_CMOS_CLOCK	"/etc/wall_cmos_clock"
5890779Simp
5916728Swpaulstatic int reallydoit = 1;
6016728Swpaulstatic int reinstall = 0;
6116728Swpaul
6216728Swpaulstatic void	usage(void);
6316728Swpaulstatic int	continent_country_menu(dialogMenuItem *);
6416728Swpaulstatic int	set_zone_multi(dialogMenuItem *);
6516728Swpaulstatic int	set_zone_whole_country(dialogMenuItem *);
6616728Swpaulstatic int	set_zone_menu(dialogMenuItem *);
6716728Swpaul
6816728Swpaulstruct continent {
6916728Swpaul	dialogMenuItem *menu;
7016728Swpaul	int		nitems;
7116728Swpaul	int		ch;
7216728Swpaul	int		sc;
7316728Swpaul};
7416728Swpaul
7516728Swpaulstatic struct continent	africa, america, antarctica, arctic, asia, atlantic;
7616728Swpaulstatic struct continent	australia, europe, indian, pacific;
7716728Swpaul
7816728Swpaulstatic struct continent_names {
7916728Swpaul	const char	*name;
8016728Swpaul	struct continent *continent;
8116728Swpaul} continent_names[] = {
8216728Swpaul	{ "Africa",	&africa },
8316728Swpaul	{ "America",	&america },
8416728Swpaul	{ "Antarctica",	&antarctica },
8516728Swpaul	{ "Arctic",	&arctic },
8616728Swpaul	{ "Asia",	&asia },
8716728Swpaul	{ "Atlantic",	&atlantic },
8816728Swpaul	{ "Australia",	&australia },
8916728Swpaul	{ "Europe",	&europe },
9016728Swpaul	{ "Indian",	&indian },
9116728Swpaul	{ "Pacific",	&pacific }
9216728Swpaul};
9316728Swpaul
9416728Swpaulstatic struct continent_items {
9516728Swpaul	char		prompt[2];
9616728Swpaul	char		title[30];
9716728Swpaul} continent_items[] = {
9816728Swpaul	{ "1",	"Africa" },
9916728Swpaul	{ "2",	"America -- North and South" },
10016728Swpaul	{ "3",	"Antarctica" },
10116728Swpaul	{ "4",	"Arctic Ocean" },
10216728Swpaul	{ "5",	"Asia" },
10390779Simp	{ "6",	"Atlantic Ocean" },
10416728Swpaul	{ "7",	"Australia" },
10516728Swpaul	{ "8",	"Europe" },
10616728Swpaul	{ "9",	"Indian Ocean" },
10716728Swpaul	{ "0",	"Pacific Ocean" }
10816728Swpaul};
10916728Swpaul
11016728Swpaul#define	NCONTINENTS	\
11116728Swpaul    (int)((sizeof(continent_items)) / (sizeof(continent_items[0])))
11290779Simpstatic dialogMenuItem continents[NCONTINENTS];
11316728Swpaul
11416728Swpaul#define	OCEANP(x)	((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
11516728Swpaul
11616728Swpaulstatic int
11716728Swpaulcontinent_country_menu(dialogMenuItem *continent)
11816728Swpaul{
11916728Swpaul	char		title[64], prompt[64];
12016728Swpaul	struct continent *contp = continent->data;
12116728Swpaul	int		isocean = OCEANP(continent - continents);
12216728Swpaul	int		menulen;
12316728Swpaul	int		rv;
12416728Swpaul
12516728Swpaul	/* Short cut -- if there's only one country, don't post a menu. */
12616728Swpaul	if (contp->nitems == 1)
12716728Swpaul		return (contp->menu[0].fire(&contp->menu[0]));
12816728Swpaul
12916728Swpaul	/* It's amazing how much good grammar really matters... */
130108470Sschweikh	if (!isocean) {
13116728Swpaul		snprintf(title, sizeof(title), "Countries in %s",
13290779Simp		    continent->title);
13316728Swpaul		snprintf(prompt, sizeof(prompt), "Select a country or region");
13416728Swpaul	} else {
13516728Swpaul		snprintf(title, sizeof(title), "Islands and groups in the %s",
13616728Swpaul		    continent->title);
13716728Swpaul		snprintf(prompt, sizeof(prompt), "Select an island or group");
13816728Swpaul	}
13916728Swpaul
14016728Swpaul	menulen = contp->nitems < 16 ? contp->nitems : 16;
14116728Swpaul	rv = dialog_menu(title, prompt, -1, -1, menulen, -contp->nitems,
14216728Swpaul	    contp->menu, 0, &contp->ch, &contp->sc);
14316728Swpaul	if (rv == 0)
14416728Swpaul		return (DITEM_LEAVE_MENU);
14516728Swpaul	return (DITEM_RECREATE);
14616728Swpaul}
14716728Swpaul
14816728Swpaulstatic struct continent *
14916728Swpaulfind_continent(const char *name)
15016728Swpaul{
15116728Swpaul	int		i;
15216728Swpaul
15316728Swpaul	for (i = 0; i < NCONTINENTS; i++)
15416728Swpaul		if (strcmp(name, continent_names[i].name) == 0)
15516728Swpaul			return (continent_names[i].continent);
15616728Swpaul	return (0);
15716728Swpaul}
15816728Swpaul
15916728Swpaulstruct country {
16016728Swpaul	char		*name;
16116728Swpaul	char		*tlc;
16216728Swpaul	int		nzones;
16316728Swpaul	char		*filename;	/* use iff nzones < 0 */
16416728Swpaul	struct continent *continent;	/* use iff nzones < 0 */
16516728Swpaul	TAILQ_HEAD(, zone) zones;	/* use iff nzones > 0 */
16616728Swpaul	dialogMenuItem	*submenu;	/* use iff nzones > 0 */
16716728Swpaul};
16816728Swpaul
169struct zone {
170	TAILQ_ENTRY(zone) link;
171	char		*descr;
172	char		*filename;
173	struct continent *continent;
174};
175
176/*
177 * This is the easiest organization... we use ISO 3166 country codes,
178 * of the two-letter variety, so we just size this array to suit.
179 * Beats worrying about dynamic allocation.
180 */
181#define	NCOUNTRIES	(26 * 26)
182static struct country countries[NCOUNTRIES];
183
184#define	CODE2INT(s)	((s[0] - 'A') * 26 + (s[1] - 'A'))
185
186/*
187 * Read the ISO 3166 country code database in _PATH_ISO3166
188 * (/usr/share/misc/iso3166).  On error, exit via err(3).
189 */
190static void
191read_iso3166_table(void)
192{
193	FILE		*fp;
194	struct country	*cp;
195	size_t		len;
196	char		*s, *t, *name;
197	int		lineno;
198
199	fp = fopen(_PATH_ISO3166, "r");
200	if (!fp)
201		err(1, _PATH_ISO3166);
202	lineno = 0;
203
204	while ((s = fgetln(fp, &len)) != 0) {
205		lineno++;
206		if (s[len - 1] != '\n')
207			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
208		s[len - 1] = '\0';
209		if (s[0] == '#' || strspn(s, " \t") == len - 1)
210			continue;
211
212		/* Isolate the two-letter code. */
213		t = strsep(&s, "\t");
214		if (t == 0 || strlen(t) != 2)
215			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
216		if (t[0] < 'A' || t[0] > 'Z' || t[1] < 'A' || t[1] > 'Z')
217			errx(1, _PATH_ISO3166 ":%d: invalid code `%s'",
218			    lineno, t);
219
220		/* Now skip past the three-letter and numeric codes. */
221		name = strsep(&s, "\t");	/* 3-let */
222		if (name == 0 || strlen(name) != 3)
223			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
224		name = strsep(&s, "\t");	/* numeric */
225		if (name == 0 || strlen(name) != 3)
226			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
227
228		name = s;
229
230		cp = &countries[CODE2INT(t)];
231		if (cp->name)
232			errx(1, _PATH_ISO3166
233			    ":%d: country code `%s' multiply defined: %s",
234			    lineno, t, cp->name);
235		cp->name = strdup(name);
236		if (cp->name == NULL)
237			errx(1, "malloc failed");
238		cp->tlc = strdup(t);
239		if (cp->tlc == NULL)
240			errx(1, "malloc failed");
241	}
242
243	fclose(fp);
244}
245
246static void
247add_zone_to_country(int lineno, const char *tlc, const char *descr,
248    const char *file, struct continent *cont)
249{
250	struct zone	*zp;
251	struct country	*cp;
252
253	if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
254		errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid",
255		    lineno, tlc);
256
257	cp = &countries[CODE2INT(tlc)];
258	if (cp->name == 0)
259		errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown",
260		    lineno, tlc);
261
262	if (descr) {
263		if (cp->nzones < 0)
264			errx(1, _PATH_ZONETAB
265			    ":%d: conflicting zone definition", lineno);
266
267		zp = malloc(sizeof(*zp));
268		if (zp == 0)
269			errx(1, "malloc(%zu)", sizeof(*zp));
270
271		if (cp->nzones == 0)
272			TAILQ_INIT(&cp->zones);
273
274		zp->descr = strdup(descr);
275		if (zp->descr == NULL)
276			errx(1, "malloc failed");
277		zp->filename = strdup(file);
278		if (zp->filename == NULL)
279			errx(1, "malloc failed");
280		zp->continent = cont;
281		TAILQ_INSERT_TAIL(&cp->zones, zp, link);
282		cp->nzones++;
283	} else {
284		if (cp->nzones > 0)
285			errx(1, _PATH_ZONETAB
286			    ":%d: zone must have description", lineno);
287		if (cp->nzones < 0)
288			errx(1, _PATH_ZONETAB
289			    ":%d: zone multiply defined", lineno);
290		cp->nzones = -1;
291		cp->filename = strdup(file);
292		if (cp->filename == NULL)
293			errx(1, "malloc failed");
294		cp->continent = cont;
295	}
296}
297
298/*
299 * This comparison function intentionally sorts all of the null-named
300 * ``countries''---i.e., the codes that don't correspond to a real
301 * country---to the end.  Everything else is lexical by country name.
302 */
303static int
304compare_countries(const void *xa, const void *xb)
305{
306	const struct country *a = xa, *b = xb;
307
308	if (a->name == 0 && b->name == 0)
309		return (0);
310	if (a->name == 0 && b->name != 0)
311		return (1);
312	if (b->name == 0)
313		return (-1);
314
315	return (strcmp(a->name, b->name));
316}
317
318/*
319 * This must be done AFTER all zone descriptions are read, since it breaks
320 * CODE2INT().
321 */
322static void
323sort_countries(void)
324{
325
326	qsort(countries, NCOUNTRIES, sizeof(countries[0]), compare_countries);
327}
328
329static void
330read_zones(void)
331{
332	char		contbuf[16];
333	FILE		*fp;
334	struct continent *cont;
335	size_t		len;
336	char		*line, *tlc, *coord, *file, *descr, *p;
337	int		lineno;
338
339	fp = fopen(_PATH_ZONETAB, "r");
340	if (!fp)
341		err(1, _PATH_ZONETAB);
342	lineno = 0;
343
344	while ((line = fgetln(fp, &len)) != 0) {
345		lineno++;
346		if (line[len - 1] != '\n')
347			errx(1, _PATH_ZONETAB ":%d: invalid format", lineno);
348		line[len - 1] = '\0';
349		if (line[0] == '#')
350			continue;
351
352		tlc = strsep(&line, "\t");
353		if (strlen(tlc) != 2)
354			errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'",
355			    lineno, tlc);
356		coord = strsep(&line, "\t");
357		file = strsep(&line, "\t");
358		p = strchr(file, '/');
359		if (p == 0)
360			errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'",
361			    lineno, file);
362		contbuf[0] = '\0';
363		strncat(contbuf, file, p - file);
364		cont = find_continent(contbuf);
365		if (!cont)
366			errx(1, _PATH_ZONETAB ":%d: invalid region `%s'",
367			    lineno, contbuf);
368
369		descr = (line != NULL && *line != '\0') ? line : NULL;
370
371		add_zone_to_country(lineno, tlc, descr, file, cont);
372	}
373	fclose(fp);
374}
375
376static void
377make_menus(void)
378{
379	struct country	*cp;
380	struct zone	*zp, *zp2;
381	struct continent *cont;
382	dialogMenuItem	*dmi;
383	int		i;
384
385	/*
386	 * First, count up all the countries in each continent/ocean.
387	 * Be careful to count those countries which have multiple zones
388	 * only once for each.  NB: some countries are in multiple
389	 * continents/oceans.
390	 */
391	for (cp = countries; cp->name; cp++) {
392		if (cp->nzones == 0)
393			continue;
394		if (cp->nzones < 0) {
395			cp->continent->nitems++;
396		} else {
397			TAILQ_FOREACH(zp, &cp->zones, link) {
398				cont = zp->continent;
399				for (zp2 = TAILQ_FIRST(&cp->zones);
400				    zp2->continent != cont;
401				    zp2 = TAILQ_NEXT(zp2, link))
402					;
403				if (zp2 == zp)
404					zp->continent->nitems++;
405			}
406		}
407	}
408
409	/*
410	 * Now allocate memory for the country menus and initialize
411	 * continent menus.  We set nitems back to zero so that we can
412	 * use it for counting again when we actually build the menus.
413	 */
414	memset(continents, 0, sizeof(continents));
415	for (i = 0; i < NCONTINENTS; i++) {
416		continent_names[i].continent->menu =
417		    malloc(sizeof(dialogMenuItem) *
418		    continent_names[i].continent->nitems);
419		if (continent_names[i].continent->menu == 0)
420			errx(1, "malloc for continent menu");
421		continent_names[i].continent->nitems = 0;
422		continents[i].prompt = continent_items[i].prompt;
423		continents[i].title = continent_items[i].title;
424		continents[i].fire = continent_country_menu;
425		continents[i].data = continent_names[i].continent;
426	}
427
428	/*
429	 * Now that memory is allocated, create the menu items for
430	 * each continent.  For multiple-zone countries, also create
431	 * the country's zone submenu.
432	 */
433	for (cp = countries; cp->name; cp++) {
434		if (cp->nzones == 0)
435			continue;
436		if (cp->nzones < 0) {
437			dmi = &cp->continent->menu[cp->continent->nitems];
438			memset(dmi, 0, sizeof(*dmi));
439			asprintf(&dmi->prompt, "%d", ++cp->continent->nitems);
440			dmi->title = cp->name;
441			dmi->checked = 0;
442			dmi->fire = set_zone_whole_country;
443			dmi->selected = 0;
444			dmi->data = cp;
445		} else {
446			cp->submenu = malloc(cp->nzones * sizeof(*dmi));
447			if (cp->submenu == 0)
448				errx(1, "malloc for submenu");
449			cp->nzones = 0;
450			TAILQ_FOREACH(zp, &cp->zones, link) {
451				cont = zp->continent;
452				dmi = &cp->submenu[cp->nzones];
453				memset(dmi, 0, sizeof(*dmi));
454				asprintf(&dmi->prompt, "%d", ++cp->nzones);
455				dmi->title = zp->descr;
456				dmi->checked = 0;
457				dmi->fire = set_zone_multi;
458				dmi->selected = 0;
459				dmi->data = zp;
460
461				for (zp2 = TAILQ_FIRST(&cp->zones);
462				    zp2->continent != cont;
463				    zp2 = TAILQ_NEXT(zp2, link))
464					;
465				if (zp2 != zp)
466					continue;
467
468				dmi = &cont->menu[cont->nitems];
469				memset(dmi, 0, sizeof(*dmi));
470				asprintf(&dmi->prompt, "%d", ++cont->nitems);
471				dmi->title = cp->name;
472				dmi->checked = 0;
473				dmi->fire = set_zone_menu;
474				dmi->selected = 0;
475				dmi->data = cp;
476			}
477		}
478	}
479}
480
481static int
482set_zone_menu(dialogMenuItem *dmi)
483{
484	char		title[64], prompt[64];
485	struct country	*cp = dmi->data;
486	int		menulen;
487	int		rv;
488
489	snprintf(title, sizeof(title), "%s Time Zones", cp->name);
490	snprintf(prompt, sizeof(prompt),
491	    "Select a zone which observes the same time as your locality.");
492	menulen = cp->nzones < 16 ? cp->nzones : 16;
493	rv = dialog_menu(title, prompt, -1, -1, menulen, -cp->nzones,
494	    cp->submenu, 0, 0, 0);
495	if (rv != 0)
496		return (DITEM_RECREATE);
497	return (DITEM_LEAVE_MENU);
498}
499
500static int
501install_zone_file(const char *filename, int usedialog)
502{
503	char		buf[1024];
504	char		title[64], prompt[64];
505	struct stat	sb;
506	ssize_t		len;
507	int		fd1, fd2, copymode;
508	FILE		*f;
509
510	if (lstat(_PATH_LOCALTIME, &sb) < 0) {
511		/* Nothing there yet... */
512		copymode = 1;
513	} else if (S_ISLNK(sb.st_mode))
514		copymode = 0;
515	else
516		copymode = 1;
517
518#ifdef VERBOSE
519	if (copymode)
520		snprintf(prompt, sizeof(prompt),
521		    "Copying %s to " _PATH_LOCALTIME, filename);
522	else
523		snprintf(prompt, sizeof(prompt),
524		    "Creating symbolic link " _PATH_LOCALTIME " to %s",
525		    filename);
526	if (usedialog)
527		dialog_notify(prompt);
528	else
529		fprintf(stderr, "%s\n", prompt);
530#endif
531
532	if (reallydoit) {
533		if (copymode) {
534			fd1 = open(filename, O_RDONLY, 0);
535			if (fd1 < 0) {
536				snprintf(title, sizeof(title), "Error");
537				snprintf(prompt, sizeof(prompt),
538				    "Could not open %s: %s", filename,
539				    strerror(errno));
540				if (usedialog)
541					dialog_mesgbox(title, prompt, 8, 72);
542				else
543					fprintf(stderr, "%s\n", prompt);
544				return (DITEM_FAILURE | DITEM_RECREATE);
545			}
546
547			unlink(_PATH_LOCALTIME);
548			fd2 = open(_PATH_LOCALTIME, O_CREAT | O_EXCL | O_WRONLY,
549			    S_IRUSR | S_IRGRP | S_IROTH);
550			if (fd2 < 0) {
551				snprintf(title, sizeof(title), "Error");
552				snprintf(prompt, sizeof(prompt),
553				    "Could not open " _PATH_LOCALTIME ": %s",
554				    strerror(errno));
555				if (usedialog)
556					dialog_mesgbox(title, prompt, 8, 72);
557				else
558					fprintf(stderr, "%s\n", prompt);
559				return (DITEM_FAILURE | DITEM_RECREATE);
560			}
561
562			while ((len = read(fd1, buf, sizeof(buf))) > 0)
563				len = write(fd2, buf, len);
564
565			if (len == -1) {
566				snprintf(title, sizeof(title), "Error");
567				snprintf(prompt, sizeof(prompt),
568				    "Error copying %s to " _PATH_LOCALTIME
569				    ": %s", filename, strerror(errno));
570				if (usedialog)
571					dialog_mesgbox(title, prompt, 8, 72);
572				else
573					fprintf(stderr, "%s\n", prompt);
574				/* Better to leave none than a corrupt one. */
575				unlink(_PATH_LOCALTIME);
576				return (DITEM_FAILURE | DITEM_RECREATE);
577			}
578			close(fd1);
579			close(fd2);
580		} else {
581			if (access(filename, R_OK) != 0) {
582				snprintf(title, sizeof(title), "Error");
583				snprintf(prompt, sizeof(prompt),
584				    "Cannot access %s: %s", filename,
585				    strerror(errno));
586				if (usedialog)
587					dialog_mesgbox(title, prompt, 8, 72);
588				else
589					fprintf(stderr, "%s\n", prompt);
590				return (DITEM_FAILURE | DITEM_RECREATE);
591			}
592			unlink(_PATH_LOCALTIME);
593			if (symlink(filename, _PATH_LOCALTIME) < 0) {
594				snprintf(title, sizeof(title), "Error");
595				snprintf(prompt, sizeof(prompt),
596				    "Cannot create symbolic link "
597				    _PATH_LOCALTIME " to %s: %s", filename,
598				    strerror(errno));
599				if (usedialog)
600					dialog_mesgbox(title, prompt, 8, 72);
601				else
602					fprintf(stderr, "%s\n", prompt);
603				return (DITEM_FAILURE | DITEM_RECREATE);
604			}
605		}
606	}
607
608#ifdef VERBOSE
609	snprintf(title, sizeof(title), "Done");
610	if (copymode)
611		snprintf(prompt, sizeof(prompt),
612		    "Copied timezone file from %s to " _PATH_LOCALTIME,
613		    filename);
614	else
615		snprintf(prompt, sizeof(prompt), "Created symbolic link from "
616		    _PATH_LOCALTIME " to %s", filename);
617	if (usedialog)
618		dialog_mesgbox(title, prompt, 8, 72);
619	else
620		fprintf(stderr, "%s\n", prompt);
621#endif
622
623	/* Save knowledge for later */
624	if ((f = fopen(_PATH_DB, "w")) != NULL) {
625		fprintf(f, "%s\n", filename + strlen(_PATH_ZONEINFO) + 1);
626		fclose(f);
627	}
628
629	return (DITEM_LEAVE_MENU);
630}
631
632static int
633confirm_zone(const char *filename)
634{
635	char		title[64], prompt[64];
636	time_t		t = time(0);
637	struct tm	*tm;
638	int		rv;
639
640	setenv("TZ", filename, 1);
641	tzset();
642	tm = localtime(&t);
643
644	snprintf(title, sizeof(title), "Confirmation");
645	snprintf(prompt, sizeof(prompt),
646	    "Does the abbreviation `%s' look reasonable?", tm->tm_zone);
647	rv = !dialog_yesno(title, prompt, 5, 72);
648	return (rv);
649}
650
651static int
652set_zone_multi(dialogMenuItem *dmi)
653{
654	struct zone	*zp = dmi->data;
655	char		*fn;
656	int		rv;
657
658	if (!confirm_zone(zp->filename))
659		return (DITEM_FAILURE | DITEM_RECREATE);
660
661	asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename);
662	rv = install_zone_file(fn, 1);
663	free(fn);
664	return (rv);
665}
666
667static int
668set_zone_whole_country(dialogMenuItem *dmi)
669{
670	struct country	*cp = dmi->data;
671	char		*fn;
672	int		rv;
673
674	if (!confirm_zone(cp->filename))
675		return (DITEM_FAILURE | DITEM_RECREATE);
676
677	asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename);
678	rv = install_zone_file(fn, 1);
679	free(fn);
680	return (rv);
681}
682
683static void
684usage(void)
685{
686
687	fprintf(stderr, "usage: tzsetup [-nrs] [zoneinfo file]\n");
688	exit(1);
689}
690
691#if defined(__sparc64__)
692#define	DIALOG_UTC	dialog_yesno
693#else
694#define	DIALOG_UTC	dialog_noyes
695#endif
696
697int
698main(int argc, char **argv)
699{
700	char		title[64], prompt[128];
701	int		c, fd, rv, skiputc;
702
703	skiputc = 0;
704	while ((c = getopt(argc, argv, "nrs")) != -1) {
705		switch(c) {
706		case 'n':
707			reallydoit = 0;
708			break;
709		case 'r':
710			reinstall = 1;
711			break;
712		case 's':
713			skiputc = 1;
714			break;
715		default:
716			usage();
717		}
718	}
719
720	if (argc - optind > 1)
721		usage();
722
723	/* Override the user-supplied umask. */
724	(void)umask(S_IWGRP | S_IWOTH);
725
726	read_iso3166_table();
727	read_zones();
728	sort_countries();
729	make_menus();
730
731	if (reinstall == 1) {
732		FILE *f;
733		char zonefile[MAXPATHLEN];
734
735		sprintf(zonefile, "%s/", _PATH_ZONEINFO);
736		if ((f = fopen(_PATH_DB, "r")) != NULL) {
737			if (fgets(zonefile + strlen(zonefile),
738			    sizeof(zonefile) - strlen(zonefile), f) != NULL) {
739				zonefile[sizeof(zonefile) - 1] = 0;
740				if (strlen(zonefile) > 0) {
741					zonefile[strlen(zonefile) - 1] = 0;
742					rv = install_zone_file(zonefile, 0);
743					exit(rv & ~DITEM_LEAVE_MENU);
744				}
745				errx(1, "Error reading %s.\n", _PATH_DB);
746			}
747			fclose(f);
748			errx(1,
749			    "Unable to determine earlier installed zoneinfo "
750			    "file. Check %s", _PATH_DB);
751		}
752		errx(1, "Cannot open %s for reading. Does it exist?", _PATH_DB);
753	}
754
755	init_dialog();
756	if (skiputc == 0) {
757		snprintf(title, sizeof(title),
758		    "Select local or UTC (Greenwich Mean Time) clock");
759		snprintf(prompt, sizeof(prompt),
760		    "Is this machine's CMOS clock set to UTC?  "
761		    "If it is set to local time,\n"
762		    "or you don't know, please choose NO here!");
763		if (!DIALOG_UTC(title, prompt, 7, 72)) {
764			if (reallydoit)
765				unlink(_PATH_WALL_CMOS_CLOCK);
766		} else {
767			if (reallydoit) {
768				fd = open(_PATH_WALL_CMOS_CLOCK,
769				    O_WRONLY | O_CREAT | O_TRUNC,
770				    S_IRUSR | S_IRGRP | S_IROTH);
771				if (fd < 0) {
772					end_dialog();
773					err(1, "create %s",
774					    _PATH_WALL_CMOS_CLOCK);
775				}
776				close(fd);
777			}
778		}
779		dialog_clear_norefresh();
780	}
781	if (optind == argc - 1) {
782		snprintf(title, sizeof(title), "Default timezone provided");
783		snprintf(prompt, sizeof(prompt),
784		    "\nUse the default `%s' zone?", argv[optind]);
785		if (!dialog_yesno(title, prompt, 7, 72)) {
786			rv = install_zone_file(argv[optind], 1);
787			dialog_clear();
788			end_dialog();
789			exit(rv & ~DITEM_LEAVE_MENU);
790		}
791		dialog_clear_norefresh();
792	}
793	snprintf(title, sizeof(title), "Time Zone Selector");
794	snprintf(prompt, sizeof(prompt), "Select a region");
795	dialog_menu(title, prompt, -1, -1, NCONTINENTS, -NCONTINENTS,
796	    continents, 0, NULL, NULL);
797
798	dialog_clear();
799	end_dialog();
800	return (0);
801}
802