/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License, Version 1.0 only * (the "License"). You may not use this file except in compliance * with the License. * * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE * or http://www.opensolaris.org/os/licensing. * See the License for the specific language governing permissions * and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at usr/src/OPENSOLARIS.LICENSE. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" #include #include #include #include #include #include #include #include #include #include #include #include #include #define DEFINIT "/etc/default/init" #define ZONEINFOTABDIR "/usr/share/lib/zoneinfo/tab/" #define CONTINENT_TAB ZONEINFOTABDIR "continent.tab" #define COUNTRY_TAB ZONEINFOTABDIR "country.tab" #define ZONE_SUN_TAB ZONEINFOTABDIR "zone_sun.tab" #define NEWLINE "\n" #define SLASH "/" #define WHITESPACE "\t " #define WHITESPACE_NL "\t \n" #define DIGITS "0123456789" #define BUFFLEN 1024 #define CCLEN 2 /* country code length */ #define GMT_MAX (12*60*60) /* The maximum GMT offset */ #define GMT_MIN (-13*60*60) /* The minimum GMT offset */ #define GMT_FMT_Q "%c%d" #define GMT_FMT_Q_LEN (11) /* "+dd" - maximum 11 chars */ #define GMT0_FMT "GMT0" /* backwards compatibility name */ #define GMT_FMT_ZONE ":Etc/GMT%c%d" /* ":Etc/GMT+dd" */ #define GMT_FMT_ZONE_LEN (11) /* ":Etc/GMT+dd" - maximum 11 chars */ #define TZ_FMT "TZ=%s\n" /* format TZ entry init file */ #define TZ_FMT_Q "TZ=\"%s\"\n" /* format quoted TZ entry init file */ #define COORD_FMTLEN1 (sizeof ("+DDMM+DDDMM") - 1) #define COORD_FMTLEN2 (sizeof ("+DDMMSS+DDDMMSS") - 1) #define COORD_FMT1 (1) /* flag for format 1 */ #define COORD_FMT2 (2) /* flag for format 2 */ #define COORD_DLEN_LAT (2) /* length of DD for latitude */ #define COORD_DLEN_LONG (3) /* length of DDD for longtitude */ #define COORD_MLEN (2) /* length of MM */ #define COORD_SLEN (2) /* length of SS */ #define TRAILER "/XXXXXX" #define TR_LEN (sizeof (TRAILER) -1) /* Internal Declarations */ static char *skipwhite(char *); static int skipline(char *); static int trav_link(char **); static void remove_component(char *); static void strip_quotes(char *, char *); static int compar(struct tz_country *, struct tz_country *); static int get_coord(struct tz_timezone *, char *, size_t); static int _tz_match(const char *, const char *); static char *_conv_gmt_zoneinfo(int); static char *_conv_gmt_posix(int); /* * get_tz_continents() reads the continent.tab file, and * returns a list of continents. */ int get_tz_continents(struct tz_continent **cont) { FILE *fp; char buff[BUFFLEN]; char *lp; /* line pointer */ char *lptr, *ptr; /* temp pointer */ struct tz_continent *head = NULL, *lcp, *prev = NULL; int sav_errno = 0, ncount, status; size_t len; /* open continents file */ if ((fp = fopen(CONTINENT_TAB, "r")) == NULL) { /* fopen() sets errno */ return (-1); } /* read and count continents */ ncount = 0; /*CONSTANTCONDITION*/ while (1) { if (fgets(buff, sizeof (buff), fp) == NULL) { if (feof(fp) == 0) { /* fgets() sets errno */ sav_errno = errno; ncount = -1; } break; } /* Skip comments or blank/whitespace lines */ if ((status = skipline(buff)) != 0) { if (status == 1) continue; else { sav_errno = EINVAL; ncount = -1; break; } } /* Get continent name */ lp = skipwhite(&buff[0]); if ((len = strcspn(lp, WHITESPACE)) > _TZBUFLEN -1) { sav_errno = ENAMETOOLONG; ncount = -1; break; } /* create continent struct */ if ((lcp = (struct tz_continent *) calloc(1, sizeof (struct tz_continent))) == NULL) { sav_errno = ENOMEM; ncount = -1; break; } (void) strncpy(lcp->ctnt_name, lp, len); lcp->ctnt_name[len] = '\0'; /* Get continent description */ lp = skipwhite(lp + len); len = strcspn(lp, NEWLINE); if ((ptr = malloc(len + 1)) == NULL) { (void) free_tz_continents(lcp); sav_errno = ENOMEM; ncount = -1; break; } (void) strncpy(ptr, lp, len); *(ptr + len) = '\0'; lcp->ctnt_id_desc = ptr; /* Get localized continent description */ lptr = dgettext(TEXT_DOMAIN, lcp->ctnt_id_desc); if ((ptr = strdup(lptr)) == NULL) { (void) free_tz_continents(lcp); sav_errno = ENOMEM; ncount = -1; break; } lcp->ctnt_display_desc = ptr; if (head == NULL) { head = lcp; } else { prev->ctnt_next = lcp; } prev = lcp; ncount++; } (void) fclose(fp); if (ncount == -1) { if (head != NULL) { (void) free_tz_continents(head); } if (sav_errno) errno = sav_errno; } else { *cont = head; } return (ncount); } /* * get_tz_countries() finds the list of countries from the zone_sun.tab * file, for the input continent, and retrieves the country * names from the country.tab file. It also retrieves the localized * country names. The returned list of countries is sorted by the * countries' localized name fields. */ int get_tz_countries(struct tz_country **country, struct tz_continent *cont) { FILE *fp_zone, *fp_cc; char buff[BUFFLEN], ccbuf[_CCBUFLEN], *ptr; char *lp, *lptr, *lp_coord, *lp_cc, *lp_tz; /* line pointer */ struct tz_country *head = NULL, *prev = NULL, *next, *cp, *cp2; int sav_errno = 0, ncount, i; int cmp, status; size_t len, len_coord, len_ctnt; if (cont->ctnt_name == NULL) { errno = EINVAL; return (-1); } len_ctnt = strlen(cont->ctnt_name); ccbuf[0] = '\0'; /* open zone_sun.tab and country.tab files */ if ((fp_zone = fopen(ZONE_SUN_TAB, "r")) == NULL) { /* fopen() sets errno */ return (-1); } if ((fp_cc = fopen(COUNTRY_TAB, "r")) == NULL) { /* fopen() sets errno */ (void) fclose(fp_zone); return (-1); } /* read timezones to match continents, and get countries */ ncount = 0; /*CONSTANTCONDITION*/ while (1) { if (fgets(buff, sizeof (buff), fp_zone) == NULL) { if (feof(fp_zone) == 0) { /* fgets() error - errno set */ sav_errno = errno; ncount = -1; } break; } /* Skip comments or blank/whitespace lines */ if ((status = skipline(buff)) != 0) { if (status == 1) continue; else { sav_errno = EINVAL; ncount = -1; break; } } /* * If country matches previously *matched* country, skip * entry, since zone.tab is alphabetized by country code * (It should be a *matched* country, because the same country * can be in different continents.) */ /* Get country code */ lp_cc = skipwhite(&buff[0]); if (strcspn(lp_cc, WHITESPACE) != CCLEN) { ncount = -1; sav_errno = EINVAL; break; } /* Check country code cache; skip if already found */ if (strncmp(ccbuf, lp_cc, CCLEN) == 0) { continue; } /* Get coordinates */ lp_coord = skipwhite(lp_cc + CCLEN); if (((len_coord = strcspn(lp_coord, WHITESPACE)) != COORD_FMTLEN1) && (len_coord != COORD_FMTLEN2)) { ncount = -1; sav_errno = EINVAL; break; } /* Get timezone name (Skip timezone description) */ lp_tz = skipwhite(lp_coord + len_coord); if ((len = strcspn(lp_tz, SLASH)) == 0) { ncount = -1; sav_errno = EINVAL; break; } /* If continents match, allocate a country struct */ if ((len == len_ctnt) && (strncmp(cont->ctnt_name, lp_tz, len) == 0)) { if ((cp = (struct tz_country *) calloc(1, sizeof (struct tz_country))) == NULL) { sav_errno = ENOMEM; ncount = -1; break; } /* Copy and save country code (len already checked) */ (void) strncpy(cp->ctry_code, lp_cc, CCLEN); cp->ctry_code[CCLEN] = '\0'; (void) strncpy(ccbuf, lp_cc, CCLEN); ccbuf[CCLEN] = '\0'; /* Create linked list */ if (head == NULL) { head = cp; } else { prev->ctry_next = cp; }; prev = cp; ncount++; } } /* while */ if (ncount == -1) goto error; /* Get country name from country.tab; get localized country name */ /* Read country list, match country codes to process entry */ cp = head; /*CONSTANTCONDITION*/ while (1) { if (fgets(buff, sizeof (buff), fp_cc) == NULL) { if (feof(fp_cc) == 0) { /* fgets() sets errno */ ncount = -1; sav_errno = errno; } break; } /* Skip comments or blank/whitespace lines */ if ((status = skipline(buff)) != 0) { if (status == 1) continue; else { sav_errno = EINVAL; ncount = -1; break; } } /* Match country codes */ if ((len = strcspn(buff, WHITESPACE)) != CCLEN) { sav_errno = EINVAL; ncount = -1; break; } if ((cmp = strncmp(cp->ctry_code, buff, CCLEN)) == 0) { /* Get country description, and localized desc. */ /* Skip to country description */ lp = &buff[CCLEN]; if ((len = strspn(lp, WHITESPACE)) == 0) { sav_errno = EINVAL; ncount = -1; break; } lp += len; /* lp points to country desc. */ len = strcspn(lp, NEWLINE); if ((ptr = calloc(len + 1, 1)) == NULL) { ncount = -1; errno = ENOMEM; break; } (void) strncpy(ptr, lp, len); *(ptr + len) = '\0'; cp->ctry_id_desc = ptr; /* Get localized country description */ lptr = dgettext(TEXT_DOMAIN, ptr); if ((ptr = strdup(lptr)) == NULL) { ncount = -1; errno = ENOMEM; break; } cp->ctry_display_desc = ptr; } else if (cmp > 0) { /* Keep searching country.tab */ continue; } else { /* Not found - should not happen */ ncount = -1; errno = EILSEQ; break; } if (cp->ctry_next == NULL) { /* done with countries list */ break; } else { cp = cp->ctry_next; } } /* while */ /* Now sort the list by ctry_display_desc field */ if ((ncount != -1) && ((cp2 = calloc(ncount, sizeof (struct tz_country))) != NULL)) { /* * First copy list to a static array for qsort() to use. * Use the cnt_next field to point back to original structure. */ cp = head; for (i = 0; i < ncount; i++) { next = cp->ctry_next; cp->ctry_next = cp; (void) memcpy(&cp2[i], cp, sizeof (struct tz_country)); cp = next; } /* Next, call qsort() using strcoll() to order */ qsort(cp2, ncount, sizeof (struct tz_country), (int (*)(const void *, const void *))compar); /* Rearrange the country list according to qsort order */ head = cp2->ctry_next; /* ctry_next is pointer to orig struct */ cp = head; for (i = 0; i < ncount; i++) { prev = cp; cp = cp2[i].ctry_next; prev->ctry_next = cp; } cp->ctry_next = NULL; /* Last, free the static buffer */ free(cp2); } else { if (ncount != -1) ncount = -1; } error: (void) fclose(fp_zone); (void) fclose(fp_cc); if (ncount == -1) { /* free the linked list */ if (head != NULL) (void) free_tz_countries(head); if (sav_errno) errno = sav_errno; } else { *country = head; } return (ncount); } /* * get_timezones_by_country() finds the list of timezones from the * zone_sun.tab file, for the input country. */ int get_timezones_by_country(struct tz_timezone **tmzone, struct tz_country *country) { FILE *fp_zone; /* zone.tab */ int match = 0, ncount = 0, sav_errno = 0, status; char buff[1024]; char *lp_cc, *lp_tz, *lp_otz, *lp_coord, *lp_tzdesc, *ptr, *lptr; size_t len_tz, len_otz, len_coord, len_tzdesc; struct tz_timezone *head = NULL, *prev = NULL, *tp; /* open zone.tab file */ if ((fp_zone = fopen(ZONE_SUN_TAB, "r")) == NULL) return (-1); /* Read through zone.tab until countries match */ /*CONSTANTCONDITION*/ while (1) { if (fgets(buff, sizeof (buff), fp_zone) == NULL) { if (feof(fp_zone)) { break; } else { /* fgets() sets errno */ ncount = -1; sav_errno = errno; break; } } /* Skip comments or blank/whitespace lines */ if ((status = skipline(buff)) != 0) { if (status == 1) continue; else { sav_errno = EINVAL; ncount = -1; break; } } /* * Find country entries, or detect if no country matches. */ lp_cc = skipwhite(&buff[0]); if (strcspn(lp_cc, WHITESPACE) != CCLEN) { sav_errno = EINVAL; ncount = -1; break; } if (strncmp(country->ctry_code, lp_cc, CCLEN) == 0) { match = 1; /* Get coordinates */ lp_coord = skipwhite(lp_cc + CCLEN); if (((len_coord = strcspn(lp_coord, WHITESPACE)) != COORD_FMTLEN1) && (len_coord != COORD_FMTLEN2)) { ncount = -1; sav_errno = EINVAL; break; } /* Get Olson timezone name */ lp_otz = skipwhite(lp_coord + len_coord); len_otz = strcspn(lp_otz, WHITESPACE); /* Get Solaris compatible timezone name */ lp_tz = skipwhite(lp_otz + len_otz); len_tz = strcspn(lp_tz, WHITESPACE_NL); if (*(lp_tz + len_tz - 1) == '\n') { /* No timezone description */ len_tz--; lp_tzdesc = NULL; len_tzdesc = 0; } else { /* Get timezone description */ lp_tzdesc = skipwhite(lp_tz + len_tz); len_tzdesc = strcspn(lp_tzdesc, NEWLINE); } /* * Check tz name lengths. This check assumes the * tz_oname and tz_name fields are the same size. * (since tz_name may be written with lp_otz, if * lp_tz is "-".) */ if ((len_otz > _TZBUFLEN - 1) || (len_tz > _TZBUFLEN - 1)) { sav_errno = ENAMETOOLONG; ncount = -1; break; } /* Create timezone struct */ if ((tp = (struct tz_timezone *) calloc(1, sizeof (struct tz_timezone))) == NULL) { sav_errno = ENOMEM; ncount = -1; break; } /* * Copy the timezone names - use the Solaris * compatible timezone name if one exists, * otherwise use the current Olson timezone * name. */ (void) strncpy(tp->tz_oname, lp_otz, len_otz); tp->tz_oname[len_otz] = '\0'; if (strncmp("-", lp_tz, len_tz) == 0) { lp_tz = lp_otz; len_tz = len_otz; } /* If name has numeric digits, prefix ':' */ if (strcspn(lp_tz, DIGITS) < len_tz) { if (len_tz > _TZBUFLEN - 2) { free(tp); sav_errno = ENAMETOOLONG; ncount = -1; break; } tp->tz_name[0] = ':'; (void) strncpy(tp->tz_name + 1, lp_tz, len_tz); tp->tz_name[len_tz + 1] = '\0'; } else { (void) strncpy(tp->tz_name, lp_tz, len_tz); tp->tz_name[len_tz] = '\0'; } /* Process timezone description, if one exists */ if ((lp_tzdesc != NULL) && (*lp_tzdesc != '\n')) { if ((ptr = calloc(1, len_tzdesc + 1)) == NULL) { sav_errno = ENOMEM; ncount = -1; (void) free_timezones(tp); break; } (void) strncpy(ptr, lp_tzdesc, len_tzdesc); *(ptr + len_tzdesc) = '\0'; tp->tz_id_desc = ptr; /* Get localized country description */ lptr = dgettext(TEXT_DOMAIN, ptr); if ((ptr = strdup(lptr)) == NULL) { sav_errno = ENOMEM; ncount = -1; (void) free_timezones(tp); break; } tp->tz_display_desc = ptr; } else { tp->tz_id_desc = NULL; tp->tz_display_desc = NULL; } /* Get coordinate information */ if (get_coord(tp, lp_coord, len_coord) == -1) { sav_errno = EILSEQ; ncount = -1; (void) free_timezones(tp); break; } /* Store timezone struct in a linked list */ if (head == NULL) { head = tp; } else { prev->tz_next = tp; } prev = tp; ncount++; } else { if (match == 1) { /* * At this point, since zone_sun.tab is ordered, * if we've already found timezone entries for * the input country, then we've found all of * the desired timezone entries (since we will * be past that country's section in * zone_sun.tab), and we are done. */ break; } } } /* Finish up */ (void) fclose(fp_zone); if (ncount == -1) { if (head != NULL) (void) free_timezones(head); if (sav_errno) errno = sav_errno; } else { *tmzone = head; } return (ncount); } int free_tz_continents(struct tz_continent *cont) { struct tz_continent *cptr, *cprev; cptr = cont; while (cptr != NULL) { if (cptr->ctnt_id_desc != NULL) free(cptr->ctnt_id_desc); if (cptr->ctnt_display_desc != NULL) free(cptr->ctnt_display_desc); cprev = cptr; cptr = cptr->ctnt_next; free(cprev); } return (0); } int free_tz_countries(struct tz_country *country) { struct tz_country *cptr, *cprev; cptr = country; while (cptr != NULL) { if (cptr->ctry_id_desc != NULL) free(cptr->ctry_id_desc); if (cptr->ctry_display_desc != NULL) free(cptr->ctry_display_desc); cprev = cptr; cptr = cptr->ctry_next; free(cprev); } return (0); } int free_timezones(struct tz_timezone *timezone) { struct tz_timezone *tzptr, *tzprev; tzptr = timezone; while (tzptr != NULL) { if (tzptr->tz_id_desc != NULL) free(tzptr->tz_id_desc); if (tzptr->tz_display_desc != NULL) free(tzptr->tz_display_desc); tzprev = tzptr; tzptr = tzptr->tz_next; free(tzprev); } return (0); } /* * conv_gmt() returns a GMT-offset style timezone * If flag = 0, return Quoted POSIX timezone like: +8 * If flag = 1, return zoneinfo timezone like: :Etc/GMT+8 */ char * conv_gmt(int seconds, int flag) { int hour; char *cp; if ((seconds < _GMT_MIN) || (seconds > _GMT_MAX)) { errno = EINVAL; return (NULL); } hour = (seconds / 60) / 60; if (flag == 0) { cp = _conv_gmt_posix(hour); } else if (flag == 1) { cp = _conv_gmt_zoneinfo(hour); } else { errno = EINVAL; return (NULL); } return (cp); } static char * _conv_gmt_posix(int hour) { char *cp; char xsign; if (hour == 0) { if ((cp = strdup(GMT0_FMT)) == NULL) { errno = ENOMEM; return (NULL); } } else { if (hour < 0) { xsign = '-'; /* make hour positive for snprintf() */ hour = -hour; } else { xsign = '+'; } if ((cp = malloc(GMT_FMT_Q_LEN + 1)) == NULL) { errno = ENOMEM; return (NULL); } (void) snprintf(cp, GMT_FMT_Q_LEN + 1, GMT_FMT_Q, xsign, hour, xsign, hour); } return (cp); } static char * _conv_gmt_zoneinfo(int hour) { char *cp; char xsign; if (hour < 0) { xsign = '-'; /* make hour positive for snprintf() */ hour = -hour; } else { xsign = '+'; } if ((cp = malloc(GMT_FMT_ZONE_LEN + 1)) == NULL) { errno = ENOMEM; return (NULL); } (void) snprintf(cp, GMT_FMT_ZONE_LEN + 1, GMT_FMT_ZONE, xsign, hour); return (cp); } /* Regular expression for POSIX GMT-offset timezone */ #define _GMT_EXPR "(" _GMT_EXPR_U "|" _GMT_EXPR_Q ")" #define _GMT_EXPR_U "^[gG][mM][tT][-+]?[0-2]?[0-9]$" #define _GMT_EXPR_Q "^<[gG][mM][tT][-+]?[0-2]?[0-9]>[-+]?[0-2]?[0-9]$" /* * Regular expression for quoted POSIX timezone. */ /* Avoid alphabetic ranges (eg, a-z) due to effect of LC_COLLATE */ #define _ALPHA "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" #define _NUM "0123456789" /* for safe */ #define _STD_Q_ELM "[-+" _ALPHA _NUM "]" #define _STD_Q "<" _STD_Q_ELM _STD_Q_ELM _STD_Q_ELM "+>" /* Regular expression for unquoted POSIX timezone */ #define _STD_U_ELM_1 "[^-+,<" _NUM "]" #define _STD_U_ELM "[^-+," _NUM "]" #define _STD_U _STD_U_ELM_1 _STD_U_ELM _STD_U_ELM "+" /* Regular expression for POSIX timezone */ #define _STD "(" _STD_U "|" _STD_Q ")" #define _DST _STD #define _OFFSET "[-+]?" _TIME #define _START "(" _DATEJ "|" _DATEn "|" _DATEM ")" #define _DATEJ "J(([0-2]?[0-9]?[0-9])|3[0-5][0-9]|36[0-5])" #define _DATEn "(([0-2]?[0-9]?[0-9])|3[0-5][0-9]|36[0-5])" #define _DATEM "M([0-9]|10|11|12)\\.[1-5]\\.[0-6]" #define _END _START #define _TIME _HH "(:" _MM "(:" _SS ")?" ")?" #define _HH "(([0-1]?[0-9])|20|21|22|23|24)" #define _MM "[0-5]?[0-9]" #define _SS _MM #define _POSIX_EXPR "^" _STD _OFFSET "(" _DST "(" _OFFSET ")?" \ "(," _START "(/" _TIME ")?" \ "," _END "(/" _TIME ")?" ")?" ")?" "$" #define LEN_TZDIR (sizeof (TZDIR) - 1) /* * isvalid_tz() checks if timezone is a valid POSIX or zoneinfo * timezone, depending on the value of flag. For flag = _VTZ_INSTALL, * isvalid_tz() behaves according to the behavior of Solaris Install * in Solaris 9 and earlier, where timezones under /usr/share/lib/zoneinfo * were validated. isvalid_tz() has a special check for GMT+-* timezones * because Solaris Install validated /usr/share/lib/zoneinfo/GMT+-*. * However, when /usr/share/lib/zoneinfo/GMT+-* are EOF'd, that check * no longer works. * * isvalid_tz() returns 1 if a valid timezone is detected. */ int isvalid_tz(char *timezone, char *root, int flag) { char path[MAXPATHLEN]; char buf[sizeof (struct tzhead)]; int fid, ret; if ((timezone == NULL) || (*timezone == '\0')) { return (0); } /* First check if timezone is a valid POSIX timezone */ switch (flag) { case _VTZ_INSTALL: /* * Special check for POSIX GMT timezone. * If no match, check for zoneinfo timezone below */ if (_tz_match(_GMT_EXPR, timezone) == 0) { /* Valid GMT timezone */ return (1); } break; case _VTZ_POSIX: /* Check for generic POSIX timezone */ if (_tz_match(_POSIX_EXPR, timezone) == 0) { /* Valid POSIX timezone */ return (1); } /* Invalid POSIX timezone */ return (0); case _VTZ_ALL: /* Check for generic POSIX timezone */ if (_tz_match(_POSIX_EXPR, timezone) == 0) { /* Valid POSIX timezone */ return (1); } break; case _VTZ_ZONEINFO: break; default: return (0); } /* * Check for valid zoneinfo timezone - * open zoneinfo file and check for magic number */ /* skip prepended ':' if one exists */ if (*timezone == ':') { timezone++; } /* Construct full zoneinfo pathname */ if ((root != NULL) && (*root != '\0')) { ret = snprintf(path, sizeof (path), "%s%s/%s", root, TZDIR, timezone); if (ret >= sizeof (path)) { /* too long */ return (0); } } else { ret = snprintf(path, sizeof (path), "%s/%s", TZDIR, timezone); if (ret >= sizeof (path)) { /* too long */ return (0); } } if ((fid = open(path, O_RDONLY)) == -1) { return (0); } if (read(fid, buf, sizeof (struct tzhead)) != sizeof (struct tzhead)) { (void) close(fid); return (0); } if (strncmp(buf, TZ_MAGIC, sizeof (TZ_MAGIC) - 1) != 0) { (void) close(fid); return (0); } if (close(fid) == -1) { return (0); } /* Valid zoneinfo timezone */ return (1); } #define N_MATCH 1 int _tz_match(const char *expr, const char *string) { regex_t reg; regmatch_t pmatch[N_MATCH]; int ret; ret = regcomp(®, expr, REG_EXTENDED); if (ret != 0) { return (-1); } ret = regexec((const regex_t *)®, string, N_MATCH, pmatch, 0); if (ret == 0) { #ifdef DEBUG printf("OK matched - %s\n", string); #endif regfree(®); return (0); } #ifdef DEBUG printf("NOT matched - %s\n", string); #endif regfree(®); return (-1); } char * get_system_tz(char *root) { FILE *ifp; char buff[512]; int serrno, ret; char *sp, *ptr, *p; char fname[MAXPATHLEN]; if ((ret = snprintf(fname, sizeof (fname), "%s/%s", root, DEFINIT)) >= sizeof (fname)) { errno = ENAMETOOLONG; return (NULL); } else if (ret < 0) { return (NULL); } if ((ifp = fopen(fname, "r")) == NULL) return (NULL); while (fgets(buff, sizeof (buff), ifp) != NULL) { if (strncmp(buff, "TZ=", 3) == 0) { (void) fclose(ifp); p = &buff[3]; if ((sp = strchr(p, ';')) != NULL) { *sp = '\0'; } else if ((sp = strchr(p, '\n')) != NULL) { *sp = '\0'; } if (strpbrk(p, "\"'") != NULL) { strip_quotes(p, p); } ptr = strdup(p); if (ptr == NULL) { errno = ENOMEM; return (NULL); } return (ptr); } } /* Either reached EOF with no TZ= entry, or got fgets() error */ serrno = errno; if (feof(ifp) != 0) { /* No "TZ=" entry found */ serrno = EINVAL; } (void) fclose(ifp); errno = serrno; return (NULL); } int set_system_tz(char *tz, char *root) { FILE *ifp, *ofp; /* Input & output files */ char *tmpdir, *tmp; /* Temp file name and location */ char buff[1024]; int replaced = 0, ret, serrno; char *tdb; struct stat sb; char fname[MAXPATHLEN]; const char *tzfmt; int len, fd; if (tz == NULL || root == NULL) return (-1); if (strchr(tz, '<')) { tzfmt = TZ_FMT_Q; } else { tzfmt = TZ_FMT; } if ((ret = snprintf(fname, sizeof (fname), "%s/%s", root, DEFINIT)) >= sizeof (fname)) { errno = ENAMETOOLONG; return (-1); } else if (ret < 0) { return (-1); } /* * Generate temporary file name to use. We make sure it's in the same * directory as the db we're processing so that we can use rename to * do the replace later. Otherwise we run the risk of being on the * wrong filesystem and having rename() fail for that reason. */ tdb = fname; if (trav_link(&tdb) == -1) return (-1); if ((tmpdir = strdup(tdb)) == NULL) { errno = ENOMEM; return (-1); } remove_component(tmpdir); if ((len = strlen(tmpdir)) == 0) { (void) strcpy(tmpdir, "."); len = 1; } if ((tmp = malloc(len + TR_LEN + 1)) == NULL) { free(tmpdir); errno = ENOMEM; return (-1); } (void) strcpy(tmp, tmpdir); (void) strcpy(tmp + len, TRAILER); free(tmpdir); if ((fd = mkstemp(tmp)) == -1) { free(tmp); return (-1); } if ((ofp = fdopen(fd, "w")) == NULL) { serrno = errno; (void) close(fd); free(tmp); errno = serrno; return (-1); } /* Preserve permissions of current file if it exists */ if (stat(tdb, &sb) == 0) { if (fchmod(fileno(ofp), sb.st_mode) == -1) { serrno = errno; (void) fclose(ofp); (void) unlink(tmp); free(tmp); errno = serrno; return (-1); } if (fchown(fileno(ofp), sb.st_uid, sb.st_gid) == -1) { serrno = errno; (void) fclose(ofp); (void) unlink(tmp); free(tmp); errno = serrno; return (-1); } } else if (errno != ENOENT) { serrno = errno; (void) fclose(ofp); (void) unlink(tmp); free(tmp); errno = serrno; return (-1); } if ((ifp = fopen(fname, "r+")) != NULL) { while (fgets(buff, sizeof (buff), ifp) != NULL) { if (!replaced && (strncmp(buff, "TZ=", 3) == 0)) { ret = snprintf(buff, sizeof (buff), tzfmt, tz); if ((ret >= sizeof (buff)) || (ret < 0)) { if (ret >= sizeof (buff)) serrno = EINVAL; (void) fclose(ofp); (void) fclose(ifp); (void) unlink(tmp); free(tmp); errno = serrno; return (-1); } replaced = 1; } if (fputs(buff, ofp) == EOF) { serrno = errno; (void) fclose(ofp); (void) fclose(ifp); (void) unlink(tmp); free(tmp); errno = serrno; return (-1); } } (void) fclose(ifp); } else if (errno != ENOENT) { serrno = errno; (void) fclose(ofp); (void) unlink(tmp); free(tmp); errno = serrno; return (-1); } /* * no $(ROOT)/etc/default/init found, or * no "TZ=" entry found in the init file. */ if (!replaced && (fprintf(ofp, tzfmt, tz) == EOF)) { serrno = errno; (void) fclose(ofp); (void) unlink(tmp); free(tmp); errno = serrno; return (-1); } if (fsync(fileno(ofp))) { serrno = errno; (void) unlink(tmp); free(tmp); errno = serrno; return (-1); } (void) fclose(ofp); if (rename(tmp, tdb) != 0) { serrno = errno; (void) unlink(tmp); free(tmp); errno = serrno; return (-1); } else { free(tmp); return (0); } } /* * Function to traverse a symlink path to find the real file at the end of * the rainbow. */ int trav_link(char **path) { static char newpath[MAXPATHLEN]; char lastpath[MAXPATHLEN]; int len, ret; char *tp; (void) strcpy(lastpath, *path); while ((len = readlink(*path, newpath, sizeof (newpath))) != -1) { newpath[len] = '\0'; if (newpath[0] != '/') { if ((tp = strdup(newpath)) == NULL) { errno = ENOMEM; return (-1); } remove_component(lastpath); ret = snprintf(newpath, sizeof (newpath), "%s/%s", lastpath, tp); free(tp); if ((ret >= sizeof (newpath)) || (ret < 0)) return (-1); } (void) strcpy(lastpath, newpath); *path = newpath; } /* * ENOENT or EINVAL is the normal exit case of the above loop. */ if ((errno == ENOENT) || (errno == EINVAL)) return (0); else return (-1); } void remove_component(char *path) { char *p; p = strrchr(path, '/'); /* find last '/' */ if (p == NULL) { *path = '\0'; /* set path to null str */ } else { *p = '\0'; /* zap it */ } } /* * get_coord() fills in the tz_coord structure of the tz_timezone * struct. It returns 0 on success, or -1 on error. * The format of p_coord is: * * Latitude and longitude of the zone's principal location * in ISO 6709 sign-degrees-minutes-seconds format, * either +-DDMM+-DDDMM or +-DDMMSS+-DDDMMSS, * first latitude (+ is north), then longitude (+ is east). */ static int get_coord(struct tz_timezone *tp, char *p_coord, size_t len_coord) { int i, fmt_flag, nchar; int *signp, *degp, *minp, *secp; struct tz_coord *tcp; char buff[512], *endp; tcp = &(tp->tz_coord); /* Figure out which format to use */ if (len_coord == COORD_FMTLEN1) { /* "+-DDMM+-DDDMM" */ fmt_flag = COORD_FMT1; } else if (len_coord == COORD_FMTLEN2) { /* "+-DDMMSS+-DDDMMSS" */ fmt_flag = COORD_FMT2; } else { /* error */ return (-1); } /* * First time through, get values for latitude; * second time through, get values for longitude. */ for (i = 0; i < 2; i++) { /* Set up pointers */ if (i == 0) { /* Do latitude */ nchar = COORD_DLEN_LAT; signp = (int *)&(tcp->lat_sign); degp = (int *)&(tcp->lat_degree); minp = (int *)&(tcp->lat_minute); secp = (int *)&(tcp->lat_second); } else { /* Do longitude */ nchar = COORD_DLEN_LONG; signp = (int *)&(tcp->long_sign); degp = (int *)&tcp->long_degree; minp = (int *)&tcp->long_minute; secp = (int *)&tcp->long_second; } /* Get latitude/logitude sign */ if (*p_coord == '+') { *signp = 1; } else if (*p_coord == '-') { *signp = -1; } else { return (-1); } p_coord++; /* Get DD latitude, or DDD longitude */ (void) strncpy(buff, p_coord, nchar); buff[nchar] = '\0'; errno = 0; *degp = (int)strtol(buff, &endp, 10); if ((endp != &buff[nchar]) || ((*degp == 0) && (errno != 0))) return (-1); p_coord += nchar; /* Get MM latitude/longitude */ (void) strncpy(buff, p_coord, COORD_MLEN); buff[COORD_MLEN] = '\0'; errno = 0; *minp = (int)strtol(buff, &endp, 10); if ((endp != &buff[COORD_MLEN]) || ((*degp == 0) && (errno != 0))) return (-1); p_coord += COORD_MLEN; /* If FMT2, then get SS latitude/longitude */ if (fmt_flag == COORD_FMT2) { (void) strncpy(buff, p_coord, COORD_SLEN); buff[COORD_SLEN] = '\0'; errno = 0; *secp = (int)strtol(buff, &endp, 10); if ((endp != &buff[COORD_SLEN]) || ((*degp == 0) && (errno != 0))) return (-1); p_coord += COORD_SLEN; } else { *secp = 0; } } return (0); } static char * skipwhite(char *cp) { while (*cp && ((*cp == ' ') || (*cp == '\t'))) { cp++; } return (cp); } /* * skipline() checks if the line begins with a comment * comment character anywhere in the line, or if the * line is only whitespace. * skipline() also checks if the line read is too long to * fit in the buffer. * skipline() returns 1 if the line can be skipped, -1 if * the line read is too long, and 0 if the line should not be skipped. */ static int skipline(char *line) { size_t len; len = strlen(line); if (line[len - 1] != '\n') return (-1); if (line[0] == '#' || line[0] == '\0' || (len = strspn(line, " \t\n")) == strlen(line) || strchr(line, '#') == line + len) return (1); else return (0); } /* * strip_quotes -- strip double (") or single (') quotes */ static void strip_quotes(char *from, char *to) { char *strip_ptr = NULL; while (*from != '\0') { if ((*from == '"') || (*from == '\'')) { if (strip_ptr == NULL) strip_ptr = to; } else { if (strip_ptr != NULL) { *strip_ptr = *from; strip_ptr++; } else { *to = *from; to++; } } from++; } if (strip_ptr != NULL) { *strip_ptr = '\0'; } else { *to = '\0'; } } /* * Compare function used by get_tz_countries() - uses strcoll() * for locale-sensitive comparison for the localized country names. */ static int compar(struct tz_country *p1, struct tz_country *p2) { int ret; ret = strcoll(p1->ctry_display_desc, p2->ctry_display_desc); return (ret); }