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