diff --git a/build/compile b/build/compile index 2cf182ac5..b97e36f5e 100755 --- a/build/compile +++ b/build/compile @@ -45,6 +45,7 @@ COUNTERMAND= # The GNU Compiler Collection passes a lot of CFLAGS to the preprocessor # (which we call CCFLAGS) but it should pass more; and we do just that. +NOPG=0 FIRST=1 OUTARG=0 INVISIBLE=0 @@ -65,13 +66,16 @@ for x; do -w) set -- "$@" "$x" -D__W__ ;; + -x-no-pg) + NOPG=1 + ;; -pg) - if [ $INVISIBLE -eq 0 ]; then + if [ $NOPG -eq 0 ] && [ $INVISIBLE -eq 0 ]; then set -- "$@" "$x" -D__PG__ # @see libc/macros.h fi ;; -mfentry) - if [ $INVISIBLE -eq 0 ]; then + if [ $NOPG -eq 0 ] && [ $INVISIBLE -eq 0 ]; then set -- "$@" "$x" -D__MFENTRY__ fi ;; diff --git a/build/definitions.mk b/build/definitions.mk index b033c7564..e983c6e2c 100644 --- a/build/definitions.mk +++ b/build/definitions.mk @@ -63,6 +63,7 @@ FC = gfortran #/opt/cross9f/bin/x86_64-linux-musl-gfortran AS = o/third_party/gcc/bin/x86_64-linux-musl-as CC = o/third_party/gcc/bin/x86_64-linux-musl-gcc CXX = o/third_party/gcc/bin/x86_64-linux-musl-g++ +CXXFILT = o/third_party/gcc/bin/x86_64-linux-musl-c++filt LD = o/third_party/gcc/bin/x86_64-linux-musl-ld.bfd AR = o/third_party/gcc/bin/x86_64-linux-musl-ar NM = o/third_party/gcc/bin/x86_64-linux-musl-nm @@ -319,6 +320,7 @@ OBJECTIFY.c2x.c = $(CC) $(OBJECTIFY.c.flags) -std=c2x -Wextra -Werror -pedantic- OBJECTIFY.real.c = \ $(GCC) \ + -x-no-pg \ $(OBJECTIFY.c.flags) \ -wrapper build/realify.sh \ -D__REAL_MODE__ \ @@ -330,7 +332,12 @@ OBJECTIFY.real.c = \ -ffixed-r13 \ -ffixed-r14 \ -ffixed-r15 \ + -mno-red-zone \ -fcall-used-rbx \ + -fno-jump-tables \ + -fno-shrink-wrap \ + -fno-schedule-insns2 \ + -flive-range-shrinkage \ -fno-omit-frame-pointer \ -momit-leaf-frame-pointer \ -mpreferred-stack-boundary=3 \ diff --git a/build/realify.sed b/build/realify.sed index 97893c79a..0bb2dcabf 100644 --- a/build/realify.sed +++ b/build/realify.sed @@ -15,17 +15,6 @@ # remove comments s/[ \t][ \t]*#.*// -# preserve hardcoded stack offsets -# bloats code size 13% compared to recomputing stack solution -s/leave\(q\|\)/leavew\n\tadd\t$6,%sp/ -s/call\(q\|\)\t/sub\t$6,%sp\n\tcallw\t/ -s/ret\(q\|\)/retw\t$6/ -s/pushq\t\(.*\)/sub\t$6,%sp\n\tpush\t\1/ -s/popq\t\(.*\)/pop\t\1\n\tadd\t$6,%sp/ - -# can be used instead if -# 1. functions have 6 args or fewer -# 2. long double parameters forceinline #s/leave\(q\|\)/leavew/ #s/call\(q\|\)/callw/ #s/ret\(q\|\)/retw/ @@ -34,6 +23,14 @@ s/popq\t\(.*\)/pop\t\1\n\tadd\t$6,%sp/ #s/pushq\t\(.*\)/sub $6,%sp\n\tpush \1/ #s/popq\t\(.*\)/pop \1\n\tadd $6,%sp/ +# preserve hardcoded stack offsets +# bloats code size 13% +s/leave\(q\|\)/leavew\n\tadd\t$6,%sp/ +s/call\(q\|\)\t/sub\t$6,%sp\n\tcallw\t/ +s/ret\(q\|\)/retw\t$6/ +s/pushq\t\(.*\)/sub\t$6,%sp\n\tpush\t\1/ +s/popq\t\(.*\)/pop\t\1\n\tadd\t$6,%sp/ + s/, /,/g # 32-bitify diff --git a/build/rules.mk b/build/rules.mk index b3e15a40c..bbaa6eb8e 100644 --- a/build/rules.mk +++ b/build/rules.mk @@ -72,7 +72,7 @@ o/$(MODE)/%.c11.o: %.c11.c; @ACTION=OBJECTIFY.c11 build/compile $(OBJECTIFY.c11. o/$(MODE)/%.c2x.o: %.c2x.c; @ACTION=OBJECTIFY.c2x build/compile $(OBJECTIFY.c2x.c) $(OUTPUT_OPTION) $< o/$(MODE)/%.initabi.o: %.initabi.c; @ACTION=OBJECTIFY.init build/compile $(OBJECTIFY.initabi.c) $(OUTPUT_OPTION) $< o/$(MODE)/%.ncabi.o: %.ncabi.c; @ACTION=OBJECTIFY.nc build/compile $(OBJECTIFY.ncabi.c) $(OUTPUT_OPTION) $< -o/tiny/%.real.o: %.real.c; @ACTION=OBJECTIFY.real build/compile $(OBJECTIFY.real.c) $(OUTPUT_OPTION) $< +o/$(MODE)/%.real.o: %.c; @ACTION=OBJECTIFY.real build/compile $(OBJECTIFY.real.c) $(OUTPUT_OPTION) $< o/$(MODE)/%.runs: o/$(MODE)/%; @ACTION=CHECK.runs TARGET=$< build/runcom $< $(TESTARGS) && touch $@ o/$(MODE)/%.pkg:; @build/package $(OUTPUT_OPTION) $(addprefix -d,$(filter %.pkg,$^)) $(filter %.o,$^) o/$(MODE)/%.zip.o: %; @build/zipobj $(OUTPUT_OPTION) $< diff --git a/examples/examples.mk b/examples/examples.mk index 304cd0a2b..765e25669 100644 --- a/examples/examples.mk +++ b/examples/examples.mk @@ -49,6 +49,7 @@ EXAMPLES_DIRECTDEPS = \ LIBC_CONV \ LIBC_FMT \ LIBC_LOG \ + LIBC_LOG_ASAN \ LIBC_MEM \ LIBC_NEXGEN32E \ LIBC_NT_KERNELBASE \ diff --git a/examples/printargs.c b/examples/printargs.c index 0953aed35..70b992bd8 100644 --- a/examples/printargs.c +++ b/examples/printargs.c @@ -55,7 +55,7 @@ int main(int argc, char *argv[], char **envp) { unsigned long val; char fmt[64], **env; printf("\nArguments:\n"); - for (i = 0; i < argc; ++i) { + for (i = 0; i < g_argc; ++i) { printf(" ☼ %s\n", argv[i]); } printf("\nEnvironment:\n"); diff --git a/libc/alg/memmem.c b/libc/alg/memmem.c index bfa0e7f25..666a5faf3 100644 --- a/libc/alg/memmem.c +++ b/libc/alg/memmem.c @@ -71,27 +71,25 @@ static size_t KnuthMorrisPratt(m, T, W, n, S) * @param needlelen is its character count * @return pointer to first result or NULL if not found */ -void *(memmem)(const void *haystack_, size_t haystacklen, const void *needle_, +void *(memmem)(const void *haystackp, size_t haystacklen, const void *needlep, size_t needlelen) { + long *T; + size_t i; const char *haystack, *needle, *h; - haystack = haystack_; - needle = needle_; + needle = needlep; + haystack = haystackp; if (needlelen > haystacklen) return NULL; - if (!needlelen) return (/*unconst*/ void *)haystack; + if (!needlelen) return haystack; h = memchr(haystack, *needle, haystacklen); - if (!h || needlelen == 1) return (/*unconst*/ void *)h; + if (!h || needlelen == 1) return h; haystacklen -= h - haystack; - long stacktmp[16]; - void *freeme = NULL; - long *T = (needlelen + 1 < ARRAYLEN(stacktmp)) - ? &stacktmp[0] - : (freeme = malloc((needlelen + 1) * sizeof(long))); - KnuthMorrisPrattInit(needlelen, T, needle); - size_t i = KnuthMorrisPratt(needlelen, T, needle, haystacklen, h); - free(freeme); - if (i < haystacklen) { - return (/*unconst*/ char *)h + i * sizeof(char); + if (needlelen < haystacklen && memcmp(h, needle, needlelen) == 0) { + return h; } else { - return NULL; + T = malloc((needlelen + 1) * sizeof(long)); + KnuthMorrisPrattInit(needlelen, T, needle); + i = KnuthMorrisPratt(needlelen, T, needle, haystacklen, h); + free(T); + return i < haystacklen ? h + i * sizeof(char) : NULL; } } diff --git a/libc/calls/atfork.c b/libc/calls/atfork.c index 2c8f87980..df62268d9 100644 --- a/libc/calls/atfork.c +++ b/libc/calls/atfork.c @@ -17,10 +17,7 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/bits/bits.h" -#include "libc/calls/internal.h" #include "libc/macros.h" -#include "libc/runtime/internal.h" #include "libc/runtime/runtime.h" #include "libc/sysv/errfuns.h" diff --git a/libc/calls/clock_gettime.c b/libc/calls/clock_gettime.c index afc4f6a3d..367c8a2ed 100644 --- a/libc/calls/clock_gettime.c +++ b/libc/calls/clock_gettime.c @@ -77,7 +77,7 @@ int clock_gettime(int clockid, struct timespec *out_ts) { } else { struct NtFileTime ft; GetSystemTimeAsFileTime(&ft); - *out_ts = filetimetotimespec(ft); + *out_ts = FileTimeToTimeSpec(ft); return 0; } } diff --git a/libc/calls/fstat-nt.c b/libc/calls/fstat-nt.c index 95d16dd03..b3d4c63c7 100644 --- a/libc/calls/fstat-nt.c +++ b/libc/calls/fstat-nt.c @@ -49,9 +49,9 @@ textwindows int fstat$nt(int64_t handle, struct stat *st) { : (((filetype == kNtFileTypeDisk) ? S_IFBLK : 0) | ((filetype == kNtFileTypeChar) ? S_IFCHR : 0) | ((filetype == kNtFileTypePipe) ? S_IFIFO : 0)))); - st->st_atim = filetimetotimespec(wst.ftLastAccessFileTime); - st->st_mtim = filetimetotimespec(wst.ftLastWriteFileTime); - st->st_ctim = filetimetotimespec(wst.ftCreationFileTime); + st->st_atim = FileTimeToTimeSpec(wst.ftLastAccessFileTime); + st->st_mtim = FileTimeToTimeSpec(wst.ftLastWriteFileTime); + st->st_ctim = FileTimeToTimeSpec(wst.ftCreationFileTime); st->st_size = (uint64_t)wst.nFileSizeHigh << 32 | wst.nFileSizeLow; st->st_blksize = PAGESIZE; st->st_dev = wst.dwVolumeSerialNumber; diff --git a/libc/calls/getpid.c b/libc/calls/getpid.c index 233743559..b4cd4958c 100644 --- a/libc/calls/getpid.c +++ b/libc/calls/getpid.c @@ -28,7 +28,6 @@ static int g_pid; static void __updatepid(void) { - atfork(__updatepid, NULL); g_pid = __getpid(); } @@ -47,8 +46,9 @@ int __getpid(void) { int getpid(void) { static bool once; if (!once) { - once = true; __updatepid(); + atfork(__updatepid, NULL); + once = true; } return g_pid; } diff --git a/libc/calls/getrusage-nt.c b/libc/calls/getrusage-nt.c index 2df0f13f9..794f65f62 100644 --- a/libc/calls/getrusage-nt.c +++ b/libc/calls/getrusage-nt.c @@ -36,8 +36,8 @@ textwindows int getrusage$nt(int who, struct rusage *usage) { if ((who == RUSAGE_SELF ? GetProcessTimes : GetThreadTimes)( (who == RUSAGE_SELF ? GetCurrentProcess : GetCurrentThread)(), &CreationFileTime, &ExitFileTime, &KernelFileTime, &UserFileTime)) { - filetimetotimeval(&usage->ru_utime, UserFileTime); - filetimetotimeval(&usage->ru_stime, KernelFileTime); + FileTimeToTimeVal(&usage->ru_utime, UserFileTime); + FileTimeToTimeVal(&usage->ru_stime, KernelFileTime); return 0; } else { return winerr(); diff --git a/libc/calls/gettimeofday-nt.c b/libc/calls/gettimeofday-nt.c index 03f78700c..87e20a49b 100644 --- a/libc/calls/gettimeofday-nt.c +++ b/libc/calls/gettimeofday-nt.c @@ -29,7 +29,7 @@ int gettimeofday$nt(struct timeval *tv, struct timezone *tz) { struct NtFileTime ft; GetSystemTimeAsFileTime(&ft); - filetimetotimeval(tv, ft); + FileTimeToTimeVal(tv, ft); if (tz) memset(tz, 0, sizeof(*tz)); return 0; } diff --git a/libc/calls/open.c b/libc/calls/open.c index d707ac834..150e93b19 100644 --- a/libc/calls/open.c +++ b/libc/calls/open.c @@ -35,7 +35,7 @@ * @param mode is an octal user/group/other permission signifier, that's * ignored if O_CREAT or O_TMPFILE weren't passed * @return number needing close(), or -1 w/ errno - * @asyncsignalsafe + * @note don't call open() from signal handlers */ nodiscard int open(const char *file, int flags, ...) { va_list va; diff --git a/libc/calls/sigaction.c b/libc/calls/sigaction.c index 0aa2bc505..d389922a4 100644 --- a/libc/calls/sigaction.c +++ b/libc/calls/sigaction.c @@ -155,7 +155,7 @@ int(sigaction)(int sig, const struct sigaction *act, struct sigaction *oldact) { ap->sa_restorer = &__restore_rt; } } - if (rva >= 0) { + if (rva >= kSigactionMinRva) { ap->sa_sigaction = (sigaction_f)__sigenter; } } diff --git a/libc/calls/utimensat-nt.c b/libc/calls/utimensat-nt.c index d0e449bd9..847bb985f 100644 --- a/libc/calls/utimensat-nt.c +++ b/libc/calls/utimensat-nt.c @@ -63,7 +63,7 @@ textwindows int utimensat$nt(int dirfd, const char *path, } else if (ts[i].tv_nsec == UTIME_OMIT) { ftp[i] = NULL; } else { - ft[i] = timespectofiletime(ts[i]); + ft[i] = TimeSpecToFileTime(ts[i]); ftp[i] = &ft[i]; } } diff --git a/libc/calls/wait4-nt.c b/libc/calls/wait4-nt.c index 1a2fc8ee5..cfa94ed19 100644 --- a/libc/calls/wait4-nt.c +++ b/libc/calls/wait4-nt.c @@ -50,8 +50,8 @@ textwindows int wait4$nt(int pid, int *opt_out_wstatus, int options, memset(opt_out_rusage, 0, sizeof(*opt_out_rusage)); GetProcessTimes(GetCurrentProcess(), &createfiletime, &exitfiletime, &kernelfiletime, &userfiletime); - filetimetotimeval(&opt_out_rusage->ru_utime, userfiletime); - filetimetotimeval(&opt_out_rusage->ru_stime, kernelfiletime); + FileTimeToTimeVal(&opt_out_rusage->ru_utime, userfiletime); + FileTimeToTimeVal(&opt_out_rusage->ru_stime, kernelfiletime); } return pid; } else if (options & WNOHANG) { diff --git a/libc/conv/conv.h b/libc/conv/conv.h index 713ae0b20..4ac49914c 100644 --- a/libc/conv/conv.h +++ b/libc/conv/conv.h @@ -15,8 +15,8 @@ * of the UNIX operation system signalled the end of modernity. Windows * timestamps are living proof. */ -#define MODERNITYSECONDS 11644473600 -#define HECTONANOSECONDS 10000000 +#define MODERNITYSECONDS 11644473600ull +#define HECTONANOSECONDS 10000000ull #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ @@ -46,12 +46,13 @@ long strtol(const char *, char **, int) │ cosmopolitan § conversion » time ─╬─│┼ ╚────────────────────────────────────────────────────────────────────────────│*/ -struct timespec filetimetotimespec(struct NtFileTime); -struct NtFileTime timespectofiletime(struct timespec); -struct NtFileTime timetofiletime(int64_t) nothrow pureconst; +int64_t DosDateTimeToUnix(unsigned, unsigned); +struct timespec FileTimeToTimeSpec(struct NtFileTime); +struct NtFileTime TimeSpecToFileTime(struct timespec); +struct NtFileTime TimeToFileTime(int64_t) nothrow pureconst; int64_t filetimetotime(struct NtFileTime) nothrow pureconst; -void filetimetotimeval(struct timeval *, struct NtFileTime) nothrow; -struct NtFileTime timevaltofiletime(const struct timeval *) nosideeffect; +void FileTimeToTimeVal(struct timeval *, struct NtFileTime) nothrow; +struct NtFileTime TimeValToFileTime(const struct timeval *) nosideeffect; long convertmicros(const struct timeval *, long) paramsnonnull() nosideeffect; /*───────────────────────────────────────────────────────────────────────────│─╗ diff --git a/libc/conv/conv.mk b/libc/conv/conv.mk index 69ca38f82..062cf03c3 100644 --- a/libc/conv/conv.mk +++ b/libc/conv/conv.mk @@ -52,6 +52,7 @@ $(LIBC_CONV_A).pkg: \ $(LIBC_CONV_A_OBJS) \ $(foreach x,$(LIBC_CONV_A_DIRECTDEPS),$($(x)_A).pkg) +o/$(MODE)/libc/conv/dosdatetimetounix.o \ o/$(MODE)/libc/conv/itoa64radix10.greg.o \ o/$(MODE)/libc/conv/timetofiletime.o \ o/$(MODE)/libc/conv/filetimetotime.o \ diff --git a/libc/conv/dosdatetimetounix.c b/libc/conv/dosdatetimetounix.c new file mode 100644 index 000000000..5415e76e0 --- /dev/null +++ b/libc/conv/dosdatetimetounix.c @@ -0,0 +1,43 @@ +/*-*- 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 │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/conv/conv.h" +#include "libc/macros.h" +#include "libc/time/time.h" + +/** + * Converts MS-DOS timestamp to UNIX. + * + * @note type signature supports dates greater than 2100 + * @see PKZIP, FAT + */ +int64_t DosDateTimeToUnix(unsigned date, unsigned time) { + unsigned weekday, year, month, day, hour, minute, second, yday, leap; + year = ((date & 0xfffffe00) >> 9) + 1980 - 1900; + month = MAX(1, MIN(12, (date & 0x01e0) >> 5)); + day = (date & 0x001f) ? (date & 0x001f) - 1 : 0; + hour = (time & 0x0000f800) >> 11; + minute = (time & 0x000007e0) >> 5; + second = (time & 0x0000001f) << 1; + leap = year % 4 == 0 && (year % 100 || year % 400 == 0); + yday = day + kMonthYearDay[leap][month - 1]; + return second + minute * 60 + hour * 3600 + yday * 86400 + + (year - 70) * 31536000ull + ((year - 69) / 4) * 86400ull - + ((year - 1) / 100) * 86400ull + ((year + 299) / 400) * 86400ull; +} diff --git a/libc/conv/filetimetotimespec.c b/libc/conv/filetimetotimespec.c index f1f79689f..e218d4f87 100644 --- a/libc/conv/filetimetotimespec.c +++ b/libc/conv/filetimetotimespec.c @@ -24,11 +24,11 @@ /** * Converts Windows COBOL timestamp to UNIX epoch in nanoseconds. */ -struct timespec filetimetotimespec(struct NtFileTime ft) { +struct timespec FileTimeToTimeSpec(struct NtFileTime ft) { uint64_t x; x = ft.dwHighDateTime; x <<= 32; x |= ft.dwLowDateTime; - x -= MODERNITYSECONDS; - return (struct timespec){x / HECTONANOSECONDS, x % HECTONANOSECONDS * 100}; + return (struct timespec){x / HECTONANOSECONDS - MODERNITYSECONDS, + x % HECTONANOSECONDS * 100}; } diff --git a/libc/conv/filetimetotimeval.c b/libc/conv/filetimetotimeval.c index d489ab0e4..64596ec67 100644 --- a/libc/conv/filetimetotimeval.c +++ b/libc/conv/filetimetotimeval.c @@ -22,9 +22,11 @@ #include "libc/conv/conv.h" #include "libc/nt/struct/filetime.h" -void filetimetotimeval(struct timeval *tv, struct NtFileTime ft) { - uint64_t t = (uint64_t)ft.dwHighDateTime << 32 | ft.dwLowDateTime; - uint64_t x = t - MODERNITYSECONDS * HECTONANOSECONDS; - tv->tv_sec = x / HECTONANOSECONDS; +void FileTimeToTimeVal(struct timeval *tv, struct NtFileTime ft) { + uint64_t x; + x = ft.dwHighDateTime; + x <<= 32; + x |= ft.dwLowDateTime; + tv->tv_sec = x / HECTONANOSECONDS - MODERNITYSECONDS; tv->tv_usec = x % HECTONANOSECONDS / 10; } diff --git a/libc/conv/kmonthyearday.c b/libc/conv/kmonthyearday.c new file mode 100644 index 000000000..79b13b34f --- /dev/null +++ b/libc/conv/kmonthyearday.c @@ -0,0 +1,25 @@ +/*-*- 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 │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/time/time.h" + +const unsigned short kMonthYearDay[2][12] = { + {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334}, + {0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335}, +}; diff --git a/libc/conv/timespectofiletime.c b/libc/conv/timespectofiletime.c index 4d3dc8f5a..1e2c3a8dd 100644 --- a/libc/conv/timespectofiletime.c +++ b/libc/conv/timespectofiletime.c @@ -25,7 +25,7 @@ /** * Converts UNIX nanosecond timestamp to Windows COBOL timestamp. */ -struct NtFileTime timespectofiletime(struct timespec ts) { +struct NtFileTime TimeSpecToFileTime(struct timespec ts) { uint64_t x; x = MODERNITYSECONDS; x += ts.tv_sec * HECTONANOSECONDS; diff --git a/libc/conv/timetofiletime.c b/libc/conv/timetofiletime.c index a7266240e..a45e1a158 100644 --- a/libc/conv/timetofiletime.c +++ b/libc/conv/timetofiletime.c @@ -20,7 +20,7 @@ #include "libc/conv/conv.h" #include "libc/nt/struct/filetime.h" -struct NtFileTime timetofiletime(int64_t t) { +struct NtFileTime TimeToFileTime(int64_t t) { uint64_t t2 = (t + MODERNITYSECONDS) * HECTONANOSECONDS; return (struct NtFileTime){(uint32_t)t2, (uint32_t)(t2 >> 32)}; } diff --git a/libc/conv/timevaltofiletime.c b/libc/conv/timevaltofiletime.c index eeb55f2dd..2155d0300 100644 --- a/libc/conv/timevaltofiletime.c +++ b/libc/conv/timevaltofiletime.c @@ -22,7 +22,7 @@ #include "libc/nt/struct/filetime.h" #include "libc/time/time.h" -struct NtFileTime timevaltofiletime(const struct timeval *tv) { +struct NtFileTime TimeValToFileTime(const struct timeval *tv) { uint64_t t2 = tv->tv_sec * HECTONANOSECONDS + tv->tv_usec * 10 + MODERNITYSECONDS * HECTONANOSECONDS; return (struct NtFileTime){(uint32_t)t2, (uint32_t)(t2 >> 32)}; diff --git a/libc/dos.h b/libc/dos.h index a74e49826..d9a1a8265 100644 --- a/libc/dos.h +++ b/libc/dos.h @@ -1,5 +1,6 @@ #ifndef COSMOPOLITAN_LIBC_DOS_H_ #define COSMOPOLITAN_LIBC_DOS_H_ +#include "libc/macros.h" /** * @fileoverview MS-DOS Data Structures. diff --git a/libc/fmt/fcvt.c b/libc/fmt/fcvt.c new file mode 100644 index 000000000..65086221f --- /dev/null +++ b/libc/fmt/fcvt.c @@ -0,0 +1,128 @@ +/*-*- 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 2009 Ian Piumarta │ +│ │ +│ Permission is hereby granted, free of charge, to any person obtaining a copy │ +│ of this software and associated documentation files (the 'Software'), to │ +│ deal in the Software without restriction, including without limitation the │ +│ rights to use, copy, modify, merge, publish, distribute, and/or sell copies │ +│ of the Software, and to permit persons to whom the Software is furnished to │ +│ do so, provided that the above copyright notice(s) and this permission │ +│ notice appear in all copies of the Software. Inclusion of the above │ +│ copyright notice(s) and this permission notice in supporting documentation │ +│ would be appreciated but is not required. │ +│ │ +│ THE SOFTWARE IS PROVIDED 'AS IS'. USE ENTIRELY AT YOUR OWN RISK. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" +#include "libc/fmt/fmt.h" +#include "libc/macros.h" +#include "libc/math.h" +#include "libc/runtime/runtime.h" +#include "libc/str/str.h" + +asm(".ident\t\"\\n\\n\ +ecvt, fcvt (MIT License)\\n\ +Copyright 2009 Ian Piumarta\""); + +/** + * @fileoverview Replacements for the functions ecvt() and fcvt() + * + * These functions were recently deprecated in POSIX. The interface and + * behaviour is identical to the functions that they replace and faster. + * + * For details on the use of these functions, see your ecvt(3) manual + * page. If you don't have one handy, there might still be one available + * here: http://opengroup.org/onlinepubs/007908799/xsh/ecvt.html + * + * @see https://www.piumarta.com/software/fcvt/ + */ + +static char *Fcvt(double value, int ndigit, int *decpt, int *sign, int fflag) { + static char buf[128]; + double i; + uint64_t l, mant; + int exp2, exp10, ptr; + memcpy(&l, &value, 8); + exp2 = (0x7ff & (l >> 52)) - 1023; + mant = l & 0x000fffffffffffffULL; + if ((*sign = l >> 63)) value = -value; + if (exp2 == 0x400) { + *decpt = 0; + return mant ? "nan" : "inf"; + } + exp10 = (value == 0) ? !fflag : (int)ceil(log10(value)); + if (exp10 < -307) exp10 = -307; /* otherwise overflow in pow() */ + value *= pow(10.0, -exp10); + if (value) { + while (value < 0.1) { + value *= 10; + --exp10; + } + while (value >= 1.0) { + value /= 10; + ++exp10; + } + } + assert(value == 0 || (0.1 <= value && value < 1.0)); + if (fflag) { + if (ndigit + exp10 < 0) { + *decpt = -ndigit; + return ""; + } + ndigit += exp10; + } + *decpt = exp10; + if (ARRAYLEN(buf) < ndigit + 2) abort(); + ptr = 1; +#if 0 /* slow and safe (and dreadfully boring) */ + while (ptr <= ndigit) { + i; + value = modf(value * 10, &i); + buf[ptr++] = '0' + (int)i; + } + if (value >= 0.5) { + while (--ptr && ++buf[ptr] > '9') { + buf[ptr] = '0'; + } + } +#else /* faster */ + memcpy(&l, &value, 8); + exp2 = (0x7ff & (l >> 52)) - 1023; + assert(value == 0 || (-4 <= exp2 && exp2 <= -1)); + mant = l & 0x000fffffffffffffULL; + if (exp2 == -1023) { + ++exp2; + } else { + mant |= 0x0010000000000000ULL; + } + mant <<= (exp2 + 4); /* 56-bit denormalised signifier */ + while (ptr <= ndigit) { + mant &= 0x00ffffffffffffffULL; /* mod 1.0 */ + mant = (mant << 1) + (mant << 3); + buf[ptr++] = '0' + (mant >> 56); + } + if (mant & 0x0080000000000000ULL) /* 1/2 << 56 */ + while (--ptr && ++buf[ptr] > '9') buf[ptr] = '0'; +#endif + if (ptr) { + buf[ndigit + 1] = 0; + return buf + 1; + } + if (fflag) { + ++ndigit; + ++*decpt; + } + buf[0] = '1'; + buf[ndigit] = 0; + return buf; +} + +char *ecvt(double value, int ndigit, int *decpt, int *sign) { + return Fcvt(value, ndigit, decpt, sign, 0); +} + +char *fcvt(double value, int ndigit, int *decpt, int *sign) { + return Fcvt(value, ndigit, decpt, sign, 1); +} diff --git a/libc/fmt/fmt.h b/libc/fmt/fmt.h index 2f10324e8..57bc139f4 100644 --- a/libc/fmt/fmt.h +++ b/libc/fmt/fmt.h @@ -29,6 +29,8 @@ char *strerror(int) returnsnonnull nothrow nocallback; int strerror_r(int, char *, size_t) nothrow nocallback; int palandprintf(void *, void *, const char *, va_list) hidden; char *itoa(int, char *, int) compatfn; +char *fcvt(double, int, int *, int *); +char *ecvt(double, int, int *, int *); /*───────────────────────────────────────────────────────────────────────────│─╗ │ cosmopolitan § string formatting » optimizations ─╬─│┼ diff --git a/libc/fmt/palandftoa.c b/libc/fmt/palandftoa.c index 8b11bc6ef..fa3bbff91 100644 --- a/libc/fmt/palandftoa.c +++ b/libc/fmt/palandftoa.c @@ -40,7 +40,7 @@ * @see xdtoa() for higher precision at the cost of bloat * @see palandprintf() which is intended caller */ -int ftoa(int out(int, void *), void *arg, long double value, int prec, +int ftoa(int out(long, void *), void *arg, long double value, int prec, unsigned long width, unsigned long flags) { long whole, frac; long double tmp, diff; diff --git a/libc/fmt/palandntoa.c b/libc/fmt/palandntoa.c index 093a6ca4e..2b06158d0 100644 --- a/libc/fmt/palandntoa.c +++ b/libc/fmt/palandntoa.c @@ -31,7 +31,7 @@ uintmax_t __udivmodti4(uintmax_t, uintmax_t, uintmax_t *); -static int ntoaformat(int out(int, void *), void *arg, char *buf, unsigned len, +static int ntoaformat(int out(long, void *), void *arg, char *buf, unsigned len, bool negative, unsigned log2base, unsigned prec, unsigned width, unsigned char flags) { unsigned i, idx; @@ -103,7 +103,7 @@ static int ntoaformat(int out(int, void *), void *arg, char *buf, unsigned len, return 0; } -int ntoa2(int out(int, void *), void *arg, uintmax_t value, bool neg, +int ntoa2(int out(long, void *), void *arg, uintmax_t value, bool neg, unsigned log2base, unsigned prec, unsigned width, unsigned flags, const char *alphabet) { uintmax_t remainder; @@ -135,7 +135,7 @@ int ntoa2(int out(int, void *), void *arg, uintmax_t value, bool neg, return ntoaformat(out, arg, buf, len, neg, log2base, prec, width, flags); } -int ntoa(int out(int, void *), void *arg, va_list va, unsigned char signbit, +int ntoa(int out(long, void *), void *arg, va_list va, unsigned char signbit, unsigned long log2base, unsigned long prec, unsigned long width, unsigned char flags, const char *lang) { bool neg; diff --git a/libc/fmt/palandprintf.c b/libc/fmt/palandprintf.c index ab1d57369..47e09e1ae 100644 --- a/libc/fmt/palandprintf.c +++ b/libc/fmt/palandprintf.c @@ -75,7 +75,7 @@ static int ppatoi(const char **str) { * - `%Lf` long double * - `%p` pointer (48-bit hexadecimal) * - * Length Modifiers + * Size Modifiers * * - `%hhd` char (8-bit) * - `%hd` short (16-bit) @@ -89,7 +89,11 @@ static int ppatoi(const char **str) { * - `%08d` fixed columns w/ zero leftpadding * - `%8d` fixed columns w/ space leftpadding * - `%*s` variable column string (thompson-pike) - * - `%.*s` variable column data (ignore nul terminator) + * + * Precision Modifiers + * + * - `%.8s` supplied character length (ignore nul terminator) + * - `%.*s` supplied character length argument (ignore nul terminator) * * Formatting Modifiers * @@ -112,9 +116,9 @@ hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) { long double ldbl; wchar_t charbuf[3]; const char *alphabet; - int (*out)(int, void *); + int (*out)(long, void *); unsigned char signbit, log2base; - int w, rc, flags, width, lasterr, precision; + int w, flags, width, lasterr, precision; lasterr = errno; out = fn ? fn : (int (*)(int, void *))missingno; @@ -255,7 +259,8 @@ hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) { case 'u': { flags &= ~FLAGS_HASH; /* no hash for dec format */ DoNumber: - if (weaken(ntoa)(out, arg, va, signbit, log2base, precision, width, + if (!weaken(ntoa) || + weaken(ntoa)(out, arg, va, signbit, log2base, precision, width, flags, alphabet) == -1) { return -1; } @@ -269,7 +274,8 @@ hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) { } else { ldbl = va_arg(va, double); } - if (weaken(ftoa)(out, arg, ldbl, precision, width, flags) == -1) { + if (!weaken(ftoa) || + weaken(ftoa)(out, arg, ldbl, precision, width, flags) == -1) { return -1; } break; @@ -297,8 +303,10 @@ hidden int palandprintf(void *fn, void *arg, const char *format, va_list va) { case 's': p = va_arg(va, void *); showstr: - rc = weaken(stoa)(out, arg, p, flags, precision, width, signbit, qchar); - if (rc == -1) return -1; + if (!weaken(stoa) || weaken(stoa)(out, arg, p, flags, precision, width, + signbit, qchar) == -1) { + return -1; + } break; case '%': diff --git a/libc/fmt/palandprintf.h b/libc/fmt/palandprintf.h index c9a654597..44b47f0ba 100644 --- a/libc/fmt/palandprintf.h +++ b/libc/fmt/palandprintf.h @@ -7,12 +7,12 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -int spacepad(int(int, void *), void *, unsigned long) hidden; -int ftoa(int(int, void *), void *, long double, int, unsigned long, +int spacepad(int(long, void *), void *, unsigned long) hidden; +int ftoa(int(long, void *), void *, long double, int, unsigned long, unsigned long) hidden; -int stoa(int(int, void *), void *, void *, unsigned long, unsigned long, +int stoa(int(long, void *), void *, void *, unsigned long, unsigned long, unsigned long, unsigned char, unsigned char) hidden; -int ntoa(int(int, void *), void *, va_list, unsigned char, unsigned long, +int ntoa(int(long, void *), void *, va_list, unsigned char, unsigned long, unsigned long, unsigned long, unsigned char, const char *) hidden; COSMOPOLITAN_C_END_ diff --git a/libc/fmt/spacepad.c b/libc/fmt/spacepad.c index 5af66e51e..9121e3820 100644 --- a/libc/fmt/spacepad.c +++ b/libc/fmt/spacepad.c @@ -19,7 +19,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/fmt/palandprintf.h" -int spacepad(int out(int, void *), void *arg, unsigned long n) { +int spacepad(int out(long, void *), void *arg, unsigned long n) { int i, rc; for (rc = i = 0; i < n; ++i) rc |= out(' ', arg); return rc; diff --git a/libc/fmt/stoa.c b/libc/fmt/stoa.c index 7b99132ce..3de67697a 100644 --- a/libc/fmt/stoa.c +++ b/libc/fmt/stoa.c @@ -17,47 +17,54 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/bits/bits.h" -#include "libc/bits/safemacros.h" #include "libc/bits/weaken.h" #include "libc/escape/escape.h" -#include "libc/fmt/fmt.h" #include "libc/fmt/paland.inc" #include "libc/fmt/palandprintf.h" #include "libc/nexgen32e/tinystrlen.h" -#include "libc/str/internal.h" #include "libc/str/str.h" -#include "libc/str/tpdecode.h" -#include "libc/str/tpencode.h" +#include "libc/str/thompike.h" +#include "libc/str/tpenc.h" +#include "libc/str/utf16.h" #include "libc/unicode/unicode.h" -forceinline unsigned long tpiencode(wint_t wc) { - char buf[8]; - memset(buf, 0, sizeof(buf)); - tpencode(buf, sizeof(buf), wc, false); - return read64le(buf); +typedef int (*emit_f)(int (*)(long, void *), void *, wint_t); + +static int StoaEmitByte(int f(long, void *), void *a, wint_t c) { + return f(c, a); } -forceinline int emitwc(int out(int, void *), void *arg, unsigned flags, - wint_t wc) { - unsigned long pending; - if (flags & FLAGS_QUOTE) { - if (wc > 127) { - pending = tpiencode(wc); - } else { - pending = cescapec(wc); - } - } else { - pending = tpiencode(wc); - } +static int StoaEmitWordEncodedString(int f(long, void *), void *a, uint64_t w) { do { - if (out(pending & 0xff, arg) == -1) return -1; - } while ((pending >>= 8)); + if (f(w & 0xff, a) == -1) { + return -1; + } + } while ((w >>= 8)); return 0; } -forceinline int emitquote(int out(int, void *), void *arg, unsigned flags, - char ch, unsigned char signbit) { +static int StoaEmitUnicode(int f(long, void *), void *a, wint_t c) { + if (0 <= c && c <= 127) { + return f(c, a); + } else { + return StoaEmitWordEncodedString(f, a, tpenc(c)); + } +} + +static int StoaEmitQuoted(int f(long, void *), void *a, wint_t c) { + if (0 <= c && c <= 127) { + return StoaEmitWordEncodedString(f, a, cescapec(c)); + } else { + return StoaEmitWordEncodedString(f, a, tpenc(c)); + } +} + +static int StoaEmitVisualized(int f(long, void *), void *a, wint_t c) { + return StoaEmitUnicode(f, a, (*weaken(kCp437))[c]); +} + +static int StoaEmitQuote(int out(long, void *), void *arg, unsigned flags, + char ch, unsigned char signbit) { if (flags & FLAGS_REPR) { if (signbit == 63) { if (out('L', arg) == -1) return -1; @@ -78,13 +85,15 @@ forceinline int emitquote(int out(int, void *), void *arg, unsigned flags, * * @see palandprintf() */ -int stoa(int out(int, void *), void *arg, void *data, unsigned long flags, +int stoa(int out(long, void *), void *arg, void *data, unsigned long flags, unsigned long precision, unsigned long width, unsigned char signbit, unsigned char qchar) { char *p; wint_t wc; - unsigned w, c; - bool ignorenul; + unsigned n; + emit_f emit; + bool justdobytes; + unsigned w, c, pad; p = data; if (!p) { @@ -93,89 +102,102 @@ int stoa(int out(int, void *), void *arg, void *data, unsigned long flags, flags |= FLAGS_NOQUOTE; signbit = 0; } else { - if (emitquote(out, arg, flags, qchar, signbit) == -1) return -1; + if (StoaEmitQuote(out, arg, flags, qchar, signbit) == -1) return -1; } - w = precision ? precision : -1; + if (!(flags & FLAGS_PRECISION)) { + if (signbit == 63) { + precision = tinywcsnlen((const wchar_t *)p, -1); + } else if (signbit == 15) { + precision = tinystrnlen16((const char16_t *)p, -1); + } else { + precision = strlen(p); + } + } + + pad = 0; if (width) { + w = precision; if (signbit == 63) { if (weaken(wcsnwidth)) { - w = weaken(wcsnwidth)((const wchar_t *)p, w); - } else { - w = tinywcsnlen((const wchar_t *)p, w); + w = weaken(wcsnwidth)((const wchar_t *)p, precision); } } else if (signbit == 15) { if (weaken(strnwidth16)) { - w = weaken(strnwidth16)((const char16_t *)p, w); - } else { - w = tinystrnlen16((const char16_t *)p, w); + w = weaken(strnwidth16)((const char16_t *)p, precision); } } else if (weaken(strnwidth)) { - w = weaken(strnwidth)(p, w); - } else { - w = strnlen(p, w); + w = weaken(strnwidth)(p, precision); + } + if (w < width) { + pad = width - w; } } - if (flags & FLAGS_PRECISION) { - w = MIN(w, precision); + if (pad && !(flags & FLAGS_LEFT)) { + if (spacepad(out, arg, pad) == -1) return -1; } - if (w < width && !(flags & FLAGS_LEFT)) { - if (spacepad(out, arg, width - w) == -1) return -1; + justdobytes = false; + if (signbit == 15 || signbit == 63) { + if (flags & FLAGS_QUOTE) { + emit = StoaEmitQuoted; + } else { + emit = StoaEmitUnicode; + } + } else if ((flags & FLAGS_HASH) && weaken(kCp437)) { + justdobytes = true; + emit = StoaEmitVisualized; + } else if (flags & FLAGS_QUOTE) { + emit = StoaEmitQuoted; + } else { + justdobytes = true; + emit = StoaEmitByte; } - ignorenul = (flags & FLAGS_PRECISION) && (flags & (FLAGS_HASH | FLAGS_QUOTE)); - for (; !(flags & FLAGS_PRECISION) || precision; --precision) { - if (signbit == 15) { - if ((wc = *(const char16_t *)p) || ignorenul) { - if ((1 <= wc && wc <= 0xD7FF)) { + if (justdobytes) { + while (precision--) { + wc = *p++ & 0xff; + if (emit(out, arg, wc) == -1) return -1; + } + } else { + while (precision--) { + if (signbit == 15) { + wc = *(const char16_t *)p; + if (IsUcs2(wc)) { p += sizeof(char16_t); - } else if ((wc & UTF16_MASK) == UTF16_CONT) { + } else if (IsUtf16Cont(wc)) { p += sizeof(char16_t); continue; + } else if (!precision) { + break; } else { - char16_t buf[4] = {wc}; - if (!(flags & FLAGS_PRECISION) || precision > 1) { - buf[1] = ((const char16_t *)p)[1]; + --precision; + wc = MergeUtf16(wc, *(const char16_t *)p); + } + } else if (signbit == 63) { + wc = *(const wint_t *)p; + p += sizeof(wint_t); + if (!wc) break; + } else { + wc = *p++ & 0xff; + if (!isascii(wc)) { + if (ThomPikeCont(wc)) continue; + n = ThomPikeLen(wc) - 1; + wc = ThomPikeByte(wc); + if (n > precision) break; + precision -= n; + while (n--) { + wc = ThomPikeMerge(wc, *p++); } - p += max(1, getutf16((const char16_t *)p, &wc)) * sizeof(char16_t); } - } else { - break; - } - } else if (signbit == 63) { - wc = *(const wint_t *)p; - p += sizeof(wint_t); - if (!wc) break; - } else if (flags & FLAGS_HASH) { - c = *p & 0xff; - if (!c && !ignorenul) break; - wc = (*weaken(kCp437))[c]; - p++; - } else { - if ((wc = *p & 0xff) || ignorenul) { - if (1 <= wc && wc <= 0x7f) { - ++p; - } else if (iscont(wc & 0xff)) { - ++p; - continue; - } else { - char buf[8]; - memset(buf, 0, sizeof(buf)); - memcpy(buf, p, - !(flags & FLAGS_PRECISION) ? 7 : MIN(7, precision - 1)); - p += max(1, tpdecode(p, &wc)); - } - } else { - break; } + if (emit(out, arg, wc) == -1) return -1; } - if (emitwc(out, arg, flags, wc) == -1) return -1; } - if (w <= width && (flags & FLAGS_LEFT)) { - if (spacepad(out, arg, width - w) == -1) return -1; + if (pad && (flags & FLAGS_LEFT)) { + if (spacepad(out, arg, pad) == -1) return -1; } if (!(flags & FLAGS_NOQUOTE) && (flags & FLAGS_REPR)) { diff --git a/libc/fmt/vsnprintf.c b/libc/fmt/vsnprintf.c index f557b6593..8f29f0833 100644 --- a/libc/fmt/vsnprintf.c +++ b/libc/fmt/vsnprintf.c @@ -25,17 +25,13 @@ struct SprintfStr { char *p; - size_t i, n; + size_t i; + size_t n; }; static int vsnprintfputchar(unsigned char c, struct SprintfStr *str) { - if (str->i < str->n) { - if (str->p) str->p[str->i] = c; - str->i++; - } else { - if (!IsTrustworthy() && str->i >= INT_MAX) abort(); - str->i++; - } + if (str->i < str->n) str->p[str->i] = c; + str->i++; return 0; } @@ -48,15 +44,11 @@ static int vsnprintfputchar(unsigned char c, struct SprintfStr *str) { * @return number of bytes written, excluding the NUL terminator; or, * if the output buffer wasn't passed, or was too short, then the * number of characters that *would* have been written is returned - * @throw EOVERFLOW when a formatted field exceeds its limit, which can - * be checked by setting errno to 0 before calling * @see palandprintf() and printf() for detailed documentation */ int(vsnprintf)(char *buf, size_t size, const char *fmt, va_list va) { struct SprintfStr str = {buf, 0, size}; palandprintf(vsnprintfputchar, &str, fmt, va); - if (str.p && str.n) { - str.p[min(str.i, str.n - 1)] = '\0'; - } + if (str.n) str.p[min(str.i, str.n - 1)] = '\0'; return str.i; } diff --git a/libc/integral/normalize.inc b/libc/integral/normalize.inc index 76b634d4c..7df7ba0e5 100644 --- a/libc/integral/normalize.inc +++ b/libc/integral/normalize.inc @@ -66,7 +66,7 @@ #endif #define BIGPAGESIZE 0x200000 -#define STACKSIZE 0x80000 /* todo: zlib's fault? */ +#define STACKSIZE 0x10000 #define FRAMESIZE 0x10000 /* 8086 */ #define PAGESIZE 0x1000 /* i386+ */ #define BUFSIZ 0x1000 /* best stdio default */ diff --git a/libc/linux/linux.mk b/libc/linux/linux.mk index c4872cee1..21cc812d2 100644 --- a/libc/linux/linux.mk +++ b/libc/linux/linux.mk @@ -3,7 +3,7 @@ PKGS += LIBC_LINUX -LIBC_LINUX_HDRS = $(filter %.h,$(LIBC_FILES)) +LIBC_LINUX_HDRS = $(filter %.h,$(LIBC_LINUX_FILES)) LIBC_LINUX_FILES := $(wildcard libc/linux/*) LIBC_LINUX_CHECKS = $(LIBC_LINUX_HDRS:%=o/$(MODE)/%.ok) diff --git a/libc/log/asan.c b/libc/log/asan.c index 9ac455532..7b923d626 100644 --- a/libc/log/asan.c +++ b/libc/log/asan.c @@ -41,6 +41,8 @@ #include "libc/sysv/consts/prot.h" #include "third_party/dlmalloc/dlmalloc.h" +STATIC_YOINK("_init_asan"); + /** * @fileoverview Cosmopolitan Address Sanitizer Runtime. * diff --git a/libc/log/checkfail.c b/libc/log/checkfail.c index c0fcbdb12..5ee422c6c 100644 --- a/libc/log/checkfail.c +++ b/libc/log/checkfail.c @@ -31,6 +31,10 @@ #include "libc/str/str.h" #include "libc/sysv/consts/auxv.h" +STATIC_YOINK("ntoa"); +STATIC_YOINK("stoa"); +STATIC_YOINK("ftoa"); + /** * Handles failure of CHECK_xx() macros. */ @@ -47,30 +51,30 @@ relegated void __check_fail(const char *suffix, const char *opstr, if (!memccpy(sufbuf, suffix, '\0', sizeof(sufbuf))) strcpy(sufbuf, "?"); strtoupper(sufbuf); - fprintf(stderr, - "check failed\n" - "\tCHECK_%s(%s, %s);\n" - "\t\t → %#lx (%s)\n" - "\t\t%s %#lx (%s)\n", - sufbuf, wantstr, gotstr, want, wantstr, opstr, got, gotstr); + (fprintf)(stderr, + "check failed\n" + "\tCHECK_%s(%s, %s);\n" + "\t\t → %#lx (%s)\n" + "\t\t%s %#lx (%s)\n", + sufbuf, wantstr, gotstr, want, wantstr, opstr, got, gotstr); if (!isempty(fmt)) { fputc('\t', stderr); va_start(va, fmt); - vfprintf(stderr, fmt, va); + (vfprintf)(stderr, fmt, va); va_end(va); fputc('\n', stderr); } - fprintf(stderr, "\t%s\n\t%s%s%s%s\n", strerror(lasterr), SUBTLE, - getauxval(AT_EXECFN), g_argc > 1 ? " \\" : "", RESET); + (fprintf)(stderr, "\t%s\n\t%s%s%s%s\n", strerror(lasterr), SUBTLE, + getauxval(AT_EXECFN), g_argc > 1 ? " \\" : "", RESET); for (i = 1; i < g_argc; ++i) { - fprintf(stderr, "\t\t%s%s\n", g_argv[i], i < g_argc - 1 ? " \\" : ""); + (fprintf)(stderr, "\t\t%s%s\n", g_argv[i], i < g_argc - 1 ? " \\" : ""); } if (!IsTiny() && lasterr == ENOMEM) { - fprintf(stderr, "\n"); + (fprintf)(stderr, "\n"); fflush(stderr); PrintMemoryIntervals(fileno(stderr), &_mmi); } diff --git a/libc/log/log.h b/libc/log/log.h index 5b943a50f..d2b14d3aa 100644 --- a/libc/log/log.h +++ b/libc/log/log.h @@ -15,7 +15,7 @@ * Log level for compile-time DCE. */ #ifndef LOGGABLELEVEL -#ifndef NDEBUG +#ifndef TINY #define LOGGABLELEVEL kLogDebug /* #elif IsTiny() */ /* #define LOGGABLELEVEL kLogInfo */ diff --git a/libc/log/log.mk b/libc/log/log.mk index efbb144d9..eec740781 100644 --- a/libc/log/log.mk +++ b/libc/log/log.mk @@ -65,6 +65,11 @@ $(LIBC_LOG_A_OBJS): \ $(NO_MAGIC) \ -fwrapv +ifeq (,$(MODE)) +LIBC_LOG_ASAN = o/$(MODE)/libc/log/asan.o +endif +LIBC_LOG_ASAN_A = o/$(MODE)/libc/log/log.a + LIBC_LOG_LIBS = $(foreach x,$(LIBC_LOG_ARTIFACTS),$($(x))) LIBC_LOG_SRCS = $(foreach x,$(LIBC_LOG_ARTIFACTS),$($(x)_SRCS)) LIBC_LOG_HDRS = $(foreach x,$(LIBC_LOG_ARTIFACTS),$($(x)_HDRS)) diff --git a/libc/log/vflogf.c b/libc/log/vflogf.c index ed76dd89e..5fe4a11c7 100644 --- a/libc/log/vflogf.c +++ b/libc/log/vflogf.c @@ -37,6 +37,10 @@ #define kNontrivialSize (8 * 1000 * 1000) +STATIC_YOINK("ntoa"); +STATIC_YOINK("stoa"); +STATIC_YOINK("ftoa"); + static int loglevel2char(unsigned level) { switch (level) { case kLogInfo: @@ -47,6 +51,8 @@ static int loglevel2char(unsigned level) { return 'W'; case kLogFatal: return 'F'; + case kLogVerbose: + return 'V'; default: return '?'; } @@ -80,8 +86,7 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f, long double t2; const char *prog; int64_t secs, nsec, dots; - char zonebuf[8], *zonebufp; - char timebuf[24], *timebufp; + char timebuf[32], *timebufp; if (!f) f = g_logfile; if (fileno(f) == -1) return; t2 = nowl(); @@ -89,22 +94,19 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f, nsec = rem1000000000int64(t2 * 1e9L); if (secs > ts.tv_sec) { localtime_r(&secs, &tm); - strftime(timebuf, sizeof(timebuf), "%Y%m%dT%H%M%S", &tm); - strftime(zonebuf, sizeof(zonebuf), "%Z", &tm); + strftime(timebuf, sizeof(timebuf), "%Y-%m-%dT%H:%M:%S.", &tm); timebufp = timebuf; - zonebufp = zonebuf; dots = nsec; } else { - timebufp = "---------------"; - zonebufp = "---"; + timebufp = "--------------------"; dots = nsec - ts.tv_nsec; } ts.tv_sec = secs; ts.tv_nsec = nsec; prog = basename(program_invocation_name); - if (fprintf(f, "%c%s%s%06ld:%s:%d:%.*s:%d] ", loglevel2char(level), timebufp, - zonebufp, rem1000000int64(div1000int64(dots)), file, line, - strchrnul(prog, '.') - prog, prog, getpid()) <= 0) { + if ((fprintf)(f, "%c%s%06ld:%s:%d:%.*s:%d] ", loglevel2char(level), timebufp, + rem1000000int64(div1000int64(dots)), file, line, + strchrnul(prog, '.') - prog, prog, getpid()) <= 0) { vflogf_onfail(f); } (vfprintf)(f, fmt, va); @@ -112,7 +114,7 @@ void(vflogf)(unsigned level, const char *file, int line, FILE *f, fputc('\n', f); if (level == kLogFatal) { startfatal(file, line); - fprintf(stderr, "fatal error see logfile\n"); + (fprintf)(stderr, "fatal error see logfile\n"); die(); unreachable; } diff --git a/libc/nt/winsock.h b/libc/nt/winsock.h index 080478818..23385b7e6 100644 --- a/libc/nt/winsock.h +++ b/libc/nt/winsock.h @@ -1,6 +1,8 @@ #ifndef COSMOPOLITAN_LIBC_NT_WINSOCK_H_ #define COSMOPOLITAN_LIBC_NT_WINSOCK_H_ #include "libc/nt/struct/overlapped.h" +#include "libc/nt/struct/pollfd.h" +#include "libc/sock/sock.h" #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ #if 0 @@ -55,10 +57,6 @@ COSMOPOLITAN_C_START_ #define kNtTfUseSystemThread 0x10 #define kNtTfUseKernelApc 0x20 -struct sockaddr; -struct sockaddr_in; -struct pollfd$nt; - enum NtWsaEComparator { COMP_EQUAL, COMP_NOTLESS }; enum NtWsaCompletionType { diff --git a/libc/runtime/executive.S b/libc/runtime/executive.S index 1b7792225..1a6e04674 100644 --- a/libc/runtime/executive.S +++ b/libc/runtime/executive.S @@ -53,8 +53,7 @@ _executive: call _setstack mov %eax,%edi call exit -9: .endfn _executive,weak,hidden - + .endfn _executive,weak,hidden ud2 #ifdef __PG__ diff --git a/libc/runtime/getstack.c b/libc/runtime/getstack.c index 15cc3ed7e..013aea321 100644 --- a/libc/runtime/getstack.c +++ b/libc/runtime/getstack.c @@ -28,9 +28,9 @@ */ void *_getstack(void) { char *p; - p = mmap((char *)0x700000000000 - STACKSIZE, STACKSIZE, - PROT_READ | PROT_WRITE, MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, - 0); + p = mmap((char *)0x700000000000 /* IMAGE_BASE_VIRTUAL */ - STACKSIZE, + STACKSIZE, PROT_READ | PROT_WRITE, + MAP_FIXED | MAP_PRIVATE | MAP_ANONYMOUS, -1, 0); if (p == MAP_FAILED) abort(); return p + STACKSIZE; } diff --git a/libc/runtime/isheap.c b/libc/runtime/isheap.c index 3ff2f0ce1..40b289e7c 100644 --- a/libc/runtime/isheap.c +++ b/libc/runtime/isheap.c @@ -28,8 +28,11 @@ */ bool isheap(void *p) { int x, i; +#if 1 register intptr_t rsp asm("rsp"); if ((intptr_t)p >= rsp) return false; +#endif + if ((intptr_t)p <= (intptr_t)_end) return false; x = (intptr_t)p >> 16; i = FindMemoryInterval(&_mmi, x); return i < _mmi.i && x >= _mmi.p[i].x && x <= _mmi.p[i].y; diff --git a/libc/sock/inet_ntop.c b/libc/sock/inet_ntop.c index cd776e068..ad23eb9e4 100644 --- a/libc/sock/inet_ntop.c +++ b/libc/sock/inet_ntop.c @@ -17,11 +17,9 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/fmt/fmt.h" -#include "libc/sock/internal.h" -#include "libc/sock/sock.h" -#include "libc/sysv/errfuns.h" +#include "libc/conv/itoa.h" #include "libc/sysv/consts/af.h" +#include "libc/sysv/errfuns.h" /** * Formats internet address to string. @@ -33,11 +31,21 @@ * @return dst on success or NULL w/ errno */ const char *inet_ntop(int af, const void *src, char *dst, uint32_t size) { + char *p; + unsigned char *ip4; if (src) { if (af == AF_INET) { - unsigned char *p = (unsigned char *)src; - if (snprintf(dst, size, "%hhu.%hhu.%hhu.%hhu", p[0], p[1], p[2], p[3]) < - size) { + if (size >= 16) { + p = dst; + ip4 = src; + p += uint64toarray_radix10(ip4[0], p); + *p++ = '.'; + p += uint64toarray_radix10(ip4[1], p); + *p++ = '.'; + p += uint64toarray_radix10(ip4[2], p); + *p++ = '.'; + p += uint64toarray_radix10(ip4[3], p); + *p = '\0'; return dst; } else { enospc(); diff --git a/libc/stdio/fwritebuf.c b/libc/stdio/fwritebuf.c index 6368fa0a6..58259431c 100644 --- a/libc/stdio/fwritebuf.c +++ b/libc/stdio/fwritebuf.c @@ -37,6 +37,7 @@ int fwritebuf(FILE *f) { unsigned bytes; bytes = (f->beg < f->end ? f->end : f->size) - f->beg; if ((put = write(f->fd, &f->buf[f->beg], bytes)) == -1) { + if (errno == EINTR) return 0; return (int)fseterrno(f); } f->beg = (unsigned)((f->beg + put) & (f->size - 1)); diff --git a/libc/str/getutf16.ncabi.c b/libc/str/getutf16.ncabi.c index 669e3a856..2dfaa934e 100644 --- a/libc/str/getutf16.ncabi.c +++ b/libc/str/getutf16.ncabi.c @@ -19,6 +19,7 @@ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/errno.h" #include "libc/str/str.h" +#include "libc/str/utf16.h" /** * Decodes UTF-16 character. @@ -30,16 +31,15 @@ */ forcealignargpointer unsigned(getutf16)(const char16_t *p, wint_t *wc) { unsigned skip = 0; - while ((p[skip] & UTF16_MASK) == UTF16_CONT) skip++; - if ((p[skip] & UTF16_MASK) != UTF16_MOAR) { + while (IsUtf16Cont(p[skip])) skip++; + if (IsUcs2(p[skip])) { *wc = p[skip]; return skip + 1; - } else if ((p[skip + 1] & UTF16_MASK) != UTF16_CONT) { + } else if (IsUtf16Cont(p[skip + 1])) { *wc = INVALID_CODEPOINT; return -1; } else { - *wc = 0x10000 + ((((unsigned)p[skip + 0] - 0xd800) << 10) + - ((unsigned)p[skip + 1] - 0xdc00)); + *wc = MergeUtf16(p[skip], p[skip + 1]); return skip + 2; } } diff --git a/libc/str/str.h b/libc/str/str.h index 980512d5e..7472292ce 100644 --- a/libc/str/str.h +++ b/libc/str/str.h @@ -64,10 +64,6 @@ void *isnotplaintext(const void *, size_t) nothrow nocallback nosideeffect; #define INVALID_CODEPOINT 0xfffd -#define UTF16_MASK 0b1111110000000000 -#define UTF16_MOAR 0b1101100000000000 /* 0xD800..0xDBFF */ -#define UTF16_CONT 0b1101110000000000 /* 0xDC00..0xDBFF */ - unsigned getutf16(const char16_t *, wint_t *); int pututf16(char16_t *, size_t, wint_t, bool); int iswalnum(wint_t); diff --git a/libc/str/strclen16.c b/libc/str/strclen16.c index eea41bbdc..2ea65d11b 100644 --- a/libc/str/strclen16.c +++ b/libc/str/strclen16.c @@ -18,11 +18,14 @@ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/str/str.h" +#include "libc/str/utf16.h" /** * Returns number of characters in UTF-16 or UCS-2 string. */ -size_t strclen16(const char16_t *s) { return strnclen16(s, -1ull); } +size_t strclen16(const char16_t *s) { + return strnclen16(s, -1ull); +} noinline size_t strnclen16(const char16_t *p, size_t n) { size_t l = 0; diff --git a/libc/str/thompike.h b/libc/str/thompike.h index 8319827c6..982e77f97 100644 --- a/libc/str/thompike.h +++ b/libc/str/thompike.h @@ -2,9 +2,10 @@ #define COSMOPOLITAN_LIBC_STR_THOMPIKE_H_ #include "libc/nexgen32e/bsr.h" -#define ThomPikeCont(x) ((x & 0b11000000) == 0b10000000) -#define ThomPikeByte(x) (x & (((1 << ThomPikeMsb(x)) - 1) | 3)) -#define ThomPikeLen(x) (7 - ThomPikeMsb(x)) -#define ThomPikeMsb(x) (x < 252 ? bsr(~x & 0xff) : 1) +#define ThomPikeCont(x) (((x)&0b11000000) == 0b10000000) +#define ThomPikeByte(x) ((x) & (((1 << ThomPikeMsb(x)) - 1) | 3)) +#define ThomPikeLen(x) (7 - ThomPikeMsb(x)) +#define ThomPikeMsb(x) ((x) < 252 ? bsr(~(x)&0xff) : 1) +#define ThomPikeMerge(x, y) ((x) << 6 | (y)&0b00111111) #endif /* COSMOPOLITAN_LIBC_STR_THOMPIKE_H_ */ diff --git a/libc/str/utf16.h b/libc/str/utf16.h new file mode 100644 index 000000000..0827a2050 --- /dev/null +++ b/libc/str/utf16.h @@ -0,0 +1,12 @@ +#ifndef COSMOPOLITAN_LIBC_STR_UTF16_H_ +#define COSMOPOLITAN_LIBC_STR_UTF16_H_ + +#define UTF16_MASK 0xfc00 +#define UTF16_MOAR 0xd800 /* 0xD800..0xDBFF */ +#define UTF16_CONT 0xdc00 /* 0xDC00..0xDBFF */ + +#define IsUcs2(wc) (((wc)&UTF16_MASK) != UTF16_MOAR) +#define IsUtf16Cont(wc) (((wc)&UTF16_MASK) != UTF16_MOAR) +#define MergeUtf16(lo, hi) ((((lo)-0xD800) << 10) + ((hi)-0xDC00) + 0x10000) + +#endif /* COSMOPOLITAN_LIBC_STR_UTF16_H_ */ diff --git a/libc/time/localtime.c b/libc/time/localtime.c index 1c98e4b8e..e1ba128d5 100644 --- a/libc/time/localtime.c +++ b/libc/time/localtime.c @@ -1421,11 +1421,11 @@ offtime(timep, offset) ** where, to make the math easy, the answer for year zero is defined as zero. */ -pureconst static int +pureconst optimizespeed static int leaps_thru_end_of(y) register const int y; { - return (y >= 0) ? (y / 4 - div100int64(y) + y / 400) : + return (y >= 0) ? (y / 4 - y / 100 + y / 400) : -(leaps_thru_end_of(-(y + 1)) + 1); } @@ -1605,15 +1605,19 @@ timesub(timep, offset, sp, tmp) ** Simplified normalize logic courtesy Paul Eggert. */ -static int +static inline int increment_overflow(number, delta) 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 int diff --git a/libc/time/time.h b/libc/time/time.h index 6542918e9..772ea20cf 100644 --- a/libc/time/time.h +++ b/libc/time/time.h @@ -12,6 +12,7 @@ extern const char kWeekdayNameShort[7][4]; extern const char kWeekdayName[7][10]; extern const char kMonthNameShort[12][4]; extern const char kMonthName[12][10]; +extern const unsigned short kMonthYearDay[2][12]; extern char *tzname[2]; extern long CLOCKS_PER_SEC; diff --git a/libc/time/times.c b/libc/time/times.c index 25e04f1ca..9486a5143 100644 --- a/libc/time/times.c +++ b/libc/time/times.c @@ -47,9 +47,9 @@ static noinline long times2(struct tms *out_times, struct rusage *ru) { &KernelTime, &UserTime)) { return winerr(); } - filetimetotimeval(&tv, UserTime); + FileTimeToTimeVal(&tv, UserTime); out_times->tms_utime = convertmicros(&tv, tick); - filetimetotimeval(&tv, KernelTime); + FileTimeToTimeVal(&tv, KernelTime); out_times->tms_stime = convertmicros(&tv, tick); out_times->tms_cutime = 0; out_times->tms_cstime = 0; diff --git a/libc/zip.h b/libc/zip.h index 41f989823..7aa91532f 100644 --- a/libc/zip.h +++ b/libc/zip.h @@ -57,6 +57,7 @@ #define kZipCfileOffsetLastmodifieddate 14 #define kZipCfileOffsetCrc32 16 #define kZipCfileOffsetCompressedsize 20 +#define kZipCfileOffsetUncompressedsize 24 #define kZipCfileOffsetExternalattributes 38 #define kZipCfileOffsetOffset 42 @@ -71,10 +72,10 @@ #define kZipGflagUtf8 0x800 -#define kZipExtraHdrSize 4 -#define kZipExtraZip64 0x0001 -#define kZipExtraNtfs 0x000a -#define kZipExtraNtfsFiletimes 0x0001 +#define kZipExtraHdrSize 4 +#define kZipExtraZip64 0x0001 +#define kZipExtraNtfs 0x000a +#define kZipExtraExtendedTimestamp 0x5455 #define kZipCfileMagic "PK\001\002" @@ -105,9 +106,10 @@ READ16LE((P) + kZipCfileOffsetLastmodifiedtime) /* @see DOS_TIME() */ #define ZIP_CFILE_LASTMODIFIEDDATE(P) \ READ16LE((P) + kZipCfileOffsetLastmodifieddate) /* @see DOS_DATE() */ -#define ZIP_CFILE_CRC32(P) READ32LE((P) + kZipCfileOffsetCrc32) -#define ZIP_CFILE_COMPRESSEDSIZE(P) READ32LE(P + kZipCfileOffsetCompressedsize) -#define ZIP_CFILE_UNCOMPRESSEDSIZE(P) READ32LE((P) + 24) +#define ZIP_CFILE_CRC32(P) READ32LE((P) + kZipCfileOffsetCrc32) +#define ZIP_CFILE_COMPRESSEDSIZE(P) READ32LE(P + kZipCfileOffsetCompressedsize) +#define ZIP_CFILE_UNCOMPRESSEDSIZE(P) \ + READ32LE((P) + kZipCfileOffsetUncompressedsize) #define ZIP_CFILE_NAMESIZE(P) READ16LE((P) + 28) #define ZIP_CFILE_EXTRASIZE(P) READ16LE((P) + 30) #define ZIP_CFILE_COMMENTSIZE(P) READ16LE((P) + 32) @@ -124,7 +126,7 @@ (ZIP_CFILE_NAMESIZE(P) + ZIP_CFILE_EXTRASIZE(P) + ZIP_CFILE_COMMENTSIZE(P) + \ kZipCfileHdrMinSize) -/* central directory file header */ +/* local file header */ #define ZIP_LFILE_MAGIC(P) READ32LE(P) #define ZIP_LFILE_VERSIONNEED(P) ((P)[4]) #define ZIP_LFILE_OSNEED(P) ((P)[5]) diff --git a/libc/zipos/close.c b/libc/zipos/close.c index f70959f2f..4c6be835a 100644 --- a/libc/zipos/close.c +++ b/libc/zipos/close.c @@ -29,7 +29,7 @@ */ int __zipos_close(struct ZiposHandle *h) { if (h) { - munmap(h->map, h->mapsize); + free(h->map); free(h); } return 0; diff --git a/libc/zipos/find.c b/libc/zipos/find.c index 146282aa3..d0da16bfd 100644 --- a/libc/zipos/find.c +++ b/libc/zipos/find.c @@ -27,16 +27,14 @@ ssize_t __zipos_find(struct Zipos *zipos, const struct ZiposUri *name) { size_t i, cf; - if ((zipos = __zipos_get())) { - assert(ZIP_CDIR_MAGIC(zipos->cdir) == kZipCdirHdrMagic); - for (i = 0, cf = ZIP_CDIR_OFFSET(zipos->cdir); - i < ZIP_CDIR_RECORDS(zipos->cdir); - ++i, cf += ZIP_CFILE_HDRSIZE(zipos->map + cf)) { - assert(ZIP_CFILE_MAGIC(zipos->map + cf) == kZipCfileHdrMagic); - if (name->len == ZIP_CFILE_NAMESIZE(zipos->map + cf) && - memcmp(name->path, ZIP_CFILE_NAME(zipos->map + cf), name->len) == 0) { - return cf; - } + assert(ZIP_CDIR_MAGIC(zipos->cdir) == kZipCdirHdrMagic); + for (i = 0, cf = ZIP_CDIR_OFFSET(zipos->cdir); + i < ZIP_CDIR_RECORDS(zipos->cdir); + ++i, cf += ZIP_CFILE_HDRSIZE(zipos->map + cf)) { + assert(ZIP_CFILE_MAGIC(zipos->map + cf) == kZipCfileHdrMagic); + if (name->len == ZIP_CFILE_NAMESIZE(zipos->map + cf) && + memcmp(name->path, ZIP_CFILE_NAME(zipos->map + cf), name->len) == 0) { + return cf; } } return -1; diff --git a/libc/zipos/open.c b/libc/zipos/open.c index 0cb29c803..acd8a555d 100644 --- a/libc/zipos/open.c +++ b/libc/zipos/open.c @@ -99,9 +99,9 @@ static int __zipos_load(struct Zipos *zipos, size_t cf, unsigned flags, if ((h->size = ZIP_LFILE_UNCOMPRESSEDSIZE(zipos->map + lf))) { if (ZIP_LFILE_COMPRESSIONMETHOD(zipos->map + lf)) { assert(ZIP_LFILE_COMPRESSEDSIZE(zipos->map + lf)); - h->mapsize = ROUNDUP(h->size + FRAMESIZE, FRAMESIZE); - if ((h->map = mapanon(h->mapsize)) != MAP_FAILED) { - h->mem = h->map + FRAMESIZE / 2; + h->mapsize = h->size; + if ((h->map = malloc(h->mapsize)) != MAP_FAILED) { + h->mem = h->map; if ((IsTiny() ? __zipos_inflate_tiny : __zipos_inflate_fast)( h, ZIP_LFILE_CONTENT(zipos->map + lf), ZIP_LFILE_COMPRESSEDSIZE(zipos->map + lf)) == -1) { @@ -132,7 +132,7 @@ static int __zipos_load(struct Zipos *zipos, size_t cf, unsigned flags, * Loads compressed file from αcτµαlly pδrταblε εxεcµταblε object store. * * @param uri is obtained via __zipos_parseuri() - * @asyncsignalsafe + * @note don't call open() from signal handlers */ int __zipos_open(const struct ZiposUri *name, unsigned flags, int mode) { int fd; @@ -140,7 +140,6 @@ int __zipos_open(const struct ZiposUri *name, unsigned flags, int mode) { sigset_t oldmask; struct Zipos *zipos; assert((flags & O_ACCMODE) == O_RDONLY); - sigprocmask(SIG_BLOCK, &kSigsetFull, &oldmask); if ((zipos = __zipos_get())) { if ((cf = __zipos_find(zipos, name)) != -1) { fd = __zipos_load(zipos, cf, flags, mode); @@ -150,6 +149,5 @@ int __zipos_open(const struct ZiposUri *name, unsigned flags, int mode) { } else { fd = enoexec(); } - sigprocmask(SIG_SETMASK, &oldmask, NULL); return fd; } diff --git a/net/http/formathttpdatetime.c b/net/http/formathttpdatetime.c new file mode 100644 index 000000000..7a622f596 --- /dev/null +++ b/net/http/formathttpdatetime.c @@ -0,0 +1,67 @@ +/*-*- 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 │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/str/str.h" +#include "libc/time/struct/tm.h" +#include "libc/time/time.h" +#include "net/http/http.h" + +/** + * Formats HTTP timestamp, e.g. + * + * Sun, 04 Oct 2020 19:50:10 GMT + * + * @param tm must be zulu see gmtime_r() and nowl() + * @see ParseHttpDateTime() + */ +char *FormatHttpDateTime(char p[hasatleast 30], struct tm *tm) { + unsigned i; + p = mempcpy(p, kWeekdayNameShort[tm->tm_wday], 3); + *p++ = ','; + *p++ = ' '; + i = tm->tm_mday; + *p++ = '0' + i / 10; + *p++ = '0' + i % 10; + *p++ = ' '; + p = mempcpy(p, kMonthNameShort[tm->tm_mon], 3); + *p++ = ' '; + i = tm->tm_year + 1900; + *p++ = '0' + i / 1000; + *p++ = '0' + i / 100 % 10; + *p++ = '0' + i / 10 % 10; + *p++ = '0' + i % 10; + *p++ = ' '; + i = tm->tm_hour; + *p++ = '0' + i / 10; + *p++ = '0' + i % 10; + *p++ = ':'; + i = tm->tm_min; + *p++ = '0' + i / 10; + *p++ = '0' + i % 10; + *p++ = ':'; + i = tm->tm_sec; + *p++ = '0' + i / 10; + *p++ = '0' + i % 10; + *p++ = ' '; + *p++ = 'G'; + *p++ = 'M'; + *p++ = 'T'; + *p = '\0'; + return p; +} diff --git a/net/http/gethttpheader.gperf b/net/http/gethttpheader.gperf index b2d504d51..6e554ab05 100644 --- a/net/http/gethttpheader.gperf +++ b/net/http/gethttpheader.gperf @@ -58,5 +58,5 @@ Retry-After, kHttpRetryAfter Server, kHttpServer Vary, kHttpVary Warning, kHttpWarning -WWW-Authenticate, kHttpWwwAuthenticate +Www-Authenticate, kHttpWwwAuthenticate Last-Modified, kHttpLastModified diff --git a/net/http/gethttpheader.inc b/net/http/gethttpheader.inc index 890efdfe1..673dd103f 100644 --- a/net/http/gethttpheader.inc +++ b/net/http/gethttpheader.inc @@ -216,7 +216,7 @@ LookupHttpHeader (register const char *str, register size_t len) #line 43 "gethttpheader.gperf" {"Keep-Alive", kHttpKeepAlive}, #line 61 "gethttpheader.gperf" - {"WWW-Authenticate", kHttpWwwAuthenticate}, + {"Www-Authenticate", kHttpWwwAuthenticate}, #line 35 "gethttpheader.gperf" {"Expires", kHttpExpires}, #line 46 "gethttpheader.gperf" diff --git a/net/http/gethttpmethod.gperf b/net/http/gethttpmethod.gperf index 2d2caa606..1e44e350b 100644 --- a/net/http/gethttpmethod.gperf +++ b/net/http/gethttpmethod.gperf @@ -9,7 +9,7 @@ %readonly-tables %struct-type %define lookup-function-name LookupHttpMethod -struct HttpMethodSlot { char *name; char code; }; +struct HttpMethodSlot { char name[8]; char code; }; %% DELETE, kHttpDelete GET, kHttpGet @@ -20,17 +20,11 @@ CONNECT, kHttpConnect OPTIONS, kHttpOptions TRACE, kHttpTrace COPY, kHttpCopy -CHECKOUT, kHttpCheckout LOCK, kHttpLock MERGE, kHttpMerge -MKACTIVITY, kHttpMkactivity MKCOL, kHttpMkcol MOVE, kHttpMove NOTIFY, kHttpNotify PATCH, kHttpPatch -PROPFIND, kHttpPropfind -PROPPATCH, kHttpProppatch REPORT, kHttpReport -SUBSCRIBE, kHttpSubscribe UNLOCK, kHttpUnlock -UNSUBSCRIBE, kHttpUnsubscribe diff --git a/net/http/gethttpmethod.inc b/net/http/gethttpmethod.inc index 3678aa386..38d487254 100644 --- a/net/http/gethttpmethod.inc +++ b/net/http/gethttpmethod.inc @@ -35,14 +35,14 @@ #include "net/http/http.h" #define GPERF_DOWNCASE #line 12 "gethttpmethod.gperf" -struct HttpMethodSlot { char *name; char code; }; +struct HttpMethodSlot { char name[8]; char code; }; -#define TOTAL_KEYWORDS 23 +#define TOTAL_KEYWORDS 17 #define MIN_WORD_LENGTH 3 -#define MAX_WORD_LENGTH 11 -#define MIN_HASH_VALUE 4 -#define MAX_HASH_VALUE 34 -/* maximum key range = 31, duplicates = 0 */ +#define MAX_WORD_LENGTH 7 +#define MIN_HASH_VALUE 3 +#define MAX_HASH_VALUE 25 +/* maximum key range = 23, duplicates = 0 */ #ifndef GPERF_DOWNCASE #define GPERF_DOWNCASE 1 @@ -101,32 +101,32 @@ hash (register const char *str, register size_t len) { static const unsigned char asso_values[] = { - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 0, 35, 5, 10, 10, - 35, 5, 10, 35, 35, 0, 30, 15, 0, 0, - 0, 35, 5, 15, 0, 5, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 0, 35, 5, - 10, 10, 35, 5, 10, 35, 35, 0, 30, 15, - 0, 0, 0, 35, 5, 15, 0, 5, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35, 35, 35, 35, 35, - 35, 35, 35, 35, 35, 35 + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 0, 26, 5, 15, 0, + 26, 5, 0, 26, 26, 10, 15, 10, 0, 5, + 0, 26, 10, 26, 5, 0, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 0, 26, 5, + 15, 0, 26, 5, 0, 26, 26, 10, 15, 10, + 0, 5, 0, 26, 10, 26, 5, 0, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26, 26, 26, 26, 26, + 26, 26, 26, 26, 26, 26 }; return len + asso_values[(unsigned char)str[1]] + asso_values[(unsigned char)str[0]]; } @@ -136,58 +136,46 @@ LookupHttpMethod (register const char *str, register size_t len) { static const struct HttpMethodSlot wordlist[] = { - {""}, {""}, {""}, {""}, -#line 17 "gethttpmethod.gperf" - {"POST", kHttpPost}, -#line 30 "gethttpmethod.gperf" - {"PATCH", kHttpPatch}, -#line 29 "gethttpmethod.gperf" - {"NOTIFY", kHttpNotify}, -#line 20 "gethttpmethod.gperf" - {"OPTIONS", kHttpOptions}, + {""}, {""}, {""}, #line 18 "gethttpmethod.gperf" {"PUT", kHttpPut}, -#line 22 "gethttpmethod.gperf" - {"COPY", kHttpCopy}, -#line 21 "gethttpmethod.gperf" - {"TRACE", kHttpTrace}, -#line 35 "gethttpmethod.gperf" +#line 16 "gethttpmethod.gperf" + {"HEAD", kHttpHead}, +#line 28 "gethttpmethod.gperf" + {"PATCH", kHttpPatch}, +#line 30 "gethttpmethod.gperf" {"UNLOCK", kHttpUnlock}, -#line 19 "gethttpmethod.gperf" - {"CONNECT", kHttpConnect}, -#line 31 "gethttpmethod.gperf" - {"PROPFIND", kHttpPropfind}, -#line 32 "gethttpmethod.gperf" - {"PROPPATCH", kHttpProppatch}, - {""}, -#line 36 "gethttpmethod.gperf" - {"UNSUBSCRIBE", kHttpUnsubscribe}, {""}, #line 15 "gethttpmethod.gperf" {"GET", kHttpGet}, -#line 28 "gethttpmethod.gperf" - {"MOVE", kHttpMove}, -#line 27 "gethttpmethod.gperf" - {"MKCOL", kHttpMkcol}, -#line 33 "gethttpmethod.gperf" - {"REPORT", kHttpReport}, +#line 17 "gethttpmethod.gperf" + {"POST", kHttpPost}, + {""}, +#line 27 "gethttpmethod.gperf" + {"NOTIFY", kHttpNotify}, +#line 20 "gethttpmethod.gperf" + {"OPTIONS", kHttpOptions}, + {""}, +#line 22 "gethttpmethod.gperf" + {"COPY", kHttpCopy}, +#line 24 "gethttpmethod.gperf" + {"MERGE", kHttpMerge}, +#line 29 "gethttpmethod.gperf" + {"REPORT", kHttpReport}, +#line 19 "gethttpmethod.gperf" + {"CONNECT", kHttpConnect}, {""}, -#line 23 "gethttpmethod.gperf" - {"CHECKOUT", kHttpCheckout}, -#line 16 "gethttpmethod.gperf" - {"HEAD", kHttpHead}, #line 26 "gethttpmethod.gperf" - {"MKACTIVITY", kHttpMkactivity}, + {"MOVE", kHttpMove}, +#line 21 "gethttpmethod.gperf" + {"TRACE", kHttpTrace}, #line 14 "gethttpmethod.gperf" {"DELETE", kHttpDelete}, {""}, {""}, -#line 34 "gethttpmethod.gperf" - {"SUBSCRIBE", kHttpSubscribe}, +#line 23 "gethttpmethod.gperf" + {"LOCK", kHttpLock}, #line 25 "gethttpmethod.gperf" - {"MERGE", kHttpMerge}, - {""}, {""}, {""}, -#line 24 "gethttpmethod.gperf" - {"LOCK", kHttpLock} + {"MKCOL", kHttpMkcol} }; if (len <= MAX_WORD_LENGTH && len >= MIN_WORD_LENGTH) diff --git a/net/http/http.h b/net/http/http.h index 6801183a6..c36797737 100644 --- a/net/http/http.h +++ b/net/http/http.h @@ -1,30 +1,25 @@ #ifndef COSMOPOLITAN_LIBC_HTTP_HTTP_H_ #define COSMOPOLITAN_LIBC_HTTP_HTTP_H_ #include "libc/alg/alg.h" +#include "libc/time/struct/tm.h" -#define kHttpGet 0 -#define kHttpHead 1 -#define kHttpPost 2 -#define kHttpPut 3 -#define kHttpDelete 4 -#define kHttpConnect 5 -#define kHttpOptions 6 -#define kHttpTrace 7 -#define kHttpCopy 8 -#define kHttpCheckout 9 -#define kHttpLock 10 -#define kHttpMerge 11 -#define kHttpMkactivity 12 -#define kHttpMkcol 13 -#define kHttpMove 14 -#define kHttpNotify 15 -#define kHttpPatch 16 -#define kHttpPropfind 17 -#define kHttpProppatch 18 -#define kHttpReport 19 -#define kHttpSubscribe 20 -#define kHttpUnlock 21 -#define kHttpUnsubscribe 22 +#define kHttpGet 0 +#define kHttpHead 1 +#define kHttpPost 2 +#define kHttpPut 3 +#define kHttpDelete 4 +#define kHttpConnect 5 +#define kHttpOptions 6 +#define kHttpTrace 7 +#define kHttpCopy 8 +#define kHttpLock 9 +#define kHttpMerge 10 +#define kHttpMkcol 11 +#define kHttpMove 12 +#define kHttpNotify 13 +#define kHttpPatch 14 +#define kHttpReport 15 +#define kHttpUnlock 16 #define kHttpAccept 0 #define kHttpAcceptCharset 1 @@ -86,17 +81,24 @@ struct HttpRequestSlice { struct HttpRequest { int method; + int length; struct HttpRequestSlice uri; struct HttpRequestSlice version; struct HttpRequestSlice scratch; struct HttpRequestSlice headers[kHttpHeadersMax]; }; +extern const char kHttpMethod[17][8]; + int GetHttpHeader(const char *, size_t); int GetHttpMethod(const char *, size_t); int ParseHttpRequest(struct HttpRequest *, const char *, size_t); int NegotiateHttpRequest(int, const char *, uint32_t *, char *, uint32_t *, uint32_t *, bool, long double); +char *FormatHttpDateTime(char[hasatleast 30], struct tm *); +bool ParseHttpRange(const char *, size_t, long, long *, long *); +unsigned ParseHttpVersion(const char *, size_t); +int64_t ParseHttpDateTime(const char *, size_t); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/net/http/http.mk b/net/http/http.mk index 35051538b..80d9e8605 100644 --- a/net/http/http.mk +++ b/net/http/http.mk @@ -12,65 +12,70 @@ NET_HTTP_A_SRCS_S = $(filter %.S,$(NET_HTTP_A_FILES)) NET_HTTP_A_SRCS_C = $(filter %.c,$(NET_HTTP_A_FILES)) NET_HTTP_A_SRCS_R = $(filter %.rl,$(NET_HTTP_A_FILES)) -NET_HTTP_A_SRCS = \ - $(NET_HTTP_A_SRCS_S) \ - $(NET_HTTP_A_SRCS_C) \ +NET_HTTP_A_SRCS = \ + $(NET_HTTP_A_SRCS_S) \ + $(NET_HTTP_A_SRCS_C) \ $(NET_HTTP_A_SRCS_R) -NET_HTTP_A_OBJS = \ - $(NET_HTTP_A_SRCS:%=o/$(MODE)/%.zip.o) \ - $(NET_HTTP_A_SRCS_S:%.S=o/$(MODE)/%.o) \ - $(NET_HTTP_A_SRCS_C:%.c=o/$(MODE)/%.o) \ +NET_HTTP_A_OBJS = \ + $(NET_HTTP_A_SRCS:%=o/$(MODE)/%.zip.o) \ + $(NET_HTTP_A_SRCS_S:%.S=o/$(MODE)/%.o) \ + $(NET_HTTP_A_SRCS_C:%.c=o/$(MODE)/%.o) \ $(NET_HTTP_A_SRCS_R:%.rl=o/$(MODE)/%.o) -NET_HTTP_A_CHECKS = \ - $(NET_HTTP_A).pkg \ +NET_HTTP_A_CHECKS = \ + $(NET_HTTP_A).pkg \ $(NET_HTTP_A_HDRS:%=o/$(MODE)/%.ok) -NET_HTTP_A_DIRECTDEPS = \ - LIBC_ALG \ - LIBC_CALLS \ - LIBC_CONV \ - LIBC_FMT \ - LIBC_LOG \ - LIBC_NEXGEN32E \ - LIBC_RUNTIME \ - LIBC_SOCK \ - LIBC_STDIO \ - LIBC_STUBS \ - LIBC_SYSV \ - LIBC_TIME \ +NET_HTTP_A_DIRECTDEPS = \ + LIBC_ALG \ + LIBC_CALLS \ + LIBC_CONV \ + LIBC_FMT \ + LIBC_LOG \ + LIBC_NEXGEN32E \ + LIBC_RUNTIME \ + LIBC_SOCK \ + LIBC_STDIO \ + LIBC_STUBS \ + LIBC_SYSV \ + LIBC_TIME \ LIBC_X -NET_HTTP_A_DEPS := \ +NET_HTTP_A_DEPS := \ $(call uniq,$(foreach x,$(NET_HTTP_A_DIRECTDEPS),$($(x)))) -$(NET_HTTP_A): net/http/ \ - $(NET_HTTP_A).pkg \ +$(NET_HTTP_A): net/http/ \ + $(NET_HTTP_A).pkg \ $(NET_HTTP_A_OBJS) -$(NET_HTTP_A).pkg: \ - $(NET_HTTP_A_OBJS) \ +$(NET_HTTP_A).pkg: \ + $(NET_HTTP_A_OBJS) \ $(foreach x,$(NET_HTTP_A_DIRECTDEPS),$($(x)_A).pkg) +o/$(MODE)/net/http/formathttpdatetime.o: \ + OVERRIDE_CFLAGS += \ + -O3 + +ifeq (,$(MODE)) +$(NET_HTTP_A_OBJS): \ + OVERRIDE_CFLAGS += \ + -fsanitize=address +endif + NET_HTTP_LIBS = $(foreach x,$(NET_HTTP_ARTIFACTS),$($(x))) NET_HTTP_SRCS = $(foreach x,$(NET_HTTP_ARTIFACTS),$($(x)_SRCS)) NET_HTTP_HDRS = $(foreach x,$(NET_HTTP_ARTIFACTS),$($(x)_HDRS)) NET_HTTP_CHECKS = $(foreach x,$(NET_HTTP_ARTIFACTS),$($(x)_CHECKS)) NET_HTTP_OBJS = $(foreach x,$(NET_HTTP_ARTIFACTS),$($(x)_OBJS)) -$(NET_HTTP_OBJS): $(BUILD_FILES) net/http/http.mk - -.PRECIOUS: \ - $(NET_HTTP_A_SRCS_R:%.rl=build/bootstrap/%.c) \ - o/$(MODE)/net/http/uricspn.s \ - o/$(MODE)/net/http/uriparse.s \ - o/$(MODE)/net/http/uricspn.i \ - o/$(MODE)/net/http/uriparse.i \ - o/$(MODE)/net/http/uriparse.c \ +.PRECIOUS: \ + $(NET_HTTP_A_SRCS_R:%.rl=o/$(MODE)/%.c) \ + o/$(MODE)/net/http/uricspn.s \ + o/$(MODE)/net/http/uricspn.i \ o/$(MODE)/net/http/uricspn.c .PHONY: o/$(MODE)/net/http -o/$(MODE)/net/http: \ - $(NET_HTTP_CHECKS) \ +o/$(MODE)/net/http: \ + $(NET_HTTP_CHECKS) \ $(NET_HTTP_A_SRCS_R:%.rl=%.svgz) diff --git a/net/http/khttpmethod.c b/net/http/khttpmethod.c new file mode 100644 index 000000000..fcb8bd296 --- /dev/null +++ b/net/http/khttpmethod.c @@ -0,0 +1,40 @@ +/*-*- 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 │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "net/http/http.h" + +const char kHttpMethod[17][8] = { + "GET", // + "HEAD", // + "POST", // + "PUT", // + "DELETE", // + "CONNECT", // + "OPTIONS", // + "TRACE", // + "COPY", // + "LOCK", // + "MERGE", // + "MKCOL", // + "MOVE", // + "NOTIFY", // + "PATCH", // + "REPORT", // + "UNLOCK", // +}; diff --git a/net/http/parsehttpdatetime.c b/net/http/parsehttpdatetime.c new file mode 100644 index 000000000..f78b7c558 --- /dev/null +++ b/net/http/parsehttpdatetime.c @@ -0,0 +1,57 @@ +/*-*- 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 │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/bits.h" +#include "libc/time/time.h" +#include "net/http/http.h" + +static unsigned ParseMonth(const char *p) { + int i; + for (i = 0; i < 12; ++i) { + if ((READ32LE(p) & 0x00ffffff) == READ32LE(kMonthNameShort[i])) { + return i + 1; + } + } + return 1; +} + +/** + * Parses HTTP timestamp, e.g. + * + * Sun, 04 Oct 2020 19:50:10 GMT + * + * @return seconds from unix epoch + * @see FormatHttpDateTime() + */ +int64_t ParseHttpDateTime(const char *p, size_t n) { + unsigned weekday, year, month, day, hour, minute, second, yday, leap; + if (n != 29) return 0; + day = (p[5] - '0') * 10 + (p[6] - '0') - 1; + month = ParseMonth(p + 8); + year = (p[12] - '0') * 1000 + (p[13] - '0') * 100 + (p[14] - '0') * 10 + + (p[15] - '0') - 1900; + hour = (p[17] - '0') * 10 + (p[18] - '0'); + minute = (p[20] - '0') * 10 + (p[21] - '0'); + second = (p[23] - '0') * 10 + (p[24] - '0'); + leap = year % 4 == 0 && (year % 100 || year % 400 == 0); + yday = day + kMonthYearDay[leap][month - 1]; + return second + minute * 60 + hour * 3600 + yday * 86400 + + (year - 70) * 31536000ull + ((year - 69) / 4) * 86400ull - + ((year - 1) / 100) * 86400ull + ((year + 299) / 400) * 86400ull; +} diff --git a/net/http/parsehttprange.c b/net/http/parsehttprange.c new file mode 100644 index 000000000..a6dee493c --- /dev/null +++ b/net/http/parsehttprange.c @@ -0,0 +1,72 @@ +/*-*- 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 │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/str/str.h" +#include "net/http/http.h" + +/** + * Parses HTTP Range request header. + */ +bool ParseHttpRange(const char *p, size_t n, long resourcelength, + long *out_start, long *out_length) { + long start, length, ending; + *out_start = 0; + *out_length = 0; + if (memchr(p, ',', n)) return false; + if (n < 7 || memcmp(p, "bytes=", 6) != 0) return false; + p += 6, n -= 6; + if (n && *p == '-') { + ++p, --n; + length = 0; + while (n && '0' <= *p && *p <= '9') { + if (__builtin_mul_overflow(length, 10, &length)) return false; + if (__builtin_add_overflow(length, *p - '0', &length)) return false; + ++p, --n; + } + if (__builtin_sub_overflow(resourcelength, length, &start)) return false; + } else { + start = 0; + while (n && '0' <= *p && *p <= '9') { + if (__builtin_mul_overflow(start, 10, &start)) return false; + if (__builtin_add_overflow(start, *p - '0', &start)) return false; + ++p, --n; + } + if (n && *p == '-') { + ++p, --n; + length = 0; + while (n && '0' <= *p && *p <= '9') { + if (__builtin_mul_overflow(length, 10, &length)) return false; + if (__builtin_add_overflow(length, *p - '0', &length)) return false; + ++p, --n; + } + if (__builtin_add_overflow(length, 1, &length)) return false; + if (__builtin_sub_overflow(length, start, &length)) return false; + } else if (__builtin_sub_overflow(resourcelength, start, &length)) { + return false; + } + } + if (n) return false; + if (start < 0) return false; + if (length < 0) return false; + *out_start = start; + *out_length = length; + if (__builtin_add_overflow(start, length, &ending)) return false; + if (ending > resourcelength) return false; + return true; +} diff --git a/net/http/parsehttprequest.c b/net/http/parsehttprequest.c index cd44558a1..1f01f7d94 100644 --- a/net/http/parsehttprequest.c +++ b/net/http/parsehttprequest.c @@ -112,7 +112,10 @@ int ParseHttpRequest(struct HttpRequest *req, const char *p, size_t n) { } break; case LF2: - if (c == '\n') return 0; + if (c == '\n') { + req->length = i + 1; + return 0; + } return ebadmsg(); default: unreachable; diff --git a/net/http/parsehttpversion.c b/net/http/parsehttpversion.c new file mode 100644 index 000000000..6e26ad8ef --- /dev/null +++ b/net/http/parsehttpversion.c @@ -0,0 +1,33 @@ +/*-*- 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 │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/bits.h" +#include "net/http/http.h" + +unsigned ParseHttpVersion(const char *p, size_t n) { + unsigned x; + if (n >= 8 && READ32LE(p) == ('H' | 'T' << 8 | 'T' << 16 | 'P' << 24)) { + if (READ32LE(p + 4) == ('/' | '1' << 8 | '.' << 16 | '1' << 24)) { + return 101; + } else if (READ32LE(p + 4) == ('/' | '1' << 8 | '.' << 16 | '0' << 24)) { + return 100; + } + } + return -1; +} diff --git a/test/libc/conv/dosdatetimetounix_test.c b/test/libc/conv/dosdatetimetounix_test.c new file mode 100644 index 000000000..d01c7dc59 --- /dev/null +++ b/test/libc/conv/dosdatetimetounix_test.c @@ -0,0 +1,32 @@ +/*-*- 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 │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/conv/conv.h" +#include "libc/dos.h" +#include "libc/testlib/testlib.h" + +TEST(DosDateTimeToUnix, test) { + EXPECT_EQ(1601929396, + DosDateTimeToUnix(DOS_DATE(2020, 10, 5), DOS_TIME(20, 23, 16))); +} + +TEST(DosDateTimeToUnix, testNotLeapYear) { + EXPECT_EQ(4107529396, + DosDateTimeToUnix(DOS_DATE(2100, 2, 28), DOS_TIME(20, 23, 16))); +} diff --git a/test/libc/conv/test.mk b/test/libc/conv/test.mk index 0309f708a..9d642c945 100644 --- a/test/libc/conv/test.mk +++ b/test/libc/conv/test.mk @@ -25,6 +25,9 @@ TEST_LIBC_CONV_CHECKS = \ TEST_LIBC_CONV_DIRECTDEPS = \ LIBC_CONV \ + LIBC_FMT \ + LIBC_LOG \ + LIBC_STDIO \ LIBC_NEXGEN32E \ LIBC_STUBS \ LIBC_TINYMATH \ diff --git a/test/libc/conv/timevaltofiletime_test.c b/test/libc/conv/timevaltofiletime_test.c index 15c09f132..3f39801dd 100644 --- a/test/libc/conv/timevaltofiletime_test.c +++ b/test/libc/conv/timevaltofiletime_test.c @@ -23,11 +23,11 @@ #include "libc/testlib/testlib.h" #include "libc/time/time.h" -TEST(timevaltofiletime, roundTrip) { +TEST(TimeValToFileTime, roundTrip) { struct timeval tv1, tv2; tv1.tv_sec = 31337; tv1.tv_usec = 1337; - filetimetotimeval(&tv2, timevaltofiletime(&tv1)); + FileTimeToTimeVal(&tv2, TimeValToFileTime(&tv1)); EXPECT_EQ(31337, tv2.tv_sec); EXPECT_EQ(1337, tv2.tv_usec); } diff --git a/test/libc/fmt/fcvt_test.c b/test/libc/fmt/fcvt_test.c new file mode 100644 index 000000000..90e1985bb --- /dev/null +++ b/test/libc/fmt/fcvt_test.c @@ -0,0 +1,32 @@ +/*-*- 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 │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/fmt/fmt.h" +#include "libc/runtime/gc.h" +#include "libc/testlib/testlib.h" +#include "libc/x/x.h" + +TEST(fcvt, test) { + int decpt, sign; + ASSERT_STREQ("3.14159265358979", xasprintf("%.14f", 3.14159265358979323846)); + ASSERT_STREQ("3141592653589793", + fcvt(3.14159265358979323846, 15, &decpt, &sign)); + ASSERT_EQ(1, decpt); + ASSERT_EQ(0, sign); +} diff --git a/test/libc/fmt/palandprintf_test.c b/test/libc/fmt/palandprintf_test.c index 2c562a9ef..c797aa783 100644 --- a/test/libc/fmt/palandprintf_test.c +++ b/test/libc/fmt/palandprintf_test.c @@ -679,9 +679,12 @@ TEST(snprintf, formatStringLiteral) { } BENCH(palandprintf, bench) { - EZBENCH2("snprintf %x", donothing, Format("%x", VEIL("r", INT_MIN))); - EZBENCH2("snprintf %d", donothing, Format("%d", VEIL("r", INT_MIN))); - EZBENCH2("snprintf %s", donothing, Format("%s", VEIL("r", "hi (╯°□°)╯"))); + EZBENCH2("23 %x", donothing, Format("%x", VEIL("r", 23))); + EZBENCH2("23 %d", donothing, Format("%d", VEIL("r", 23))); + EZBENCH2("INT_MIN %x", donothing, Format("%x", VEIL("r", INT_MIN))); + EZBENCH2("INT_MIN %d", donothing, Format("%d", VEIL("r", INT_MIN))); + EZBENCH2("ascii %s", donothing, Format("%s", VEIL("r", "hiuhcreohucreo"))); + EZBENCH2("utf8 %s", donothing, Format("%s", VEIL("r", "hi (╯°□°)╯"))); EZBENCH2("snprintf %hs", donothing, Format("%hs", VEIL("r", u"hi (╯°□°)╯"))); EZBENCH2("snprintf %ls", donothing, Format("%ls", VEIL("r", L"hi (╯°□°)╯"))); EZBENCH2("int64toarray", donothing, int64toarray_radix10(-3, buffer)); diff --git a/test/libc/fmt/sprintf_s.inc b/test/libc/fmt/sprintf_s.inc index f815d0e3d..280803f86 100644 --- a/test/libc/fmt/sprintf_s.inc +++ b/test/libc/fmt/sprintf_s.inc @@ -72,6 +72,22 @@ TEST(SUITE(snprintf), testStringPrecision_showsTrueBinary) { EXPECT_STREQ("\3\4\0", Format("%.*s", 3, "\3\4\0")); } -TEST(SUITE(snprintf), testPrecision_usesCodepointCount) { - EXPECT_STREQ("ちゃぶ台返し", Format("%.*s", 6, "ちゃぶ台返し")); +TEST(SUITE(snprintf), testPrecision_usesByteCount) { + EXPECT_STREQ("ちゃ", Format("%.*s", 6, "ちゃぶ台返し")); +} + +TEST(SUITE(snprintf), testReprChar16) { + EXPECT_STREQ("u'♥'", Format("%`'hc", u'♥')); +} + +TEST(SUITE(snprintf), testReprChar32) { + EXPECT_STREQ("L'♥'", Format("%`'Lc", L'♥')); +} + +TEST(SUITE(snprintf), testReprUtf8) { + EXPECT_STREQ("\"♥\"", Format("%`'s", u8"♥")); +} + +TEST(SUITE(snprintf), testReprUtf8Precision_countsBytes) { + EXPECT_STREQ("\"♥\"", Format("%`'.*s", 3, u8"♥")); } diff --git a/test/libc/sock/inet_ntop_test.c b/test/libc/sock/inet_ntop_test.c index fdf7df2ed..973ab1cac 100644 --- a/test/libc/sock/inet_ntop_test.c +++ b/test/libc/sock/inet_ntop_test.c @@ -54,10 +54,3 @@ TEST(inet_ntop, testNoSpace) { ASSERT_STREQ("", buf); tfree(buf); } - -TEST(inet_ntop, testSqueeze) { - char *buf = memcpy(tmalloc(8), "hi", 3); - uint8_t localhost[4] = {0, 0, 0, 0}; - ASSERT_STREQ("0.0.0.0", inet_ntop(AF_INET, localhost, buf, 8)); - tfree(buf); -} diff --git a/test/net/http/parsehttpdatetime_test.c b/test/net/http/parsehttpdatetime_test.c new file mode 100644 index 000000000..437a84fb6 --- /dev/null +++ b/test/net/http/parsehttpdatetime_test.c @@ -0,0 +1,48 @@ +/*-*- 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 │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/str/str.h" +#include "libc/testlib/testlib.h" +#include "libc/time/struct/tm.h" +#include "libc/time/time.h" +#include "net/http/http.h" + +#define PARSE(s) ParseHttpDateTime(s, sizeof(s) - 1) + +TEST(ParseHttpDateTime, testRoundTrip) { + int64_t t; + struct tm tm; + char b[30], *s = "Mon, 05 Oct 2020 20:23:16 GMT"; + t = ParseHttpDateTime(s, strlen(s)); + EXPECT_EQ(1601929396, t); + gmtime_r(&t, &tm); + FormatHttpDateTime(b, &tm); + EXPECT_STREQ(s, b); +} + +TEST(ParseHttpDateTime, test64bit) { + int64_t t; + struct tm tm; + char b[30], *s = "Tue, 05 Oct 2100 20:23:16 GMT"; + t = ParseHttpDateTime(s, strlen(s)); + EXPECT_EQ(4126450996, t); + gmtime_r(&t, &tm); + FormatHttpDateTime(b, &tm); + EXPECT_STREQ(s, b); +} diff --git a/test/net/http/parsehttprange_test.c b/test/net/http/parsehttprange_test.c new file mode 100644 index 000000000..a97362791 --- /dev/null +++ b/test/net/http/parsehttprange_test.c @@ -0,0 +1,102 @@ +/*-*- 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 │ +│ │ +│ This program is free software; you can redistribute it and/or modify │ +│ it under the terms of the GNU General Public License as published by │ +│ the Free Software Foundation; version 2 of the License. │ +│ │ +│ This program is distributed in the hope that it will be useful, but │ +│ WITHOUT ANY WARRANTY; without even the implied warranty of │ +│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ +│ General Public License for more details. │ +│ │ +│ You should have received a copy of the GNU General Public License │ +│ along with this program; if not, write to the Free Software │ +│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ +│ 02110-1301 USA │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/str/str.h" +#include "libc/testlib/testlib.h" +#include "net/http/http.h" + +TEST(ParseHttpRange, testEmptyHack) { + long start, length; + const char *s = "bytes=-0"; + EXPECT_TRUE(ParseHttpRange(s, strlen(s), 100, &start, &length)); + EXPECT_EQ(100, start); + EXPECT_EQ(0, length); +} + +TEST(ParseHttpRange, testEmptyRange_isntEmpty) { + long start, length; + const char *s = "bytes=0-0"; + EXPECT_TRUE(ParseHttpRange(s, strlen(s), 100, &start, &length)); + EXPECT_EQ(0, start); + EXPECT_EQ(1, length); +} + +TEST(ParseHttpRange, testInclusiveIndexing) { + long start, length; + const char *s = "bytes=0-10"; + EXPECT_TRUE(ParseHttpRange(s, strlen(s), 100, &start, &length)); + EXPECT_EQ(0, start); + EXPECT_EQ(11, length); +} + +TEST(ParseHttpRange, testOffset) { + long start, length; + const char *s = "bytes=1-10"; + EXPECT_TRUE(ParseHttpRange(s, strlen(s), 100, &start, &length)); + EXPECT_EQ(1, start); + EXPECT_EQ(10, length); +} + +TEST(ParseHttpRange, testToEnd) { + long start, length; + const char *s = "bytes=40"; + EXPECT_TRUE(ParseHttpRange(s, strlen(s), 100, &start, &length)); + EXPECT_EQ(40, start); + EXPECT_EQ(60, length); +} + +TEST(ParseHttpRange, testFromEnd) { + long start, length; + const char *s = "bytes=-40"; + EXPECT_TRUE(ParseHttpRange(s, strlen(s), 100, &start, &length)); + EXPECT_EQ(60, start); + EXPECT_EQ(40, length); +} + +TEST(ParseHttpRange, testOutOfRange) { + long start, length; + const char *s = "bytes=0-100"; + EXPECT_FALSE(ParseHttpRange(s, strlen(s), 100, &start, &length)); + EXPECT_EQ(0, start); + EXPECT_EQ(101, length); +} + +TEST(ParseHttpRange, testInvalidRange) { + long start, length; + const char *s = "bytes=10-0"; + EXPECT_FALSE(ParseHttpRange(s, strlen(s), 100, &start, &length)); + EXPECT_EQ(0, start); + EXPECT_EQ(0, length); +} + +TEST(ParseHttpRange, testOverflow_duringIntepretation_doesntSetRanges) { + long start, length; + const char *s = "bytes=99-9223372036854775808"; + EXPECT_FALSE(ParseHttpRange(s, strlen(s), 100, &start, &length)); + EXPECT_EQ(0, start); + EXPECT_EQ(0, length); +} + +TEST(ParseHttpRange, testOverflow_duringAddition_setsErrorRange) { + long start, length; + const char *s = "bytes=4611686018427387904-4611686018427387915"; + EXPECT_FALSE(ParseHttpRange(s, strlen(s), 100, &start, &length)); + EXPECT_EQ(4611686018427387904, start); + EXPECT_EQ(12, length); +} diff --git a/test/tool/build/lib/disinst_test.c b/test/tool/build/lib/disinst_test.c index d52018c9d..55eac8e78 100644 --- a/test/tool/build/lib/disinst_test.c +++ b/test/tool/build/lib/disinst_test.c @@ -17,6 +17,10 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/rand/lcg.h" +#include "libc/rand/rand.h" +#include "libc/stdio/stdio.h" +#include "libc/testlib/ezbench.h" #include "libc/testlib/testlib.h" #include "tool/build/lib/dis.h" #include "tool/build/lib/modrm.h" @@ -227,7 +231,7 @@ TEST(DisInst, testOrImmCode16gcc) { xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_REAL); ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, op, sizeof(op))); DisInst(d, b1, DisSpec(d->xedd, b2)); - EXPECT_STREQ("or $0xc00,12(%esp)", b1); + EXPECT_STREQ("orw $0xc00,12(%esp)", b1); } TEST(DisInst, testPause) { @@ -269,3 +273,19 @@ TEST(DisInst, testJmpEq) { DisInst(d, b1, DisSpec(d->xedd, b2)); EXPECT_STREQ("jmp %rax", b1); } + +TEST(DisInst, testMovswSs) { + uint8_t op[] = {0x36, 0xA5}; + xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_REAL); + ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, op, sizeof(op))); + DisInst(d, b1, DisSpec(d->xedd, b2)); + EXPECT_STREQ("movs %ss:(%si),(%di)", b1); +} + +TEST(DisInst, testRealModrm_sibOverlap_showsNoDisplacement) { + uint8_t op[] = {0x8b, 0b00100101}; + xed_decoded_inst_zero_set_mode(d->xedd, XED_MACHINE_MODE_REAL); + ASSERT_EQ(0, xed_instruction_length_decode(d->xedd, op, sizeof(op))); + DisInst(d, b1, DisSpec(d->xedd, b2)); + EXPECT_STREQ("mov (%di),%sp", b1); +} diff --git a/third_party/zlib/zlib.mk b/third_party/zlib/zlib.mk index 9d29c102d..81c6ac40a 100644 --- a/third_party/zlib/zlib.mk +++ b/third_party/zlib/zlib.mk @@ -57,6 +57,12 @@ o/$(MODE)/third_party/zlib/adler32.o: \ -ffunction-sections \ -fdata-sections +ifeq (,$(MODE)) +$(THIRD_PARTY_ZLIB_A_OBJS): \ + OVERRIDE_CFLAGS += \ + -fsanitize=address +endif + THIRD_PARTY_ZLIB_LIBS = $(foreach x,$(THIRD_PARTY_ZLIB_ARTIFACTS),$($(x))) THIRD_PARTY_ZLIB_SRCS = $(foreach x,$(THIRD_PARTY_ZLIB_ARTIFACTS),$($(x)_SRCS)) THIRD_PARTY_ZLIB_HDRS = $(foreach x,$(THIRD_PARTY_ZLIB_ARTIFACTS),$($(x)_HDRS)) diff --git a/tool/build/build.mk b/tool/build/build.mk index f3910ed02..13bbb41c6 100644 --- a/tool/build/build.mk +++ b/tool/build/build.mk @@ -35,6 +35,7 @@ TOOL_BUILD_DIRECTDEPS = \ LIBC_ELF \ LIBC_FMT \ LIBC_LOG \ + LIBC_LOG_ASAN \ LIBC_TINYMATH \ LIBC_MEM \ LIBC_NEXGEN32E \ diff --git a/tool/build/emubin/emubin.mk b/tool/build/emubin/emubin.mk index 5684d4a36..918aeb7a5 100644 --- a/tool/build/emubin/emubin.mk +++ b/tool/build/emubin/emubin.mk @@ -27,6 +27,7 @@ TOOL_BUILD_EMUBIN_CHECKS = \ TOOL_BUILD_EMUBIN_DIRECTDEPS = \ LIBC_STUBS \ + LIBC_NEXGEN32E \ LIBC_TINYMATH TOOL_BUILD_EMUBIN_DEPS := \ @@ -57,10 +58,10 @@ o/dbg/tool/build/emubin/lisp.real.com.dbg: \ $(APE) -@$(APELINK) -o/tiny/tool/build/emubin/lisp.bin.dbg: \ +o/$(MODE)/tool/build/emubin/lisp.bin.dbg: \ $(TOOL_BUILD_EMUBIN_DEPS) \ - o/tiny/tool/build/emubin/lisp.real.o \ - o/tiny/tool/build/emubin/lispstart.o \ + o/$(MODE)/tool/build/emubin/lisp.real.o \ + o/$(MODE)/tool/build/emubin/lispstart.o \ tool/build/emubin/lisp.lds @$(ELFLINK) -z max-page-size=0x10 diff --git a/tool/build/emubin/lisp.real.c b/tool/build/emubin/lisp.c similarity index 62% rename from tool/build/emubin/lisp.real.c rename to tool/build/emubin/lisp.c index 202c31c09..4814f369c 100644 --- a/tool/build/emubin/lisp.real.c +++ b/tool/build/emubin/lisp.c @@ -3,36 +3,35 @@ ╞══════════════════════════════════════════════════════════════════════════════╡ │ Copyright 2020 Justine Alexandra Roberts Tunney │ │ │ -│ This program is free software; you can redistribute it and/or modify │ -│ it under the terms of the GNU General Public License as published by │ -│ the Free Software Foundation; version 2 of the License. │ +│ 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. │ │ │ -│ This program is distributed in the hope that it will be useful, but │ -│ WITHOUT ANY WARRANTY; without even the implied warranty of │ -│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │ -│ General Public License for more details. │ -│ │ -│ You should have received a copy of the GNU General Public License │ -│ along with this program; if not, write to the Free Software │ -│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ -│ 02110-1301 USA │ +│ 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. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #define TRACE 0 #define ERRORS 1 #define LONG long #define WORD short -#define WORDS 2048 +#define WORDS 8192 /*───────────────────────────────────────────────────────────────────────────│─╗ -│ The LISP Challenge § 8086 PC BIOS / x86_64 Linux System Integration ─╬─│┼ +│ The LISP Challenge § Impure x86_64 Linux 8086 PC BIOS System Integration ─╬─│┼ ╚────────────────────────────────────────────────────────────────────────────│*/ -#define ATOM(x) /* a.k.a. !(x&1) */ \ - ({ \ - char IsAtom; \ - asm("test%z1\t$1,%1" : "=@ccz"(IsAtom) : "Qm"((char)x)); \ - IsAtom; \ +#define TYPE(x) /* a.k.a. x&1 */ \ + ({ \ + char IsAtom; \ + asm("test%z1\t$1,%1" : "=@ccnz"(IsAtom) : "Qm"((char)x)); \ + IsAtom; \ }) #define OBJECT(t, v) /* a.k.a. v<<1|t */ \ @@ -57,71 +56,109 @@ c; \ }) -#define REAL_READ(BASE, INDEX, DISP) /* a.k.a. b[i] */ \ - ({ \ - __typeof(*(BASE)) Reg; \ - if (__builtin_constant_p(INDEX) && !(INDEX)) { \ - asm("mov\t%c2(%1),%0" \ - : "=Q"(Reg) \ - : "bDS"(BASE), "i"((DISP) * sizeof(*(BASE)))); \ - } else { \ - asm("mov\t%c3(%1,%2),%0" \ - : "=Q"(Reg) \ - : "b"(BASE), "DS"((long)(INDEX) * sizeof(*(BASE))), \ - "i"((DISP) * sizeof(*(BASE)))); \ - } \ - Reg; \ +#define REAL_READ_(REG, BASE, INDEX, DISP) \ + ({ \ + __typeof(*(BASE)) Reg; \ + if (__builtin_constant_p(INDEX) && !(INDEX)) { \ + asm("mov\t%c2(%1),%0" \ + : REG(Reg) \ + : "bDS"(BASE), "i"((DISP) * sizeof(*(BASE))), \ + "m"(BASE[(INDEX) + (DISP)])); \ + } else { \ + asm("mov\t%c3(%1,%2),%0" \ + : REG(Reg) \ + : "b"(BASE), "DS"((long)(INDEX) * sizeof(*(BASE))), \ + "i"((DISP) * sizeof(*(BASE))), "m"(BASE[(INDEX) + (DISP)])); \ + } \ + Reg; \ }) -#define REAL_READ_ARRAY_FIELD(OBJECT, MEMBER, INDEX, DISP) /* o->m[i] */ \ +/* #ifdef __REAL_MODE__ */ +#define REAL_READ(BASE, INDEX, DISP) /* a.k.a. b[i] */ \ + (sizeof(*(BASE)) == 1 ? REAL_READ_("=Q", BASE, INDEX, DISP) \ + : REAL_READ_("=r", BASE, INDEX, DISP)) +/* #else */ +/* #define REAL_READ(BASE, INDEX, DISP) BASE[INDEX + DISP] */ +/* #endif */ + +#define REAL_READ_ARRAY_FIELD_(REG, OBJECT, MEMBER, INDEX, DISP) \ ({ \ __typeof(*(OBJECT->MEMBER)) Reg; \ if (!(OBJECT)) { \ asm("mov\t%c2(%1),%0" \ - : "=Q"(Reg) \ + : REG(Reg) \ : "bDS"((long)(INDEX) * sizeof(*(OBJECT->MEMBER))), \ "i"(__builtin_offsetof(__typeof(*(OBJECT)), MEMBER) + \ - sizeof(*(OBJECT->MEMBER)) * (DISP))); \ + sizeof(*(OBJECT->MEMBER)) * (DISP)), \ + "m"(OBJECT->MEMBER)); \ } else { \ asm("mov\t%c3(%1,%2),%0" \ - : "=Q"(Reg) \ + : REG(Reg) \ : "b"(OBJECT), "DS"((long)(INDEX) * sizeof(*(OBJECT->MEMBER))), \ "i"(__builtin_offsetof(__typeof(*(OBJECT)), MEMBER) + \ - sizeof(*(OBJECT->MEMBER)) * (DISP))); \ + sizeof(*(OBJECT->MEMBER)) * (DISP)), \ + "m"(OBJECT->MEMBER)); \ } \ Reg; \ }) -#define REAL_WRITE_ARRAY_FIELD(OBJECT, MEMBER, INDEX, DISP, VALUE) \ - do { \ - __typeof(*(OBJECT->MEMBER)) Reg; \ - if (!(OBJECT)) { \ - asm volatile("mov\t%0,%c2(%1)" \ - : /* manual output */ \ - : "Q"((__typeof(*(OBJECT->MEMBER)))(VALUE)), \ - "bDS"((long)(INDEX) * sizeof(*(OBJECT->MEMBER))), \ - "i"(__builtin_offsetof(__typeof(*(OBJECT)), MEMBER) + \ - sizeof(*(OBJECT->MEMBER)) * (DISP)) \ - : "memory"); \ - } else { \ - asm volatile("mov\t%0,%c3(%1,%2)" \ - : /* manual output */ \ - : "Q"((__typeof(*(OBJECT->MEMBER)))(VALUE)), "b"(OBJECT), \ - "DS"((long)(INDEX) * sizeof(*(OBJECT->MEMBER))), \ - "i"(__builtin_offsetof(__typeof(*(OBJECT)), MEMBER) + \ - sizeof(*(OBJECT->MEMBER)) * (DISP)) \ - : "memory"); \ - } \ +/* #ifdef __REAL_MODE__ */ +#define REAL_READ_ARRAY_FIELD(OBJECT, MEMBER, INDEX, DISP) /* o->m[i] */ \ + (sizeof(*(OBJECT->MEMBER)) == 1 \ + ? REAL_READ_ARRAY_FIELD_("=Q", OBJECT, MEMBER, INDEX, DISP) \ + : REAL_READ_ARRAY_FIELD_("=r", OBJECT, MEMBER, INDEX, DISP)) +/* #else */ +/* #define REAL_READ_ARRAY_FIELD(o, m, i, d) o->m[i + d] */ +/* #endif */ + +#define REAL_WRITE_ARRAY_FIELD_(REG, OBJECT, MEMBER, INDEX, DISP, VALUE) \ + do { \ + if (!(OBJECT)) { \ + asm("mov\t%1,%c3(%2)" \ + : "=m"(OBJECT->MEMBER) \ + : REG((__typeof(*(OBJECT->MEMBER)))(VALUE)), \ + "bDS"((long)(INDEX) * sizeof(*(OBJECT->MEMBER))), \ + "i"(__builtin_offsetof(__typeof(*(OBJECT)), MEMBER) + \ + sizeof(*(OBJECT->MEMBER)) * (DISP))); \ + } else { \ + asm("mov\t%1,%c4(%2,%3)" \ + : "=m"(OBJECT->MEMBER) \ + : REG((__typeof(*(OBJECT->MEMBER)))(VALUE)), "b"(OBJECT), \ + "DS"((long)(INDEX) * sizeof(*(OBJECT->MEMBER))), \ + "i"(__builtin_offsetof(__typeof(*(OBJECT)), MEMBER) + \ + sizeof(*(OBJECT->MEMBER)) * (DISP))); \ + } \ } while (0) -static void *SetMemory(void *di, int al, unsigned long cx) { +/* #ifdef __REAL_MODE__ */ +#define REAL_WRITE_ARRAY_FIELD(OBJECT, MEMBER, INDEX, DISP, VALUE) \ + do { \ + __typeof(*(OBJECT->MEMBER)) Reg; \ + switch (sizeof(*(OBJECT->MEMBER))) { \ + case 1: \ + REAL_WRITE_ARRAY_FIELD_("Q", OBJECT, MEMBER, INDEX, DISP, VALUE); \ + break; \ + default: \ + REAL_WRITE_ARRAY_FIELD_("ri", OBJECT, MEMBER, INDEX, DISP, VALUE); \ + break; \ + } \ + } while (0) +/* #else */ +/* #define REAL_WRITE_ARRAY_FIELD(o, m, i, d, v) o->m[i + d] = v */ +/* #endif */ + +long jb[8]; +int setjmp(void *) __attribute__((__returns_twice__)); +int longjmp(void *, int) __attribute__((__noreturn__)); + +static inline void *SetMemory(void *di, int al, unsigned long cx) { asm("rep stosb" : "=D"(di), "=c"(cx), "=m"(*(char(*)[cx])di) : "0"(di), "1"(cx), "a"(al)); return di; } -static void *CopyMemory(void *di, void *si, unsigned long cx) { +static inline void *CopyMemory(void *di, void *si, unsigned long cx) { asm("rep movsb" : "=D"(di), "=S"(si), "=c"(cx), "=m"(*(char(*)[cx])di) : "0"(di), "1"(si), "2"(cx)); @@ -147,14 +184,13 @@ static void RawMode(void) { #endif } -__attribute__((__noinline__)) static int PrintChar(LONG c) { +__attribute__((__noinline__)) static void PrintChar(LONG c) { #ifdef __REAL_MODE__ asm volatile("mov\t$0x0E,%%ah\n\t" "int\t$0x10" : /* no outputs */ : "a"(c), "b"(7) : "memory"); - return 0; #else static short buf; int rc; @@ -163,7 +199,6 @@ __attribute__((__noinline__)) static int PrintChar(LONG c) { : "=a"(rc) : "0"(1), "D"(1), "S"(&buf), "d"(1) : "rcx", "r11", "memory"); - return rc; #endif } @@ -177,6 +212,7 @@ static void PrintString(char *s) { } static int XlatChar(LONG c) { + if (c == 0x7F) return '\b'; if (c >= 'a') { asm volatile("" ::: "memory"); if (c <= 'z') c -= 'a' - 'A'; @@ -185,19 +221,16 @@ static int XlatChar(LONG c) { } static int EchoChar(LONG c) { - if (c == '\b' || c == 0x7F) { - PrintString("\b \b"); - return '\b'; - } else { + if (c != '\b') { PrintChar(c); if (c == '\r') { PrintChar('\n'); } - return c; } + return c; } -static noinline int ReadChar(void) { +__attribute__((__noinline__)) static noinline int ReadChar(void) { int c; #ifdef __REAL_MODE__ asm volatile("int\t$0x16" : "=a"(c) : "0"(0) : "memory"); @@ -213,13 +246,13 @@ static noinline int ReadChar(void) { } /*───────────────────────────────────────────────────────────────────────────│─╗ -│ The LISP Challenge § LISP Machine ─╬─│┼ +│ The LISP Challenge § Pure Original LISP Machine ─╬─│┼ ╚────────────────────────────────────────────────────────────────────────────│*/ -#define TYPE_ATOM 0 -#define TYPE_CONS 1 +#define ATOM 0 +#define CONS 1 -#define ATOM_NIL 0 +#define NIL 0 #define ATOM_T 8 #define ATOM_QUOTE 12 #define ATOM_ATOM 24 @@ -234,7 +267,7 @@ static noinline int ReadChar(void) { #define ATOM_DEFUN 110 #define Quote(x) List(ATOM_QUOTE, x) -#define List(x, y) Cons(x, Cons(y, ATOM_NIL)) +#define List(x, y) Cons(x, Cons(y, NIL)) #define Caar(x) Car(Car(x)) // ((A B C D) (E F G) H I) → A #define Cdar(x) Cdr(Car(x)) // ((A B C D) (E F G) H I) → (B C D) #define Cadar(x) Cadr(Car(x)) // ((A B C D) (E F G) H I) → B @@ -242,22 +275,40 @@ static noinline int ReadChar(void) { #define Cadr(x) Car(Cdr(x)) // ((A B C D) (E F G) H I) → (E F G) #define Caddr(x) Cadr(Cdr(x)) // ((A B C D) (E F G) H I) → H -#define BOOL(x) ((x) ? ATOM_T : ATOM_NIL) +#define BOOL(x) ((x) ? ATOM_T : NIL) #define VALUE(x) ((x) >> 1) +#define PTR(i) ((i) << 1 | CONS) + +#define ARRAYLEN(A) \ + ((sizeof(A) / sizeof(*(A))) / ((unsigned)!(sizeof(A) % sizeof(*(A))))) struct Lisp { - WORD memory[WORDS]; + WORD mem[WORDS]; unsigned char syntax[256]; - unsigned char look; - char token[16]; + WORD look; WORD globals; - int index; + WORD index; + char token[128]; char str[WORDS]; }; -const char kSymbols[] aligned(1) = "\ +_Static_assert(sizeof(struct Lisp) <= 0x7c00 - 0x600, + "LISP Machine too large for real mode"); + +_Alignas(char) const char kSymbols[] = "\ NIL\0T\0QUOTE\0ATOM\0EQ\0COND\0CAR\0CDR\0CONS\0LABEL\0LAMBDA\0SET\0DEFUN\0"; +_Alignas(WORD) const WORD kGlobals[] = { + [0] = PTR(2), // ((T . T) (NIL . NIL)) + [1] = PTR(4), // + [2] = ATOM_T, // (T . T) + [3] = ATOM_T, // + [4] = PTR(6), // ((NIL . NIL)) + [5] = NIL, // + [6] = NIL, // (NIL . NIL) + [7] = NIL, // +}; + #ifdef __REAL_MODE__ static struct Lisp *const q; #else @@ -281,28 +332,39 @@ static void SetupSyntax(void) { q->syntax['\''] = '\''; } -forceinline WORD Car(LONG x) { - return REAL_READ_ARRAY_FIELD(q, memory, VALUE(x), 0); +static inline WORD Car(LONG x) { + return REAL_READ_ARRAY_FIELD(q, mem, VALUE(x), 0); } -forceinline WORD Cdr(LONG x) { - return REAL_READ_ARRAY_FIELD(q, memory, VALUE(x), 1); +static inline WORD Cdr(LONG x) { + return REAL_READ_ARRAY_FIELD(q, mem, VALUE(x), 1); } static WORD Cons(WORD car, WORD cdr) { - int i, c; +#if TRACE + PrintString("CONS->"); + Print(car); + PrintString(" "); + Print(cdr); +#endif + int i, cell; i = q->index; - REAL_WRITE_ARRAY_FIELD(q, memory, i, 0, car); - REAL_WRITE_ARRAY_FIELD(q, memory, i, 1, cdr); - q->index += 2; - c = OBJECT(TYPE_CONS, i); - return c; + REAL_WRITE_ARRAY_FIELD(q, mem, i, 0, car); + REAL_WRITE_ARRAY_FIELD(q, mem, i, 1, cdr); + q->index = i + 2; + cell = OBJECT(CONS, i); +#if TRACE + PrintString("CONS<-"); + Print(cell); +#endif + return cell; } static void SetupBuiltins(void) { CopyMemory(q->str, kSymbols, sizeof(kSymbols)); - q->globals = - Cons(Cons(ATOM_NIL, ATOM_NIL), Cons(Cons(ATOM_T, ATOM_T), ATOM_NIL)); + CopyMemory(q->mem, kGlobals, sizeof(kGlobals)); + q->index = ARRAYLEN(kGlobals); + q->globals = PTR(0); } static char *StpCpy(char *d, char *s) { @@ -314,7 +376,7 @@ static char *StpCpy(char *d, char *s) { return d; } -static WORD Intern(char *s) { +WORD Intern(char *s) { int j, cx; char c, *z, *t; z = q->str; @@ -325,7 +387,7 @@ static WORD Intern(char *s) { break; } if (!c) { - return OBJECT(TYPE_ATOM, z - q->str - j - 1); + return OBJECT(ATOM, z - q->str - j - 1); } c = LODS(z); } @@ -334,11 +396,11 @@ static WORD Intern(char *s) { } --z; StpCpy(z, s); - return OBJECT(TYPE_ATOM, SUB((long)z, q->str)); + return OBJECT(ATOM, SUB((long)z, q->str)); } forceinline unsigned char XlatSyntax(unsigned char b) { - return REAL_READ_ARRAY_FIELD(q, syntax, b, 0); /* a.k.a. q->syntax[b] */ + return REAL_READ_ARRAY_FIELD(q, syntax, b, 0); } static void GetToken(void) { @@ -357,7 +419,8 @@ static void GetToken(void) { if (b != '\b') { STOS(t, b); } else if (t > q->token) { - --t; + PrintString("\b \b"); + if (t > q->token) --t; } b = ReadChar(); } @@ -387,7 +450,7 @@ static WORD GetList(void) { case '\'': return AddList(GetQuote()); case ')': - return ATOM_NIL; + return NIL; case '.': return ConsumeObject(); } @@ -422,7 +485,7 @@ static void PrintList(LONG x) { PrintChar('('); PrintObject(Car(x)); while ((x = Cdr(x))) { - if (!ATOM(x)) { + if (TYPE(x) == CONS) { PrintChar(' '); PrintObject(Car(x)); } else { @@ -434,7 +497,7 @@ static void PrintList(LONG x) { } static void PrintObject(LONG x) { - if (ATOM(x)) { + if (TYPE(x) == ATOM) { PrintAtom(x); } else { PrintList(x); @@ -447,8 +510,7 @@ static void Print(LONG i) { } __attribute__((__noreturn__)) static void Reset(void) { - asm volatile("jmp\tRepl"); - __builtin_unreachable(); + longjmp(jb, 1); } __attribute__((__noreturn__)) static void OnUndefined(LONG x) { @@ -472,7 +534,7 @@ __attribute__((__noreturn__)) static void OnArity(void) { ╚────────────────────────────────────────────────────────────────────────────│*/ static WORD Atom(LONG x) { - return BOOL(ATOM(x)); + return BOOL(TYPE(x) == ATOM); } static WORD Null(LONG x) { @@ -480,24 +542,32 @@ static WORD Null(LONG x) { } static WORD Eq(LONG x, LONG y) { - return BOOL(x == y); /* undefiled if !ATOM(x)||!ATOM(y) */ + return BOOL(x == y); /* undefined if !Atom(x)||!Atom(y) */ } static WORD Assoc(LONG x, LONG y) { - for (;;) { - if (!y) OnUndefined(x); - if (Eq(Caar(y), x)) break; - y = Cdr(y); - } - return Cdar(y); + if (Null(y)) OnUndefined(x); + if (Eq(Caar(y), x)) return Cdar(y); + return Assoc(x, Cdr(y)); } static WORD Append(LONG x, LONG y) { - if (x) { - return Cons(Car(x), Append(Cdr(x), y)); +#if TRACE + PrintString("APPEND->"); + Print(x); + PrintString(" "); + Print(y); +#endif + if (!Null(x)) { + x = Cons(Car(x), Append(Cdr(x), y)); } else { - return y; + x = y; } +#if TRACE + PrintString("APPEND<-"); + Print(x); +#endif + return x; } /** @@ -506,26 +576,41 @@ static WORD Append(LONG x, LONG y) { * @note recoded to make lists in dot notation * @note it's zip() basically */ -static WORD Pair(LONG x, LONG y) { - if (!x && !y) { - return ATOM_NIL; - } else if (!ATOM(x) && !ATOM(y)) { - return Cons(Cons(Car(x), Car(y)), Pair(Cdr(x), Cdr(y))); +static WORD Pair_(LONG x, LONG y) { + if (Null(x) && Null(y)) { + return NIL; + } else if (TYPE(x) == CONS && TYPE(y) == CONS) { + return Cons(Cons(Car(x), Car(y)), Pair_(Cdr(x), Cdr(y))); } else { OnArity(); } } +static WORD Pair(LONG x, LONG y) { +#if TRACE + PrintString("PAIR->"); + Print(x); + PrintString(" "); + Print(y); +#endif + x = Pair_(x, y); +#if TRACE + PrintString("PAIR<-"); + Print(x); +#endif + return x; +} + static WORD Appq(long m) { if (m) { return Cons(List(ATOM_QUOTE, Car(m)), Appq(Cdr(m))); } else { - return ATOM_NIL; + return NIL; } } static WORD Apply(long f, long a) { - return Eval(Cons(f, Appq(a)), ATOM_NIL); + return Eval(Cons(f, Appq(a)), NIL); } static WORD Evcon(LONG c, LONG a) { @@ -536,14 +621,29 @@ static WORD Evcon(LONG c, LONG a) { } } -static WORD Evlis(LONG m, LONG a) { +static WORD Evlis_(LONG m, LONG a) { if (m) { - return Cons(Eval(Car(m), a), Evlis(Cdr(m), a)); + return Cons(Eval(Car(m), a), Evlis_(Cdr(m), a)); } else { - return ATOM_NIL; + return NIL; } } +static WORD Evlis(LONG m, LONG a) { +#if TRACE + PrintString("EVLIS->"); + Print(m); + PrintString(" "); + Print(a); +#endif + m = Evlis_(m, a); +#if TRACE + PrintString("EVLIS<-"); + Print(m); +#endif + return m; +} + static WORD Set(LONG e) { WORD name, value; name = Car(e); @@ -563,9 +663,9 @@ static WORD Defun(LONG e) { } static WORD Evaluate(LONG e, LONG a) { - if (ATOM(e)) { + if (Atom(e)) { return Assoc(e, a); - } else if (ATOM(Car(e))) { + } else if (Atom(Car(e))) { switch (Car(e)) { case ATOM_QUOTE: return Cadr(e); @@ -601,6 +701,8 @@ static WORD Eval(LONG e, LONG a) { #if TRACE PrintString("->"); Print(e); + PrintString(" "); + Print(a); #endif e = Evaluate(e, a); #if TRACE @@ -615,6 +717,7 @@ static WORD Eval(LONG e, LONG a) { ╚────────────────────────────────────────────────────────────────────────────│*/ void Repl(void) { + setjmp(jb); for (;;) { PrintString("* "); Print(Eval(Read(), q->globals)); @@ -622,7 +725,7 @@ void Repl(void) { } int main(int argc, char *argv[]) { - RawMode(); + /* RawMode(); */ SetupSyntax(); SetupBuiltins(); PrintString("THE LISP CHALLENGE V1\r\n" diff --git a/tool/build/emubin/lisp.lds b/tool/build/emubin/lisp.lds index 8b0b7f300..55ab58819 100644 --- a/tool/build/emubin/lisp.lds +++ b/tool/build/emubin/lisp.lds @@ -29,6 +29,7 @@ SECTIONS { . = 0x1fe; SHORT(0xaa55); *(.text .text.*) + _etext = .; . = ALIGN(512); } @@ -43,10 +44,11 @@ SECTIONS { } } -syntax = 0x600+2048*2; -look = 0x600+2048*2+256; -token = 0x600+2048*2+256+1; -globals = 0x600+2048*2+256+1+16; -index = 0x600+2048*2+256+1+16+2; -str = 0x600+2048*2+256+1+16+2+4; -v_sectors = SIZEOF(.text) / 512; +boot = 0x7c00; +q.syntax = 8192*2; +q.look = 8192*2+256; +q.globals = 8192*2+256+2; +q.index = 8192*2+256+2+2; +q.token = 8192*2+256+2+2+2; +q.str = 8192*2+256+2+2+2+128; +v_sectors = SIZEOF(.text) / 512; diff --git a/tool/build/emubin/lispstart.S b/tool/build/emubin/lispstart.S index 2d9e88a96..34e33fbff 100644 --- a/tool/build/emubin/lispstart.S +++ b/tool/build/emubin/lispstart.S @@ -21,8 +21,8 @@ .code16 .section .start,"ax",@progbits _start: jmp 1f -1: ljmp $0x600>>4,$2f -2: push %cs +1: ljmp $0x600>>4,$_begin +_begin: push %cs pop %ds push %cs pop %es @@ -50,3 +50,45 @@ _start: jmp 1f .globl _start .globl v_sectors .globl main + +setjmp: mov %sp,%ax + stosw # sp + xchg %ax,%si + movsw %ss:(%si),(%di) # ip + mov %bp,%ax + stosw # bp + ret + .type setjmp,@function + .size setjmp,.-setjmp + .globl setjmp + +longjmp: + mov (%di),%sp + mov 2(%di),%dx + mov 4(%di),%bp + pop %ax + mov %si,%ax + jmp *%dx + .type longjmp,@function + .size longjmp,.-longjmp + .globl longjmp + + .globl q.syntax + .type q.syntax,@function + .globl q.look + .type q.look,@function + .globl q.globals + .type q.globals,@function + .globl q.index + .type q.index,@function + .globl q.token + .type q.token,@function + .globl q.str + .type q.str,@function + + .globl boot + .type boot,@function + .globl bss + .type bss,@function + .globl rodata + .type rodata,@function diff --git a/tool/build/emulator.c b/tool/build/emulator.c index 4572150b7..b9ae03b16 100644 --- a/tool/build/emulator.c +++ b/tool/build/emulator.c @@ -56,6 +56,7 @@ #include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/termios.h" #include "libc/sysv/errfuns.h" +#include "libc/time/time.h" #include "libc/unicode/unicode.h" #include "libc/x/x.h" #include "third_party/dtoa/dtoa.h" @@ -91,8 +92,7 @@ DESCRIPTION\n\ \n\ FLAGS\n\ \n\ - -h\n\ - -? help\n\ + -h help\n\ -v verbosity\n\ -r real mode\n\ -s statistics\n\ @@ -137,8 +137,9 @@ COMPLETENESS\n\ #define QUIT 0x200 #define EXIT 0x400 -#define CTRL(C) ((C) ^ 0100) -#define ALT(C) (('\e' << 010) | (C)) +#define CTRL(C) ((C) ^ 0100) +#define ALT(C) (('\e' << 010) | (C)) +#define SEX(x, b) ((x) | ((x) & (1ull << (b)) ? -(1ull << (b)) : 0)) struct Panels { union { @@ -215,16 +216,6 @@ static struct Breakpoints breakpoints; static void SetupDraw(void); static void Redraw(void); -static uint64_t SignExtend(uint64_t x, uint8_t b) { - uint64_t s; - s = 1; - b -= 1; - b &= 63; - s <<= b; - if (x & s) x |= ~(s - 1); - return x; -} - static char *FormatDouble(char *b, double x) { return g_fmt(b, x); } @@ -234,16 +225,15 @@ static void SetCarry(bool cf) { } static bool IsCall(void) { - return m->xedd->op.map == XED_ILD_MAP0 && - (m->xedd->op.opcode == 0xE8 || - (m->xedd->op.opcode == 0xFF && m->xedd->op.reg == 2)); + return (m->xedd->op.dispatch == 0x0E8 || + (m->xedd->op.dispatch == 0x0FF && m->xedd->op.reg == 2)); } static bool IsLongBranch(void) { - return m->xedd && m->xedd->op.map == XED_ILD_MAP0 && - (m->xedd->op.opcode == 0xEA || m->xedd->op.opcode == 0x9A || - (m->xedd->op.opcode == 0xFF && m->xedd->op.reg == 3) || - (m->xedd->op.opcode == 0xFF && m->xedd->op.reg == 5)); + return m->mode != XED_MODE_LONG && + (m->xedd->op.dispatch == 0x0EA || m->xedd->op.dispatch == 0x09A || + (m->xedd->op.opcode == 0x0FF && m->xedd->op.reg == 3) || + (m->xedd->op.opcode == 0x0FF && m->xedd->op.reg == 5)); } static bool IsDebugBreak(void) { @@ -370,13 +360,16 @@ static void GetTtySize(void) { static void TuiRejuvinate(void) { GetTtySize(); ttyhidecursor(STDOUT_FILENO); - ttyraw(0); + ttyraw(kTtySigs); xsigaction(SIGBUS, OnBusted, SA_NODEFER, 0, NULL); } static void OnCtrlC(void) { - LOGF("OnCtrlC"); - action |= INT; + if (tuimode) { + action |= INT; + } else { + HaltMachine(m, kMachineExit); + } } static void OnQ(void) { @@ -404,7 +397,7 @@ static void OnCont(void) { static void TuiCleanup(void) { sigaction(SIGWINCH, oldsig + 0, NULL); sigaction(SIGCONT, oldsig + 2, NULL); - ttyraw((enum TtyRawFlags)(-1u)); + ttyraw(-1); ttyshowcursor(STDOUT_FILENO); CHECK_NE(-1, close(ttyfd)); tuimode = false; @@ -447,6 +440,7 @@ void TuiSetup(void) { CHECK_NE(-1, (ttyfd = open("/dev/tty", O_RDWR))); xsigaction(SIGWINCH, OnWinch, 0, 0, oldsig + 0); xsigaction(SIGCONT, OnCont, SA_RESTART, 0, oldsig + 2); + xsigaction(SIGINT, OnCtrlC, 0 /* SA_NODEFER */, 0, oldsig + 3); memcpy(&m[1], &m[0], sizeof(m[0])); TuiRejuvinate(); } @@ -660,12 +654,19 @@ static void SetupDraw(void) { pan.display.right - pan.display.left); } +static long Disassemble(void) { + long lines, current; + lines = pan.disassembly.bottom - pan.disassembly.top * 2; + CHECK_NE(-1, Dis(dis, m, GetIp(), m->ip, lines)); + current = DisFind(dis, GetIp()); + CHECK_NE(-1, current); + return current; +} + static long GetDisIndex(void) { long i; - if ((i = DisFind(dis, GetIp())) == -1 || IsLongBranch()) { - Dis(dis, m, GetIp(), m->ip, - pan.disassembly.bottom - pan.disassembly.top * 2); - CHECK_NE(-1, (i = DisFind(dis, GetIp()))); + if ((i = DisFind(dis, GetIp())) == -1) { + i = Disassemble(); } while (i + 1 < dis->ops.i && !dis->ops.p[i].size) ++i; return i; @@ -864,7 +865,7 @@ static void DrawXmm(struct Panel *p, long i, long r) { if (0 && ssewidth == 1 && (040 <= ival && ival < 0200 - 1)) { sprintf(buf, "%`'c", ival); } else { - int64toarray_radix10(SignExtend(ival, ssewidth * 8), buf); + int64toarray_radix10(SEX(ival, ssewidth * 8), buf); } } else { uint64toarray_fixed16(ival, buf, ssewidth * 8); @@ -977,7 +978,7 @@ static void DrawBreakpoints(struct Panel *p) { name = sym != -1 ? dis->syms.stab + dis->syms.p[sym].name : "UNKNOWN"; s = buf; s += sprintf(s, "%p ", addr); - CHECK_LT(Demangle(s, name), buf + ARRAYLEN(buf)); + CHECK_LT(Demangle(s, name, DIS_MAX_SYMBOL_LENGTH), buf + ARRAYLEN(buf)); AppendPanel(p, line, buf); if (sym != -1 && addr != dis->syms.p[sym].addr) { snprintf(buf, sizeof(buf), "+%#lx", addr - dis->syms.p[sym].addr); @@ -1015,7 +1016,7 @@ static void DrawTrace(struct Panel *p) { name = sym != -1 ? dis->syms.stab + dis->syms.p[sym].name : "UNKNOWN"; s = line; s += sprintf(s, "%p %p ", Read64(m->ss) + bp, rp); - s = Demangle(s, name); + s = Demangle(s, name, DIS_MAX_SYMBOL_LENGTH); AppendPanel(p, i, line); if (sym != -1 && rp != dis->syms.p[sym].addr) { snprintf(line, sizeof(line), "+%#lx", rp - dis->syms.p[sym].addr); @@ -1417,7 +1418,6 @@ static void OnVidyaServiceTeletypeOutput(void) { char buf[12]; n = FormatCga(m->bx[0], buf); n += tpencode(buf + n, 6, VidyaServiceXlatTeletype(m->ax[0]), false); - LOGF("teletype output %`'.*s", n, buf); MachinePtyWrite(pty, buf, n); } @@ -1520,6 +1520,7 @@ static void OnInt15h(void) { } static bool OnHalt(int interrupt) { + LOGF("OnHalt(%d)", interrupt); ReactiveDraw(); switch (interrupt) { case 1: @@ -1577,15 +1578,24 @@ static void OnPageDown(void) { static void OnUpArrow(void) { if (action & CONTINUE) { - speed = MIN(0x40000000, MAX(1, speed) << 1); + if (speed >= -1) { + speed = MIN(0x40000000, MAX(1, speed) << 1); // 1..40mips skip + } else { + speed >>= 1; + } } else { --opstart; } + LOGF("speed %d", speed); } static void OnDownArrow(void) { if (action & CONTINUE) { - speed >>= 1; + if (speed > 0) { + speed >>= 1; + } else { + speed = MAX(-(5 * 1000), MIN(-10, speed) << 1); // 10ms..5s delay + } } else { ++opstart; } @@ -1897,6 +1907,9 @@ static void Tui(void) { action &= ~WINCHED; } interactive = ++tick > speed; + if (interactive && speed < 0) { + dsleep(.001L * -speed); + } if (!(action & CONTINUE) || interactive) { tick = 0; GetDisIndex(); @@ -1907,7 +1920,8 @@ static void Tui(void) { LOGF("TUI FAILURE"); PrintMessageBox(ttyfd, systemfailure, tyn, txn); ReadKeyboard(); - } else if (!IsExecuting() || (interactive && HasPendingKeyboard())) { + } else if (!IsExecuting() || ((interactive || !(action & CONTINUE)) && + !(action & INT) && HasPendingKeyboard())) { ReadKeyboard(); } if (action & INT) { @@ -1961,6 +1975,9 @@ static void Tui(void) { } if (!IsDebugBreak()) { ExecuteInstruction(m); + if (IsLongBranch()) { + Disassemble(); + } } else { m->ip += m->xedd->length; action &= ~NEXT; @@ -1997,7 +2014,7 @@ static void Tui(void) { static void GetOpts(int argc, char *argv[]) { int opt; stpcpy(stpcpy(stpcpy(logpath, kTmpPath), basename(argv[0])), ".log"); - while ((opt = getopt(argc, argv, "?hvtrRsb:HL:")) != -1) { + while ((opt = getopt(argc, argv, "hvtrRsb:HL:")) != -1) { switch (opt) { case 't': tuimode = true; @@ -2007,6 +2024,7 @@ static void GetOpts(int argc, char *argv[]) { break; case 'r': m->mode = XED_MACHINE_MODE_REAL; + g_disisprog_disable = true; break; case 's': printstats = true; @@ -2024,7 +2042,6 @@ static void GetOpts(int argc, char *argv[]) { strcpy(logpath, optarg); break; case 'h': - case '?': PrintUsage(EXIT_SUCCESS, stdout); default: PrintUsage(EX_USAGE, stderr); diff --git a/tool/build/lib/buffer.c b/tool/build/lib/buffer.c index 444e6c4bc..4a894e8fe 100644 --- a/tool/build/lib/buffer.c +++ b/tool/build/lib/buffer.c @@ -64,13 +64,12 @@ ssize_t WriteBuffer(struct Buffer *b, int fd) { p = b->p; n = b->i; do { - TryAgain: if ((rc = write(fd, p, n)) != -1) { wrote = rc; p += wrote; n -= wrote; } else if (errno == EINTR) { - goto TryAgain; + break; } else { return -1; } diff --git a/tool/build/lib/demangle.c b/tool/build/lib/demangle.c index e9274883c..1ada7671f 100644 --- a/tool/build/lib/demangle.c +++ b/tool/build/lib/demangle.c @@ -17,10 +17,14 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/assert.h" +#include "libc/bits/safemacros.h" #include "libc/calls/calls.h" #include "libc/calls/hefty/spawn.h" +#include "libc/calls/struct/iovec.h" #include "libc/macros.h" #include "libc/runtime/runtime.h" +#include "libc/sock/sock.h" #include "libc/str/str.h" #include "tool/build/lib/demangle.h" @@ -37,12 +41,14 @@ void CloseCxxFilt(void) { void SpawnCxxFilt(void) { int pid; + const char *cxxfilt; char path[PATH_MAX]; - if (commandv("c++filt", path)) { + cxxfilt = firstnonnull(getenv("CXXFILT"), "c++filt"); + if (commandv(cxxfilt, path)) { g_cxxfilt.fds[0] = -1; g_cxxfilt.fds[1] = -1; g_cxxfilt.fds[2] = 2; - if ((pid = spawnve(0, g_cxxfilt.fds, path, (char *const[]){"c++filt", NULL}, + if ((pid = spawnve(0, g_cxxfilt.fds, path, (char *const[]){cxxfilt, NULL}, environ)) != -1) { atexit(CloseCxxFilt); } @@ -52,30 +58,50 @@ void SpawnCxxFilt(void) { g_cxxfilt.pid = pid; } -char *DemangleCxxFilt(char *p, const char *symbol) { - int n; - char buf[512]; - bool iscomplicated; +char *CopySymbol(char *p, size_t pn, const char *s, size_t sn) { + size_t extra; + bool showdots, iscomplicated; + assert(pn >= 1 + 3 + 1 + 1); + iscomplicated = memchr(s, ' ', sn) || memchr(s, '(', sn); + extra = 1; + if (iscomplicated) extra += 2; + if (sn + extra > pn) { + sn = pn - extra - 3; + showdots = true; + } else { + showdots = false; + } + if (iscomplicated) *p++ = '"'; + p = mempcpy(p, s, sn); + if (showdots) p = stpcpy(p, "..."); + if (iscomplicated) *p++ = '"'; + *p = '\0'; + return p; +} + +char *DemangleCxxFilt(char *p, size_t pn, const char *s, size_t sn) { + ssize_t rc; + size_t got; + struct iovec iov[2]; + static char buf[PAGESIZE]; if (!g_cxxfilt.pid) SpawnCxxFilt(); if (g_cxxfilt.pid == -1) return NULL; - if ((n = strlen(symbol)) >= ARRAYLEN(buf)) return NULL; - memcpy(buf, symbol, n); - buf[n] = '\n'; - write(g_cxxfilt.fds[0], buf, n + 1); - n = read(g_cxxfilt.fds[1], buf, ARRAYLEN(buf)); - if (n > 1 && buf[n - 1] == '\n') { - if (buf[n - 2] == '\r') --n; - --n; - iscomplicated = memchr(buf, ' ', n) || memchr(buf, '(', n); - if (iscomplicated) *p++ = '"'; - p = mempcpy(p, buf, n); - if (iscomplicated) *p++ = '"'; - *p = '\0'; - return p; - } else { - CloseCxxFilt(); - return NULL; + buf[0] = '\n'; + iov[0].iov_base = s; + iov[0].iov_len = sn; + iov[1].iov_base = buf; + iov[1].iov_len = 1; + writev(g_cxxfilt.fds[0], iov, ARRAYLEN(iov)); + if ((rc = read(g_cxxfilt.fds[1], buf, sizeof(buf))) != -1) { + got = rc; + if (got >= 2 && buf[got - 1] == '\n') { + if (buf[got - 2] == '\r') --got; + --got; + return CopySymbol(p, pn, buf, got); + } } + CloseCxxFilt(); + return NULL; } /** @@ -85,10 +111,12 @@ char *DemangleCxxFilt(char *p, const char *symbol) { * x86_64 disassembler. That's just for the GNU encoding scheme. So * what we'll do, is just offload this work to the c++filt program. */ -char *Demangle(char *p, const char *symbol) { +char *Demangle(char *p, const char *symbol, size_t n) { char *r; + size_t sn; + sn = strlen(symbol); if (startswith(symbol, "_Z")) { - if ((r = DemangleCxxFilt(p, symbol))) return r; + if ((r = DemangleCxxFilt(p, n, symbol, sn))) return r; } - return stpcpy(p, symbol); + return CopySymbol(p, n, symbol, sn); } diff --git a/tool/build/lib/demangle.h b/tool/build/lib/demangle.h index 209cd6c40..f19b0a0aa 100644 --- a/tool/build/lib/demangle.h +++ b/tool/build/lib/demangle.h @@ -3,7 +3,7 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ -char *Demangle(char *, const char *); +char *Demangle(char *, const char *, size_t); COSMOPOLITAN_C_END_ #endif /* !(__ASSEMBLER__ + __LINKER__ + 0) */ diff --git a/tool/build/lib/dis.c b/tool/build/lib/dis.c index e8cb8d683..02a532130 100644 --- a/tool/build/lib/dis.c +++ b/tool/build/lib/dis.c @@ -150,7 +150,7 @@ static char *DisLineData(struct Dis *d, char *p, const uint8_t *b, size_t n) { static char *DisLabel(struct Dis *d, char *p, const char *name) { p = DisColumn(DisAddr(d, p), p, ADDRLEN); p = HighStart(p, g_high.label); - p = Demangle(p, name); + p = Demangle(p, name, DIS_MAX_SYMBOL_LENGTH); p = HighEnd(p); *p++ = ':'; *p = '\0'; diff --git a/tool/build/lib/dis.h b/tool/build/lib/dis.h index 116a5cb55..6325bf734 100644 --- a/tool/build/lib/dis.h +++ b/tool/build/lib/dis.h @@ -6,6 +6,8 @@ #if !(__ASSEMBLER__ + __LINKER__ + 0) COSMOPOLITAN_C_START_ +#define DIS_MAX_SYMBOL_LENGTH 32 + struct Dis { struct DisOps { size_t i, n; @@ -50,6 +52,8 @@ struct Dis { char buf[512]; }; +extern bool g_disisprog_disable; + long Dis(struct Dis *, struct Machine *, uint64_t, uint64_t, int); long DisFind(struct Dis *, int64_t); void DisFree(struct Dis *); diff --git a/tool/build/lib/disarg.c b/tool/build/lib/disarg.c index e0867606a..77479789e 100644 --- a/tool/build/lib/disarg.c +++ b/tool/build/lib/disarg.c @@ -143,21 +143,30 @@ static char *DisInt(char *p, int64_t x) { return p; } -static char *DisSym(struct Dis *d, char *p, int64_t addr, int64_t ip) { - long sym; +static char *DisSymImpl(struct Dis *d, char *p, int64_t x, long sym) { int64_t addend; const char *name; - if ((sym = DisFindSym(d, ip)) != -1 && d->syms.p[sym].name) { - addend = ip - d->syms.p[sym].addr; - name = d->syms.stab + d->syms.p[sym].name; - p = Demangle(p, name); - if (addend) { - *p++ = '+'; - p = DisInt(p, addend); - } - return p; + addend = x - d->syms.p[sym].addr; + name = d->syms.stab + d->syms.p[sym].name; + p = Demangle(p, name, DIS_MAX_SYMBOL_LENGTH); + if (addend) { + *p++ = '+'; + p = DisInt(p, addend); + } + return p; +} + +static char *DisSym(struct Dis *d, char *p, int64_t x1, int64_t x2, + bool isrelative) { + long sym; + if ((sym = DisFindSym(d, x2)) != -1 && d->syms.p[sym].name && + (d->syms.p[sym].isabs ^ isrelative)) { + return DisSymImpl(d, p, x2, sym); + } else if ((sym = DisFindSym(d, x1)) != -1 && d->syms.p[sym].name && + (d->syms.p[sym].isabs ^ isrelative)) { + return DisSymImpl(d, p, x1, sym); } else { - return DisInt(p, addr); + return DisInt(p, x1); } } @@ -165,7 +174,7 @@ static char *DisSymLiteral(struct Dis *d, uint32_t rde, char *p, uint64_t addr, uint64_t ip) { *p++ = '$'; p = HighStart(p, g_high.literal); - p = DisSym(d, p, addr, ip); + p = DisSym(d, p, addr, addr, false); p = HighEnd(p); return p; } @@ -196,27 +205,36 @@ static bool IsRealModrmAbsolute(uint32_t rde) { return Eamode(rde) == XED_MODE_REAL && ModrmRm(rde) == 6 && !ModrmMod(rde); } -static char *DisM(struct Dis *d, uint32_t rde, char *p) { +static char *DisDisp(struct Dis *d, uint32_t rde, char *p) { + bool rela; int64_t disp; - const char *base, *index, *scale; - p = DisSego(d, rde, p); - base = index = scale = NULL; if (ModrmMod(rde) == 0b01 || ModrmMod(rde) == 0b10 || IsRipRelative(rde) || IsRealModrmAbsolute(rde) || - (ModrmMod(rde) == 0b00 && ModrmRm(rde) == 0b100 && - SibBase(d->xedd) == 0b101)) { + (Eamode(rde) != XED_MODE_REAL && ModrmMod(rde) == 0b00 && + ModrmRm(rde) == 0b100 && SibBase(d->xedd) == 0b101)) { disp = d->xedd->op.disp; if (IsRipRelative(rde)) { if (Mode(rde) == XED_MODE_LONG) { disp = RipRelative(d, disp); + rela = true; } else { disp = Unrelative(rde, disp); + rela = false; } } else if (IsRealModrmAbsolute(rde)) { disp = Unrelative(rde, disp); + rela = false; + } else { + rela = true; } - p = DisSym(d, p, disp, disp); + p = DisSym(d, p, disp, disp, rela); } + return p; +} + +static char *DisBis(struct Dis *d, uint32_t rde, char *p) { + const char *base, *index, *scale; + base = index = scale = NULL; if (Eamode(rde) != XED_MODE_REAL) { if (!SibExists(rde)) { DCHECK(!d->xedd->op.has_sib); @@ -290,6 +308,13 @@ static char *DisM(struct Dis *d, uint32_t rde, char *p) { return p; } +static char *DisM(struct Dis *d, uint32_t rde, char *p) { + p = DisSego(d, rde, p); + p = DisDisp(d, rde, p); + p = DisBis(d, rde, p); + return p; +} + static char *DisRegMem(struct Dis *d, uint32_t rde, char *p, char *f(struct Dis *, uint32_t, char *)) { if (IsModrmRegister(rde)) { @@ -452,11 +477,11 @@ static char *DisJb(struct Dis *d, uint32_t rde, char *p) { static char *DisJvds(struct Dis *d, uint32_t rde, char *p) { return DisSym(d, p, RipRelative(d, d->xedd->op.disp), - RipRelative(d, d->xedd->op.disp) - Read64(d->m->cs)); + RipRelative(d, d->xedd->op.disp) - Read64(d->m->cs), true); } static char *DisAbs(struct Dis *d, uint32_t rde, char *p) { - return DisSym(d, p, d->xedd->op.disp, d->xedd->op.disp); + return DisSym(d, p, d->xedd->op.disp, d->xedd->op.disp, false); } static char *DisSw(struct Dis *d, uint32_t rde, char *p) { @@ -477,12 +502,12 @@ static char *DisY(struct Dis *d, uint32_t rde, char *p) { } static char *DisX(struct Dis *d, uint32_t rde, char *p) { - DisSego(d, rde, p); + p = DisSego(d, rde, p); return DisSpecialAddr(d, rde, p, 6); // ds:si } static char *DisBBb(struct Dis *d, uint32_t rde, char *p) { - DisSego(d, rde, p); + p = DisSego(d, rde, p); return DisSpecialAddr(d, rde, p, 3); // ds:bx } diff --git a/tool/build/lib/diself.c b/tool/build/lib/diself.c index afb51bd13..6cc87cbd5 100644 --- a/tool/build/lib/diself.c +++ b/tool/build/lib/diself.c @@ -22,9 +22,13 @@ #include "libc/elf/elf.h" #include "libc/elf/struct/sym.h" #include "libc/log/check.h" +#include "libc/log/log.h" #include "libc/macros.h" +#include "libc/str/str.h" #include "tool/build/lib/dis.h" +bool g_disisprog_disable; + static int DisSymCompare(const struct DisSym *a, const struct DisSym *b) { if (a->addr != b->addr) { if (a->addr < b->addr) return -1; @@ -72,6 +76,8 @@ static void DisLoadElfSyms(struct Dis *d, struct Elf *elf) { if (!st[i].st_name) continue; if (!(0 <= st[i].st_name && st[i].st_name < stablen)) continue; if (ELF64_ST_TYPE(st[i].st_info) == STT_SECTION) continue; + if (ELF64_ST_TYPE(st[i].st_info) == STT_FILE) continue; + if (startswith(d->syms.stab + st[i].st_name, "v_")) continue; isabs = st[i].st_shndx == SHN_ABS; isweak = ELF64_ST_BIND(st[i].st_info) == STB_WEAK; islocal = ELF64_ST_BIND(st[i].st_info) == STB_LOCAL; @@ -85,6 +91,7 @@ static void DisLoadElfSyms(struct Dis *d, struct Elf *elf) { t.addr = st[i].st_value; t.rank = -islocal + -isweak + -isabs + isprotected + isobject + isfunc; t.iscode = DisIsText(d, st[i].st_value) ? !isobject : isfunc; + t.isabs = isabs; APPEND(&d->syms.p, &d->syms.i, &d->syms.n, &t); } } @@ -93,6 +100,7 @@ static void DisLoadElfSyms(struct Dis *d, struct Elf *elf) { bool DisIsProg(struct Dis *d, int64_t addr) { long i; + if (g_disisprog_disable) return true; for (i = 0; i < d->loads.i; ++i) { if (addr >= d->loads.p[i].addr && addr < d->loads.p[i].addr + d->loads.p[i].size) { diff --git a/tool/build/lib/disinst.c b/tool/build/lib/disinst.c index b52e96785..dfb7e47de 100644 --- a/tool/build/lib/disinst.c +++ b/tool/build/lib/disinst.c @@ -20,6 +20,7 @@ #include "libc/log/check.h" #include "libc/nexgen32e/tinystrcmp.h" #include "libc/str/str.h" +#include "third_party/zlib/zlib.h" #include "tool/build/lib/dis.h" #include "tool/build/lib/high.h" #include "tool/build/lib/modrm.h" @@ -163,7 +164,7 @@ static char *DisName(struct Dis *d, char *bp, const char *name, } else if (wantsuffix || (ambiguous && !startswith(name, "f") && !startswith(name, "set"))) { if (Osz(rde)) { - if (Mode(rde) != XED_MODE_REAL) { + if (ambiguous || Mode(rde) != XED_MODE_REAL) { *p++ = 'w'; } } else if (Rexw(rde)) { @@ -187,8 +188,8 @@ static char *DisName(struct Dis *d, char *bp, const char *name, */ char *DisInst(struct Dis *d, char *p, const char *spec) { long i, n; - char sbuf[300]; - char args[4][300]; + char sbuf[64]; + char args[4][256]; char *s, *name, *state; bool hasarg, hasmodrm, hasregister, hasmemory; CHECK_EQ(0, (int)d->xedd->op.error); diff --git a/tool/build/lib/modrm.h b/tool/build/lib/modrm.h index 43410e4ce..95fca4351 100644 --- a/tool/build/lib/modrm.h +++ b/tool/build/lib/modrm.h @@ -48,7 +48,7 @@ COSMOPOLITAN_C_START_ #define SibHasIndex(x) (SibIndex(x) != 4 || Rexx(x)) #define SibHasBase(x, r) (SibBase(x) != 5 || ModrmMod(r)) #define SibIsAbsolute(x, r) (!SibHasBase(x, r) && !SibHasIndex(x)) -#define IsRipRelative(x) (ModrmRm(x) == 5 && !ModrmMod(x)) +#define IsRipRelative(x) (Eamode(x) && ModrmRm(x) == 5 && !ModrmMod(x)) struct AddrSeg { int64_t addr; diff --git a/tool/build/lib/pty.c b/tool/build/lib/pty.c index 7ef15182a..31e81594f 100644 --- a/tool/build/lib/pty.c +++ b/tool/build/lib/pty.c @@ -474,11 +474,8 @@ ssize_t MachinePtyWrite(struct MachinePty *pty, const void *data, size_t n) { break; case kMachinePtyUtf8: if (ThomPikeCont(p[i])) { - pty->u8 <<= 6; - pty->u8 |= p[i] & 0b00111111; - if (--pty->n8) { - break; - } + pty->u8 = ThomPikeMerge(pty->u8, p[i]); + if (--pty->n8) break; } SetMachinePtyCell(pty, pty->u8); pty->state = kMachinePtyAscii; diff --git a/tool/build/zipobj.c b/tool/build/zipobj.c index b22d54599..c12282ff0 100644 --- a/tool/build/zipobj.c +++ b/tool/build/zipobj.c @@ -130,7 +130,7 @@ void GetDosLocalTime(int64_t utcunixts, uint16_t *out_time, struct tm tm; CHECK_NOTNULL(localtime_r(&utcunixts, &tm)); *out_time = DOS_TIME(tm.tm_hour, tm.tm_min, tm.tm_sec); - *out_date = DOS_DATE(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday); + *out_date = DOS_DATE(tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday + 1); } static unsigned char *EmitZipLfileHdr(unsigned char *op, const void *name, diff --git a/tool/decode/lib/asmcodegen.c b/tool/decode/lib/asmcodegen.c index aaa08a237..631f2a6cd 100644 --- a/tool/decode/lib/asmcodegen.c +++ b/tool/decode/lib/asmcodegen.c @@ -43,6 +43,10 @@ nodiscard char *tabpad(const char *s, unsigned width) { need = width > l ? (roundup(width, 8) - l - 1) / 8 + 1 : 0; p = memcpy(malloc(l + need + 1), s, l); for (i = 0; i < need; ++i) p[l + i] = '\t'; + if (!need) { + p[l] = ' '; + ++need; + } p[l + need] = '\0'; return p; } diff --git a/tool/decode/lib/zipnames.c b/tool/decode/lib/zipnames.c index 533eea2ce..58b14ffaa 100644 --- a/tool/decode/lib/zipnames.c +++ b/tool/decode/lib/zipnames.c @@ -30,6 +30,7 @@ const struct IdName kZipCompressionNames[] = { const struct IdName kZipExtraNames[] = { {kZipExtraZip64, "kZipExtraZip64"}, {kZipExtraNtfs, "kZipExtraNtfs"}, + {kZipExtraExtendedTimestamp, "kZipExtraExtendedTimestamp"}, {0, 0}, }; diff --git a/tool/decode/zip.c b/tool/decode/zip.c index 55694acdc..d86e68189 100644 --- a/tool/decode/zip.c +++ b/tool/decode/zip.c @@ -94,12 +94,12 @@ void showcompressmethod(uint16_t compressmethod) { void showextrantfs(uint8_t *ntfs) { struct timespec mtime, atime, ctime; - mtime = filetimetotimespec( + mtime = FileTimeToTimeSpec( (struct NtFileTime){read32le(ntfs + 8), read32le(ntfs + 12)}); - atime = filetimetotimespec( + atime = FileTimeToTimeSpec( (struct NtFileTime){read32le(ntfs + 16), read32le(ntfs + 20)}); - ctime = filetimetotimespec( - (struct NtFileTime){read32le(ntfs + 24), read32le(ntfs + 30)}); + ctime = FileTimeToTimeSpec( + (struct NtFileTime){read32le(ntfs + 24), read32le(ntfs + 28)}); show(".long", gc(xasprintf("%d", read32le(ntfs))), "ntfs reserved"); show(".short", gc(xasprintf("0x%04x", read16le(ntfs + 4))), "ntfs attribute tag value #1"); @@ -114,11 +114,47 @@ void showextrantfs(uint8_t *ntfs) { gc(xasprintf("%s (%s)", "ntfs creation time", gc(xiso8601(&ctime))))); } -void showextra(uint8_t *extra) { +void ShowExtendedTimestamp(uint8_t *p, size_t n, bool islocal) { + int flag; + int64_t x; + struct timespec ts; + flag = *p++; + show(".byte", gc(xasprintf("0b%03hhb", flag)), "fields present in local"); + if (!islocal) { + show(".quad", gc(xasprintf("%u", READ32LE(p))), + gc(xasprintf("%s (%s)", "last modified", gc(xiso8601(&ts))))); + } else { + if (flag & 1) { + ts = (struct timespec){READ32LE(p)}; + show(".quad", gc(xasprintf("%u", READ32LE(p))), + gc(xasprintf("%s (%s)", "last modified", gc(xiso8601(&ts))))); + p += 4; + } + flag >>= 1; + if (flag & 1) { + ts = (struct timespec){READ32LE(p)}; + show(".quad", gc(xasprintf("%u", READ32LE(p))), + gc(xasprintf("%s (%s)", "access time", gc(xiso8601(&ts))))); + p += 4; + } + flag >>= 1; + if (flag & 1) { + ts = (struct timespec){READ32LE(p)}; + show(".quad", gc(xasprintf("%u", READ32LE(p))), + gc(xasprintf("%s (%s)", "creation time", gc(xiso8601(&ts))))); + } + } +} + +void showextra(uint8_t *extra, bool islocal) { switch (ZIP_EXTRA_HEADERID(extra)) { case kZipExtraNtfs: showextrantfs(ZIP_EXTRA_CONTENT(extra)); break; + case kZipExtraExtendedTimestamp: + ShowExtendedTimestamp(ZIP_EXTRA_CONTENT(extra), + ZIP_EXTRA_CONTENTSIZE(extra), islocal); + break; case kZipExtraZip64: /* TODO */ default: @@ -140,7 +176,7 @@ void showexternalattributes(uint8_t *cf) { } } -void showextras(uint8_t *extras, uint16_t extrassize) { +void showextras(uint8_t *extras, uint16_t extrassize, bool islocal) { int i; bool first; uint8_t *p, *pe; @@ -159,7 +195,7 @@ void showextras(uint8_t *extras, uint16_t extrassize) { first = false; printf("%d:", (i + 1) * 10); } - showextra(p); + showextra(p, islocal); printf("%d:", (i + 2) * 10); } } @@ -201,7 +237,7 @@ void showlocalfileheader(uint8_t *lf, uint16_t idx) { gc(strndup(ZIP_LFILE_NAME(lf), ZIP_LFILE_NAMESIZE(lf)))), "name"); printf("1:"); - showextras(ZIP_LFILE_EXTRA(lf), ZIP_LFILE_EXTRASIZE(lf)); + showextras(ZIP_LFILE_EXTRA(lf), ZIP_LFILE_EXTRASIZE(lf), true); printf("2:"); disassemblehex(ZIP_LFILE_CONTENT(lf), ZIP_LFILE_COMPRESSEDSIZE(lf), stdout); printf("3:\n"); @@ -252,7 +288,7 @@ void showcentralfileheader(uint8_t *cf) { gc(strndup(ZIP_CFILE_NAME(cf), ZIP_CFILE_NAMESIZE(cf)))), "name"); printf("1:"); - showextras(ZIP_CFILE_EXTRA(cf), ZIP_CFILE_EXTRASIZE(cf)); + showextras(ZIP_CFILE_EXTRA(cf), ZIP_CFILE_EXTRASIZE(cf), false); printf("2:"); disassemblehex(ZIP_CFILE_COMMENT(cf), ZIP_CFILE_COMMENTSIZE(cf), stdout); printf("3:\n"); diff --git a/tool/net/net.mk b/tool/net/net.mk index 6c5a531dc..ae1be8777 100644 --- a/tool/net/net.mk +++ b/tool/net/net.mk @@ -7,59 +7,77 @@ TOOL_NET_FILES := $(wildcard tool/net/*) TOOL_NET_SRCS = $(filter %.c,$(TOOL_NET_FILES)) TOOL_NET_HDRS = $(filter %.h,$(TOOL_NET_FILES)) -TOOL_NET_OBJS = \ - $(TOOL_NET_SRCS:%=o/$(MODE)/%.zip.o) \ +TOOL_NET_OBJS = \ + $(TOOL_NET_SRCS:%=o/$(MODE)/%.zip.o) \ $(TOOL_NET_SRCS:%.c=o/$(MODE)/%.o) -TOOL_NET_COMS = \ +TOOL_NET_COMS = \ $(TOOL_NET_SRCS:%.c=o/$(MODE)/%.com) -TOOL_NET_BINS = \ - $(TOOL_NET_COMS) \ +TOOL_NET_BINS = \ + $(TOOL_NET_COMS) \ $(TOOL_NET_COMS:%=%.dbg) -TOOL_NET_DIRECTDEPS = \ - LIBC_CALLS \ - LIBC_CONV \ - LIBC_DNS \ - LIBC_FMT \ - LIBC_LOG \ - LIBC_NEXGEN32E \ - LIBC_RUNTIME \ - LIBC_SOCK \ - LIBC_STDIO \ - LIBC_STR \ - LIBC_STUBS \ - LIBC_SYSV \ - LIBC_TIME \ - LIBC_UNICODE \ - LIBC_X \ - NET_HTTP \ - THIRD_PARTY_GETOPT \ +TOOL_NET_DIRECTDEPS = \ + LIBC_ALG \ + LIBC_BITS \ + LIBC_CALLS \ + LIBC_CONV \ + LIBC_DNS \ + LIBC_FMT \ + LIBC_LOG \ + LIBC_LOG_ASAN \ + LIBC_MEM \ + LIBC_NEXGEN32E \ + LIBC_RUNTIME \ + LIBC_SOCK \ + LIBC_STDIO \ + LIBC_STR \ + LIBC_STUBS \ + LIBC_SYSV \ + LIBC_SYSV_CALLS \ + LIBC_TIME \ + LIBC_UNICODE \ + LIBC_X \ + NET_HTTP \ + THIRD_PARTY_GETOPT \ + THIRD_PARTY_ZLIB \ TOOL_DECODE_LIB -TOOL_NET_DEPS := \ +TOOL_NET_DEPS := \ $(call uniq,$(foreach x,$(TOOL_NET_DIRECTDEPS),$($(x)))) -o/$(MODE)/tool/net/net.pkg: \ - $(TOOL_NET_OBJS) \ +o/$(MODE)/tool/net/net.pkg: \ + $(TOOL_NET_OBJS) \ $(foreach x,$(TOOL_NET_DIRECTDEPS),$($(x)_A).pkg) -o/$(MODE)/tool/net/%.com.dbg: \ - $(TOOL_NET_DEPS) \ - o/$(MODE)/tool/net/%.o \ - o/$(MODE)/tool/net/net.pkg \ - $(CRT) \ +o/$(MODE)/tool/net/%.com.dbg: \ + $(TOOL_NET_DEPS) \ + o/$(MODE)/tool/net/%.o \ + o/$(MODE)/tool/net/net.pkg \ + $(CRT) \ + $(APE) + @$(APELINK) + +o/$(MODE)/tool/net/redbean.com.dbg: \ + $(TOOL_NET_DEPS) \ + o/$(MODE)/tool/net/redbean.o \ + o/$(MODE)/tool/net/redbean.ico.zip.o \ + o/$(MODE)/tool/net/redbean.png.zip.o \ + o/$(MODE)/tool/net/redbean.css.zip.o \ + o/$(MODE)/tool/net/redbean.html.zip.o \ + o/$(MODE)/tool/net/net.pkg \ + $(CRT) \ $(APE) @$(APELINK) ifeq (,$(MODE)) -$(TOOL_NET_A_OBJS): \ - OVERRIDE_CFLAGS += \ +$(TOOL_NET_OBJS): \ + OVERRIDE_CFLAGS += \ -fsanitize=address endif .PHONY: o/$(MODE)/tool/net -o/$(MODE)/tool/net: \ - $(TOOL_NET_BINS) \ +o/$(MODE)/tool/net: \ + $(TOOL_NET_BINS) \ $(TOOL_NET_CHECKS) diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 097f69c1a..e01b2fcc3 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -17,7 +17,14 @@ │ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │ │ 02110-1301 USA │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/alg/arraylist2.h" +#include "libc/bits/bits.h" +#include "libc/bits/bswap.h" +#include "libc/bits/safemacros.h" #include "libc/calls/calls.h" +#include "libc/calls/struct/iovec.h" +#include "libc/calls/struct/itimerval.h" +#include "libc/calls/struct/stat.h" #include "libc/calls/weirdtypes.h" #include "libc/conv/conv.h" #include "libc/conv/itoa.h" @@ -25,76 +32,367 @@ #include "libc/fmt/fmt.h" #include "libc/log/check.h" #include "libc/log/log.h" +#include "libc/macros.h" +#include "libc/math.h" +#include "libc/mem/mem.h" +#include "libc/nexgen32e/crc32.h" #include "libc/runtime/gc.h" +#include "libc/runtime/missioncritical.h" #include "libc/runtime/runtime.h" #include "libc/sock/sock.h" #include "libc/stdio/stdio.h" #include "libc/str/str.h" +#include "libc/str/undeflate.h" #include "libc/sysv/consts/af.h" +#include "libc/sysv/consts/auxv.h" +#include "libc/sysv/consts/clock.h" #include "libc/sysv/consts/ex.h" #include "libc/sysv/consts/exit.h" #include "libc/sysv/consts/f.h" #include "libc/sysv/consts/fd.h" #include "libc/sysv/consts/inaddr.h" #include "libc/sysv/consts/ipproto.h" +#include "libc/sysv/consts/itimer.h" +#include "libc/sysv/consts/map.h" +#include "libc/sysv/consts/o.h" +#include "libc/sysv/consts/prot.h" #include "libc/sysv/consts/sa.h" #include "libc/sysv/consts/shut.h" #include "libc/sysv/consts/sig.h" #include "libc/sysv/consts/so.h" #include "libc/sysv/consts/sock.h" #include "libc/sysv/consts/sol.h" +#include "libc/sysv/errfuns.h" #include "libc/time/struct/tm.h" #include "libc/time/time.h" #include "libc/x/x.h" +#include "libc/zip.h" +#include "libc/zipos/zipos.h" #include "net/http/http.h" #include "third_party/getopt/getopt.h" +#include "third_party/zlib/zlib.h" -#define STPCPY(p, s) mempcpy(p, s, strlen(s)) +/* TODO(jart): Implement more lenient message framing */ +/* TODO(jart): Windows IOCP fiber system call support */ -bool daemonize; -bool terminated; -int server, client; -const int yes = true; -char serverdate[128]; -struct HttpRequest req; -uint32_t clientaddrsize; -struct sockaddr_in serveraddr; -struct sockaddr_in clientaddr; -char inbuf[PAGESIZE] aligned(PAGESIZE); -char outbuf[PAGESIZE] aligned(PAGESIZE); -char serveraddrstr[32], clientaddrstr[32]; +#define USAGE \ + " [-hvdsm] [-p PORT]\n\ +\n\ +DESCRIPTION\n\ +\n\ + redbean distributable static web server\n\ +\n\ +FLAGS\n\ +\n\ + -h help\n\ + -v verbosity\n\ + -d daemonize\n\ + -s uniprocess\n\ + -m log messages\n\ + -c INT cache seconds\n\ + -r /X=/Y redirect X to Y\n\ + -l ADDR listen ip [default 0.0.0.0]\n\ + -p PORT listen port [default 8080]\n\ + -L PATH log file location\n\ + -P PATH pid file location\n\ + -U INT daemon set user id\n\ + -G INT daemon set group id\n\ + -B STR changes server header\n\ +\n\ +FEATURES\n\ +\n\ + - HTTP v1.1\n\ + - Content-Encoding\n\ + - Range / Content-Range\n\ + - Last-Modified / If-Modified-Since\n\ +\n\ +USAGE\n\ +\n\ + This executable is also a ZIP file that contains static assets.\n\ +\n\ + unzip -vl redbean.com # shows listing of zip contents\n\ +\n\ + Audio video content should not be compressed in your ZIP files.\n\ + Uncompressed assets enable browsers to send Range HTTP request.\n\ + On the other hand compressed assets are best for gzip encoding.\n\ +\n\ + zip redbean.com index.html # adds file\n\ + zip -0 redbean.com video.mp4 # adds without compression\n\ +\n\ + Each connection uses a point in time snapshot of your ZIP file.\n\ + If your ZIP is deleted then serving continues. If it's replaced\n\ + then issuing SIGUSR1 (or SIGHUP if daemon) will reindex the zip\n\ + for subsequent connections without interrupting active ones. If\n\ + SIGINT or SIGTERM is issued then a graceful shutdown is started\n\ + but if it's issued a second time, active connections are reset.\n\ +\n" -void OnTerminate(void) { - terminated = true; +#define HASH_LOAD_FACTOR /* 1. / */ 4 +#define DEFAULT_PORT 8080 +#define DEFAULT_SERVER "redbean/0.1" +#define DEFAULT_CONTENT_TYPE "application/octet-stream" +#define DEFAULT_PATH "/tool/net/redbean.html" +#define FAVICON "tool/net/redbean.ico" + +#define STPCPY(p, s) mempcpy(p, s, strlen(s)) +#define AppendHeaderName(p, s) STPCPY(STPCPY(p, s), ": ") + +static const struct itimerval kHeartbeat = { + {0, 500000}, + {0, 500000}, +}; + +static const uint8_t kGzipHeader[] = { + 0x1F, // MAGNUM + 0x8B, // MAGNUM + 0x08, // CM: DEFLATE + 0x00, // FLG: NONE + 0x00, // MTIME: NONE + 0x00, // + 0x00, // + 0x00, // + 0x00, // XFL + kZipOsUnix, // OS +}; + +static const struct ContentTypeExtension { + unsigned char ext[8]; + const char *mime; +} kContentTypeExtension[] = { + {"S", "text/plain"}, // + {"bmp", "image/x-ms-bmp"}, // + {"c", "text/plain"}, // + {"cc", "text/plain"}, // + {"css", "text/css"}, // + {"csv", "text/csv"}, // + {"gif", "image/gif"}, // + {"h", "text/plain"}, // + {"html", "text/html"}, // + {"i", "text/plain"}, // + {"ico", "image/vnd.microsoft.icon"}, // + {"jpeg", "image/jpeg"}, // + {"jpg", "image/jpeg"}, // + {"js", "application/javascript"}, // + {"json", "application/json"}, // + {"m4a", "audio/mpeg"}, // + {"mp2", "audio/mpeg"}, // + {"mp3", "audio/mpeg"}, // + {"mp4", "video/mp4"}, // + {"mpg", "video/mpeg"}, // + {"otf", "font/otf"}, // + {"pdf", "application/pdf"}, // + {"png", "image/png"}, // + {"png", "image/png"}, // + {"png", "image/png"}, // + {"png", "image/png"}, // + {"s", "text/plain"}, // + {"svg", "image/svg+xml"}, // + {"tiff", "image/tiff"}, // + {"ttf", "font/ttf"}, // + {"txt", "text/plain"}, // + {"wav", "audio/x-wav"}, // + {"woff", "font/woff"}, // + {"woff2", "font/woff2"}, // + {"xml", "application/xml"}, // + {"zip", "application/zip"}, // +}; + +static struct Redirects { + size_t i, n; + struct Redirect { + const char *path; + size_t pathlen; + const char *dest; + } * p; +} redirects; + +static struct Assets { + uint32_t n; + struct Asset { + uint32_t hash; + uint32_t cf; + int64_t lastmodified; + char *lastmodifiedstr; + } * p; +} assets; + +static bool killed; +static bool notimer; +static bool heartbeat; +static bool daemonize; +static bool terminated; +static bool uniprocess; +static bool legacyhttp; +static bool invalidated; +static bool logmessages; + +static int gmtoff; +static int server; +static int client; +static int daemonuid; +static int daemongid; +static int cacheseconds; +static uint32_t clientaddrsize; + +static void *zmap; +static uint8_t *zbase; +static uint8_t *zcdir; +static size_t zmapsize; +static const char *pidpath; +static const char *logpath; +static int64_t programtime; +static const char *programfile; +static const char *serverheader; + +static long double nowish; +static long double startrequest; +static long double startconnection; +static struct sockaddr_in serveraddr; +static struct sockaddr_in clientaddr; + +static struct HttpRequest req; +static char currentdate[32]; +static char clientaddrstr[32]; +static char serveraddrstr[32]; +static char inbuf[PAGESIZE]; +static char outbuf[PAGESIZE]; + +static void OnHup(void) { + invalidated = true; } -noreturn void ShowUsage(FILE *f, int rc) { - fprintf(f, "%s: %s %s\n", "Usage", program_invocation_name, - "[-?drv] [-l LISTENIP] [-p PORT] [-t TIMEOUTMS]"); +static void OnAlarm(void) { + heartbeat = true; +} + +static void OnTerminate(void) { + if (terminated) { + killed = true; + } else { + terminated = true; + } +} + +static void AddRedirect(const char *arg) { + const char *p; + struct Redirect r; + CHECK_NOTNULL((p = strchr(arg, '='))); + CHECK_GT(p - arg, 0); + r.path = arg; + r.pathlen = p - arg; + r.dest = strdup(p + 1); + APPEND(&redirects.p, &redirects.i, &redirects.n, &r); +} + +static int CompareRedirects(const struct Redirect *a, + const struct Redirect *b) { + return strcmp(a->path, b->path); +} + +static void SortRedirects(void) { + qsort(redirects.p, redirects.i, sizeof(struct Redirect), + (void *)CompareRedirects); +} + +static const char *LookupRedirect(const char *path, size_t n) { + int c, m, l, r, z; + l = 0; + r = redirects.i - 1; + while (l <= r) { + m = (l + r) >> 1; + c = memcmp(redirects.p[m].path, path, MIN(redirects.p[m].pathlen, n)); + if (c < 0) { + l = m + 1; + } else if (c > 0) { + r = m - 1; + } else if (redirects.p[m].pathlen < n) { + l = m + 1; + } else if (redirects.p[m].pathlen > n) { + r = m - 1; + } else { + return redirects.p[m].dest; + } + } + return NULL; +} + +static int CompareInts(const uint64_t x, uint64_t y) { + return x > y ? 1 : x < y ? -1 : 0; +} + +static const char *LookupContentType(uint64_t ext) { + int c, m, l, r; + l = 0; + r = ARRAYLEN(kContentTypeExtension) - 1; + while (l <= r) { + m = (l + r) >> 1; + c = CompareInts(READ64BE(kContentTypeExtension[m].ext), ext); + if (c < 0) { + l = m + 1; + } else if (c > 0) { + r = m - 1; + } else { + return kContentTypeExtension[m].mime; + } + } + return DEFAULT_CONTENT_TYPE; +} + +static const char *GetContentType(const char *path, size_t n) { + size_t i; + uint64_t x; + const char *p; + if ((p = memrchr(path, '.', n))) { + for (x = 0, i = n; i-- > p + 1 - path;) { + x <<= 8; + x |= path[i] & 0xFF; + } + return LookupContentType(bswap_64(x)); + } else { + return DEFAULT_CONTENT_TYPE; + } +} + +static noreturn void PrintUsage(FILE *f, int rc) { + fprintf(f, "SYNOPSIS\n\n %s%s", program_invocation_name, USAGE); exit(rc); } -char *DescribeAddress(char buf[32], const struct sockaddr_in *addr) { - char ip4buf[16]; - sprintf(buf, "%s:%hu", - inet_ntop(addr->sin_family, &addr->sin_addr.s_addr, ip4buf, - sizeof(ip4buf)), - ntohs(addr->sin_port)); - return buf; +static void DescribeAddress(char buf[32], const struct sockaddr_in *addr) { + char *p = buf; + const uint8_t *ip4 = (const uint8_t *)&addr->sin_addr.s_addr; + p += uint64toarray_radix10(ip4[0], p), *p++ = '.'; + p += uint64toarray_radix10(ip4[1], p), *p++ = '.'; + p += uint64toarray_radix10(ip4[2], p), *p++ = '.'; + p += uint64toarray_radix10(ip4[3], p), *p++ = ':'; + p += uint64toarray_radix10(ntohs(addr->sin_port), p); + *p = '\0'; } void GetOpts(int argc, char *argv[]) { int opt; serveraddr.sin_family = AF_INET; - serveraddr.sin_port = htons(8080); + serveraddr.sin_port = htons(DEFAULT_PORT); serveraddr.sin_addr.s_addr = INADDR_ANY; - while ((opt = getopt(argc, argv, "?dvl:p:w:")) != -1) { + while ((opt = getopt(argc, argv, "hduvml:p:w:r:c:L:P:U:G:B:")) != -1) { switch (opt) { + case 'v': + g_loglevel++; + break; case 'd': daemonize = true; break; - case 'v': - g_loglevel++; + case 'u': + uniprocess = true; + break; + case 'm': + logmessages = true; + break; + case 'r': + AddRedirect(optarg); + break; + case 'c': + cacheseconds = atoi(optarg); break; case 'p': CHECK_NE(0xFFFF, (serveraddr.sin_port = htons(parseport(optarg)))); @@ -102,116 +400,791 @@ void GetOpts(int argc, char *argv[]) { case 'l': CHECK_EQ(1, inet_pton(AF_INET, optarg, &serveraddr.sin_addr)); break; - case '?': - ShowUsage(stdout, EXIT_SUCCESS); + case 'B': + serverheader = optarg; + break; + case 'L': + logpath = optarg; + break; + case 'P': + pidpath = optarg; + break; + case 'U': + daemonuid = atoi(optarg); + break; + case 'G': + daemonuid = atoi(optarg); + break; + case 'h': + PrintUsage(stdout, EXIT_SUCCESS); default: - ShowUsage(stderr, EX_USAGE); + PrintUsage(stderr, EX_USAGE); } } + SortRedirects(); + if (logpath) { + CHECK_NOTNULL(freopen(logpath, "a", stderr)); + } } -void GenerateHttpDate(char buf[128]) { - int64_t now; - struct tm tm; - time(&now); - gmtime_r(&now, &tm); - strftime(buf, 128, "%a, %d %b %Y %H:%M:%S GMT", &tm); +static void Daemonize(void) { + char ibuf[21]; + int i, fd, pid; + for (i = 0; i < 128; ++i) close(i); + xsigaction(SIGHUP, OnHup, 0, 0, 0); + CHECK_NE(-1, (pid = fork())); + if (pid > 0) exit(0); + if (pid == -1) return; + CHECK_NE(-1, setsid()); + CHECK_NE(-1, (pid = fork())); + if (pid > 0) _exit(0); + LOGIFNEG1(umask(0)); + if (pidpath) { + CHECK_NE(-1, (fd = open(pidpath, O_CREAT | O_EXCL | O_WRONLY, 0644))); + CHECK_NE(-1, write(fd, ibuf, uint64toarray_radix10(getpid(), ibuf))); + LOGIFNEG1(close(fd)); + } + if (!logpath) logpath = "/dev/null"; + CHECK_NOTNULL(freopen("/dev/null", "r", stdin)); + CHECK_NOTNULL(freopen(logpath, "a", stdout)); + CHECK_NOTNULL(freopen(logpath, "a", stderr)); } -int CompareHeaderValue(int h, const char *s) { +static int CompareHeaderValue(int h, const char *s) { return strncmp(s, inbuf + req.headers[h].a, req.headers[h].b - req.headers[h].a); } -ssize_t EasyWrite(int fd, const void *data, size_t size) { - char *p; +static void KillAll(void) { + CHECK_NE(-1, kill(0, SIGTERM)); +} + +static void WaitAll(void) { + for (;;) { + if (wait(NULL) != -1) continue; + if (errno == ECHILD) break; + if (errno == EINTR) { + if (killed) { + WARNF("%s server killed", serveraddrstr); + KillAll(); + } + continue; + } + FATALF("%s wait error %s", serveraddrstr, strerror(errno)); + } +} + +static size_t GetIovSize(struct iovec *iov, int iovlen) { + int i; + size_t size; + for (size = i = 0; i < iovlen; ++i) { + DCHECK_NOTNULL(iov[i].iov_base); + size += iov[i].iov_len; + } + return size; +} + +static ssize_t WritevAll(int fd, struct iovec *iov, int iovlen) { ssize_t rc; - size_t wrote, n; - p = data; - n = size; + size_t wrote; do { - if ((rc = write(fd, p, n)) != -1) { + TryAgain: + if ((rc = writev(fd, iov, iovlen)) != -1) { wrote = rc; - p += wrote; - n -= wrote; + do { + if (wrote >= iov->iov_len) { + wrote -= iov->iov_len; + ++iov; + --iovlen; + } else { + iov->iov_base = (char *)iov->iov_base + wrote; + iov->iov_len -= wrote; + wrote = 0; + } + } while (wrote); + } else if (errno == EINTR) { + if (killed) return -1; + goto TryAgain; } else { return -1; } - } while (n); + } while (iovlen); return 0; } -void SendResponse(const char *data, size_t size) { - ssize_t rc; - if ((rc = EasyWrite(client, data, size)) == -1) { - LOGF("send error %s %s", clientaddrstr, strerror(errno)); +static uint32_t Hash(const void *data, size_t size) { + uint32_t h; + h = crc32c(0, data, size); + if (!h) h = 1; + return h; +} + +static bool HasHeader(int h) { + return req.headers[h].b > req.headers[h].a; +} + +int64_t GetGmtOffset(void) { + int64_t t; + struct tm tm; + t = nowl(); + localtime_r(&t, &tm); + return tm.tm_gmtoff; +} + +static int64_t LocoTimeToZulu(int64_t x) { + return x - gmtoff; +} + +static int64_t GetLastModifiedZip(uint8_t *cfile) { + uint8_t *p, *pe; + for (p = ZIP_CFILE_EXTRA(cfile), pe = p + ZIP_CFILE_EXTRASIZE(cfile); p < pe; + p += ZIP_EXTRA_SIZE(p)) { + if (ZIP_EXTRA_HEADERID(p) == kZipExtraNtfs) { + return LocoTimeToZulu(READ64LE(ZIP_EXTRA_CONTENT(p) + 8) / + HECTONANOSECONDS - + MODERNITYSECONDS); + } else if (ZIP_EXTRA_HEADERID(p) == kZipExtraExtendedTimestamp) { + return READ32LE(ZIP_EXTRA_CONTENT(p) + 1); + } + } + return LocoTimeToZulu(DosDateTimeToUnix(ZIP_CFILE_LASTMODIFIEDDATE(cfile), + ZIP_CFILE_LASTMODIFIEDTIME(cfile))); +} + +static bool IsCompressed(struct Asset *a) { + return ZIP_CFILE_COMPRESSIONMETHOD(zbase + a->cf) == kZipCompressionDeflate; +} + +static int GetHttpVersion(void) { + return ParseHttpVersion(inbuf + req.version.a, req.version.b - req.version.a); +} + +static bool IsNotModified(struct Asset *a) { + if (!HasHeader(kHttpIfModifiedSince)) return false; + return a->lastmodified >= + ParseHttpDateTime(inbuf + req.headers[kHttpIfModifiedSince].a, + req.headers[kHttpIfModifiedSince].b - + req.headers[kHttpIfModifiedSince].a); +} + +static char *FormatUnixHttpDateTime(char *s, int64_t t) { + struct tm tm; + gmtime_r(&t, &tm); + FormatHttpDateTime(s, &tm); + return s; +} + +static void FreeAssetsIndex(struct Asset *p, size_t n) { + int i; + if (p) { + for (i = 0; i < n; ++i) { + free(p[i].lastmodifiedstr); + } + free(p); } } -void HandleRequest(void) { - int contentlength; - char *p, ibuf[21]; - const char *content = "\ -

