xref: /illumos-gate/usr/src/lib/libzoneinfo/common/libzone.c (revision 45ede40b2394db7967e59f19288fae9b62efd4aa)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License, Version 1.0 only
6  * (the "License").  You may not use this file except in compliance
7  * with the License.
8  *
9  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
10  * or http://www.opensolaris.org/os/licensing.
11  * See the License for the specific language governing permissions
12  * and limitations under the License.
13  *
14  * When distributing Covered Code, include this CDDL HEADER in each
15  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
16  * If applicable, add the following below this CDDL HEADER, with the
17  * fields enclosed by brackets "[]" replaced with your own identifying
18  * information: Portions Copyright [yyyy] [name of copyright owner]
19  *
20  * CDDL HEADER END
21  */
22 /*
23  * Copyright 2004 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 /*
28  * Copyright (c) 2018, Joyent, Inc.
29  */
30 
31 #include <stdlib.h>
32 #include <stdio.h>
33 #include <string.h>
34 #include <unistd.h>
35 #include <sys/param.h>
36 #include <sys/types.h>
37 #include <sys/stat.h>
38 #include <tzfile.h>
39 #include <fcntl.h>
40 #include <regex.h>
41 #include <errno.h>
42 #include <libintl.h>
43 #include <libzoneinfo.h>
44 
45 #define	DEFINIT		"/etc/default/init"
46 #define	ZONEINFOTABDIR	"/usr/share/lib/zoneinfo/tab/"
47 #define	CONTINENT_TAB	ZONEINFOTABDIR "continent.tab"
48 #define	COUNTRY_TAB	ZONEINFOTABDIR "country.tab"
49 #define	ZONE_SUN_TAB	ZONEINFOTABDIR "zone_sun.tab"
50 
51 #define	NEWLINE		"\n"
52 #define	SLASH		"/"
53 #define	WHITESPACE	"\t "
54 #define	WHITESPACE_NL	"\t \n"
55 #define	DIGITS		"0123456789"
56 #define	BUFFLEN		1024
57 
58 #define	CCLEN		2		/* country code length */
59 
60 #define	GMT_MAX		(12*60*60)	/* The maximum GMT offset */
61 #define	GMT_MIN		(-13*60*60)	/* The minimum GMT offset */
62 #define	GMT_FMT_Q	"<GMT%c%d>%c%d"
63 #define	GMT_FMT_Q_LEN	(11)		/* "<GMT+dd>+dd" - maximum 11 chars */
64 #define	GMT0_FMT	"GMT0"		/* backwards compatibility name */
65 #define	GMT_FMT_ZONE	":Etc/GMT%c%d"	/* ":Etc/GMT+dd" */
66 #define	GMT_FMT_ZONE_LEN	(11)	/* ":Etc/GMT+dd" - maximum 11 chars */
67 
68 #define	TZ_FMT		"TZ=%s\n"	/* format TZ entry init file */
69 #define	TZ_FMT_Q	"TZ=\"%s\"\n"	/* format quoted TZ entry init file */
70 
71 #define	COORD_FMTLEN1	(sizeof ("+DDMM+DDDMM") - 1)
72 #define	COORD_FMTLEN2	(sizeof ("+DDMMSS+DDDMMSS") - 1)
73 #define	COORD_FMT1		(1)	/* flag for format 1 */
74 #define	COORD_FMT2		(2)	/* flag for format 2 */
75 #define	COORD_DLEN_LAT		(2)	/* length of DD for latitude */
76 #define	COORD_DLEN_LONG		(3)	/* length of DDD for longtitude */
77 #define	COORD_MLEN		(2)	/* length of MM */
78 #define	COORD_SLEN		(2)	/* length of SS */
79 
80 #define	TRAILER		"/XXXXXX"
81 #define	TR_LEN		(sizeof (TRAILER) -1)
82 
83 /* Internal Declarations */
84 static char *skipwhite(char *);
85 static int skipline(char *);
86 static int trav_link(char **);
87 static void remove_component(char *);
88 static void strip_quotes(char *, char *);
89 static int compar(struct tz_country *, struct tz_country *);
90 static int get_coord(struct tz_timezone *, char *, size_t);
91 static int _tz_match(const char *, const char *);
92 static char *_conv_gmt_zoneinfo(int);
93 static char *_conv_gmt_posix(int);
94 
95 /*
96  * get_tz_continents() reads the continent.tab file, and
97  * returns a list of continents.
98  */
99 int
100 get_tz_continents(struct tz_continent **cont)
101 {
102 	FILE *fp;
103 	char buff[BUFFLEN];
104 	char *lp;		/* line pointer */
105 	char *lptr, *ptr;	/* temp pointer */
106 	struct tz_continent *head = NULL, *lcp, *prev = NULL;
107 	int sav_errno = 0, ncount, status;
108 	size_t len;
109 
110 	/* open continents file */
111 	if ((fp = fopen(CONTINENT_TAB, "r")) == NULL) {
112 		/* fopen() sets errno */
113 		return (-1);
114 	}
115 	/* read and count continents */
116 	ncount = 0;
117 	/*CONSTANTCONDITION*/
118 	while (1) {
119 		if (fgets(buff, sizeof (buff), fp) == NULL) {
120 			if (feof(fp) == 0) {
121 				/* fgets() sets errno */
122 				sav_errno = errno;
123 				ncount = -1;
124 			}
125 			break;
126 		}
127 		/* Skip comments or blank/whitespace lines */
128 		if ((status = skipline(buff)) != 0) {
129 			if (status == 1)
130 				continue;
131 			else {
132 				sav_errno = EINVAL;
133 				ncount = -1;
134 				break;
135 			}
136 		}
137 		/* Get continent name */
138 		lp = skipwhite(&buff[0]);
139 		if ((len = strcspn(lp, WHITESPACE)) > _TZBUFLEN -1) {
140 			sav_errno = ENAMETOOLONG;
141 			ncount = -1;
142 			break;
143 		}
144 		/* create continent struct */
145 		if ((lcp = (struct tz_continent *)
146 			calloc(1, sizeof (struct tz_continent))) == NULL) {
147 			sav_errno = ENOMEM;
148 			ncount = -1;
149 			break;
150 		}
151 		(void) strncpy(lcp->ctnt_name, lp, len);
152 		lcp->ctnt_name[len] = '\0';
153 
154 		/* Get continent description */
155 		lp = skipwhite(lp + len);
156 		len = strcspn(lp, NEWLINE);
157 		if ((ptr = malloc(len + 1)) == NULL) {
158 			(void) free_tz_continents(lcp);
159 			sav_errno = ENOMEM;
160 			ncount = -1;
161 			break;
162 		}
163 		(void) strncpy(ptr, lp, len);
164 		*(ptr + len) = '\0';
165 		lcp->ctnt_id_desc = ptr;
166 
167 		/* Get localized continent description */
168 		lptr = dgettext(TEXT_DOMAIN, lcp->ctnt_id_desc);
169 		if ((ptr = strdup(lptr)) == NULL) {
170 			(void) free_tz_continents(lcp);
171 			sav_errno = ENOMEM;
172 			ncount = -1;
173 			break;
174 		}
175 		lcp->ctnt_display_desc = ptr;
176 
177 		if (head == NULL) {
178 			head = lcp;
179 		} else {
180 			prev->ctnt_next = lcp;
181 		}
182 		prev = lcp;
183 		ncount++;
184 	}
185 	(void) fclose(fp);
186 	if (ncount == -1) {
187 		if (head != NULL) {
188 			(void) free_tz_continents(head);
189 		}
190 		if (sav_errno)
191 			errno = sav_errno;
192 	} else {
193 		*cont = head;
194 	}
195 	return (ncount);
196 }
197 
198 /*
199  * get_tz_countries() finds the list of countries from the zone_sun.tab
200  * file, for the input continent, and retrieves the country
201  * names from the country.tab file.  It also retrieves the localized
202  * country names.  The returned list of countries is sorted by the
203  * countries' localized name fields.
204  */
205 int
206 get_tz_countries(struct tz_country **country, struct tz_continent *cont)
207 {
208 	FILE *fp_zone, *fp_cc;
209 	char buff[BUFFLEN], ccbuf[_CCBUFLEN], *ptr;
210 	char *lp, *lptr, *lp_coord, *lp_cc, *lp_tz;	/* line pointer */
211 	struct tz_country *head = NULL, *prev = NULL, *next, *cp, *cp2;
212 	int sav_errno = 0, ncount, i;
213 	int cmp, status;
214 	size_t len, len_coord, len_ctnt;
215 
216 	len_ctnt = strlen(cont->ctnt_name);
217 	ccbuf[0] = '\0';
218 
219 	/* open zone_sun.tab and country.tab files */
220 	if ((fp_zone = fopen(ZONE_SUN_TAB, "r")) == NULL) {
221 		/* fopen() sets errno */
222 		return (-1);
223 	}
224 	if ((fp_cc = fopen(COUNTRY_TAB, "r")) == NULL) {
225 		/* fopen() sets errno */
226 		(void) fclose(fp_zone);
227 		return (-1);
228 	}
229 
230 	/* read timezones to match continents, and get countries */
231 	ncount = 0;
232 	/*CONSTANTCONDITION*/
233 	while (1) {
234 		if (fgets(buff, sizeof (buff), fp_zone) == NULL) {
235 			if (feof(fp_zone) == 0) {
236 				/* fgets() error - errno set */
237 				sav_errno = errno;
238 				ncount = -1;
239 			}
240 			break;
241 		}
242 		/* Skip comments or blank/whitespace lines */
243 		if ((status = skipline(buff)) != 0) {
244 			if (status == 1)
245 				continue;
246 			else {
247 				sav_errno = EINVAL;
248 				ncount = -1;
249 				break;
250 			}
251 		}
252 		/*
253 		 * If country matches previously *matched* country, skip
254 		 * entry, since zone.tab is alphabetized by country code
255 		 * (It should be a *matched* country, because the same country
256 		 * can be in different continents.)
257 		 */
258 		/* Get country code */
259 		lp_cc = skipwhite(&buff[0]);
260 		if (strcspn(lp_cc, WHITESPACE) != CCLEN) {
261 			ncount = -1;
262 			sav_errno = EINVAL;
263 			break;
264 		}
265 		/* Check country code cache; skip if already found */
266 		if (strncmp(ccbuf, lp_cc, CCLEN) == 0) {
267 			continue;
268 		}
269 		/* Get coordinates */
270 		lp_coord = skipwhite(lp_cc + CCLEN);
271 		if (((len_coord = strcspn(lp_coord, WHITESPACE)) !=
272 				COORD_FMTLEN1) &&
273 				(len_coord != COORD_FMTLEN2)) {
274 			ncount = -1;
275 			sav_errno = EINVAL;
276 			break;
277 		}
278 
279 		/* Get timezone name (Skip timezone description) */
280 		lp_tz = skipwhite(lp_coord + len_coord);
281 		if ((len = strcspn(lp_tz, SLASH)) == 0) {
282 			ncount = -1;
283 			sav_errno = EINVAL;
284 			break;
285 		}
286 		/* If continents match, allocate a country struct */
287 		if ((len == len_ctnt) &&
288 				(strncmp(cont->ctnt_name, lp_tz, len) == 0)) {
289 			if ((cp = (struct tz_country *)
290 			    calloc(1, sizeof (struct tz_country))) == NULL) {
291 				sav_errno = ENOMEM;
292 				ncount = -1;
293 				break;
294 			}
295 			/* Copy and save country code (len already checked) */
296 			(void) strncpy(cp->ctry_code, lp_cc, CCLEN);
297 			cp->ctry_code[CCLEN] = '\0';
298 			(void) strncpy(ccbuf, lp_cc, CCLEN);
299 			ccbuf[CCLEN] = '\0';
300 
301 			/* Create linked list */
302 			if (head == NULL) {
303 				head = cp;
304 			} else {
305 				prev->ctry_next = cp;
306 			};
307 			prev = cp;
308 			ncount++;
309 		}
310 	}	/* while */
311 
312 	if (ncount == -1)
313 		goto error;
314 
315 	/* Get country name from country.tab; get localized country name */
316 	/* Read country list, match country codes to process entry */
317 	cp = head;
318 	/*CONSTANTCONDITION*/
319 	while (1) {
320 		if (fgets(buff, sizeof (buff), fp_cc) == NULL) {
321 			if (feof(fp_cc) == 0) {
322 				/* fgets() sets errno */
323 				ncount = -1;
324 				sav_errno = errno;
325 			}
326 			break;
327 		}
328 		/* Skip comments or blank/whitespace lines */
329 		if ((status = skipline(buff)) != 0) {
330 			if (status == 1)
331 				continue;
332 			else {
333 				sav_errno = EINVAL;
334 				ncount = -1;
335 				break;
336 			}
337 		}
338 		/* Match country codes */
339 		if ((len = strcspn(buff, WHITESPACE)) != CCLEN) {
340 			sav_errno = EINVAL;
341 			ncount = -1;
342 			break;
343 		}
344 		if ((cmp = strncmp(cp->ctry_code, buff, CCLEN)) == 0) {
345 			/* Get country description, and localized desc. */
346 			/* Skip to country description */
347 			lp = &buff[CCLEN];
348 			if ((len = strspn(lp, WHITESPACE)) == 0) {
349 				sav_errno = EINVAL;
350 				ncount = -1;
351 				break;
352 			}
353 			lp += len;		/* lp points to country desc. */
354 			len = strcspn(lp, NEWLINE);
355 			if ((ptr = calloc(len + 1, 1)) == NULL) {
356 				ncount = -1;
357 				errno = ENOMEM;
358 				break;
359 			}
360 			(void) strncpy(ptr, lp, len);
361 			*(ptr + len) = '\0';
362 			cp->ctry_id_desc = ptr;
363 
364 			/* Get localized country description */
365 			lptr = dgettext(TEXT_DOMAIN, ptr);
366 			if ((ptr = strdup(lptr)) == NULL) {
367 				ncount = -1;
368 				errno = ENOMEM;
369 				break;
370 			}
371 			cp->ctry_display_desc = ptr;
372 		} else if (cmp > 0) {
373 			/* Keep searching country.tab */
374 			continue;
375 		} else {
376 			/* Not found - should not happen */
377 			ncount = -1;
378 			errno = EILSEQ;
379 			break;
380 		}
381 		if (cp->ctry_next == NULL) {
382 			/* done with countries list */
383 			break;
384 		} else {
385 			cp = cp->ctry_next;
386 		}
387 	}		/* while */
388 
389 	/* Now sort the list by ctry_display_desc field */
390 	if ((ncount != -1) &&
391 		((cp2 = calloc(ncount, sizeof (struct tz_country))) != NULL)) {
392 		/*
393 		 * First copy list to a static array for qsort() to use.
394 		 * Use the cnt_next field to point back to original structure.
395 		 */
396 		cp = head;
397 		for (i = 0; i < ncount; i++) {
398 			next = cp->ctry_next;
399 			cp->ctry_next = cp;
400 			(void) memcpy(&cp2[i], cp, sizeof (struct tz_country));
401 			cp = next;
402 		}
403 
404 		/* Next, call qsort() using strcoll() to order */
405 		qsort(cp2, ncount, sizeof (struct tz_country),
406 			(int (*)(const void *, const void *))compar);
407 
408 		/* Rearrange the country list according to qsort order */
409 		head = cp2->ctry_next; /* ctry_next is pointer to orig struct */
410 		cp = head;
411 		for (i = 0; i < ncount; i++) {
412 			prev = cp;
413 			cp = cp2[i].ctry_next;
414 			prev->ctry_next = cp;
415 		}
416 		cp->ctry_next = NULL;
417 
418 		/* Last, free the static buffer */
419 		free(cp2);
420 
421 	} else {
422 		if (ncount != -1)
423 			ncount = -1;
424 	}
425 
426 error:
427 	(void) fclose(fp_zone);
428 	(void) fclose(fp_cc);
429 	if (ncount == -1) {
430 		/* free the linked list */
431 		if (head != NULL)
432 			(void) free_tz_countries(head);
433 		if (sav_errno)
434 			errno = sav_errno;
435 	} else {
436 		*country = head;
437 	}
438 	return (ncount);
439 }
440 
441 /*
442  * get_timezones_by_country() finds the list of timezones from the
443  * zone_sun.tab file, for the input country.
444  */
445 int
446 get_timezones_by_country(struct tz_timezone **tmzone,
447 	struct tz_country *country)
448 {
449 	FILE *fp_zone;		/* zone.tab */
450 	int match = 0, ncount = 0, sav_errno = 0, status;
451 	char buff[1024];
452 	char *lp_cc, *lp_tz, *lp_otz, *lp_coord, *lp_tzdesc, *ptr, *lptr;
453 	size_t len_tz, len_otz, len_coord, len_tzdesc;
454 	struct tz_timezone *head = NULL, *prev = NULL, *tp;
455 
456 	/* open zone.tab file */
457 	if ((fp_zone = fopen(ZONE_SUN_TAB, "r")) == NULL)
458 		return (-1);
459 
460 	/* Read through zone.tab until countries match */
461 	/*CONSTANTCONDITION*/
462 	while (1) {
463 		if (fgets(buff, sizeof (buff), fp_zone) == NULL) {
464 			if (feof(fp_zone)) {
465 				break;
466 			} else {
467 				/* fgets() sets errno */
468 				ncount = -1;
469 				sav_errno = errno;
470 				break;
471 			}
472 		}
473 		/* Skip comments or blank/whitespace lines */
474 		if ((status = skipline(buff)) != 0) {
475 			if (status == 1)
476 				continue;
477 			else {
478 				sav_errno = EINVAL;
479 				ncount = -1;
480 				break;
481 			}
482 		}
483 		/*
484 		 * Find country entries, or detect if no country matches.
485 		 */
486 		lp_cc = skipwhite(&buff[0]);
487 		if (strcspn(lp_cc, WHITESPACE) != CCLEN) {
488 			sav_errno = EINVAL;
489 			ncount = -1;
490 			break;
491 		}
492 		if (strncmp(country->ctry_code, lp_cc, CCLEN) == 0) {
493 			match = 1;
494 
495 			/* Get coordinates */
496 			lp_coord = skipwhite(lp_cc + CCLEN);
497 			if (((len_coord = strcspn(lp_coord, WHITESPACE)) !=
498 					COORD_FMTLEN1) &&
499 					(len_coord != COORD_FMTLEN2)) {
500 				ncount = -1;
501 				sav_errno = EINVAL;
502 				break;
503 			}
504 			/* Get Olson timezone name */
505 			lp_otz = skipwhite(lp_coord + len_coord);
506 			len_otz = strcspn(lp_otz, WHITESPACE);
507 
508 			/* Get Solaris compatible timezone name */
509 			lp_tz = skipwhite(lp_otz + len_otz);
510 			len_tz = strcspn(lp_tz, WHITESPACE_NL);
511 			if (*(lp_tz + len_tz - 1) == '\n') {
512 				/* No timezone description */
513 				len_tz--;
514 				lp_tzdesc = NULL;
515 				len_tzdesc = 0;
516 			} else {
517 				/* Get timezone description */
518 				lp_tzdesc = skipwhite(lp_tz +
519 					len_tz);
520 				len_tzdesc = strcspn(lp_tzdesc,
521 					NEWLINE);
522 			}
523 			/*
524 			 * Check tz name lengths.  This check assumes the
525 			 * tz_oname and tz_name fields are the same size.
526 			 * (since tz_name may be written with lp_otz, if
527 			 * lp_tz is "-".)
528 			 */
529 			if ((len_otz > _TZBUFLEN - 1) ||
530 				(len_tz > _TZBUFLEN - 1)) {
531 				sav_errno = ENAMETOOLONG;
532 				ncount = -1;
533 				break;
534 			}
535 			/* Create timezone struct */
536 			if ((tp =  (struct tz_timezone *)
537 				calloc(1, sizeof (struct tz_timezone))) ==
538 					NULL) {
539 				sav_errno = ENOMEM;
540 				ncount = -1;
541 				break;
542 			}
543 			/*
544 			 * Copy the timezone names - use the Solaris
545 			 * compatible timezone name if one exists,
546 			 * otherwise use the current Olson timezone
547 			 * name.
548 			 */
549 			(void) strncpy(tp->tz_oname, lp_otz, len_otz);
550 			tp->tz_oname[len_otz] = '\0';
551 			if (strncmp("-", lp_tz, len_tz) == 0) {
552 				lp_tz = lp_otz;
553 				len_tz = len_otz;
554 			}
555 			/* If name has numeric digits, prefix ':' */
556 			if (strcspn(lp_tz, DIGITS) < len_tz) {
557 				if (len_tz > _TZBUFLEN - 2) {
558 					free(tp);
559 					sav_errno = ENAMETOOLONG;
560 					ncount = -1;
561 					break;
562 				}
563 				tp->tz_name[0] = ':';
564 				(void) strncpy(tp->tz_name + 1, lp_tz, len_tz);
565 				tp->tz_name[len_tz + 1] = '\0';
566 			} else {
567 				(void) strncpy(tp->tz_name, lp_tz, len_tz);
568 				tp->tz_name[len_tz] = '\0';
569 			}
570 			/* Process timezone description, if one exists */
571 			if ((lp_tzdesc != NULL) && (*lp_tzdesc != '\n')) {
572 				if ((ptr = calloc(1, len_tzdesc + 1))
573 						== NULL) {
574 					sav_errno = ENOMEM;
575 					ncount = -1;
576 					(void) free_timezones(tp);
577 					break;
578 				}
579 				(void) strncpy(ptr, lp_tzdesc, len_tzdesc);
580 				*(ptr + len_tzdesc) = '\0';
581 				tp->tz_id_desc = ptr;
582 
583 				/* Get localized country description */
584 				lptr = dgettext(TEXT_DOMAIN, ptr);
585 				if ((ptr = strdup(lptr)) == NULL) {
586 					sav_errno = ENOMEM;
587 					ncount = -1;
588 					(void) free_timezones(tp);
589 					break;
590 				}
591 				tp->tz_display_desc = ptr;
592 
593 			} else {
594 				tp->tz_id_desc = NULL;
595 				tp->tz_display_desc = NULL;
596 			}
597 			/* Get coordinate information */
598 			if (get_coord(tp, lp_coord, len_coord) == -1) {
599 				sav_errno = EILSEQ;
600 				ncount = -1;
601 				(void) free_timezones(tp);
602 				break;
603 			}
604 			/* Store timezone struct in a linked list */
605 			if (head == NULL) {
606 				head = tp;
607 			} else {
608 				prev->tz_next = tp;
609 			}
610 			prev = tp;
611 			ncount++;
612 		} else {
613 			if (match == 1) {
614 				/*
615 				 * At this point, since zone_sun.tab is ordered,
616 				 * if we've already found timezone entries for
617 				 * the input country, then we've found all of
618 				 * the desired timezone entries (since we will
619 				 * be past that country's section in
620 				 * zone_sun.tab), and we are done.
621 				 */
622 				break;
623 			}
624 		}
625 	}
626 
627 	/* Finish up */
628 	(void) fclose(fp_zone);
629 	if (ncount == -1) {
630 		if (head != NULL)
631 			(void) free_timezones(head);
632 		if (sav_errno)
633 			errno = sav_errno;
634 	} else {
635 		*tmzone = head;
636 	}
637 	return (ncount);
638 }
639 
640 int
641 free_tz_continents(struct tz_continent *cont)
642 {
643 	struct tz_continent *cptr, *cprev;
644 
645 	cptr = cont;
646 	while (cptr != NULL) {
647 		if (cptr->ctnt_id_desc != NULL)
648 			free(cptr->ctnt_id_desc);
649 		if (cptr->ctnt_display_desc != NULL)
650 			free(cptr->ctnt_display_desc);
651 		cprev = cptr;
652 		cptr = cptr->ctnt_next;
653 		free(cprev);
654 	}
655 	return (0);
656 }
657 
658 int
659 free_tz_countries(struct tz_country *country)
660 {
661 	struct tz_country *cptr, *cprev;
662 
663 	cptr = country;
664 	while (cptr != NULL) {
665 		if (cptr->ctry_id_desc != NULL)
666 			free(cptr->ctry_id_desc);
667 		if (cptr->ctry_display_desc != NULL)
668 			free(cptr->ctry_display_desc);
669 		cprev = cptr;
670 		cptr = cptr->ctry_next;
671 		free(cprev);
672 	}
673 	return (0);
674 }
675 
676 int
677 free_timezones(struct tz_timezone *timezone)
678 {
679 	struct tz_timezone *tzptr, *tzprev;
680 
681 	tzptr = timezone;
682 	while (tzptr != NULL) {
683 		if (tzptr->tz_id_desc != NULL)
684 			free(tzptr->tz_id_desc);
685 		if (tzptr->tz_display_desc != NULL)
686 			free(tzptr->tz_display_desc);
687 		tzprev = tzptr;
688 		tzptr = tzptr->tz_next;
689 		free(tzprev);
690 	}
691 	return (0);
692 }
693 
694 /*
695  *  conv_gmt() returns a GMT-offset style timezone
696  *    If flag = 0, return Quoted POSIX timezone like: <GMT+8>+8
697  *    If flag = 1, return zoneinfo timezone like:  :Etc/GMT+8
698  */
699 char *
700 conv_gmt(int seconds, int flag)
701 {
702 	int hour;
703 	char *cp;
704 
705 	if ((seconds < _GMT_MIN) || (seconds > _GMT_MAX)) {
706 		errno = EINVAL;
707 		return (NULL);
708 	}
709 	hour = (seconds / 60) / 60;
710 
711 	if (flag == 0) {
712 		cp = _conv_gmt_posix(hour);
713 	} else if (flag == 1) {
714 		cp = _conv_gmt_zoneinfo(hour);
715 	} else {
716 		errno = EINVAL;
717 		return (NULL);
718 	}
719 	return (cp);
720 }
721 
722 static char *
723 _conv_gmt_posix(int hour)
724 {
725 	char *cp;
726 	char xsign;
727 
728 	if (hour == 0) {
729 		if ((cp = strdup(GMT0_FMT)) == NULL) {
730 			errno = ENOMEM;
731 			return (NULL);
732 		}
733 	} else {
734 		if (hour < 0) {
735 			xsign = '-';
736 			/* make hour positive for snprintf() */
737 			hour = -hour;
738 		} else {
739 			xsign = '+';
740 		}
741 		if ((cp = malloc(GMT_FMT_Q_LEN + 1)) == NULL) {
742 			errno = ENOMEM;
743 			return (NULL);
744 		}
745 		(void) snprintf(cp, GMT_FMT_Q_LEN + 1, GMT_FMT_Q,
746 			xsign, hour, xsign, hour);
747 	}
748 	return (cp);
749 }
750 
751 static char *
752 _conv_gmt_zoneinfo(int hour)
753 {
754 	char *cp;
755 	char xsign;
756 
757 	if (hour < 0) {
758 		xsign = '-';
759 		/* make hour positive for snprintf() */
760 		hour = -hour;
761 	} else {
762 		xsign = '+';
763 	}
764 	if ((cp = malloc(GMT_FMT_ZONE_LEN + 1)) == NULL) {
765 		errno = ENOMEM;
766 		return (NULL);
767 	}
768 	(void) snprintf(cp, GMT_FMT_ZONE_LEN + 1, GMT_FMT_ZONE,
769 		xsign, hour);
770 	return (cp);
771 }
772 
773 /* Regular expression for POSIX GMT-offset timezone */
774 #define	_GMT_EXPR	"(" _GMT_EXPR_U "|" _GMT_EXPR_Q ")"
775 #define	_GMT_EXPR_U	"^[gG][mM][tT][-+]?[0-2]?[0-9]$"
776 #define	_GMT_EXPR_Q	"^<[gG][mM][tT][-+]?[0-2]?[0-9]>[-+]?[0-2]?[0-9]$"
777 
778 /*
779  * Regular expression for quoted POSIX timezone.
780  */
781 /* Avoid alphabetic ranges (eg, a-z) due to effect of LC_COLLATE */
782 #define	_ALPHA	"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
783 #define	_NUM	"0123456789"    /* for safe */
784 #define	_STD_Q_ELM	"[-+" _ALPHA _NUM "]"
785 #define	_STD_Q		"<" _STD_Q_ELM _STD_Q_ELM _STD_Q_ELM "+>"
786 
787 /* Regular expression for unquoted POSIX timezone */
788 #define	_STD_U_ELM_1	"[^-+,<" _NUM "]"
789 #define	_STD_U_ELM	"[^-+,"  _NUM "]"
790 #define	_STD_U		_STD_U_ELM_1 _STD_U_ELM _STD_U_ELM "+"
791 
792 /* Regular expression for POSIX timezone */
793 #define	_STD		"(" _STD_U "|" _STD_Q ")"
794 #define	_DST		_STD
795 #define	_OFFSET		"[-+]?" _TIME
796 #define	_START		"(" _DATEJ "|" _DATEn "|" _DATEM ")"
797 #define	_DATEJ		"J(([0-2]?[0-9]?[0-9])|3[0-5][0-9]|36[0-5])"
798 #define	_DATEn		"(([0-2]?[0-9]?[0-9])|3[0-5][0-9]|36[0-5])"
799 #define	_DATEM		"M([0-9]|10|11|12)\\.[1-5]\\.[0-6]"
800 #define	_END		_START
801 #define	_TIME		_HH "(:" _MM "(:" _SS ")?" ")?"
802 #define	_HH		"(([0-1]?[0-9])|20|21|22|23|24)"
803 #define	_MM		"[0-5]?[0-9]"
804 #define	_SS		_MM
805 #define	_POSIX_EXPR	"^" _STD _OFFSET "(" _DST "(" _OFFSET ")?" \
806 				"(," _START "(/" _TIME ")?" \
807 				"," _END "(/" _TIME ")?" ")?" ")?" "$"
808 
809 #define	LEN_TZDIR	(sizeof (TZDIR) - 1)
810 
811 /*
812  *  isvalid_tz() checks if timezone is a valid POSIX or zoneinfo
813  *  timezone, depending on the value of flag.  For flag = _VTZ_INSTALL,
814  *  isvalid_tz() behaves according to the behavior of Solaris Install
815  *  in Solaris 9 and earlier, where timezones under /usr/share/lib/zoneinfo
816  *  were validated.  isvalid_tz() has a special check for GMT+-* timezones
817  *  because Solaris Install validated /usr/share/lib/zoneinfo/GMT+-*.
818  *  However, when /usr/share/lib/zoneinfo/GMT+-* are EOF'd, that check
819  *  no longer works.
820  *
821  *  isvalid_tz() returns 1 if a valid timezone is detected.
822  */
823 int
824 isvalid_tz(char *timezone, char *root, int flag)
825 {
826 	char path[MAXPATHLEN];
827 	char buf[sizeof (struct tzhead)];
828 	int fid, ret;
829 
830 	if ((timezone == NULL) || (*timezone == '\0')) {
831 		return (0);
832 	}
833 
834 	/* First check if timezone is a valid POSIX timezone */
835 	switch (flag) {
836 	case _VTZ_INSTALL:
837 		/*
838 		 * Special check for POSIX GMT timezone.
839 		 * If no match, check for zoneinfo timezone below
840 		 */
841 		if (_tz_match(_GMT_EXPR, timezone) == 0) {
842 			/* Valid GMT timezone */
843 			return (1);
844 		}
845 		break;
846 	case _VTZ_POSIX:
847 		/* Check for generic POSIX timezone */
848 		if (_tz_match(_POSIX_EXPR, timezone) == 0) {
849 			/* Valid POSIX timezone */
850 			return (1);
851 		}
852 		/* Invalid POSIX timezone */
853 		return (0);
854 	case _VTZ_ALL:
855 		/* Check for generic POSIX timezone */
856 		if (_tz_match(_POSIX_EXPR, timezone) == 0) {
857 			/* Valid POSIX timezone */
858 			return (1);
859 		}
860 		break;
861 	case _VTZ_ZONEINFO:
862 		break;
863 	default:
864 		return (0);
865 	}
866 
867 	/*
868 	 * Check for valid zoneinfo timezone -
869 	 * open zoneinfo file and check for magic number
870 	 */
871 
872 	/* skip prepended ':' if one exists */
873 	if (*timezone == ':') {
874 		timezone++;
875 	}
876 	/* Construct full zoneinfo pathname */
877 	if ((root != NULL) && (*root != '\0')) {
878 		ret = snprintf(path, sizeof (path),
879 		    "%s%s/%s", root, TZDIR, timezone);
880 		if (ret >= sizeof (path)) {
881 			/* too long */
882 			return (0);
883 		}
884 	} else {
885 		ret = snprintf(path, sizeof (path),
886 		    "%s/%s", TZDIR, timezone);
887 		if (ret >= sizeof (path)) {
888 			/* too long */
889 			return (0);
890 		}
891 	}
892 	if ((fid = open(path, O_RDONLY)) == -1) {
893 		return (0);
894 	}
895 	if (read(fid, buf, sizeof (struct tzhead)) !=
896 	    sizeof (struct tzhead)) {
897 		(void) close(fid);
898 		return (0);
899 	}
900 	if (strncmp(buf, TZ_MAGIC, sizeof (TZ_MAGIC) - 1) != 0) {
901 		(void) close(fid);
902 		return (0);
903 	}
904 	if (close(fid) == -1) {
905 		return (0);
906 	}
907 	/* Valid zoneinfo timezone */
908 	return (1);
909 }
910 
911 #define	N_MATCH		1
912 
913 int
914 _tz_match(const char *expr, const char *string)
915 {
916 	regex_t reg;
917 	regmatch_t pmatch[N_MATCH];
918 	int ret;
919 
920 	ret = regcomp(&reg, expr, REG_EXTENDED);
921 	if (ret != 0) {
922 		return (-1);
923 	}
924 
925 	ret = regexec((const regex_t *)&reg, string, N_MATCH, pmatch, 0);
926 	if (ret == 0) {
927 #ifdef DEBUG
928 		printf("OK matched - %s\n", string);
929 #endif
930 		regfree(&reg);
931 		return (0);
932 	}
933 #ifdef DEBUG
934 	printf("NOT matched - %s\n", string);
935 #endif
936 	regfree(&reg);
937 	return (-1);
938 }
939 
940 char *
941 get_system_tz(char *root)
942 {
943 	FILE *ifp;
944 	char buff[512];
945 	int serrno, ret;
946 	char *sp, *ptr, *p;
947 	char fname[MAXPATHLEN];
948 
949 	if ((ret = snprintf(fname, sizeof (fname), "%s/%s", root, DEFINIT)) >=
950 			sizeof (fname)) {
951 		errno = ENAMETOOLONG;
952 		return (NULL);
953 	} else if (ret < 0) {
954 		return (NULL);
955 	}
956 	if ((ifp = fopen(fname, "r")) == NULL)
957 		return (NULL);
958 	while (fgets(buff, sizeof (buff), ifp) != NULL) {
959 		if (strncmp(buff, "TZ=", 3) == 0) {
960 			(void) fclose(ifp);
961 			p = &buff[3];
962 			if ((sp = strchr(p, ';')) != NULL) {
963 				*sp = '\0';
964 			} else if ((sp = strchr(p, '\n')) != NULL) {
965 				*sp = '\0';
966 			}
967 			if (strpbrk(p, "\"'") != NULL) {
968 				strip_quotes(p, p);
969 			}
970 			ptr = strdup(p);
971 			if (ptr == NULL) {
972 				errno = ENOMEM;
973 				return (NULL);
974 			}
975 			return (ptr);
976 		}
977 	}
978 
979 	/* Either reached EOF with no TZ= entry, or got fgets() error */
980 	serrno = errno;
981 	if (feof(ifp) != 0) {
982 		/* No "TZ=" entry found */
983 		serrno = EINVAL;
984 	}
985 	(void) fclose(ifp);
986 	errno = serrno;
987 	return (NULL);
988 }
989 
990 int
991 set_system_tz(char *tz, char *root)
992 {
993 	FILE *ifp, *ofp;	/* Input & output files */
994 	char *tmpdir, *tmp;	/* Temp file name and location */
995 	char buff[1024];
996 	int replaced = 0, ret, serrno;
997 	char *tdb;
998 	struct stat sb;
999 	char fname[MAXPATHLEN];
1000 	const char *tzfmt;
1001 	int len, fd;
1002 
1003 	if (tz == NULL || root == NULL)
1004 		return (-1);
1005 
1006 	if (strchr(tz, '<')) {
1007 		tzfmt = TZ_FMT_Q;
1008 	} else {
1009 		tzfmt = TZ_FMT;
1010 	}
1011 
1012 	if ((ret = snprintf(fname, sizeof (fname), "%s/%s", root, DEFINIT)) >=
1013 			sizeof (fname)) {
1014 		errno = ENAMETOOLONG;
1015 		return (-1);
1016 	} else if (ret < 0) {
1017 		return (-1);
1018 	}
1019 
1020 	/*
1021 	 * Generate temporary file name to use.  We make sure it's in the same
1022 	 * directory as the db we're processing so that we can use rename to
1023 	 * do the replace later.  Otherwise we run the risk of being on the
1024 	 * wrong filesystem and having rename() fail for that reason.
1025 	 */
1026 	tdb = fname;
1027 	if (trav_link(&tdb) == -1)
1028 		return (-1);
1029 	if ((tmpdir = strdup(tdb)) == NULL) {
1030 		errno = ENOMEM;
1031 		return (-1);
1032 	}
1033 	remove_component(tmpdir);
1034 	if ((len = strlen(tmpdir)) == 0) {
1035 		(void) strcpy(tmpdir, ".");
1036 		len = 1;
1037 	}
1038 
1039 	if ((tmp = malloc(len + TR_LEN + 1)) == NULL) {
1040 		free(tmpdir);
1041 		errno = ENOMEM;
1042 		return (-1);
1043 	}
1044 	(void) strcpy(tmp, tmpdir);
1045 	(void) strcpy(tmp + len, TRAILER);
1046 	free(tmpdir);
1047 	if ((fd = mkstemp(tmp)) == -1) {
1048 		free(tmp);
1049 		return (-1);
1050 	}
1051 	if ((ofp = fdopen(fd, "w")) == NULL) {
1052 		serrno = errno;
1053 		(void) close(fd);
1054 		free(tmp);
1055 		errno = serrno;
1056 		return (-1);
1057 	}
1058 
1059 	/* Preserve permissions of current file if it exists */
1060 	if (stat(tdb, &sb) == 0) {
1061 		if (fchmod(fileno(ofp), sb.st_mode) == -1) {
1062 			serrno = errno;
1063 			(void) fclose(ofp);
1064 			(void) unlink(tmp);
1065 			free(tmp);
1066 			errno = serrno;
1067 			return (-1);
1068 		}
1069 		if (fchown(fileno(ofp), sb.st_uid, sb.st_gid) == -1) {
1070 			serrno = errno;
1071 			(void) fclose(ofp);
1072 			(void) unlink(tmp);
1073 			free(tmp);
1074 			errno = serrno;
1075 			return (-1);
1076 		}
1077 	} else if (errno != ENOENT) {
1078 		serrno = errno;
1079 		(void) fclose(ofp);
1080 		(void) unlink(tmp);
1081 		free(tmp);
1082 		errno = serrno;
1083 		return (-1);
1084 	}
1085 
1086 	if ((ifp = fopen(fname, "r+")) != NULL) {
1087 		while (fgets(buff, sizeof (buff), ifp) != NULL) {
1088 			if (!replaced && (strncmp(buff, "TZ=", 3) == 0)) {
1089 				ret = snprintf(buff, sizeof (buff), tzfmt,
1090 							tz);
1091 				if ((ret >= sizeof (buff)) || (ret < 0)) {
1092 					if (ret >= sizeof (buff))
1093 						serrno = EINVAL;
1094 					(void) fclose(ofp);
1095 					(void) fclose(ifp);
1096 					(void) unlink(tmp);
1097 					free(tmp);
1098 					errno = serrno;
1099 					return (-1);
1100 				}
1101 				replaced = 1;
1102 			}
1103 			if (fputs(buff, ofp) == EOF) {
1104 				serrno = errno;
1105 				(void) fclose(ofp);
1106 				(void) fclose(ifp);
1107 				(void) unlink(tmp);
1108 				free(tmp);
1109 				errno = serrno;
1110 				return (-1);
1111 			}
1112 		}
1113 		(void) fclose(ifp);
1114 
1115 	} else if (errno != ENOENT) {
1116 		serrno = errno;
1117 		(void) fclose(ofp);
1118 		(void) unlink(tmp);
1119 		free(tmp);
1120 		errno = serrno;
1121 		return (-1);
1122 	}
1123 
1124 	/*
1125 	 * no $(ROOT)/etc/default/init found, or
1126 	 * no "TZ=" entry found in the init file.
1127 	 */
1128 	if (!replaced &&
1129 	    (fprintf(ofp, tzfmt, tz) == EOF)) {
1130 		serrno = errno;
1131 		(void) fclose(ofp);
1132 		(void) unlink(tmp);
1133 		free(tmp);
1134 		errno = serrno;
1135 		return (-1);
1136 	}
1137 
1138 	if (fsync(fileno(ofp))) {
1139 		serrno = errno;
1140 		(void) unlink(tmp);
1141 		free(tmp);
1142 		errno = serrno;
1143 		return (-1);
1144 	}
1145 
1146 	(void) fclose(ofp);
1147 	if (rename(tmp, tdb) != 0) {
1148 		serrno = errno;
1149 		(void) unlink(tmp);
1150 		free(tmp);
1151 		errno = serrno;
1152 		return (-1);
1153 	} else {
1154 		free(tmp);
1155 		return (0);
1156 	}
1157 }
1158 
1159 /*
1160  * Function to traverse a symlink path to find the real file at the end of
1161  * the rainbow.
1162  */
1163 int
1164 trav_link(char **path)
1165 {
1166 	static char newpath[MAXPATHLEN];
1167 	char lastpath[MAXPATHLEN];
1168 	int len, ret;
1169 	char *tp;
1170 
1171 	(void) strcpy(lastpath, *path);
1172 	while ((len = readlink(*path, newpath, sizeof (newpath))) != -1) {
1173 		newpath[len] = '\0';
1174 		if (newpath[0] != '/') {
1175 			if ((tp = strdup(newpath)) == NULL) {
1176 				errno = ENOMEM;
1177 				return (-1);
1178 			}
1179 			remove_component(lastpath);
1180 			ret = snprintf(newpath, sizeof (newpath),
1181 				"%s/%s", lastpath, tp);
1182 			free(tp);
1183 			if ((ret >= sizeof (newpath)) || (ret < 0))
1184 				return (-1);
1185 		}
1186 		(void) strcpy(lastpath, newpath);
1187 		*path = newpath;
1188 	}
1189 
1190 	/*
1191 	 * ENOENT or EINVAL is the normal exit case of the above loop.
1192 	 */
1193 	if ((errno == ENOENT) || (errno == EINVAL))
1194 		return (0);
1195 	else
1196 		return (-1);
1197 }
1198 
1199 void
1200 remove_component(char *path)
1201 {
1202 	char *p;
1203 
1204 	p = strrchr(path, '/'); 		/* find last '/' 	*/
1205 	if (p == NULL) {
1206 		*path = '\0';			/* set path to null str	*/
1207 	} else {
1208 		*p = '\0';			/* zap it 		*/
1209 	}
1210 }
1211 
1212 /*
1213  *  get_coord() fills in the tz_coord structure of the tz_timezone
1214  *  struct.  It returns 0 on success, or -1 on error.
1215  *  The format of p_coord is:
1216  *
1217  *	Latitude and longitude of the zone's principal location
1218  *	in ISO 6709 sign-degrees-minutes-seconds format,
1219  *	either +-DDMM+-DDDMM or +-DDMMSS+-DDDMMSS,
1220  *	first latitude (+ is north), then longitude (+ is east).
1221  */
1222 static int
1223 get_coord(struct tz_timezone *tp, char *p_coord, size_t len_coord)
1224 {
1225 	int i, fmt_flag, nchar;
1226 	int *signp, *degp, *minp, *secp;
1227 	struct tz_coord *tcp;
1228 	char buff[512], *endp;
1229 
1230 	tcp = &(tp->tz_coord);
1231 
1232 	/* Figure out which format to use */
1233 	if (len_coord == COORD_FMTLEN1) {
1234 		/* "+-DDMM+-DDDMM" */
1235 		fmt_flag = COORD_FMT1;
1236 	} else if (len_coord == COORD_FMTLEN2) {
1237 		/* "+-DDMMSS+-DDDMMSS" */
1238 		fmt_flag = COORD_FMT2;
1239 	} else {
1240 		/* error */
1241 		return (-1);
1242 	}
1243 	/*
1244 	 * First time through, get values for latitude;
1245 	 * second time through, get values for longitude.
1246 	 */
1247 	for (i = 0; i < 2; i++) {
1248 		/* Set up pointers */
1249 		if (i == 0) {
1250 			/* Do latitude */
1251 			nchar = COORD_DLEN_LAT;
1252 			signp = (int *)&(tcp->lat_sign);
1253 			degp = (int *)&(tcp->lat_degree);
1254 			minp = (int *)&(tcp->lat_minute);
1255 			secp = (int *)&(tcp->lat_second);
1256 		} else {
1257 			/* Do longitude */
1258 			nchar = COORD_DLEN_LONG;
1259 			signp = (int *)&(tcp->long_sign);
1260 			degp = (int *)&tcp->long_degree;
1261 			minp = (int *)&tcp->long_minute;
1262 			secp = (int *)&tcp->long_second;
1263 		}
1264 		/* Get latitude/logitude sign */
1265 		if (*p_coord == '+') {
1266 			*signp = 1;
1267 		} else if (*p_coord == '-') {
1268 			*signp = -1;
1269 		} else {
1270 			return (-1);
1271 		}
1272 		p_coord++;
1273 
1274 		/* Get DD latitude, or DDD longitude */
1275 		(void) strncpy(buff, p_coord, nchar);
1276 		buff[nchar] = '\0';
1277 		errno = 0;
1278 		*degp = (int)strtol(buff, &endp, 10);
1279 		if ((endp != &buff[nchar]) || ((*degp == 0) && (errno != 0)))
1280 			return (-1);
1281 		p_coord += nchar;
1282 
1283 		/* Get MM latitude/longitude */
1284 		(void) strncpy(buff, p_coord, COORD_MLEN);
1285 		buff[COORD_MLEN] = '\0';
1286 		errno = 0;
1287 		*minp = (int)strtol(buff, &endp, 10);
1288 		if ((endp != &buff[COORD_MLEN]) ||
1289 				((*degp == 0) && (errno != 0)))
1290 			return (-1);
1291 		p_coord += COORD_MLEN;
1292 
1293 		/* If FMT2, then get SS latitude/longitude */
1294 		if (fmt_flag == COORD_FMT2) {
1295 			(void) strncpy(buff, p_coord, COORD_SLEN);
1296 			buff[COORD_SLEN] = '\0';
1297 			errno = 0;
1298 			*secp = (int)strtol(buff, &endp, 10);
1299 			if ((endp != &buff[COORD_SLEN]) ||
1300 					((*degp == 0) && (errno != 0)))
1301 				return (-1);
1302 			p_coord += COORD_SLEN;
1303 		} else {
1304 			*secp = 0;
1305 		}
1306 	}
1307 	return (0);
1308 }
1309 
1310 static char *
1311 skipwhite(char *cp)
1312 {
1313 	while (*cp && ((*cp == ' ') || (*cp == '\t'))) {
1314 		cp++;
1315 	}
1316 
1317 	return (cp);
1318 }
1319 
1320 /*
1321  *  skipline() checks if the line begins with a comment
1322  *  comment character anywhere in the line, or if the
1323  *  line is only whitespace.
1324  *  skipline() also checks if the line read is too long to
1325  *  fit in the buffer.
1326  *  skipline() returns 1 if the line can be skipped, -1 if
1327  *  the line read is too long, and 0 if the line should not be skipped.
1328  */
1329 static int
1330 skipline(char *line)
1331 {
1332 	size_t len;
1333 
1334 	len = strlen(line);
1335 	if (line[len - 1] != '\n')
1336 		return (-1);
1337 	if (line[0] == '#' || line[0] == '\0' ||
1338 		(len = strspn(line, " \t\n")) == strlen(line) ||
1339 		strchr(line, '#') == line + len)
1340 
1341 		return (1);
1342 	else
1343 		return (0);
1344 }
1345 
1346 /*
1347  * strip_quotes -- strip double (") or single (') quotes
1348  */
1349 static void
1350 strip_quotes(char *from, char *to)
1351 {
1352 	char *strip_ptr = NULL;
1353 
1354 	while (*from != '\0') {
1355 		if ((*from == '"') || (*from == '\'')) {
1356 			if (strip_ptr == NULL)
1357 				strip_ptr = to;
1358 		} else {
1359 			if (strip_ptr != NULL) {
1360 				*strip_ptr = *from;
1361 				strip_ptr++;
1362 			} else {
1363 				*to = *from;
1364 				to++;
1365 			}
1366 		}
1367 		from++;
1368 	}
1369 	if (strip_ptr != NULL) {
1370 		*strip_ptr = '\0';
1371 	} else {
1372 		*to = '\0';
1373 	}
1374 }
1375 
1376 /*
1377  * Compare function used by get_tz_countries() - uses strcoll()
1378  * for locale-sensitive comparison for the localized country names.
1379  */
1380 static int
1381 compar(struct tz_country *p1, struct tz_country *p2)
1382 {
1383 	int ret;
1384 
1385 	ret = strcoll(p1->ctry_display_desc, p2->ctry_display_desc);
1386 	return (ret);
1387 }
1388