From 6a145a92628d0cf17d437806d2c86a350f17cfcf Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Wed, 27 Apr 2022 05:39:39 -0700 Subject: [PATCH] Make improvements - Add hierarchical auto-completion to redbean's repl - Fetch latest localtime() and strftime() from Eggert - Shave a few milliseconds off redbean start latency - Fix redbean repl with multi-line statements - Make the Lua unix module code more elegant - Harden Lua data structure serialization --- examples/examples.mk | 2 +- libc/calls/kopenflags.S | 57 +- libc/calls/reservefd.c | 12 +- libc/calls/uname.c | 2 + libc/calls/weirdtypes.h | 20 + libc/fmt/strerror_wr.greg.c | 6 +- libc/runtime/getsymboltable.greg.c | 15 +- libc/str/longsort.c | 4 + libc/str/strchrnul.c | 2 + libc/sysv/consts.sh | 3 - libc/sysv/consts/exit.h | 13 +- libc/time/asctime.c | 137 +- libc/time/asctime_r.c | 40 - libc/time/ctime.c | 30 +- libc/time/ctime_r.c | 26 +- libc/time/difftime.c | 78 +- libc/time/localtime.c | 2454 ++++++++++++++------------- libc/time/strftime.c | 881 ++++++---- libc/time/time.mk | 4 + libc/time/tz.internal.h | 542 ++++++ libc/time/tzfile.internal.h | 125 +- third_party/chibicc/chibicc.mk | 2 +- third_party/linenoise/linenoise.c | 15 +- third_party/lua/cosmo.h | 4 +- third_party/lua/ldo.c | 3 +- third_party/lua/lrepl.c | 73 +- third_party/lua/ltests.c | 2 +- third_party/lua/lua.main.c | 2 - third_party/lua/lua.mk | 2 +- third_party/lua/luaencodejsondata.c | 14 +- third_party/lua/luaencodeluadata.c | 118 +- third_party/lua/luaformatstack.c | 2 +- third_party/make/make.mk | 2 +- third_party/python/python.mk | 2 +- third_party/quickjs/quickjs.mk | 2 +- third_party/sqlite3/sqlite3.mk | 2 +- tool/build/build.mk | 2 +- tool/net/help.txt | 61 +- tool/net/lunix.c | 130 +- tool/net/net.mk | 17 +- tool/net/redbean.c | 14 +- tool/plinko/plinko.mk | 2 +- tool/viz/viz.mk | 4 +- usr/share/zoneinfo/Anchorage | Bin 0 -> 977 bytes 44 files changed, 2987 insertions(+), 1941 deletions(-) delete mode 100644 libc/time/asctime_r.c create mode 100644 libc/time/tz.internal.h create mode 100644 usr/share/zoneinfo/Anchorage diff --git a/examples/examples.mk b/examples/examples.mk index 0f6973377..a76e23ebd 100644 --- a/examples/examples.mk +++ b/examples/examples.mk @@ -140,7 +140,7 @@ o/$(MODE)/examples/nesemu1.com: \ @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com \ -o o/$(MODE)/examples/.nesemu1/.symtab $< - @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \ + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \ o/$(MODE)/examples/.nesemu1/.symtab o/$(MODE)/examples/hello.com.dbg: \ diff --git a/libc/calls/kopenflags.S b/libc/calls/kopenflags.S index 9ced691a0..a0749a5af 100644 --- a/libc/calls/kopenflags.S +++ b/libc/calls/kopenflags.S @@ -31,34 +31,35 @@ .align 4 .underrun kOpenFlags: - .e O_RDWR,"RDWR" // order matters - .e O_RDONLY,"RDONLY" // - .e O_WRONLY,"WRONLY" // - .e O_ACCMODE,"ACCMODE" // mask of prev three - .e O_CREAT,"CREAT" // - .e O_EXCL,"EXCL" // - .e O_TRUNC,"TRUNC" // - .e O_CLOEXEC,"CLOEXEC" // - .e O_DIRECT,"DIRECT" // no-op on xnu/openbsd - .e O_APPEND,"APPEND" // weird on nt - .e O_TMPFILE,"TMPFILE" // linux, windows - .e O_NOFOLLOW,"NOFOLLOW" // unix - .e O_SYNC,"SYNC" // unix - .e O_ASYNC,"ASYNC" // unix - .e O_NOCTTY,"NOCTTY" // unix - .e O_NOATIME,"NOATIME" // linux - .e O_EXEC,"EXEC" // free/openbsd - .e O_SEARCH,"SEARCH" // free/netbsd - .e O_DSYNC,"DSYNC" // linux/xnu/open/netbsd - .e O_RSYNC,"RSYNC" // linux/open/netbsd - .e O_PATH,"PATH" // linux - .e O_VERIFY,"VERIFY" // freebsd - .e O_SHLOCK,"SHLOCK" // bsd - .e O_EXLOCK,"EXLOCK" // bsd - .e O_RANDOM,"RANDOM" // windows - .e O_SEQUENTIAL,"SEQUENTIAL" // windows - .e O_COMPRESSED,"COMPRESSED" // windows - .e O_INDEXED,"INDEXED" // windows + .e O_RDWR,"RDWR" // order matters + .e O_RDONLY,"RDONLY" // + .e O_WRONLY,"WRONLY" // + .e O_ACCMODE,"ACCMODE" // mask of prev three + .e O_CREAT,"CREAT" // + .e O_EXCL,"EXCL" // + .e O_TRUNC,"TRUNC" // + .e O_CLOEXEC,"CLOEXEC" // + .e O_NONBLOCK,"NONBLOCK" // + .e O_DIRECT,"DIRECT" // no-op on xnu/openbsd + .e O_APPEND,"APPEND" // weird on nt + .e O_TMPFILE,"TMPFILE" // linux, windows + .e O_NOFOLLOW,"NOFOLLOW" // unix + .e O_SYNC,"SYNC" // unix + .e O_ASYNC,"ASYNC" // unix + .e O_NOCTTY,"NOCTTY" // unix + .e O_NOATIME,"NOATIME" // linux + .e O_EXEC,"EXEC" // free/openbsd + .e O_SEARCH,"SEARCH" // free/netbsd + .e O_DSYNC,"DSYNC" // linux/xnu/open/netbsd + .e O_RSYNC,"RSYNC" // linux/open/netbsd + .e O_PATH,"PATH" // linux + .e O_VERIFY,"VERIFY" // freebsd + .e O_SHLOCK,"SHLOCK" // bsd + .e O_EXLOCK,"EXLOCK" // bsd + .e O_RANDOM,"RANDOM" // windows + .e O_SEQUENTIAL,"SEQUENTIAL" // windows + .e O_COMPRESSED,"COMPRESSED" // windows + .e O_INDEXED,"INDEXED" // windows .long MAGNUM_TERMINATOR .endobj kOpenFlags,globl,hidden .overrun diff --git a/libc/calls/reservefd.c b/libc/calls/reservefd.c index 1937d2e08..a68a56e7c 100644 --- a/libc/calls/reservefd.c +++ b/libc/calls/reservefd.c @@ -88,9 +88,9 @@ int __reservefd(int start) { /** * Closes non-stdio file descriptors to free dynamic memory. */ -static void __freefds(void) { +static void FreeFds(void) { int i; - NTTRACE("__freefds()"); + NTTRACE("FreeFds()"); for (i = 3; i < g_fds.n; ++i) { if (g_fds.p[i].kind) { close(i); @@ -104,10 +104,10 @@ static void __freefds(void) { } } -static textstartup void __freefds_init(void) { - atexit(__freefds); +static textstartup void FreeFdsInit(void) { + atexit(FreeFds); } -const void *const __freefds_ctor[] initarray = { - __freefds_init, +const void *const FreeFdsCtor[] initarray = { + FreeFdsInit, }; diff --git a/libc/calls/uname.c b/libc/calls/uname.c index b96686840..fa187db12 100644 --- a/libc/calls/uname.c +++ b/libc/calls/uname.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/weaken.h" #include "libc/calls/calls.h" #include "libc/calls/internal.h" #include "libc/calls/strace.internal.h" @@ -23,6 +24,7 @@ #include "libc/dce.h" #include "libc/fmt/itoa.h" #include "libc/intrin/asan.internal.h" +#include "libc/log/log.h" #include "libc/nt/enum/computernameformat.h" #include "libc/nt/struct/teb.h" #include "libc/runtime/runtime.h" diff --git a/libc/calls/weirdtypes.h b/libc/calls/weirdtypes.h index 4b0cb8a65..de7973ae1 100644 --- a/libc/calls/weirdtypes.h +++ b/libc/calls/weirdtypes.h @@ -48,6 +48,26 @@ typedef __UINT_FAST32_TYPE__ uint_fast32_t; typedef __INT_FAST64_TYPE__ int_fast64_t; typedef __UINT_FAST64_TYPE__ uint_fast64_t; +#define TIME_T_MAX __INT64_MAX__ +#define UINT_FAST64_MAX __UINT_FAST64_MAX__ +#define UINT_FAST8_MAX __UINT_FAST8_MAX__ +#define INT_FAST32_MAX __INT_FAST32_MAX__ +#define INT_FAST16_MAX __INT_FAST16_MAX__ +#define UINT_FAST32_MAX __UINT_FAST32_MAX__ +#define INT_FAST8_MAX __INT_FAST8_MAX__ +#define INT_FAST64_MAX __INT_FAST64_MAX__ +#define UINT_FAST16_MAX __UINT_FAST16_MAX__ + +#define TIME_T_MIN (-TIME_T_MAX - 1) +#define UINT_FAST64_MIN (-UINT_FAST64_MAX - 1) +#define UINT_FAST8_MIN (-UINT_FAST8_MAX - 1) +#define INT_FAST32_MIN (-INT_FAST32_MAX - 1) +#define INT_FAST16_MIN (-INT_FAST16_MAX - 1) +#define UINT_FAST32_MIN (-UINT_FAST32_MAX - 1) +#define INT_FAST8_MIN (-INT_FAST8_MAX - 1) +#define INT_FAST64_MIN (-INT_FAST64_MAX - 1) +#define UINT_FAST16_MIN (-UINT_FAST16_MAX - 1) + #define atomic_bool _Atomic(_Bool) #define atomic_bool32 atomic_int_fast32_t #define atomic_char _Atomic(char) diff --git a/libc/fmt/strerror_wr.greg.c b/libc/fmt/strerror_wr.greg.c index 93b8616c4..8fad73863 100644 --- a/libc/fmt/strerror_wr.greg.c +++ b/libc/fmt/strerror_wr.greg.c @@ -44,17 +44,17 @@ int strerror_wr(int err, uint32_t winerr, char *buf, size_t size) { if (size > 1) *buf++ = c; if (size) *buf = 0; } else if (!IsWindows() || err == winerr || !winerr) { - ksnprintf(buf, size, "%s:%d:%s", sym, err, msg); + ksnprintf(buf, size, "%s/%d/%s", sym, err, msg); } else { if ((n = FormatMessage( kNtFormatMessageFromSystem | kNtFormatMessageIgnoreInserts, 0, winerr, MAKELANGID(kNtLangNeutral, kNtSublangDefault), winmsg, ARRAYLEN(winmsg), 0))) { while ((n && winmsg[n - 1] <= ' ') || winmsg[n - 1] == '.') --n; - ksnprintf(buf, size, "%s:%d:%s:%d:%.*hs", sym, err, msg, winerr, n, + ksnprintf(buf, size, "%s/%d/%s/%d/%.*hs", sym, err, msg, winerr, n, winmsg); } else { - ksnprintf(buf, size, "%s:%d:%s:%d", sym, err, msg, winerr); + ksnprintf(buf, size, "%s/%d/%s/%d", sym, err, msg, winerr); } } return 0; diff --git a/libc/runtime/getsymboltable.greg.c b/libc/runtime/getsymboltable.greg.c index 584170d26..60af4749a 100644 --- a/libc/runtime/getsymboltable.greg.c +++ b/libc/runtime/getsymboltable.greg.c @@ -54,7 +54,7 @@ static ssize_t FindSymtabInZip(struct Zipos *zipos) { * @note This code can't depend on dlmalloc() */ static struct SymbolTable *GetSymbolTableFromZip(struct Zipos *zipos) { - ssize_t cf, lf; + ssize_t rc, cf, lf; size_t size, size2; struct DeflateState ds; struct SymbolTable *res = 0; @@ -67,14 +67,16 @@ static struct SymbolTable *GetSymbolTableFromZip(struct Zipos *zipos) { case kZipCompressionNone: memcpy(res, (void *)ZIP_LFILE_CONTENT(zipos->map + lf), size); break; +#if 0 case kZipCompressionDeflate: - if (undeflate(res, size, (void *)ZIP_LFILE_CONTENT(zipos->map + lf), - GetZipLfileCompressedSize(zipos->map + lf), - &ds) == -1) { + rc = undeflate(res, size, (void *)ZIP_LFILE_CONTENT(zipos->map + lf), + GetZipLfileCompressedSize(zipos->map + lf), &ds); + if (rc == -1) { munmap(res, size2); res = 0; } break; +#endif default: munmap(res, size2); res = 0; @@ -115,11 +117,8 @@ static struct SymbolTable *GetSymbolTableFromElf(void) { * @return symbol table, or NULL w/ errno on first call */ struct SymbolTable *GetSymbolTable(void) { - int ft, st; struct Zipos *z; if (!g_symtab && !__isworker) { - ft = g_ftrace, g_ftrace = 0; - st = __strace, __strace = 0; if (weaken(__zipos_get) && (z = weaken(__zipos_get)())) { if ((g_symtab = GetSymbolTableFromZip(z))) { g_symtab->names = @@ -131,8 +130,6 @@ struct SymbolTable *GetSymbolTable(void) { if (!g_symtab) { g_symtab = GetSymbolTableFromElf(); } - g_ftrace = ft; - __strace = st; } return g_symtab; } diff --git a/libc/str/longsort.c b/libc/str/longsort.c index bdf420b81..fcbb9e366 100644 --- a/libc/str/longsort.c +++ b/libc/str/longsort.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/strace.internal.h" #include "libc/dce.h" #include "libc/intrin/asan.internal.h" #include "libc/macros.internal.h" @@ -80,4 +81,7 @@ void longsort(long *x, size_t n) { longsort_pure(x, n, t); } } + if (n > 1000) { + STRACE("longsort(%p, %'zu)", x, n); + } } diff --git a/libc/str/strchrnul.c b/libc/str/strchrnul.c index 981b9aaa6..3a3125442 100644 --- a/libc/str/strchrnul.c +++ b/libc/str/strchrnul.c @@ -53,6 +53,8 @@ noasan static inline const char *strchrnul_sse(const char *s, unsigned char c) { /** * Returns pointer to first instance of character. * + * If c is not found then a pointer to the nul byte is returned. + * * @param s is a NUL-terminated string * @param c is masked with 255 as byte to search for * @return pointer to first instance of c, or pointer to diff --git a/libc/sysv/consts.sh b/libc/sysv/consts.sh index ec140561e..7cd16c0b2 100755 --- a/libc/sysv/consts.sh +++ b/libc/sysv/consts.sh @@ -1110,9 +1110,6 @@ syscon pf PF_VSOCK 40 0 0 0 0 0 syscon pf PF_WANPIPE 25 0 0 0 0 0 syscon pf PF_X25 9 0 0 0 0 0 -syscon exit EXIT_SUCCESS 0 0 0 0 0 0 # consensus -syscon exit EXIT_FAILURE 1 1 1 1 1 1 # consensus - # Eric Allman's exit() codes # # - Broadly supported style guideline; diff --git a/libc/sysv/consts/exit.h b/libc/sysv/consts/exit.h index b980539d0..70e67d83f 100644 --- a/libc/sysv/consts/exit.h +++ b/libc/sysv/consts/exit.h @@ -1,16 +1,7 @@ #ifndef COSMOPOLITAN_LIBC_SYSV_CONSTS_EXIT_H_ #define COSMOPOLITAN_LIBC_SYSV_CONSTS_EXIT_H_ -#include "libc/runtime/symbolic.h" -#define EXIT_FAILURE SYMBOLIC(EXIT_FAILURE) -#define EXIT_SUCCESS SYMBOLIC(EXIT_SUCCESS) +#define EXIT_FAILURE 1 +#define EXIT_SUCCESS 0 -#if !(__ASSEMBLER__ + __LINKER__ + 0) -COSMOPOLITAN_C_START_ - -extern const long EXIT_FAILURE; -extern const long EXIT_SUCCESS; - -COSMOPOLITAN_C_END_ -#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ #endif /* COSMOPOLITAN_LIBC_SYSV_CONSTS_EXIT_H_ */ diff --git a/libc/time/asctime.c b/libc/time/asctime.c index b17b44e6f..c076e59f1 100644 --- a/libc/time/asctime.c +++ b/libc/time/asctime.c @@ -1,31 +1,114 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/fmt/fmt.h" #include "libc/time/time.h" +#include "libc/time/tz.internal.h" +// clang-format off +/* asctime and asctime_r a la POSIX and ISO C, except pad years before 1000. */ -static char g_asctime_buf[64]; +/* +** This file is in the public domain, so clarified as of +** 1996-06-05 by Arthur David Olson. +*/ -/** - * Converts date time to string. - * - * @return date time string in statically allocated buffer - * @see asctime_r for reentrant version - */ -char *asctime(const struct tm *date) { - return asctime_r(date, g_asctime_buf); +/* +** Avoid the temptation to punt entirely to strftime; +** the output of strftime is supposed to be locale specific +** whereas the output of asctime is supposed to be constant. +*/ + +/* +** Some systems only handle "%.2d"; others only handle "%02d"; +** "%02.2d" makes (most) everybody happy. +** At least some versions of gcc warn about the %02.2d; +** we conditionalize below to avoid the warning. +*/ +/* +** All years associated with 32-bit time_t values are exactly four digits long; +** some years associated with 64-bit time_t values are not. +** Vintage programs are coded for years that are always four digits long +** and may assume that the newline always lands in the same place. +** For years that are less than four digits, we pad the output with +** leading zeroes to get the newline in the traditional place. +** The -4 ensures that we get four characters of output even if +** we call a strftime variant that produces fewer characters for some years. +** The ISO C and POSIX standards prohibit padding the year, +** but many implementations pad anyway; most likely the standards are buggy. +*/ +#ifdef __GNUC__ +#define ASCTIME_FMT "%s %s%3d %2.2d:%2.2d:%2.2d %-4s\n" +#else /* !defined __GNUC__ */ +#define ASCTIME_FMT "%s %s%3d %02.2d:%02.2d:%02.2d %-4s\n" +#endif /* !defined __GNUC__ */ +/* +** For years that are more than four digits we put extra spaces before the year +** so that code trying to overwrite the newline won't end up overwriting +** a digit within a year and truncating the year (operating on the assumption +** that no output is better than wrong output). +*/ +#ifdef __GNUC__ +#define ASCTIME_FMT_B "%s %s%3d %2.2d:%2.2d:%2.2d %s\n" +#else /* !defined __GNUC__ */ +#define ASCTIME_FMT_B "%s %s%3d %02.2d:%02.2d:%02.2d %s\n" +#endif /* !defined __GNUC__ */ + +#define STD_ASCTIME_BUF_SIZE 26 +/* +** Big enough for something such as +** ??? ???-2147483648 -2147483648:-2147483648:-2147483648 -2147483648\n +** (two three-character abbreviations, five strings denoting integers, +** seven explicit spaces, two explicit colons, a newline, +** and a trailing NUL byte). +** The values above are for systems where an int is 32 bits and are provided +** as an example; the define below calculates the maximum for the system at +** hand. +*/ +#define MAX_ASCTIME_BUF_SIZE (2*3+5*INT_STRLEN_MAXIMUM(int)+7+2+1+1) + +static char buf_asctime[MAX_ASCTIME_BUF_SIZE]; + +char * +asctime_r(register const struct tm *timeptr, char *buf) +{ + register const char * wn; + register const char * mn; + char year[INT_STRLEN_MAXIMUM(int) + 2]; + char result[MAX_ASCTIME_BUF_SIZE]; + + if (timeptr == NULL) { + errno = EINVAL; + return strcpy(buf, "??? ??? ?? ??:??:?? ????\n"); + } + if (timeptr->tm_wday < 0 || timeptr->tm_wday >= DAYSPERWEEK) + wn = "???"; + else wn = kWeekdayNameShort[timeptr->tm_wday]; + if (timeptr->tm_mon < 0 || timeptr->tm_mon >= MONSPERYEAR) + mn = "???"; + else mn = kMonthNameShort[timeptr->tm_mon]; + /* + ** Use strftime's %Y to generate the year, to avoid overflow problems + ** when computing timeptr->tm_year + TM_YEAR_BASE. + ** Assume that strftime is unaffected by other out-of-range members + ** (e.g., timeptr->tm_mday) when processing "%Y". + */ + strftime(year, sizeof year, "%Y", timeptr); + /* + ** We avoid using snprintf since it's not available on all systems. + */ + (sprintf)(result, + ((strlen(year) <= 4) ? ASCTIME_FMT : ASCTIME_FMT_B), + wn, mn, + timeptr->tm_mday, timeptr->tm_hour, + timeptr->tm_min, timeptr->tm_sec, + year); + if (strlen(result) < STD_ASCTIME_BUF_SIZE || buf == buf_asctime) + return strcpy(buf, result); + else { + errno = EOVERFLOW; + return NULL; + } +} + +char * +asctime(register const struct tm *timeptr) +{ + return asctime_r(timeptr, buf_asctime); } diff --git a/libc/time/asctime_r.c b/libc/time/asctime_r.c deleted file mode 100644 index fda926f26..000000000 --- a/libc/time/asctime_r.c +++ /dev/null @@ -1,40 +0,0 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/fmt/fmt.h" -#include "libc/time/struct/tm.h" -#include "libc/time/time.h" - -static unsigned clip(unsigned index, unsigned count) { - return index < count ? index : 0; -} - -/** - * Converts date time to string. - * - * @param buf needs to have 64 bytes - * @return pointer to buf - * @see asctime_r for reentrant version - */ -char *asctime_r(const struct tm *date, char buf[hasatleast 64]) { - (snprintf)(buf, 64, "%.3s %.3s%3d %.2d:%.2d:%.2d %d\n", - kWeekdayNameShort[clip(date->tm_wday, 7)], - kMonthNameShort[clip(date->tm_mon, 12)], date->tm_mday, - date->tm_hour, date->tm_min, date->tm_sec, 1900 + date->tm_year); - return buf; -} diff --git a/libc/time/ctime.c b/libc/time/ctime.c index e0e34ebfb..feedb7676 100644 --- a/libc/time/ctime.c +++ b/libc/time/ctime.c @@ -1,21 +1,13 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/weirdtypes.h" #include "libc/time/time.h" -char *ctime(const int64_t *timep) { return asctime(localtime(timep)); } +char *ctime(const time_t *timep) { + /* + ** Section 4.12.3.2 of X3.159-1989 requires that + ** The ctime function converts the calendar time pointed to by timer + ** to local time in the form of a string. It is equivalent to + ** asctime(localtime(timer)) + */ + struct tm *tmp = localtime(timep); + return tmp ? asctime(tmp) : NULL; +} diff --git a/libc/time/ctime_r.c b/libc/time/ctime_r.c index ddfd57b52..c830a081c 100644 --- a/libc/time/ctime_r.c +++ b/libc/time/ctime_r.c @@ -1,25 +1,9 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ -╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/weirdtypes.h" #include "libc/time/struct/tm.h" #include "libc/time/time.h" -char *ctime_r(const int64_t *timep, char buf[hasatleast 64]) { - struct tm date[1]; - return asctime_r(localtime_r(timep, date), buf); +char *ctime_r(const time_t *timep, char *buf) { + struct tm mytm; + struct tm *tmp = localtime_r(timep, &mytm); + return tmp ? asctime_r(tmp, buf) : NULL; } diff --git a/libc/time/difftime.c b/libc/time/difftime.c index 435401775..00f1daa00 100644 --- a/libc/time/difftime.c +++ b/libc/time/difftime.c @@ -1,21 +1,61 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ -╞══════════════════════════════════════════════════════════════════════════════╡ -│ Copyright 2020 Justine Alexandra Roberts Tunney │ -│ │ -│ Permission to use, copy, modify, and/or distribute this software for │ -│ any purpose with or without fee is hereby granted, provided that the │ -│ above copyright notice and this permission notice appear in all copies. │ -│ │ -│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ -│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ -│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ -│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ -│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ -│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ -│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ -│ PERFORMANCE OF THIS SOFTWARE. │ +/*-*- mode:c; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/time/time.h" +#include "libc/time/tz.internal.h" +// clang-format off +/* Return the difference between two timestamps. */ -double difftime(int64_t x, int64_t y) { return x - y; } +/* +** This file is in the public domain, so clarified as of +** 1996-06-05 by Arthur David Olson. +*/ + +/* Return -X as a double. Using this avoids casting to 'double'. */ +static inline double +dminus(double x) +{ + return -x; +} + +double +difftime(time_t time1, time_t time0) +{ + /* + ** If double is large enough, simply convert and subtract + ** (assuming that the larger type has more precision). + */ + if (sizeof(time_t) < sizeof(double)) { + double t1 = time1, t0 = time0; + return t1 - t0; + } + + /* + ** The difference of two unsigned values can't overflow + ** if the minuend is greater than or equal to the subtrahend. + */ + if (!TYPE_SIGNED(time_t)) + return time0 <= time1 ? time1 - time0 : dminus(time0 - time1); + + /* Use uintmax_t if wide enough. */ + if (sizeof(time_t) <= sizeof(uintmax_t)) { + uintmax_t t1 = time1, t0 = time0; + return time0 <= time1 ? t1 - t0 : dminus(t0 - t1); + } + + /* + ** Handle cases where both time1 and time0 have the same sign + ** (meaning that their difference cannot overflow). + */ + if ((time1 < 0) == (time0 < 0)) + return time1 - time0; + + /* + ** The values have opposite signs and uintmax_t is too narrow. + ** This suffers from double rounding; attempt to lessen that + ** by using long double temporaries. + */ + { + long double t1 = time1, t0 = time0; + return t1 - t0; + } +} diff --git a/libc/time/localtime.c b/libc/time/localtime.c index a79d0330c..68ac75a34 100644 --- a/libc/time/localtime.c +++ b/libc/time/localtime.c @@ -1,35 +1,18 @@ /*-*- mode:c; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ │vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/bits/initializer.internal.h" +#define LOCALTIME_IMPLEMENTATION #include "libc/calls/calls.h" -#include "libc/macros.internal.h" -#include "libc/math.h" -#include "libc/mem/mem.h" -#include "libc/nexgen32e/nexgen32e.h" -#include "libc/runtime/runtime.h" +#include "libc/intrin/spinlock.h" #include "libc/str/str.h" #include "libc/sysv/consts/o.h" -#include "libc/time/struct/tm.h" #include "libc/time/time.h" +#include "libc/time/tz.internal.h" #include "libc/time/tzfile.internal.h" -#define ALL_STATE - -#define time_t int64_t -#define int_fast64_t int64_t -#define int_fast32_t int32_t -#define GRANDPARENTED "local time zone must be set" -#define AVGSECSPERYEAR 31556952L -#define SECSPERREPEAT \ - ((int_fast64_t)YEARSPERREPEAT * (int_fast64_t)AVGSECSPERYEAR) -#define SECSPERREPEAT_BITS 34 /* ceil(log2(SECSPERREPEAT)) */ -#define YEARSPERREPEAT 400 /* years before a Gregorian repeat */ -#define TM_ZONE tm_zone -#define INITIALIZE(x) x = 0 - STATIC_YOINK("zip_uri_support"); STATIC_YOINK("usr/share/zoneinfo/"); +STATIC_YOINK("usr/share/zoneinfo/Anchorage"); STATIC_YOINK("usr/share/zoneinfo/Beijing"); STATIC_YOINK("usr/share/zoneinfo/Berlin"); STATIC_YOINK("usr/share/zoneinfo/Boulder"); @@ -41,26 +24,31 @@ STATIC_YOINK("usr/share/zoneinfo/Japan"); STATIC_YOINK("usr/share/zoneinfo/London"); STATIC_YOINK("usr/share/zoneinfo/Melbourne"); STATIC_YOINK("usr/share/zoneinfo/New_York"); -STATIC_YOINK("usr/share/zoneinfo/Singapore"); STATIC_YOINK("usr/share/zoneinfo/UTC"); -/* clang-format off */ +// clang-format off +/* Convert timestamp from time_t to struct tm. */ /* ** This file is in the public domain, so clarified as of ** 1996-06-05 by Arthur David Olson. */ -/* #ifndef lint */ -/* #ifndef NOID */ -/* static char elsieid[] = "@(#)localtime.c 8.3"; */ -/* #endif /\* !defined NOID *\/ */ -/* #endif /\* !defined lint *\/ */ - /* ** Leap second handling from Bradley White. ** POSIX-style TZ environment variable handling from Guy Harris. */ +_Alignas(64) static char locallock; + +static int lock(void) { + _spinlock(&locallock); + return 0; +} + +static void unlock(void) { + _spunlock(&locallock); +} + #ifndef TZ_ABBR_MAX_LEN #define TZ_ABBR_MAX_LEN 16 #endif /* !defined TZ_ABBR_MAX_LEN */ @@ -98,36 +86,44 @@ STATIC_YOINK("usr/share/zoneinfo/UTC"); #endif /* !defined WILDABBR */ static const char wildabbr[] = WILDABBR; -static char wildabbr2[sizeof(WILDABBR)]; -static const char gmt[] = "UTC"; +static const char gmt[] = "GMT"; /* ** The DST rules to use if TZ has no rules and we can't load TZDEFRULES. -** We default to US rules as of 1999-08-17. -** POSIX 1003.1 section 8.1.1 says that the default DST rules are -** implementation dependent; for historical reasons, US rules are a -** common default. +** Default to US rules as of 2017-05-07. +** POSIX does not specify the default DST rules; +** for historical reasons, US rules are a common default. */ #ifndef TZDEFRULESTRING -#define TZDEFRULESTRING ",M4.1.0,M10.5.0" -#endif /* !defined TZDEFDST */ +#define TZDEFRULESTRING ",M3.2.0,M11.1.0" +#endif struct ttinfo { /* time type information */ - long tt_gmtoff; /* UTC offset in seconds */ - int tt_isdst; /* used to set tm_isdst */ - int tt_abbrind; /* abbreviation list index */ - int tt_ttisstd; /* TRUE if transition is std time */ - int tt_ttisgmt; /* TRUE if transition is UTC */ + int_fast32_t tt_utoff; /* UT offset in seconds */ + bool tt_isdst; /* used to set tm_isdst */ + int tt_desigidx; /* abbreviation list index */ + bool tt_ttisstd; /* transition is std time */ + bool tt_ttisut; /* transition is UT */ }; struct lsinfo { /* leap second information */ time_t ls_trans; /* transition time */ - long ls_corr; /* correction to apply */ + int_fast32_t ls_corr; /* correction to apply */ }; +#define SMALLEST(a, b) (((a) < (b)) ? (a) : (b)) #define BIGGEST(a, b) (((a) > (b)) ? (a) : (b)) +/* This abbreviation means local time is unspecified. */ +static char const UNSPEC[] = "-00"; + +/* How many extra bytes are needed at the end of struct state's chars array. + This needs to be at least 1 for null termination in case the input + data isn't properly terminated, and it also needs to be big enough + for ttunspecified to work without crashing. */ +enum { CHARS_EXTRA = BIGGEST(sizeof UNSPEC, 2) - 1 }; + #ifdef TZNAME_MAX #define MY_TZNAME_MAX TZNAME_MAX #endif /* defined TZNAME_MAX */ @@ -140,63 +136,46 @@ struct state { int timecnt; int typecnt; int charcnt; - int goback; - int goahead; + bool goback; + bool goahead; time_t ats[TZ_MAX_TIMES]; unsigned char types[TZ_MAX_TIMES]; struct ttinfo ttis[TZ_MAX_TYPES]; - char chars[BIGGEST(BIGGEST(TZ_MAX_CHARS + 1, sizeof gmt), + char chars[BIGGEST(BIGGEST(TZ_MAX_CHARS + CHARS_EXTRA, + sizeof gmt), (2 * (MY_TZNAME_MAX + 1)))]; struct lsinfo lsis[TZ_MAX_LEAPS]; + + /* The time type to use for early times or if no transitions. + It is always zero for recent tzdb releases. + It might be nonzero for data from tzdb 2018e or earlier. */ + int defaulttype; +}; + +enum r_type { + JULIAN_DAY, /* Jn = Julian day */ + DAY_OF_YEAR, /* n = day of year */ + MONTH_NTH_DAY_OF_WEEK /* Mm.n.d = month, week, day of week */ }; struct rule { - int r_type; /* type of rule--see below */ + enum r_type r_type; /* type of rule */ int r_day; /* day number of rule */ int r_week; /* week number of rule */ int r_mon; /* month number of rule */ - int32_t r_time; /* transition time of rule */ + int_fast32_t r_time; /* transition time of rule */ }; -#define JULIAN_DAY 0 /* Jn - Julian day */ -#define DAY_OF_YEAR 1 /* n - day of year */ -#define MONTH_NTH_DAY_OF_WEEK 2 /* Mm.n.d - month, week, day of week */ - -/* -** Prototypes for static functions. -*/ - -static int32_t detzcode(const char *); -static time_t detzcode64(const char *); -static int differ_by_repeat(time_t, time_t); -static const char * getzname(const char *); -static const char * getqzname(const char *, const int); -static const char * getnum(const char *, int *, int, int); -static const char * getsecs(const char *, int32_t *); -static const char * getoffset(const char *, int32_t *); -static const char * getrule(const char *, struct rule *); -static void gmtload(struct state *); -static struct tm * gmtsub(const time_t *, int32_t, struct tm *); -static struct tm * localsub(const time_t *, int32_t, struct tm *); -static int increment_overflow(int *, int); -static int leaps_thru_end_of(int); -static int normalize_overflow(int *, int *, int); -static void settzname(void); -static time_t time1(struct tm *, struct tm * (*)(const time_t *, - int32_t, struct tm *), - int32_t); -static time_t time2(struct tm *, struct tm *(*)(const time_t *, - int32_t, struct tm *), - int32_t, int *); -static time_t time2sub(struct tm *, struct tm *(*)(const time_t *, - int32_t, struct tm*), - int32_t, int *, int); -static struct tm * timesub(const time_t *, int32_t, - const struct state *, struct tm *); -static int tmcomp(const struct tm *, const struct tm *); -static time_t transtime(time_t, int, const struct rule *, int32_t); -static int tzload(const char *, struct state *, int); -static int tzparse(const char *, struct state *, int); +static struct tm *gmtsub(struct state const *, time_t const *, int_fast32_t, + struct tm *); +static bool increment_overflow(int *, int); +static bool increment_overflow_time(time_t *, int_fast32_t); +static int_fast32_t leapcorr(struct state const *, time_t); +static bool normalize_overflow32(int_fast32_t *, int *, int); +static struct tm *timesub(time_t const *, int_fast32_t, struct state const *, + struct tm *); +static bool typesequiv(struct state const *, int, int); +static bool tzparse(char const *, struct state *, struct state *); #ifdef ALL_STATE static struct state * lclptr; @@ -216,18 +195,6 @@ static struct state gmtmem; static char lcl_TZname[TZ_STRLEN_MAX + 1]; static int lcl_is_set; -static int gmt_is_set; - -char * tzname[2] /* = { */ -/* wildabbr, */ -/* wildabbr */ -/* } */; - -INITIALIZER(400, _init_localtime, { - memcpy(wildabbr2, wildabbr, sizeof(WILDABBR)); - tzname[0] = wildabbr2; - tzname[1] = wildabbr2; -}) /* ** Section 4.12.3 of X3.159-1989 requires that @@ -239,87 +206,145 @@ INITIALIZER(400, _init_localtime, { static struct tm tm; -#ifdef USG_COMPAT -time_t timezone; +#if 2 <= HAVE_TZNAME + TZ_TIME_T +char * tzname[2] = { + (char *) wildabbr, + (char *) wildabbr +}; +#endif +#if 2 <= USG_COMPAT + TZ_TIME_T +long timezone; int daylight; -#endif /* defined USG_COMPAT */ +#endif +#if 2 <= ALTZONE + TZ_TIME_T +long altzone; +#endif -#ifdef ALTZONE -time_t altzone; -#endif /* defined ALTZONE */ +/* Initialize *S to a value based on UTOFF, ISDST, and DESIGIDX. */ +static void +init_ttinfo(struct ttinfo *s, int_fast32_t utoff, bool isdst, int desigidx) +{ + s->tt_utoff = utoff; + s->tt_isdst = isdst; + s->tt_desigidx = desigidx; + s->tt_ttisstd = false; + s->tt_ttisut = false; +} -static int32_t -detzcode( - const char * const codep -) { - register int32_t result; +/* Return true if SP's time type I does not specify local time. */ +static bool +ttunspecified(struct state const *sp, int i) +{ + char const *abbr = &sp->chars[sp->ttis[i].tt_desigidx]; + /* memcmp is likely faster than strcmp, and is safe due to CHARS_EXTRA. */ + return memcmp(abbr, UNSPEC, sizeof UNSPEC) == 0; +} + +static int_fast32_t +detzcode(const char *const codep) +{ + register int_fast32_t result; register int i; - result = (codep[0] & 0x80) ? ~0L : 0; - for (i = 0; i < 4; ++i) - result = ((unsigned)result << 8) | (codep[i] & 0xff); + int_fast32_t one = 1; + int_fast32_t halfmaxval = one << (32 - 2); + int_fast32_t maxval = halfmaxval - 1 + halfmaxval; + int_fast32_t minval = -1 - maxval; + + result = codep[0] & 0x7f; + for (i = 1; i < 4; ++i) + result = (result << 8) | (codep[i] & 0xff); + + if (codep[0] & 0x80) { + /* Do two's-complement negation even on non-two's-complement machines. + If the result would be minval - 1, return minval. */ + result -= !TWOS_COMPLEMENT(int_fast32_t) && result != 0; + result += minval; + } return result; } -static time_t -detzcode64( - const char * const codep -) { - register time_t result; - register int i; - result = (codep[0] & 0x80) ? (~(int_fast64_t) 0) : 0; - for (i = 0; i < 8; ++i) - result = result * 256 + (codep[i] & 0xff); +static int_fast64_t +detzcode64(const char *const codep) +{ + register int_fast64_t result; + register int i; + int_fast64_t one = 1; + int_fast64_t halfmaxval = one << (64 - 2); + int_fast64_t maxval = halfmaxval - 1 + halfmaxval; + int_fast64_t minval = -TWOS_COMPLEMENT(int_fast64_t) - maxval; + + result = codep[0] & 0x7f; + for (i = 1; i < 8; ++i) + result = (result << 8) | (codep[i] & 0xff); + + if (codep[0] & 0x80) { + /* Do two's-complement negation even on non-two's-complement machines. + If the result would be minval - 1, return minval. */ + result -= !TWOS_COMPLEMENT(int_fast64_t) && result != 0; + result += minval; + } return result; } +static void +update_tzname_etc(struct state const *sp, struct ttinfo const *ttisp) +{ +#if HAVE_TZNAME + tzname[ttisp->tt_isdst] = (char *) &sp->chars[ttisp->tt_desigidx]; +#endif +#if USG_COMPAT + if (!ttisp->tt_isdst) + timezone = - ttisp->tt_utoff; +#endif +#if ALTZONE + if (ttisp->tt_isdst) + altzone = - ttisp->tt_utoff; +#endif +} + static void settzname(void) { - register struct state * sp; - register int i; - sp = lclptr; - tzname[0] = wildabbr2; - tzname[1] = wildabbr2; -#ifdef USG_COMPAT + register struct state * const sp = lclptr; + register int i; + +#if HAVE_TZNAME + tzname[0] = tzname[1] = (char *) (sp ? wildabbr : gmt); +#endif +#if USG_COMPAT daylight = 0; timezone = 0; -#endif /* defined USG_COMPAT */ -#ifdef ALTZONE +#endif +#if ALTZONE altzone = 0; -#endif /* defined ALTZONE */ -#ifdef ALL_STATE +#endif if (sp == NULL) { - tzname[0] = tzname[1] = gmt; return; } -#endif /* defined ALL_STATE */ + /* + ** And to get the latest time zone abbreviations into tzname. . . + */ for (i = 0; i < sp->typecnt; ++i) { register const struct ttinfo * const ttisp = &sp->ttis[i]; - tzname[ttisp->tt_isdst] = - &sp->chars[ttisp->tt_abbrind]; -#ifdef USG_COMPAT - if (ttisp->tt_isdst) - daylight = 1; - if (i == 0 || !ttisp->tt_isdst) - timezone = -(ttisp->tt_gmtoff); -#endif /* defined USG_COMPAT */ -#ifdef ALTZONE - if (i == 0 || ttisp->tt_isdst) - altzone = -(ttisp->tt_gmtoff); -#endif /* defined ALTZONE */ + update_tzname_etc(sp, ttisp); } - /* - ** And to get the latest zone names into tzname. . . - */ for (i = 0; i < sp->timecnt; ++i) { register const struct ttinfo * const ttisp = &sp->ttis[ sp->types[i]]; - tzname[ttisp->tt_isdst] = - &sp->chars[ttisp->tt_abbrind]; + update_tzname_etc(sp, ttisp); +#if USG_COMPAT + if (ttisp->tt_isdst) + daylight = 1; +#endif } +} + +static void +scrub_abbrs(struct state *sp) +{ + int i; /* - ** Finally, scrub the abbreviations. ** First, replace bogus characters. */ for (i = 0; i < sp->charcnt; ++i) @@ -330,330 +355,521 @@ settzname(void) */ for (i = 0; i < sp->typecnt; ++i) { register const struct ttinfo * const ttisp = &sp->ttis[i]; - register char * cp = &sp->chars[ttisp->tt_abbrind]; + char *cp = &sp->chars[ttisp->tt_desigidx]; + if (strlen(cp) > TZ_ABBR_MAX_LEN && strcmp(cp, GRANDPARENTED) != 0) *(cp + TZ_ABBR_MAX_LEN) = '\0'; } } -forceinline int -differ_by_repeat( - const time_t t1, - const time_t t0 -) { - if (TYPE_INTEGRAL(time_t) && - TYPE_BIT(time_t) - TYPE_SIGNED(time_t) < SECSPERREPEAT_BITS) - return 0; - return (t1 - t0) == SECSPERREPEAT; -} +/* Input buffer for data read from a compiled tz file. */ +union input_buffer { + /* The first part of the buffer, interpreted as a header. */ + struct tzhead tzhead; -forceinline int -cmpstr( - const char *l, - const char *r -) { - size_t i = 0; - while (l[i] == r[i] && r[i]) ++i; - return (l[i] & 0xff) - (r[i] & 0xff); -} + /* The entire buffer. */ + char buf[2 * sizeof(struct tzhead) + 2 * sizeof(struct state) + + 4 * TZ_MAX_TIMES]; +}; +/* TZDIR with a trailing '/' rather than a trailing '\0'. */ +static char const tzdirslash[sizeof TZDIR] = TZDIR "/"; + +/* Local storage needed for 'tzloadbody'. */ +union local_storage { + /* The results of analyzing the file's contents after it is opened. */ + struct file_analysis { + /* The input buffer. */ + union input_buffer u; + + /* A temporary state used for parsing a TZ string in the file. */ + struct state st; + } u; + + /* The file name to be opened. */ + char fullname[BIGGEST(sizeof(struct file_analysis), + sizeof tzdirslash + 1024)]; +}; + +/* Load tz data from the file named NAME into *SP. Read extended + format if DOEXTEND. Use *LSP for temporary storage. Return 0 on + success, an errno value on failure. */ static int -typesequiv( - const struct state *sp, - int a, - int b -) { - int result; +tzloadbody(char const *name, struct state *sp, bool doextend, + union local_storage *lsp) +{ + register int i; + register int fid; + register int stored; + register ssize_t nread; + register bool doaccess; + register union input_buffer *up = &lsp->u.u; + register int tzheadsize = sizeof(struct tzhead); + + sp->goback = sp->goahead = false; + + if (! name) { + name = TZDEFAULT; + if (! name) + return EINVAL; + } + + if (name[0] == ':') + ++name; +#ifdef SUPPRESS_TZDIR + /* Do not prepend TZDIR. This is intended for specialized + applications only, due to its security implications. */ + doaccess = true; +#else + doaccess = name[0] == '/'; +#endif + if (!doaccess) { + char const *dot; + size_t namelen = strlen(name); + if (sizeof lsp->fullname - sizeof tzdirslash <= namelen) + return ENAMETOOLONG; + + /* Create a string "TZDIR/NAME". Using sprintf here + would pull in stdio (and would fail if the + resulting string length exceeded INT_MAX!). */ + memcpy(lsp->fullname, tzdirslash, sizeof tzdirslash); + strcpy(lsp->fullname + sizeof tzdirslash, name); + + /* Set doaccess if NAME contains a ".." file name + component, as such a name could read a file outside + the TZDIR virtual subtree. */ + for (dot = name; (dot = strchr(dot, '.')); dot++) + if ((dot == name || dot[-1] == '/') && dot[1] == '.' + && (dot[2] == '/' || !dot[2])) { + doaccess = true; + break; + } + + name = lsp->fullname; + } + if (doaccess && access(name, R_OK) != 0) + return errno; + fid = open(name, O_RDONLY); + if (fid < 0) + return errno; + + nread = read(fid, up->buf, sizeof up->buf); + if (nread < tzheadsize) { + int err = nread < 0 ? errno : EINVAL; + close(fid); + return err; + } + if (close(fid) < 0) + return errno; + for (stored = 4; stored <= 8; stored *= 2) { + char version = up->tzhead.tzh_version[0]; + bool skip_datablock = stored == 4 && version; + int_fast32_t datablock_size; + int_fast32_t ttisstdcnt = detzcode(up->tzhead.tzh_ttisstdcnt); + int_fast32_t ttisutcnt = detzcode(up->tzhead.tzh_ttisutcnt); + int_fast64_t prevtr = -1; + int_fast32_t prevcorr = 0; + int_fast32_t leapcnt = detzcode(up->tzhead.tzh_leapcnt); + int_fast32_t timecnt = detzcode(up->tzhead.tzh_timecnt); + int_fast32_t typecnt = detzcode(up->tzhead.tzh_typecnt); + int_fast32_t charcnt = detzcode(up->tzhead.tzh_charcnt); + char const *p = up->buf + tzheadsize; + /* Although tzfile(5) currently requires typecnt to be nonzero, + support future formats that may allow zero typecnt + in files that have a TZ string and no transitions. */ + if (! (0 <= leapcnt && leapcnt < TZ_MAX_LEAPS + && 0 <= typecnt && typecnt < TZ_MAX_TYPES + && 0 <= timecnt && timecnt < TZ_MAX_TIMES + && 0 <= charcnt && charcnt < TZ_MAX_CHARS + && 0 <= ttisstdcnt && ttisstdcnt < TZ_MAX_TYPES + && 0 <= ttisutcnt && ttisutcnt < TZ_MAX_TYPES)) + return EINVAL; + datablock_size + = (timecnt * stored /* ats */ + + timecnt /* types */ + + typecnt * 6 /* ttinfos */ + + charcnt /* chars */ + + leapcnt * (stored + 4) /* lsinfos */ + + ttisstdcnt /* ttisstds */ + + ttisutcnt); /* ttisuts */ + if (nread < tzheadsize + datablock_size) + return EINVAL; + if (skip_datablock) + p += datablock_size; + else { + if (! ((ttisstdcnt == typecnt || ttisstdcnt == 0) + && (ttisutcnt == typecnt || ttisutcnt == 0))) + return EINVAL; + + sp->leapcnt = leapcnt; + sp->timecnt = timecnt; + sp->typecnt = typecnt; + sp->charcnt = charcnt; + + /* Read transitions, discarding those out of time_t range. + But pretend the last transition before TIME_T_MIN + occurred at TIME_T_MIN. */ + timecnt = 0; + for (i = 0; i < sp->timecnt; ++i) { + int_fast64_t at + = stored == 4 ? detzcode(p) : detzcode64(p); + sp->types[i] = at <= TIME_T_MAX; + if (sp->types[i]) { + time_t attime + = ((TYPE_SIGNED(time_t) ? at < TIME_T_MIN : at < 0) + ? TIME_T_MIN : at); + if (timecnt && attime <= sp->ats[timecnt - 1]) { + if (attime < sp->ats[timecnt - 1]) + return EINVAL; + sp->types[i - 1] = 0; + timecnt--; + } + sp->ats[timecnt++] = attime; + } + p += stored; + } + + timecnt = 0; + for (i = 0; i < sp->timecnt; ++i) { + unsigned char typ = *p++; + if (sp->typecnt <= typ) + return EINVAL; + if (sp->types[i]) + sp->types[timecnt++] = typ; + } + sp->timecnt = timecnt; + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + unsigned char isdst, desigidx; + + ttisp = &sp->ttis[i]; + ttisp->tt_utoff = detzcode(p); + p += 4; + isdst = *p++; + if (! (isdst < 2)) + return EINVAL; + ttisp->tt_isdst = isdst; + desigidx = *p++; + if (! (desigidx < sp->charcnt)) + return EINVAL; + ttisp->tt_desigidx = desigidx; + } + for (i = 0; i < sp->charcnt; ++i) + sp->chars[i] = *p++; + /* Ensure '\0'-terminated, and make it safe to call + ttunspecified later. */ + memset(&sp->chars[i], 0, CHARS_EXTRA); + + /* Read leap seconds, discarding those out of time_t range. */ + leapcnt = 0; + for (i = 0; i < sp->leapcnt; ++i) { + int_fast64_t tr = stored == 4 ? detzcode(p) : detzcode64(p); + int_fast32_t corr = detzcode(p + stored); + p += stored + 4; + + /* Leap seconds cannot occur before the Epoch, + or out of order. */ + if (tr <= prevtr) + return EINVAL; + + /* To avoid other botches in this code, each leap second's + correction must differ from the previous one's by 1 + second or less, except that the first correction can be + any value; these requirements are more generous than + RFC 8536, to allow future RFC extensions. */ + if (! (i == 0 + || (prevcorr < corr + ? corr == prevcorr + 1 + : (corr == prevcorr + || corr == prevcorr - 1)))) + return EINVAL; + prevtr = tr; + prevcorr = corr; + + if (tr <= TIME_T_MAX) { + sp->lsis[leapcnt].ls_trans = tr; + sp->lsis[leapcnt].ls_corr = corr; + leapcnt++; + } + } + sp->leapcnt = leapcnt; + + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + + ttisp = &sp->ttis[i]; + if (ttisstdcnt == 0) + ttisp->tt_ttisstd = false; + else { + if (*p != true && *p != false) + return EINVAL; + ttisp->tt_ttisstd = *p++; + } + } + for (i = 0; i < sp->typecnt; ++i) { + register struct ttinfo * ttisp; + + ttisp = &sp->ttis[i]; + if (ttisutcnt == 0) + ttisp->tt_ttisut = false; + else { + if (*p != true && *p != false) + return EINVAL; + ttisp->tt_ttisut = *p++; + } + } + } + + nread -= p - up->buf; + memmove(up->buf, p, nread); + + /* If this is an old file, we're done. */ + if (!version) + break; + } + if (doextend && nread > 2 && + up->buf[0] == '\n' && up->buf[nread - 1] == '\n' && + sp->typecnt + 2 <= TZ_MAX_TYPES) { + struct state *ts = &lsp->u.st; + + up->buf[nread - 1] = '\0'; + if (tzparse(&up->buf[1], ts, sp)) { + + /* Attempt to reuse existing abbreviations. + Without this, America/Anchorage would be right on + the edge after 2037 when TZ_MAX_CHARS is 50, as + sp->charcnt equals 40 (for LMT AST AWT APT AHST + AHDT YST AKDT AKST) and ts->charcnt equals 10 + (for AKST AKDT). Reusing means sp->charcnt can + stay 40 in this example. */ + int gotabbr = 0; + int charcnt = sp->charcnt; + for (i = 0; i < ts->typecnt; i++) { + char *tsabbr = ts->chars + ts->ttis[i].tt_desigidx; + int j; + for (j = 0; j < charcnt; j++) + if (strcmp(sp->chars + j, tsabbr) == 0) { + ts->ttis[i].tt_desigidx = j; + gotabbr++; + break; + } + if (! (j < charcnt)) { + int tsabbrlen = strlen(tsabbr); + if (j + tsabbrlen < TZ_MAX_CHARS) { + strcpy(sp->chars + j, tsabbr); + charcnt = j + tsabbrlen + 1; + ts->ttis[i].tt_desigidx = j; + gotabbr++; + } + } + } + if (gotabbr == ts->typecnt) { + sp->charcnt = charcnt; + + /* Ignore any trailing, no-op transitions generated + by zic as they don't help here and can run afoul + of bugs in zic 2016j or earlier. */ + while (1 < sp->timecnt + && (sp->types[sp->timecnt - 1] + == sp->types[sp->timecnt - 2])) + sp->timecnt--; + + for (i = 0; + i < ts->timecnt && sp->timecnt < TZ_MAX_TIMES; + i++) { + time_t t = ts->ats[i]; + if (increment_overflow_time(&t, leapcorr(sp, t)) + || (0 < sp->timecnt + && t <= sp->ats[sp->timecnt - 1])) + continue; + sp->ats[sp->timecnt] = t; + sp->types[sp->timecnt] = (sp->typecnt + + ts->types[i]); + sp->timecnt++; + } + for (i = 0; i < ts->typecnt; i++) + sp->ttis[sp->typecnt++] = ts->ttis[i]; + } + } + } + if (sp->typecnt == 0) + return EINVAL; + if (sp->timecnt > 1) { + if (sp->ats[0] <= TIME_T_MAX - SECSPERREPEAT) { + time_t repeatat = sp->ats[0] + SECSPERREPEAT; + int repeattype = sp->types[0]; + for (i = 1; i < sp->timecnt; ++i) + if (sp->ats[i] == repeatat + && typesequiv(sp, sp->types[i], repeattype)) { + sp->goback = true; + break; + } + } + if (TIME_T_MIN + SECSPERREPEAT <= sp->ats[sp->timecnt - 1]) { + time_t repeatat = sp->ats[sp->timecnt - 1] - SECSPERREPEAT; + int repeattype = sp->types[sp->timecnt - 1]; + for (i = sp->timecnt - 2; i >= 0; --i) + if (sp->ats[i] == repeatat + && typesequiv(sp, sp->types[i], repeattype)) { + sp->goahead = true; + break; + } + } + } + + /* Infer sp->defaulttype from the data. Although this default + type is always zero for data from recent tzdb releases, + things are trickier for data from tzdb 2018e or earlier. + + The first set of heuristics work around bugs in 32-bit data + generated by tzdb 2013c or earlier. The workaround is for + zones like Australia/Macquarie where timestamps before the + first transition have a time type that is not the earliest + standard-time type. See: + https://mm.icann.org/pipermail/tz/2013-May/019368.html */ + /* + ** If type 0 does not specify local time, or is unused in transitions, + ** it's the type to use for early times. + */ + for (i = 0; i < sp->timecnt; ++i) + if (sp->types[i] == 0) + break; + i = i < sp->timecnt && ! ttunspecified(sp, 0) ? -1 : 0; + /* + ** Absent the above, + ** if there are transition times + ** and the first transition is to a daylight time + ** find the standard type less than and closest to + ** the type of the first transition. + */ + if (i < 0 && sp->timecnt > 0 && sp->ttis[sp->types[0]].tt_isdst) { + i = sp->types[0]; + while (--i >= 0) + if (!sp->ttis[i].tt_isdst) + break; + } + /* The next heuristics are for data generated by tzdb 2018e or + earlier, for zones like EST5EDT where the first transition + is to DST. */ + /* + ** If no result yet, find the first standard type. + ** If there is none, punt to type zero. + */ + if (i < 0) { + i = 0; + while (sp->ttis[i].tt_isdst) + if (++i >= sp->typecnt) { + i = 0; + break; + } + } + /* A simple 'sp->defaulttype = 0;' would suffice here if we + didn't have to worry about 2018e-or-earlier data. Even + simpler would be to remove the defaulttype member and just + use 0 in its place. */ + sp->defaulttype = i; + + return 0; +} + +/* Load tz data from the file named NAME into *SP. Read extended + format if DOEXTEND. Return 0 on success, an errno value on failure. */ +static int +tzload(char const *name, struct state *sp, bool doextend) +{ +#ifdef ALL_STATE + union local_storage *lsp = malloc(sizeof *lsp); + if (!lsp) { + return HAVE_MALLOC_ERRNO ? errno : ENOMEM; + } else { + int err = tzloadbody(name, sp, doextend, lsp); + free(lsp); + return err; + } +#else + int i; + volatile char *p; + volatile unsigned x; + union local_storage ls; /* 70+ kilobytes */ + p = (char *)&ls; + for (x = i = 0; i < sizeof(ls); i += 4096) { + x += p[i]; /* make sure tzdata doesn't smash the stack */ + } + return tzloadbody(name, sp, doextend, &ls); +#endif +} + +static bool +typesequiv(const struct state *sp, int a, int b) +{ + register bool result; + if (sp == NULL || - a < 0 || a >= sp->typecnt || - b < 0 || b >= sp->typecnt) - result = FALSE; + a < 0 || a >= sp->typecnt || + b < 0 || b >= sp->typecnt) + result = false; else { - const struct ttinfo * ap = &sp->ttis[a]; - const struct ttinfo * bp = &sp->ttis[b]; - result = ap->tt_gmtoff == bp->tt_gmtoff && - ap->tt_isdst == bp->tt_isdst && - ap->tt_ttisstd == bp->tt_ttisstd && - ap->tt_ttisgmt == bp->tt_ttisgmt && - cmpstr(&sp->chars[ap->tt_abbrind], - &sp->chars[bp->tt_abbrind]) == 0; + register const struct ttinfo * ap = &sp->ttis[a]; + register const struct ttinfo * bp = &sp->ttis[b]; + result = (ap->tt_utoff == bp->tt_utoff + && ap->tt_isdst == bp->tt_isdst + && ap->tt_ttisstd == bp->tt_ttisstd + && ap->tt_ttisut == bp->tt_ttisut + && (strcmp(&sp->chars[ap->tt_desigidx], + &sp->chars[bp->tt_desigidx]) + == 0)); } return result; } -static int -tzload( - const char * name, - struct state * const sp, - const int doextend -) { - register const char * p; - register int i; - register int fid; - register int stored; - register int nread; - union { - struct tzhead tzhead; - char buf[2 * sizeof(struct tzhead) + - 2 * sizeof *sp + - 4 * TZ_MAX_TIMES]; - } * up; - char fullname[PATH_MAX+1]; - - up = calloc(1, sizeof *up); - if (up == NULL) - return -1; - - sp->goback = sp->goahead = FALSE; - if (name != NULL /* && issetugid() != 0 */) { - if ((name[0] == ':' && (strchr(name, '/'))) || - name[0] == '/' || strchr(name, '.')) - name = NULL; - } - if (name == NULL && (name = TZDEFAULT) == NULL) - goto oops; - - if (name[0] == ':') - ++name; - if (name[0] != '/') { - if ((p = TZDIR) == NULL) - goto oops; - if ((strlen(p) + strlen(name) + 1) >= sizeof fullname) - goto oops; - strlcpy(fullname, p, sizeof fullname); - strlcat(fullname, "/", sizeof fullname); - strlcat(fullname, name, sizeof fullname); - name = fullname; - } - if ((fid = open(name, O_RDONLY)) == -1) - goto oops; - - nread = read(fid, up->buf, sizeof up->buf); - if (close(fid) < 0 || nread <= 0) - goto oops; - for (stored = 4; stored <= 8; stored *= 2) { - int ttisstdcnt; - int ttisgmtcnt; - - ttisstdcnt = (int) detzcode(up->tzhead.tzh_ttisstdcnt); - ttisgmtcnt = (int) detzcode(up->tzhead.tzh_ttisgmtcnt); - sp->leapcnt = (int) detzcode(up->tzhead.tzh_leapcnt); - sp->timecnt = (int) detzcode(up->tzhead.tzh_timecnt); - sp->typecnt = (int) detzcode(up->tzhead.tzh_typecnt); - sp->charcnt = (int) detzcode(up->tzhead.tzh_charcnt); - p = up->tzhead.tzh_charcnt + sizeof up->tzhead.tzh_charcnt; - if (sp->leapcnt < 0 || sp->leapcnt > TZ_MAX_LEAPS || - sp->typecnt <= 0 || sp->typecnt > TZ_MAX_TYPES || - sp->timecnt < 0 || sp->timecnt > TZ_MAX_TIMES || - sp->charcnt < 0 || sp->charcnt > TZ_MAX_CHARS || - (ttisstdcnt != sp->typecnt && ttisstdcnt != 0) || - (ttisgmtcnt != sp->typecnt && ttisgmtcnt != 0)) - goto oops; - if (nread - (p - up->buf) < - sp->timecnt * stored + /* ats */ - sp->timecnt + /* types */ - sp->typecnt * 6 + /* ttinfos */ - sp->charcnt + /* chars */ - sp->leapcnt * (stored + 4) + /* lsinfos */ - ttisstdcnt + /* ttisstds */ - ttisgmtcnt) /* ttisgmts */ - goto oops; - for (i = 0; i < sp->timecnt; ++i) { - sp->ats[i] = (stored == 4) ? - detzcode(p) : detzcode64(p); - p += stored; - } - for (i = 0; i < sp->timecnt; ++i) { - sp->types[i] = (unsigned char) *p++; - if (sp->types[i] >= sp->typecnt) - goto oops; - } - for (i = 0; i < sp->typecnt; ++i) { - struct ttinfo * ttisp; - - ttisp = &sp->ttis[i]; - ttisp->tt_gmtoff = detzcode(p); - p += 4; - ttisp->tt_isdst = (unsigned char) *p++; - if (ttisp->tt_isdst != 0 && ttisp->tt_isdst != 1) - goto oops; - ttisp->tt_abbrind = (unsigned char) *p++; - if (ttisp->tt_abbrind < 0 || - ttisp->tt_abbrind > sp->charcnt) - goto oops; - } - for (i = 0; i < sp->charcnt; ++i) - sp->chars[i] = *p++; - sp->chars[i] = '\0'; /* ensure '\0' at end */ - for (i = 0; i < sp->leapcnt; ++i) { - struct lsinfo * lsisp; - - lsisp = &sp->lsis[i]; - lsisp->ls_trans = (stored == 4) ? - detzcode(p) : detzcode64(p); - p += stored; - lsisp->ls_corr = detzcode(p); - p += 4; - } - for (i = 0; i < sp->typecnt; ++i) { - struct ttinfo * ttisp; - - ttisp = &sp->ttis[i]; - if (ttisstdcnt == 0) - ttisp->tt_ttisstd = FALSE; - else { - ttisp->tt_ttisstd = *p++; - if (ttisp->tt_ttisstd != TRUE && - ttisp->tt_ttisstd != FALSE) - goto oops; - } - } - for (i = 0; i < sp->typecnt; ++i) { - struct ttinfo * ttisp; - - ttisp = &sp->ttis[i]; - if (ttisgmtcnt == 0) - ttisp->tt_ttisgmt = FALSE; - else { - ttisp->tt_ttisgmt = *p++; - if (ttisp->tt_ttisgmt != TRUE && - ttisp->tt_ttisgmt != FALSE) - goto oops; - } - } - /* - ** Out-of-sort ats should mean we're running on a - ** signed time_t system but using a data file with - ** unsigned values (or vice versa). - */ - for (i = 0; i < sp->timecnt - 2; ++i) - if (sp->ats[i] > sp->ats[i + 1]) { - ++i; - /* - ** Ignore the end (easy). - */ - sp->timecnt = i; - break; - } - /* - ** If this is an old file, we're done. - */ - if (up->tzhead.tzh_version[0] == '\0') - break; - nread -= p - up->buf; - for (i = 0; i < nread; ++i) - up->buf[i] = p[i]; - /* - ** If this is a narrow integer time_t system, we're done. - */ - if (stored >= sizeof(time_t)) - break; - } - if (doextend && nread > 2 && - up->buf[0] == '\n' && up->buf[nread - 1] == '\n' && - sp->typecnt + 2 <= TZ_MAX_TYPES) { - struct state *ts; - int result; - ts = calloc(1, sizeof(struct state)); - if (!ts) abort(); - up->buf[nread - 1] = '\0'; - result = tzparse(&up->buf[1], ts, FALSE); - if (result == 0 && ts->typecnt == 2 && - sp->charcnt + ts->charcnt <= TZ_MAX_CHARS) { - for (i = 0; i < 2; ++i) - ts->ttis[i].tt_abbrind += - sp->charcnt; - for (i = 0; i < ts->charcnt; ++i) - sp->chars[sp->charcnt++] = - ts->chars[i]; - i = 0; - while (i < ts->timecnt && - ts->ats[i] <= - sp->ats[sp->timecnt - 1]) - ++i; - while (i < ts->timecnt && - sp->timecnt < TZ_MAX_TIMES) { - sp->ats[sp->timecnt] = - ts->ats[i]; - sp->types[sp->timecnt] = - sp->typecnt + - ts->types[i]; - ++sp->timecnt; - ++i; - } - sp->ttis[sp->typecnt++] = ts->ttis[0]; - sp->ttis[sp->typecnt++] = ts->ttis[1]; - } - free(ts); - } - if (sp->timecnt > 1) { - for (i = 1; i < sp->timecnt; ++i) { - if (typesequiv(sp, sp->types[i], sp->types[0]) && - differ_by_repeat(sp->ats[i], sp->ats[0])) { - sp->goback = TRUE; - break; - } - } - for (i = sp->timecnt - 2; i >= 0; --i) { - if (typesequiv(sp, sp->types[sp->timecnt - 1], - sp->types[i]) && - differ_by_repeat(sp->ats[sp->timecnt - 1], - sp->ats[i])) { - sp->goahead = TRUE; - break; - } - } - } - free(up); - return 0; -oops: - free(up); - return -1; -} - -static const unsigned char kMonthLengths[2][MONSPERYEAR] = { +static const int mon_lengths[2][MONSPERYEAR] = { { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }, { 31, 29, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } }; -static const int kYearLengths[2] = { +static const int year_lengths[2] = { DAYSPERNYEAR, DAYSPERLYEAR }; +/* Is C an ASCII digit? */ +static inline bool +is_digit(char c) +{ + return '0' <= c && c <= '9'; +} + /* -** Given a pointer into a time zone string, scan until a character that is not -** a valid character in a zone name is found. Return a pointer to that -** character. +** Given a pointer into a timezone string, scan until a character that is not +** a valid character in a time zone abbreviation is found. +** Return a pointer to that character. */ -static const char * -getzname( - const char * strp -) { - char c; - while ((c = *strp) != '\0' && !isdigit(c) && c != ',' && c != '-' && - c != '+') { - ++strp; - } +static nosideeffect const char * +getzname(register const char *strp) +{ + register char c; + + while ((c = *strp) != '\0' && !is_digit(c) && c != ',' && c != '-' && + c != '+') + ++strp; return strp; } /* -** Given a pointer into an extended time zone string, scan until the ending -** delimiter of the zone name is located. Return a pointer to the delimiter. +** Given a pointer into an extended timezone string, scan until the ending +** delimiter of the time zone abbreviation is located. +** Return a pointer to the delimiter. ** ** As with getzname above, the legal character set is actually quite ** restricted, with other characters producing undefined results. ** We don't do any checking here; checking is done later in common-case code. */ -static const char * -getqzname( - const char * strp, - const int delim -) { - register int c; +static nosideeffect const char * +getqzname(register const char *strp, const int delim) +{ + register int c; while ((c = *strp) != '\0' && c != delim) ++strp; @@ -661,23 +877,19 @@ getqzname( } /* -** Given a pointer into a time zone string, extract a number from that string. +** Given a pointer into a timezone string, extract a number from that string. ** Check that the number is within a specified range; if it is not, return ** NULL. ** Otherwise, return a pointer to the first character not part of the number. */ static const char * -getnum( - const char * strp, - int * const nump, - const int min, - const int max -) { - register char c; - register int num; +getnum(register const char *strp, int *const nump, const int min, const int max) +{ + register char c; + register int num; - if (strp == NULL || !isdigit(c = *strp)) + if (strp == NULL || !is_digit(c = *strp)) return NULL; num = 0; do { @@ -685,7 +897,7 @@ getnum( if (num > max) return NULL; /* illegal value */ c = *++strp; - } while (isdigit(c)); + } while (is_digit(c)); if (num < min) return NULL; /* illegal value */ *nump = num; @@ -693,7 +905,7 @@ getnum( } /* -** Given a pointer into a time zone string, extract a number of seconds, +** Given a pointer into a timezone string, extract a number of seconds, ** in hh[:mm[:ss]] form, from the string. ** If any error occurs, return NULL. ** Otherwise, return a pointer to the first character not part of the number @@ -701,21 +913,21 @@ getnum( */ static const char * -getsecs( - const char * strp, - int32_t * const secsp -) { - int num; +getsecs(register const char *strp, int_fast32_t *const secsp) +{ + int num; + int_fast32_t secsperhour = SECSPERHOUR; + /* - ** `HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like + ** 'HOURSPERDAY * DAYSPERWEEK - 1' allows quasi-Posix rules like ** "M10.4.6/26", which does not conform to Posix, ** but which specifies the equivalent of - ** ``02:00 on the first Sunday on or after 23 Oct''. + ** "02:00 on the first Sunday on or after 23 Oct". */ strp = getnum(strp, &num, 0, HOURSPERDAY * DAYSPERWEEK - 1); if (strp == NULL) return NULL; - *secsp = num * (int32_t) SECSPERHOUR; + *secsp = num * secsperhour; if (*strp == ':') { ++strp; strp = getnum(strp, &num, 0, MINSPERHOUR - 1); @@ -724,7 +936,7 @@ getsecs( *secsp += num * SECSPERMIN; if (*strp == ':') { ++strp; - /* `SECSPERMIN' allows for leap seconds. */ + /* 'SECSPERMIN' allows for leap seconds. */ strp = getnum(strp, &num, 0, SECSPERMIN); if (strp == NULL) return NULL; @@ -735,20 +947,19 @@ getsecs( } /* -** Given a pointer into a time zone string, extract an offset, in +** Given a pointer into a timezone string, extract an offset, in ** [+-]hh[:mm[:ss]] form, from the string. ** If any error occurs, return NULL. ** Otherwise, return a pointer to the first character not part of the time. */ static const char * -getoffset( - const char * strp, - int32_t * const offsetp -) { - register int neg = 0; +getoffset(register const char *strp, int_fast32_t *const offsetp) +{ + register bool neg = false; + if (*strp == '-') { - neg = 1; + neg = true; ++strp; } else if (*strp == '+') ++strp; @@ -761,17 +972,15 @@ getoffset( } /* -** Given a pointer into a time zone string, extract a rule in the form +** Given a pointer into a timezone string, extract a rule in the form ** date[/time]. See POSIX section 8 for the format of "date" and "time". ** If a valid rule is not found, return NULL. ** Otherwise, return a pointer to the first character not part of the rule. */ static const char * -getrule( - const char * strp, - struct rule * const rulep -) { +getrule(const char *strp, register struct rule *const rulep) +{ if (*strp == 'J') { /* ** Julian day. @@ -796,7 +1005,7 @@ getrule( if (*strp++ != '.') return NULL; strp = getnum(strp, &rulep->r_day, 0, DAYSPERWEEK - 1); - } else if (isdigit(*strp)) { + } else if (is_digit(*strp)) { /* ** Day of year. */ @@ -810,30 +1019,25 @@ getrule( ** Time specified. */ ++strp; - strp = getsecs(strp, &rulep->r_time); + strp = getoffset(strp, &rulep->r_time); } else rulep->r_time = 2 * SECSPERHOUR; /* default = 2:00:00 */ return strp; } /* -** Given the Epoch-relative time of January 1, 00:00:00 UTC, in a year, the -** year, a rule, and the offset from UTC at the time that rule takes effect, -** calculate the Epoch-relative time that rule takes effect. +** Given a year, a rule, and the offset from UT at the time that rule takes +** effect, calculate the year-relative time that rule takes effect. */ -static time_t -transtime( - const time_t janfirst, - const int year, - const struct rule * const rulep, - const int32_t offset -) { - register int leapyear; - register time_t value; - register int i; - int d, m1, yy0, yy1, yy2, dow; +static int_fast32_t +transtime(const int year, register const struct rule *const rulep, + const int_fast32_t offset) +{ + register bool leapyear; + register int_fast32_t value; + register int i; + int d, m1, yy0, yy1, yy2, dow; - INITIALIZE(value); leapyear = isleap(year); switch (rulep->r_type) { @@ -845,7 +1049,7 @@ transtime( ** add SECSPERDAY times the day number-1 to the time of ** January 1, midnight, to get the day. */ - value = janfirst + (rulep->r_day - 1) * SECSPERDAY; + value = (rulep->r_day - 1) * SECSPERDAY; if (leapyear && rulep->r_day >= 60) value += SECSPERDAY; break; @@ -856,16 +1060,13 @@ transtime( ** Just add SECSPERDAY times the day number to the time of ** January 1, midnight, to get the day. */ - value = janfirst + rulep->r_day * SECSPERDAY; + value = rulep->r_day * SECSPERDAY; break; case MONTH_NTH_DAY_OF_WEEK: /* ** Mm.n.d - nth "dth day" of month m. */ - value = janfirst; - for (i = 0; i < rulep->r_mon - 1; ++i) - value += kMonthLengths[leapyear][i] * SECSPERDAY; /* ** Use Zeller's Congruence to get day-of-week of first day of @@ -890,7 +1091,7 @@ transtime( d += DAYSPERWEEK; for (i = 1; i < rulep->r_week; ++i) { if (d + DAYSPERWEEK >= - kMonthLengths[leapyear][rulep->r_mon - 1]) + mon_lengths[leapyear][rulep->r_mon - 1]) break; d += DAYSPERWEEK; } @@ -898,15 +1099,19 @@ transtime( /* ** "d" is the day-of-month (zero-origin) of the day we want. */ - value += d * SECSPERDAY; + value = d * SECSPERDAY; + for (i = 0; i < rulep->r_mon - 1; ++i) + value += mon_lengths[leapyear][i] * SECSPERDAY; break; + + default: UNREACHABLE(); } /* - ** "value" is the Epoch-relative time of 00:00:00 UTC on the day in - ** question. To get the Epoch-relative time of the specified local + ** "value" is the year-relative time of 00:00:00 UT on the day in + ** question. To get the year-relative time of the specified local ** time on that day, add the transition time and the current offset - ** from UTC. + ** from UT. */ return value + rulep->r_time + offset; } @@ -916,142 +1121,194 @@ transtime( ** appropriate. */ -static int -tzparse( - const char * name, - struct state * const sp, - const int lastditch -) { - const char * stdname; - const char * dstname; - size_t stdlen; - size_t dstlen; - int32_t stdoffset; - int32_t dstoffset; - register time_t * atp; - register unsigned char *typep; - register char * cp; - register int load_result; +static bool +tzparse(const char *name, struct state *sp, struct state *basep) +{ + const char * stdname; + const char * dstname; + size_t stdlen; + size_t dstlen; + size_t charcnt; + int_fast32_t stdoffset; + int_fast32_t dstoffset; + register char * cp; + register bool load_ok; + time_t atlo = TIME_T_MIN, leaplo = TIME_T_MIN; - INITIALIZE(dstname); stdname = name; - if (lastditch) { - stdlen = strlen(name); /* length of standard zone name */ - name += stdlen; - if (stdlen >= sizeof sp->chars) - stdlen = (sizeof sp->chars) - 1; - stdoffset = 0; + if (*name == '<') { + name++; + stdname = name; + name = getqzname(name, '>'); + if (*name != '>') + return false; + stdlen = name - stdname; + name++; } else { - if (*name == '<') { - name++; - stdname = name; - name = getqzname(name, '>'); - if (*name != '>') - return (-1); - stdlen = name - stdname; - name++; - } else { - name = getzname(name); - stdlen = name - stdname; - } - if (*name == '\0') - return -1; - name = getoffset(name, &stdoffset); - if (name == NULL) - return -1; + name = getzname(name); + stdlen = name - stdname; } - load_result = tzload(TZDEFRULES, sp, FALSE); - if (load_result != 0) - sp->leapcnt = 0; /* so, we're off a little */ - sp->timecnt = 0; + if (!stdlen) + return false; + name = getoffset(name, &stdoffset); + if (name == NULL) + return false; + charcnt = stdlen + 1; + if (sizeof sp->chars < charcnt) + return false; + if (basep) { + if (0 < basep->timecnt) + atlo = basep->ats[basep->timecnt - 1]; + load_ok = false; + sp->leapcnt = basep->leapcnt; + memcpy(sp->lsis, basep->lsis, sp->leapcnt * sizeof *sp->lsis); + } else { + load_ok = tzload(TZDEFRULES, sp, false) == 0; + if (!load_ok) + sp->leapcnt = 0; /* So, we're off a little. */ + } + if (0 < sp->leapcnt) + leaplo = sp->lsis[sp->leapcnt - 1].ls_trans; if (*name != '\0') { if (*name == '<') { dstname = ++name; name = getqzname(name, '>'); if (*name != '>') - return -1; + return false; dstlen = name - dstname; name++; } else { dstname = name; name = getzname(name); - dstlen = name - dstname; /* length of DST zone name */ + dstlen = name - dstname; /* length of DST abbr. */ } + if (!dstlen) + return false; + charcnt += dstlen + 1; + if (sizeof sp->chars < charcnt) + return false; if (*name != '\0' && *name != ',' && *name != ';') { name = getoffset(name, &dstoffset); if (name == NULL) - return -1; + return false; } else dstoffset = stdoffset - SECSPERHOUR; - if (*name == '\0' && load_result != 0) + if (*name == '\0' && !load_ok) name = TZDEFRULESTRING; if (*name == ',' || *name == ';') { struct rule start; struct rule end; register int year; - register time_t janfirst; - time_t starttime; - time_t endtime; + register int timecnt; + time_t janfirst; + int_fast32_t janoffset = 0; + int yearbeg, yearlim; ++name; if ((name = getrule(name, &start)) == NULL) - return -1; + return false; if (*name++ != ',') - return -1; + return false; if ((name = getrule(name, &end)) == NULL) - return -1; + return false; if (*name != '\0') - return -1; + return false; sp->typecnt = 2; /* standard time and DST */ /* ** Two transitions per year, from EPOCH_YEAR forward. */ - sp->ttis[0].tt_gmtoff = -dstoffset; - sp->ttis[0].tt_isdst = 1; - sp->ttis[0].tt_abbrind = stdlen + 1; - sp->ttis[1].tt_gmtoff = -stdoffset; - sp->ttis[1].tt_isdst = 0; - sp->ttis[1].tt_abbrind = 0; - atp = sp->ats; - typep = sp->types; + init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); + init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); + sp->defaulttype = 0; + timecnt = 0; janfirst = 0; - for (year = EPOCH_YEAR; - sp->timecnt + 2 <= TZ_MAX_TIMES; - ++year) { - time_t newfirst; + yearbeg = EPOCH_YEAR; - starttime = transtime(janfirst, year, &start, - stdoffset); - endtime = transtime(janfirst, year, &end, - dstoffset); - if (starttime > endtime) { - *atp++ = endtime; - *typep++ = 1; /* DST ends */ - *atp++ = starttime; - *typep++ = 0; /* DST begins */ - } else { - *atp++ = starttime; - *typep++ = 0; /* DST begins */ - *atp++ = endtime; - *typep++ = 1; /* DST ends */ - } - sp->timecnt += 2; - newfirst = janfirst; - newfirst += kYearLengths[isleap(year)] * - SECSPERDAY; - if (newfirst <= janfirst) - break; - janfirst = newfirst; + do { + int_fast32_t yearsecs + = year_lengths[isleap(yearbeg - 1)] * SECSPERDAY; + yearbeg--; + if (increment_overflow_time(&janfirst, -yearsecs)) { + janoffset = -yearsecs; + break; + } + } while (atlo < janfirst + && EPOCH_YEAR - YEARSPERREPEAT / 2 < yearbeg); + + while (true) { + int_fast32_t yearsecs + = year_lengths[isleap(yearbeg)] * SECSPERDAY; + int yearbeg1 = yearbeg; + time_t janfirst1 = janfirst; + if (increment_overflow_time(&janfirst1, yearsecs) + || increment_overflow(&yearbeg1, 1) + || atlo <= janfirst1) + break; + yearbeg = yearbeg1; + janfirst = janfirst1; } + + yearlim = yearbeg; + if (increment_overflow(&yearlim, YEARSPERREPEAT + 1)) + yearlim = INT_MAX; + for (year = yearbeg; year < yearlim; year++) { + int_fast32_t + starttime = transtime(year, &start, stdoffset), + endtime = transtime(year, &end, dstoffset); + int_fast32_t + yearsecs = (year_lengths[isleap(year)] + * SECSPERDAY); + bool reversed = endtime < starttime; + if (reversed) { + int_fast32_t swap = starttime; + starttime = endtime; + endtime = swap; + } + if (reversed + || (starttime < endtime + && endtime - starttime < yearsecs)) { + if (TZ_MAX_TIMES - 2 < timecnt) + break; + sp->ats[timecnt] = janfirst; + if (! increment_overflow_time + (&sp->ats[timecnt], + janoffset + starttime) + && atlo <= sp->ats[timecnt]) + sp->types[timecnt++] = !reversed; + sp->ats[timecnt] = janfirst; + if (! increment_overflow_time + (&sp->ats[timecnt], + janoffset + endtime) + && atlo <= sp->ats[timecnt]) { + sp->types[timecnt++] = reversed; + } + } + if (endtime < leaplo) { + yearlim = year; + if (increment_overflow(&yearlim, + YEARSPERREPEAT + 1)) + yearlim = INT_MAX; + } + if (increment_overflow_time + (&janfirst, janoffset + yearsecs)) + break; + janoffset = 0; + } + sp->timecnt = timecnt; + if (! timecnt) { + sp->ttis[0] = sp->ttis[1]; + sp->typecnt = 1; /* Perpetual DST. */ + } else if (YEARSPERREPEAT < year - yearbeg) + sp->goback = sp->goahead = true; } else { - register int32_t theirstdoffset; - register int32_t theirdstoffset; - register int32_t theiroffset; - register int isdst; + register int_fast32_t theirstdoffset; + register int_fast32_t theirdstoffset; + register int_fast32_t theiroffset; + register bool isdst; register int i; register int j; if (*name != '\0') - return -1; + return false; /* ** Initial values of theirstdoffset and theirdstoffset. */ @@ -1060,7 +1317,7 @@ tzparse( j = sp->types[i]; if (!sp->ttis[j].tt_isdst) { theirstdoffset = - -sp->ttis[j].tt_gmtoff; + - sp->ttis[j].tt_utoff; break; } } @@ -1069,15 +1326,14 @@ tzparse( j = sp->types[i]; if (sp->ttis[j].tt_isdst) { theirdstoffset = - -sp->ttis[j].tt_gmtoff; + - sp->ttis[j].tt_utoff; break; } } /* ** Initially we're assumed to be in standard time. */ - isdst = FALSE; - theiroffset = theirstdoffset; + isdst = false; /* ** Now juggle transition times and types ** tracking offsets as you do. @@ -1085,16 +1341,17 @@ tzparse( for (i = 0; i < sp->timecnt; ++i) { j = sp->types[i]; sp->types[i] = sp->ttis[j].tt_isdst; - if (sp->ttis[j].tt_ttisgmt) { + if (sp->ttis[j].tt_ttisut) { /* No adjustment to transition time */ } else { /* - ** If summer time is in effect, and the - ** transition time was not specified as - ** standard time, add the summer time - ** offset to the transition time; - ** otherwise, add the standard time - ** offset to the transition time. + ** If daylight saving time is in + ** effect, and the transition time was + ** not specified as standard time, add + ** the daylight saving time offset to + ** the transition time; otherwise, add + ** the standard time offset to the + ** transition time. */ /* ** Transitions from DST to DDST @@ -1110,206 +1367,191 @@ tzparse( theirstdoffset; } } - theiroffset = -sp->ttis[j].tt_gmtoff; + theiroffset = -sp->ttis[j].tt_utoff; if (sp->ttis[j].tt_isdst) theirdstoffset = theiroffset; else theirstdoffset = theiroffset; } /* ** Finally, fill in ttis. - ** ttisstd and ttisgmt need not be handled. */ - sp->ttis[0].tt_gmtoff = -stdoffset; - sp->ttis[0].tt_isdst = FALSE; - sp->ttis[0].tt_abbrind = 0; - sp->ttis[1].tt_gmtoff = -dstoffset; - sp->ttis[1].tt_isdst = TRUE; - sp->ttis[1].tt_abbrind = stdlen + 1; + init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); + init_ttinfo(&sp->ttis[1], -dstoffset, true, stdlen + 1); sp->typecnt = 2; + sp->defaulttype = 0; } } else { dstlen = 0; sp->typecnt = 1; /* only standard time */ sp->timecnt = 0; - sp->ttis[0].tt_gmtoff = -stdoffset; - sp->ttis[0].tt_isdst = 0; - sp->ttis[0].tt_abbrind = 0; + init_ttinfo(&sp->ttis[0], -stdoffset, false, 0); + sp->defaulttype = 0; } - sp->charcnt = stdlen + 1; - if (dstlen != 0) - sp->charcnt += dstlen + 1; - if ((size_t) sp->charcnt > sizeof sp->chars) - return -1; + sp->charcnt = charcnt; cp = sp->chars; - (void) strncpy(cp, stdname, stdlen); + memcpy(cp, stdname, stdlen); cp += stdlen; *cp++ = '\0'; if (dstlen != 0) { - (void) strncpy(cp, dstname, dstlen); + memcpy(cp, dstname, dstlen); *(cp + dstlen) = '\0'; } - return 0; + return true; } static void -gmtload( - struct state * const sp -) { - if (tzload(gmt, sp, TRUE) != 0) - (void) tzparse(gmt, sp, TRUE); +gmtload(struct state *const sp) +{ + if (tzload(gmt, sp, true) != 0) + tzparse("GMT0", sp, NULL); } -#ifndef STD_INSPIRED -/* -** A non-static declaration of tzsetwall in a system header file -** may cause a warning about this upcoming static declaration... -*/ -static -#endif /* !defined STD_INSPIRED */ -void -tzsetwall(void) +/* Initialize *SP to a value appropriate for the TZ setting NAME. + Return 0 on success, an errno value on failure. */ +static int +zoneinit(struct state *sp, char const *name) { - if (lcl_is_set < 0) - return; - lcl_is_set = -1; -#ifdef ALL_STATE - if (lclptr == NULL) { - if ((lclptr = malloc(sizeof(*lclptr)))) { - __cxa_atexit(free, lclptr, 0); - } else { - settzname(); /* all we can do */ - return; - } + if (name && ! name[0]) { + /* + ** User wants it fast rather than right. + */ + sp->leapcnt = 0; /* so, we're off a little */ + sp->timecnt = 0; + sp->typecnt = 0; + sp->charcnt = 0; + sp->goback = sp->goahead = false; + init_ttinfo(&sp->ttis[0], 0, false, 0); + strcpy(sp->chars, gmt); + sp->defaulttype = 0; + return 0; + } else { + int err = tzload(name, sp, true); + if (err != 0 && name && name[0] != ':' && tzparse(name, sp, NULL)) + err = 0; + if (err == 0) + scrub_abbrs(sp); + return err; } +} + +static void +tzset_unlocked(void) +{ + char const *name = getenv("TZ"); + struct state *sp = lclptr; + int lcl = name ? strlen(name) < sizeof lcl_TZname : -1; + if (lcl < 0 + ? lcl_is_set < 0 + : 0 < lcl_is_set && strcmp(lcl_TZname, name) == 0) + return; +#ifdef ALL_STATE + if (! sp) + lclptr = sp = malloc(sizeof *lclptr); #endif /* defined ALL_STATE */ - if (tzload((char *) NULL, lclptr, TRUE) != 0) - gmtload(lclptr); + if (sp) { + if (zoneinit(sp, name) != 0) + zoneinit(sp, ""); + if (0 < lcl) + strcpy(lcl_TZname, name); + } settzname(); + lcl_is_set = lcl; } void tzset(void) { - register const char * name = NULL; - /* static char buf[PROP_VALUE_MAX]; */ - - name = getenv("TZ"); - - /* // try the "persist.sys.timezone" system property first */ - /* if (name == NULL && __system_property_get("persist.sys.timezone", buf) > 0) */ - /* name = buf; */ - - if (name == NULL) { - tzsetwall(); + if (lock() != 0) return; - } + tzset_unlocked(); + unlock(); +} - if (lcl_is_set > 0 && strcmp(lcl_TZname, name) == 0) +static void +gmtcheck(void) +{ + static bool gmt_is_set; + if (lock() != 0) return; - lcl_is_set = strlen(name) < sizeof lcl_TZname; - if (lcl_is_set) - (void) strcpy(lcl_TZname, name); - + if (! gmt_is_set) { #ifdef ALL_STATE - if (lclptr == NULL) { - if ((lclptr = malloc(sizeof(*lclptr)))) { - __cxa_atexit(free, lclptr, 0); - } else { - settzname(); /* all we can do */ - return; - } + gmtptr = malloc(sizeof *gmtptr); +#endif + if (gmtptr) + gmtload(gmtptr); + gmt_is_set = true; } -#endif /* defined ALL_STATE */ - if (*name == '\0') { - /* - ** User wants it fast rather than right. - */ - lclptr->leapcnt = 0; /* so, we're off a little */ - lclptr->timecnt = 0; - lclptr->typecnt = 0; - lclptr->ttis[0].tt_isdst = 0; - lclptr->ttis[0].tt_gmtoff = 0; - lclptr->ttis[0].tt_abbrind = 0; - (void) strcpy(lclptr->chars, gmt); - } else if (tzload(name, lclptr, TRUE) != 0) - if (name[0] == ':' || tzparse(name, lclptr, FALSE) != 0) - (void) gmtload(lclptr); - settzname(); + unlock(); } /* ** The easy way to behave "as if no library function calls" localtime -** is to not call it--so we drop its guts into "localsub", which can be -** freely called. (And no, the PANS doesn't require the above behavior-- +** is to not call it, so we drop its guts into "localsub", which can be +** freely called. (And no, the PANS doesn't require the above behavior, ** but it *is* desirable.) ** -** The unused offset argument is for the benefit of mktime variants. +** If successful and SETNAME is nonzero, +** set the applicable parts of tzname, timezone and altzone; +** however, it's OK to omit this step if the timezone is POSIX-compatible, +** since in that case tzset should have already done this step correctly. +** SETNAME's type is int_fast32_t for compatibility with gmtsub, +** but it is actually a boolean and its value should be 0 or 1. */ /*ARGSUSED*/ static struct tm * -localsub( - const time_t * const timep, - const int32_t offset, - struct tm * const tmp -) { - register struct state * sp; +localsub(struct state const *sp, time_t const *timep, int_fast32_t setname, + struct tm *const tmp) +{ register const struct ttinfo * ttisp; register int i; register struct tm * result; const time_t t = *timep; - sp = lclptr; -#ifdef ALL_STATE - if (sp == NULL) - return gmtsub(timep, offset, tmp); -#endif /* defined ALL_STATE */ + if (sp == NULL) { + /* Don't bother to set tzname etc.; tzset has already done it. */ + return gmtsub(gmtptr, timep, 0, tmp); + } if ((sp->goback && t < sp->ats[0]) || (sp->goahead && t > sp->ats[sp->timecnt - 1])) { - time_t newt = t; + time_t newt; register time_t seconds; - register time_t tcycles; - register int_fast64_t icycles; + register time_t years; if (t < sp->ats[0]) seconds = sp->ats[0] - t; else seconds = t - sp->ats[sp->timecnt - 1]; --seconds; - tcycles = seconds / YEARSPERREPEAT / AVGSECSPERYEAR; - ++tcycles; - icycles = tcycles; - if (tcycles - icycles >= 1 || icycles - tcycles >= 1) - return NULL; - seconds = icycles; - seconds *= YEARSPERREPEAT; - seconds *= AVGSECSPERYEAR; + + /* Beware integer overflow, as SECONDS might + be close to the maximum time_t. */ + years = seconds / SECSPERREPEAT * YEARSPERREPEAT; + seconds = years * AVGSECSPERYEAR; + years += YEARSPERREPEAT; if (t < sp->ats[0]) - newt += seconds; - else newt -= seconds; + newt = t + seconds + SECSPERREPEAT; + else + newt = t - seconds - SECSPERREPEAT; + if (newt < sp->ats[0] || newt > sp->ats[sp->timecnt - 1]) return NULL; /* "cannot happen" */ - result = localsub(&newt, offset, tmp); - if (result == tmp) { - register time_t newy; + result = localsub(sp, &newt, setname, tmp); + if (result) { + register int_fast64_t newy; - newy = tmp->tm_year; + newy = result->tm_year; if (t < sp->ats[0]) - newy -= icycles * YEARSPERREPEAT; - else newy += icycles * YEARSPERREPEAT; - tmp->tm_year = newy; - if (tmp->tm_year != newy) + newy -= years; + else newy += years; + if (! (INT_MIN <= newy && newy <= INT_MAX)) return NULL; + result->tm_year = newy; } return result; } if (sp->timecnt == 0 || t < sp->ats[0]) { - i = 0; - while (sp->ttis[i].tt_isdst) - if (++i >= sp->typecnt) { - i = 0; - break; - } + i = sp->defaulttype; } else { register int lo = 1; register int hi = sp->timecnt; @@ -1321,43 +1563,50 @@ localsub( hi = mid; else lo = mid + 1; } - i = (int) sp->types[lo - 1]; + i = sp->types[lo - 1]; } ttisp = &sp->ttis[i]; /* ** To get (wrong) behavior that's compatible with System V Release 2.0 ** you'd replace the statement below with - ** t += ttisp->tt_gmtoff; + ** t += ttisp->tt_utoff; ** timesub(&t, 0L, sp, tmp); */ - result = timesub(&t, ttisp->tt_gmtoff, sp, tmp); - tmp->tm_isdst = ttisp->tt_isdst; - tzname[tmp->tm_isdst] = &sp->chars[ttisp->tt_abbrind]; -#ifdef TM_ZONE - tmp->TM_ZONE = &sp->chars[ttisp->tt_abbrind]; -#endif /* defined TM_ZONE */ + result = timesub(&t, ttisp->tt_utoff, sp, tmp); + if (result) { + result->tm_isdst = ttisp->tt_isdst; + result->tm_zone = (char *) &sp->chars[ttisp->tt_desigidx]; + if (setname) + update_tzname_etc(sp, ttisp); + } return result; } -struct tm * -localtime( - const time_t * const timep -) { - tzset(); - return localsub(timep, 0L, &tm); +static struct tm * +localtime_tzset(time_t const *timep, struct tm *tmp, bool setname) +{ + int err = lock(); + if (err) { + errno = err; + return NULL; + } + if (setname || !lcl_is_set) + tzset_unlocked(); + tmp = localsub(lclptr, timep, setname, tmp); + unlock(); + return tmp; } -/* -** Re-entrant version of localtime. -*/ +struct tm * +localtime(const time_t *timep) +{ + return localtime_tzset(timep, &tm, true); +} struct tm * -localtime_r( - const time_t * const timep, - struct tm * tmp -) { - tzset(); - return localsub(timep, 0L, tmp); +localtime_r(const time_t *timep, struct tm *tmp) +{ + return localtime_tzset(timep, tmp, false); } /* @@ -1365,249 +1614,164 @@ localtime_r( */ static struct tm * -gmtsub( - const time_t * const timep, - const int32_t offset, - struct tm * const tmp -) { +gmtsub(struct state const *sp, time_t const *timep, int_fast32_t offset, + struct tm *tmp) +{ register struct tm * result; - if (!gmt_is_set) { - gmt_is_set = TRUE; -#ifdef ALL_STATE - if (!gmtptr) { - gmtptr = malloc(sizeof(*gmtptr)); - __cxa_atexit(free, gmtptr, 0); - } - if (gmtptr) -#endif /* defined ALL_STATE */ - gmtload(gmtptr); - } + result = timesub(timep, offset, gmtptr, tmp); -#ifdef TM_ZONE /* ** Could get fancy here and deliver something such as - ** "UTC+xxxx" or "UTC-xxxx" if offset is non-zero, + ** "+xx" or "-xx" if offset is non-zero, ** but this is no time for a treasure hunt. */ - if (offset != 0) - tmp->TM_ZONE = wildabbr; - else { -#ifdef ALL_STATE - if (gmtptr == NULL) - tmp->TM_ZONE = gmt; - else tmp->TM_ZONE = gmtptr->chars; -#endif /* defined ALL_STATE */ -#ifndef ALL_STATE - tmp->TM_ZONE = gmtptr->chars; -#endif /* State Farm */ - } -#endif /* defined TM_ZONE */ + tmp->tm_zone = ((char *) + (offset ? wildabbr : gmtptr ? gmtptr->chars : gmt)); return result; } -struct tm * -gmtime( - const time_t * const timep -) { - return gmtsub(timep, 0L, &tm); -} - /* * Re-entrant version of gmtime. */ struct tm * -gmtime_r( - const time_t * const timep, - struct tm * tmp -) { - return gmtsub(timep, 0L, tmp); +gmtime_r(const time_t *timep, struct tm *tmp) +{ + gmtcheck(); + return gmtsub(gmtptr, timep, 0, tmp); } -#ifdef STD_INSPIRED - struct tm * -offtime( - const time_t * const timep, - const int32_t offset -) { - return gmtsub(timep, offset, &tm); +gmtime(const time_t *timep) +{ + return gmtime_r(timep, &tm); } -#endif /* defined STD_INSPIRED */ - /* ** Return the number of leap years through the end of the given year ** where, to make the math easy, the answer for year zero is defined as zero. */ -pureconst optimizespeed static int -leaps_thru_end_of( - const int y -) { - return (y >= 0) ? (y / 4 - y / 100 + y / 400) : - -(leaps_thru_end_of(-(y + 1)) + 1); +static time_t +leaps_thru_end_of_nonneg(time_t y) +{ + return y / 4 - y / 100 + y / 400; +} + +static time_t +leaps_thru_end_of(time_t y) +{ + return (y < 0 + ? -1 - leaps_thru_end_of_nonneg(-1 - y) + : leaps_thru_end_of_nonneg(y)); } static struct tm * -timesub( - const time_t * const timep, - const int32_t offset, - const struct state * const sp, - struct tm * const tmp -) { - const struct lsinfo * lp; - time_t tdays; - int idays; /* unsigned would be so 2003 */ - long rem; /* ^wut */ - int y; - int leap; - long corr; - int hit; - int i; +timesub(const time_t *timep, int_fast32_t offset, + const struct state *sp, struct tm *tmp) +{ + register const struct lsinfo * lp; + register time_t tdays; + register const int * ip; + register int_fast32_t corr; + register int i; + int_fast32_t idays, rem, dayoff, dayrem; + time_t y; + + /* If less than SECSPERMIN, the number of seconds since the + most recent positive leap second; otherwise, do not add 1 + to localtime tm_sec because of leap seconds. */ + time_t secs_since_posleap = SECSPERMIN; corr = 0; - hit = 0; -#ifdef ALL_STATE i = (sp == NULL) ? 0 : sp->leapcnt; -#endif /* defined ALL_STATE */ -#ifndef ALL_STATE - i = sp->leapcnt; -#endif /* State Farm */ while (--i >= 0) { lp = &sp->lsis[i]; if (*timep >= lp->ls_trans) { - if (*timep == lp->ls_trans) { - hit = ((i == 0 && lp->ls_corr > 0) || - lp->ls_corr > sp->lsis[i - 1].ls_corr); - if (hit) - while (i > 0 && - sp->lsis[i].ls_trans == - sp->lsis[i - 1].ls_trans + 1 && - sp->lsis[i].ls_corr == - sp->lsis[i - 1].ls_corr + 1) { - ++hit; - --i; - } - } corr = lp->ls_corr; + if ((i == 0 ? 0 : lp[-1].ls_corr) < corr) + secs_since_posleap = *timep - lp->ls_trans; break; } } - y = EPOCH_YEAR; - tdays = *timep / SECSPERDAY; - rem = *timep - tdays * SECSPERDAY; - while (tdays < 0 || tdays >= kYearLengths[isleap(y)]) { - int newy; - register time_t tdelta; - register int idelta; - register int leapdays; - tdelta = tdays / DAYSPERLYEAR; - idelta = tdelta; - if (tdelta - idelta >= 1 || idelta - tdelta >= 1) - return NULL; - if (idelta == 0) - idelta = (tdays < 0) ? -1 : 1; - newy = y; - if (increment_overflow(&newy, idelta)) - return NULL; + /* Calculate the year, avoiding integer overflow even if + time_t is unsigned. */ + tdays = *timep / SECSPERDAY; + rem = *timep % SECSPERDAY; + rem += offset % SECSPERDAY - corr % SECSPERDAY + 3 * SECSPERDAY; + dayoff = offset / SECSPERDAY - corr / SECSPERDAY + rem / SECSPERDAY - 3; + rem %= SECSPERDAY; + /* y = (EPOCH_YEAR + + floor((tdays + dayoff) / DAYSPERREPEAT) * YEARSPERREPEAT), + sans overflow. But calculate against 1570 (EPOCH_YEAR - + YEARSPERREPEAT) instead of against 1970 so that things work + for localtime values before 1970 when time_t is unsigned. */ + dayrem = tdays % DAYSPERREPEAT; + dayrem += dayoff % DAYSPERREPEAT; + y = (EPOCH_YEAR - YEARSPERREPEAT + + ((1 + dayoff / DAYSPERREPEAT + dayrem / DAYSPERREPEAT + - ((dayrem % DAYSPERREPEAT) < 0) + + tdays / DAYSPERREPEAT) + * YEARSPERREPEAT)); + /* idays = (tdays + dayoff) mod DAYSPERREPEAT, sans overflow. */ + idays = tdays % DAYSPERREPEAT; + idays += dayoff % DAYSPERREPEAT + 2 * DAYSPERREPEAT; + idays %= DAYSPERREPEAT; + /* Increase Y and decrease IDAYS until IDAYS is in range for Y. */ + while (year_lengths[isleap(y)] <= idays) { + int tdelta = idays / DAYSPERLYEAR; + int_fast32_t ydelta = tdelta + !tdelta; + time_t newy = y + ydelta; + register int leapdays; leapdays = leaps_thru_end_of(newy - 1) - leaps_thru_end_of(y - 1); - tdays -= ((time_t) newy - y) * DAYSPERNYEAR; - tdays -= leapdays; + idays -= ydelta * DAYSPERNYEAR; + idays -= leapdays; y = newy; } - { - register long seconds; - seconds = tdays * SECSPERDAY + 0.5; - tdays = seconds / SECSPERDAY; - rem += seconds - tdays * SECSPERDAY; + if (!TYPE_SIGNED(time_t) && y < TM_YEAR_BASE) { + int signed_y = y; + tmp->tm_year = signed_y - TM_YEAR_BASE; + } else if ((!TYPE_SIGNED(time_t) || INT_MIN + TM_YEAR_BASE <= y) + && y - TM_YEAR_BASE <= INT_MAX) + tmp->tm_year = y - TM_YEAR_BASE; + else { + errno = EOVERFLOW; + return NULL; } - /* - ** Given the range, we can now fearlessly cast... - */ - idays = tdays; - rem += offset - corr; - while (rem < 0) { - rem += SECSPERDAY; - --idays; - } - while (rem >= SECSPERDAY) { - rem -= SECSPERDAY; - ++idays; - } - while (idays < 0) { - if (increment_overflow(&y, -1)) - return NULL; - idays += kYearLengths[isleap(y)]; - } - while (idays >= kYearLengths[isleap(y)]) { - idays -= kYearLengths[isleap(y)]; - if (increment_overflow(&y, 1)) - return NULL; - } - tmp->tm_year = y; - if (increment_overflow(&tmp->tm_year, -TM_YEAR_BASE)) - return NULL; tmp->tm_yday = idays; /* ** The "extra" mods below avoid overflow problems. */ - tmp->tm_wday = EPOCH_WDAY + - ((y - EPOCH_YEAR) % DAYSPERWEEK) * - (DAYSPERNYEAR % DAYSPERWEEK) + - leaps_thru_end_of(y - 1) - - leaps_thru_end_of(EPOCH_YEAR - 1) + - idays; + tmp->tm_wday = (TM_WDAY_BASE + + ((tmp->tm_year % DAYSPERWEEK) + * (DAYSPERNYEAR % DAYSPERWEEK)) + + leaps_thru_end_of(y - 1) + - leaps_thru_end_of(TM_YEAR_BASE - 1) + + idays); tmp->tm_wday %= DAYSPERWEEK; if (tmp->tm_wday < 0) tmp->tm_wday += DAYSPERWEEK; - tmp->tm_hour = (int) (rem / SECSPERHOUR); + tmp->tm_hour = rem / SECSPERHOUR; rem %= SECSPERHOUR; - tmp->tm_min = (int) (rem / SECSPERMIN); - /* - ** A positive leap second requires a special - ** representation. This uses "... ??:59:60" et seq. - */ - tmp->tm_sec = (int) (rem % SECSPERMIN) + hit; - leap = isleap(y); - for (tmp->tm_mon = 0; - idays >= kMonthLengths[leap][tmp->tm_mon]; - ++(tmp->tm_mon)) { - idays -= kMonthLengths[leap][tmp->tm_mon]; - } - tmp->tm_mday = (int) (idays + 1); + tmp->tm_min = rem / SECSPERMIN; + tmp->tm_sec = rem % SECSPERMIN; + + /* Use "... ??:??:60" at the end of the localtime minute containing + the second just before the positive leap second. */ + tmp->tm_sec += secs_since_posleap <= tmp->tm_sec; + + ip = mon_lengths[isleap(y)]; + for (tmp->tm_mon = 0; idays >= ip[tmp->tm_mon]; ++(tmp->tm_mon)) + idays -= ip[tmp->tm_mon]; + tmp->tm_mday = idays + 1; tmp->tm_isdst = 0; -#ifdef TM_GMTOFF - tmp->TM_GMTOFF = offset; -#endif /* defined TM_GMTOFF */ + tmp->tm_gmtoff = offset; return tmp; } -/* char * */ -/* ctime(timep) */ -/* const time_t * const timep; */ -/* { */ -/* /\* */ -/* ** Section 4.12.3.2 of X3.159-1989 requires that */ -/* ** The ctime function converts the calendar time pointed to by timer */ -/* ** to local time in the form of a string. It is equivalent to */ -/* ** asctime(localtime(timer)) */ -/* *\/ */ -/* return asctime(localtime(timep)); */ -/* } */ - -/* char * */ -/* ctime_r(timep, buf) */ -/* const time_t * const timep; */ -/* char * buf; */ -/* { */ -/* struct tm mytm; */ -/* return asctime_r(localtime_r(timep, &mytm), buf); */ -/* } */ - /* ** Adapted from code provided by Robert Elz, who writes: ** The "best" way to do mktime I think is based on an idea of Bob @@ -1622,31 +1786,58 @@ timesub( #endif /* !defined WRONG */ /* -** Simplified normalize logic courtesy Paul Eggert. +** Normalize logic courtesy Paul Eggert. */ -static inline int -increment_overflow( - int * number, - int delta -) { -#ifdef __GNUC__ - return __builtin_add_overflow(*number, delta, number); -#else - int number0; - number0 = *number; - *number += delta; - return (*number < number0) != (delta < 0); -#endif +static bool +increment_overflow(int *ip, int j) +{ + register int const i = *ip; + + /* + ** If i >= 0 there can only be overflow if i + j > INT_MAX + ** or if j > INT_MAX - i; given i >= 0, INT_MAX - i cannot overflow. + ** If i < 0 there can only be overflow if i + j < INT_MIN + ** or if j < INT_MIN - i; given i < 0, INT_MIN - i cannot overflow. + */ + if ((i >= 0) ? (j > INT_MAX - i) : (j < INT_MIN - i)) + return true; + *ip += j; + return false; } -static int -normalize_overflow( - int * const tensptr, - int * const unitsptr, - const int base -) { +static bool +increment_overflow32(int_fast32_t *const lp, int const m) +{ + register int_fast32_t const l = *lp; + + if ((l >= 0) ? (m > INT_FAST32_MAX - l) : (m < INT_FAST32_MIN - l)) + return true; + *lp += m; + return false; +} + +static bool +increment_overflow_time(time_t *tp, int_fast32_t j) +{ + /* + ** This is like + ** 'if (! (TIME_T_MIN <= *tp + j && *tp + j <= TIME_T_MAX)) ...', + ** except that it does the right thing even if *tp + j would overflow. + */ + if (! (j < 0 + ? (TYPE_SIGNED(time_t) ? TIME_T_MIN - j <= *tp : -1 - j < *tp) + : *tp <= TIME_T_MAX - j)) + return true; + *tp += j; + return false; +} + +static bool +normalize_overflow(int *const tensptr, int *const unitsptr, const int base) +{ register int tensdelta; + tensdelta = (*unitsptr >= 0) ? (*unitsptr / base) : (-1 - (-1 - *unitsptr) / base); @@ -1654,14 +1845,27 @@ normalize_overflow( return increment_overflow(tensptr, tensdelta); } +static bool +normalize_overflow32(int_fast32_t *tensptr, int *unitsptr, int base) +{ + register int tensdelta; + + tensdelta = (*unitsptr >= 0) ? + (*unitsptr / base) : + (-1 - (-1 - *unitsptr) / base); + *unitsptr -= tensdelta * base; + return increment_overflow32(tensptr, tensdelta); +} + static int -tmcomp( - const struct tm * const atmp, - const struct tm * const btmp -) { +tmcomp(register const struct tm *const atmp, + register const struct tm *const btmp) +{ register int result; - if ((result = (atmp->tm_year - btmp->tm_year)) == 0 && - (result = (atmp->tm_mon - btmp->tm_mon)) == 0 && + + if (atmp->tm_year != btmp->tm_year) + return atmp->tm_year < btmp->tm_year ? -1 : 1; + if ((result = (atmp->tm_mon - btmp->tm_mon)) == 0 && (result = (atmp->tm_mday - btmp->tm_mday)) == 0 && (result = (atmp->tm_hour - btmp->tm_hour)) == 0 && (result = (atmp->tm_min - btmp->tm_min)) == 0) @@ -1670,25 +1874,26 @@ tmcomp( } static time_t -time2sub( - struct tm * const tmp, - struct tm * (* const funcp)(const time_t*, int32_t, struct tm*), - const int32_t offset, - int * const okayp, - const int do_norm_secs -) { - register const struct state * sp; +time2sub(struct tm *const tmp, + struct tm *(*funcp)(struct state const *, time_t const *, + int_fast32_t, struct tm *), + struct state const *sp, + const int_fast32_t offset, + bool *okayp, + bool do_norm_secs) +{ register int dir; register int i, j; register int saved_seconds; - register long li; + register int_fast32_t li; register time_t lo; register time_t hi; - int32_t y; + int_fast32_t y; time_t newt; time_t t; struct tm yourtm, mytm; - *okayp = FALSE; + + *okayp = false; yourtm = *tmp; if (do_norm_secs) { if (normalize_overflow(&yourtm.tm_min, &yourtm.tm_sec, @@ -1700,42 +1905,42 @@ time2sub( if (normalize_overflow(&yourtm.tm_mday, &yourtm.tm_hour, HOURSPERDAY)) return WRONG; y = yourtm.tm_year; - if (normalize_overflow(&y, &yourtm.tm_mon, MONSPERYEAR)) + if (normalize_overflow32(&y, &yourtm.tm_mon, MONSPERYEAR)) return WRONG; /* ** Turn y into an actual year number for now. ** It is converted back to an offset from TM_YEAR_BASE later. */ - if (increment_overflow(&y, TM_YEAR_BASE)) + if (increment_overflow32(&y, TM_YEAR_BASE)) return WRONG; while (yourtm.tm_mday <= 0) { - if (increment_overflow(&y, -1)) + if (increment_overflow32(&y, -1)) return WRONG; li = y + (1 < yourtm.tm_mon); - yourtm.tm_mday += kYearLengths[isleap(li)]; + yourtm.tm_mday += year_lengths[isleap(li)]; } while (yourtm.tm_mday > DAYSPERLYEAR) { li = y + (1 < yourtm.tm_mon); - yourtm.tm_mday -= kYearLengths[isleap(li)]; - if (increment_overflow(&y, 1)) + yourtm.tm_mday -= year_lengths[isleap(li)]; + if (increment_overflow32(&y, 1)) return WRONG; } for ( ; ; ) { - i = kMonthLengths[isleap(y)][yourtm.tm_mon]; + i = mon_lengths[isleap(y)][yourtm.tm_mon]; if (yourtm.tm_mday <= i) break; yourtm.tm_mday -= i; if (++yourtm.tm_mon >= MONSPERYEAR) { yourtm.tm_mon = 0; - if (increment_overflow(&y, 1)) + if (increment_overflow32(&y, 1)) return WRONG; } } - if (increment_overflow(&y, -TM_YEAR_BASE)) + if (increment_overflow32(&y, -TM_YEAR_BASE)) + return WRONG; + if (! (INT_MIN <= y && y <= INT_MAX)) return WRONG; yourtm.tm_year = y; - if (yourtm.tm_year != y) - return WRONG; if (yourtm.tm_sec >= 0 && yourtm.tm_sec < SECSPERMIN) saved_seconds = 0; else if (y + TM_YEAR_BASE < EPOCH_YEAR) { @@ -1758,27 +1963,15 @@ time2sub( /* ** Do a binary search (this works whatever time_t's type is). */ - if (!TYPE_SIGNED(time_t)) { - lo = 0; - hi = lo - 1; - } else if (!TYPE_INTEGRAL(time_t)) { - if (sizeof(time_t) > sizeof(float)) - hi = (time_t) DBL_MAX; - else hi = (time_t) FLT_MAX; - lo = -hi; - } else { - lo = 1; - for (i = 0; i < (int) TYPE_BIT(time_t) - 1; ++i) - lo *= 2; - hi = -(lo + 1); - } + lo = TIME_T_MIN; + hi = TIME_T_MAX; for ( ; ; ) { t = lo / 2 + hi / 2; if (t < lo) t = lo; else if (t > hi) t = hi; - if ((*funcp)(&t, offset, &mytm) == NULL) { + if (! funcp(sp, &t, offset, &mytm)) { /* ** Assume that t is too extreme to be represented in ** a struct tm; arrange things so that it is less @@ -1788,14 +1981,14 @@ time2sub( } else dir = tmcomp(&mytm, &yourtm); if (dir != 0) { if (t == lo) { - ++t; - if (t <= lo) + if (t == TIME_T_MAX) return WRONG; + ++t; ++lo; } else if (t == hi) { - --t; - if (t >= hi) + if (t == TIME_T_MIN) return WRONG; + --t; --hi; } if (lo > hi) @@ -1805,6 +1998,35 @@ time2sub( else lo = t; continue; } +#if defined TM_GMTOFF && ! UNINIT_TRAP + if (mytm.TM_GMTOFF != yourtm.TM_GMTOFF + && (yourtm.TM_GMTOFF < 0 + ? (-SECSPERDAY <= yourtm.TM_GMTOFF + && (mytm.TM_GMTOFF <= + (SMALLEST(INT_FAST32_MAX, LONG_MAX) + + yourtm.TM_GMTOFF))) + : (yourtm.TM_GMTOFF <= SECSPERDAY + && ((BIGGEST(INT_FAST32_MIN, LONG_MIN) + + yourtm.TM_GMTOFF) + <= mytm.TM_GMTOFF)))) { + /* MYTM matches YOURTM except with the wrong UT offset. + YOURTM.TM_GMTOFF is plausible, so try it instead. + It's OK if YOURTM.TM_GMTOFF contains uninitialized data, + since the guess gets checked. */ + time_t altt = t; + int_fast32_t diff = mytm.TM_GMTOFF - yourtm.TM_GMTOFF; + if (!increment_overflow_time(&altt, diff)) { + struct tm alttm; + if (funcp(sp, &altt, offset, &alttm) + && alttm.tm_isdst == mytm.tm_isdst + && alttm.TM_GMTOFF == yourtm.TM_GMTOFF + && tmcomp(&alttm, &yourtm) == 0) { + t = altt; + mytm = alttm; + } + } + } +#endif if (yourtm.tm_isdst < 0 || mytm.tm_isdst == yourtm.tm_isdst) break; /* @@ -1813,25 +2035,19 @@ time2sub( ** It's okay to guess wrong since the guess ** gets checked. */ - /* - ** The (void *) casts are the benefit of SunOS 3.3 on Sun 2's. - */ - sp = (const struct state *) - (((void *) funcp == (void *) localsub) ? - lclptr : gmtptr); -#ifdef ALL_STATE if (sp == NULL) return WRONG; -#endif /* defined ALL_STATE */ for (i = sp->typecnt - 1; i >= 0; --i) { if (sp->ttis[i].tt_isdst != yourtm.tm_isdst) continue; for (j = sp->typecnt - 1; j >= 0; --j) { if (sp->ttis[j].tt_isdst == yourtm.tm_isdst) continue; - newt = t + sp->ttis[j].tt_gmtoff - - sp->ttis[i].tt_gmtoff; - if ((*funcp)(&newt, offset, &mytm) == NULL) + if (ttunspecified(sp, j)) + continue; + newt = (t + sp->ttis[j].tt_utoff + - sp->ttis[i].tt_utoff); + if (! funcp(sp, &newt, offset, &mytm)) continue; if (tmcomp(&mytm, &yourtm) != 0) continue; @@ -1851,57 +2067,62 @@ label: if ((newt < t) != (saved_seconds < 0)) return WRONG; t = newt; - if ((*funcp)(&t, offset, tmp)) - *okayp = TRUE; + if (funcp(sp, &t, offset, tmp)) + *okayp = true; return t; } static time_t -time2( - struct tm * const tmp, - struct tm * (* const funcp)(const time_t*, int32_t, struct tm*), - const int32_t offset, - int * const okayp -) { - time_t t; +time2(struct tm * const tmp, + struct tm *(*funcp)(struct state const *, time_t const *, + int_fast32_t, struct tm *), + struct state const *sp, + const int_fast32_t offset, + bool *okayp) +{ + time_t t; + /* ** First try without normalization of seconds ** (in case tm_sec contains a value associated with a leap second). ** If that fails, try with normalization of seconds. */ - t = time2sub(tmp, funcp, offset, okayp, FALSE); - return *okayp ? t : time2sub(tmp, funcp, offset, okayp, TRUE); + t = time2sub(tmp, funcp, sp, offset, okayp, false); + return *okayp ? t : time2sub(tmp, funcp, sp, offset, okayp, true); } static time_t -time1( - struct tm * const tmp, - struct tm * (* const funcp)(const time_t *, int32_t, struct tm *), - const int32_t offset -) { +time1(struct tm *const tmp, + struct tm *(*funcp)(struct state const *, time_t const *, + int_fast32_t, struct tm *), + struct state const *sp, + const int_fast32_t offset) +{ register time_t t; - register const struct state * sp; register int samei, otheri; register int sameind, otherind; register int i; register int nseen; - int seen[TZ_MAX_TYPES]; - int types[TZ_MAX_TYPES]; - int okay; + char seen[TZ_MAX_TYPES]; + unsigned char types[TZ_MAX_TYPES]; + bool okay; + + if (tmp == NULL) { + errno = EINVAL; + return WRONG; + } if (tmp->tm_isdst > 1) tmp->tm_isdst = 1; - t = time2(tmp, funcp, offset, &okay); -#ifdef PCTS - /* - ** PCTS code courtesy Grant Sullivan. - */ + t = time2(tmp, funcp, sp, offset, &okay); if (okay) return t; if (tmp->tm_isdst < 0) +#ifdef PCTS + /* + ** POSIX Conformance Test Suite code courtesy Grant Sullivan. + */ tmp->tm_isdst = 0; /* reset to std and try again */ -#endif /* defined PCTS */ -#ifndef PCTS - if (okay || tmp->tm_isdst < 0) +#else return t; #endif /* !defined PCTS */ /* @@ -1910,21 +2131,14 @@ time1( ** We try to divine the type they started from and adjust to the ** type they need. */ - /* - ** The (void *) casts are the benefit of SunOS 3.3 on Sun 2's. - */ - sp = (const struct state *) (((void *) funcp == (void *) localsub) ? - lclptr : gmtptr); -#ifdef ALL_STATE if (sp == NULL) return WRONG; -#endif /* defined ALL_STATE */ for (i = 0; i < sp->typecnt; ++i) - seen[i] = FALSE; + seen[i] = false; nseen = 0; for (i = sp->timecnt - 1; i >= 0; --i) - if (!seen[sp->types[i]]) { - seen[sp->types[i]] = TRUE; + if (!seen[sp->types[i]] && !ttunspecified(sp, sp->types[i])) { + seen[sp->types[i]] = true; types[nseen++] = sp->types[i]; } for (sameind = 0; sameind < nseen; ++sameind) { @@ -1935,117 +2149,57 @@ time1( otheri = types[otherind]; if (sp->ttis[otheri].tt_isdst == tmp->tm_isdst) continue; - tmp->tm_sec += sp->ttis[otheri].tt_gmtoff - - sp->ttis[samei].tt_gmtoff; + tmp->tm_sec += (sp->ttis[otheri].tt_utoff + - sp->ttis[samei].tt_utoff); tmp->tm_isdst = !tmp->tm_isdst; - t = time2(tmp, funcp, offset, &okay); + t = time2(tmp, funcp, sp, offset, &okay); if (okay) return t; - tmp->tm_sec -= sp->ttis[otheri].tt_gmtoff - - sp->ttis[samei].tt_gmtoff; + tmp->tm_sec -= (sp->ttis[otheri].tt_utoff + - sp->ttis[samei].tt_utoff); tmp->tm_isdst = !tmp->tm_isdst; } } return WRONG; } -time_t -mktime( - struct tm * const tmp -) { - tzset(); - return time1(tmp, localsub, 0L); +static time_t +mktime_tzname(struct state *sp, struct tm *tmp, bool setname) +{ + if (sp) + return time1(tmp, localsub, sp, setname); + else { + gmtcheck(); + return time1(tmp, gmtsub, gmtptr, 0); + } } time_t -timelocal( - struct tm * const tmp -) { - tmp->tm_isdst = -1; /* in case it wasn't initialized */ - return mktime(tmp); +mktime(struct tm *tmp) +{ + time_t t; + int err = lock(); + if (err) { + errno = err; + return -1; + } + tzset_unlocked(); + t = mktime_tzname(lclptr, tmp, true); + unlock(); + return t; } -time_t -timegm( - struct tm * const tmp -) { - tmp->tm_isdst = 0; - return time1(tmp, gmtsub, 0L); -} - -time_t -timeoff( - struct tm * const tmp, - const long offset -) { - tmp->tm_isdst = 0; - return time1(tmp, gmtsub, offset); -} - -/* -** IEEE Std 1003.1-1988 (POSIX) legislates that 536457599 -** shall correspond to "Wed Dec 31 23:59:59 UTC 1986", which -** is not the case if we are accounting for leap seconds. -** So, we provide the following conversion routines for use -** when exchanging timestamps with POSIX conforming systems. -*/ - -static long -leapcorr( - time_t * timep -) { - register struct state * sp; - register struct lsinfo * lp; +static int_fast32_t +leapcorr(struct state const *sp, time_t t) +{ + register struct lsinfo const * lp; register int i; - sp = lclptr; i = sp->leapcnt; while (--i >= 0) { lp = &sp->lsis[i]; - if (*timep >= lp->ls_trans) + if (t >= lp->ls_trans) return lp->ls_corr; } return 0; } - -pureconst time_t -time2posix( - time_t t -) { - tzset(); - return t - leapcorr(&t); -} - -pureconst time_t -posix2time( - time_t t -) { - time_t x; - time_t y; - - tzset(); - /* - ** For a positive leap second hit, the result - ** is not unique. For a negative leap second - ** hit, the corresponding time doesn't exist, - ** so we return an adjacent second. - */ - x = t + leapcorr(&t); - y = x - leapcorr(&x); - if (y < t) { - do { - x++; - y = x - leapcorr(&x); - } while (y < t); - if (t != y) - return x - 1; - } else if (y > t) { - do { - --x; - y = x - leapcorr(&x); - } while (y > t); - if (t != y) - return x + 1; - } - return x; -} diff --git a/libc/time/strftime.c b/libc/time/strftime.c index f49462291..41d16607f 100644 --- a/libc/time/strftime.c +++ b/libc/time/strftime.c @@ -1,5 +1,5 @@ -/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│ -│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│ +/*-*- mode:c; indent-tabs-mode:t; tab-width:8; coding:utf-8 -*-│ +│vi: set et ft=c ts=8 tw=8 fenc=utf-8 :vi│ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright (c) 1989 The Regents of the University of California. │ │ All rights reserved. │ @@ -16,354 +16,529 @@ │ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED │ │ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/assert.h" -#include "libc/calls/calls.h" #include "libc/fmt/fmt.h" -#include "libc/fmt/itoa.h" -#include "libc/macros.internal.h" -#include "libc/nexgen32e/nexgen32e.h" -#include "libc/time/struct/tm.h" +#include "libc/inttypes.h" +#include "libc/stdio/stdio.h" #include "libc/time/time.h" -#include "libc/time/tzfile.internal.h" +#include "libc/time/tz.internal.h" +#include "libc/unicode/locale.h" +// clang-format off +// converts broken-down timestamp to string + +#define DIVISOR 100 asm(".ident\t\"\\n\\n\ strftime (BSD-3)\\n\ Copyright 1989 The Regents of the University of California\""); asm(".include \"libc/disclaimer.inc\""); -static char *strftime_add(char *p, const char *pe, const char *str) { - while (p < pe && (*p = *str++)) ++p; - return p; +/* +** Based on the UCB version with the copyright notice appearing above. +** +** This is ANSIish only when "multibyte character == plain character". +*/ + +struct lc_time_T { + const char * mon[MONSPERYEAR]; + const char * month[MONSPERYEAR]; + const char * wday[DAYSPERWEEK]; + const char * weekday[DAYSPERWEEK]; + const char * X_fmt; + const char * x_fmt; + const char * c_fmt; + const char * am; + const char * pm; + const char * date_fmt; +}; + +#define Locale (&C_time_locale) + +static const struct lc_time_T C_time_locale = { + { + "Jan", "Feb", "Mar", "Apr", "May", "Jun", + "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" + }, { + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" + }, { + "Sun", "Mon", "Tue", "Wed", + "Thu", "Fri", "Sat" + }, { + "Sunday", "Monday", "Tuesday", "Wednesday", + "Thursday", "Friday", "Saturday" + }, + + /* X_fmt */ + "%H:%M:%S", + + /* + ** x_fmt + ** C99 and later require this format. + ** Using just numbers (as here) makes Quakers happier; + ** it's also compatible with SVR4. + */ + "%m/%d/%y", + + /* + ** c_fmt + ** C99 and later require this format. + ** Previously this code used "%D %X", but we now conform to C99. + ** Note that + ** "%a %b %d %H:%M:%S %Y" + ** is used by Solaris 2.3. + */ + "%a %b %e %T %Y", + + /* am */ + "AM", + + /* pm */ + "PM", + + /* date_fmt */ + "%a %b %e %H:%M:%S %Z %Y" +}; + +enum warn { IN_NONE, IN_SOME, IN_THIS, IN_ALL }; + +#ifndef YEAR_2000_NAME +#define YEAR_2000_NAME "CHECK_STRFTIME_FORMATS_FOR_TWO_DIGIT_YEARS" +#endif /* !defined YEAR_2000_NAME */ + +static char * +_add(const char *str, char *pt, const char *ptlim) +{ + while (pt < ptlim && (*pt = *str++) != '\0') + ++pt; + return pt; } -static char *strftime_conv(char *p, const char *pe, int n, const char *format) { - char buf[22]; - (snprintf)(buf, sizeof(buf), format, n); - return strftime_add(p, pe, buf); +static char * +_conv(int n, const char *format, char *pt, const char *ptlim) +{ + char buf[INT_STRLEN_MAXIMUM(int) + 1]; + (sprintf)(buf, format, n); + return _add(buf, pt, ptlim); } -static char *strftime_secs(char *p, const char *pe, const struct tm *t) { - char ibuf[21]; - struct tm tmp; - int64_t s; - tmp = *t; /* Make a copy, mktime(3) modifies the tm struct. */ - s = mktime(&tmp); - FormatInt64(ibuf, s); - return strftime_add(p, pe, ibuf); +/* +** POSIX and the C Standard are unclear or inconsistent about +** what %C and %y do if the year is negative or exceeds 9999. +** Use the convention that %C concatenated with %y yields the +** same output as %Y, and that %Y contains at least 4 bytes, +** with more only if necessary. +*/ + +static char * +_yconv(int a, int b, bool convert_top, bool convert_yy, + char *pt, const char *ptlim) +{ + register int lead; + register int trail; + + trail = a % DIVISOR + b % DIVISOR; + lead = a / DIVISOR + b / DIVISOR + trail / DIVISOR; + trail %= DIVISOR; + if (trail < 0 && lead > 0) { + trail += DIVISOR; + --lead; + } else if (lead < 0 && trail > 0) { + trail -= DIVISOR; + ++lead; + } + if (convert_top) { + if (lead == 0 && trail < 0) + pt = _add("-0", pt, ptlim); + else pt = _conv(lead, "%02d", pt, ptlim); + } + if (convert_yy) + pt = _conv(((trail < 0) ? -trail : trail), "%02d", pt, ptlim); + return pt; } -static char *strftime_timefmt(char *p, const char *pe, const char *format, - const struct tm *t) { - int i; - long diff; - char const *sign; - /* size_t z1, z2, z3; */ - for (; *format; ++format) { - if (*format == '%') { - label: - switch (*++format) { - case '\0': - --format; - break; - case 'A': - p = strftime_add(p, pe, - (t->tm_wday < 0 || t->tm_wday > 6) - ? "?" - : kWeekdayName[t->tm_wday]); - continue; - case 'a': - p = strftime_add(p, pe, - (t->tm_wday < 0 || t->tm_wday > 6) - ? "?" - : kWeekdayNameShort[t->tm_wday]); - continue; - case 'B': - p = strftime_add( - p, pe, - (t->tm_mon < 0 || t->tm_mon > 11) ? "?" : kMonthName[t->tm_mon]); - continue; - case 'b': - case 'h': - p = strftime_add(p, pe, - (t->tm_mon < 0 || t->tm_mon > 11) - ? "?" - : kMonthNameShort[t->tm_mon]); - continue; - case 'c': - p = strftime_timefmt(p, pe, "%D %X", t); - continue; - case 'C': - /* - ** %C used to do a... - ** strftime_timefmt("%a %b %e %X %Y", t); - ** ...whereas now POSIX 1003.2 calls for - ** something completely different. - ** (ado, 5/24/93) - */ - p = strftime_conv(p, pe, div100int64(t->tm_year + TM_YEAR_BASE), - "%02d"); - continue; - case 'D': - p = strftime_timefmt(p, pe, "%m/%d/%y", t); - continue; - case 'x': - /* - ** Version 3.0 of strftime from Arnold Robbins - ** (arnold@skeeve.atl.ga.us) does the - ** equivalent of... - ** strftime_timefmt("%a %b %e %Y"); - ** ...for %x; since the X3J11 C language - ** standard calls for "date, using locale's - ** date format," anything goes. Using just - ** numbers (as here) makes Quakers happier. - ** Word from Paul Eggert (eggert@twinsun.com) - ** is that %Y-%m-%d is the ISO standard date - ** format, specified in ISO 2014 and later - ** ISO 8601:1988, with a summary available in - ** pub/doc/ISO/english/ISO8601.ps.Z on - ** ftp.uni-erlangen.de. - ** (ado, 5/30/93) - */ - p = strftime_timefmt(p, pe, "%m/%d/%y", t); - continue; - case 'd': - p = strftime_conv(p, pe, t->tm_mday, "%02d"); - continue; - case 'E': - case 'O': - /* - ** POSIX locale extensions, a la - ** Arnold Robbins' strftime version 3.0. - ** The sequences - ** %Ec %EC %Ex %Ey %EY - ** %Od %oe %OH %OI %Om %OM - ** %OS %Ou %OU %OV %Ow %OW %Oy - ** are supposed to provide alternate - ** representations. - ** (ado, 5/24/93) - */ - goto label; - case 'e': - p = strftime_conv(p, pe, t->tm_mday, "%2d"); - continue; - case 'H': - p = strftime_conv(p, pe, t->tm_hour, "%02d"); - continue; - case 'I': - p = strftime_conv(p, pe, (t->tm_hour % 12) ? (t->tm_hour % 12) : 12, - "%02d"); - continue; - case 'j': - p = strftime_conv(p, pe, t->tm_yday + 1, "%03d"); - continue; - case 'k': - /* - ** This used to be... - ** strftime_conv(t->tm_hour % 12 ? - ** t->tm_hour % 12 : 12, 2, ' '); - ** ...and has been changed to the below to - ** match SunOS 4.1.1 and Arnold Robbins' - ** strftime version 3.0. That is, "%k" and - ** "%l" have been swapped. - ** (ado, 5/24/93) - */ - p = strftime_conv(p, pe, t->tm_hour, "%2d"); - continue; +static char * +_fmt(const char *format, const struct tm *t, char *pt, + const char *ptlim, enum warn *warnp) +{ + for ( ; *format; ++format) { + if (*format == '%') { +label: + switch (*++format) { + case '\0': + --format; + break; + case 'A': + pt = _add((t->tm_wday < 0 || + t->tm_wday >= DAYSPERWEEK) ? + "?" : Locale->weekday[t->tm_wday], + pt, ptlim); + continue; + case 'a': + pt = _add((t->tm_wday < 0 || + t->tm_wday >= DAYSPERWEEK) ? + "?" : Locale->wday[t->tm_wday], + pt, ptlim); + continue; + case 'B': + pt = _add((t->tm_mon < 0 || + t->tm_mon >= MONSPERYEAR) ? + "?" : Locale->month[t->tm_mon], + pt, ptlim); + continue; + case 'b': + case 'h': + pt = _add((t->tm_mon < 0 || + t->tm_mon >= MONSPERYEAR) ? + "?" : Locale->mon[t->tm_mon], + pt, ptlim); + continue; + case 'C': + /* + ** %C used to do a... + ** _fmt("%a %b %e %X %Y", t); + ** ...whereas now POSIX 1003.2 calls for + ** something completely different. + ** (ado, 1993-05-24) + */ + pt = _yconv(t->tm_year, TM_YEAR_BASE, + true, false, pt, ptlim); + continue; + case 'c': + { + enum warn warn2 = IN_SOME; + + pt = _fmt(Locale->c_fmt, t, pt, ptlim, &warn2); + if (warn2 == IN_ALL) + warn2 = IN_THIS; + if (warn2 > *warnp) + *warnp = warn2; + } + continue; + case 'D': + pt = _fmt("%m/%d/%y", t, pt, ptlim, warnp); + continue; + case 'd': + pt = _conv(t->tm_mday, "%02d", pt, ptlim); + continue; + case 'E': + case 'O': + /* + ** Locale modifiers of C99 and later. + ** The sequences + ** %Ec %EC %Ex %EX %Ey %EY + ** %Od %oe %OH %OI %Om %OM + ** %OS %Ou %OU %OV %Ow %OW %Oy + ** are supposed to provide alternative + ** representations. + */ + goto label; + case 'e': + pt = _conv(t->tm_mday, "%2d", pt, ptlim); + continue; + case 'F': + pt = _fmt("%Y-%m-%d", t, pt, ptlim, warnp); + continue; + case 'H': + pt = _conv(t->tm_hour, "%02d", pt, ptlim); + continue; + case 'I': + pt = _conv((t->tm_hour % 12) ? + (t->tm_hour % 12) : 12, + "%02d", pt, ptlim); + continue; + case 'j': + pt = _conv(t->tm_yday + 1, "%03d", pt, ptlim); + continue; + case 'k': + /* + ** This used to be... + ** _conv(t->tm_hour % 12 ? + ** t->tm_hour % 12 : 12, 2, ' '); + ** ...and has been changed to the below to + ** match SunOS 4.1.1 and Arnold Robbins' + ** strftime version 3.0. That is, "%k" and + ** "%l" have been swapped. + ** (ado, 1993-05-24) + */ + pt = _conv(t->tm_hour, "%2d", pt, ptlim); + continue; #ifdef KITCHEN_SINK - case 'K': - /* - ** After all this time, still unclaimed! - */ - p = strftime_add(p, pe, "kitchen sink"); - continue; + case 'K': + /* + ** After all this time, still unclaimed! + */ + pt = _add("kitchen sink", pt, ptlim); + continue; #endif /* defined KITCHEN_SINK */ - case 'l': - /* - ** This used to be... - ** strftime_conv(t->tm_hour, 2, ' '); - ** ...and has been changed to the below to - ** match SunOS 4.1.1 and Arnold Robbin's - ** strftime version 3.0. That is, "%k" and - ** "%l" have been swapped. - ** (ado, 5/24/93) - */ - p = strftime_conv(p, pe, (t->tm_hour % 12) ? (t->tm_hour % 12) : 12, - "%2d"); - continue; - case 'M': - p = strftime_conv(p, pe, t->tm_min, "%02d"); - continue; - case 'm': - p = strftime_conv(p, pe, t->tm_mon + 1, "%02d"); - continue; - case 'n': - p = strftime_add(p, pe, "\n"); - continue; - case 'p': - p = strftime_add(p, pe, t->tm_hour >= 12 ? "PM" : "AM"); - continue; - case 'R': - p = strftime_timefmt(p, pe, "%H:%M", t); - continue; - case 'r': - p = strftime_timefmt(p, pe, "%I:%M:%S %p", t); - continue; - case 'S': - p = strftime_conv(p, pe, t->tm_sec, "%02d"); - continue; - case 's': - p = strftime_secs(p, pe, t); - continue; - case 'T': - case 'X': - p = strftime_timefmt(p, pe, "%H:%M:%S", t); - continue; - case 't': - p = strftime_add(p, pe, "\t"); - continue; - case 'U': - p = strftime_conv(p, pe, (t->tm_yday + 7 - t->tm_wday) / 7, "%02d"); - continue; - case 'u': - /* - ** From Arnold Robbins' strftime version 3.0: - ** "ISO 8601: Weekday as a decimal number - ** [1 (Monday) - 7]" - ** (ado, 5/24/93) - */ - p = strftime_conv(p, pe, (t->tm_wday == 0) ? 7 : t->tm_wday, "%d"); - continue; - case 'V': - /* - ** From Arnold Robbins' strftime version 3.0: - ** "the week number of the year (the first - ** Monday as the first day of week 1) as a - ** decimal number (01-53). The method for - ** determining the week number is as specified - ** by ISO 8601 (to wit: if the week containing - ** January 1 has four or more days in the new - ** year, then it is week 1, otherwise it is - ** week 53 of the previous year and the next - ** week is week 1)." - ** (ado, 5/24/93) - */ - /* - ** XXX--If January 1 falls on a Friday, - ** January 1-3 are part of week 53 of the - ** previous year. By analogy, if January - ** 1 falls on a Thursday, are December 29-31 - ** of the PREVIOUS year part of week 1??? - ** (ado 5/24/93) - ** - ** You are understood not to expect this. - */ - i = (t->tm_yday + 10 - (t->tm_wday ? (t->tm_wday - 1) : 6)) / 7; - if (i == 0) { - /* - ** What day of the week does - ** January 1 fall on? - */ - i = t->tm_wday - (t->tm_yday - 1); - /* - ** Fri Jan 1: 53 - ** Sun Jan 1: 52 - ** Sat Jan 1: 53 if previous - ** year a leap - ** year, else 52 - */ - if (i == TM_FRIDAY) - i = 53; - else if (i == TM_SUNDAY) - i = 52; - else - i = isleap(t->tm_year + TM_YEAR_BASE) ? 53 : 52; -#ifdef XPG4_1994_04_09 - /* - ** As of 4/9/94, though, - ** XPG4 calls for 53 - ** unconditionally. - */ - i = 53; -#endif /* defined XPG4_1994_04_09 */ - } - p = strftime_conv(p, pe, i, "%02d"); - continue; - case 'v': - /* - ** From Arnold Robbins' strftime version 3.0: - ** "date as dd-bbb-YYYY" - ** (ado, 5/24/93) - */ - p = strftime_timefmt(p, pe, "%e-%b-%Y", t); - continue; - case 'W': - p = strftime_conv( - p, pe, (t->tm_yday + 7 - (t->tm_wday ? (t->tm_wday - 1) : 6)) / 7, - "%02d"); - continue; - case 'w': - p = strftime_conv(p, pe, t->tm_wday, "%d"); - continue; - case 'y': - p = strftime_conv(p, pe, (t->tm_year + TM_YEAR_BASE) % 100, "%02d"); - continue; - case 'Y': - p = strftime_conv(p, pe, t->tm_year + TM_YEAR_BASE, "%04d"); - continue; - case 'Z': - if (t->tm_zone) { - p = strftime_add(p, pe, t->tm_zone); - } else { - if (t->tm_isdst == 0 || t->tm_isdst == 1) { - p = strftime_add(p, pe, tzname[t->tm_isdst]); - } else { - p = strftime_add(p, pe, "?"); - } - } - continue; - case 'z': - if (t->tm_isdst < 0) continue; -#ifdef TM_GMTOFF - diff = t->TM_GMTOFF; -#else /* !defined TM_GMTOFF */ - if (t->tm_isdst == 0) -#ifdef USG_COMPAT - diff = -timezone; -#else /* !defined USG_COMPAT */ - continue; -#endif /* !defined USG_COMPAT */ - else -#ifdef ALTZONE - diff = -altzone; -#else /* !defined ALTZONE */ - continue; -#endif /* !defined ALTZONE */ -#endif /* !defined TM_GMTOFF */ - if (diff < 0) { - sign = "-"; - diff = -diff; - } else { - sign = "+"; - } - p = strftime_add(p, pe, sign); - diff /= SECSPERMIN; - diff = (diff / MINSPERHOUR) * 100 + (diff % MINSPERHOUR); - p = strftime_conv(p, pe, diff, "%04d"); - continue; - case '%': - /* - * X311J/88-090 (4.12.3.5): if conversion char is - * undefined, behavior is undefined. Print out the - * character itself as printf(3) also does. - */ - default: - break; - } - } - if (p >= pe) break; - *p++ = *format; - } - return p; + case 'l': + /* + ** This used to be... + ** _conv(t->tm_hour, 2, ' '); + ** ...and has been changed to the below to + ** match SunOS 4.1.1 and Arnold Robbin's + ** strftime version 3.0. That is, "%k" and + ** "%l" have been swapped. + ** (ado, 1993-05-24) + */ + pt = _conv((t->tm_hour % 12) ? + (t->tm_hour % 12) : 12, + "%2d", pt, ptlim); + continue; + case 'M': + pt = _conv(t->tm_min, "%02d", pt, ptlim); + continue; + case 'm': + pt = _conv(t->tm_mon + 1, "%02d", pt, ptlim); + continue; + case 'n': + pt = _add("\n", pt, ptlim); + continue; + case 'p': + pt = _add((t->tm_hour >= (HOURSPERDAY / 2)) ? + Locale->pm : + Locale->am, + pt, ptlim); + continue; + case 'R': + pt = _fmt("%H:%M", t, pt, ptlim, warnp); + continue; + case 'r': + pt = _fmt("%I:%M:%S %p", t, pt, ptlim, warnp); + continue; + case 'S': + pt = _conv(t->tm_sec, "%02d", pt, ptlim); + continue; + case 's': + { + struct tm tm; + char buf[INT_STRLEN_MAXIMUM( + time_t) + 1]; + time_t mkt; + + tm = *t; + tm.tm_yday = -1; + mkt = mktime(&tm); + if (mkt == (time_t) -1) { + /* Fail unless this -1 represents + a valid time. */ + struct tm tm_1; + if (!localtime_r(&mkt, &tm_1)) + return NULL; + if (!(tm.tm_year == tm_1.tm_year + && tm.tm_yday == tm_1.tm_yday + && tm.tm_hour == tm_1.tm_hour + && tm.tm_min == tm_1.tm_min + && tm.tm_sec == tm_1.tm_sec)) + return NULL; + } + if (TYPE_SIGNED(time_t)) { + intmax_t n = mkt; + (sprintf)(buf, "%"PRIdMAX, n); + } else { + uintmax_t n = mkt; + (sprintf)(buf, "%"PRIuMAX, n); + } + pt = _add(buf, pt, ptlim); + } + continue; + case 'T': + pt = _fmt("%H:%M:%S", t, pt, ptlim, warnp); + continue; + case 't': + pt = _add("\t", pt, ptlim); + continue; + case 'U': + pt = _conv((t->tm_yday + DAYSPERWEEK - + t->tm_wday) / DAYSPERWEEK, + "%02d", pt, ptlim); + continue; + case 'u': + /* + ** From Arnold Robbins' strftime version 3.0: + ** "ISO 8601: Weekday as a decimal number + ** [1 (Monday) - 7]" + ** (ado, 1993-05-24) + */ + pt = _conv((t->tm_wday == 0) ? + DAYSPERWEEK : t->tm_wday, + "%d", pt, ptlim); + continue; + case 'V': /* ISO 8601 week number */ + case 'G': /* ISO 8601 year (four digits) */ + case 'g': /* ISO 8601 year (two digits) */ +/* +** From Arnold Robbins' strftime version 3.0: "the week number of the +** year (the first Monday as the first day of week 1) as a decimal number +** (01-53)." +** (ado, 1993-05-24) +** +** From by Markus Kuhn: +** "Week 01 of a year is per definition the first week which has the +** Thursday in this year, which is equivalent to the week which contains +** the fourth day of January. In other words, the first week of a new year +** is the week which has the majority of its days in the new year. Week 01 +** might also contain days from the previous year and the week before week +** 01 of a year is the last week (52 or 53) of the previous year even if +** it contains days from the new year. A week starts with Monday (day 1) +** and ends with Sunday (day 7). For example, the first week of the year +** 1997 lasts from 1996-12-30 to 1997-01-05..." +** (ado, 1996-01-02) +*/ + { + int year; + int base; + int yday; + int wday; + int w; + + year = t->tm_year; + base = TM_YEAR_BASE; + yday = t->tm_yday; + wday = t->tm_wday; + for ( ; ; ) { + int len; + int bot; + int top; + + len = isleap_sum(year, base) ? + DAYSPERLYEAR : + DAYSPERNYEAR; + /* + ** What yday (-3 ... 3) does + ** the ISO year begin on? + */ + bot = ((yday + 11 - wday) % + DAYSPERWEEK) - 3; + /* + ** What yday does the NEXT + ** ISO year begin on? + */ + top = bot - + (len % DAYSPERWEEK); + if (top < -3) + top += DAYSPERWEEK; + top += len; + if (yday >= top) { + ++base; + w = 1; + break; + } + if (yday >= bot) { + w = 1 + ((yday - bot) / + DAYSPERWEEK); + break; + } + --base; + yday += isleap_sum(year, base) ? + DAYSPERLYEAR : + DAYSPERNYEAR; + } + if (*format == 'V') + pt = _conv(w, "%02d", + pt, ptlim); + else if (*format == 'g') { + *warnp = IN_ALL; + pt = _yconv(year, base, + false, true, + pt, ptlim); + } else pt = _yconv(year, base, + true, true, + pt, ptlim); + } + continue; + case 'v': + /* + ** From Arnold Robbins' strftime version 3.0: + ** "date as dd-bbb-YYYY" + ** (ado, 1993-05-24) + */ + pt = _fmt("%e-%b-%Y", t, pt, ptlim, warnp); + continue; + case 'W': + pt = _conv((t->tm_yday + DAYSPERWEEK - + (t->tm_wday ? + (t->tm_wday - 1) : + (DAYSPERWEEK - 1))) / DAYSPERWEEK, + "%02d", pt, ptlim); + continue; + case 'w': + pt = _conv(t->tm_wday, "%d", pt, ptlim); + continue; + case 'X': + pt = _fmt(Locale->X_fmt, t, pt, ptlim, warnp); + continue; + case 'x': + { + enum warn warn2 = IN_SOME; + + pt = _fmt(Locale->x_fmt, t, pt, ptlim, &warn2); + if (warn2 == IN_ALL) + warn2 = IN_THIS; + if (warn2 > *warnp) + *warnp = warn2; + } + continue; + case 'y': + *warnp = IN_ALL; + pt = _yconv(t->tm_year, TM_YEAR_BASE, + false, true, + pt, ptlim); + continue; + case 'Y': + pt = _yconv(t->tm_year, TM_YEAR_BASE, + true, true, + pt, ptlim); + continue; + case 'Z': + pt = _add(t->tm_zone, pt, ptlim); + /* + ** C99 and later say that %Z must be + ** replaced by the empty string if the + ** time zone abbreviation is not + ** determinable. + */ + continue; + case 'z': + { + long diff; + char const * sign; + bool negative; + + diff = t->tm_gmtoff; + negative = diff < 0; + if (diff == 0) { + negative = t->tm_zone[0] == '-'; + } + if (negative) { + sign = "-"; + diff = -diff; + } else sign = "+"; + pt = _add(sign, pt, ptlim); + diff /= SECSPERMIN; + diff = (diff / MINSPERHOUR) * 100 + + (diff % MINSPERHOUR); + pt = _conv(diff, "%04d", pt, ptlim); + } + continue; + case '+': + pt = _fmt(Locale->date_fmt, t, pt, ptlim, + warnp); + continue; + case '%': + /* + ** X311J/88-090 (4.12.3.5): if conversion char is + ** undefined, behavior is undefined. Print out the + ** character itself as printf(3) also does. + */ + default: + break; + } + } + if (pt == ptlim) + break; + *pt++ = *format; + } + return pt; } /** @@ -379,14 +554,32 @@ static char *strftime_timefmt(char *p, const char *pe, const char *format, * * @return bytes copied excluding nul, or 0 on error */ -size_t strftime(char *s, size_t size, const char *f, const struct tm *t) { - char *p; - p = strftime_timefmt(s, s + size, f, t); - if (p < s + size) { - *p = '\0'; - return p - s; - } else { - s[size - 1] = '\0'; - return 0; - } +size_t +strftime(char *s, size_t maxsize, const char *format, const struct tm *t) +{ + char * p; + int saved_errno = errno; + enum warn warn = IN_NONE; + + tzset(); + p = _fmt(format, t, s, s + maxsize, &warn); + if (!p) { + errno = EOVERFLOW; + return 0; + } + if (p == s + maxsize) { + errno = ERANGE; + return 0; + } + *p = '\0'; + errno = saved_errno; + return p - s; +} + +size_t +strftime_l(char *s, size_t maxsize, char const *format, struct tm const *t, + locale_t locale) +{ + /* Just call strftime, as only the C locale is supported. */ + return strftime(s, maxsize, format, t); } diff --git a/libc/time/time.mk b/libc/time/time.mk index 412653371..0690eaa7c 100644 --- a/libc/time/time.mk +++ b/libc/time/time.mk @@ -67,6 +67,10 @@ o/$(MODE)/libc/time/strftime.o: \ -fdata-sections \ -ffunction-sections +o/$(MODE)/libc/time/localtime.o: \ + OVERRIDE_CPPFLAGS += \ + -DSTACK_FRAME_UNLIMITED + o/$(MODE)/libc/time/now.o: \ OVERRIDE_CFLAGS += \ -O3 diff --git a/libc/time/tz.internal.h b/libc/time/tz.internal.h new file mode 100644 index 000000000..5c4f2acaf --- /dev/null +++ b/libc/time/tz.internal.h @@ -0,0 +1,542 @@ +#ifndef COSMOPOLITAN_THIRD_PARTY_TZ_PRIVATE_H_ +#define COSMOPOLITAN_THIRD_PARTY_TZ_PRIVATE_H_ +#include "libc/calls/weirdtypes.h" +#include "libc/errno.h" +#include "libc/inttypes.h" +#include "libc/limits.h" +#include "libc/runtime/runtime.h" +#include "libc/sysv/consts/ok.h" +#if !(__ASSEMBLER__ + __LINKER__ + 0) +COSMOPOLITAN_C_START_ +/* clang-format off */ +/* Private header for tzdb code. */ + +/* +** This file is in the public domain, so clarified as of +** 1996-06-05 by Arthur David Olson. +*/ + +/* +** This header is for use ONLY with the time conversion code. +** There is no guarantee that it will remain unchanged, +** or that it will remain at all. +** Do NOT copy it to any system include directory. +** Thank you! +*/ + +/* +** zdump has been made independent of the rest of the time +** conversion package to increase confidence in the verification it provides. +** You can use zdump to help in verifying other implementations. +** To do this, compile with -DUSE_LTZ=0 and link without the tz library. +*/ +#ifndef USE_LTZ +# define USE_LTZ 1 +#endif + +/* This string was in the Factory zone through version 2016f. */ +#define GRANDPARENTED "Local time zone must be set--see zic manual page" + +/* +** Defaults for preprocessor symbols. +** You can override these in your C compiler options, e.g. '-DHAVE_GETTEXT=1'. +*/ + +#ifndef HAVE_DECL_ASCTIME_R +#define HAVE_DECL_ASCTIME_R 1 +#endif + +#if !defined HAVE_GENERIC && defined __has_extension +# if __has_extension(c_generic_selections) +# define HAVE_GENERIC 1 +# else +# define HAVE_GENERIC 0 +# endif +#endif +/* _Generic is buggy in pre-4.9 GCC. */ +#if !defined HAVE_GENERIC && defined __GNUC__ +# define HAVE_GENERIC (4 < __GNUC__ + (9 <= __GNUC_MINOR__)) +#endif +#ifndef HAVE_GENERIC +# define HAVE_GENERIC (201112 <= __STDC_VERSION__) +#endif + +#ifndef HAVE_GETTEXT +#define HAVE_GETTEXT 0 +#endif /* !defined HAVE_GETTEXT */ + +#ifndef HAVE_INCOMPATIBLE_CTIME_R +#define HAVE_INCOMPATIBLE_CTIME_R 0 +#endif + +#ifndef HAVE_LINK +#define HAVE_LINK 1 +#endif /* !defined HAVE_LINK */ + +#ifndef HAVE_MALLOC_ERRNO +#define HAVE_MALLOC_ERRNO 1 +#endif + +#ifndef HAVE_POSIX_DECLS +#define HAVE_POSIX_DECLS 1 +#endif + +#ifndef HAVE_STRTOLL +#define HAVE_STRTOLL 1 +#endif + +#ifndef HAVE_SYMLINK +#define HAVE_SYMLINK 1 +#endif /* !defined HAVE_SYMLINK */ + +#if HAVE_INCOMPATIBLE_CTIME_R +#define asctime_r _incompatible_asctime_r +#define ctime_r _incompatible_ctime_r +#endif /* HAVE_INCOMPATIBLE_CTIME_R */ + +/* +** Nested includes +*/ + +/* Avoid clashes with NetBSD by renaming NetBSD's declarations. + If defining the 'timezone' variable, avoid a clash with FreeBSD's + 'timezone' function by renaming its declaration. */ +#define localtime_rz sys_localtime_rz +#define mktime_z sys_mktime_z +#define posix2time_z sys_posix2time_z +#define time2posix_z sys_time2posix_z +#if defined USG_COMPAT && USG_COMPAT == 2 +# define timezone sys_timezone +#endif +#define timezone_t sys_timezone_t +#define tzalloc sys_tzalloc +#define tzfree sys_tzfree +#undef localtime_rz +#undef mktime_z +#undef posix2time_z +#undef time2posix_z +#if defined USG_COMPAT && USG_COMPAT == 2 +# undef timezone +#endif +#undef timezone_t +#undef tzalloc +#undef tzfree + +#if HAVE_GETTEXT +#include +#endif /* HAVE_GETTEXT */ + +#ifndef HAVE_STRFTIME_L +# if _POSIX_VERSION < 200809 +# define HAVE_STRFTIME_L 0 +# else +# define HAVE_STRFTIME_L 1 +# endif +#endif + +#ifndef USG_COMPAT +# ifndef _XOPEN_VERSION +# define USG_COMPAT 0 +# else +# define USG_COMPAT 1 +# endif +#endif + +#ifndef HAVE_TZNAME +# if _POSIX_VERSION < 198808 && !USG_COMPAT +# define HAVE_TZNAME 0 +# else +# define HAVE_TZNAME 1 +# endif +#endif + +#ifndef ALTZONE +# if defined __sun || defined _M_XENIX +# define ALTZONE 1 +# else +# define ALTZONE 0 +# endif +#endif + +#ifndef R_OK +#define R_OK 4 +#endif /* !defined R_OK */ + +#if 3 <= __GNUC__ +# define ATTRIBUTE_FORMAT(spec) __attribute__((__format__ spec)) +#else +# define ATTRIBUTE_FORMAT(spec) /* empty */ +#endif + +/* +** Workarounds for compilers/systems. +*/ + +#ifndef EPOCH_LOCAL +# define EPOCH_LOCAL 0 +#endif +#ifndef EPOCH_OFFSET +# define EPOCH_OFFSET 0 +#endif + +/* +** Compile with -Dtime_tz=T to build the tz package with a private +** int64_t type equivalent to T rather than the system-supplied int64_t. +** This debugging feature can test unusual design decisions +** (e.g., int64_t wider than 'long', or unsigned int64_t) even on +** typical platforms. +*/ +#if defined time_tz || EPOCH_LOCAL || EPOCH_OFFSET != 0 +# define TZ_INT64_T 1 +#else +# define TZ_INT64_T 0 +#endif + +#if defined LOCALTIME_IMPLEMENTATION && TZ_INT64_T +static int64_t sys_time(int64_t *x) { return time(x); } +#endif + +#if TZ_INT64_T + +typedef time_tz tz_int64_t; + +# undef asctime +# define asctime tz_asctime +# undef asctime_r +# define asctime_r tz_asctime_r +# undef ctime +# define ctime tz_ctime +# undef ctime_r +# define ctime_r tz_ctime_r +# undef difftime +# define difftime tz_difftime +# undef gmtime +# define gmtime tz_gmtime +# undef gmtime_r +# define gmtime_r tz_gmtime_r +# undef localtime +# define localtime tz_localtime +# undef localtime_r +# define localtime_r tz_localtime_r +# undef localtime_rz +# define localtime_rz tz_localtime_rz +# undef mktime +# define mktime tz_mktime +# undef mktime_z +# define mktime_z tz_mktime_z +# undef offtime +# define offtime tz_offtime +# undef posix2time +# define posix2time tz_posix2time +# undef posix2time_z +# define posix2time_z tz_posix2time_z +# undef strftime +# define strftime tz_strftime +# undef time +# define time tz_time +# undef time2posix +# define time2posix tz_time2posix +# undef time2posix_z +# define time2posix_z tz_time2posix_z +# undef int64_t +# define int64_t tz_int64_t +# undef timegm +# define timegm tz_timegm +# undef timelocal +# define timelocal tz_timelocal +# undef timeoff +# define timeoff tz_timeoff +# undef tzalloc +# define tzalloc tz_tzalloc +# undef tzfree +# define tzfree tz_tzfree +# undef tzset +# define tzset tz_tzset +# if HAVE_STRFTIME_L +# undef strftime_l +# define strftime_l tz_strftime_l +# endif +# if HAVE_TZNAME +# undef tzname +# define tzname tz_tzname +# endif +# if USG_COMPAT +# undef daylight +# define daylight tz_daylight +# undef timezone +# define timezone tz_timezone +# endif +# if ALTZONE +# undef altzone +# define altzone tz_altzone +# endif + +char *asctime(struct tm const *); +char *asctime_r(struct tm const *restrict, char *restrict); +char *ctime(int64_t const *); +char *ctime_r(int64_t const *, char *); +double difftime(int64_t, int64_t) pureconst; +size_t strftime(char *restrict, size_t, char const *restrict, + struct tm const *restrict); +# if HAVE_STRFTIME_L +size_t strftime_l(char *restrict, size_t, char const *restrict, + struct tm const *restrict, locale_t); +# endif +struct tm *gmtime(int64_t const *); +struct tm *gmtime_r(int64_t const *restrict, struct tm *restrict); +struct tm *localtime(int64_t const *); +struct tm *localtime_r(int64_t const *restrict, struct tm *restrict); +int64_t mktime(struct tm *); +int64_t time(int64_t *); +void tzset(void); +#endif + +#if !HAVE_DECL_ASCTIME_R && !defined asctime_r +extern char *asctime_r(struct tm const *restrict, char *restrict); +#endif + +#ifndef HAVE_DECL_ENVIRON +# if defined environ || defined __USE_GNU +# define HAVE_DECL_ENVIRON 1 +# else +# define HAVE_DECL_ENVIRON 0 +# endif +#endif + +#if 2 <= HAVE_TZNAME + (TZ_INT64_T || !HAVE_POSIX_DECLS) +extern char *tzname[]; +#endif +#if 2 <= USG_COMPAT + (TZ_INT64_T || !HAVE_POSIX_DECLS) +extern long timezone; +extern int daylight; +#endif +#if 2 <= ALTZONE + (TZ_INT64_T || !HAVE_POSIX_DECLS) +extern long altzone; +#endif + +/* +** The STD_INSPIRED functions are similar, but most also need +** declarations if time_tz is defined. +*/ + +#ifdef STD_INSPIRED +# if TZ_INT64_T || !defined offtime +struct tm *offtime(int64_t const *, long); +# endif +# if TZ_INT64_T || !defined timegm +int64_t timegm(struct tm *); +# endif +# if TZ_INT64_T || !defined timelocal +int64_t timelocal(struct tm *); +# endif +# if TZ_INT64_T || !defined timeoff +int64_t timeoff(struct tm *, long); +# endif +# if TZ_INT64_T || !defined time2posix +int64_t time2posix(int64_t); +# endif +# if TZ_INT64_T || !defined posix2time +int64_t posix2time(int64_t); +# endif +#endif + +/* Infer TM_ZONE on systems where this information is known, but suppress + guessing if NO_TM_ZONE is defined. Similarly for TM_GMTOFF. */ +#define TM_GMTOFF tm_gmtoff +#define TM_ZONE tm_zone + +/* +** Define functions that are ABI compatible with NetBSD but have +** better prototypes. NetBSD 6.1.4 defines a pointer type timezone_t +** and labors under the misconception that 'const timezone_t' is a +** pointer to a constant. This use of 'const' is ineffective, so it +** is not done here. What we call 'struct state' NetBSD calls +** 'struct __state', but this is a private name so it doesn't matter. +*/ +#if NETBSD_INSPIRED +typedef struct state *timezone_t; +struct tm *localtime_rz(timezone_t restrict, int64_t const *restrict, + struct tm *restrict); +int64_t mktime_z(timezone_t restrict, struct tm *restrict); +timezone_t tzalloc(char const *); +void tzfree(timezone_t); +# ifdef STD_INSPIRED +# if TZ_INT64_T || !defined posix2time_z +int64_t posix2time_z(timezone_t, int64_t) nosideeffect; +# endif +# if TZ_INT64_T || !defined time2posix_z +int64_t time2posix_z(timezone_t, int64_t) nosideeffect; +# endif +# endif +#endif + +/* +** Finally, some convenience items. +*/ + +#define TYPE_BIT(type) (sizeof(type) * CHAR_BIT) +#define TYPE_SIGNED(type) (((type) -1) < 0) +#define TWOS_COMPLEMENT(t) ((t) ~ (t) 0 < 0) + +/* Max and min values of the integer type T, of which only the bottom + B bits are used, and where the highest-order used bit is considered + to be a sign bit if T is signed. */ +#define MAXVAL(t, b) \ + ((t) (((t) 1 << ((b) - 1 - TYPE_SIGNED(t))) \ + - 1 + ((t) 1 << ((b) - 1 - TYPE_SIGNED(t))))) +#define MINVAL(t, b) \ + ((t) (TYPE_SIGNED(t) ? - TWOS_COMPLEMENT(t) - MAXVAL(t, b) : 0)) + +/* The extreme time values, assuming no padding. */ +#define INT64_T_MIN_NO_PADDING MINVAL(int64_t, TYPE_BIT(int64_t)) +#define INT64_T_MAX_NO_PADDING MAXVAL(int64_t, TYPE_BIT(int64_t)) + +/* The extreme time values. These are macros, not constants, so that + any portability problems occur only when compiling .c files that use + the macros, which is safer for applications that need only zdump and zic. + This implementation assumes no padding if int64_t is signed and + either the compiler lacks support for _Generic or int64_t is not one + of the standard signed integer types. */ +#if HAVE_GENERIC +# define INT64_T_MIN \ + _Generic((int64_t) 0, \ + signed char: SCHAR_MIN, short: SHRT_MIN, \ + int: INT_MIN, long: LONG_MIN, long long: LLONG_MIN, \ + default: INT64_T_MIN_NO_PADDING) +# define INT64_T_MAX \ + (TYPE_SIGNED(int64_t) \ + ? _Generic((int64_t) 0, \ + signed char: SCHAR_MAX, short: SHRT_MAX, \ + int: INT_MAX, long: LONG_MAX, long long: LLONG_MAX, \ + default: INT64_T_MAX_NO_PADDING) \ + : (int64_t) -1) +#else +# define INT64_T_MIN INT64_T_MIN_NO_PADDING +# define INT64_T_MAX INT64_T_MAX_NO_PADDING +#endif + +/* +** 302 / 1000 is log10(2.0) rounded up. +** Subtract one for the sign bit if the type is signed; +** add one for integer division truncation; +** add one more for a minus sign if the type is signed. +*/ +#define INT_STRLEN_MAXIMUM(type) \ + ((TYPE_BIT(type) - TYPE_SIGNED(type)) * 302 / 1000 + \ + 1 + TYPE_SIGNED(type)) + +/* +** INITIALIZE(x) +*/ + +#define INITIALIZE(x) ((x) = 0) + +/* Whether memory access must strictly follow the C standard. + If 0, it's OK to read uninitialized storage so long as the value is + not relied upon. Defining it to 0 lets mktime access parts of + struct tm that might be uninitialized, as a heuristic when the + standard doesn't say what to return and when tm_gmtoff can help + mktime likely infer a better value. */ +#ifndef UNINIT_TRAP +# define UNINIT_TRAP 0 +#endif + +#ifdef DEBUG +# define UNREACHABLE() abort() +#elif 4 < __GNUC__ + (5 <= __GNUC_MINOR__) +# define UNREACHABLE() __builtin_unreachable() +#elif defined __has_builtin +# if __has_builtin(__builtin_unreachable) +# define UNREACHABLE() __builtin_unreachable() +# endif +#endif +#ifndef UNREACHABLE +# define UNREACHABLE() ((void) 0) +#endif + +/* +** For the benefit of GNU folk... +** '_(MSGID)' uses the current locale's message library string for MSGID. +** The default is to use gettext if available, and use MSGID otherwise. +*/ + +#if HAVE_GETTEXT +#define _(msgid) gettext(msgid) +#else /* !HAVE_GETTEXT */ +#define _(msgid) msgid +#endif /* !HAVE_GETTEXT */ + +#if !defined TZ_DOMAIN && defined HAVE_GETTEXT +# define TZ_DOMAIN "tz" +#endif + +#if HAVE_INCOMPATIBLE_CTIME_R +#undef asctime_r +#undef ctime_r +char *asctime_r(struct tm const *, char *); +char *ctime_r(int64_t const *, char *); +#endif /* HAVE_INCOMPATIBLE_CTIME_R */ + +/* Handy macros that are independent of tzfile implementation. */ + +#define SECSPERMIN 60 +#define MINSPERHOUR 60 +#define HOURSPERDAY 24 +#define DAYSPERWEEK 7 +#define DAYSPERNYEAR 365 +#define DAYSPERLYEAR 366 +#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) +#define SECSPERDAY ((int_fast32_t) SECSPERHOUR * HOURSPERDAY) +#define MONSPERYEAR 12 + +#define YEARSPERREPEAT 400 /* years before a Gregorian repeat */ +#define DAYSPERREPEAT ((int_fast32_t) 400 * 365 + 100 - 4 + 1) +#define SECSPERREPEAT ((int_fast64_t) DAYSPERREPEAT * SECSPERDAY) +#define AVGSECSPERYEAR (SECSPERREPEAT / YEARSPERREPEAT) + +#define TM_SUNDAY 0 +#define TM_MONDAY 1 +#define TM_TUESDAY 2 +#define TM_WEDNESDAY 3 +#define TM_THURSDAY 4 +#define TM_FRIDAY 5 +#define TM_SATURDAY 6 + +#define TM_JANUARY 0 +#define TM_FEBRUARY 1 +#define TM_MARCH 2 +#define TM_APRIL 3 +#define TM_MAY 4 +#define TM_JUNE 5 +#define TM_JULY 6 +#define TM_AUGUST 7 +#define TM_SEPTEMBER 8 +#define TM_OCTOBER 9 +#define TM_NOVEMBER 10 +#define TM_DECEMBER 11 + +#define TM_YEAR_BASE 1900 +#define TM_WDAY_BASE TM_MONDAY + +#define EPOCH_YEAR 1970 +#define EPOCH_WDAY TM_THURSDAY + +#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) + +/* +** Since everything in isleap is modulo 400 (or a factor of 400), we know that +** isleap(y) == isleap(y % 400) +** and so +** isleap(a + b) == isleap((a + b) % 400) +** or +** isleap(a + b) == isleap(a % 400 + b % 400) +** This is true even if % means modulo rather than Fortran remainder +** (which is allowed by C89 but not by C99 or later). +** We use this to avoid addition overflow problems. +*/ + +#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) + +COSMOPOLITAN_C_END_ +#endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ +#endif /* COSMOPOLITAN_THIRD_PARTY_TZ_PRIVATE_H_ */ diff --git a/libc/time/tzfile.internal.h b/libc/time/tzfile.internal.h index cc2aa6067..bfef13de0 100644 --- a/libc/time/tzfile.internal.h +++ b/libc/time/tzfile.internal.h @@ -1,46 +1,56 @@ #ifndef TZFILE_H #define TZFILE_H - -#define TM_ZONE tm_zone -#define TM_GMTOFF tm_gmtoff +/* clang-format off */ +/* Layout and location of TZif files. */ /* ** This file is in the public domain, so clarified as of ** 1996-06-05 by Arthur David Olson. */ +/* +** This header is for use ONLY with the time conversion code. +** There is no guarantee that it will remain unchanged, +** or that it will remain at all. +** Do NOT copy it to any system include directory. +** Thank you! +*/ + /* ** Information about time zone files. */ #ifndef TZDIR -#define TZDIR "/zip/usr/share/zoneinfo" -#endif +#define TZDIR "/zip/usr/share/zoneinfo" /* Time zone object file directory */ +#endif /* !defined TZDIR */ #ifndef TZDEFAULT -#define TZDEFAULT "GST" -#endif +#define TZDEFAULT "GST" +#endif /* !defined TZDEFAULT */ #ifndef TZDEFRULES -#define TZDEFRULES "New_York" -#endif +#define TZDEFRULES "New_York" +#endif /* !defined TZDEFRULES */ + + +/* See Internet RFC 8536 for more details about the following format. */ /* ** Each file begins with. . . */ -#define TZ_MAGIC "TZif" +#define TZ_MAGIC "TZif" struct tzhead { - char tzh_magic[4]; /* TZ_MAGIC */ - char tzh_version[1]; /* '\0' or '2' or '3' as of 2013 */ - char tzh_reserved[15]; /* reserved; must be zero */ - char tzh_ttisgmtcnt[4]; /* coded number of trans. time flags */ - char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ - char tzh_leapcnt[4]; /* coded number of leap seconds */ - char tzh_timecnt[4]; /* coded number of transition times */ - char tzh_typecnt[4]; /* coded number of local time types */ - char tzh_charcnt[4]; /* coded number of abbr. chars */ + char tzh_magic[4]; /* TZ_MAGIC */ + char tzh_version[1]; /* '\0' or '2'-'4' as of 2021 */ + char tzh_reserved[15]; /* reserved; must be zero */ + char tzh_ttisutcnt[4]; /* coded number of trans. time flags */ + char tzh_ttisstdcnt[4]; /* coded number of trans. time flags */ + char tzh_leapcnt[4]; /* coded number of leap seconds */ + char tzh_timecnt[4]; /* coded number of transition times */ + char tzh_typecnt[4]; /* coded number of local time types */ + char tzh_charcnt[4]; /* coded number of abbr. chars */ }; /* @@ -58,14 +68,15 @@ struct tzhead { ** one (char [4]) total correction after above ** tzh_ttisstdcnt (char)s indexed by type; if 1, transition ** time is standard time, if 0, -** transition time is wall clock time -** if absent, transition times are -** assumed to be wall clock time -** tzh_ttisgmtcnt (char)s indexed by type; if 1, transition -** time is UT, if 0, -** transition time is local time -** if absent, transition times are +** transition time is local (wall clock) +** time; if absent, transition times are ** assumed to be local time +** tzh_ttisutcnt (char)s indexed by type; if 1, transition +** time is UT, if 0, transition time is +** local time; if absent, transition +** times are assumed to be local time. +** When this is 1, the corresponding +** std/wall indicator must also be 1. */ /* @@ -91,73 +102,21 @@ struct tzhead { */ #ifndef TZ_MAX_TIMES -#define TZ_MAX_TIMES 1200 +#define TZ_MAX_TIMES 2000 #endif /* !defined TZ_MAX_TIMES */ #ifndef TZ_MAX_TYPES /* This must be at least 17 for Europe/Samara and Europe/Vilnius. */ -#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ +#define TZ_MAX_TYPES 256 /* Limited by what (unsigned char)'s can hold */ #endif /* !defined TZ_MAX_TYPES */ #ifndef TZ_MAX_CHARS -#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ -/* (limited by what unsigned chars can hold) */ +#define TZ_MAX_CHARS 50 /* Maximum number of abbreviation characters */ + /* (limited by what unsigned chars can hold) */ #endif /* !defined TZ_MAX_CHARS */ #ifndef TZ_MAX_LEAPS -#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ +#define TZ_MAX_LEAPS 50 /* Maximum number of leap second corrections */ #endif /* !defined TZ_MAX_LEAPS */ -#define SECSPERMIN 60 -#define MINSPERHOUR 60 -#define HOURSPERDAY 24 -#define DAYSPERWEEK 7 -#define DAYSPERNYEAR 365 -#define DAYSPERLYEAR 366 -#define SECSPERHOUR (SECSPERMIN * MINSPERHOUR) -#define SECSPERDAY ((int_fast32_t)SECSPERHOUR * HOURSPERDAY) -#define MONSPERYEAR 12 - -#define TM_SUNDAY 0 -#define TM_MONDAY 1 -#define TM_TUESDAY 2 -#define TM_WEDNESDAY 3 -#define TM_THURSDAY 4 -#define TM_FRIDAY 5 -#define TM_SATURDAY 6 - -#define TM_JANUARY 0 -#define TM_FEBRUARY 1 -#define TM_MARCH 2 -#define TM_APRIL 3 -#define TM_MAY 4 -#define TM_JUNE 5 -#define TM_JULY 6 -#define TM_AUGUST 7 -#define TM_SEPTEMBER 8 -#define TM_OCTOBER 9 -#define TM_NOVEMBER 10 -#define TM_DECEMBER 11 - -#define TM_YEAR_BASE 1900 - -#define EPOCH_YEAR 1970 -#define EPOCH_WDAY TM_THURSDAY - -#define isleap(y) (((y) % 4) == 0 && (((y) % 100) != 0 || ((y) % 400) == 0)) - -/* -** Since everything in isleap is modulo 400 (or a factor of 400), we know that -** isleap(y) == isleap(y % 400) -** and so -** isleap(a + b) == isleap((a + b) % 400) -** or -** isleap(a + b) == isleap(a % 400 + b % 400) -** This is true even if % means modulo rather than Fortran remainder -** (which is allowed by C89 but not C99). -** We use this to avoid addition overflow problems. -*/ - -#define isleap_sum(a, b) isleap((a) % 400 + (b) % 400) - #endif /* !defined TZFILE_H */ diff --git a/third_party/chibicc/chibicc.mk b/third_party/chibicc/chibicc.mk index c77672dac..ec387ba89 100644 --- a/third_party/chibicc/chibicc.mk +++ b/third_party/chibicc/chibicc.mk @@ -116,7 +116,7 @@ o/$(MODE)/third_party/chibicc/chibicc.com: \ @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com \ -o o/$(MODE)/third_party/chibicc/.chibicc/.symtab $< - @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \ + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \ o/$(MODE)/third_party/chibicc/.chibicc/.symtab o/$(MODE)/third_party/chibicc/as.com.dbg: \ diff --git a/third_party/linenoise/linenoise.c b/third_party/linenoise/linenoise.c index 964ed1e24..2eac70d57 100644 --- a/third_party/linenoise/linenoise.c +++ b/third_party/linenoise/linenoise.c @@ -339,7 +339,7 @@ static int notwseparator(wint_t c) { } static int iswname(wint_t c) { - return !iswseparator(c) || c == '_' || c == '-' || c == '.'; + return !iswseparator(c) || c == '_' || c == '-' || c == '.' || c == ':'; } static int notwname(wint_t c) { @@ -1956,23 +1956,22 @@ ssize_t linenoiseEdit(struct linenoiseState *l, const char *prompt, char **obuf, // handle tab and tab-tab completion if (seq[0] == '\t' && completionCallback) { - size_t i, j, k, n, m, itemlen; + size_t i, n, m; // we know that the user pressed tab once rc = 0; linenoiseFreeCompletions(&l->lc); i = Backwards(l, l->pos, iswname); - j = l->pos; { - char *s = strndup(l->buf + i, j - i); + char *s = strndup(l->buf + i, l->pos - i); completionCallback(s, &l->lc); free(s); } m = GetCommonPrefixLength(&l->lc); - if (m > j - i || (m == j - i && l->lc.len == 1)) { + if (m > l->pos - i || (m == l->pos - i && l->lc.len == 1)) { // on common prefix (or single completion) we complete and return - n = i + m + (l->len - j); + n = i + m + (l->len - l->pos); if (linenoiseGrow(l, n + 1)) { - memmove(l->buf + i + m, l->buf + i + j, l->len - j + 1); + memmove(l->buf + i + m, l->buf + l->pos, l->len - l->pos + 1); memcpy(l->buf + i, l->lc.cvec[0], m); l->pos = i + m; l->len = n; @@ -1994,7 +1993,7 @@ ssize_t linenoiseEdit(struct linenoiseState *l, const char *prompt, char **obuf, if (rc == 1 && seq[0] == '\t') { const char **p; struct abuf ab; - int i, x, y, xn, yn, xy; + int i, k, x, y, xn, yn, xy, itemlen; itemlen = linenoiseMaxCompletionWidth(&l->lc) + 4; xn = MAX(1, (l->ws.ws_col - 1) / itemlen); yn = (l->lc.len + (xn - 1)) / xn; diff --git a/third_party/lua/cosmo.h b/third_party/lua/cosmo.h index 6b8980b8b..06d136548 100644 --- a/third_party/lua/cosmo.h +++ b/third_party/lua/cosmo.h @@ -8,8 +8,8 @@ COSMOPOLITAN_C_START_ char *LuaFormatStack(lua_State *) dontdiscard; int LuaCallWithTrace(lua_State *, int, int, lua_State *); -int LuaEncodeJsonData(lua_State *, char **, int, char *, int); -int LuaEncodeLuaData(lua_State *, char **, int, char *, int); +int LuaEncodeJsonData(lua_State *, char **, char *, int); +int LuaEncodeLuaData(lua_State *, char **, char *, int); int LuaEncodeUrl(lua_State *); int LuaParseUrl(lua_State *); int LuaPushHeader(lua_State *, struct HttpMessage *, char *, int); diff --git a/third_party/lua/ldo.c b/third_party/lua/ldo.c index e873e6de8..b9a1670cb 100644 --- a/third_party/lua/ldo.c +++ b/third_party/lua/ldo.c @@ -27,6 +27,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #define ldo_c #define LUA_CORE +#include "libc/log/log.h" #include "libc/runtime/gc.h" #include "third_party/lua/lapi.h" #include "third_party/lua/ldebug.h" @@ -148,7 +149,7 @@ l_noret luaD_throw (lua_State *L, int errcode) { lua_unlock(L); g->panic(L); /* call panic function (last chance to jump out) */ } - abort(); + __die(); } } } diff --git a/third_party/lua/lrepl.c b/third_party/lua/lrepl.c index cf3d131e4..5bdd29561 100644 --- a/third_party/lua/lrepl.c +++ b/third_party/lua/lrepl.c @@ -29,15 +29,18 @@ #include "libc/alg/alg.h" #include "libc/calls/calls.h" #include "libc/calls/sigbits.h" +#include "libc/errno.h" #include "libc/intrin/kprintf.h" #include "libc/intrin/nomultics.internal.h" #include "libc/log/check.h" #include "libc/macros.internal.h" #include "libc/runtime/gc.h" #include "libc/runtime/runtime.h" +#include "libc/str/str.h" #include "libc/sysv/consts/sa.h" #include "libc/x/x.h" #include "third_party/linenoise/linenoise.h" +#include "third_party/lua/cosmo.h" #include "third_party/lua/lauxlib.h" #include "third_party/lua/lprefix.h" #include "third_party/lua/lrepl.h" @@ -87,37 +90,74 @@ static const char *g_historypath; #endif -static void lua_readline_addcompletion (linenoiseCompletions *c, const char *s) { - char **p, *t; +static void lua_readline_addcompletion (linenoiseCompletions *c, char *s) { + char **p; if ((p = realloc(c->cvec, (c->len + 1) * sizeof(*p)))) { c->cvec = p; - if ((t = strdup(s))) { - c->cvec[c->len++] = t; - } + c->cvec[c->len++] = s; } } void lua_readline_completions (const char *p, linenoiseCompletions *c) { int i; + bool found; lua_State *L; const char *name; - for (i = 0; i < ARRAYLEN(kKeywordHints); ++i) { - if (startswithi(kKeywordHints[i], p)) { - lua_readline_addcompletion(c, kKeywordHints[i]); - } - } + char *a, *b, *component; + + // start searching globals L = globalL; lua_pushglobaltable(L); + + // traverse parent objects + // split foo.bar and foo:bar + a = p; + b = strpbrk(a, ".:"); + while (b) { + component = strndup(a, b - a); + found = false; + lua_pushnil(L); // search key + while (lua_next(L, -2)) { + if (lua_type(L, -2) == LUA_TSTRING) { + name = lua_tostring(L, -2); + if (!strcmp(name, component)) { + lua_remove(L, -3); // remove table + lua_remove(L, -2); // remove key + found = true; + break; + } + } + lua_pop(L, 1); // pop value + } + free(component); + if (!found) { + lua_pop(L, 1); // pop table + return; + } + a = b + 1; + b = strpbrk(a, ".:"); + } + + // search final object lua_pushnil(L); - while (lua_next(L, -2) != 0) { - name = lua_tostring(L, -2); - if (startswithi(name, p)) { - lua_readline_addcompletion(c, name); + while (lua_next(L, -2)) { + if (lua_type(L, -2) == LUA_TSTRING) { + name = lua_tostring(L, -2); + if (startswithi(name, a)) { + lua_readline_addcompletion(c, xasprintf("%.*s%s", a - p, p, name)); + } } lua_pop(L, 1); } + lua_pop(L, 1); + + for (i = 0; i < ARRAYLEN(kKeywordHints); ++i) { + if (startswithi(kKeywordHints[i], p)) { + lua_readline_addcompletion(c, xstrdup(kKeywordHints[i])); + } + } if (lua_repl_completions_callback) { lua_repl_completions_callback(p, c); } @@ -191,7 +231,7 @@ static ssize_t pushline (lua_State *L, int firstline) { prmt = strdup(get_prompt(L, firstline)); lua_pop(L, 1); /* remove prompt */ LUA_REPL_UNLOCK; - rc = linenoiseEdit(lua_repl_linenoise, 0, &b, !firstline || lua_repl_blocking); + rc = linenoiseEdit(lua_repl_linenoise, prmt, &b, !firstline || lua_repl_blocking); free(prmt); if (rc != -1) { if (b && *b) { @@ -207,6 +247,9 @@ static ssize_t pushline (lua_State *L, int firstline) { LUA_REPL_LOCK; rc = b ? 1 : -1; } + if (!(rc == -1 && errno == EAGAIN)) { + write(1, "\n", 1); + } if (rc == -1 || (!rc && !b)) { return rc; } diff --git a/third_party/lua/ltests.c b/third_party/lua/ltests.c index 6b1606902..f0cf71020 100644 --- a/third_party/lua/ltests.c +++ b/third_party/lua/ltests.c @@ -1724,7 +1724,7 @@ static int runC (lua_State *L, lua_State *L1, const char *pc) { lua_error(L1); } else if EQ("abort") { - abort(); + __die(); } else if EQ("throw") { #if defined(__cplusplus) diff --git a/third_party/lua/lua.main.c b/third_party/lua/lua.main.c index afbc7280b..3ff767466 100644 --- a/third_party/lua/lua.main.c +++ b/third_party/lua/lua.main.c @@ -324,7 +324,6 @@ static void doREPL (lua_State *L) { progname = oldprogname; return; } - lua_writeline(); if (status == LUA_OK) status = lua_runchunk(L, 0, LUA_MULTRET); if (status == LUA_OK) { @@ -335,7 +334,6 @@ static void doREPL (lua_State *L) { } lua_freerepl(); lua_settop(L, 0); /* clear stack */ - lua_writeline(); progname = oldprogname; } diff --git a/third_party/lua/lua.mk b/third_party/lua/lua.mk index 4b892d8d4..e63b3fe92 100644 --- a/third_party/lua/lua.mk +++ b/third_party/lua/lua.mk @@ -77,7 +77,7 @@ o/$(MODE)/third_party/lua/lua.com: \ @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com \ -o o/$(MODE)/third_party/lua/.lua/.symtab $< - @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \ + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \ o/$(MODE)/third_party/lua/.lua/.symtab o//third_party/lua/lgc.o: \ diff --git a/third_party/lua/luaencodejsondata.c b/third_party/lua/luaencodejsondata.c index d894ff0e5..16c9ba5e6 100644 --- a/third_party/lua/luaencodejsondata.c +++ b/third_party/lua/luaencodejsondata.c @@ -25,8 +25,8 @@ #include "third_party/lua/lauxlib.h" #include "third_party/lua/lua.h" -int LuaEncodeJsonData(lua_State *L, char **buf, int level, char *numformat, - int idx) { +static int LuaEncodeJsonDataImpl(lua_State *L, char **buf, int level, + char *numformat, int idx) { char *s; bool isarray; size_t tbllen, z; @@ -93,7 +93,7 @@ int LuaEncodeJsonData(lua_State *L, char **buf, int level, char *numformat, for (size_t i = 1; i <= tbllen; i++) { if (i > 1) appendw(buf, ','); lua_rawgeti(L, -1, i); // table/-2, value/-1 - LuaEncodeJsonData(L, buf, level - 1, numformat, -1); + LuaEncodeJsonDataImpl(L, buf, level - 1, numformat, -1); lua_pop(L, 1); } } else { @@ -121,7 +121,7 @@ int LuaEncodeJsonData(lua_State *L, char **buf, int level, char *numformat, lua_remove(L, -1); // remove copied key: tab/-3, key/-2, val/-1 } appendw(buf, '"' | ':' << 010); - LuaEncodeJsonData(L, buf, level - 1, numformat, -1); + LuaEncodeJsonDataImpl(L, buf, level - 1, numformat, -1); lua_pop(L, 1); // table/-2, key/-1 } // stack: table/-1, as the key was popped by lua_next @@ -137,3 +137,9 @@ int LuaEncodeJsonData(lua_State *L, char **buf, int level, char *numformat, unreachable; } } + +int LuaEncodeJsonData(lua_State *L, char **buf, char *numformat, int idx) { + int rc; + rc = LuaEncodeJsonDataImpl(L, buf, 64, numformat, idx); + return rc; +} diff --git a/third_party/lua/luaencodeluadata.c b/third_party/lua/luaencodeluadata.c index b07bf852c..15aa03518 100644 --- a/third_party/lua/luaencodeluadata.c +++ b/third_party/lua/luaencodeluadata.c @@ -16,42 +16,98 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" #include "libc/bits/bits.h" #include "libc/fmt/itoa.h" #include "libc/math.h" +#include "libc/mem/mem.h" #include "libc/stdio/append.internal.h" +#include "libc/x/x.h" #include "third_party/lua/cosmo.h" #include "third_party/lua/lauxlib.h" #include "third_party/lua/lua.h" -int LuaEncodeLuaData(lua_State *L, char **buf, int level, char *numformat, - int idx) { +struct Visited { + int n; + void **p; +}; + +static bool PushVisit(struct Visited *visited, void *p) { + int i; + for (i = 0; i < visited->n; ++i) { + if (visited->p[i] == p) { + return false; + } + } + visited->p = xrealloc(visited->p, ++visited->n * sizeof(*visited->p)); + visited->p[visited->n - 1] = p; + return true; +} + +static void PopVisit(struct Visited *visited) { + assert(visited->n > 0); + --visited->n; +} + +static int LuaEncodeLuaDataImpl(lua_State *L, char **buf, int level, + char *numformat, int idx, + struct Visited *visited) { char *s; - int ktype; + bool didcomma; lua_Integer i; + int ktype, vtype; size_t tbllen, buflen, slen; char ibuf[21], fmt[] = "%.14g"; if (level > 0) { switch (lua_type(L, idx)) { + case LUA_TNIL: appendw(buf, READ32LE("nil")); return 0; + case LUA_TSTRING: s = lua_tolstring(L, idx, &slen); EscapeLuaString(s, slen, buf); return 0; + case LUA_TFUNCTION: appendf(buf, "\"func@%p\"", lua_touserdata(L, idx)); return 0; - case LUA_TUSERDATA: - appendf(buf, "\"udata@%p\"", lua_touserdata(L, idx)); - return 0; + case LUA_TLIGHTUSERDATA: appendf(buf, "\"light@%p\"", lua_touserdata(L, idx)); return 0; + case LUA_TTHREAD: appendf(buf, "\"thread@%p\"", lua_touserdata(L, idx)); return 0; + + case LUA_TUSERDATA: + if (luaL_callmeta(L, idx, "__repr")) { + if (lua_type(L, -1) == LUA_TSTRING) { + s = lua_tolstring(L, -1, &slen); + appendd(buf, s, slen); + } else { + appendf(buf, "[[error %s returned a %s value]]", "__repr", + luaL_typename(L, -1)); + } + lua_pop(L, 1); + return 0; + } + if (luaL_callmeta(L, idx, "__tostring")) { + if (lua_type(L, -1) == LUA_TSTRING) { + s = lua_tolstring(L, -1, &slen); + EscapeLuaString(s, slen, buf); + } else { + appendf(buf, "[[error %s returned a %s value]]", "__tostring", + luaL_typename(L, -1)); + } + lua_pop(L, 1); + return 0; + } + appendf(buf, "\"udata@%p\"", lua_touserdata(L, idx)); + return 0; + case LUA_TNUMBER: if (lua_isinteger(L, idx)) { appendd(buf, ibuf, @@ -69,11 +125,13 @@ int LuaEncodeLuaData(lua_State *L, char **buf, int level, char *numformat, fmt[4] = *numformat; break; default: - return luaL_error(L, "numformat string not allowed"); + luaL_error(L, "numformat string not allowed"); + unreachable; } appendf(buf, fmt, lua_tonumber(L, idx)); } return 0; + case LUA_TBOOLEAN: if (lua_toboolean(L, idx)) { appendw(buf, READ32LE("true")); @@ -81,26 +139,47 @@ int LuaEncodeLuaData(lua_State *L, char **buf, int level, char *numformat, appendw(buf, READ64LE("false\0\0")); } return 0; + case LUA_TTABLE: i = 0; + didcomma = false; appendw(buf, '{'); + lua_pushvalue(L, idx); lua_pushnil(L); // push the first key - while (lua_next(L, -2) != 0) { + while (lua_next(L, -2)) { + ++i; ktype = lua_type(L, -2); - if (i++ > 0) appendw(buf, ','); + vtype = lua_type(L, -1); if (ktype != LUA_TNUMBER || lua_tointeger(L, -2) != i) { - appendw(buf, '['); - lua_pushvalue(L, -2); // table/-4, key/-3, value/-2, key/-1 - LuaEncodeLuaData(L, buf, level - 1, numformat, -1); - lua_remove(L, -1); // remove copied key: table/-3, key/-2, value/-1 - appendw(buf, ']' | '=' << 010); + if (PushVisit(visited, lua_touserdata(L, -2))) { + if (i > 1) appendw(buf, ',' | ' ' << 8); + didcomma = true; + appendw(buf, '['); + LuaEncodeLuaDataImpl(L, buf, level - 1, numformat, -2, visited); + appendw(buf, ']' | '=' << 010); + PopVisit(visited); + } else { + // TODO: Strange Lua data structure, do nothing. + lua_pop(L, 1); + continue; + } + } + if (PushVisit(visited, lua_touserdata(L, -1))) { + if (!didcomma && i > 1) appendw(buf, ',' | ' ' << 8); + LuaEncodeLuaDataImpl(L, buf, level - 1, numformat, -1, visited); + PopVisit(visited); + } else { + // TODO: Strange Lua data structure, do nothing. + lua_pop(L, 1); + continue; } - LuaEncodeLuaData(L, buf, level - 1, numformat, -1); lua_pop(L, 1); // table/-2, key/-1 } + lua_pop(L, 1); // table // stack: table/-1, as the key was popped by lua_next appendw(buf, '}'); return 0; + default: luaL_error(L, "can't serialize value of this type"); unreachable; @@ -110,3 +189,12 @@ int LuaEncodeLuaData(lua_State *L, char **buf, int level, char *numformat, unreachable; } } + +int LuaEncodeLuaData(lua_State *L, char **buf, char *numformat, int idx) { + int rc; + struct Visited visited = {0}; + rc = LuaEncodeLuaDataImpl(L, buf, 64, numformat, idx, &visited); + assert(!visited.n); + free(visited.p); + return rc; +} diff --git a/third_party/lua/luaformatstack.c b/third_party/lua/luaformatstack.c index bf155443c..f6b362dce 100644 --- a/third_party/lua/luaformatstack.c +++ b/third_party/lua/luaformatstack.c @@ -28,7 +28,7 @@ dontdiscard char *LuaFormatStack(lua_State *L) { for (i = 1; i <= top; i++) { if (i > 1) appendw(&b, '\n'); appendf(&b, "\t%d\t%s\t", i, luaL_typename(L, i)); - LuaEncodeLuaData(L, &b, 64, "g", -1); + LuaEncodeLuaData(L, &b, "g", i); } return b; } diff --git a/third_party/make/make.mk b/third_party/make/make.mk index b759ab637..fd472595e 100644 --- a/third_party/make/make.mk +++ b/third_party/make/make.mk @@ -131,7 +131,7 @@ o/$(MODE)/third_party/make/make.com: \ @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com \ -o o/$(MODE)/third_party/make/.make/.symtab $< - @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \ + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \ o/$(MODE)/third_party/make/.make/.symtab $(THIRD_PARTY_MAKE_OBJS): \ diff --git a/third_party/python/python.mk b/third_party/python/python.mk index 840617bb9..3448ae3b9 100644 --- a/third_party/python/python.mk +++ b/third_party/python/python.mk @@ -4197,7 +4197,7 @@ o/$(MODE)/third_party/python/python.com: \ @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com \ -o o/$(MODE)/third_party/python/.python/.symtab $< - @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \ + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \ o/$(MODE)/third_party/python/.python/.symtab ################################################################################ diff --git a/third_party/quickjs/quickjs.mk b/third_party/quickjs/quickjs.mk index 3fa14c00a..4749a6bc8 100644 --- a/third_party/quickjs/quickjs.mk +++ b/third_party/quickjs/quickjs.mk @@ -154,7 +154,7 @@ o/$(MODE)/third_party/quickjs/qjs.com: \ @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com \ -o o/$(MODE)/third_party/quickjs/.qjs/.symtab $< - @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \ + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \ o/$(MODE)/third_party/quickjs/.qjs/.symtab o/$(MODE)/third_party/quickjs/qjsc.com.dbg: \ diff --git a/third_party/sqlite3/sqlite3.mk b/third_party/sqlite3/sqlite3.mk index 4094baec5..81b204bf0 100644 --- a/third_party/sqlite3/sqlite3.mk +++ b/third_party/sqlite3/sqlite3.mk @@ -83,7 +83,7 @@ o/$(MODE)/third_party/sqlite3/sqlite3.com: \ @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com \ -o o/$(MODE)/third_party/sqlite3/.sqlite3/.symtab $< - @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \ + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \ o/$(MODE)/third_party/sqlite3/.sqlite3/.symtab $(THIRD_PARTY_SQLITE3_A): \ diff --git a/tool/build/build.mk b/tool/build/build.mk index b2d1b9eb9..9df4dace4 100644 --- a/tool/build/build.mk +++ b/tool/build/build.mk @@ -97,7 +97,7 @@ o/$(MODE)/tool/build/blinkenlights.com: \ @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com \ -o o/$(MODE)/tool/build/.blinkenlights/.symtab $< - @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \ + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \ o/$(MODE)/tool/build/.blinkenlights/.symtab o/$(MODE)/tool/build/ar.com.dbg: \ diff --git a/tool/net/help.txt b/tool/net/help.txt index 85470e687..d267d3390 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -585,7 +585,6 @@ FUNCTIONS ignored if used outside of request handling code. - numformat: sets numeric format to be used, which can be 'g', 'f', or 'a' [experimental api] - - maxdepth: (number=64) sets the max number of nested tables. EncodeLua(value[,options:table]) → json:str Turns passed Lua value into a Lua string. The following options @@ -593,7 +592,6 @@ FUNCTIONS - useoutput: (bool=false) encodes the result directly to the output buffer and returns `nil` value. This option is ignored if used outside of request handling code. - - maxdepth: (number=64) sets the max number of nested tables. EncodeLatin1(utf-8:str[,flags:int]) → iso-8859-1:str Turns UTF-8 into ISO-8859-1 string. @@ -1421,11 +1419,12 @@ UNIX MODULE The following values may also be OR'd into `flags`: - - `O_CREAT`: create file if it doesn't exist + - `O_CREAT` create file if it doesn't exist - `O_TRUNC` automatic ftruncate(fd,0) if exists - - `O_CLOEXEC`: automatic close() upon execve() - - `O_EXCL`: exclusive access (see below) - - `O_APPEND`: open file for append only + - `O_CLOEXEC` automatic close() upon execve() + - `O_EXCL` exclusive access (see below) + - `O_APPEND` open file for append only + - `O_NONBLOCK` asks read/write to fail with EAGAIN rather than block - `O_DIRECT` it's complicated (not supported on Apple and OpenBSD) - `O_DIRECTORY` useful for stat'ing (hint on UNIX but required on NT) - `O_TMPFILE` try to make temp more secure (Linux and Windows only) @@ -1615,7 +1614,7 @@ UNIX MODULE - `O_CLOEXEC`: Automatically close file descriptor upon execve() - - `O_NONBLOCK`: Request `EAGAIN` be raised rather than blocking. + - `O_NONBLOCK`: Request `EAGAIN` be raised rather than blocking - `O_DIRECT`: Enable packet mode w/ atomic reads and writes, so long as they're no larger than `PIPE_BUF` (guaranteed to be 512+ bytes) @@ -2634,6 +2633,52 @@ UNIX MODULE Returns information about resource limit. + unix.gmtime(unixts:int) + ├─→ year,mon,mday,hour,min,sec,gmtoffsec,wday,yday,dst:int,zone:str + └─→ nil,unix.Errno + + Breaks down UNIX timestamp into Zulu Time numbers. + + - `mon` 1 ≤ mon ≤ 12 + - `mday` 1 ≤ mday ≤ 31 + - `hour` 0 ≤ hour ≤ 23 + - `min` 0 ≤ min ≤ 59 + - `sec` 0 ≤ sec ≤ 60 + - `gmtoff` ±93600 seconds + - `wday` 0 ≤ wday ≤ 6 + - `yday` 0 ≤ yday ≤ 365 + - `dst` 1 if daylight savings, 0 if not, -1 if not unknown + + unix.localtime(unixts:int) + ├─→ year,mon,mday,hour,min,sec,gmtoffsec,wday,yday,dst:int,zone:str + └─→ nil,unix.Errno + + Breaks down UNIX timestamp into local time numbers. + + This follows the same API as gmtime() which has further details. + + Your redbean ships with a subset of the time zone database. + + - `/zip/usr/share/zoneinfo/Honolulu` + - `/zip/usr/share/zoneinfo/Anchorage` + - `/zip/usr/share/zoneinfo/GST` + - `/zip/usr/share/zoneinfo/Boulder` + - `/zip/usr/share/zoneinfo/Chicago` + - `/zip/usr/share/zoneinfo/New_York` + - `/zip/usr/share/zoneinfo/UTC` + - `/zip/usr/share/zoneinfo/London` + - `/zip/usr/share/zoneinfo/Berlin` + - `/zip/usr/share/zoneinfo/Israel` + - `/zip/usr/share/zoneinfo/Beijing` + - `/zip/usr/share/zoneinfo/Japan` + - `/zip/usr/share/zoneinfo/Sydney` + + You can control which timezone is used using the `TZ` environment + variable. If your time zone isn't included in the above list, you + can simply copy it inside your redbean. The same is also the case + for future updates to the database, which can be swapped out when + needed, without having to recompile. + unix.stat(path:str[, flags:int[, dirfd:int]]) ├─→ unix.Stat └─→ nil, unix.Errno @@ -2847,7 +2892,7 @@ UNIX MODULE actually consumes. For example, for small file systems, your system might report this number as being 8, which means 4096 bytes. - On Windows NT, if compression is enabled for a file, then this + On Windows NT, if `O_COMPRESSED` is used for a file, then this number will reflect the size *after* compression. you can use: st = assert(unix.stat("moby.txt")) diff --git a/tool/net/lunix.c b/tool/net/lunix.c index f1b1fc567..7c46553f6 100644 --- a/tool/net/lunix.c +++ b/tool/net/lunix.c @@ -98,15 +98,9 @@ struct UnixDir { DIR *dir; }; -struct UnixStat { - int refs; - struct stat st; -}; - struct UnixErrno { - int refs; - uint16_t errno; - uint16_t winerr; + int errno; + int winerr; const char *call; }; @@ -137,12 +131,17 @@ static void *LuaUnixAlloc(lua_State *L, size_t n) { } static void LuaPushSigset(lua_State *L, struct sigset set) { - struct sigset *sp; - sp = lua_newuserdatauv(L, sizeof(*sp), 1); + struct sigset *sp = lua_newuserdatauv(L, sizeof(*sp), 1); luaL_setmetatable(L, "unix.Sigset"); *sp = set; } +static void LuaPushStat(lua_State *L, struct stat *st) { + struct stat *stp = lua_newuserdatauv(L, sizeof(*stp), 1); + luaL_setmetatable(L, "unix.Stat"); + *stp = *st; +} + static void LuaSetIntField(lua_State *L, const char *k, lua_Integer v) { lua_pushinteger(L, v); lua_setfield(L, -2, k); @@ -163,19 +162,8 @@ static dontinline int ReturnString(lua_State *L, const char *x) { return 1; } -static void LuaUnixPushErrno(lua_State *L, const char *sc, int uerr, int werr) { - struct UnixErrno *ue, **uep; - ue = LuaUnixAlloc(L, sizeof(*ue)); - ue->refs = 1; - ue->call = sc; - ue->errno = uerr; - ue->winerr = werr; - uep = lua_newuserdatauv(L, sizeof(*uep), 1); - luaL_setmetatable(L, "unix.Errno"); - *uep = ue; -} - -static dontinline int SysretErrno(lua_State *L, const char *call, int olderr) { +static int SysretErrno(lua_State *L, const char *call, int olderr) { + struct UnixErrno *ep; int i, unixerr, winerr; unixerr = errno; winerr = GetLastError(); @@ -183,7 +171,11 @@ static dontinline int SysretErrno(lua_State *L, const char *call, int olderr) { WARNF("errno should not be %d", unixerr); } lua_pushnil(L); - LuaUnixPushErrno(L, call, unixerr, winerr); + ep = lua_newuserdatauv(L, sizeof(*ep), 1); + luaL_setmetatable(L, "unix.Errno"); + ep->errno = unixerr; + ep->winerr = winerr; + ep->call = call; errno = olderr; return 2; } @@ -936,49 +928,33 @@ static int LuaUnixWrite(lua_State *L) { return SysretInteger(L, "write", olderr, rc); } -static int ReturnStat(lua_State *L, struct UnixStat *ust) { - struct UnixStat **ustp; - ust->refs = 1; - ustp = lua_newuserdatauv(L, sizeof(*ustp), 1); - luaL_setmetatable(L, "unix.Stat"); - *ustp = ust; - return 1; -} - // unix.stat(path:str[, flags:int[, dirfd:int]]) // ├─→ unix.Stat // └─→ nil, unix.Errno static int LuaUnixStat(lua_State *L) { - const char *path; - struct UnixStat *ust; - int dirfd, flags, olderr = errno; - path = luaL_checkstring(L, 1); - flags = luaL_optinteger(L, 2, 0); - dirfd = luaL_optinteger(L, 3, AT_FDCWD); - if ((ust = LuaUnixAllocRaw(L, sizeof(*ust)))) { - if (!fstatat(dirfd, path, &ust->st, flags)) { - return ReturnStat(L, ust); - } - free(ust); + struct stat st; + int olderr = errno; + if (!fstatat(luaL_optinteger(L, 3, AT_FDCWD), luaL_checkstring(L, 1), &st, + luaL_optinteger(L, 2, 0))) { + LuaPushStat(L, &st); + return 1; + } else { + return SysretErrno(L, "stat", olderr); } - return SysretErrno(L, "stat", olderr); } // unix.fstat(fd:int) // ├─→ unix.Stat // └─→ nil, unix.Errno static int LuaUnixFstat(lua_State *L) { - int fd, olderr = errno; - struct UnixStat *ust; - olderr = errno; - fd = luaL_checkinteger(L, 1); - if ((ust = LuaUnixAllocRaw(L, sizeof(*ust)))) { - if (!fstat(fd, &ust->st)) { - return ReturnStat(L, ust); - } - free(ust); + struct stat st; + int olderr = errno; + if (!fstat(luaL_checkinteger(L, 1), &st)) { + LuaPushStat(L, &st); + return 1; + } else { + return SysretErrno(L, "fstat", olderr); } - return SysretErrno(L, "fstat", olderr); } static bool IsSockoptBool(int l, int x) { @@ -1746,9 +1722,7 @@ static int LuaUnixMinor(lua_State *L) { // unix.Stat object static dontinline struct stat *GetUnixStat(lua_State *L) { - struct UnixStat **ust; - ust = luaL_checkudata(L, 1, "unix.Stat"); - return &(*ust)->st; + return luaL_checkudata(L, 1, "unix.Stat"); } // unix.Stat:size() @@ -1853,28 +1827,12 @@ static int LuaUnixStatFlags(lua_State *L) { return ReturnInteger(L, GetUnixStat(L)->st_flags); } -static void FreeUnixStat(struct UnixStat *stat) { - if (!--stat->refs) { - free(stat); - } -} - static int LuaUnixStatToString(lua_State *L) { struct stat *st = GetUnixStat(L); lua_pushstring(L, "unix.Stat{}"); return 1; } -static int LuaUnixStatGc(lua_State *L) { - struct UnixStat **ust; - ust = luaL_checkudata(L, 1, "unix.Stat"); - if (*ust) { - FreeUnixStat(*ust); - *ust = 0; - } - return 0; -} - static const luaL_Reg kLuaUnixStatMeth[] = { {"atim", LuaUnixStatAtim}, // {"birthtim", LuaUnixStatBirthtim}, // @@ -1897,7 +1855,6 @@ static const luaL_Reg kLuaUnixStatMeth[] = { static const luaL_Reg kLuaUnixStatMeta[] = { {"__tostring", LuaUnixStatToString}, // - {"__gc", LuaUnixStatGc}, // {0}, // }; @@ -1913,10 +1870,8 @@ static void LuaUnixStatObj(lua_State *L) { //////////////////////////////////////////////////////////////////////////////// // unix.Errno object -static dontinline struct UnixErrno *GetUnixErrno(lua_State *L) { - struct UnixErrno **ep; - ep = luaL_checkudata(L, 1, "unix.Errno"); - return *ep; +static struct UnixErrno *GetUnixErrno(lua_State *L) { + return luaL_checkudata(L, 1, "unix.Errno"); } static int LuaUnixErrnoErrno(lua_State *L) { @@ -1948,22 +1903,6 @@ static int LuaUnixErrnoToString(lua_State *L) { return 1; } -static void FreeUnixErrno(struct UnixErrno *errno) { - if (!--errno->refs) { - free(errno); - } -} - -static int LuaUnixErrnoGc(lua_State *L) { - struct UnixErrno **ue; - ue = luaL_checkudata(L, 1, "unix.Errno"); - if (*ue) { - FreeUnixErrno(*ue); - *ue = 0; - } - return 0; -} - static const luaL_Reg kLuaUnixErrnoMeth[] = { {"strerror", LuaUnixErrnoToString}, // {"errno", LuaUnixErrnoErrno}, // @@ -1975,7 +1914,6 @@ static const luaL_Reg kLuaUnixErrnoMeth[] = { static const luaL_Reg kLuaUnixErrnoMeta[] = { {"__tostring", LuaUnixErrnoToString}, // - {"__gc", LuaUnixErrnoGc}, // {0}, // }; diff --git a/tool/net/net.mk b/tool/net/net.mk index 5a0dc7c88..3de3d5ec2 100644 --- a/tool/net/net.mk +++ b/tool/net/net.mk @@ -117,9 +117,10 @@ o/$(MODE)/tool/net/redbean.com: \ @$(COMPILE) -AMKDIR -T$@ mkdir -p o/$(MODE)/tool/net/.redbean @$(COMPILE) -ADD -T$@ dd if=$@ of=o/$(MODE)/tool/net/.redbean/.ape bs=64 count=11 conv=notrunc 2>/dev/null @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com -o o/$(MODE)/tool/net/.redbean/.symtab $< + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \ + o/$(MODE)/tool/net/.redbean/.symtab @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \ o/$(MODE)/tool/net/.redbean/.ape \ - o/$(MODE)/tool/net/.redbean/.symtab \ tool/net/help.txt \ tool/net/.init.lua \ tool/net/favicon.ico \ @@ -262,9 +263,10 @@ o/$(MODE)/tool/net/redbean-demo.com: \ @$(COMPILE) -ADD -T$@ dd if=$@ of=o/$(MODE)/tool/net/.redbean-demo/.ape bs=64 count=11 conv=notrunc 2>/dev/null @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com \ -o o/$(MODE)/tool/net/.redbean-demo/.symtab $< + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \ + o/$(MODE)/tool/net/.redbean-demo/.symtab @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \ o/$(MODE)/tool/net/.redbean-demo/.ape \ - o/$(MODE)/tool/net/.redbean-demo/.symtab \ tool/net/help.txt # REDBEAN-STATIC.COM @@ -284,9 +286,10 @@ o/$(MODE)/tool/net/redbean-static.com: \ @$(COMPILE) -ADD -T$@ dd if=$@ of=o/$(MODE)/tool/net/.redbean-static/.ape bs=64 count=11 conv=notrunc 2>/dev/null @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com \ -o o/$(MODE)/tool/net/.redbean-static/.symtab $< + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \ + o/$(MODE)/tool/net/.redbean-static/.symtab @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \ o/$(MODE)/tool/net/.redbean-static/.ape \ - o/$(MODE)/tool/net/.redbean-static/.symtab \ tool/net/help.txt \ tool/net/favicon.ico \ tool/net/redbean.png @@ -320,9 +323,10 @@ o/$(MODE)/tool/net/redbean-unsecure.com: \ @$(COMPILE) -ADD -T$@ dd if=$@ of=o/$(MODE)/tool/net/.redbean-unsecure/.ape bs=64 count=11 conv=notrunc 2>/dev/null @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com \ -o o/$(MODE)/tool/net/.redbean-unsecure/.symtab $< + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \ + o/$(MODE)/tool/net/.redbean-unsecure/.symtab @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \ o/$(MODE)/tool/net/.redbean-unsecure/.ape \ - o/$(MODE)/tool/net/.redbean-unsecure/.symtab \ tool/net/help.txt \ tool/net/favicon.ico \ tool/net/redbean.png @@ -363,9 +367,10 @@ o/$(MODE)/tool/net/redbean-original.com: \ @$(COMPILE) -ADD -T$@ dd if=$@ of=o/$(MODE)/tool/net/.redbean-original/.ape bs=64 count=11 conv=notrunc 2>/dev/null @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com \ -o o/$(MODE)/tool/net/.redbean-original/.symtab $< + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \ + o/$(MODE)/tool/net/.redbean-original/.symtab @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \ o/$(MODE)/tool/net/.redbean-original/.ape \ - o/$(MODE)/tool/net/.redbean-original/.symtab \ tool/net/help.txt \ tool/net/favicon.ico \ tool/net/redbean.png @@ -436,6 +441,8 @@ o/$(MODE)/tool/net/redbean-assimilate.com: \ @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ @$(COMPILE) -AMKDIR -T$@ mkdir -p o/$(MODE)/tool/net/.redbean-assimilate @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com -o o/$(MODE)/tool/net/.redbean-assimilate/.symtab $< + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \ + o/$(MODE)/tool/net/.redbean-assimilate/.symtab @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \ o/$(MODE)/tool/net/.redbean-assimilate/.symtab \ tool/net/help.txt \ diff --git a/tool/net/redbean.c b/tool/net/redbean.c index dd53ed025..6af6e3eef 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -4120,7 +4120,7 @@ static int LuaLog(lua_State *L) { } static int LuaEncodeSmth(lua_State *L, - int Encoder(lua_State *, char **, int, char *, int)) { + int Encoder(lua_State *, char **, char *, int)) { int useoutput = false; int maxdepth = 64; char *numformat = "%.14g"; @@ -4129,15 +4129,14 @@ static int LuaEncodeSmth(lua_State *L, lua_settop(L, 2); // discard any extra arguments lua_getfield(L, 2, "useoutput"); // ignore useoutput outside of request handling - if (ishandlingrequest && lua_isboolean(L, -1)) + if (ishandlingrequest && lua_isboolean(L, -1)) { useoutput = lua_toboolean(L, -1); - lua_getfield(L, 2, "maxdepth"); - maxdepth = luaL_optinteger(L, -1, maxdepth); + } lua_getfield(L, 2, "numformat"); numformat = luaL_optstring(L, -1, numformat); } lua_settop(L, 1); // keep the passed argument on top - Encoder(L, useoutput ? &outbuf : &p, maxdepth, numformat, -1); + Encoder(L, useoutput ? &outbuf : &p, numformat, -1); if (useoutput) { lua_pushnil(L); } else { @@ -5185,7 +5184,7 @@ static void LuaPrint(lua_State *L) { if (n > 0) { for (i = 1; i <= n; i++) { if (i > 1) appendw(&b, '\t'); - LuaEncodeLuaData(L, &b, 64, "g", i); + LuaEncodeLuaData(L, &b, "g", i); } appendw(&b, '\n'); WRITE(1, b, appendz(b).i); @@ -5222,7 +5221,6 @@ static void LuaInterpreter(lua_State *L) { } for (;;) { status = lua_loadline(L); - write(1, "\n", 1); if (status == -1) break; // eof if (status == -2) { if (errno == EINTR) { @@ -6502,7 +6500,6 @@ static int HandleReadline(void) { if (status == -1) { OnTerm(SIGHUP); // eof INFOF("got repl eof"); - write(1, "\n", 1); return -1; } else if (errno == EINTR) { errno = 0; @@ -6516,7 +6513,6 @@ static int HandleReadline(void) { return -1; } } - write(1, "\n", 1); linenoiseDisableRawMode(); LUA_REPL_LOCK; if (status == LUA_OK) { diff --git a/tool/plinko/plinko.mk b/tool/plinko/plinko.mk index dd96951fb..f841c1c40 100644 --- a/tool/plinko/plinko.mk +++ b/tool/plinko/plinko.mk @@ -55,7 +55,7 @@ o/$(MODE)/tool/plinko/plinko.com: \ @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ @$(COMPILE) -AMKDIR -T$@ mkdir -p o/$(MODE)/tool/plinko/.redbean @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com -o o/$(MODE)/tool/plinko/.plinko/.symtab $< - @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \ + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \ o/$(MODE)/tool/plinko/.plinko/.symtab $(TOOL_PLINKO_OBJS): \ diff --git a/tool/viz/viz.mk b/tool/viz/viz.mk index 7dfba9880..744f644f8 100644 --- a/tool/viz/viz.mk +++ b/tool/viz/viz.mk @@ -88,7 +88,7 @@ o/$(MODE)/tool/viz/printimage.com: \ @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com \ -o o/$(MODE)/tool/viz/.printimage/.symtab $< - @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \ + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \ o/$(MODE)/tool/viz/.printimage/.symtab o/$(MODE)/tool/viz/printvideo.com: \ @@ -98,7 +98,7 @@ o/$(MODE)/tool/viz/printvideo.com: \ @$(COMPILE) -AOBJCOPY -T$@ $(OBJCOPY) -S -O binary $< $@ @$(COMPILE) -ASYMTAB o/$(MODE)/tool/build/symtab.com \ -o o/$(MODE)/tool/viz/.printvideo/.symtab $< - @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -9qj $@ \ + @$(COMPILE) -AZIP -T$@ o/$(MODE)/third_party/zip/zip.com -0qj $@ \ o/$(MODE)/tool/viz/.printvideo/.symtab o/$(MODE)/tool/viz/derasterize.o: \ diff --git a/usr/share/zoneinfo/Anchorage b/usr/share/zoneinfo/Anchorage new file mode 100644 index 0000000000000000000000000000000000000000..cdf0572be31d3052a98494e3d01802b83737f23c GIT binary patch literal 977 zcmb8sUr1AN6bJC%=G>o~)yl0u(`-5CoUZHL{FCdS_s%U>Hqtf9i&gJ(z=X374+=r7m(tA9> zmJJ=BphJ;Z{*weB6Zol8c{EQizNCEaiV~%q8*a=}&bxjjk-I|@6y>Ey$7IUt zojnr$kKKyMlyq0CVEv#6F6a+usBh?Q$x<%tsDzD*8#ejPu<80y*kYZBE$LaAk2B%x z3wXtc9NrhbnUko$GMk21P37R#cV}ekTgTGyn&C-!?U_-yxOW7$b@s#dy-9doFb=Qx z9)vfP_hlH5jh1@Yq3?hlH8v~t9p6H5$tQP;T$-{(jK(41EofxYkE!qxLhxaP@I*!SvMijT3a zw*uFL;|t+iz28v6Jy=`mF6Y zxbPF+{#?NPj@j4n&WyI-U5}^W-8Z!THjj_Pa-X(e_f0T6k0)xKL_^-!gG5_Dg*t6e^sp`NW?k;9f)uE)$jJt^UyO@1%{2b<|ou4Qn8cwiID%yCVwDC%yJ4p7$>coI3`C@**=ocm7FFwuL-T(jq literal 0 HcmV?d00001