Hello World

\r\n\ -"; - CHECK_NE(-1, shutdown(client, SHUT_RD)); - contentlength = strlen(content); - p = outbuf; - p = STPCPY(p, "HTTP/1.1 200 OK\r\n\ -Connection: close\r\n\ -Content-Type: text/html; charset=utf-8\r\n\ -Date: "); - p = stpcpy(p, serverdate); - p = STPCPY(p, "\r\n\ -Content-Length: "); - p = mempcpy(p, ibuf, int64toarray_radix10(contentlength, ibuf)); - p = STPCPY(p, "\r\n\ -\r\n"); - p = mempcpy(p, content, contentlength); - SendResponse(outbuf, p - outbuf); -} - -void ProcessRequest(void) { - size_t got; - ssize_t rc; - clientaddrsize = sizeof(clientaddr); - CHECK_NE(-1, (client = accept4(server, &clientaddr, &clientaddrsize, - SOCK_CLOEXEC))); - VERBOSEF("accepted %s", DescribeAddress(clientaddrstr, &clientaddr)); - if ((rc = read(client, inbuf, sizeof(inbuf))) != -1) { - if ((got = rc)) { - if (ParseHttpRequest(&req, inbuf, got) != -1) { - HandleRequest(); +static bool IndexAssets(const uint8_t *base, const uint8_t *cdir) { + bool ok; + int64_t lm; + struct Asset *p; + uint32_t i, n, m, cf, step, hash; + DCHECK_GE(HASH_LOAD_FACTOR, 2); + n = ZIP_CDIR_RECORDS(cdir); + m = roundup2pow(MAX(1, n) * HASH_LOAD_FACTOR); + p = calloc(m, sizeof(struct Asset)); + ok = ZIP_CDIR_MAGIC(cdir) == kZipCdirHdrMagic; + if (p && ok) { + for (cf = ZIP_CDIR_OFFSET(cdir); n--; cf += ZIP_CFILE_HDRSIZE(base + cf)) { + if (ZIP_CFILE_MAGIC(base + cf) == kZipCfileHdrMagic) { + hash = Hash(ZIP_CFILE_NAME(base + cf), ZIP_CFILE_NAMESIZE(base + cf)); + step = 0; + do { + i = (hash + (step * (step + 1)) >> 1) & (m - 1); + ++step; + } while (p[i].hash); + lm = GetLastModifiedZip(base + cf); + p[i].hash = hash; + p[i].cf = cf; + p[i].lastmodified = lm; + p[i].lastmodifiedstr = FormatUnixHttpDateTime(xmalloc(30), lm); } else { - LOGF("parse error %s %s", DescribeAddress(clientaddrstr, &clientaddr), - strerror(errno)); + WARNF("corrupt zip central directory entry"); + ok = false; + break; } } } else { - LOGF("recv error %s %s", DescribeAddress(clientaddrstr, &clientaddr), - strerror(errno)); + WARNF("corrupt zip central directory"); } - VERBOSEF("closing %s", DescribeAddress(clientaddrstr, &clientaddr)); - LOGIFNEG1(close(client)); + if (ok) { + FreeAssetsIndex(assets.p, assets.n); + assets.p = p; + assets.n = m; + } else { + FreeAssetsIndex(p, m); + } + return ok; +} + +static bool OpenZip(const char *path) { + int fd; + bool ok; + void *map; + struct stat st; + const uint8_t *cdir; + if (zmap) { + LOGIFNEG1(munmap(zmap, zmapsize)); + } + if (!zmap && ZIP_CDIR_MAGIC(__zip_end) == kZipCdirHdrMagic) { + if (IndexAssets(_base, __zip_end)) { + ok = true; + zbase = _base; + zcdir = __zip_end; + } else { + ok = false; + } + } else { + fd = -1; + map = MAP_FAILED; + if ((fd = open(path, O_RDONLY)) != -1 && fstat(fd, &st) != -1 && + st.st_size && + (map = mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0)) && + (cdir = zipfindcentraldir(zmap, zmapsize)) && IndexAssets(map, cdir)) { + ok = true; + zmap = map; + zbase = map; + zcdir = cdir; + map = MAP_FAILED; + zmapsize = st.st_size; + } else { + ok = false; + } + if (map != MAP_FAILED) LOGIFNEG1(munmap(map, st.st_size)); + if (fd != -1) LOGIFNEG1(close(fd)); + } + return ok; +} + +static struct Asset *FindAsset(const char *path, size_t pathlen) { + uint32_t i, step, hash; + if (pathlen && path[0] == '/') ++path, --pathlen; + hash = Hash(path, pathlen); + for (step = 0;; ++step) { + i = (hash + (step * (step + 1)) >> 1) & (assets.n - 1); + if (!assets.p[i].hash) return NULL; + if (hash == assets.p[i].hash && + pathlen == ZIP_CFILE_NAMESIZE(zbase + assets.p[i].cf) && + memcmp(path, ZIP_CFILE_NAME(zbase + assets.p[i].cf), pathlen) == 0) { + return &assets.p[i]; + } + } +} + +static struct Asset *FindFile(const char *path, size_t pathlen) { + char *p, *buf; + struct Asset *asset; + if ((asset = FindAsset(path, pathlen))) return asset; + if (pathlen == 12 && memcmp(path, "/favicon.ico", 12) == 0) { + return FindAsset(FAVICON, strlen(FAVICON)); + } else { + return NULL; + } +} + +static void *AddRange(char *content, long start, long length) { + intptr_t mend, mstart; + if (!__builtin_add_overflow((intptr_t)content, start, &mstart) || + !__builtin_add_overflow(mstart, length, &mend) || + ((intptr_t)zbase <= mstart && mstart <= (intptr_t)zbase + zmapsize) || + ((intptr_t)zbase <= mend && mend <= (intptr_t)zbase + zmapsize)) { + return (void *)mstart; + } else { + abort(); + } +} + +static bool IsConnectionClose(void) { + int n; + char *p; + p = inbuf + req.headers[kHttpConnection].a; + n = req.headers[kHttpConnection].b - req.headers[kHttpConnection].a; + return n == 5 && memcmp(p, "close", 5) == 0; +} + +static char *AppendCrlf(char *p) { + return STPCPY(p, "\r\n"); +} + +#define AppendStatus(p, c, s) AppendStatus(p, c, s, sizeof(s) - 1) +static char *(AppendStatus)(char *p, int c, const char *s, size_t n) { + if (legacyhttp) { + p = STPCPY(p, "HTTP/1.0 "); + } else { + p = STPCPY(p, "HTTP/1.1 "); + } + p += uint64toarray_radix10(c, p); + *p++ = ' '; + p = mempcpy(p, s, n); + return AppendCrlf(p); +} + +static void UpdateCurrentDate(long double now) { + int64_t t; + struct tm tm; + t = nowish = now; + gmtime_r(&t, &tm); + FormatHttpDateTime(currentdate, &tm); +} + +static char *AppendDate(char *p) { + p = AppendHeaderName(p, "Date"); + p = mempcpy(p, currentdate, 29); + return AppendCrlf(p); +} + +static char *AppendLastModified(char *p, const char *s) { + p = AppendHeaderName(p, "Last-Modified"); + p = mempcpy(p, s, 29); + return AppendCrlf(p); +} + +static char *AppendServer(char *p) { + const char *s; + if (*(s = firstnonnull(serverheader, DEFAULT_SERVER))) { + p = AppendHeaderName(p, "Server"); + p = stpcpy(p, s); + p = AppendCrlf(p); + } + return p; +} + +static char *AppendConnectionClose(char *p) { + p = AppendHeaderName(p, "Connection"); + p = STPCPY(p, "close"); + return AppendCrlf(p); +} + +static char *AppendAcceptRangesBytes(char *p) { + p = AppendHeaderName(p, "Accept-Ranges"); + p = STPCPY(p, "bytes"); + return AppendCrlf(p); +} + +static char *AppendNosniff(char *p) { + p = AppendHeaderName(p, "X-Content-Type-Options"); + p = STPCPY(p, "nosniff"); + return AppendCrlf(p); +} + +static char *AppendContentType(char *p, const char *ct) { + p = AppendHeaderName(p, "Content-Type"); + p = stpcpy(p, ct); + if (startswith(ct, "text/")) { + p = STPCPY(p, "; charset=utf-8"); + } + return AppendCrlf(p); +} + +static char *AppendContentTypeTextPlain(char *p) { + return AppendContentType(p, "text/plain"); +} + +static char *AppendExpires(char *p, int64_t t) { + struct tm tm; + gmtime_r(&t, &tm); + p = AppendHeaderName(p, "Expires"); + p = FormatHttpDateTime(p, &tm); + return AppendCrlf(p); +} + +static char *AppendVaryContentEncoding(char *p) { + p = AppendHeaderName(p, "Vary"); + p = STPCPY(p, "Accept-Encoding"); + return AppendCrlf(p); +} + +static char *AppendCache(char *p) { + int x; + x = cacheseconds; + if (!x) return p; + x = MAX(0, x); + p = AppendHeaderName(p, "Cache-Control"); + p = STPCPY(p, "max-age="); + p += uint64toarray_radix10(x, p); + if (x) p = STPCPY(p, ", public"); + p = AppendCrlf(p); + return AppendExpires(p, nowish + cacheseconds); +} + +static char *AppendContentLength(char *p, size_t n) { + p = AppendHeaderName(p, "Content-Length"); + p += uint64toarray_radix10(n, p); + return AppendCrlf(p); +} + +static char *AppendContentRange(char *p, long rangestart, long rangelength, + long contentlength) { + long endrange; + CHECK_GE(rangestart + rangelength, rangestart); + CHECK_LE(rangestart + rangelength, contentlength); + if (__builtin_add_overflow(rangestart, rangelength, &endrange)) abort(); + p = AppendHeaderName(p, "Content-Range"); + p = STPCPY(p, "bytes "); + p += uint64toarray_radix10(rangestart, p); + *p++ = '-'; + p += uint64toarray_radix10(endrange, p); + *p++ = '/'; + p += uint64toarray_radix10(contentlength, p); + return AppendCrlf(p); +} + +static char *AppendContentEncodingGzip(char *p) { + p = AppendHeaderName(p, "Content-Encoding"); + p = STPCPY(p, "gzip"); + return AppendCrlf(p); +} + +static char *AppendRedirect(char *p, const char *s) { + VERBOSEF("%s %s %.*s redirect %s", clientaddrstr, kHttpMethod[req.method], + req.uri.b - req.uri.a, inbuf + req.uri.a, s); + p = AppendStatus(p, 302, "Temporary Redirect"); + p = AppendHeaderName(p, "Location"); + p = STPCPY(p, s); + return AppendCrlf(p); +} + +static bool InflateTiny(uint8_t *outbuf, size_t outsize, const uint8_t *inbuf, + size_t insize) { + struct DeflateState ds; + return undeflate(outbuf, outsize, inbuf, insize, &ds) != -1; +} + +static bool InflateZlib(uint8_t *outbuf, size_t outsize, const uint8_t *inbuf, + size_t insize) { + bool ok; + z_stream zs; + ok = false; + zs.next_in = inbuf; + zs.avail_in = insize; + zs.total_in = insize; + zs.next_out = outbuf; + zs.avail_out = outsize; + zs.total_out = outsize; + zs.zfree = Z_NULL; + zs.zalloc = Z_NULL; + if (inflateInit2(&zs, -MAX_WBITS) == Z_OK) { + switch (inflate(&zs, Z_NO_FLUSH)) { + case Z_STREAM_END: + ok = true; + break; + case Z_MEM_ERROR: + WARNF("Z_MEM_ERROR"); + break; + case Z_DATA_ERROR: + WARNF("Z_DATA_ERROR"); + break; + case Z_NEED_DICT: + WARNF("Z_NEED_DICT"); + break; + default: + abort(); + } + inflateEnd(&zs); + } + return ok; +} + +static bool Inflate(uint8_t *outbuf, size_t outsize, const uint8_t *inbuf, + size_t insize) { + if (IsTiny()) { + return InflateTiny(outbuf, outsize, inbuf, insize); + } else { + return InflateZlib(outbuf, outsize, inbuf, insize); + } +} + +static void LogRequestLatency(void) { + long double now = nowl(); + LOGF("%s latency req %,16ldns conn %,16ldns", clientaddrstr, + (long)((now - startrequest) * 1e9), + (long)((now - startconnection) * 1e9)); +} + +void HandleRequest(size_t got) { + char *p; + int iovlen; + bool gzipped; + void *content; + size_t pathlen; + struct Asset *a; + unsigned version; + struct iovec iov[4]; + uint8_t gzip_footer[8]; + const char *path, *location; + long lf, contentlength, actualcontentlength, rangestart, rangelength; + p = outbuf; + content = ""; + gzipped = false; + contentlength = -1; + if (ParseHttpRequest(&req, inbuf, got) != -1) { + if (logmessages) { + LOGF("%s received %,d byte message\n%.*s", clientaddrstr, req.length, + req.length - 4, inbuf); + } + version = GetHttpVersion(); + if (version < 101) terminated = true, legacyhttp = true; + if (version <= 101) { + if (IsConnectionClose()) terminated = true; + path = inbuf + req.uri.a; + pathlen = req.uri.b - req.uri.a; + if (req.method == kHttpGet || req.method == kHttpHead) { + VERBOSEF("%s %s %.*s referer %.*s", clientaddrstr, + kHttpMethod[req.method], pathlen, path, + req.headers[kHttpReferer].b - req.headers[kHttpReferer].a, + inbuf + req.headers[kHttpReferer].a); + if ((location = LookupRedirect(path, pathlen))) { + p = AppendRedirect(p, DEFAULT_PATH); + } else if ((a = FindFile(path, pathlen))) { + if (IsNotModified(a)) { + VERBOSEF("%s %s %.*s not modified", clientaddrstr, + kHttpMethod[req.method], pathlen, path); + p = AppendStatus(p, 304, "Not Modified"); + } else { + lf = ZIP_CFILE_OFFSET(zbase + a->cf); + content = ZIP_LFILE_CONTENT(zbase + lf); + contentlength = ZIP_CFILE_COMPRESSEDSIZE(zbase + a->cf); + if (IsCompressed(a)) { + if (memmem(inbuf + req.headers[kHttpAcceptEncoding].a, + req.headers[kHttpAcceptEncoding].b - + req.headers[kHttpAcceptEncoding].a, + "gzip", 4)) { + gzipped = true; + memcpy(gzip_footer + 0, zbase + a->cf + kZipCfileOffsetCrc32, + 4); + memcpy(gzip_footer + 4, + zbase + a->cf + kZipCfileOffsetUncompressedsize, 4); + p = AppendStatus(p, 200, "OK"); + p = AppendContentEncodingGzip(p); + } else if (Inflate( + (content = gc(xmalloc( + ZIP_CFILE_UNCOMPRESSEDSIZE(zbase + a->cf)))), + (contentlength = + ZIP_CFILE_UNCOMPRESSEDSIZE(zbase + a->cf)), + ZIP_LFILE_CONTENT(zbase + lf), + ZIP_CFILE_COMPRESSEDSIZE(zbase + a->cf))) { + p = AppendStatus(p, 200, "OK"); + } else { + WARNF("%s %s %.*s internal server error", clientaddrstr, + kHttpMethod[req.method], pathlen, path); + p = AppendStatus(p, 500, "Internal Server Error"); + content = "Internal Server Error\r\n"; + contentlength = -1; + } + } else if (HasHeader(kHttpRange)) { + if (ParseHttpRange( + inbuf + req.headers[kHttpRange].a, + req.headers[kHttpRange].b - req.headers[kHttpRange].a, + contentlength, &rangestart, &rangelength)) { + p = AppendStatus(p, 206, "Partial Content"); + p = AppendContentRange(p, rangestart, rangelength, + contentlength); + content = AddRange(content, rangestart, rangelength); + contentlength = rangelength; + } else { + WARNF("%s %s %.*s bad range %`'.*s", clientaddrstr, + kHttpMethod[req.method], pathlen, path, + req.headers[kHttpRange].b - req.headers[kHttpRange].a, + inbuf + req.headers[kHttpRange].a); + p = AppendStatus(p, 416, "Range Not Satisfiable"); + p = AppendContentRange(p, rangestart, rangelength, + contentlength); + content = ""; + contentlength = 0; + } + } else { + p = AppendStatus(p, 200, "OK"); + } + } + p = AppendLastModified(p, a->lastmodifiedstr); + p = AppendContentType(p, GetContentType(path, pathlen)); + p = AppendCache(p); + if (!IsCompressed(a)) { + p = AppendAcceptRangesBytes(p); + } else { + p = AppendVaryContentEncoding(p); + } + } else { + WARNF("%s %s %.*s not found", clientaddrstr, kHttpMethod[req.method], + pathlen, path); + p = AppendStatus(p, 404, "Not Found"); + p = AppendContentTypeTextPlain(p); + content = "Not Found\r\n"; + } + } else { + WARNF("%s %s %.*s method not allowed", clientaddrstr, + kHttpMethod[req.method], pathlen, path); + p = AppendStatus(p, 405, "method not allowed"); + p = AppendContentTypeTextPlain(p); + content = "Method Not Allowed\r\n"; + } + } else { + WARNF("%s http version not supported %`'.*s", clientaddrstr, + req.version.b - req.version.a, inbuf + req.version.a); + p = AppendStatus(p, 505, "HTTP Version Not Supported"); + p = AppendContentTypeTextPlain(p); + content = "HTTP Version Not Supported\r\n"; + terminated = true; + } + } else { + WARNF("%s parse error %s", clientaddrstr, strerror(errno)); + p = AppendStatus(p, 400, "Bad Request"); + p = AppendContentTypeTextPlain(p); + content = "Bad Request\r\n"; + terminated = true; + } + if (terminated) LOGIFNEG1(shutdown(client, SHUT_RD)); + p = AppendDate(p); + p = AppendNosniff(p); + p = AppendServer(p); + if (terminated) p = AppendConnectionClose(p); + if (contentlength == -1) contentlength = strlen(content); + actualcontentlength = contentlength; + if (gzipped) actualcontentlength += sizeof(kGzipHeader) + sizeof(gzip_footer); + p = AppendContentLength(p, actualcontentlength); + p = AppendCrlf(p); + if (logmessages) { + LOGF("%s sending %,d byte message\n%.*s", clientaddrstr, p - outbuf, + p - outbuf - 4, outbuf); + } + CHECK_LT(p, outbuf + sizeof(outbuf)); + iovlen = 0; + iov[iovlen].iov_base = outbuf; + iov[iovlen].iov_len = p - outbuf; + ++iovlen; + if (req.method != kHttpHead) { + if (gzipped) { + iov[iovlen].iov_base = kGzipHeader; + iov[iovlen].iov_len = sizeof(kGzipHeader); + ++iovlen; + } + iov[iovlen].iov_base = content; + iov[iovlen].iov_len = contentlength; + ++iovlen; + if (gzipped) { + iov[iovlen].iov_base = gzip_footer; + iov[iovlen].iov_len = sizeof(gzip_footer); + ++iovlen; + } + } + DCHECK_EQ(p - outbuf + actualcontentlength, GetIovSize(iov, iovlen)); + /* LogRequestLatency(); */ + if (WritevAll(client, iov, iovlen) == -1) { + VERBOSEF("%s send error %s", clientaddrstr, strerror(errno)); + terminated = true; + } +} + +void ProcessRequests(void) { + size_t got; + ssize_t rc; + long double now; + do { + if ((rc = read(client, inbuf, sizeof(inbuf))) != -1) { + startrequest = now = nowl(); + if (now - nowish > 1) UpdateCurrentDate(now); + if (!(got = rc)) break; + HandleRequest(got); + } else if (errno == EINTR) { + continue; + } else if (errno == ECONNRESET) { + DEBUGF("%s reset", clientaddrstr); + break; + } else { + WARNF("%s recv error %s", clientaddrstr, strerror(errno)); + break; + } + } while (!terminated); + if (killed) { + WARNF("%s killed", clientaddrstr); + } else { + DEBUGF("%s terminated", clientaddrstr); + } +} + +void ProcessConnection(void) { + int pid; + clientaddrsize = sizeof(clientaddr); + client = accept4(server, &clientaddr, &clientaddrsize, SOCK_CLOEXEC); + if (client != -1) { + startconnection = nowl(); + if ((pid = uniprocess ? -1 : fork()) > 0) { + close(client); + return; + } + if (!pid) close(server); + if (pid == -1) terminated = true; + DescribeAddress(clientaddrstr, &clientaddr); + DEBUGF("%s accept", clientaddrstr); + ProcessRequests(); + DEBUGF("%s close", clientaddrstr); + LOGIFNEG1(close(client)); + if (!pid) _exit(0); + terminated = false; + } else if (errno != EINTR) { + FATALF("%s accept error %s", serveraddrstr, strerror(errno)); + } +} + +static void SetReusePortAndAllowMultipleProcesses(void) { + int yes = 1; + LOGIFNEG1(setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))); + LOGIFNEG1(setsockopt(server, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes))); +} + +void RedBean(void) { + gmtoff = GetGmtOffset(); + programfile = (const char *)getauxval(AT_EXECFN); + CHECK(OpenZip(programfile)); + xsigaction(SIGINT, OnTerminate, 0, 0, 0); + xsigaction(SIGTERM, OnTerminate, 0, 0, 0); + xsigaction(SIGCHLD, SIG_IGN, 0, 0, 0); + xsigaction(SIGPIPE, SIG_IGN, 0, 0, 0); + xsigaction(SIGUSR1, OnHup, 0, 0, 0); + xsigaction(SIGALRM, OnAlarm, 0, 0, 0); + if (setitimer(ITIMER_REAL, &kHeartbeat, NULL) == -1) notimer = true; + CHECK_NE(-1, (server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))); + SetReusePortAndAllowMultipleProcesses(); + CHECK_NE(-1, bind(server, &serveraddr, sizeof(serveraddr))); + CHECK_NE(-1, listen(server, 10)); + DescribeAddress(serveraddrstr, &serveraddr); + if (daemonize) Daemonize(); + VERBOSEF("%s listen", serveraddrstr); + heartbeat = true; + while (!terminated) { + if (invalidated) { + if (OpenZip(programfile)) { + LOGF("%s reindexed zip", serveraddrstr); + } else { + WARNF("%s reindexing failed", serveraddrstr); + } + invalidated = false; + } + if (heartbeat | notimer) { + UpdateCurrentDate(nowl()); + heartbeat = false; + } + ProcessConnection(); + } + VERBOSEF("%s terminated", serveraddrstr); + LOGIFNEG1(close(server)); + KillAll(); + WaitAll(); } int main(int argc, char *argv[]) { showcrashreports(); GetOpts(argc, argv); - GenerateHttpDate(serverdate); - xsigaction(SIGINT, OnTerminate, 0, 0, 0); - xsigaction(SIGTERM, OnTerminate, 0, 0, 0); - CHECK_NE(-1, (server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP))); - LOGIFNEG1(setsockopt(server, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes))); - LOGIFNEG1(setsockopt(server, SOL_SOCKET, SO_REUSEPORT, &yes, sizeof(yes))); - CHECK_NE(-1, bind(server, &serveraddr, sizeof(serveraddr))); - CHECK_NE(-1, listen(server, 10)); - LOGIFNEG1(fcntl(server, F_SETFD, FD_CLOEXEC)); - DescribeAddress(serveraddrstr, &serveraddr); - LOGF("listening on tcp %s", serveraddrstr); - while (!terminated) { - ProcessRequest(); - } - LOGIFNEG1(close(server)); + RedBean(); return 0; } diff --git a/tool/net/redbean.css b/tool/net/redbean.css new file mode 100644 index 000000000..310286b5a --- /dev/null +++ b/tool/net/redbean.css @@ -0,0 +1,43 @@ +html { + font-family: sans-serif; + font-size: 14pt; +} + +body { + max-width: 700px; + min-width: 700px; + margin: 0 auto 0 auto; +} + +pre, +code { + font-family: monospace; + font-size: 12pt; +} + +p { + text-align: justify; +} + +.indent { + margin-left: 1em; +} + +h1, +h2 { + margin: 1.5em 0 1.5em 0; +} + +h1 strong { + font-size: 14pt; +} + +footer { + margin-top: 12em; + margin-bottom: 3em; + font-size: 14pt; +} + +.logo { + float: right; +} diff --git a/tool/net/redbean.html b/tool/net/redbean.html index 77d754bcf..b25f922e4 100644 --- a/tool/net/redbean.html +++ b/tool/net/redbean.html @@ -1,6 +1,29 @@ - - - -

Red Bean

+redbean + + + +

+ redbean
+ distributable static web server +

+ +

+ Here's what you need to know about redbean: + +

+ +

+ redbean is based on αcτµαlly pδrταblε εxεcµταblε + and cosmopolitan. +
+ redbean is authored by Justine Tunney who's on + GitHub and + Twitter.