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