tzsetup.c revision 209190
119872Swollman/*
219872Swollman * Copyright 1996 Massachusetts Institute of Technology
319872Swollman *
419872Swollman * Permission to use, copy, modify, and distribute this software and
519872Swollman * its documentation for any purpose and without fee is hereby
619872Swollman * granted, provided that both the above copyright notice and this
719872Swollman * permission notice appear in all copies, that both the above
819872Swollman * copyright notice and this permission notice appear in all
919872Swollman * supporting documentation, and that the name of M.I.T. not be used
1019872Swollman * in advertising or publicity pertaining to distribution of the
1119872Swollman * software without specific, written prior permission.  M.I.T. makes
1219872Swollman * no representations about the suitability of this software for any
1319872Swollman * purpose.  It is provided "as is" without express or implied
1419872Swollman * warranty.
15179530Sjkim *
1619872Swollman * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
1719872Swollman * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
1819872Swollman * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
1919872Swollman * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
2019872Swollman * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2119872Swollman * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2219872Swollman * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
2319872Swollman * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
2419872Swollman * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
2519872Swollman * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
2619872Swollman * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2719872Swollman * SUCH DAMAGE.
2819872Swollman */
2919872Swollman
3019872Swollman/*
3119872Swollman * Second attempt at a `tzmenu' program, using the separate description
3219872Swollman * files provided in newer tzdata releases.
3319872Swollman */
3419872Swollman
35179530Sjkim#include <sys/cdefs.h>
36179530Sjkim__FBSDID("$FreeBSD: head/usr.sbin/tzsetup/tzsetup.c 209190 2010-06-14 23:51:35Z emaste $");
3730763Scharnier
3819872Swollman#include <dialog.h>
3919872Swollman#include <err.h>
4019872Swollman#include <errno.h>
4119872Swollman#include <stdio.h>
4219872Swollman#include <stdlib.h>
4319872Swollman#include <string.h>
4466907Swollman#include <time.h>
4519872Swollman#include <unistd.h>
4619872Swollman
4719872Swollman#include <sys/fcntl.h>
48198267Sedwin#include <sys/param.h>
4919872Swollman#include <sys/queue.h>
5019872Swollman#include <sys/stat.h>
5119872Swollman
52179530Sjkim#define	_PATH_ZONETAB		"/usr/share/zoneinfo/zone.tab"
53179530Sjkim#define	_PATH_ISO3166		"/usr/share/misc/iso3166"
54179530Sjkim#define	_PATH_ZONEINFO		"/usr/share/zoneinfo"
55179530Sjkim#define	_PATH_LOCALTIME		"/etc/localtime"
56198267Sedwin#define	_PATH_DB		"/var/db/zoneinfo"
57179530Sjkim#define	_PATH_WALL_CMOS_CLOCK	"/etc/wall_cmos_clock"
5819872Swollman
59198350Sedwinstatic char	path_zonetab[MAXPATHLEN], path_iso3166[MAXPATHLEN],
60198350Sedwin		path_zoneinfo[MAXPATHLEN], path_localtime[MAXPATHLEN],
61198350Sedwin		path_db[MAXPATHLEN], path_wall_cmos_clock[MAXPATHLEN];
62198350Sedwin
6319872Swollmanstatic int reallydoit = 1;
64198267Sedwinstatic int reinstall = 0;
65198350Sedwinstatic int usedialog = 1;
66198350Sedwinstatic char *chrootenv = NULL;
6719872Swollman
68179530Sjkimstatic void	usage(void);
69179530Sjkimstatic int	continent_country_menu(dialogMenuItem *);
70179530Sjkimstatic int	set_zone_multi(dialogMenuItem *);
71179530Sjkimstatic int	set_zone_whole_country(dialogMenuItem *);
72179530Sjkimstatic int	set_zone_menu(dialogMenuItem *);
7319872Swollman
7419872Swollmanstruct continent {
7519872Swollman	dialogMenuItem *menu;
76179530Sjkim	int		nitems;
77179530Sjkim	int		ch;
78179530Sjkim	int		sc;
7919872Swollman};
8019872Swollman
81179530Sjkimstatic struct continent	africa, america, antarctica, arctic, asia, atlantic;
82179530Sjkimstatic struct continent	australia, europe, indian, pacific;
8319872Swollman
8419872Swollmanstatic struct continent_names {
85179530Sjkim	const char	*name;
8619872Swollman	struct continent *continent;
8719872Swollman} continent_names[] = {
88179530Sjkim	{ "Africa",	&africa },
89179530Sjkim	{ "America",	&america },
90179530Sjkim	{ "Antarctica",	&antarctica },
91179530Sjkim	{ "Arctic",	&arctic },
92179530Sjkim	{ "Asia",	&asia },
93179530Sjkim	{ "Atlantic",	&atlantic },
94179530Sjkim	{ "Australia",	&australia },
95179530Sjkim	{ "Europe",	&europe },
96179530Sjkim	{ "Indian",	&indian },
97179530Sjkim	{ "Pacific",	&pacific }
9819872Swollman};
9919872Swollman
100179530Sjkimstatic struct continent_items {
101179530Sjkim	char		prompt[2];
102179530Sjkim	char		title[30];
103179530Sjkim} continent_items[] = {
104179530Sjkim	{ "1",	"Africa" },
105179530Sjkim	{ "2",	"America -- North and South" },
106179530Sjkim	{ "3",	"Antarctica" },
107179530Sjkim	{ "4",	"Arctic Ocean" },
108179530Sjkim	{ "5",	"Asia" },
109179530Sjkim	{ "6",	"Atlantic Ocean" },
110179530Sjkim	{ "7",	"Australia" },
111179530Sjkim	{ "8",	"Europe" },
112179530Sjkim	{ "9",	"Indian Ocean" },
113179530Sjkim	{ "0",	"Pacific Ocean" }
11419872Swollman};
11519872Swollman
116179530Sjkim#define	NCONTINENTS	\
117179530Sjkim    (int)((sizeof(continent_items)) / (sizeof(continent_items[0])))
118179530Sjkimstatic dialogMenuItem continents[NCONTINENTS];
119179530Sjkim
120179530Sjkim#define	OCEANP(x)	((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
121179530Sjkim
12219872Swollmanstatic int
12319872Swollmancontinent_country_menu(dialogMenuItem *continent)
12419872Swollman{
125179530Sjkim	char		title[64], prompt[64];
12619872Swollman	struct continent *contp = continent->data;
127179530Sjkim	int		isocean = OCEANP(continent - continents);
128179530Sjkim	int		menulen;
129179530Sjkim	int		rv;
13019872Swollman
13119872Swollman	/* Short cut -- if there's only one country, don't post a menu. */
132179497Sjkim	if (contp->nitems == 1)
133179497Sjkim		return (contp->menu[0].fire(&contp->menu[0]));
13419872Swollman
13519872Swollman	/* It's amazing how much good grammar really matters... */
136179530Sjkim	if (!isocean) {
137179530Sjkim		snprintf(title, sizeof(title), "Countries in %s",
138179530Sjkim		    continent->title);
139179530Sjkim		snprintf(prompt, sizeof(prompt), "Select a country or region");
140179530Sjkim	} else {
141179530Sjkim		snprintf(title, sizeof(title), "Islands and groups in the %s",
142179530Sjkim		    continent->title);
143179530Sjkim		snprintf(prompt, sizeof(prompt), "Select an island or group");
144179530Sjkim	}
14519872Swollman
14619872Swollman	menulen = contp->nitems < 16 ? contp->nitems : 16;
147179530Sjkim	rv = dialog_menu(title, prompt, -1, -1, menulen, -contp->nitems,
148179530Sjkim	    contp->menu, 0, &contp->ch, &contp->sc);
14919872Swollman	if (rv == 0)
150179530Sjkim		return (DITEM_LEAVE_MENU);
151179530Sjkim	return (DITEM_RECREATE);
15219872Swollman}
15319872Swollman
15419872Swollmanstatic struct continent *
15519872Swollmanfind_continent(const char *name)
15619872Swollman{
157179530Sjkim	int		i;
15819872Swollman
159179530Sjkim	for (i = 0; i < NCONTINENTS; i++)
16019872Swollman		if (strcmp(name, continent_names[i].name) == 0)
161179530Sjkim			return (continent_names[i].continent);
162179530Sjkim	return (0);
16319872Swollman}
16419872Swollman
16519872Swollmanstruct country {
166179530Sjkim	char		*name;
167179530Sjkim	char		*tlc;
168179530Sjkim	int		nzones;
169179530Sjkim	char		*filename;	/* use iff nzones < 0 */
170179530Sjkim	struct continent *continent;	/* use iff nzones < 0 */
171179530Sjkim	TAILQ_HEAD(, zone) zones;	/* use iff nzones > 0 */
172179530Sjkim	dialogMenuItem	*submenu;	/* use iff nzones > 0 */
17319872Swollman};
17419872Swollman
17519872Swollmanstruct zone {
17660938Sjake	TAILQ_ENTRY(zone) link;
177179530Sjkim	char		*descr;
178179530Sjkim	char		*filename;
17919872Swollman	struct continent *continent;
18019872Swollman};
18119872Swollman
18219872Swollman/*
18319872Swollman * This is the easiest organization... we use ISO 3166 country codes,
18419872Swollman * of the two-letter variety, so we just size this array to suit.
18519872Swollman * Beats worrying about dynamic allocation.
18619872Swollman */
187179530Sjkim#define	NCOUNTRIES	(26 * 26)
18822181Sjhaystatic struct country countries[NCOUNTRIES];
18919872Swollman
190179530Sjkim#define	CODE2INT(s)	((s[0] - 'A') * 26 + (s[1] - 'A'))
191179530Sjkim
19219872Swollman/*
19319872Swollman * Read the ISO 3166 country code database in _PATH_ISO3166
19419872Swollman * (/usr/share/misc/iso3166).  On error, exit via err(3).
19519872Swollman */
19619872Swollmanstatic void
19719872Swollmanread_iso3166_table(void)
19819872Swollman{
199179530Sjkim	FILE		*fp;
200179530Sjkim	struct country	*cp;
201179530Sjkim	size_t		len;
202179530Sjkim	char		*s, *t, *name;
203179530Sjkim	int		lineno;
20419872Swollman
205198350Sedwin	fp = fopen(path_iso3166, "r");
20619872Swollman	if (!fp)
207209190Semaste		err(1, "%s", path_iso3166);
20819872Swollman	lineno = 0;
20919872Swollman
21019872Swollman	while ((s = fgetln(fp, &len)) != 0) {
21119872Swollman		lineno++;
21219872Swollman		if (s[len - 1] != '\n')
213198350Sedwin			errx(1, "%s:%d: invalid format", path_iso3166, lineno);
21419872Swollman		s[len - 1] = '\0';
21530999Sjoerg		if (s[0] == '#' || strspn(s, " \t") == len - 1)
21619872Swollman			continue;
21719872Swollman
21819872Swollman		/* Isolate the two-letter code. */
21919872Swollman		t = strsep(&s, "\t");
22019872Swollman		if (t == 0 || strlen(t) != 2)
221198350Sedwin			errx(1, "%s:%d: invalid format", path_iso3166, lineno);
22219872Swollman		if (t[0] < 'A' || t[0] > 'Z' || t[1] < 'A' || t[1] > 'Z')
223198350Sedwin			errx(1, "%s:%d: invalid code `%s'", path_iso3166,
224179530Sjkim			    lineno, t);
22519872Swollman
22619872Swollman		/* Now skip past the three-letter and numeric codes. */
227179530Sjkim		name = strsep(&s, "\t");	/* 3-let */
22819872Swollman		if (name == 0 || strlen(name) != 3)
229198350Sedwin			errx(1, "%s:%d: invalid format", path_iso3166, lineno);
230179530Sjkim		name = strsep(&s, "\t");	/* numeric */
23119872Swollman		if (name == 0 || strlen(name) != 3)
232198350Sedwin			errx(1, "%s:%d: invalid format", path_iso3166, lineno);
23319872Swollman
23419872Swollman		name = s;
23519872Swollman
23619872Swollman		cp = &countries[CODE2INT(t)];
23719872Swollman		if (cp->name)
238198350Sedwin			errx(1, "%s:%d: country code `%s' multiply defined: %s",
239198350Sedwin			    path_iso3166, lineno, t, cp->name);
24019872Swollman		cp->name = strdup(name);
24156487Scharnier		if (cp->name == NULL)
24256487Scharnier			errx(1, "malloc failed");
24319872Swollman		cp->tlc = strdup(t);
24456487Scharnier		if (cp->tlc == NULL)
24556487Scharnier			errx(1, "malloc failed");
24619872Swollman	}
24719872Swollman
24819872Swollman	fclose(fp);
24919872Swollman}
25019872Swollman
25119872Swollmanstatic void
25219872Swollmanadd_zone_to_country(int lineno, const char *tlc, const char *descr,
253179530Sjkim    const char *file, struct continent *cont)
25419872Swollman{
255179530Sjkim	struct zone	*zp;
256179530Sjkim	struct country	*cp;
25719872Swollman
25819872Swollman	if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
259198350Sedwin		errx(1, "%s:%d: country code `%s' invalid", path_zonetab,
260179530Sjkim		    lineno, tlc);
26119872Swollman
26219872Swollman	cp = &countries[CODE2INT(tlc)];
26319872Swollman	if (cp->name == 0)
264198350Sedwin		errx(1, "%s:%d: country code `%s' unknown", path_zonetab,
265179530Sjkim		    lineno, tlc);
26619872Swollman
26719872Swollman	if (descr) {
26819872Swollman		if (cp->nzones < 0)
269198350Sedwin			errx(1, "%s:%d: conflicting zone definition",
270198350Sedwin			    path_zonetab, lineno);
27119872Swollman
272179530Sjkim		zp = malloc(sizeof(*zp));
27319872Swollman		if (zp == 0)
274179530Sjkim			errx(1, "malloc(%zu)", sizeof(*zp));
27519872Swollman
27619872Swollman		if (cp->nzones == 0)
27719872Swollman			TAILQ_INIT(&cp->zones);
27819872Swollman
27919872Swollman		zp->descr = strdup(descr);
28056487Scharnier		if (zp->descr == NULL)
28156487Scharnier			errx(1, "malloc failed");
28219872Swollman		zp->filename = strdup(file);
28356487Scharnier		if (zp->filename == NULL)
28456487Scharnier			errx(1, "malloc failed");
28519872Swollman		zp->continent = cont;
28619872Swollman		TAILQ_INSERT_TAIL(&cp->zones, zp, link);
28719872Swollman		cp->nzones++;
28819872Swollman	} else {
28919872Swollman		if (cp->nzones > 0)
290198350Sedwin			errx(1, "%s:%d: zone must have description",
291198350Sedwin			    path_zonetab, lineno);
29219872Swollman		if (cp->nzones < 0)
293198350Sedwin			errx(1, "%s:%d: zone multiply defined",
294198350Sedwin			    path_zonetab, lineno);
29519872Swollman		cp->nzones = -1;
29619872Swollman		cp->filename = strdup(file);
29756487Scharnier		if (cp->filename == NULL)
29856487Scharnier			errx(1, "malloc failed");
29919872Swollman		cp->continent = cont;
30019872Swollman	}
30119872Swollman}
30219872Swollman
30319872Swollman/*
30419872Swollman * This comparison function intentionally sorts all of the null-named
30519872Swollman * ``countries''---i.e., the codes that don't correspond to a real
30619872Swollman * country---to the end.  Everything else is lexical by country name.
30719872Swollman */
30819872Swollmanstatic int
30919872Swollmancompare_countries(const void *xa, const void *xb)
31019872Swollman{
31119872Swollman	const struct country *a = xa, *b = xb;
31219872Swollman
31319872Swollman	if (a->name == 0 && b->name == 0)
314179530Sjkim		return (0);
31519872Swollman	if (a->name == 0 && b->name != 0)
316179530Sjkim		return (1);
31719872Swollman	if (b->name == 0)
318179530Sjkim		return (-1);
31919872Swollman
320179530Sjkim	return (strcmp(a->name, b->name));
32119872Swollman}
32219872Swollman
32319872Swollman/*
32419872Swollman * This must be done AFTER all zone descriptions are read, since it breaks
32519872Swollman * CODE2INT().
32619872Swollman */
32719872Swollmanstatic void
32819872Swollmansort_countries(void)
32919872Swollman{
330179530Sjkim
331179530Sjkim	qsort(countries, NCOUNTRIES, sizeof(countries[0]), compare_countries);
33219872Swollman}
33319872Swollman
33419872Swollmanstatic void
33519872Swollmanread_zones(void)
33619872Swollman{
337179530Sjkim	char		contbuf[16];
338179530Sjkim	FILE		*fp;
33919872Swollman	struct continent *cont;
340179530Sjkim	size_t		len;
341179530Sjkim	char		*line, *tlc, *coord, *file, *descr, *p;
342179530Sjkim	int		lineno;
34319872Swollman
344198350Sedwin	fp = fopen(path_zonetab, "r");
34519872Swollman	if (!fp)
346209190Semaste		err(1, "%s", path_zonetab);
34719872Swollman	lineno = 0;
34819872Swollman
34919872Swollman	while ((line = fgetln(fp, &len)) != 0) {
35019872Swollman		lineno++;
35119872Swollman		if (line[len - 1] != '\n')
352198350Sedwin			errx(1, "%s:%d: invalid format", path_zonetab, lineno);
35319872Swollman		line[len - 1] = '\0';
35419872Swollman		if (line[0] == '#')
35519872Swollman			continue;
35619872Swollman
35719872Swollman		tlc = strsep(&line, "\t");
35819872Swollman		if (strlen(tlc) != 2)
359198350Sedwin			errx(1, "%s:%d: invalid country code `%s'",
360198350Sedwin			    path_zonetab, lineno, tlc);
361208831Sedwin		coord = strsep(&line, "\t");	 /* Unused */
36219872Swollman		file = strsep(&line, "\t");
36319872Swollman		p = strchr(file, '/');
36419872Swollman		if (p == 0)
365198350Sedwin			errx(1, "%s:%d: invalid zone name `%s'", path_zonetab,
366179530Sjkim			    lineno, file);
36719872Swollman		contbuf[0] = '\0';
36819872Swollman		strncat(contbuf, file, p - file);
36919872Swollman		cont = find_continent(contbuf);
37019872Swollman		if (!cont)
371198350Sedwin			errx(1, "%s:%d: invalid region `%s'", path_zonetab,
372179530Sjkim			    lineno, contbuf);
37319872Swollman
374179530Sjkim		descr = (line != NULL && *line != '\0') ? line : NULL;
37519872Swollman
37619872Swollman		add_zone_to_country(lineno, tlc, descr, file, cont);
37719872Swollman	}
37819872Swollman	fclose(fp);
37919872Swollman}
38019872Swollman
38119872Swollmanstatic void
38219872Swollmanmake_menus(void)
38319872Swollman{
384179530Sjkim	struct country	*cp;
385179530Sjkim	struct zone	*zp, *zp2;
38619872Swollman	struct continent *cont;
387179530Sjkim	dialogMenuItem	*dmi;
388179530Sjkim	int		i;
38919872Swollman
39019872Swollman	/*
39119872Swollman	 * First, count up all the countries in each continent/ocean.
39219872Swollman	 * Be careful to count those countries which have multiple zones
39319872Swollman	 * only once for each.  NB: some countries are in multiple
39419872Swollman	 * continents/oceans.
39519872Swollman	 */
39619872Swollman	for (cp = countries; cp->name; cp++) {
39719872Swollman		if (cp->nzones == 0)
39819872Swollman			continue;
39919872Swollman		if (cp->nzones < 0) {
40019872Swollman			cp->continent->nitems++;
40119872Swollman		} else {
40270486Sben			TAILQ_FOREACH(zp, &cp->zones, link) {
40319872Swollman				cont = zp->continent;
40470486Sben				for (zp2 = TAILQ_FIRST(&cp->zones);
405179530Sjkim				    zp2->continent != cont;
406179530Sjkim				    zp2 = TAILQ_NEXT(zp2, link))
40719872Swollman					;
40819872Swollman				if (zp2 == zp)
40919872Swollman					zp->continent->nitems++;
41019872Swollman			}
41119872Swollman		}
41219872Swollman	}
41319872Swollman
41419872Swollman	/*
415179530Sjkim	 * Now allocate memory for the country menus and initialize
416179530Sjkim	 * continent menus.  We set nitems back to zero so that we can
417179530Sjkim	 * use it for counting again when we actually build the menus.
41819872Swollman	 */
419179530Sjkim	memset(continents, 0, sizeof(continents));
42019872Swollman	for (i = 0; i < NCONTINENTS; i++) {
42119872Swollman		continent_names[i].continent->menu =
422179530Sjkim		    malloc(sizeof(dialogMenuItem) *
423179530Sjkim		    continent_names[i].continent->nitems);
42419872Swollman		if (continent_names[i].continent->menu == 0)
42556487Scharnier			errx(1, "malloc for continent menu");
42619872Swollman		continent_names[i].continent->nitems = 0;
427179530Sjkim		continents[i].prompt = continent_items[i].prompt;
428179530Sjkim		continents[i].title = continent_items[i].title;
429179530Sjkim		continents[i].fire = continent_country_menu;
430179530Sjkim		continents[i].data = continent_names[i].continent;
43119872Swollman	}
43219872Swollman
43319872Swollman	/*
43419872Swollman	 * Now that memory is allocated, create the menu items for
43519872Swollman	 * each continent.  For multiple-zone countries, also create
43619872Swollman	 * the country's zone submenu.
43719872Swollman	 */
43819872Swollman	for (cp = countries; cp->name; cp++) {
43919872Swollman		if (cp->nzones == 0)
44019872Swollman			continue;
44119872Swollman		if (cp->nzones < 0) {
44219872Swollman			dmi = &cp->continent->menu[cp->continent->nitems];
443179530Sjkim			memset(dmi, 0, sizeof(*dmi));
444179530Sjkim			asprintf(&dmi->prompt, "%d", ++cp->continent->nitems);
44519872Swollman			dmi->title = cp->name;
44619872Swollman			dmi->checked = 0;
44719872Swollman			dmi->fire = set_zone_whole_country;
44819872Swollman			dmi->selected = 0;
44919872Swollman			dmi->data = cp;
45019872Swollman		} else {
451179530Sjkim			cp->submenu = malloc(cp->nzones * sizeof(*dmi));
45219872Swollman			if (cp->submenu == 0)
45356487Scharnier				errx(1, "malloc for submenu");
45419872Swollman			cp->nzones = 0;
45570486Sben			TAILQ_FOREACH(zp, &cp->zones, link) {
45619872Swollman				cont = zp->continent;
45719872Swollman				dmi = &cp->submenu[cp->nzones];
458179530Sjkim				memset(dmi, 0, sizeof(*dmi));
459179530Sjkim				asprintf(&dmi->prompt, "%d", ++cp->nzones);
46019872Swollman				dmi->title = zp->descr;
46119872Swollman				dmi->checked = 0;
46219872Swollman				dmi->fire = set_zone_multi;
46319872Swollman				dmi->selected = 0;
46419872Swollman				dmi->data = zp;
46519872Swollman
46670486Sben				for (zp2 = TAILQ_FIRST(&cp->zones);
467179530Sjkim				    zp2->continent != cont;
468179530Sjkim				    zp2 = TAILQ_NEXT(zp2, link))
46919872Swollman					;
47019872Swollman				if (zp2 != zp)
47119872Swollman					continue;
47219872Swollman
47319872Swollman				dmi = &cont->menu[cont->nitems];
474179530Sjkim				memset(dmi, 0, sizeof(*dmi));
47519872Swollman				asprintf(&dmi->prompt, "%d", ++cont->nitems);
47619872Swollman				dmi->title = cp->name;
47719872Swollman				dmi->checked = 0;
47819872Swollman				dmi->fire = set_zone_menu;
47919872Swollman				dmi->selected = 0;
48019872Swollman				dmi->data = cp;
48119872Swollman			}
48219872Swollman		}
48319872Swollman	}
48419872Swollman}
48519872Swollman
48619872Swollmanstatic int
48719872Swollmanset_zone_menu(dialogMenuItem *dmi)
48819872Swollman{
489179530Sjkim	char		title[64], prompt[64];
490179530Sjkim	struct country	*cp = dmi->data;
491179530Sjkim	int		menulen;
492179530Sjkim	int		rv;
49319872Swollman
494179530Sjkim	snprintf(title, sizeof(title), "%s Time Zones", cp->name);
495179530Sjkim	snprintf(prompt, sizeof(prompt),
496179530Sjkim	    "Select a zone which observes the same time as your locality.");
49719872Swollman	menulen = cp->nzones < 16 ? cp->nzones : 16;
498179530Sjkim	rv = dialog_menu(title, prompt, -1, -1, menulen, -cp->nzones,
499179530Sjkim	    cp->submenu, 0, 0, 0);
50019872Swollman	if (rv != 0)
501179530Sjkim		return (DITEM_RECREATE);
502179530Sjkim	return (DITEM_LEAVE_MENU);
50319872Swollman}
50419872Swollman
50519872Swollmanstatic int
506198350Sedwininstall_zoneinfo_file(const char *zoneinfo_file)
50719872Swollman{
508179530Sjkim	char		buf[1024];
509179530Sjkim	char		title[64], prompt[64];
510179530Sjkim	struct stat	sb;
511179530Sjkim	ssize_t		len;
512179530Sjkim	int		fd1, fd2, copymode;
51319872Swollman
514198350Sedwin	if (lstat(path_localtime, &sb) < 0) {
51519872Swollman		/* Nothing there yet... */
51619872Swollman		copymode = 1;
517179530Sjkim	} else if (S_ISLNK(sb.st_mode))
51819872Swollman		copymode = 0;
51919872Swollman	else
52019872Swollman		copymode = 1;
52119872Swollman
52221915Sjkh#ifdef VERBOSE
52319872Swollman	if (copymode)
524179530Sjkim		snprintf(prompt, sizeof(prompt),
525198350Sedwin		    "Copying %s to %s", zoneinfo_file, path_localtime);
52619872Swollman	else
527179530Sjkim		snprintf(prompt, sizeof(prompt),
528198350Sedwin		    "Creating symbolic link %s to %s",
529198350Sedwin		    path_localtime, zoneinfo_file);
530198267Sedwin	if (usedialog)
531198267Sedwin		dialog_notify(prompt);
532198267Sedwin	else
533198267Sedwin		fprintf(stderr, "%s\n", prompt);
53421915Sjkh#endif
53519872Swollman
53619872Swollman	if (reallydoit) {
53719872Swollman		if (copymode) {
538198350Sedwin			fd1 = open(zoneinfo_file, O_RDONLY, 0);
53919872Swollman			if (fd1 < 0) {
540179530Sjkim				snprintf(title, sizeof(title), "Error");
541179530Sjkim				snprintf(prompt, sizeof(prompt),
542198350Sedwin				    "Could not open %s: %s", zoneinfo_file,
543179530Sjkim				    strerror(errno));
544198267Sedwin				if (usedialog)
545198267Sedwin					dialog_mesgbox(title, prompt, 8, 72);
546198267Sedwin				else
547198267Sedwin					fprintf(stderr, "%s\n", prompt);
548179530Sjkim				return (DITEM_FAILURE | DITEM_RECREATE);
54919872Swollman			}
55019872Swollman
551198350Sedwin			unlink(path_localtime);
552198350Sedwin			fd2 = open(path_localtime, O_CREAT | O_EXCL | O_WRONLY,
553179530Sjkim			    S_IRUSR | S_IRGRP | S_IROTH);
55419872Swollman			if (fd2 < 0) {
555179530Sjkim				snprintf(title, sizeof(title), "Error");
556179530Sjkim				snprintf(prompt, sizeof(prompt),
557198350Sedwin				    "Could not open %s: %s",
558198350Sedwin				    path_localtime, strerror(errno));
559198267Sedwin				if (usedialog)
560198267Sedwin					dialog_mesgbox(title, prompt, 8, 72);
561198267Sedwin				else
562198267Sedwin					fprintf(stderr, "%s\n", prompt);
563179530Sjkim				return (DITEM_FAILURE | DITEM_RECREATE);
56419872Swollman			}
56519872Swollman
566179530Sjkim			while ((len = read(fd1, buf, sizeof(buf))) > 0)
567208830Sedwin				if ((len = write(fd2, buf, len)) < 0)
568208830Sedwin					break;
56919872Swollman
57019872Swollman			if (len == -1) {
571179530Sjkim				snprintf(title, sizeof(title), "Error");
572179530Sjkim				snprintf(prompt, sizeof(prompt),
573198350Sedwin				    "Error copying %s to %s %s", zoneinfo_file,
574198350Sedwin				    path_localtime, strerror(errno));
575198267Sedwin				if (usedialog)
576198267Sedwin					dialog_mesgbox(title, prompt, 8, 72);
577198267Sedwin				else
578198267Sedwin					fprintf(stderr, "%s\n", prompt);
57919872Swollman				/* Better to leave none than a corrupt one. */
580198350Sedwin				unlink(path_localtime);
581179530Sjkim				return (DITEM_FAILURE | DITEM_RECREATE);
58219872Swollman			}
58319872Swollman			close(fd1);
58419872Swollman			close(fd2);
58519872Swollman		} else {
586198350Sedwin			if (access(zoneinfo_file, R_OK) != 0) {
587179530Sjkim				snprintf(title, sizeof(title), "Error");
588179530Sjkim				snprintf(prompt, sizeof(prompt),
589198350Sedwin				    "Cannot access %s: %s", zoneinfo_file,
590179530Sjkim				    strerror(errno));
591198267Sedwin				if (usedialog)
592198267Sedwin					dialog_mesgbox(title, prompt, 8, 72);
593198267Sedwin				else
594198267Sedwin					fprintf(stderr, "%s\n", prompt);
595179530Sjkim				return (DITEM_FAILURE | DITEM_RECREATE);
59619872Swollman			}
597198350Sedwin			unlink(path_localtime);
598198350Sedwin			if (symlink(zoneinfo_file, path_localtime) < 0) {
599179530Sjkim				snprintf(title, sizeof(title), "Error");
600179530Sjkim				snprintf(prompt, sizeof(prompt),
601198350Sedwin				    "Cannot create symbolic link %s to %s: %s",
602198350Sedwin				    path_localtime, zoneinfo_file,
603179530Sjkim				    strerror(errno));
604198267Sedwin				if (usedialog)
605198267Sedwin					dialog_mesgbox(title, prompt, 8, 72);
606198267Sedwin				else
607198267Sedwin					fprintf(stderr, "%s\n", prompt);
608179530Sjkim				return (DITEM_FAILURE | DITEM_RECREATE);
60919872Swollman			}
61019872Swollman		}
61119872Swollman	}
61219872Swollman
61321915Sjkh#ifdef VERBOSE
614179530Sjkim	snprintf(title, sizeof(title), "Done");
61519872Swollman	if (copymode)
616179530Sjkim		snprintf(prompt, sizeof(prompt),
617198350Sedwin		    "Copied timezone file from %s to %s", zoneinfo_file,
618198350Sedwin		    path_localtime);
61919872Swollman	else
620198350Sedwin		snprintf(prompt, sizeof(prompt),
621198350Sedwin		    "Created symbolic link from %s to %s", zoneinfo_file,
622198350Sedwin		    path_localtime);
623198267Sedwin	if (usedialog)
624198267Sedwin		dialog_mesgbox(title, prompt, 8, 72);
625198267Sedwin	else
626198267Sedwin		fprintf(stderr, "%s\n", prompt);
62721915Sjkh#endif
628198267Sedwin
629198350Sedwin	return (DITEM_LEAVE_MENU);
630198350Sedwin}
631198350Sedwin
632198350Sedwinstatic int
633198350Sedwininstall_zoneinfo(const char *zoneinfo)
634198350Sedwin{
635198350Sedwin	int		rv;
636198350Sedwin	FILE		*f;
637198350Sedwin	char		path_zoneinfo_file[MAXPATHLEN];
638198350Sedwin
639198350Sedwin	sprintf(path_zoneinfo_file, "%s/%s", path_zoneinfo, zoneinfo);
640198350Sedwin	rv = install_zoneinfo_file(path_zoneinfo_file);
641198350Sedwin
642198267Sedwin	/* Save knowledge for later */
643198350Sedwin	if ((f = fopen(path_db, "w")) != NULL) {
644198350Sedwin		fprintf(f, "%s\n", zoneinfo);
645198267Sedwin		fclose(f);
646198267Sedwin	}
647198267Sedwin
648198350Sedwin	return (rv);
64919872Swollman}
65019872Swollman
65119872Swollmanstatic int
65219872Swollmanconfirm_zone(const char *filename)
65319872Swollman{
654179530Sjkim	char		title[64], prompt[64];
655179530Sjkim	time_t		t = time(0);
656179530Sjkim	struct tm	*tm;
657179530Sjkim	int		rv;
65819872Swollman
65919872Swollman	setenv("TZ", filename, 1);
66019872Swollman	tzset();
66119872Swollman	tm = localtime(&t);
66219872Swollman
663179530Sjkim	snprintf(title, sizeof(title), "Confirmation");
664179530Sjkim	snprintf(prompt, sizeof(prompt),
665179530Sjkim	    "Does the abbreviation `%s' look reasonable?", tm->tm_zone);
666179530Sjkim	rv = !dialog_yesno(title, prompt, 5, 72);
667179530Sjkim	return (rv);
66819872Swollman}
66919872Swollman
67019872Swollmanstatic int
67119872Swollmanset_zone_multi(dialogMenuItem *dmi)
67219872Swollman{
673179530Sjkim	struct zone	*zp = dmi->data;
674179530Sjkim	int		rv;
67519872Swollman
67619872Swollman	if (!confirm_zone(zp->filename))
677179530Sjkim		return (DITEM_FAILURE | DITEM_RECREATE);
67819872Swollman
679198350Sedwin	rv = install_zoneinfo(zp->filename);
680179530Sjkim	return (rv);
68119872Swollman}
68219872Swollman
68319872Swollmanstatic int
68419872Swollmanset_zone_whole_country(dialogMenuItem *dmi)
68519872Swollman{
686179530Sjkim	struct country	*cp = dmi->data;
687179530Sjkim	int		rv;
68819872Swollman
68919872Swollman	if (!confirm_zone(cp->filename))
690179530Sjkim		return (DITEM_FAILURE | DITEM_RECREATE);
69119872Swollman
692198350Sedwin	rv = install_zoneinfo(cp->filename);
693179530Sjkim	return (rv);
69419872Swollman}
69519872Swollman
69630763Scharnierstatic void
697179530Sjkimusage(void)
69830763Scharnier{
699179530Sjkim
700198267Sedwin	fprintf(stderr, "usage: tzsetup [-nrs] [zoneinfo file]\n");
70130763Scharnier	exit(1);
70230763Scharnier}
70330763Scharnier
704179530Sjkim#if defined(__sparc64__)
705179530Sjkim#define	DIALOG_UTC	dialog_yesno
706179530Sjkim#else
707179530Sjkim#define	DIALOG_UTC	dialog_noyes
708179530Sjkim#endif
709179530Sjkim
71019872Swollmanint
71119872Swollmanmain(int argc, char **argv)
71219872Swollman{
713179530Sjkim	char		title[64], prompt[128];
714198267Sedwin	int		c, fd, rv, skiputc;
71519872Swollman
716195339Sattilio	skiputc = 0;
717198350Sedwin	while ((c = getopt(argc, argv, "C:nrs")) != -1) {
71819872Swollman		switch(c) {
719198350Sedwin		case 'C':
720198350Sedwin			chrootenv = optarg;
721198350Sedwin			break;
72219872Swollman		case 'n':
72319872Swollman			reallydoit = 0;
72419872Swollman			break;
725198267Sedwin		case 'r':
726198267Sedwin			reinstall = 1;
727198350Sedwin			usedialog = 0;
728198267Sedwin			break;
729195339Sattilio		case 's':
730195339Sattilio			skiputc = 1;
731195339Sattilio			break;
73219872Swollman		default:
73330763Scharnier			usage();
73419872Swollman		}
73519872Swollman	}
73619872Swollman
73743544Swollman	if (argc - optind > 1)
73830763Scharnier		usage();
73919872Swollman
740198350Sedwin	if (chrootenv == NULL) {
741198350Sedwin		strcpy(path_zonetab, _PATH_ZONETAB);
742198350Sedwin		strcpy(path_iso3166, _PATH_ISO3166);
743198350Sedwin		strcpy(path_zoneinfo, _PATH_ZONEINFO);
744198350Sedwin		strcpy(path_localtime, _PATH_LOCALTIME);
745198350Sedwin		strcpy(path_db, _PATH_DB);
746198350Sedwin		strcpy(path_wall_cmos_clock, _PATH_WALL_CMOS_CLOCK);
747198350Sedwin	} else {
748198350Sedwin		sprintf(path_zonetab, "%s/%s", chrootenv, _PATH_ZONETAB);
749198350Sedwin		sprintf(path_iso3166, "%s/%s", chrootenv, _PATH_ISO3166);
750198350Sedwin		sprintf(path_zoneinfo, "%s/%s", chrootenv, _PATH_ZONEINFO);
751198350Sedwin		sprintf(path_localtime, "%s/%s", chrootenv, _PATH_LOCALTIME);
752198350Sedwin		sprintf(path_db, "%s/%s", chrootenv, _PATH_DB);
753198350Sedwin		sprintf(path_wall_cmos_clock, "%s/%s", chrootenv,
754198350Sedwin		    _PATH_WALL_CMOS_CLOCK);
755198350Sedwin	}
756198350Sedwin
757198350Sedwin
75849435Sru	/* Override the user-supplied umask. */
759179530Sjkim	(void)umask(S_IWGRP | S_IWOTH);
76049435Sru
76119872Swollman	read_iso3166_table();
76219872Swollman	read_zones();
76319872Swollman	sort_countries();
76419872Swollman	make_menus();
76519872Swollman
766198267Sedwin	if (reinstall == 1) {
767198267Sedwin		FILE *f;
768198267Sedwin		char zonefile[MAXPATHLEN];
769198350Sedwin		char path_db[MAXPATHLEN];
770198267Sedwin
771198350Sedwin		zonefile[0] = '\0';
772198350Sedwin		path_db[0] = '\0';
773198350Sedwin		if (chrootenv != NULL) {
774198350Sedwin			sprintf(zonefile, "%s/", chrootenv);
775198350Sedwin			sprintf(path_db, "%s/", chrootenv);
776198350Sedwin		}
777198350Sedwin		strcat(zonefile, _PATH_ZONEINFO);
778198350Sedwin		strcat(zonefile, "/");
779198350Sedwin		strcat(path_db, _PATH_DB);
780198350Sedwin
781198350Sedwin		if ((f = fopen(path_db, "r")) != NULL) {
782198350Sedwin			if (fgets(zonefile, sizeof(zonefile), f) != NULL) {
783198267Sedwin				zonefile[sizeof(zonefile) - 1] = 0;
784198267Sedwin				if (strlen(zonefile) > 0) {
785198267Sedwin					zonefile[strlen(zonefile) - 1] = 0;
786198350Sedwin					rv = install_zoneinfo(zonefile);
787198267Sedwin					exit(rv & ~DITEM_LEAVE_MENU);
788198267Sedwin				}
789198350Sedwin				errx(1, "Error reading %s.\n", path_db);
790198267Sedwin			}
791198267Sedwin			fclose(f);
792198267Sedwin			errx(1,
793198267Sedwin			    "Unable to determine earlier installed zoneinfo "
794198350Sedwin			    "file. Check %s", path_db);
795198267Sedwin		}
796198350Sedwin		errx(1, "Cannot open %s for reading. Does it exist?", path_db);
797198267Sedwin	}
798198267Sedwin
799198350Sedwin	/*
800198350Sedwin	 * If the arguments on the command-line do not specify a file,
801198350Sedwin	 * then interpret it as a zoneinfo name
802198350Sedwin	 */
803198350Sedwin	if (optind == argc - 1) {
804198350Sedwin		struct stat sb;
805198350Sedwin
806198350Sedwin		if (stat(argv[optind], &sb) != 0) {
807198350Sedwin			usedialog = 0;
808198350Sedwin			rv = install_zoneinfo(argv[optind]);
809198350Sedwin			exit(rv & ~DITEM_LEAVE_MENU);
810198350Sedwin		}
811198350Sedwin		/* FALLTHROUGH */
812198350Sedwin	}
813198350Sedwin
81419872Swollman	init_dialog();
815195339Sattilio	if (skiputc == 0) {
816195339Sattilio		snprintf(title, sizeof(title),
817195339Sattilio		    "Select local or UTC (Greenwich Mean Time) clock");
818195339Sattilio		snprintf(prompt, sizeof(prompt),
819195339Sattilio		    "Is this machine's CMOS clock set to UTC?  "
820195339Sattilio		    "If it is set to local time,\n"
821195339Sattilio		    "or you don't know, please choose NO here!");
822195339Sattilio		if (!DIALOG_UTC(title, prompt, 7, 72)) {
823195339Sattilio			if (reallydoit)
824195339Sattilio				unlink(_PATH_WALL_CMOS_CLOCK);
825195339Sattilio		} else {
826195339Sattilio			if (reallydoit) {
827195339Sattilio				fd = open(_PATH_WALL_CMOS_CLOCK,
828195339Sattilio				    O_WRONLY | O_CREAT | O_TRUNC,
829195339Sattilio				    S_IRUSR | S_IRGRP | S_IROTH);
830198254Sedwin				if (fd < 0) {
831198254Sedwin					end_dialog();
832195339Sattilio					err(1, "create %s",
833195339Sattilio					    _PATH_WALL_CMOS_CLOCK);
834198254Sedwin				}
835195339Sattilio				close(fd);
836195339Sattilio			}
83741852Speter		}
838195339Sattilio		dialog_clear_norefresh();
83932394Ssteve	}
84043544Swollman	if (optind == argc - 1) {
841179530Sjkim		snprintf(title, sizeof(title), "Default timezone provided");
842179530Sjkim		snprintf(prompt, sizeof(prompt),
843179530Sjkim		    "\nUse the default `%s' zone?", argv[optind]);
844179530Sjkim		if (!dialog_yesno(title, prompt, 7, 72)) {
845198350Sedwin			rv = install_zoneinfo_file(argv[optind]);
84643544Swollman			dialog_clear();
84743544Swollman			end_dialog();
848198267Sedwin			exit(rv & ~DITEM_LEAVE_MENU);
84943544Swollman		}
85043544Swollman		dialog_clear_norefresh();
85143544Swollman	}
852179530Sjkim	snprintf(title, sizeof(title), "Time Zone Selector");
853179530Sjkim	snprintf(prompt, sizeof(prompt), "Select a region");
854179530Sjkim	dialog_menu(title, prompt, -1, -1, NCONTINENTS, -NCONTINENTS,
855179530Sjkim	    continents, 0, NULL, NULL);
85643544Swollman
85722815Sjkh	dialog_clear();
85819872Swollman	end_dialog();
859179530Sjkim	return (0);
86019872Swollman}
861