github.com/afumu/libc@v0.0.6/musl/src/time/__tz.c (about)

     1  #include "time_impl.h"
     2  #include <stdint.h>
     3  #include <limits.h>
     4  #include <stdlib.h>
     5  #include <string.h>
     6  #include <sys/mman.h>
     7  #include "libc.h"
     8  #include "lock.h"
     9  
    10  long  __timezone = 0;
    11  int   __daylight = 0;
    12  char *__tzname[2] = { 0, 0 };
    13  
    14  weak_alias(__timezone, timezone);
    15  weak_alias(__daylight, daylight);
    16  weak_alias(__tzname, tzname);
    17  
    18  static char std_name[TZNAME_MAX+1];
    19  static char dst_name[TZNAME_MAX+1];
    20  const char __utc[] = "UTC";
    21  
    22  static int dst_off;
    23  static int r0[5], r1[5];
    24  
    25  static const unsigned char *zi, *trans, *index, *types, *abbrevs, *abbrevs_end;
    26  static size_t map_size;
    27  
    28  static char old_tz_buf[32];
    29  static char *old_tz = old_tz_buf;
    30  static size_t old_tz_size = sizeof old_tz_buf;
    31  
    32  static volatile int lock[1];
    33  
    34  static int getint(const char **p)
    35  {
    36  	unsigned x;
    37  	for (x=0; **p-'0'<10U; (*p)++) x = **p-'0' + 10*x;
    38  	return x;
    39  }
    40  
    41  static int getoff(const char **p)
    42  {
    43  	int neg = 0;
    44  	if (**p == '-') {
    45  		++*p;
    46  		neg = 1;
    47  	} else if (**p == '+') {
    48  		++*p;
    49  	}
    50  	int off = 3600*getint(p);
    51  	if (**p == ':') {
    52  		++*p;
    53  		off += 60*getint(p);
    54  		if (**p == ':') {
    55  			++*p;
    56  			off += getint(p);
    57  		}
    58  	}
    59  	return neg ? -off : off;
    60  }
    61  
    62  static void getrule(const char **p, int rule[5])
    63  {
    64  	int r = rule[0] = **p;
    65  
    66  	if (r!='M') {
    67  		if (r=='J') ++*p;
    68  		else rule[0] = 0;
    69  		rule[1] = getint(p);
    70  	} else {
    71  		++*p; rule[1] = getint(p);
    72  		++*p; rule[2] = getint(p);
    73  		++*p; rule[3] = getint(p);
    74  	}
    75  
    76  	if (**p=='/') {
    77  		++*p;
    78  		rule[4] = getoff(p);
    79  	} else {
    80  		rule[4] = 7200;
    81  	}
    82  }
    83  
    84  static void getname(char *d, const char **p)
    85  {
    86  	int i;
    87  	if (**p == '<') {
    88  		++*p;
    89  		for (i=0; (*p)[i] && (*p)[i]!='>'; i++)
    90  			if (i<TZNAME_MAX) d[i] = (*p)[i];
    91  		if ((*p)[i]) ++*p;
    92  	} else {
    93  		for (i=0; ((*p)[i]|32)-'a'<26U; i++)
    94  			if (i<TZNAME_MAX) d[i] = (*p)[i];
    95  	}
    96  	*p += i;
    97  	d[i<TZNAME_MAX?i:TZNAME_MAX] = 0;
    98  }
    99  
   100  #define VEC(...) ((const unsigned char[]){__VA_ARGS__})
   101  
   102  static uint32_t zi_read32(const unsigned char *z)
   103  {
   104  	return (unsigned)z[0]<<24 | z[1]<<16 | z[2]<<8 | z[3];
   105  }
   106  
   107  static size_t zi_dotprod(const unsigned char *z, const unsigned char *v, size_t n)
   108  {
   109  	size_t y;
   110  	uint32_t x;
   111  	for (y=0; n; n--, z+=4, v++) {
   112  		x = zi_read32(z);
   113  		y += x * *v;
   114  	}
   115  	return y;
   116  }
   117  
   118  static void do_tzset()
   119  {
   120  	char buf[NAME_MAX+25], *pathname=buf+24;
   121  	const char *try, *s, *p;
   122  	const unsigned char *map = 0;
   123  	size_t i;
   124  	static const char search[] =
   125  		"/usr/share/zoneinfo/\0/share/zoneinfo/\0/etc/zoneinfo/\0";
   126  
   127  	s = getenv("TZ");
   128  	if (!s) s = "/etc/localtime";
   129  	if (!*s) s = __utc;
   130  
   131  	if (old_tz && !strcmp(s, old_tz)) return;
   132  
   133  	for (i=0; i<5; i++) r0[i] = r1[i] = 0;
   134  
   135  	if (zi) __munmap((void *)zi, map_size);
   136  
   137  	/* Cache the old value of TZ to check if it has changed. Avoid
   138  	 * free so as not to pull it into static programs. Growth
   139  	 * strategy makes it so free would have minimal benefit anyway. */
   140  	i = strlen(s);
   141  	if (i > PATH_MAX+1) s = __utc, i = 3;
   142  	if (i >= old_tz_size) {
   143  		old_tz_size *= 2;
   144  		if (i >= old_tz_size) old_tz_size = i+1;
   145  		if (old_tz_size > PATH_MAX+2) old_tz_size = PATH_MAX+2;
   146  		old_tz = malloc(old_tz_size);
   147  	}
   148  	if (old_tz) memcpy(old_tz, s, i+1);
   149  
   150  	/* Non-suid can use an absolute tzfile pathname or a relative
   151  	 * pathame beginning with "."; in secure mode, only the
   152  	 * standard path will be searched. */
   153  	if (*s == ':' || ((p=strchr(s, '/')) && !memchr(s, ',', p-s))) {
   154  		if (*s == ':') s++;
   155  		if (*s == '/' || *s == '.') {
   156  			if (!libc.secure || !strcmp(s, "/etc/localtime"))
   157  				map = __map_file(s, &map_size);
   158  		} else {
   159  			size_t l = strlen(s);
   160  			if (l <= NAME_MAX && !strchr(s, '.')) {
   161  				memcpy(pathname, s, l+1);
   162  				pathname[l] = 0;
   163  				for (try=search; !map && *try; try+=l+1) {
   164  					l = strlen(try);
   165  					memcpy(pathname-l, try, l);
   166  					map = __map_file(pathname-l, &map_size);
   167  				}
   168  			}
   169  		}
   170  		if (!map) s = __utc;
   171  	}
   172  	if (map && (map_size < 44 || memcmp(map, "TZif", 4))) {
   173  		__munmap((void *)map, map_size);
   174  		map = 0;
   175  		s = __utc;
   176  	}
   177  
   178  	zi = map;
   179  	if (map) {
   180  		int scale = 2;
   181  		if (sizeof(time_t) > 4 && map[4]=='2') {
   182  			size_t skip = zi_dotprod(zi+20, VEC(1,1,8,5,6,1), 6);
   183  			trans = zi+skip+44+44;
   184  			scale++;
   185  		} else {
   186  			trans = zi+44;
   187  		}
   188  		index = trans + (zi_read32(trans-12) << scale);
   189  		types = index + zi_read32(trans-12);
   190  		abbrevs = types + 6*zi_read32(trans-8);
   191  		abbrevs_end = abbrevs + zi_read32(trans-4);
   192  		if (zi[map_size-1] == '\n') {
   193  			for (s = (const char *)zi+map_size-2; *s!='\n'; s--);
   194  			s++;
   195  		} else {
   196  			const unsigned char *p;
   197  			__tzname[0] = __tzname[1] = 0;
   198  			__daylight = __timezone = dst_off = 0;
   199  			for (p=types; p<abbrevs; p+=6) {
   200  				if (!p[4] && !__tzname[0]) {
   201  					__tzname[0] = (char *)abbrevs + p[5];
   202  					__timezone = -zi_read32(p);
   203  				}
   204  				if (p[4] && !__tzname[1]) {
   205  					__tzname[1] = (char *)abbrevs + p[5];
   206  					dst_off = -zi_read32(p);
   207  					__daylight = 1;
   208  				}
   209  			}
   210  			if (!__tzname[0]) __tzname[0] = __tzname[1];
   211  			if (!__tzname[0]) __tzname[0] = (char *)__utc;
   212  			if (!__daylight) {
   213  				__tzname[1] = __tzname[0];
   214  				dst_off = __timezone;
   215  			}
   216  			return;
   217  		}
   218  	}
   219  
   220  	if (!s) s = __utc;
   221  	getname(std_name, &s);
   222  	__tzname[0] = std_name;
   223  	__timezone = getoff(&s);
   224  	getname(dst_name, &s);
   225  	__tzname[1] = dst_name;
   226  	if (dst_name[0]) {
   227  		__daylight = 1;
   228  		if (*s == '+' || *s=='-' || *s-'0'<10U)
   229  			dst_off = getoff(&s);
   230  		else
   231  			dst_off = __timezone - 3600;
   232  	} else {
   233  		__daylight = 0;
   234  		dst_off = __timezone;
   235  	}
   236  
   237  	if (*s == ',') s++, getrule(&s, r0);
   238  	if (*s == ',') s++, getrule(&s, r1);
   239  }
   240  
   241  /* Search zoneinfo rules to find the one that applies to the given time,
   242   * and determine alternate opposite-DST-status rule that may be needed. */
   243  
   244  static size_t scan_trans(long long t, int local, size_t *alt)
   245  {
   246  	int scale = 3 - (trans == zi+44);
   247  	uint64_t x;
   248  	int off = 0;
   249  
   250  	size_t a = 0, n = (index-trans)>>scale, m;
   251  
   252  	if (!n) {
   253  		if (alt) *alt = 0;
   254  		return 0;
   255  	}
   256  
   257  	/* Binary search for 'most-recent rule before t'. */
   258  	while (n > 1) {
   259  		m = a + n/2;
   260  		x = zi_read32(trans + (m<<scale));
   261  		if (scale == 3) x = x<<32 | zi_read32(trans + (m<<scale) + 4);
   262  		else x = (int32_t)x;
   263  		if (local) off = (int32_t)zi_read32(types + 6 * index[m-1]);
   264  		if (t - off < (int64_t)x) {
   265  			n /= 2;
   266  		} else {
   267  			a = m;
   268  			n -= n/2;
   269  		}
   270  	}
   271  
   272  	/* First and last entry are special. First means to use lowest-index
   273  	 * non-DST type. Last means to apply POSIX-style rule if available. */
   274  	n = (index-trans)>>scale;
   275  	if (a == n-1) return -1;
   276  	if (a == 0) {
   277  		x = zi_read32(trans + (a<<scale));
   278  		if (scale == 3) x = x<<32 | zi_read32(trans + (a<<scale) + 4);
   279  		else x = (int32_t)x;
   280  		if (local) off = (int32_t)zi_read32(types + 6 * index[a-1]);
   281  		if (t - off < (int64_t)x) {
   282  			for (a=0; a<(abbrevs-types)/6; a++) {
   283  				if (types[6*a+4] != types[4]) break;
   284  			}
   285  			if (a == (abbrevs-types)/6) a = 0;
   286  			if (types[6*a+4]) {
   287  				*alt = a;
   288  				return 0;
   289  			} else {
   290  				*alt = 0;
   291  				return a;
   292  			}
   293  		}
   294  	}
   295  
   296  	/* Try to find a neighboring opposite-DST-status rule. */
   297  	if (alt) {
   298  		if (a && types[6*index[a-1]+4] != types[6*index[a]+4])
   299  			*alt = index[a-1];
   300  		else if (a+1<n && types[6*index[a+1]+4] != types[6*index[a]+4])
   301  			*alt = index[a+1];
   302  		else
   303  			*alt = index[a];
   304  	}
   305  
   306  	return index[a];
   307  }
   308  
   309  static int days_in_month(int m, int is_leap)
   310  {
   311  	if (m==2) return 28+is_leap;
   312  	else return 30+((0xad5>>(m-1))&1);
   313  }
   314  
   315  /* Convert a POSIX DST rule plus year to seconds since epoch. */
   316  
   317  static long long rule_to_secs(const int *rule, int year)
   318  {
   319  	int is_leap;
   320  	long long t = __year_to_secs(year, &is_leap);
   321  	int x, m, n, d;
   322  	if (rule[0]!='M') {
   323  		x = rule[1];
   324  		if (rule[0]=='J' && (x < 60 || !is_leap)) x--;
   325  		t += 86400 * x;
   326  	} else {
   327  		m = rule[1];
   328  		n = rule[2];
   329  		d = rule[3];
   330  		t += __month_to_secs(m-1, is_leap);
   331  		int wday = (int)((t + 4*86400) % (7*86400)) / 86400;
   332  		int days = d - wday;
   333  		if (days < 0) days += 7;
   334  		if (n == 5 && days+28 >= days_in_month(m, is_leap)) n = 4;
   335  		t += 86400 * (days + 7*(n-1));
   336  	}
   337  	t += rule[4];
   338  	return t;
   339  }
   340  
   341  /* Determine the time zone in effect for a given time in seconds since the
   342   * epoch. It can be given in local or universal time. The results will
   343   * indicate whether DST is in effect at the queried time, and will give both
   344   * the GMT offset for the active zone/DST rule and the opposite DST. This
   345   * enables a caller to efficiently adjust for the case where an explicit
   346   * DST specification mismatches what would be in effect at the time. */
   347  
   348  void __secs_to_zone(long long t, int local, int *isdst, long *offset, long *oppoff, const char **zonename)
   349  {
   350  	LOCK(lock);
   351  
   352  	do_tzset();
   353  
   354  	if (zi) {
   355  		size_t alt, i = scan_trans(t, local, &alt);
   356  		if (i != -1) {
   357  			*isdst = types[6*i+4];
   358  			*offset = (int32_t)zi_read32(types+6*i);
   359  			*zonename = (const char *)abbrevs + types[6*i+5];
   360  			if (oppoff) *oppoff = (int32_t)zi_read32(types+6*alt);
   361  			UNLOCK(lock);
   362  			return;
   363  		}
   364  	}
   365  
   366  	if (!__daylight) goto std;
   367  
   368  	/* FIXME: may be broken if DST changes right at year boundary?
   369  	 * Also, this could be more efficient.*/
   370  	long long y = t / 31556952 + 70;
   371  	while (__year_to_secs(y, 0) > t) y--;
   372  	while (__year_to_secs(y+1, 0) < t) y++;
   373  
   374  	long long t0 = rule_to_secs(r0, y);
   375  	long long t1 = rule_to_secs(r1, y);
   376  
   377  	if (!local) {
   378  		t0 += __timezone;
   379  		t1 += dst_off;
   380  	}
   381  	if (t0 < t1) {
   382  		if (t >= t0 && t < t1) goto dst;
   383  		goto std;
   384  	} else {
   385  		if (t >= t1 && t < t0) goto std;
   386  		goto dst;
   387  	}
   388  std:
   389  	*isdst = 0;
   390  	*offset = -__timezone;
   391  	if (oppoff) *oppoff = -dst_off;
   392  	*zonename = __tzname[0];
   393  	UNLOCK(lock);
   394  	return;
   395  dst:
   396  	*isdst = 1;
   397  	*offset = -dst_off;
   398  	if (oppoff) *oppoff = -__timezone;
   399  	*zonename = __tzname[1];
   400  	UNLOCK(lock);
   401  }
   402  
   403  static void __tzset()
   404  {
   405  	LOCK(lock);
   406  	do_tzset();
   407  	UNLOCK(lock);
   408  }
   409  
   410  weak_alias(__tzset, tzset);
   411  
   412  const char *__tm_to_tzname(const struct tm *tm)
   413  {
   414  	const void *p = tm->__tm_zone;
   415  	LOCK(lock);
   416  	do_tzset();
   417  	if (p != __utc && p != __tzname[0] && p != __tzname[1] &&
   418  	    (!zi || (uintptr_t)p-(uintptr_t)abbrevs >= abbrevs_end - abbrevs))
   419  		p = "";
   420  	UNLOCK(lock);
   421  	return p;
   422  }