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