tzsetup.c revision 227011
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#include <sys/cdefs.h>
36__FBSDID("$FreeBSD: head/usr.sbin/tzsetup/tzsetup.c 227011 2011-11-02 04:21:20Z dougb $");
37
38#include <dialog.h>
39#include <err.h>
40#include <errno.h>
41#include <stdio.h>
42#include <stdlib.h>
43#include <string.h>
44#include <time.h>
45#include <unistd.h>
46
47#include <sys/fcntl.h>
48#include <sys/param.h>
49#include <sys/queue.h>
50#include <sys/stat.h>
51
52#define	_PATH_ZONETAB		"/usr/share/zoneinfo/zone.tab"
53#define	_PATH_ISO3166		"/usr/share/misc/iso3166"
54#define	_PATH_ZONEINFO		"/usr/share/zoneinfo"
55#define	_PATH_LOCALTIME		"/etc/localtime"
56#define	_PATH_DB		"/var/db/zoneinfo"
57#define	_PATH_WALL_CMOS_CLOCK	"/etc/wall_cmos_clock"
58
59static char	path_zonetab[MAXPATHLEN], path_iso3166[MAXPATHLEN],
60		path_zoneinfo[MAXPATHLEN], path_localtime[MAXPATHLEN],
61		path_db[MAXPATHLEN], path_wall_cmos_clock[MAXPATHLEN];
62
63static int reallydoit = 1;
64static int reinstall = 0;
65static int usedialog = 1;
66static char *chrootenv = NULL;
67
68static void	usage(void);
69static int	confirm_zone(const char *filename);
70static int	continent_country_menu(dialogMenuItem *);
71static int	install_zoneinfo_file(const char *zoneinfo_file);
72static int	set_zone_multi(dialogMenuItem *);
73static int	set_zone_whole_country(dialogMenuItem *);
74static int	set_zone_menu(dialogMenuItem *);
75static int	set_zone_utc(void);
76
77struct continent {
78	dialogMenuItem *menu;
79	int		nitems;
80	int		ch;
81	int		sc;
82};
83
84static struct continent	africa, america, antarctica, arctic, asia, atlantic;
85static struct continent	australia, europe, indian, pacific, utc;
86
87static struct continent_names {
88	const char	*name;
89	struct continent *continent;
90} continent_names[] = {
91	{ "Africa",	&africa },
92	{ "America",	&america },
93	{ "Antarctica",	&antarctica },
94	{ "Arctic",	&arctic },
95	{ "Asia",	&asia },
96	{ "Atlantic",	&atlantic },
97	{ "Australia",	&australia },
98	{ "Europe",	&europe },
99	{ "Indian",	&indian },
100	{ "Pacific",	&pacific },
101	{ "UTC", 	&utc }
102};
103
104static struct continent_items {
105	char		prompt[2];
106	char		title[30];
107} continent_items[] = {
108	{ "1",	"Africa" },
109	{ "2",	"America -- North and South" },
110	{ "3",	"Antarctica" },
111	{ "4",	"Arctic Ocean" },
112	{ "5",	"Asia" },
113	{ "6",	"Atlantic Ocean" },
114	{ "7",	"Australia" },
115	{ "8",	"Europe" },
116	{ "9",	"Indian Ocean" },
117	{ "0",	"Pacific Ocean" },
118	{ "a",	"UTC" }
119};
120
121#define	NCONTINENTS	\
122    (int)((sizeof(continent_items)) / (sizeof(continent_items[0])))
123static dialogMenuItem continents[NCONTINENTS];
124
125#define	OCEANP(x)	((x) == 3 || (x) == 5 || (x) == 8 || (x) == 9)
126
127static int
128continent_country_menu(dialogMenuItem *continent)
129{
130	char		title[64], prompt[64];
131	struct continent *contp = continent->data;
132	int		isocean = OCEANP(continent - continents);
133	int		menulen;
134	int		rv;
135
136	if (strcmp(continent->title, "UTC") == 0)
137	        return set_zone_utc();
138
139	/* Short cut -- if there's only one country, don't post a menu. */
140	if (contp->nitems == 1)
141		return (contp->menu[0].fire(&contp->menu[0]));
142
143	/* It's amazing how much good grammar really matters... */
144	if (!isocean) {
145		snprintf(title, sizeof(title), "Countries in %s",
146		    continent->title);
147		snprintf(prompt, sizeof(prompt), "Select a country or region");
148	} else {
149		snprintf(title, sizeof(title), "Islands and groups in the %s",
150		    continent->title);
151		snprintf(prompt, sizeof(prompt), "Select an island or group");
152	}
153
154	menulen = contp->nitems < 16 ? contp->nitems : 16;
155	rv = dialog_menu(title, prompt, -1, -1, menulen, -contp->nitems,
156	    contp->menu, 0, &contp->ch, &contp->sc);
157	if (rv == 0)
158		return (DITEM_LEAVE_MENU);
159	return (DITEM_RECREATE);
160}
161
162static struct continent *
163find_continent(const char *name)
164{
165	int		i;
166
167	for (i = 0; i < NCONTINENTS; i++)
168		if (strcmp(name, continent_names[i].name) == 0)
169			return (continent_names[i].continent);
170	return (0);
171}
172
173struct country {
174	char		*name;
175	char		*tlc;
176	int		nzones;
177	char		*filename;	/* use iff nzones < 0 */
178	struct continent *continent;	/* use iff nzones < 0 */
179	TAILQ_HEAD(, zone) zones;	/* use iff nzones > 0 */
180	dialogMenuItem	*submenu;	/* use iff nzones > 0 */
181};
182
183struct zone {
184	TAILQ_ENTRY(zone) link;
185	char		*descr;
186	char		*filename;
187	struct continent *continent;
188};
189
190/*
191 * This is the easiest organization... we use ISO 3166 country codes,
192 * of the two-letter variety, so we just size this array to suit.
193 * Beats worrying about dynamic allocation.
194 */
195#define	NCOUNTRIES	(26 * 26)
196static struct country countries[NCOUNTRIES];
197
198#define	CODE2INT(s)	((s[0] - 'A') * 26 + (s[1] - 'A'))
199
200/*
201 * Read the ISO 3166 country code database in _PATH_ISO3166
202 * (/usr/share/misc/iso3166).  On error, exit via err(3).
203 */
204static void
205read_iso3166_table(void)
206{
207	FILE		*fp;
208	struct country	*cp;
209	size_t		len;
210	char		*s, *t, *name;
211	int		lineno;
212
213	fp = fopen(path_iso3166, "r");
214	if (!fp)
215		err(1, "%s", path_iso3166);
216	lineno = 0;
217
218	while ((s = fgetln(fp, &len)) != 0) {
219		lineno++;
220		if (s[len - 1] != '\n')
221			errx(1, "%s:%d: invalid format", path_iso3166, lineno);
222		s[len - 1] = '\0';
223		if (s[0] == '#' || strspn(s, " \t") == len - 1)
224			continue;
225
226		/* Isolate the two-letter code. */
227		t = strsep(&s, "\t");
228		if (t == 0 || strlen(t) != 2)
229			errx(1, "%s:%d: invalid format", path_iso3166, lineno);
230		if (t[0] < 'A' || t[0] > 'Z' || t[1] < 'A' || t[1] > 'Z')
231			errx(1, "%s:%d: invalid code `%s'", path_iso3166,
232			    lineno, t);
233
234		/* Now skip past the three-letter and numeric codes. */
235		name = strsep(&s, "\t");	/* 3-let */
236		if (name == 0 || strlen(name) != 3)
237			errx(1, "%s:%d: invalid format", path_iso3166, lineno);
238		name = strsep(&s, "\t");	/* numeric */
239		if (name == 0 || strlen(name) != 3)
240			errx(1, "%s:%d: invalid format", path_iso3166, lineno);
241
242		name = s;
243
244		cp = &countries[CODE2INT(t)];
245		if (cp->name)
246			errx(1, "%s:%d: country code `%s' multiply defined: %s",
247			    path_iso3166, lineno, t, cp->name);
248		cp->name = strdup(name);
249		if (cp->name == NULL)
250			errx(1, "malloc failed");
251		cp->tlc = strdup(t);
252		if (cp->tlc == NULL)
253			errx(1, "malloc failed");
254	}
255
256	fclose(fp);
257}
258
259static void
260add_zone_to_country(int lineno, const char *tlc, const char *descr,
261    const char *file, struct continent *cont)
262{
263	struct zone	*zp;
264	struct country	*cp;
265
266	if (tlc[0] < 'A' || tlc[0] > 'Z' || tlc[1] < 'A' || tlc[1] > 'Z')
267		errx(1, "%s:%d: country code `%s' invalid", path_zonetab,
268		    lineno, tlc);
269
270	cp = &countries[CODE2INT(tlc)];
271	if (cp->name == 0)
272		errx(1, "%s:%d: country code `%s' unknown", path_zonetab,
273		    lineno, tlc);
274
275	if (descr) {
276		if (cp->nzones < 0)
277			errx(1, "%s:%d: conflicting zone definition",
278			    path_zonetab, lineno);
279
280		zp = malloc(sizeof(*zp));
281		if (zp == 0)
282			errx(1, "malloc(%zu)", sizeof(*zp));
283
284		if (cp->nzones == 0)
285			TAILQ_INIT(&cp->zones);
286
287		zp->descr = strdup(descr);
288		if (zp->descr == NULL)
289			errx(1, "malloc failed");
290		zp->filename = strdup(file);
291		if (zp->filename == NULL)
292			errx(1, "malloc failed");
293		zp->continent = cont;
294		TAILQ_INSERT_TAIL(&cp->zones, zp, link);
295		cp->nzones++;
296	} else {
297		if (cp->nzones > 0)
298			errx(1, "%s:%d: zone must have description",
299			    path_zonetab, lineno);
300		if (cp->nzones < 0)
301			errx(1, "%s:%d: zone multiply defined",
302			    path_zonetab, lineno);
303		cp->nzones = -1;
304		cp->filename = strdup(file);
305		if (cp->filename == NULL)
306			errx(1, "malloc failed");
307		cp->continent = cont;
308	}
309}
310
311/*
312 * This comparison function intentionally sorts all of the null-named
313 * ``countries''---i.e., the codes that don't correspond to a real
314 * country---to the end.  Everything else is lexical by country name.
315 */
316static int
317compare_countries(const void *xa, const void *xb)
318{
319	const struct country *a = xa, *b = xb;
320
321	if (a->name == 0 && b->name == 0)
322		return (0);
323	if (a->name == 0 && b->name != 0)
324		return (1);
325	if (b->name == 0)
326		return (-1);
327
328	return (strcmp(a->name, b->name));
329}
330
331/*
332 * This must be done AFTER all zone descriptions are read, since it breaks
333 * CODE2INT().
334 */
335static void
336sort_countries(void)
337{
338
339	qsort(countries, NCOUNTRIES, sizeof(countries[0]), compare_countries);
340}
341
342static void
343read_zones(void)
344{
345	char		contbuf[16];
346	FILE		*fp;
347	struct continent *cont;
348	size_t		len;
349	char		*line, *tlc, *coord, *file, *descr, *p;
350	int		lineno;
351
352	fp = fopen(path_zonetab, "r");
353	if (!fp)
354		err(1, "%s", path_zonetab);
355	lineno = 0;
356
357	while ((line = fgetln(fp, &len)) != 0) {
358		lineno++;
359		if (line[len - 1] != '\n')
360			errx(1, "%s:%d: invalid format", path_zonetab, lineno);
361		line[len - 1] = '\0';
362		if (line[0] == '#')
363			continue;
364
365		tlc = strsep(&line, "\t");
366		if (strlen(tlc) != 2)
367			errx(1, "%s:%d: invalid country code `%s'",
368			    path_zonetab, lineno, tlc);
369		coord = strsep(&line, "\t");	 /* Unused */
370		file = strsep(&line, "\t");
371		p = strchr(file, '/');
372		if (p == 0)
373			errx(1, "%s:%d: invalid zone name `%s'", path_zonetab,
374			    lineno, file);
375		contbuf[0] = '\0';
376		strncat(contbuf, file, p - file);
377		cont = find_continent(contbuf);
378		if (!cont)
379			errx(1, "%s:%d: invalid region `%s'", path_zonetab,
380			    lineno, contbuf);
381
382		descr = (line != NULL && *line != '\0') ? line : NULL;
383
384		add_zone_to_country(lineno, tlc, descr, file, cont);
385	}
386	fclose(fp);
387}
388
389static void
390make_menus(void)
391{
392	struct country	*cp;
393	struct zone	*zp, *zp2;
394	struct continent *cont;
395	dialogMenuItem	*dmi;
396	int		i;
397
398	/*
399	 * First, count up all the countries in each continent/ocean.
400	 * Be careful to count those countries which have multiple zones
401	 * only once for each.  NB: some countries are in multiple
402	 * continents/oceans.
403	 */
404	for (cp = countries; cp->name; cp++) {
405		if (cp->nzones == 0)
406			continue;
407		if (cp->nzones < 0) {
408			cp->continent->nitems++;
409		} else {
410			TAILQ_FOREACH(zp, &cp->zones, link) {
411				cont = zp->continent;
412				for (zp2 = TAILQ_FIRST(&cp->zones);
413				    zp2->continent != cont;
414				    zp2 = TAILQ_NEXT(zp2, link))
415					;
416				if (zp2 == zp)
417					zp->continent->nitems++;
418			}
419		}
420	}
421
422	/*
423	 * Now allocate memory for the country menus and initialize
424	 * continent menus.  We set nitems back to zero so that we can
425	 * use it for counting again when we actually build the menus.
426	 */
427	memset(continents, 0, sizeof(continents));
428	for (i = 0; i < NCONTINENTS; i++) {
429		continent_names[i].continent->menu =
430		    malloc(sizeof(dialogMenuItem) *
431		    continent_names[i].continent->nitems);
432		if (continent_names[i].continent->menu == 0)
433			errx(1, "malloc for continent menu");
434		continent_names[i].continent->nitems = 0;
435		continents[i].prompt = continent_items[i].prompt;
436		continents[i].title = continent_items[i].title;
437		continents[i].fire = continent_country_menu;
438		continents[i].data = continent_names[i].continent;
439	}
440
441	/*
442	 * Now that memory is allocated, create the menu items for
443	 * each continent.  For multiple-zone countries, also create
444	 * the country's zone submenu.
445	 */
446	for (cp = countries; cp->name; cp++) {
447		if (cp->nzones == 0)
448			continue;
449		if (cp->nzones < 0) {
450			dmi = &cp->continent->menu[cp->continent->nitems];
451			memset(dmi, 0, sizeof(*dmi));
452			asprintf(&dmi->prompt, "%d", ++cp->continent->nitems);
453			dmi->title = cp->name;
454			dmi->checked = 0;
455			dmi->fire = set_zone_whole_country;
456			dmi->selected = 0;
457			dmi->data = cp;
458		} else {
459			cp->submenu = malloc(cp->nzones * sizeof(*dmi));
460			if (cp->submenu == 0)
461				errx(1, "malloc for submenu");
462			cp->nzones = 0;
463			TAILQ_FOREACH(zp, &cp->zones, link) {
464				cont = zp->continent;
465				dmi = &cp->submenu[cp->nzones];
466				memset(dmi, 0, sizeof(*dmi));
467				asprintf(&dmi->prompt, "%d", ++cp->nzones);
468				dmi->title = zp->descr;
469				dmi->checked = 0;
470				dmi->fire = set_zone_multi;
471				dmi->selected = 0;
472				dmi->data = zp;
473
474				for (zp2 = TAILQ_FIRST(&cp->zones);
475				    zp2->continent != cont;
476				    zp2 = TAILQ_NEXT(zp2, link))
477					;
478				if (zp2 != zp)
479					continue;
480
481				dmi = &cont->menu[cont->nitems];
482				memset(dmi, 0, sizeof(*dmi));
483				asprintf(&dmi->prompt, "%d", ++cont->nitems);
484				dmi->title = cp->name;
485				dmi->checked = 0;
486				dmi->fire = set_zone_menu;
487				dmi->selected = 0;
488				dmi->data = cp;
489			}
490		}
491	}
492}
493
494static int
495set_zone_menu(dialogMenuItem *dmi)
496{
497	char		title[64], prompt[64];
498	struct country	*cp = dmi->data;
499	int		menulen;
500	int		rv;
501
502	snprintf(title, sizeof(title), "%s Time Zones", cp->name);
503	snprintf(prompt, sizeof(prompt),
504	    "Select a zone which observes the same time as your locality.");
505	menulen = cp->nzones < 16 ? cp->nzones : 16;
506	rv = dialog_menu(title, prompt, -1, -1, menulen, -cp->nzones,
507	    cp->submenu, 0, 0, 0);
508	if (rv != 0)
509		return (DITEM_RECREATE);
510	return (DITEM_LEAVE_MENU);
511}
512
513int
514set_zone_utc(void)
515{
516	if (!confirm_zone(NULL))
517		return (DITEM_FAILURE | DITEM_RECREATE);
518
519	return (install_zoneinfo_file(NULL));
520}
521
522static int
523install_zoneinfo_file(const char *zoneinfo_file)
524{
525	char		buf[1024];
526	char		title[64], prompt[64];
527	struct stat	sb;
528	ssize_t		len;
529	int		fd1, fd2, copymode;
530
531	if (lstat(path_localtime, &sb) < 0) {
532		/* Nothing there yet... */
533		copymode = 1;
534	} else if (S_ISLNK(sb.st_mode))
535		copymode = 0;
536	else
537		copymode = 1;
538
539#ifdef VERBOSE
540	if (copymode)
541		snprintf(prompt, sizeof(prompt),
542		    "Copying %s to %s", zoneinfo_file, path_localtime);
543	else
544		snprintf(prompt, sizeof(prompt),
545		    "Creating symbolic link %s to %s",
546		    path_localtime,
547		    zoneinfo_file == NULL ? "(UTC)" : zoneinfo_file);
548	if (usedialog)
549		dialog_notify(prompt);
550	else
551		fprintf(stderr, "%s\n", prompt);
552#endif
553
554	if (reallydoit) {
555		if (zoneinfo_file == NULL) {
556			if (unlink(path_localtime) < 0 && errno != ENOENT) {
557				snprintf(title, sizeof(title), "Error");
558				snprintf(prompt, sizeof(prompt),
559				     "Could not delete %s: %s", path_localtime,
560				     strerror(errno));
561				if (usedialog)
562					dialog_mesgbox(title, prompt, 8, 72);
563				else
564					fprintf(stderr, "%s\n", prompt);
565
566				return (DITEM_FAILURE | DITEM_RECREATE);
567			}
568			if (unlink(path_db) < 0 && errno != ENOENT) {
569				snprintf(title, sizeof(title), "Error");
570				snprintf(prompt, sizeof(prompt),
571				     "Could not delete %s: %s", path_db,
572				     strerror(errno));
573				if (usedialog)
574					dialog_mesgbox(title, prompt, 8, 72);
575				else
576					fprintf(stderr, "%s\n", prompt);
577
578				return (DITEM_FAILURE | DITEM_RECREATE);
579			}
580			return (DITEM_LEAVE_MENU);
581		}
582
583		if (copymode) {
584			fd1 = open(zoneinfo_file, O_RDONLY, 0);
585			if (fd1 < 0) {
586				snprintf(title, sizeof(title), "Error");
587				snprintf(prompt, sizeof(prompt),
588				    "Could not open %s: %s", zoneinfo_file,
589				    strerror(errno));
590				if (usedialog)
591					dialog_mesgbox(title, prompt, 8, 72);
592				else
593					fprintf(stderr, "%s\n", prompt);
594				return (DITEM_FAILURE | DITEM_RECREATE);
595			}
596
597			unlink(path_localtime);
598			fd2 = open(path_localtime, O_CREAT | O_EXCL | O_WRONLY,
599			    S_IRUSR | S_IRGRP | S_IROTH);
600			if (fd2 < 0) {
601				snprintf(title, sizeof(title), "Error");
602				snprintf(prompt, sizeof(prompt),
603				    "Could not open %s: %s",
604				    path_localtime, strerror(errno));
605				if (usedialog)
606					dialog_mesgbox(title, prompt, 8, 72);
607				else
608					fprintf(stderr, "%s\n", prompt);
609				return (DITEM_FAILURE | DITEM_RECREATE);
610			}
611
612			while ((len = read(fd1, buf, sizeof(buf))) > 0)
613				if ((len = write(fd2, buf, len)) < 0)
614					break;
615
616			if (len == -1) {
617				snprintf(title, sizeof(title), "Error");
618				snprintf(prompt, sizeof(prompt),
619				    "Error copying %s to %s %s", zoneinfo_file,
620				    path_localtime, strerror(errno));
621				if (usedialog)
622					dialog_mesgbox(title, prompt, 8, 72);
623				else
624					fprintf(stderr, "%s\n", prompt);
625				/* Better to leave none than a corrupt one. */
626				unlink(path_localtime);
627				return (DITEM_FAILURE | DITEM_RECREATE);
628			}
629			close(fd1);
630			close(fd2);
631		} else {
632			if (access(zoneinfo_file, R_OK) != 0) {
633				snprintf(title, sizeof(title), "Error");
634				snprintf(prompt, sizeof(prompt),
635				    "Cannot access %s: %s", zoneinfo_file,
636				    strerror(errno));
637				if (usedialog)
638					dialog_mesgbox(title, prompt, 8, 72);
639				else
640					fprintf(stderr, "%s\n", prompt);
641				return (DITEM_FAILURE | DITEM_RECREATE);
642			}
643			unlink(path_localtime);
644			if (symlink(zoneinfo_file, path_localtime) < 0) {
645				snprintf(title, sizeof(title), "Error");
646				snprintf(prompt, sizeof(prompt),
647				    "Cannot create symbolic link %s to %s: %s",
648				    path_localtime, zoneinfo_file,
649				    strerror(errno));
650				if (usedialog)
651					dialog_mesgbox(title, prompt, 8, 72);
652				else
653					fprintf(stderr, "%s\n", prompt);
654				return (DITEM_FAILURE | DITEM_RECREATE);
655			}
656		}
657	}
658
659#ifdef VERBOSE
660	snprintf(title, sizeof(title), "Done");
661	if (copymode)
662		snprintf(prompt, sizeof(prompt),
663		    "Copied timezone file from %s to %s", zoneinfo_file,
664		    path_localtime);
665	else
666		snprintf(prompt, sizeof(prompt),
667		    "Created symbolic link from %s to %s", zoneinfo_file,
668		    path_localtime);
669	if (usedialog)
670		dialog_mesgbox(title, prompt, 8, 72);
671	else
672		fprintf(stderr, "%s\n", prompt);
673#endif
674
675	return (DITEM_LEAVE_MENU);
676}
677
678static int
679install_zoneinfo(const char *zoneinfo)
680{
681	int		rv;
682	FILE		*f;
683	char		path_zoneinfo_file[MAXPATHLEN];
684
685	sprintf(path_zoneinfo_file, "%s/%s", path_zoneinfo, zoneinfo);
686	rv = install_zoneinfo_file(path_zoneinfo_file);
687
688	/* Save knowledge for later */
689	if ((f = fopen(path_db, "w")) != NULL) {
690		fprintf(f, "%s\n", zoneinfo);
691		fclose(f);
692	}
693
694	return (rv);
695}
696
697static int
698confirm_zone(const char *filename)
699{
700	char		title[64], prompt[64];
701	time_t		t = time(0);
702	struct tm	*tm;
703	int		rv;
704
705	setenv("TZ", filename == NULL ? "" : filename, 1);
706	tzset();
707	tm = localtime(&t);
708
709	snprintf(title, sizeof(title), "Confirmation");
710	snprintf(prompt, sizeof(prompt),
711	    "Does the abbreviation `%s' look reasonable?", tm->tm_zone);
712	rv = !dialog_yesno(title, prompt, 5, 72);
713	return (rv);
714}
715
716static int
717set_zone_multi(dialogMenuItem *dmi)
718{
719	struct zone	*zp = dmi->data;
720	int		rv;
721
722	if (!confirm_zone(zp->filename))
723		return (DITEM_FAILURE | DITEM_RECREATE);
724
725	rv = install_zoneinfo(zp->filename);
726	return (rv);
727}
728
729static int
730set_zone_whole_country(dialogMenuItem *dmi)
731{
732	struct country	*cp = dmi->data;
733	int		rv;
734
735	if (!confirm_zone(cp->filename))
736		return (DITEM_FAILURE | DITEM_RECREATE);
737
738	rv = install_zoneinfo(cp->filename);
739	return (rv);
740}
741
742static void
743usage(void)
744{
745
746	fprintf(stderr, "usage: tzsetup [-nrs] [-C chroot_directory]"
747	    " [zoneinfo_file | zoneinfo_name]\n");
748	exit(1);
749}
750
751#if defined(__sparc64__)
752#define	DIALOG_UTC	dialog_yesno
753#else
754#define	DIALOG_UTC	dialog_noyes
755#endif
756
757int
758main(int argc, char **argv)
759{
760	char		title[64], prompt[128];
761	int		c, fd, rv, skiputc;
762
763	skiputc = 0;
764	while ((c = getopt(argc, argv, "C:nrs")) != -1) {
765		switch(c) {
766		case 'C':
767			chrootenv = optarg;
768			break;
769		case 'n':
770			reallydoit = 0;
771			break;
772		case 'r':
773			reinstall = 1;
774			usedialog = 0;
775			break;
776		case 's':
777			skiputc = 1;
778			break;
779		default:
780			usage();
781		}
782	}
783
784	if (argc - optind > 1)
785		usage();
786
787	if (chrootenv == NULL) {
788		strcpy(path_zonetab, _PATH_ZONETAB);
789		strcpy(path_iso3166, _PATH_ISO3166);
790		strcpy(path_zoneinfo, _PATH_ZONEINFO);
791		strcpy(path_localtime, _PATH_LOCALTIME);
792		strcpy(path_db, _PATH_DB);
793		strcpy(path_wall_cmos_clock, _PATH_WALL_CMOS_CLOCK);
794	} else {
795		sprintf(path_zonetab, "%s/%s", chrootenv, _PATH_ZONETAB);
796		sprintf(path_iso3166, "%s/%s", chrootenv, _PATH_ISO3166);
797		sprintf(path_zoneinfo, "%s/%s", chrootenv, _PATH_ZONEINFO);
798		sprintf(path_localtime, "%s/%s", chrootenv, _PATH_LOCALTIME);
799		sprintf(path_db, "%s/%s", chrootenv, _PATH_DB);
800		sprintf(path_wall_cmos_clock, "%s/%s", chrootenv,
801		    _PATH_WALL_CMOS_CLOCK);
802	}
803
804
805	/* Override the user-supplied umask. */
806	(void)umask(S_IWGRP | S_IWOTH);
807
808	read_iso3166_table();
809	read_zones();
810	sort_countries();
811	make_menus();
812
813	if (reinstall == 1) {
814		FILE *f;
815		char zonefile[MAXPATHLEN];
816		char path_db[MAXPATHLEN];
817
818		zonefile[0] = '\0';
819		path_db[0] = '\0';
820		if (chrootenv != NULL) {
821			sprintf(zonefile, "%s/", chrootenv);
822			sprintf(path_db, "%s/", chrootenv);
823		}
824		strcat(zonefile, _PATH_ZONEINFO);
825		strcat(zonefile, "/");
826		strcat(path_db, _PATH_DB);
827
828		if ((f = fopen(path_db, "r")) != NULL) {
829			if (fgets(zonefile, sizeof(zonefile), f) != NULL) {
830				zonefile[sizeof(zonefile) - 1] = 0;
831				if (strlen(zonefile) > 0) {
832					zonefile[strlen(zonefile) - 1] = 0;
833					rv = install_zoneinfo(zonefile);
834					exit(rv & ~DITEM_LEAVE_MENU);
835				}
836				errx(1, "Error reading %s.\n", path_db);
837			}
838			fclose(f);
839			errx(1,
840			    "Unable to determine earlier installed zoneinfo "
841			    "file. Check %s", path_db);
842		}
843		errx(1, "Cannot open %s for reading. Does it exist?", path_db);
844	}
845
846	/*
847	 * If the arguments on the command-line do not specify a file,
848	 * then interpret it as a zoneinfo name
849	 */
850	if (optind == argc - 1) {
851		struct stat sb;
852
853		if (stat(argv[optind], &sb) != 0) {
854			usedialog = 0;
855			rv = install_zoneinfo(argv[optind]);
856			exit(rv & ~DITEM_LEAVE_MENU);
857		}
858		/* FALLTHROUGH */
859	}
860
861	init_dialog();
862	if (skiputc == 0) {
863		snprintf(title, sizeof(title),
864		    "Select local or UTC (Greenwich Mean Time) clock");
865		snprintf(prompt, sizeof(prompt),
866		    "Is this machine's CMOS clock set to UTC?  "
867		    "If it is set to local time,\n"
868		    "or you don't know, please choose NO here!");
869		if (!DIALOG_UTC(title, prompt, 7, 72)) {
870			if (reallydoit)
871				unlink(path_wall_cmos_clock);
872		} else {
873			if (reallydoit) {
874				fd = open(path_wall_cmos_clock,
875				    O_WRONLY | O_CREAT | O_TRUNC,
876				    S_IRUSR | S_IRGRP | S_IROTH);
877				if (fd < 0) {
878					end_dialog();
879					err(1, "create %s",
880					    path_wall_cmos_clock);
881				}
882				close(fd);
883			}
884		}
885		dialog_clear_norefresh();
886	}
887	if (optind == argc - 1) {
888		snprintf(title, sizeof(title), "Default timezone provided");
889		snprintf(prompt, sizeof(prompt),
890		    "\nUse the default `%s' zone?", argv[optind]);
891		if (!dialog_yesno(title, prompt, 7, 72)) {
892			rv = install_zoneinfo_file(argv[optind]);
893			dialog_clear();
894			end_dialog();
895			exit(rv & ~DITEM_LEAVE_MENU);
896		}
897		dialog_clear_norefresh();
898	}
899	snprintf(title, sizeof(title), "Time Zone Selector");
900	snprintf(prompt, sizeof(prompt), "Select a region");
901	dialog_menu(title, prompt, -1, -1, NCONTINENTS, -NCONTINENTS,
902	    continents, 0, NULL, NULL);
903
904	dialog_clear();
905	end_dialog();
906	return (0);
907}
908