tzsetup.c revision 30763
112115Sdyson/*
212115Sdyson * Copyright 1996 Massachusetts Institute of Technology
312115Sdyson *
412115Sdyson * Permission to use, copy, modify, and distribute this software and
512115Sdyson * its documentation for any purpose and without fee is hereby
612115Sdyson * granted, provided that both the above copyright notice and this
712115Sdyson * permission notice appear in all copies, that both the above
812115Sdyson * copyright notice and this permission notice appear in all
912115Sdyson * supporting documentation, and that the name of M.I.T. not be used
1012115Sdyson * in advertising or publicity pertaining to distribution of the
1112115Sdyson * software without specific, written prior permission.  M.I.T. makes
1212115Sdyson * no representations about the suitability of this software for any
1312115Sdyson * purpose.  It is provided "as is" without express or implied
1412115Sdyson * warranty.
1512115Sdyson *
1612115Sdyson * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
1712115Sdyson * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
1812115Sdyson * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
1912115Sdyson * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
2012115Sdyson * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
2112115Sdyson * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
2212115Sdyson * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
2312115Sdyson * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
2412115Sdyson * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
2512115Sdyson * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
2612115Sdyson * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
2712115Sdyson * SUCH DAMAGE.
2812115Sdyson */
2912115Sdyson
3012115Sdyson/*
3112115Sdyson * Second attempt at a `tzmenu' program, using the separate description
3212115Sdyson * files provided in newer tzdata releases.
3312115Sdyson */
3412115Sdyson
3512115Sdyson#ifndef lint
3612115Sdysonstatic const char rcsid[] =
3712115Sdyson	"$Id$";
3812115Sdyson#endif /* not lint */
3912115Sdyson
4012115Sdyson#include <sys/types.h>
4112115Sdyson#include <dialog.h>
4213260Swollman#include <err.h>
4312115Sdyson#include <errno.h>
4412115Sdyson#include <stdio.h>
4512115Sdyson#include <stdlib.h>
4612115Sdyson#include <string.h>
4712115Sdyson#include <unistd.h>
4812115Sdyson
4912115Sdyson#include <sys/fcntl.h>
5012115Sdyson#include <sys/queue.h>
5112115Sdyson#include <sys/stat.h>
5229906Skato
5324131Sbde#include "paths.h"
5412115Sdyson
5512115Sdysonstatic int reallydoit = 1;
5612115Sdyson
5712115Sdysonstatic int continent_country_menu(dialogMenuItem *);
5812115Sdysonstatic int set_zone_multi(dialogMenuItem *);
5912115Sdysonstatic int set_zone_whole_country(dialogMenuItem *);
6012115Sdysonstatic int set_zone_menu(dialogMenuItem *);
6112115Sdyson
6212115Sdysonstruct continent {
6312115Sdyson	dialogMenuItem *menu;
6412115Sdyson	int nitems;
6512115Sdyson	int ch;
6612115Sdyson	int sc;
6712115Sdyson};
6812115Sdyson
6912115Sdysonstatic struct continent africa, america, antarctica, arctic, asia, atlantic;
7028270Swollmanstatic struct continent australia, europe, indian, pacific;
7112911Sphk
7212911Sphkstatic struct continent_names {
7312911Sphk	char *name;
7412911Sphk	struct continent *continent;
7512911Sphk} continent_names[] = {
7612911Sphk	{ "Africa", &africa }, { "America", &america },
7712911Sphk	{ "Antarctica", &antarctica }, { "Arctic", &arctic },
7812911Sphk	{ "Asia", &asia },
7912911Sphk	{ "Atlantic", &atlantic }, { "Australia", &australia },
8012911Sphk	{ "Europe", &europe }, { "Indian", &indian }, { "Pacific", &pacific }
8112911Sphk};
8212911Sphk
8312911Sphkstatic dialogMenuItem continents[] = {
8412115Sdyson	{ "1", "Africa", 0, continent_country_menu, 0, &africa },
8531315Sbde	{ "2", "America -- North and South", 0, continent_country_menu, 0,
8630280Sphk		  &america },
8712911Sphk	{ "3", "Antarctica", 0, continent_country_menu, 0, &antarctica },
8812115Sdyson	{ "4", "Arctic Ocean", 0, continent_country_menu, 0, &arctic },
8912115Sdyson	{ "5", "Asia", 0, continent_country_menu, 0, &asia },
9012115Sdyson	{ "6", "Atlantic Ocean", 0, continent_country_menu, 0, &atlantic },
9112115Sdyson	{ "7", "Australia", 0, continent_country_menu, 0, &australia },
9212115Sdyson	{ "8", "Europe", 0, continent_country_menu, 0, &europe },
9312115Sdyson	{ "9", "Indian Ocean", 0, continent_country_menu, 0, &indian },
9412115Sdyson	{ "0", "Pacific Ocean", 0, continent_country_menu, 0, &pacific }
9512115Sdyson};
9612115Sdyson#define NCONTINENTS ((sizeof continents)/(sizeof continents[0]))
9712115Sdyson#define OCEANP(x) ((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
9812115Sdyson
9912115Sdysonstatic int
10012115Sdysoncontinent_country_menu(dialogMenuItem *continent)
10138909Sbde{
10212115Sdyson	int rv;
10312115Sdyson	struct continent *contp = continent->data;
10412115Sdyson	char title[256];
10512911Sphk	int isocean = OCEANP(continent - continents);
10612115Sdyson	int menulen;
10716322Sgpalmer
10816322Sgpalmer	/* Short cut -- if there's only one country, don't post a menu. */
10916322Sgpalmer	if (contp->nitems == 1) {
11016322Sgpalmer		return set_zone_menu(&contp->menu[0]);
11116322Sgpalmer	}
11216322Sgpalmer
11316322Sgpalmer	/* It's amazing how much good grammar really matters... */
11412115Sdyson	if (!isocean)
11516322Sgpalmer		snprintf(title, sizeof title, "Countries in %s",
11612115Sdyson			 continent->title);
11712115Sdyson	else
11812115Sdyson		snprintf(title, sizeof title, "Islands and groups in the %s",
11912115Sdyson			 continent->title);
12012115Sdyson
12112911Sphk	menulen = contp->nitems < 16 ? contp->nitems : 16;
12212115Sdyson	rv = dialog_menu(title, (isocean ? "Select an island or group"
12312115Sdyson                                 : "Select a country"), -1, -1, menulen,
12412115Sdyson			 -contp->nitems, contp->menu, 0, &contp->ch,
12512115Sdyson			 &contp->sc);
12612115Sdyson	if (rv == 0)
12712115Sdyson		return DITEM_LEAVE_MENU;
12812115Sdyson	return DITEM_RECREATE;
12912115Sdyson}
13012115Sdyson
13129208Sbdestatic struct continent *
13229208Sbdefind_continent(const char *name)
13329208Sbde{
13429208Sbde	int i;
13512115Sdyson
13612115Sdyson	for (i = 0; i < NCONTINENTS; i++) {
13712115Sdyson		if (strcmp(name, continent_names[i].name) == 0)
13812115Sdyson			return continent_names[i].continent;
13929888Skato	}
14029888Skato	return 0;
14129888Skato}
14229888Skato
14312115Sdysonstruct country {
14412115Sdyson	char *name;
14512115Sdyson	char *tlc;
14612115Sdyson	int nzones;
14712115Sdyson	char *filename;		/* use iff nzones < 0 */
14812115Sdyson	struct continent *continent; /* use iff nzones < 0 */
14912115Sdyson	TAILQ_HEAD(, zone) zones; /* use iff nzones > 0 */
15012115Sdyson	dialogMenuItem *submenu; /* use iff nzones > 0 */
15112115Sdyson};
15230469Sjulian
15312115Sdysonstruct zone {
15412115Sdyson	TAILQ_ENTRY(zone) link;
15512115Sdyson	char *descr;
15612115Sdyson	char *filename;
15712115Sdyson	struct continent *continent;
15812115Sdyson};
15912115Sdyson
16012115Sdyson/*
16112115Sdyson * This is the easiest organization... we use ISO 3166 country codes,
16212115Sdyson * of the two-letter variety, so we just size this array to suit.
16312115Sdyson * Beats worrying about dynamic allocation.
16412115Sdyson */
16512115Sdyson#define NCOUNTRIES	(26*26)
16612115Sdysonstatic struct country countries[NCOUNTRIES];
16712115Sdyson#define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A'))
16812115Sdyson
16916322Sgpalmer/*
17012115Sdyson * Read the ISO 3166 country code database in _PATH_ISO3166
17112115Sdyson * (/usr/share/misc/iso3166).  On error, exit via err(3).
17212115Sdyson */
17312115Sdysonstatic void
17412115Sdysonread_iso3166_table(void)
17512115Sdyson{
17612911Sphk	FILE *fp;
17712115Sdyson	char *s, *t, *name;
17812115Sdyson	size_t len;
17912115Sdyson	int lineno;
18012115Sdyson	struct country *cp;
18112115Sdyson
18212115Sdyson	fp = fopen(_PATH_ISO3166, "r");
18312115Sdyson	if (!fp)
18412115Sdyson		err(1, _PATH_ISO3166);
18512115Sdyson	lineno = 0;
18612115Sdyson
18712115Sdyson	while ((s = fgetln(fp, &len)) != 0) {
18812115Sdyson		lineno++;
18912115Sdyson		if (s[len - 1] != '\n')
19012115Sdyson			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
19112115Sdyson		s[len - 1] = '\0';
19212115Sdyson		if (s[0] == '#')
19312115Sdyson			continue;
19412115Sdyson
19512115Sdyson		/* Isolate the two-letter code. */
19629888Skato		t = strsep(&s, "\t");
19729888Skato		if (t == 0 || strlen(t) != 2)
19812115Sdyson			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
19912115Sdyson		if (t[0] < 'A' || t[0] > 'Z' || t[1] < 'A' || t[1] > 'Z')
20012115Sdyson			errx(1, _PATH_ISO3166 ":%d: invalid code `%s'",
20112115Sdyson			     lineno, t);
20212115Sdyson
20329888Skato		/* Now skip past the three-letter and numeric codes. */
20429888Skato		name = strsep(&s, "\t"); /* 3-let */
20529888Skato		if (name == 0 || strlen(name) != 3)
20629888Skato			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
20712115Sdyson		name = strsep(&s, "\t"); /* numeric */
20812115Sdyson		if (name == 0 || strlen(name) != 3)
20912115Sdyson			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
21012115Sdyson
21122521Sdyson		name = s;
21212115Sdyson
21312115Sdyson		cp = &countries[CODE2INT(t)];
21422521Sdyson		if (cp->name)
21512115Sdyson			errx(1, _PATH_ISO3166
21612115Sdyson			     ":%d: country code `%s' multiply defined: %s",
21712115Sdyson			     lineno, t, cp->name);
21812115Sdyson		cp->name = strdup(name);
21912115Sdyson		cp->tlc = strdup(t);
22031132Sjulian	}
22112115Sdyson
22212115Sdyson	fclose(fp);
22312115Sdyson}
22412115Sdyson
22512115Sdysonstatic void
22612115Sdysonadd_zone_to_country(int lineno, const char *tlc, const char *descr,
22712115Sdyson		    const char *file, struct continent *cont)
22812115Sdyson{
22912115Sdyson	struct zone *zp;
23012115Sdyson	struct country *cp;
23112115Sdyson
23212115Sdyson	if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
23312115Sdyson		errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid",
23412115Sdyson		     lineno, tlc);
23512115Sdyson
23612115Sdyson	cp = &countries[CODE2INT(tlc)];
23712115Sdyson	if (cp->name == 0)
23812115Sdyson		errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown",
23912115Sdyson		     lineno, tlc);
24012115Sdyson
24112115Sdyson	if (descr) {
24212115Sdyson		if (cp->nzones < 0)
24312115Sdyson			errx(1, _PATH_ZONETAB
24412115Sdyson			     ":%d: conflicting zone definition", lineno);
24512115Sdyson
24612115Sdyson		zp = malloc(sizeof *zp);
24712115Sdyson		if (zp == 0)
24812115Sdyson			err(1, "malloc(%lu)", (unsigned long)sizeof *zp);
24912115Sdyson
25012115Sdyson		if (cp->nzones == 0)
25129888Skato			TAILQ_INIT(&cp->zones);
25229888Skato
25329888Skato		zp->descr = strdup(descr);
25429888Skato		zp->filename = strdup(file);
25529888Skato		zp->continent = cont;
25612115Sdyson		TAILQ_INSERT_TAIL(&cp->zones, zp, link);
25729888Skato		cp->nzones++;
25812115Sdyson	} else {
25912115Sdyson		if (cp->nzones > 0)
26012115Sdyson			errx(1, _PATH_ZONETAB
26112115Sdyson			     ":%d: zone must have description", lineno);
26212115Sdyson		if (cp->nzones < 0)
26312115Sdyson			errx(1, _PATH_ZONETAB
26412115Sdyson			     ":%d: zone multiply defined", lineno);
26512115Sdyson		cp->nzones = -1;
26612115Sdyson		cp->filename = strdup(file);
26712115Sdyson		cp->continent = cont;
26812115Sdyson	}
26912115Sdyson}
27012115Sdyson
27112115Sdyson/*
27212115Sdyson * This comparison function intentionally sorts all of the null-named
27312115Sdyson * ``countries''---i.e., the codes that don't correspond to a real
27412115Sdyson * country---to the end.  Everything else is lexical by country name.
27512115Sdyson */
27612115Sdysonstatic int
27712115Sdysoncompare_countries(const void *xa, const void *xb)
27812115Sdyson{
27912115Sdyson	const struct country *a = xa, *b = xb;
28012115Sdyson
28112115Sdyson	if (a->name == 0 && b->name == 0)
28212115Sdyson		return 0;
28312115Sdyson	if (a->name == 0 && b->name != 0)
28412115Sdyson		return 1;
28512115Sdyson	if (b->name == 0)
28612115Sdyson		return -1;
28712115Sdyson
28812115Sdyson	return strcmp(a->name, b->name);
28912115Sdyson}
29012115Sdyson
29112115Sdyson/*
29212115Sdyson * This must be done AFTER all zone descriptions are read, since it breaks
29312115Sdyson * CODE2INT().
29412115Sdyson */
29512115Sdysonstatic void
29612115Sdysonsort_countries(void)
29712115Sdyson{
29812115Sdyson	qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
29912115Sdyson}
30012115Sdyson
30112115Sdysonstatic void
30212115Sdysonread_zones(void)
30312115Sdyson{
30412115Sdyson	FILE *fp;
30512115Sdyson	char *line;
30612115Sdyson	size_t len;
30712115Sdyson	int lineno;
30812115Sdyson	char *tlc, *coord, *file, *descr, *p;
30912115Sdyson	char contbuf[16];
31012115Sdyson	struct continent *cont;
31112115Sdyson
31212115Sdyson	fp = fopen(_PATH_ZONETAB, "r");
31312115Sdyson	if (!fp)
31412115Sdyson		err(1, _PATH_ZONETAB);
31512115Sdyson	lineno = 0;
31612115Sdyson
31712115Sdyson	while ((line = fgetln(fp, &len)) != 0) {
31812115Sdyson		lineno++;
31912115Sdyson		if (line[len - 1] != '\n')
32012115Sdyson			errx(1, _PATH_ZONETAB ":%d: invalid format", lineno);
32112115Sdyson		line[len - 1] = '\0';
32212115Sdyson		if (line[0] == '#')
32312115Sdyson			continue;
32412115Sdyson
32512115Sdyson		tlc = strsep(&line, "\t");
32612115Sdyson		if (strlen(tlc) != 2)
32712115Sdyson			errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'",
32812115Sdyson			     lineno, tlc);
32912115Sdyson		coord = strsep(&line, "\t");
33012115Sdyson		file = strsep(&line, "\t");
33112115Sdyson		p = strchr(file, '/');
33212115Sdyson		if (p == 0)
33312115Sdyson			errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'",
33412115Sdyson			     lineno, file);
33512115Sdyson		contbuf[0] = '\0';
33612115Sdyson		strncat(contbuf, file, p - file);
33712115Sdyson		cont = find_continent(contbuf);
33812115Sdyson		if (!cont)
33912115Sdyson			errx(1, _PATH_ZONETAB ":%d: invalid region `%s'",
34012115Sdyson			     lineno, contbuf);
34112115Sdyson
34212115Sdyson		descr = (line && *line) ? line : 0;
34312115Sdyson
34412115Sdyson		add_zone_to_country(lineno, tlc, descr, file, cont);
34512115Sdyson	}
34612115Sdyson	fclose(fp);
34712115Sdyson}
34812115Sdyson
34912115Sdysonstatic void
35012115Sdysonmake_menus(void)
35112115Sdyson{
35212115Sdyson	struct country *cp;
35312115Sdyson	struct zone *zp, *zp2;
35412115Sdyson	struct continent *cont;
35512115Sdyson	dialogMenuItem *dmi;
35612115Sdyson	int i;
35712115Sdyson
35812115Sdyson	/*
35912115Sdyson	 * First, count up all the countries in each continent/ocean.
36012115Sdyson	 * Be careful to count those countries which have multiple zones
36112115Sdyson	 * only once for each.  NB: some countries are in multiple
36212115Sdyson	 * continents/oceans.
36312115Sdyson	 */
36412115Sdyson	for (cp = countries; cp->name; cp++) {
36512115Sdyson		if (cp->nzones == 0)
36612115Sdyson			continue;
36712115Sdyson		if (cp->nzones < 0) {
36812115Sdyson			cp->continent->nitems++;
36912115Sdyson		} else {
37012115Sdyson			for (zp = cp->zones.tqh_first; zp;
37112115Sdyson			     zp = zp->link.tqe_next) {
37212115Sdyson				cont = zp->continent;
37312115Sdyson				for (zp2 = cp->zones.tqh_first;
37412115Sdyson				     zp2->continent != cont;
37512115Sdyson				     zp2 = zp2->link.tqe_next)
37612115Sdyson					;
37712115Sdyson				if (zp2 == zp)
37812115Sdyson					zp->continent->nitems++;
37912115Sdyson			}
38012115Sdyson		}
38112115Sdyson	}
38212115Sdyson
38312115Sdyson	/*
38412115Sdyson	 * Now allocate memory for the country menus.  We set
38512115Sdyson	 * nitems back to zero so that we can use it for counting
38612115Sdyson	 * again when we actually build the menus.
38712115Sdyson	 */
38812115Sdyson	for (i = 0; i < NCONTINENTS; i++) {
38912115Sdyson		continent_names[i].continent->menu =
39012115Sdyson			malloc(sizeof(dialogMenuItem) *
39112115Sdyson			       continent_names[i].continent->nitems);
39212115Sdyson		if (continent_names[i].continent->menu == 0)
39312115Sdyson			err(1, "malloc for continent menu");
39412115Sdyson		continent_names[i].continent->nitems = 0;
39512115Sdyson	}
39612115Sdyson
39712115Sdyson	/*
39812115Sdyson	 * Now that memory is allocated, create the menu items for
39912115Sdyson	 * each continent.  For multiple-zone countries, also create
40012115Sdyson	 * the country's zone submenu.
40112115Sdyson	 */
40212115Sdyson	for (cp = countries; cp->name; cp++) {
40312115Sdyson		if (cp->nzones == 0)
40412115Sdyson			continue;
40512115Sdyson		if (cp->nzones < 0) {
40612115Sdyson			dmi = &cp->continent->menu[cp->continent->nitems];
40712115Sdyson			memset(dmi, 0, sizeof *dmi);
40812115Sdyson			asprintf(&dmi->prompt, "%d",
40912115Sdyson				 ++cp->continent->nitems);
41027881Sdyson			dmi->title = cp->name;
41127881Sdyson			dmi->checked = 0;
41212115Sdyson			dmi->fire = set_zone_whole_country;
41312115Sdyson			dmi->selected = 0;
41412115Sdyson			dmi->data = cp;
41527881Sdyson		} else {
41612115Sdyson			cp->submenu = malloc(cp->nzones * sizeof *dmi);
41712115Sdyson			if (cp->submenu == 0)
41812115Sdyson				err(1, "malloc for submenu");
41912115Sdyson			cp->nzones = 0;
42012115Sdyson			for (zp = cp->zones.tqh_first; zp;
42112115Sdyson			     zp = zp->link.tqe_next) {
42212115Sdyson				cont = zp->continent;
42312115Sdyson				dmi = &cp->submenu[cp->nzones];
42412115Sdyson				memset(dmi, 0, sizeof *dmi);
42512115Sdyson				asprintf(&dmi->prompt, "%d",
42612115Sdyson					 ++cp->nzones);
42712115Sdyson				dmi->title = zp->descr;
42812115Sdyson				dmi->checked = 0;
42912115Sdyson				dmi->fire = set_zone_multi;
43012115Sdyson				dmi->selected = 0;
43112115Sdyson				dmi->data = zp;
43212115Sdyson
43312115Sdyson				for (zp2 = cp->zones.tqh_first;
43412115Sdyson				     zp2->continent != cont;
43512115Sdyson				     zp2 = zp2->link.tqe_next)
43612115Sdyson					;
43712115Sdyson				if (zp2 != zp)
43812115Sdyson					continue;
43912115Sdyson
44012115Sdyson				dmi = &cont->menu[cont->nitems];
44112115Sdyson				memset(dmi, 0, sizeof *dmi);
44212115Sdyson				asprintf(&dmi->prompt, "%d", ++cont->nitems);
44312115Sdyson				dmi->title = cp->name;
44412115Sdyson				dmi->checked = 0;
44512115Sdyson				dmi->fire = set_zone_menu;
44612911Sphk				dmi->selected = 0;
44712115Sdyson				dmi->data = cp;
44812115Sdyson			}
44912115Sdyson		}
45012115Sdyson	}
45112115Sdyson}
45212115Sdyson
45312115Sdysonstatic int
45412115Sdysonset_zone_menu(dialogMenuItem *dmi)
45512115Sdyson{
45612115Sdyson	int rv;
45712147Sdyson	char buf[256];
45812115Sdyson	struct country *cp = dmi->data;
45912115Sdyson	int menulen;
46012115Sdyson
46112115Sdyson	snprintf(buf, sizeof buf, "%s Time Zones", cp->name);
46212115Sdyson	menulen = cp->nzones < 16 ? cp->nzones : 16;
46312115Sdyson	rv = dialog_menu(buf, "Select a zone which observes the same time as "
46412115Sdyson			 "your locality.", -1, -1, menulen, -cp->nzones,
46512115Sdyson			 cp->submenu, 0, 0, 0);
46612115Sdyson	if (rv != 0)
46712115Sdyson		return DITEM_RECREATE;
46812115Sdyson	return DITEM_LEAVE_MENU;
46912115Sdyson}
47012115Sdyson
47112115Sdysonstatic int
47212115Sdysoninstall_zone_file(const char *filename)
47312115Sdyson{
47412115Sdyson	struct stat sb;
47512115Sdyson	int fd1, fd2;
47612115Sdyson	int copymode;
47712115Sdyson	char *msg;
47812115Sdyson	ssize_t len;
47912115Sdyson	char buf[1024];
48012115Sdyson
48112115Sdyson	if (lstat(_PATH_LOCALTIME, &sb) < 0)
48212115Sdyson		/* Nothing there yet... */
48312115Sdyson		copymode = 1;
48412115Sdyson	else if(S_ISLNK(sb.st_mode))
48512115Sdyson		copymode = 0;
48612115Sdyson	else
48712115Sdyson		copymode = 1;
48812115Sdyson
48912115Sdyson#ifdef VERBOSE
49012115Sdyson	if (copymode)
49112115Sdyson		asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename);
49212115Sdyson	else
49312115Sdyson		asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME
49412115Sdyson			 " to %s", filename);
49512115Sdyson
49612115Sdyson	dialog_notify(msg);
49712115Sdyson	free(msg);
49812115Sdyson#endif
49912115Sdyson
50012115Sdyson	if (reallydoit) {
50112115Sdyson		if (copymode) {
50212115Sdyson			fd1 = open(filename, O_RDONLY, 0);
50312115Sdyson			if (fd1 < 0) {
50412115Sdyson				asprintf(&msg, "Could not open %s: %s",
50512115Sdyson					 filename, strerror(errno));
50612115Sdyson				dialog_mesgbox("Error", msg, 8, 72);
50712115Sdyson				free(msg);
50812115Sdyson				return DITEM_FAILURE | DITEM_RECREATE;
50912115Sdyson			}
51012115Sdyson
51122521Sdyson			unlink(_PATH_LOCALTIME);
51212115Sdyson			fd2 = open(_PATH_LOCALTIME,
51312115Sdyson				   O_CREAT | O_EXCL | O_WRONLY,
51412115Sdyson				   0444);
51512115Sdyson			if (fd2 < 0) {
51612115Sdyson				asprintf(&msg, "Could not open "
51712115Sdyson					 _PATH_LOCALTIME ": %s",
51812115Sdyson					 strerror(errno));
51912115Sdyson				dialog_mesgbox("Error", msg, 8, 72);
52012115Sdyson				free(msg);
52112115Sdyson				return DITEM_FAILURE | DITEM_RECREATE;
52212115Sdyson			}
52312115Sdyson
52412115Sdyson			while ((len = read(fd1, buf, sizeof buf)) > 0)
52512115Sdyson				len = write(fd2, buf, len);
52612115Sdyson
52712115Sdyson			if (len == -1) {
52812115Sdyson				asprintf(&msg, "Error copying %s to "
52912115Sdyson					 _PATH_LOCALTIME ": %s",
53012115Sdyson					 strerror(errno));
53112115Sdyson				dialog_mesgbox("Error", msg, 8, 72);
53212115Sdyson				free(msg);
53312115Sdyson				/* Better to leave none than a corrupt one. */
53412115Sdyson				unlink(_PATH_LOCALTIME);
53512115Sdyson				return DITEM_FAILURE | DITEM_RECREATE;
53612115Sdyson			}
53712115Sdyson			close(fd1);
53812115Sdyson			close(fd2);
53912911Sphk		} else {
54012115Sdyson			if (access(filename, R_OK) != 0) {
54112115Sdyson				asprintf(&msg, "Cannot access %s: %s",
54212115Sdyson					 filename, strerror(errno));
54312115Sdyson				dialog_mesgbox("Error", msg, 8, 72);
54412115Sdyson				free(msg);
54512115Sdyson				return DITEM_FAILURE | DITEM_RECREATE;
54612115Sdyson			}
54712115Sdyson			unlink(_PATH_LOCALTIME);
54812115Sdyson			if (symlink(filename, _PATH_LOCALTIME) < 0) {
54912115Sdyson				asprintf(&msg, "Cannot create symbolic link "
55012115Sdyson					 _PATH_LOCALTIME " to %s: %s",
55112115Sdyson					 filename, strerror(errno));
55212115Sdyson				dialog_mesgbox("Error", msg, 8, 72);
55312115Sdyson				free(msg);
55412115Sdyson				return DITEM_FAILURE | DITEM_RECREATE;
55512115Sdyson			}
55612115Sdyson		}
55712115Sdyson	}
55812115Sdyson
55912115Sdyson#ifdef VERBOSE
56012115Sdyson	if (copymode)
56112115Sdyson		asprintf(&msg, "Copied timezone file from %s to "
56212115Sdyson			 _PATH_LOCALTIME, filename);
56312115Sdyson	else
56412115Sdyson		asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME
56512115Sdyson			 " to %s", filename);
56612115Sdyson
56712115Sdyson	dialog_mesgbox("Done", msg, 8, 72);
56812115Sdyson	free(msg);
56912115Sdyson#endif
57012115Sdyson	return DITEM_LEAVE_MENU;
57112115Sdyson}
57212115Sdyson
57312115Sdysonstatic int
57412115Sdysonconfirm_zone(const char *filename)
57512115Sdyson{
57612115Sdyson	char *msg;
57712115Sdyson	struct tm *tm;
57812115Sdyson	time_t t = time(0);
57912115Sdyson	int rv;
58012115Sdyson
58112115Sdyson	setenv("TZ", filename, 1);
58212115Sdyson	tzset();
58312115Sdyson	tm = localtime(&t);
58412115Sdyson
58512115Sdyson	asprintf(&msg, "Does the abbreviation `%s' look reasonable?",
58612115Sdyson		 tm->tm_zone);
58712115Sdyson	rv = !dialog_yesno("Confirmation", msg, 4, 72);
58812115Sdyson	free(msg);
58912115Sdyson	return rv;
59012115Sdyson}
59112115Sdyson
59212115Sdysonstatic int
59312115Sdysonset_zone_multi(dialogMenuItem *dmi)
59412115Sdyson{
59512115Sdyson	char *fn;
59612115Sdyson	struct zone *zp = dmi->data;
59712115Sdyson	int rv;
59812115Sdyson
59912115Sdyson	if (!confirm_zone(zp->filename))
60030280Sphk		return DITEM_FAILURE | DITEM_RECREATE;
60130474Sphk
60230474Sphk	asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename);
60330492Sphk	rv = install_zone_file(fn);
60430474Sphk	free(fn);
60530474Sphk	return rv;
60612115Sdyson}
60712115Sdyson
60812115Sdysonstatic int
60912115Sdysonset_zone_whole_country(dialogMenuItem *dmi)
61012115Sdyson{
61112115Sdyson	char *fn;
61212115Sdyson	struct country *cp = dmi->data;
61312115Sdyson	int rv;
61412115Sdyson
61512115Sdyson	if (!confirm_zone(cp->filename))
61612115Sdyson		return DITEM_FAILURE | DITEM_RECREATE;
61712115Sdyson
61812115Sdyson	asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename);
61912115Sdyson	rv = install_zone_file(fn);
62012115Sdyson	free(fn);
62112115Sdyson	return rv;
62212115Sdyson}
62312115Sdyson
62412115Sdysonstatic void
62512115Sdysonusage()
62612115Sdyson{
62712115Sdyson	fprintf(stderr, "usage: tzsetup [-n]\n");
62812115Sdyson	exit(1);
62912115Sdyson}
63012115Sdyson
63112115Sdysonint
63212115Sdysonmain(int argc, char **argv)
63312115Sdyson{
63412115Sdyson	int c;
63512115Sdyson
63638909Sbde	while ((c = getopt(argc, argv, "n")) != -1) {
63712115Sdyson		switch(c) {
63812115Sdyson		case 'n':
63912115Sdyson			reallydoit = 0;
64012115Sdyson			break;
64112115Sdyson
64212115Sdyson		default:
64312115Sdyson			usage();
64412115Sdyson		}
64512115Sdyson	}
64612115Sdyson
64712115Sdyson	if (optind != argc)
64812115Sdyson		usage();
64912115Sdyson
65034430Seivind	read_iso3166_table();
65134430Seivind	read_zones();
65234430Seivind	sort_countries();
65312115Sdyson	make_menus();
65412115Sdyson
65512115Sdyson	init_dialog();
65612115Sdyson	if (!dialog_yesno("Select local or UTC (Greenwich Mean Time) clock",
65712115Sdyson			  "Is this machine's CMOS clock set to UTC?  If it is set to local time,\n"
65812115Sdyson			  "please choose NO here!", 7, 72))
65912115Sdyson		if (reallydoit)
66012115Sdyson			system("rm -f /etc/wall_cmos_clock");
66112115Sdyson	else
66212115Sdyson		if (reallydoit)
66312115Sdyson			system("touch /etc/wall_cmos_clock");
66412115Sdyson	dialog_clear_norefresh();
66512115Sdyson	dialog_menu("Time Zone Selector", "Select a region", -1, -1,
66612115Sdyson		    NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);
66712115Sdyson	dialog_clear();
66812115Sdyson	end_dialog();
66912911Sphk	return 0;
67012115Sdyson}
67112115Sdyson
67212115Sdyson