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