tzsetup.c revision 70092
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.
1519872Swollman *
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
3530763Scharnier#ifndef lint
3630763Scharnierstatic const char rcsid[] =
3750479Speter  "$FreeBSD: head/usr.sbin/tzsetup/tzsetup.c 70092 2000-12-16 05:29:38Z jkh $";
3830763Scharnier#endif /* not lint */
3930763Scharnier
4019872Swollman#include <sys/types.h>
4119872Swollman#include <dialog.h>
4219872Swollman#include <err.h>
4319872Swollman#include <errno.h>
4419872Swollman#include <stdio.h>
4519872Swollman#include <stdlib.h>
4619872Swollman#include <string.h>
4766907Swollman#include <time.h>
4819872Swollman#include <unistd.h>
4919872Swollman
5019872Swollman#include <sys/fcntl.h>
5119872Swollman#include <sys/queue.h>
5219872Swollman#include <sys/stat.h>
5319872Swollman
5419872Swollman#include "paths.h"
5519872Swollman
5619872Swollmanstatic int reallydoit = 1;
5719872Swollman
5819872Swollmanstatic int continent_country_menu(dialogMenuItem *);
5919872Swollmanstatic int set_zone_multi(dialogMenuItem *);
6019872Swollmanstatic int set_zone_whole_country(dialogMenuItem *);
6119872Swollmanstatic int set_zone_menu(dialogMenuItem *);
6219872Swollman
6319872Swollmanstruct continent {
6419872Swollman	dialogMenuItem *menu;
6519872Swollman	int nitems;
6619872Swollman	int ch;
6719872Swollman	int sc;
6819872Swollman};
6919872Swollman
7019872Swollmanstatic struct continent africa, america, antarctica, arctic, asia, atlantic;
7119872Swollmanstatic struct continent australia, europe, indian, pacific;
7219872Swollman
7319872Swollmanstatic struct continent_names {
7419872Swollman	char *name;
7519872Swollman	struct continent *continent;
7619872Swollman} continent_names[] = {
7719872Swollman	{ "Africa", &africa }, { "America", &america },
7819872Swollman	{ "Antarctica", &antarctica }, { "Arctic", &arctic },
7919872Swollman	{ "Asia", &asia },
8019872Swollman	{ "Atlantic", &atlantic }, { "Australia", &australia },
8119872Swollman	{ "Europe", &europe }, { "Indian", &indian }, { "Pacific", &pacific }
8219872Swollman};
8319872Swollman
8419872Swollmanstatic dialogMenuItem continents[] = {
8519872Swollman	{ "1", "Africa", 0, continent_country_menu, 0, &africa },
8619872Swollman	{ "2", "America -- North and South", 0, continent_country_menu, 0,
8719872Swollman		  &america },
8819872Swollman	{ "3", "Antarctica", 0, continent_country_menu, 0, &antarctica },
8919872Swollman	{ "4", "Arctic Ocean", 0, continent_country_menu, 0, &arctic },
9019872Swollman	{ "5", "Asia", 0, continent_country_menu, 0, &asia },
9119872Swollman	{ "6", "Atlantic Ocean", 0, continent_country_menu, 0, &atlantic },
9219872Swollman	{ "7", "Australia", 0, continent_country_menu, 0, &australia },
9319872Swollman	{ "8", "Europe", 0, continent_country_menu, 0, &europe },
9419872Swollman	{ "9", "Indian Ocean", 0, continent_country_menu, 0, &indian },
9519872Swollman	{ "0", "Pacific Ocean", 0, continent_country_menu, 0, &pacific }
9619872Swollman};
9719872Swollman#define NCONTINENTS ((sizeof continents)/(sizeof continents[0]))
9819872Swollman#define OCEANP(x) ((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
9919872Swollman
10019872Swollmanstatic int
10119872Swollmancontinent_country_menu(dialogMenuItem *continent)
10219872Swollman{
10319872Swollman	int rv;
10419872Swollman	struct continent *contp = continent->data;
10519872Swollman	char title[256];
10619872Swollman	int isocean = OCEANP(continent - continents);
10719872Swollman	int menulen;
10819872Swollman
10919872Swollman	/* Short cut -- if there's only one country, don't post a menu. */
11019872Swollman	if (contp->nitems == 1) {
11119872Swollman		return set_zone_menu(&contp->menu[0]);
11219872Swollman	}
11319872Swollman
11419872Swollman	/* It's amazing how much good grammar really matters... */
11519872Swollman	if (!isocean)
11619872Swollman		snprintf(title, sizeof title, "Countries in %s",
11719872Swollman			 continent->title);
11819872Swollman	else
11919872Swollman		snprintf(title, sizeof title, "Islands and groups in the %s",
12019872Swollman			 continent->title);
12119872Swollman
12219872Swollman	menulen = contp->nitems < 16 ? contp->nitems : 16;
12319872Swollman	rv = dialog_menu(title, (isocean ? "Select an island or group"
12419872Swollman                                 : "Select a country"), -1, -1, menulen,
12519872Swollman			 -contp->nitems, contp->menu, 0, &contp->ch,
12619872Swollman			 &contp->sc);
12719872Swollman	if (rv == 0)
12819872Swollman		return DITEM_LEAVE_MENU;
12919872Swollman	return DITEM_RECREATE;
13019872Swollman}
13119872Swollman
13219872Swollmanstatic struct continent *
13319872Swollmanfind_continent(const char *name)
13419872Swollman{
13519872Swollman	int i;
13619872Swollman
13719872Swollman	for (i = 0; i < NCONTINENTS; i++) {
13819872Swollman		if (strcmp(name, continent_names[i].name) == 0)
13919872Swollman			return continent_names[i].continent;
14019872Swollman	}
14119872Swollman	return 0;
14219872Swollman}
14319872Swollman
14419872Swollmanstruct country {
14519872Swollman	char *name;
14619872Swollman	char *tlc;
14719872Swollman	int nzones;
14819872Swollman	char *filename;		/* use iff nzones < 0 */
14919872Swollman	struct continent *continent; /* use iff nzones < 0 */
15060938Sjake	TAILQ_HEAD(, zone) zones; /* use iff nzones > 0 */
15119872Swollman	dialogMenuItem *submenu; /* use iff nzones > 0 */
15219872Swollman};
15319872Swollman
15419872Swollmanstruct zone {
15560938Sjake	TAILQ_ENTRY(zone) link;
15619872Swollman	char *descr;
15719872Swollman	char *filename;
15819872Swollman	struct continent *continent;
15919872Swollman};
16019872Swollman
16119872Swollman/*
16219872Swollman * This is the easiest organization... we use ISO 3166 country codes,
16319872Swollman * of the two-letter variety, so we just size this array to suit.
16419872Swollman * Beats worrying about dynamic allocation.
16519872Swollman */
16622181Sjhay#define NCOUNTRIES	(26*26)
16722181Sjhaystatic struct country countries[NCOUNTRIES];
16819872Swollman#define CODE2INT(s) ((s[0] - 'A') * 26 + (s[1] - 'A'))
16919872Swollman
17019872Swollman/*
17119872Swollman * Read the ISO 3166 country code database in _PATH_ISO3166
17219872Swollman * (/usr/share/misc/iso3166).  On error, exit via err(3).
17319872Swollman */
17419872Swollmanstatic void
17519872Swollmanread_iso3166_table(void)
17619872Swollman{
17719872Swollman	FILE *fp;
17819872Swollman	char *s, *t, *name;
17919872Swollman	size_t len;
18019872Swollman	int lineno;
18119872Swollman	struct country *cp;
18219872Swollman
18319872Swollman	fp = fopen(_PATH_ISO3166, "r");
18419872Swollman	if (!fp)
18519872Swollman		err(1, _PATH_ISO3166);
18619872Swollman	lineno = 0;
18719872Swollman
18819872Swollman	while ((s = fgetln(fp, &len)) != 0) {
18919872Swollman		lineno++;
19019872Swollman		if (s[len - 1] != '\n')
19119872Swollman			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
19219872Swollman		s[len - 1] = '\0';
19330999Sjoerg		if (s[0] == '#' || strspn(s, " \t") == len - 1)
19419872Swollman			continue;
19519872Swollman
19619872Swollman		/* Isolate the two-letter code. */
19719872Swollman		t = strsep(&s, "\t");
19819872Swollman		if (t == 0 || strlen(t) != 2)
19919872Swollman			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
20019872Swollman		if (t[0] < 'A' || t[0] > 'Z' || t[1] < 'A' || t[1] > 'Z')
20119872Swollman			errx(1, _PATH_ISO3166 ":%d: invalid code `%s'",
20219872Swollman			     lineno, t);
20319872Swollman
20419872Swollman		/* Now skip past the three-letter and numeric codes. */
20519872Swollman		name = strsep(&s, "\t"); /* 3-let */
20619872Swollman		if (name == 0 || strlen(name) != 3)
20719872Swollman			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
20819872Swollman		name = strsep(&s, "\t"); /* numeric */
20919872Swollman		if (name == 0 || strlen(name) != 3)
21019872Swollman			errx(1, _PATH_ISO3166 ":%d: invalid format", lineno);
21119872Swollman
21219872Swollman		name = s;
21319872Swollman
21419872Swollman		cp = &countries[CODE2INT(t)];
21519872Swollman		if (cp->name)
21619872Swollman			errx(1, _PATH_ISO3166
21719872Swollman			     ":%d: country code `%s' multiply defined: %s",
21819872Swollman			     lineno, t, cp->name);
21919872Swollman		cp->name = strdup(name);
22056487Scharnier		if (cp->name == NULL)
22156487Scharnier			errx(1, "malloc failed");
22219872Swollman		cp->tlc = strdup(t);
22356487Scharnier		if (cp->tlc == NULL)
22456487Scharnier			errx(1, "malloc failed");
22519872Swollman	}
22619872Swollman
22719872Swollman	fclose(fp);
22819872Swollman}
22919872Swollman
23019872Swollmanstatic void
23119872Swollmanadd_zone_to_country(int lineno, const char *tlc, const char *descr,
23219872Swollman		    const char *file, struct continent *cont)
23319872Swollman{
23419872Swollman	struct zone *zp;
23519872Swollman	struct country *cp;
23619872Swollman
23719872Swollman	if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
23819872Swollman		errx(1, _PATH_ZONETAB ":%d: country code `%s' invalid",
23919872Swollman		     lineno, tlc);
24019872Swollman
24119872Swollman	cp = &countries[CODE2INT(tlc)];
24219872Swollman	if (cp->name == 0)
24319872Swollman		errx(1, _PATH_ZONETAB ":%d: country code `%s' unknown",
24419872Swollman		     lineno, tlc);
24519872Swollman
24619872Swollman	if (descr) {
24719872Swollman		if (cp->nzones < 0)
24819872Swollman			errx(1, _PATH_ZONETAB
24919872Swollman			     ":%d: conflicting zone definition", lineno);
25019872Swollman
25119872Swollman		zp = malloc(sizeof *zp);
25219872Swollman		if (zp == 0)
25356487Scharnier			errx(1, "malloc(%lu)", (unsigned long)sizeof *zp);
25419872Swollman
25519872Swollman		if (cp->nzones == 0)
25619872Swollman			TAILQ_INIT(&cp->zones);
25719872Swollman
25819872Swollman		zp->descr = strdup(descr);
25956487Scharnier		if (zp->descr == NULL)
26056487Scharnier			errx(1, "malloc failed");
26119872Swollman		zp->filename = strdup(file);
26256487Scharnier		if (zp->filename == NULL)
26356487Scharnier			errx(1, "malloc failed");
26419872Swollman		zp->continent = cont;
26519872Swollman		TAILQ_INSERT_TAIL(&cp->zones, zp, link);
26619872Swollman		cp->nzones++;
26719872Swollman	} else {
26819872Swollman		if (cp->nzones > 0)
26919872Swollman			errx(1, _PATH_ZONETAB
27019872Swollman			     ":%d: zone must have description", lineno);
27119872Swollman		if (cp->nzones < 0)
27219872Swollman			errx(1, _PATH_ZONETAB
27319872Swollman			     ":%d: zone multiply defined", lineno);
27419872Swollman		cp->nzones = -1;
27519872Swollman		cp->filename = strdup(file);
27656487Scharnier		if (cp->filename == NULL)
27756487Scharnier			errx(1, "malloc failed");
27819872Swollman		cp->continent = cont;
27919872Swollman	}
28019872Swollman}
28119872Swollman
28219872Swollman/*
28319872Swollman * This comparison function intentionally sorts all of the null-named
28419872Swollman * ``countries''---i.e., the codes that don't correspond to a real
28519872Swollman * country---to the end.  Everything else is lexical by country name.
28619872Swollman */
28719872Swollmanstatic int
28819872Swollmancompare_countries(const void *xa, const void *xb)
28919872Swollman{
29019872Swollman	const struct country *a = xa, *b = xb;
29119872Swollman
29219872Swollman	if (a->name == 0 && b->name == 0)
29319872Swollman		return 0;
29419872Swollman	if (a->name == 0 && b->name != 0)
29519872Swollman		return 1;
29619872Swollman	if (b->name == 0)
29719872Swollman		return -1;
29819872Swollman
29919872Swollman	return strcmp(a->name, b->name);
30019872Swollman}
30119872Swollman
30219872Swollman/*
30319872Swollman * This must be done AFTER all zone descriptions are read, since it breaks
30419872Swollman * CODE2INT().
30519872Swollman */
30619872Swollmanstatic void
30719872Swollmansort_countries(void)
30819872Swollman{
30922181Sjhay	qsort(countries, NCOUNTRIES, sizeof countries[0], compare_countries);
31019872Swollman}
31119872Swollman
31219872Swollmanstatic void
31319872Swollmanread_zones(void)
31419872Swollman{
31519872Swollman	FILE *fp;
31619872Swollman	char *line;
31719872Swollman	size_t len;
31819872Swollman	int lineno;
31919872Swollman	char *tlc, *coord, *file, *descr, *p;
32019872Swollman	char contbuf[16];
32119872Swollman	struct continent *cont;
32219872Swollman
32319872Swollman	fp = fopen(_PATH_ZONETAB, "r");
32419872Swollman	if (!fp)
32519872Swollman		err(1, _PATH_ZONETAB);
32619872Swollman	lineno = 0;
32719872Swollman
32819872Swollman	while ((line = fgetln(fp, &len)) != 0) {
32919872Swollman		lineno++;
33019872Swollman		if (line[len - 1] != '\n')
33119872Swollman			errx(1, _PATH_ZONETAB ":%d: invalid format", lineno);
33219872Swollman		line[len - 1] = '\0';
33319872Swollman		if (line[0] == '#')
33419872Swollman			continue;
33519872Swollman
33619872Swollman		tlc = strsep(&line, "\t");
33719872Swollman		if (strlen(tlc) != 2)
33819872Swollman			errx(1, _PATH_ZONETAB ":%d: invalid country code `%s'",
33919872Swollman			     lineno, tlc);
34019872Swollman		coord = strsep(&line, "\t");
34119872Swollman		file = strsep(&line, "\t");
34219872Swollman		p = strchr(file, '/');
34319872Swollman		if (p == 0)
34419872Swollman			errx(1, _PATH_ZONETAB ":%d: invalid zone name `%s'",
34519872Swollman			     lineno, file);
34619872Swollman		contbuf[0] = '\0';
34719872Swollman		strncat(contbuf, file, p - file);
34819872Swollman		cont = find_continent(contbuf);
34919872Swollman		if (!cont)
35019872Swollman			errx(1, _PATH_ZONETAB ":%d: invalid region `%s'",
35119872Swollman			     lineno, contbuf);
35219872Swollman
35319872Swollman		descr = (line && *line) ? line : 0;
35419872Swollman
35519872Swollman		add_zone_to_country(lineno, tlc, descr, file, cont);
35619872Swollman	}
35719872Swollman	fclose(fp);
35819872Swollman}
35919872Swollman
36019872Swollmanstatic void
36119872Swollmanmake_menus(void)
36219872Swollman{
36319872Swollman	struct country *cp;
36419872Swollman	struct zone *zp, *zp2;
36519872Swollman	struct continent *cont;
36619872Swollman	dialogMenuItem *dmi;
36719872Swollman	int i;
36819872Swollman
36919872Swollman	/*
37019872Swollman	 * First, count up all the countries in each continent/ocean.
37119872Swollman	 * Be careful to count those countries which have multiple zones
37219872Swollman	 * only once for each.  NB: some countries are in multiple
37319872Swollman	 * continents/oceans.
37419872Swollman	 */
37519872Swollman	for (cp = countries; cp->name; cp++) {
37619872Swollman		if (cp->nzones == 0)
37719872Swollman			continue;
37819872Swollman		if (cp->nzones < 0) {
37919872Swollman			cp->continent->nitems++;
38019872Swollman		} else {
38119872Swollman			for (zp = cp->zones.tqh_first; zp;
38219872Swollman			     zp = zp->link.tqe_next) {
38319872Swollman				cont = zp->continent;
38419872Swollman				for (zp2 = cp->zones.tqh_first;
38519872Swollman				     zp2->continent != cont;
38619872Swollman				     zp2 = zp2->link.tqe_next)
38719872Swollman					;
38819872Swollman				if (zp2 == zp)
38919872Swollman					zp->continent->nitems++;
39019872Swollman			}
39119872Swollman		}
39219872Swollman	}
39319872Swollman
39419872Swollman	/*
39519872Swollman	 * Now allocate memory for the country menus.  We set
39619872Swollman	 * nitems back to zero so that we can use it for counting
39719872Swollman	 * again when we actually build the menus.
39819872Swollman	 */
39919872Swollman	for (i = 0; i < NCONTINENTS; i++) {
40019872Swollman		continent_names[i].continent->menu =
40119872Swollman			malloc(sizeof(dialogMenuItem) *
40219872Swollman			       continent_names[i].continent->nitems);
40319872Swollman		if (continent_names[i].continent->menu == 0)
40456487Scharnier			errx(1, "malloc for continent menu");
40519872Swollman		continent_names[i].continent->nitems = 0;
40619872Swollman	}
40719872Swollman
40819872Swollman	/*
40919872Swollman	 * Now that memory is allocated, create the menu items for
41019872Swollman	 * each continent.  For multiple-zone countries, also create
41119872Swollman	 * the country's zone submenu.
41219872Swollman	 */
41319872Swollman	for (cp = countries; cp->name; cp++) {
41419872Swollman		if (cp->nzones == 0)
41519872Swollman			continue;
41619872Swollman		if (cp->nzones < 0) {
41719872Swollman			dmi = &cp->continent->menu[cp->continent->nitems];
41819872Swollman			memset(dmi, 0, sizeof *dmi);
41919872Swollman			asprintf(&dmi->prompt, "%d",
42019872Swollman				 ++cp->continent->nitems);
42119872Swollman			dmi->title = cp->name;
42219872Swollman			dmi->checked = 0;
42319872Swollman			dmi->fire = set_zone_whole_country;
42419872Swollman			dmi->selected = 0;
42519872Swollman			dmi->data = cp;
42619872Swollman		} else {
42719872Swollman			cp->submenu = malloc(cp->nzones * sizeof *dmi);
42819872Swollman			if (cp->submenu == 0)
42956487Scharnier				errx(1, "malloc for submenu");
43019872Swollman			cp->nzones = 0;
43119872Swollman			for (zp = cp->zones.tqh_first; zp;
43219872Swollman			     zp = zp->link.tqe_next) {
43319872Swollman				cont = zp->continent;
43419872Swollman				dmi = &cp->submenu[cp->nzones];
43519872Swollman				memset(dmi, 0, sizeof *dmi);
43619872Swollman				asprintf(&dmi->prompt, "%d",
43719872Swollman					 ++cp->nzones);
43819872Swollman				dmi->title = zp->descr;
43919872Swollman				dmi->checked = 0;
44019872Swollman				dmi->fire = set_zone_multi;
44119872Swollman				dmi->selected = 0;
44219872Swollman				dmi->data = zp;
44319872Swollman
44419872Swollman				for (zp2 = cp->zones.tqh_first;
44519872Swollman				     zp2->continent != cont;
44619872Swollman				     zp2 = zp2->link.tqe_next)
44719872Swollman					;
44819872Swollman				if (zp2 != zp)
44919872Swollman					continue;
45019872Swollman
45119872Swollman				dmi = &cont->menu[cont->nitems];
45219872Swollman				memset(dmi, 0, sizeof *dmi);
45319872Swollman				asprintf(&dmi->prompt, "%d", ++cont->nitems);
45419872Swollman				dmi->title = cp->name;
45519872Swollman				dmi->checked = 0;
45619872Swollman				dmi->fire = set_zone_menu;
45719872Swollman				dmi->selected = 0;
45819872Swollman				dmi->data = cp;
45919872Swollman			}
46019872Swollman		}
46119872Swollman	}
46219872Swollman}
46319872Swollman
46419872Swollmanstatic int
46519872Swollmanset_zone_menu(dialogMenuItem *dmi)
46619872Swollman{
46719872Swollman	int rv;
46819872Swollman	char buf[256];
46919872Swollman	struct country *cp = dmi->data;
47019872Swollman	int menulen;
47119872Swollman
47219872Swollman	snprintf(buf, sizeof buf, "%s Time Zones", cp->name);
47319872Swollman	menulen = cp->nzones < 16 ? cp->nzones : 16;
47419872Swollman	rv = dialog_menu(buf, "Select a zone which observes the same time as "
47519872Swollman			 "your locality.", -1, -1, menulen, -cp->nzones,
47619872Swollman			 cp->submenu, 0, 0, 0);
47719872Swollman	if (rv != 0)
47819872Swollman		return DITEM_RECREATE;
47919872Swollman	return DITEM_LEAVE_MENU;
48019872Swollman}
48119872Swollman
48219872Swollmanstatic int
48319872Swollmaninstall_zone_file(const char *filename)
48419872Swollman{
48519872Swollman	struct stat sb;
48619872Swollman	int fd1, fd2;
48719872Swollman	int copymode;
48819872Swollman	char *msg;
48919872Swollman	ssize_t len;
49019872Swollman	char buf[1024];
49119872Swollman
49219872Swollman	if (lstat(_PATH_LOCALTIME, &sb) < 0)
49319872Swollman		/* Nothing there yet... */
49419872Swollman		copymode = 1;
49519872Swollman	else if(S_ISLNK(sb.st_mode))
49619872Swollman		copymode = 0;
49719872Swollman	else
49819872Swollman		copymode = 1;
49919872Swollman
50021915Sjkh#ifdef VERBOSE
50119872Swollman	if (copymode)
50219872Swollman		asprintf(&msg, "Copying %s to " _PATH_LOCALTIME, filename);
50319872Swollman	else
50419872Swollman		asprintf(&msg, "Creating symbolic link " _PATH_LOCALTIME
50519872Swollman			 " to %s", filename);
50619872Swollman
50719872Swollman	dialog_notify(msg);
50819872Swollman	free(msg);
50921915Sjkh#endif
51019872Swollman
51119872Swollman	if (reallydoit) {
51219872Swollman		if (copymode) {
51319872Swollman			fd1 = open(filename, O_RDONLY, 0);
51419872Swollman			if (fd1 < 0) {
51519872Swollman				asprintf(&msg, "Could not open %s: %s",
51619872Swollman					 filename, strerror(errno));
51719872Swollman				dialog_mesgbox("Error", msg, 8, 72);
51819872Swollman				free(msg);
51919872Swollman				return DITEM_FAILURE | DITEM_RECREATE;
52019872Swollman			}
52119872Swollman
52219872Swollman			unlink(_PATH_LOCALTIME);
52319872Swollman			fd2 = open(_PATH_LOCALTIME,
52449435Sru				   O_CREAT|O_EXCL|O_WRONLY,
52549435Sru				   S_IRUSR|S_IRGRP|S_IROTH);
52619872Swollman			if (fd2 < 0) {
52719872Swollman				asprintf(&msg, "Could not open "
52819872Swollman					 _PATH_LOCALTIME ": %s",
52919872Swollman					 strerror(errno));
53019872Swollman				dialog_mesgbox("Error", msg, 8, 72);
53119872Swollman				free(msg);
53219872Swollman				return DITEM_FAILURE | DITEM_RECREATE;
53319872Swollman			}
53419872Swollman
53519872Swollman			while ((len = read(fd1, buf, sizeof buf)) > 0)
53619872Swollman				len = write(fd2, buf, len);
53719872Swollman
53819872Swollman			if (len == -1) {
53919872Swollman				asprintf(&msg, "Error copying %s to "
54019872Swollman					 _PATH_LOCALTIME ": %s",
54149435Sru					 filename, strerror(errno));
54219872Swollman				dialog_mesgbox("Error", msg, 8, 72);
54319872Swollman				free(msg);
54419872Swollman				/* Better to leave none than a corrupt one. */
54519872Swollman				unlink(_PATH_LOCALTIME);
54619872Swollman				return DITEM_FAILURE | DITEM_RECREATE;
54719872Swollman			}
54819872Swollman			close(fd1);
54919872Swollman			close(fd2);
55019872Swollman		} else {
55119872Swollman			if (access(filename, R_OK) != 0) {
55219872Swollman				asprintf(&msg, "Cannot access %s: %s",
55319872Swollman					 filename, strerror(errno));
55419872Swollman				dialog_mesgbox("Error", msg, 8, 72);
55519872Swollman				free(msg);
55619872Swollman				return DITEM_FAILURE | DITEM_RECREATE;
55719872Swollman			}
55819872Swollman			unlink(_PATH_LOCALTIME);
55919872Swollman			if (symlink(filename, _PATH_LOCALTIME) < 0) {
56019872Swollman				asprintf(&msg, "Cannot create symbolic link "
56119872Swollman					 _PATH_LOCALTIME " to %s: %s",
56219872Swollman					 filename, strerror(errno));
56319872Swollman				dialog_mesgbox("Error", msg, 8, 72);
56419872Swollman				free(msg);
56519872Swollman				return DITEM_FAILURE | DITEM_RECREATE;
56619872Swollman			}
56719872Swollman		}
56819872Swollman	}
56919872Swollman
57021915Sjkh#ifdef VERBOSE
57119872Swollman	if (copymode)
57219872Swollman		asprintf(&msg, "Copied timezone file from %s to "
57319872Swollman			 _PATH_LOCALTIME, filename);
57419872Swollman	else
57519872Swollman		asprintf(&msg, "Created symbolic link from " _PATH_LOCALTIME
57619872Swollman			 " to %s", filename);
57719872Swollman
57819872Swollman	dialog_mesgbox("Done", msg, 8, 72);
57919872Swollman	free(msg);
58021915Sjkh#endif
58119872Swollman	return DITEM_LEAVE_MENU;
58219872Swollman}
58319872Swollman
58419872Swollmanstatic int
58519872Swollmanconfirm_zone(const char *filename)
58619872Swollman{
58719872Swollman	char *msg;
58819872Swollman	struct tm *tm;
58919872Swollman	time_t t = time(0);
59019872Swollman	int rv;
59119872Swollman
59219872Swollman	setenv("TZ", filename, 1);
59319872Swollman	tzset();
59419872Swollman	tm = localtime(&t);
59519872Swollman
59619872Swollman	asprintf(&msg, "Does the abbreviation `%s' look reasonable?",
59719872Swollman		 tm->tm_zone);
59819872Swollman	rv = !dialog_yesno("Confirmation", msg, 4, 72);
59919872Swollman	free(msg);
60019872Swollman	return rv;
60119872Swollman}
60219872Swollman
60319872Swollmanstatic int
60419872Swollmanset_zone_multi(dialogMenuItem *dmi)
60519872Swollman{
60619872Swollman	char *fn;
60719872Swollman	struct zone *zp = dmi->data;
60819872Swollman	int rv;
60919872Swollman
61019872Swollman	if (!confirm_zone(zp->filename))
61119872Swollman		return DITEM_FAILURE | DITEM_RECREATE;
61219872Swollman
61319872Swollman	asprintf(&fn, "%s/%s", _PATH_ZONEINFO, zp->filename);
61419872Swollman	rv = install_zone_file(fn);
61519872Swollman	free(fn);
61619872Swollman	return rv;
61719872Swollman}
61819872Swollman
61919872Swollmanstatic int
62019872Swollmanset_zone_whole_country(dialogMenuItem *dmi)
62119872Swollman{
62219872Swollman	char *fn;
62319872Swollman	struct country *cp = dmi->data;
62419872Swollman	int rv;
62519872Swollman
62619872Swollman	if (!confirm_zone(cp->filename))
62719872Swollman		return DITEM_FAILURE | DITEM_RECREATE;
62819872Swollman
62919872Swollman	asprintf(&fn, "%s/%s", _PATH_ZONEINFO, cp->filename);
63019872Swollman	rv = install_zone_file(fn);
63119872Swollman	free(fn);
63219872Swollman	return rv;
63319872Swollman}
63419872Swollman
63530763Scharnierstatic void
63630763Scharnierusage()
63730763Scharnier{
63830763Scharnier	fprintf(stderr, "usage: tzsetup [-n]\n");
63930763Scharnier	exit(1);
64030763Scharnier}
64130763Scharnier
64219872Swollmanint
64319872Swollmanmain(int argc, char **argv)
64419872Swollman{
64541852Speter	int c, fd;
64619872Swollman
64719872Swollman	while ((c = getopt(argc, argv, "n")) != -1) {
64819872Swollman		switch(c) {
64919872Swollman		case 'n':
65019872Swollman			reallydoit = 0;
65119872Swollman			break;
65219872Swollman
65319872Swollman		default:
65430763Scharnier			usage();
65519872Swollman		}
65619872Swollman	}
65719872Swollman
65843544Swollman	if (argc - optind > 1)
65930763Scharnier		usage();
66019872Swollman
66149435Sru	/* Override the user-supplied umask. */
66249435Sru	(void)umask(S_IWGRP|S_IWOTH);
66349435Sru
66419872Swollman	read_iso3166_table();
66519872Swollman	read_zones();
66619872Swollman	sort_countries();
66719872Swollman	make_menus();
66819872Swollman
66919872Swollman	init_dialog();
67070092Sjkh	if (!dialog_noyes("Select local or UTC (Greenwich Mean Time) clock",
67122815Sjkh			  "Is this machine's CMOS clock set to UTC?  If it is set to local time,\n"
67248121Smharo			  "or you don't know, please choose NO here!", 7, 72)) {
67330763Scharnier		if (reallydoit)
67441852Speter			unlink(_PATH_WALL_CMOS_CLOCK);
67532394Ssteve	} else {
67641852Speter		if (reallydoit) {
67741852Speter			fd = open(_PATH_WALL_CMOS_CLOCK,
67849435Sru				  O_WRONLY|O_CREAT|O_TRUNC,
67949435Sru				  S_IRUSR|S_IRGRP|S_IROTH);
68041852Speter			if (fd < 0)
68141852Speter				err(1, "create %s", _PATH_WALL_CMOS_CLOCK);
68241852Speter			close(fd);
68341852Speter		}
68432394Ssteve	}
68522815Sjkh	dialog_clear_norefresh();
68643544Swollman	if (optind == argc - 1) {
68743544Swollman		char *msg;
68843544Swollman		asprintf(&msg, "\nUse the default `%s' zone?", argv[optind]);
68943544Swollman		if (!dialog_yesno("Default timezone provided", msg, 7, 72)) {
69043544Swollman			install_zone_file(argv[optind]);
69143544Swollman			dialog_clear();
69243544Swollman			end_dialog();
69343544Swollman			return 0;
69443544Swollman		}
69543544Swollman		free(msg);
69643544Swollman		dialog_clear_norefresh();
69743544Swollman	}
69819872Swollman	dialog_menu("Time Zone Selector", "Select a region", -1, -1,
69919887Sjoerg		    NCONTINENTS, -NCONTINENTS, continents, 0, NULL, NULL);
70043544Swollman
70122815Sjkh	dialog_clear();
70219872Swollman	end_dialog();
70319872Swollman	return 0;
70419872Swollman}
70519872Swollman
706