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