1/*
2 * Copyright 1996 Massachusetts Institute of Technology
3 *
4 * Permission to use, copy, modify, and distribute this software and
5 * its documentation for any purpose and without fee is hereby
6 * granted, provided that both the above copyright notice and this
7 * permission notice appear in all copies, that both the above
8 * copyright notice and this permission notice appear in all
9 * supporting documentation, and that the name of M.I.T. not be used
10 * in advertising or publicity pertaining to distribution of the
11 * software without specific, written prior permission.  M.I.T. makes
12 * no representations about the suitability of this software for any
13 * purpose.  It is provided "as is" without express or implied
14 * warranty.
15 *
16 * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
17 * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
18 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
19 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
20 * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
25 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
26 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
27 * SUCH DAMAGE.
28 */
29
30/*
31 * Second attempt at a `tzmenu' program, using the separate description
32 * files provided in newer tzdata releases.
33 */
34
35/*
36 * When making changes to parser code, run baseline target, check that there are
37 * no unintended changes and commit updated file.
38 */
39
40#include <sys/cdefs.h>
41#include <err.h>
42#include <errno.h>
43#include <stdio.h>
44#include <stdlib.h>
45#include <string.h>
46#include <time.h>
47#include <unistd.h>
48
49#include <sys/fcntl.h>
50#include <sys/param.h>
51#include <sys/queue.h>
52#include <sys/stat.h>
53#include <sys/sysctl.h>
54
55#ifdef HAVE_BSDDIALOG
56#include <bsddialog.h>
57#include <locale.h>
58#endif
59
60#define	_PATH_ZONETAB		"/usr/share/zoneinfo/zone1970.tab"
61#define	_PATH_ISO3166		"/usr/share/misc/iso3166"
62#define	_PATH_ZONEINFO		"/usr/share/zoneinfo"
63#define	_PATH_LOCALTIME		"/etc/localtime"
64#define	_PATH_DB		"/var/db/zoneinfo"
65#define	_PATH_WALL_CMOS_CLOCK	"/etc/wall_cmos_clock"
66
67#ifdef PATH_MAX
68#define	SILLY_BUFFER_SIZE	(2 * PATH_MAX)
69#else
70#warning "Somebody needs to fix this to dynamically size this buffer."
71#define	SILLY_BUFFER_SIZE	2048
72#endif
73
74/* special return codes for `fire' actions */
75#define DITEM_FAILURE           1
76
77/* flags - returned in upper 16 bits of return status */
78#define DITEM_LEAVE_MENU        (1 << 16)
79#define DITEM_RECREATE          (1 << 18)
80
81static char	path_zonetab[MAXPATHLEN], path_iso3166[MAXPATHLEN],
82		path_zoneinfo[MAXPATHLEN], path_localtime[MAXPATHLEN],
83		path_db[MAXPATHLEN], path_wall_cmos_clock[MAXPATHLEN];
84
85static int reallydoit = 1;
86static int reinstall = 0;
87static char *chrootenv = NULL;
88
89static void	usage(void);
90static int	install_zoneinfo(const char *zoneinfo);
91static void	message_zoneinfo_file(const char *title, char *prompt);
92static int	install_zoneinfo_file(const char *zoneinfo_file);
93
94#ifdef HAVE_BSDDIALOG
95static struct bsddialog_conf conf;
96
97/* for use in describing more exotic behaviors */
98typedef struct dialogMenuItem {
99	char *prompt;
100	char *title;
101	int (*fire)(struct dialogMenuItem *self);
102	void *data;
103} dialogMenuItem;
104
105static int
106xdialog_menu(char *title, char *cprompt, int item_no, dialogMenuItem *ditems)
107{
108	int i, result, menurows, choice = 0;
109	struct bsddialog_menuitem *listitems;
110
111	/* initialize list items */
112	listitems = calloc(item_no + 1, sizeof(struct bsddialog_menuitem));
113	if (listitems == NULL)
114		errx(1, "Failed to allocate memory in xdialog_menu");
115	for (i = 0; i < item_no; i++) {
116		listitems[i].prefix = "";
117		listitems[i].depth = 0;
118		listitems[i].bottomdesc = "";
119		listitems[i].on = false;
120		listitems[i].name = ditems[i].prompt;
121		listitems[i].desc = ditems[i].title;
122	}
123
124again:
125	conf.title = title;
126	menurows = item_no < 16 ? item_no : 16;
127	result = bsddialog_menu(&conf, cprompt, BSDDIALOG_AUTOSIZE,
128	    BSDDIALOG_AUTOSIZE, menurows, item_no, listitems, &choice);
129	switch (result) {
130	case BSDDIALOG_ESC:
131		result = -1;
132		break;
133	case BSDDIALOG_OK:
134		if (ditems[choice].fire != NULL) {
135			int status;
136
137			status = ditems[choice].fire(ditems + choice);
138			if (status & DITEM_RECREATE) {
139				goto again;
140			}
141		}
142		result = 0;
143		break;
144	case BSDDIALOG_CANCEL:
145	default:
146		result = 1;
147		break;
148	}
149
150	free(listitems);
151	return (result);
152}
153
154static int usedialog = 1;
155
156static int	confirm_zone(const char *filename);
157static int	continent_country_menu(dialogMenuItem *);
158static int	set_zone(dialogMenuItem *);
159static int	set_zone_menu(dialogMenuItem *);
160static int	set_zone_utc(void);
161
162struct continent {
163	dialogMenuItem *menu;
164	int		nitems;
165};
166
167static struct continent	africa, america, antarctica, arctic, asia, atlantic;
168static struct continent	australia, europe, indian, pacific, utc;
169
170static struct continent_names {
171	const char	*name;
172	struct continent *continent;
173} continent_names[] = {
174	{ "UTC",	&utc },
175	{ "Africa",	&africa },
176	{ "America",	&america },
177	{ "Antarctica",	&antarctica },
178	{ "Arctic",	&arctic },
179	{ "Asia",	&asia },
180	{ "Atlantic",	&atlantic },
181	{ "Australia",	&australia },
182	{ "Europe",	&europe },
183	{ "Indian",	&indian },
184	{ "Pacific",	&pacific },
185};
186
187static struct continent_items {
188	char		prompt[3];
189	char		title[30];
190} continent_items[] = {
191	{ "0",	"UTC" },
192	{ "1",	"Africa" },
193	{ "2",	"America -- North and South" },
194	{ "3",	"Antarctica" },
195	{ "4",	"Arctic Ocean" },
196	{ "5",	"Asia" },
197	{ "6",	"Atlantic Ocean" },
198	{ "7",	"Australia" },
199	{ "8",	"Europe" },
200	{ "9",	"Indian Ocean" },
201	{ "10",	"Pacific Ocean" },
202};
203
204#define	NCONTINENTS	\
205    (int)((sizeof(continent_items)) / (sizeof(continent_items[0])))
206static dialogMenuItem continents[NCONTINENTS];
207
208#define	OCEANP(x)	((x) == 4 || (x) == 6 || (x) == 9 || (x) == 10)
209
210static int
211continent_country_menu(dialogMenuItem *continent)
212{
213	char		title[64], prompt[64];
214	struct continent *contp = continent->data;
215	int		isocean = OCEANP(continent - continents);
216	int		rv;
217
218	if (strcmp(continent->title, "UTC") == 0)
219		return (set_zone_utc());
220
221	/* It's amazing how much good grammar really matters... */
222	if (!isocean) {
223		snprintf(title, sizeof(title), "Countries in %s",
224		    continent->title);
225		snprintf(prompt, sizeof(prompt), "Select a country or region");
226	} else {
227		snprintf(title, sizeof(title), "Islands and groups in the %s",
228		    continent->title);
229		snprintf(prompt, sizeof(prompt), "Select an island or group");
230	}
231
232	rv = xdialog_menu(title, prompt, contp->nitems, contp->menu);
233	return (rv == 0 ? DITEM_LEAVE_MENU : DITEM_RECREATE);
234}
235
236static struct continent *
237find_continent(int lineno, const char *name)
238{
239	char		*cname, *cp;
240	int		i;
241
242	/*
243	 * Both normal (the ones in zone filename, e.g. Europe/Andorra) and
244	 * override (e.g. Atlantic/) entries should contain '/'.
245	 */
246	cp = strdup(name);
247	if (cp == NULL)
248		err(1, "strdup");
249	cname = strsep(&cp, "/");
250	if (cp == NULL)
251		errx(1, "%s:%d: invalid entry `%s'", path_zonetab, lineno,
252		    cname);
253
254	for (i = 0; i < NCONTINENTS; i++)
255		if (strcmp(cname, continent_names[i].name) == 0) {
256			free(cname);
257			return (continent_names[i].continent);
258		}
259
260	errx(1, "%s:%d: continent `%s' unknown", path_zonetab, lineno, cname);
261}
262
263static const char *
264find_continent_name(struct continent *cont)
265{
266	int		i;
267
268	for (i = 0; i < NCONTINENTS; i++)
269		if (cont == continent_names[i].continent)
270			return (continent_names[i].name);
271	return ("Unknown");
272}
273
274struct country {
275	char		*name;
276	char		*tlc;
277	int		nzones;
278	struct continent *override;	/* continent override */
279	struct continent *alternate;	/* extra continent */
280	TAILQ_HEAD(, zone) zones;
281	dialogMenuItem	*submenu;
282};
283
284struct zone {
285	TAILQ_ENTRY(zone) link;
286	char		*descr;
287	char		*filename;
288	struct continent *continent;
289};
290
291/*
292 * This is the easiest organization... we use ISO 3166 country codes,
293 * of the two-letter variety, so we just size this array to suit.
294 * Beats worrying about dynamic allocation.
295 */
296#define	NCOUNTRIES	(26 * 26)
297static struct country countries[NCOUNTRIES];
298
299#define	CODE2INT(s)	((s[0] - 'A') * 26 + (s[1] - 'A'))
300
301/*
302 * Read the ISO 3166 country code database in _PATH_ISO3166
303 * (/usr/share/misc/iso3166).  On error, exit via err(3).
304 */
305static void
306read_iso3166_table(void)
307{
308	FILE		*fp;
309	struct country	*cp;
310	size_t		len;
311	char		*s, *t, *name;
312	int		lineno;
313
314	fp = fopen(path_iso3166, "r");
315	if (!fp)
316		err(1, "%s", path_iso3166);
317	lineno = 0;
318
319	while ((s = fgetln(fp, &len)) != NULL) {
320		lineno++;
321		if (s[len - 1] != '\n')
322			errx(1, "%s:%d: invalid format", path_iso3166, lineno);
323		s[len - 1] = '\0';
324		if (s[0] == '#' || strspn(s, " \t") == len - 1)
325			continue;
326
327		/* Isolate the two-letter code. */
328		t = strsep(&s, "\t");
329		if (t == NULL || strlen(t) != 2)
330			errx(1, "%s:%d: invalid format", path_iso3166, lineno);
331		if (t[0] < 'A' || t[0] > 'Z' || t[1] < 'A' || t[1] > 'Z')
332			errx(1, "%s:%d: invalid code `%s'", path_iso3166,
333			    lineno, t);
334
335		/* Now skip past the three-letter and numeric codes. */
336		name = strsep(&s, "\t");	/* 3-let */
337		if (name == NULL || strlen(name) != 3)
338			errx(1, "%s:%d: invalid format", path_iso3166, lineno);
339		name = strsep(&s, "\t");	/* numeric */
340		if (name == NULL || strlen(name) != 3)
341			errx(1, "%s:%d: invalid format", path_iso3166, lineno);
342
343		name = s;
344
345		cp = &countries[CODE2INT(t)];
346		if (cp->name)
347			errx(1, "%s:%d: country code `%s' multiply defined: %s",
348			    path_iso3166, lineno, t, cp->name);
349		cp->name = strdup(name);
350		if (cp->name == NULL)
351			errx(1, "malloc failed");
352		cp->tlc = strdup(t);
353		if (cp->tlc == NULL)
354			errx(1, "malloc failed");
355	}
356
357	fclose(fp);
358}
359
360static struct country *
361find_country(int lineno, const char *tlc)
362{
363	struct country	*cp;
364
365	if (strlen(tlc) != 2 ||
366	    tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
367		errx(1, "%s:%d: country code `%s' invalid", path_zonetab,
368		    lineno, tlc);
369
370	cp = &countries[CODE2INT(tlc)];
371	if (cp->name == NULL)
372		errx(1, "%s:%d: country code `%s' unknown", path_zonetab,
373		    lineno, tlc);
374
375	return (cp);
376}
377
378static void
379add_cont_to_country(struct country *cp, struct continent *cont)
380{
381	struct zone	*zp;
382
383	TAILQ_FOREACH(zp, &cp->zones, link) {
384		if (zp->continent == cont)
385			return;
386	}
387	cp->alternate = cont;
388}
389
390static void
391add_zone_to_country(int lineno, struct country *cp, const char *descr,
392    const char *file, struct continent *cont)
393{
394	struct zone	*zp;
395
396	zp = malloc(sizeof(*zp));
397	if (zp == NULL)
398		errx(1, "malloc(%zu)", sizeof(*zp));
399
400	if (cp->nzones == 0)
401		TAILQ_INIT(&cp->zones);
402
403	if (descr != NULL) {
404		zp->descr = strdup(descr);
405		if (zp->descr == NULL)
406			errx(1, "malloc failed");
407	} else {
408		zp->descr = NULL;
409	}
410	zp->filename = strdup(file);
411	if (zp->filename == NULL)
412		errx(1, "malloc failed");
413	zp->continent = cp->override != NULL ? cp->override : cont;
414	TAILQ_INSERT_TAIL(&cp->zones, zp, link);
415	cp->nzones++;
416}
417
418/*
419 * This comparison function intentionally sorts all of the null-named
420 * ``countries''---i.e., the codes that don't correspond to a real
421 * country---to the end.  Everything else is lexical by country name.
422 */
423static int
424compare_countries(const void *xa, const void *xb)
425{
426	const struct country *a = xa, *b = xb;
427
428	if (a->name == 0 && b->name == 0)
429		return (0);
430	if (a->name == 0 && b->name != 0)
431		return (1);
432	if (b->name == 0)
433		return (-1);
434
435	return (strcmp(a->name, b->name));
436}
437
438/*
439 * This must be done AFTER all zone descriptions are read, since it breaks
440 * CODE2INT().
441 */
442static void
443sort_countries(void)
444{
445
446	qsort(countries, NCOUNTRIES, sizeof(countries[0]), compare_countries);
447}
448
449static void
450read_zones(void)
451{
452	FILE		*fp;
453	struct continent *cont;
454	struct country	*cp;
455	size_t		len;
456	char		*line, *country_list, *tlc, *file, *descr;
457	char		*p, *q;
458	int		lineno;
459	int		pass = 1;
460
461	fp = fopen(path_zonetab, "r");
462	if (!fp)
463		err(1, "%s", path_zonetab);
464
465again:
466	lineno = 0;
467	while ((line = fgetln(fp, &len)) != NULL) {
468		lineno++;
469		if (line[len - 1] != '\n')
470			errx(1, "%s:%d: invalid format", path_zonetab, lineno);
471		line[len - 1] = '\0';
472
473		switch (pass)
474		{
475		case 1:
476			/*
477			 * First pass: collect overrides, only looking for
478			 * single continent ones for the moment.
479			 *
480			 * zone1970.tab introduced continent overrides in the
481			 * following format:
482			 *
483			 *   #@TLC[,TLC...]<tab>CONTINENT/[,CONTINENT/...]
484			 */
485			if (strncmp(line, "#@", strlen("#@")) != 0)
486				continue;
487			line += 2;
488			country_list = strsep(&line, "\t");
489			/* Skip multi-continent overrides */
490			if (strchr(line, ',') != NULL)
491				continue;
492			cont = find_continent(lineno, line);
493			/* Parse and store overrides */
494			while (country_list != NULL) {
495				tlc = strsep(&country_list, ",");
496				cp = find_country(lineno, tlc);
497				cp->override = cont;
498			}
499			break;
500		case 2:
501			/* Second pass: parse actual data */
502			if (line[0] == '#')
503				continue;
504
505			country_list = strsep(&line, "\t");
506			/* coord = */ strsep(&line, "\t");	 /* Unused */
507			file = strsep(&line, "\t");
508			cont = find_continent(lineno, file);
509			descr = (line != NULL && *line != '\0') ? line : NULL;
510
511			while (country_list != NULL) {
512				tlc = strsep(&country_list, ",");
513				cp = find_country(lineno, tlc);
514				add_zone_to_country(lineno, cp, descr, file,
515				    cont);
516			}
517			break;
518		case 3:
519			/* Third pass: collect multi-continent overrides */
520			if (strncmp(line, "#@", strlen("#@")) != 0)
521				continue;
522			line += 2;
523			country_list = strsep(&line, "\t");
524			/* Skip single-continent overrides */
525			if (strchr(line, ',') == NULL)
526				continue;
527			while (line != NULL) {
528				cont = find_continent(lineno, line);
529				p = q = strdup(country_list);
530				if (p == NULL)
531					errx(1, "malloc failed");
532				while (q != NULL) {
533					tlc = strsep(&q, ",");
534					cp = find_country(lineno, tlc);
535					add_cont_to_country(cp, cont);
536				}
537				free(p);
538				strsep(&line, ",");
539			}
540			break;
541		}
542	}
543
544	if (pass++ < 3) {
545		errno = 0;
546		rewind(fp);
547		if (errno != 0)
548			err(1, "failed to rewind %s", path_zonetab);
549		goto again;
550	}
551	fclose(fp);
552}
553
554static void
555dump_zonetab(void)
556{
557	struct country	*cp;
558	struct zone	*zp;
559	const char *cont;
560
561	for (cp = countries; cp->name != NULL; cp++) {
562		printf("%s:%s\n", cp->tlc, cp->name);
563		TAILQ_FOREACH(zp, &cp->zones, link) {
564			cont = find_continent_name(zp->continent);
565			printf("  %s:%s\n", cont, zp->filename);
566		}
567	}
568}
569
570static void
571make_menus(void)
572{
573	struct country	*cp;
574	struct zone	*zp, *zp2;
575	struct continent *cont;
576	dialogMenuItem	*dmi;
577	int		i;
578
579	/*
580	 * First, count up all the countries in each continent/ocean.
581	 * Be careful to count those countries which have multiple zones
582	 * only once for each.  NB: some countries are in multiple
583	 * continents/oceans.
584	 */
585	for (cp = countries; cp->name; cp++) {
586		if (cp->nzones == 0)
587			continue;
588		TAILQ_FOREACH(zp, &cp->zones, link) {
589			cont = zp->continent;
590			for (zp2 = TAILQ_FIRST(&cp->zones);
591			    zp2->continent != cont;
592			    zp2 = TAILQ_NEXT(zp2, link))
593				;
594			if (zp2 == zp)
595				zp->continent->nitems++;
596		}
597
598		for (i = 0; i < NCONTINENTS; i++) {
599			if (cp->alternate == continent_names[i].continent) {
600				continent_names[i].continent->nitems++;
601			}
602		}
603	}
604
605	/*
606	 * Now allocate memory for the country menus and initialize
607	 * continent menus.  We set nitems back to zero so that we can
608	 * use it for counting again when we actually build the menus.
609	 */
610	memset(continents, 0, sizeof(continents));
611	for (i = 0; i < NCONTINENTS; i++) {
612		continent_names[i].continent->menu =
613		    malloc(sizeof(dialogMenuItem) *
614		    continent_names[i].continent->nitems);
615		if (continent_names[i].continent->menu == NULL)
616			errx(1, "malloc for continent menu");
617		continent_names[i].continent->nitems = 0;
618		continents[i].prompt = continent_items[i].prompt;
619		continents[i].title = continent_items[i].title;
620		continents[i].fire = continent_country_menu;
621		continents[i].data = continent_names[i].continent;
622	}
623
624	/*
625	 * Now that memory is allocated, create the menu items for
626	 * each continent.  For multiple-zone countries, also create
627	 * the country's zone submenu.
628	 */
629	for (cp = countries; cp->name; cp++) {
630		if (cp->nzones == 0)
631			continue;
632		cp->submenu = malloc(cp->nzones * sizeof(*dmi));
633		if (cp->submenu == 0)
634			errx(1, "malloc for submenu");
635		cp->nzones = 0;
636		TAILQ_FOREACH(zp, &cp->zones, link) {
637			cont = zp->continent;
638			dmi = &cp->submenu[cp->nzones];
639			memset(dmi, 0, sizeof(*dmi));
640			asprintf(&dmi->prompt, "%d", ++cp->nzones);
641			dmi->title = zp->descr;
642			dmi->fire = set_zone;
643			dmi->data = zp;
644
645			for (zp2 = TAILQ_FIRST(&cp->zones);
646			    zp2->continent != cont;
647			    zp2 = TAILQ_NEXT(zp2, link))
648				;
649			if (zp2 != zp)
650				continue;
651
652			dmi = &cont->menu[cont->nitems];
653			memset(dmi, 0, sizeof(*dmi));
654			asprintf(&dmi->prompt, "%d", ++cont->nitems);
655			dmi->title = cp->name;
656			dmi->fire = set_zone_menu;
657			dmi->data = cp;
658		}
659
660		if (cp->alternate != NULL) {
661			cont = cp->alternate;
662			dmi = &cont->menu[cont->nitems];
663			memset(dmi, 0, sizeof(*dmi));
664			asprintf(&dmi->prompt, "%d", ++cont->nitems);
665			dmi->title = cp->name;
666			dmi->fire = set_zone_menu;
667			dmi->data = cp;
668		}
669	}
670}
671
672static int
673set_zone_menu(dialogMenuItem *dmi)
674{
675	char		title[64], prompt[64];
676	struct country	*cp = dmi->data;
677	int		rv;
678
679	/* Short cut -- if there's only one zone, don't post a menu. */
680	if (cp->nzones == 1)
681		return (cp->submenu[0].fire(&cp->submenu[0]));
682
683	snprintf(title, sizeof(title), "%s Time Zones", cp->name);
684	snprintf(prompt, sizeof(prompt),
685	    "Select a zone which observes the same time as your locality.");
686	rv = xdialog_menu(title, prompt, cp->nzones, cp->submenu);
687	return (rv != 0 ? DITEM_RECREATE : DITEM_LEAVE_MENU);
688}
689
690static int
691set_zone_utc(void)
692{
693	if (!confirm_zone("UTC"))
694		return (DITEM_FAILURE | DITEM_RECREATE);
695
696	return (install_zoneinfo("UTC"));
697}
698
699static int
700confirm_zone(const char *filename)
701{
702	char		prompt[64];
703	time_t		t = time(0);
704	struct tm	*tm;
705	int		rv;
706
707	setenv("TZ", filename, 1);
708	tzset();
709	tm = localtime(&t);
710
711	snprintf(prompt, sizeof(prompt),
712	    "Does the timezone abbreviation `%s' look reasonable?", tm->tm_zone);
713	conf.title = "Confirmation";
714	rv = (bsddialog_yesno(&conf, prompt, 5, 72) == BSDDIALOG_YES);
715	return (rv);
716}
717
718static int
719set_zone(dialogMenuItem *dmi)
720{
721	struct zone	*zp = dmi->data;
722	int		rv;
723
724	if (!confirm_zone(zp->filename))
725		return (DITEM_FAILURE | DITEM_RECREATE);
726
727	rv = install_zoneinfo(zp->filename);
728	return (rv);
729}
730
731#endif
732
733static void message_zoneinfo_file(const char *title, char *prompt)
734{
735#ifdef HAVE_BSDDIALOG
736	if (usedialog) {
737		conf.title = title;
738		bsddialog_msgbox(&conf, prompt, 8, 72);
739	} else
740#endif
741		fprintf(stderr, "%s: %s\n", title, prompt);
742}
743
744static int
745install_zoneinfo_file(const char *zoneinfo_file)
746{
747	char		prompt[SILLY_BUFFER_SIZE];
748
749#ifdef VERBOSE
750	snprintf(prompt, sizeof(prompt), "Creating symbolic link %s to %s",
751	    path_localtime, zoneinfo_file);
752	message_zoneinfo_file("Info", prompt);
753#endif
754
755	if (reallydoit) {
756		if (access(zoneinfo_file, R_OK) != 0) {
757			snprintf(prompt, sizeof(prompt),
758			    "Cannot access %s: %s", zoneinfo_file,
759			    strerror(errno));
760			message_zoneinfo_file("Error", prompt);
761			return (DITEM_FAILURE | DITEM_RECREATE);
762		}
763		if (unlink(path_localtime) < 0 && errno != ENOENT) {
764			snprintf(prompt, sizeof(prompt),
765			    "Could not delete %s: %s",
766			    path_localtime, strerror(errno));
767			message_zoneinfo_file("Error", prompt);
768			return (DITEM_FAILURE | DITEM_RECREATE);
769		}
770		if (symlink(zoneinfo_file, path_localtime) < 0) {
771			snprintf(prompt, sizeof(prompt),
772			    "Cannot create symbolic link %s to %s: %s",
773			    path_localtime, zoneinfo_file,
774			    strerror(errno));
775			message_zoneinfo_file("Error", prompt);
776			return (DITEM_FAILURE | DITEM_RECREATE);
777		}
778
779#ifdef VERBOSE
780		snprintf(prompt, sizeof(prompt),
781		    "Created symbolic link from %s to %s", zoneinfo_file,
782		    path_localtime);
783		message_zoneinfo_file("Done", prompt);
784#endif
785	} /* reallydoit */
786
787	return (DITEM_LEAVE_MENU);
788}
789
790static int
791install_zoneinfo(const char *zoneinfo)
792{
793	int		rv;
794	FILE		*f;
795	char		path_zoneinfo_file[MAXPATHLEN];
796
797	if ((size_t)snprintf(path_zoneinfo_file, sizeof(path_zoneinfo_file),
798	    "%s/%s", path_zoneinfo, zoneinfo) >= sizeof(path_zoneinfo_file))
799		errx(1, "%s/%s name too long", path_zoneinfo, zoneinfo);
800	rv = install_zoneinfo_file(path_zoneinfo_file);
801
802	/* Save knowledge for later */
803	if (reallydoit && (rv & DITEM_FAILURE) == 0) {
804		if ((f = fopen(path_db, "w")) != NULL) {
805			fprintf(f, "%s\n", zoneinfo);
806			fclose(f);
807		}
808	}
809
810	return (rv);
811}
812
813static void
814usage(void)
815{
816
817	fprintf(stderr, "usage: tzsetup [-nrs] [-C chroot_directory]"
818	    " [zoneinfo_file | zoneinfo_name]\n");
819	exit(1);
820}
821
822int
823main(int argc, char **argv)
824{
825#ifdef HAVE_BSDDIALOG
826	char		prompt[128];
827	int		fd;
828#endif
829	int		c, rv, skiputc;
830	char		vm_guest[16] = "";
831	size_t		len = sizeof(vm_guest);
832	char		*dztpath;
833
834	dztpath = NULL;
835	skiputc = 0;
836
837#ifdef HAVE_BSDDIALOG
838	setlocale(LC_ALL, "");
839#endif
840
841	/* Default skiputc to 1 for VM guests */
842	if (sysctlbyname("kern.vm_guest", vm_guest, &len, NULL, 0) == 0 &&
843	    strcmp(vm_guest, "none") != 0)
844		skiputc = 1;
845
846	while ((c = getopt(argc, argv, "C:d:nrs")) != -1) {
847		switch (c) {
848		case 'C':
849			chrootenv = optarg;
850			break;
851		case 'd':
852			dztpath = optarg;
853			break;
854		case 'n':
855			reallydoit = 0;
856			break;
857		case 'r':
858			reinstall = 1;
859#ifdef HAVE_BSDDIALOG
860			usedialog = 0;
861#endif
862			break;
863		case 's':
864			skiputc = 1;
865			break;
866		default:
867			usage();
868		}
869	}
870
871	if (argc - optind > 1)
872		usage();
873
874	if (chrootenv == NULL) {
875		if (dztpath == NULL)
876			strcpy(path_zonetab, _PATH_ZONETAB);
877		else
878			strlcpy(path_zonetab, dztpath, sizeof(path_zonetab));
879		strcpy(path_iso3166, _PATH_ISO3166);
880		strcpy(path_zoneinfo, _PATH_ZONEINFO);
881		strcpy(path_localtime, _PATH_LOCALTIME);
882		strcpy(path_db, _PATH_DB);
883		strcpy(path_wall_cmos_clock, _PATH_WALL_CMOS_CLOCK);
884	} else {
885		sprintf(path_zonetab, "%s/%s", chrootenv, _PATH_ZONETAB);
886		sprintf(path_iso3166, "%s/%s", chrootenv, _PATH_ISO3166);
887		sprintf(path_zoneinfo, "%s/%s", chrootenv, _PATH_ZONEINFO);
888		sprintf(path_localtime, "%s/%s", chrootenv, _PATH_LOCALTIME);
889		sprintf(path_db, "%s/%s", chrootenv, _PATH_DB);
890		sprintf(path_wall_cmos_clock, "%s/%s", chrootenv,
891		    _PATH_WALL_CMOS_CLOCK);
892	}
893
894	/* Override the user-supplied umask. */
895	(void)umask(S_IWGRP | S_IWOTH);
896
897	if (reinstall == 1) {
898		FILE *f;
899		char zoneinfo[MAXPATHLEN];
900
901		if ((f = fopen(path_db, "r")) != NULL) {
902			if (fgets(zoneinfo, sizeof(zoneinfo), f) != NULL) {
903				zoneinfo[sizeof(zoneinfo) - 1] = 0;
904				if (strlen(zoneinfo) > 0) {
905					zoneinfo[strlen(zoneinfo) - 1] = 0;
906					rv = install_zoneinfo(zoneinfo);
907					exit(rv & ~DITEM_LEAVE_MENU);
908				}
909				errx(1, "Error reading %s.\n", path_db);
910			}
911			fclose(f);
912			errx(1,
913			    "Unable to determine earlier installed zoneinfo "
914			    "name. Check %s", path_db);
915		}
916		errx(1, "Cannot open %s for reading. Does it exist?", path_db);
917	}
918
919	/*
920	 * If the arguments on the command-line do not specify a file,
921	 * then interpret it as a zoneinfo name
922	 */
923	if (optind == argc - 1) {
924		struct stat sb;
925
926		if (stat(argv[optind], &sb) != 0) {
927#ifdef HAVE_BSDDIALOG
928			usedialog = 0;
929#endif
930			rv = install_zoneinfo(argv[optind]);
931			exit(rv & ~DITEM_LEAVE_MENU);
932		}
933		/* FALLTHROUGH */
934	}
935#ifdef HAVE_BSDDIALOG
936
937	read_iso3166_table();
938	read_zones();
939	sort_countries();
940	if (dztpath != NULL) {
941		dump_zonetab();
942		return (0);
943	}
944	make_menus();
945
946	bsddialog_initconf(&conf);
947	conf.clear = true;
948	conf.auto_minwidth = 24;
949	conf.key.enable_esc = true;
950
951	if (bsddialog_init() == BSDDIALOG_ERROR)
952		errx(1, "Error bsddialog: %s\n", bsddialog_geterror());
953
954	if (skiputc == 0) {
955		snprintf(prompt, sizeof(prompt),
956		    "Is this machine's CMOS clock set to UTC?  "
957		    "If it is set to local time,\n"
958		    "or you don't know, please choose NO here!");
959
960		conf.title = "Select local or UTC (Greenwich Mean Time) clock";
961		if (bsddialog_yesno(&conf, prompt, 7, 73) == BSDDIALOG_YES) {
962			if (reallydoit)
963				unlink(path_wall_cmos_clock);
964		} else {
965			if (reallydoit) {
966				fd = open(path_wall_cmos_clock,
967				    O_WRONLY | O_CREAT | O_TRUNC,
968				    S_IRUSR | S_IRGRP | S_IROTH);
969				if (fd < 0) {
970					bsddialog_end();
971					err(1, "create %s",
972					    path_wall_cmos_clock);
973				}
974				close(fd);
975			}
976		}
977	}
978	if (optind == argc - 1) {
979		snprintf(prompt, sizeof(prompt),
980		    "\nUse the default `%s' zone?", argv[optind]);
981		conf.title = "Default timezone provided";
982		if (bsddialog_yesno(&conf, prompt, 7, 72) == BSDDIALOG_YES) {
983			rv = install_zoneinfo_file(argv[optind]);
984			bsddialog_end();
985			exit(rv & ~DITEM_LEAVE_MENU);
986		}
987	}
988	xdialog_menu("Time Zone Selector", "Select a region", NCONTINENTS,
989	    continents);
990
991	bsddialog_end();
992#else
993	usage();
994#endif
995	return (0);
996}
997