From 1f2288be6ea0f77d1fc6dd960ca0c1601ba64324 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sun, 2 May 2021 07:48:59 -0700 Subject: [PATCH 1/5] Improve backwards compatibility with GNU Make --- Makefile | 22 +-- README.md | 1 + libc/stdio/getdelim.c | 6 +- libc/str/strchr.c | 10 +- libc/str/strstr.c | 3 + libc/str/tprecode16to8.c | 3 + libc/str/tprecode8to16.c | 3 + libc/x/utf16toutf8.c | 91 +++++++++++++ libc/x/utf8toutf16.c | 86 ++++++++++++ libc/x/x.h | 2 + libc/x/xgetline.c | 18 +-- .../stdio/{getline_test.c => getdelim_test.c} | 0 test/libc/str/strrchr_test.c | 125 ++++++++++++++---- test/libc/str/strstr_test.c | 10 ++ .../strrchr_test.inc => x/utf16toutf8_test.c} | 46 +++---- test/libc/x/utf8toutf16_test.c | 40 ++++++ third_party/chibicc/as.c | 36 ++--- tool/build/mkdeps.c | 6 +- 18 files changed, 412 insertions(+), 96 deletions(-) create mode 100644 libc/x/utf16toutf8.c create mode 100644 libc/x/utf8toutf16.c rename test/libc/stdio/{getline_test.c => getdelim_test.c} (100%) rename test/libc/{str/strrchr_test.inc => x/utf16toutf8_test.c} (55%) create mode 100644 test/libc/x/utf8toutf16_test.c diff --git a/Makefile b/Makefile index c87d438fa..68ed09649 100644 --- a/Makefile +++ b/Makefile @@ -189,13 +189,13 @@ include examples/package/build.mk #-φ-examples/package/new.sh include test/test.mk -OBJS = $(foreach x,$(PKGS),$($(x)_OBJS)) -SRCS = $(foreach x,$(PKGS),$($(x)_SRCS)) -HDRS = $(foreach x,$(PKGS),$($(x)_HDRS)) -INCS = $(foreach x,$(PKGS),$($(x)_INCS)) -BINS = $(foreach x,$(PKGS),$($(x)_BINS)) -TESTS = $(foreach x,$(PKGS),$($(x)_TESTS)) -CHECKS = $(foreach x,$(PKGS),$($(x)_CHECKS)) +OBJS = $(foreach x,$(PKGS),$($(x)_OBJS)) +SRCS := $(foreach x,$(PKGS),$($(x)_SRCS)) +HDRS := $(foreach x,$(PKGS),$($(x)_HDRS)) +INCS = $(foreach x,$(PKGS),$($(x)_INCS)) +BINS = $(foreach x,$(PKGS),$($(x)_BINS)) +TESTS = $(foreach x,$(PKGS),$($(x)_TESTS)) +CHECKS = $(foreach x,$(PKGS),$($(x)_CHECKS)) bins: $(BINS) check: $(CHECKS) @@ -206,11 +206,17 @@ tags: TAGS HTAGS o/$(MODE)/.x: @mkdir -p $(@D) && touch $@ +ifneq ($(findstring 4.,,$(MAKE_VERSION)),$(MAKE_VERSION)) o/$(MODE)/srcs.txt: o/$(MODE)/.x $(MAKEFILES) $(call uniq,$(foreach x,$(SRCS),$(dir $(x)))) $(file >$@) $(foreach x,$(SRCS),$(file >>$@,$(x))) - o/$(MODE)/hdrs.txt: o/$(MODE)/.x $(MAKEFILES) $(call uniq,$(foreach x,$(HDRS) $(INCS),$(dir $(x)))) $(file >$@) $(foreach x,$(HDRS) $(INCS),$(file >>$@,$(x))) +else +o/$(MODE)/srcs.txt: o/$(MODE)/.x $(MAKEFILES) $(call uniq,$(foreach x,$(SRCS),$(dir $(x)))) + $(MAKE) MODE=rel -j8 -pn bopit 2>/dev/null | sed -ne '/^SRCS/ {s/.*:= //;s/ */\n/g;p;q}' >$@ +o/$(MODE)/hdrs.txt: o/$(MODE)/.x $(MAKEFILES) $(call uniq,$(foreach x,$(HDRS) $(INCS),$(dir $(x)))) + $(MAKE) MODE=rel -j8 -pn bopit 2>/dev/null | sed -ne '/^HDRS/ {s/.*:= //;s/ */\n/g;p;q}' >$@ +endif o/$(MODE)/depend: o/$(MODE)/.x o/$(MODE)/srcs.txt o/$(MODE)/hdrs.txt $(SRCS) $(HDRS) $(INCS) @$(COMPILE) -AMKDEPS $(MKDEPS) -o $@ -r o/$(MODE)/ o/$(MODE)/srcs.txt o/$(MODE)/hdrs.txt diff --git a/README.md b/README.md index b082a5af9..09c149fd2 100644 --- a/README.md +++ b/README.md @@ -73,3 +73,4 @@ find o -name \*.com | xargs ls -rShal | less | FreeBSD | 12 | 2018 | | OpenBSD | 6.4 | 2018 | | NetBSD | 9.1 | 2020 | +| GNU Make | 3.80 | 2010 | diff --git a/libc/stdio/getdelim.c b/libc/stdio/getdelim.c index dfdadf27e..4667ecc13 100644 --- a/libc/stdio/getdelim.c +++ b/libc/stdio/getdelim.c @@ -33,10 +33,10 @@ * allocated automatically, also NUL-terminated is guaranteed * @param n is the capacity of s (in/out) * @param delim is the stop char (and NUL is implicitly too) - * @return number of bytes read, including delim, excluding NUL, or -1 - * w/ errno on EOF or error; see ferror() and feof() + * @return number of bytes read >0, including delim, excluding NUL, + * or -1 w/ errno on EOF or error; see ferror() and feof() * @note this function can't punt EINTR to caller - * @see getline(), gettok_r() + * @see getline(), chomp(), gettok_r() */ ssize_t getdelim(char **s, size_t *n, int delim, FILE *f) { char *p; diff --git a/libc/str/strchr.c b/libc/str/strchr.c index c74aac9fb..885a4ad8e 100644 --- a/libc/str/strchr.c +++ b/libc/str/strchr.c @@ -17,16 +17,14 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/assert.h" +#include "libc/bits/bits.h" #include "libc/str/str.h" -noasan static const unsigned char *strchr_x64(const unsigned char *p, - uint64_t c) { +noasan static const char *strchr_x64(const char *p, uint64_t c) { unsigned a, b; uint64_t w, x, y; for (c *= 0x0101010101010101;; p += 8) { - w = (uint64_t)p[7] << 070 | (uint64_t)p[6] << 060 | (uint64_t)p[5] << 050 | - (uint64_t)p[4] << 040 | (uint64_t)p[3] << 030 | (uint64_t)p[2] << 020 | - (uint64_t)p[1] << 010 | (uint64_t)p[0] << 000; + w = READ64LE(p); if ((x = ~(w ^ c) & ((w ^ c) - 0x0101010101010101) & 0x8080808080808080) | (y = ~w & (w - 0x0101010101010101) & 0x8080808080808080)) { if (x) { @@ -63,7 +61,7 @@ char *strchr(const char *s, int c) { if ((*s & 0xff) == c) return s; if (!*s) return NULL; } - r = (char *)strchr_x64((const unsigned char *)s, c); + r = strchr_x64(s, c); assert(!r || *r || !c); return r; } diff --git a/libc/str/strstr.c b/libc/str/strstr.c index 70129c56a..af79548c3 100644 --- a/libc/str/strstr.c +++ b/libc/str/strstr.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/safemacros.internal.h" #include "libc/str/str.h" /** @@ -29,6 +30,8 @@ */ char *strstr(const char *haystack, const char *needle) { size_t i; + if (!*needle) return haystack; + haystack = firstnonnull(strchr(haystack, *needle), haystack); for (;;) { for (i = 0;;) { if (!needle[i]) return (/*unconst*/ char *)haystack; diff --git a/libc/str/tprecode16to8.c b/libc/str/tprecode16to8.c index 652dafd60..844127384 100644 --- a/libc/str/tprecode16to8.c +++ b/libc/str/tprecode16to8.c @@ -49,6 +49,9 @@ static noasan axdx_t tprecode16to8_sse2(char *dst, size_t dstsize, /** * Transcodes UTF-16 to UTF-8. * + * This is a low-level function intended for the core runtime. Use + * utf16toutf8() for a much better API that uses malloc(). + * * @param dst is output buffer * @param dstsize is bytes in dst * @param src is NUL-terminated UTF-16 input string diff --git a/libc/str/tprecode8to16.c b/libc/str/tprecode8to16.c index f06e7d104..e93be6dfd 100644 --- a/libc/str/tprecode8to16.c +++ b/libc/str/tprecode8to16.c @@ -46,6 +46,9 @@ static inline noasan axdx_t tprecode8to16_sse2(char16_t *dst, size_t dstsize, /** * Transcodes UTF-8 to UTF-16. * + * This is a low-level function intended for the core runtime. Use + * utf8toutf16() for a much better API that uses malloc(). + * * @param dst is output buffer * @param dstsize is shorts in dst * @param src is NUL-terminated UTF-8 input string diff --git a/libc/x/utf16toutf8.c b/libc/x/utf16toutf8.c new file mode 100644 index 000000000..8924f905a --- /dev/null +++ b/libc/x/utf16toutf8.c @@ -0,0 +1,91 @@ +/*-*- 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 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/bits.h" +#include "libc/intrin/packsswb.h" +#include "libc/intrin/pandn.h" +#include "libc/intrin/pcmpgtb.h" +#include "libc/intrin/pcmpgtw.h" +#include "libc/intrin/pmovmskb.h" +#include "libc/intrin/punpckhbw.h" +#include "libc/intrin/punpcklbw.h" +#include "libc/mem/mem.h" +#include "libc/nexgen32e/bsr.h" +#include "libc/str/str.h" +#include "libc/str/thompike.h" +#include "libc/str/tpenc.h" +#include "libc/str/utf16.h" +#include "libc/x/x.h" + +static const int16_t kDel16[8] = {127, 127, 127, 127, 127, 127, 127, 127}; + +/** + * Transcodes UTF-16 to UTF-8. + * + * @param p is input value + * @param n if -1 implies strlen + * @param z if non-NULL receives output length + */ +char *utf16toutf8(const char16_t *p, size_t n, size_t *z) { + char *r, *q; + wint_t x, y; + unsigned m, j, w; + const char16_t *e; + int16_t v1[8], v2[8], v3[8], vz[8]; + if (z) *z = 0; + if (n == -1) n = p ? strlen16(p) : 0; + if ((q = r = malloc(n * 4 + 8 + 1))) { + for (e = p + n; p < e;) { + if (p + 8 < e) { /* 17x ascii */ + memset(vz, 0, 16); + do { + memcpy(v1, p, 16); + pcmpgtw(v2, v1, vz); + pcmpgtw(v3, v1, kDel16); + pandn((void *)v2, (void *)v3, (void *)v2); + if (pmovmskb((void *)v2) != 0xFFFF) break; + packsswb((void *)v1, v1, v1); + memcpy(q, v1, 8); + p += 8; + q += 8; + } while (p + 8 < e); + } + x = *p++ & 0xffff; + if (!IsUcs2(x)) { + if (p < e) { + y = *p++ & 0xffff; + x = MergeUtf16(x, y); + } else { + x = 0xFFFD; + } + } + if (x < 0200) { + *q++ = x; + } else { + w = tpenc(x); + WRITE64LE(q, w); + q += bsr(w) >> 3; + q += 1; + } + } + if (z) *z = q - r; + *q++ = '\0'; + if ((q = realloc(r, (q - r) * 1))) r = q; + } + return r; +} diff --git a/libc/x/utf8toutf16.c b/libc/x/utf8toutf16.c new file mode 100644 index 000000000..6ae24d652 --- /dev/null +++ b/libc/x/utf8toutf16.c @@ -0,0 +1,86 @@ +/*-*- 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 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/intrin/pcmpgtb.h" +#include "libc/intrin/pmovmskb.h" +#include "libc/intrin/punpckhbw.h" +#include "libc/intrin/punpcklbw.h" +#include "libc/mem/mem.h" +#include "libc/str/str.h" +#include "libc/str/thompike.h" +#include "libc/str/utf16.h" +#include "libc/x/x.h" + +/** + * Transcodes UTF-8 to UTF-16. + * + * @param p is input value + * @param n if -1 implies strlen + * @param z if non-NULL receives output length + */ +char16_t *utf8toutf16(const char *p, size_t n, size_t *z) { + size_t i; + wint_t x, a, b; + char16_t *r, *q; + unsigned m, j, w; + uint8_t v1[16], v2[16], vz[16]; + if (z) *z = 0; + if (n == -1) n = p ? strlen(p) : 0; + if ((q = r = malloc(n * sizeof(char16_t) * 2 + sizeof(char16_t)))) { + for (i = 0; i < n;) { + if (i + 16 < n) { /* 34x ascii */ + memset(vz, 0, 16); + do { + memcpy(v1, p + i, 16); + pcmpgtb((int8_t *)v2, (int8_t *)v1, (int8_t *)vz); + if (pmovmskb(v2) != 0xFFFF) break; + punpcklbw(v2, v1, vz); + punpckhbw(v1, v1, vz); + memcpy(q + 0, v2, 16); + memcpy(q + 8, v1, 16); + i += 16; + q += 16; + } while (i + 16 < n); + } + x = p[i++] & 0xff; + if (x >= 0300) { + a = ThomPikeByte(x); + m = ThomPikeLen(x) - 1; + if (i + m <= n) { + for (j = 0;;) { + b = p[i + j] & 0xff; + if (!ThomPikeCont(b)) break; + a = ThomPikeMerge(a, b); + if (++j == m) { + x = a; + i += j; + break; + } + } + } + } + w = EncodeUtf16(x); + *q++ = w; + if ((w >>= 16)) *q++ = w; + } + if (z) *z = q - r; + *q++ = '\0'; + if ((q = realloc(r, (q - r) * sizeof(char16_t)))) r = q; + } + return r; +} diff --git a/libc/x/x.h b/libc/x/x.h index dd359878f..5bab222f5 100644 --- a/libc/x/x.h +++ b/libc/x/x.h @@ -51,6 +51,8 @@ char *xstrmul(const char *, size_t) paramsnonnull((1)) _XMAL; char *xinet_ntop(int, const void *) _XPNN _XMAL; void *xunbinga(size_t, const char16_t *) attributeallocalign((1)) _XMAL _XRET; void *xunbing(const char16_t *) _XMAL _XRET; +char16_t *utf8toutf16(const char *, size_t, size_t *) nodiscard; +char *utf16toutf8(const char16_t *, size_t, size_t *) nodiscard; /*───────────────────────────────────────────────────────────────────────────│─╗ │ cosmopolitan § eXtended apis » files ─╬─│┼ diff --git a/libc/x/xgetline.c b/libc/x/xgetline.c index 6bf4f2ba8..56123ffef 100644 --- a/libc/x/xgetline.c +++ b/libc/x/xgetline.c @@ -25,16 +25,18 @@ * * @return allocated line that needs free() and usually chomp() too, * or NULL on ferror() or feof() - * @see getline() for a more difficult api + * @see getdelim() for a more difficult api + * @see chomp() */ char *xgetline(FILE *f) { - char *res; - size_t n, got; + char *p; + size_t n; + ssize_t m; n = 0; - res = NULL; - if ((got = getdelim(&res, &n, '\n', f)) <= 0) { - free(res); - res = NULL; + p = 0; + if ((m = getdelim(&p, &n, '\n', f)) <= 0) { + free(p); + p = 0; } - return res; + return p; } diff --git a/test/libc/stdio/getline_test.c b/test/libc/stdio/getdelim_test.c similarity index 100% rename from test/libc/stdio/getline_test.c rename to test/libc/stdio/getdelim_test.c diff --git a/test/libc/str/strrchr_test.c b/test/libc/str/strrchr_test.c index cf99fc6c9..a068bcbf2 100644 --- a/test/libc/str/strrchr_test.c +++ b/test/libc/str/strrchr_test.c @@ -19,34 +19,101 @@ #include "libc/str/str.h" #include "libc/testlib/testlib.h" -#define T(NAME) NAME -#define S(S) S -#define C(C) C -#include "test/libc/str/strrchr_test.inc" -#undef C -#undef S -#undef T +TEST(strrchr, test) { + EXPECT_EQ(NULL, strrchr("hello", 'z')); + EXPECT_STREQ("lo", strrchr("hello", 'l')); + EXPECT_STREQ("llo", strchr("hello", 'l')); + EXPECT_STREQ("hello", strrchr("hello", 'h')); + EXPECT_STREQ("ello", strrchr("hello", 'e')); + EXPECT_STREQ("o", strrchr("hello", 'o')); +} -#define T(NAME) NAME##16 -#define S(S) u##S -#define C(C) u##C -#define strrchr(x, y) strrchr16(x, y) -#define strchr(x, y) strchr16(x, y) -#include "test/libc/str/strrchr_test.inc" -#undef strchr -#undef strrchr -#undef C -#undef S -#undef T +TEST(strrchr, simdVectorStuffIsntBroken) { + EXPECT_EQ(NULL, strrchr("--------------------------------", 'x')); + EXPECT_STREQ("x", strrchr("-------------------------------x", 'x')); + EXPECT_STREQ("x-------------------------------", + strrchr("x-------------------------------", 'x')); + EXPECT_STREQ("x" + "z-------------------------------", + strrchr("x" + "z-------------------------------", + 'x')); + EXPECT_STREQ("x-------------------------------" + "y-------------------------------", + strrchr("x-------------------------------" + "y-------------------------------", + 'x')); + EXPECT_STREQ("x" + "z-------------------------------" + "y-------------------------------", + strrchr("x" + "z-------------------------------" + "y-------------------------------", + 'x')); +} -#define T(NAME) NAME##32 -#define S(S) L##S -#define C(C) L##C -#define strchr(x, y) wcschr(x, y) -#define strrchr(x, y) wcsrchr(x, y) -#include "test/libc/str/strrchr_test.inc" -#undef strchr -#undef strrchr -#undef C -#undef S -#undef T +TEST(strrchr16, test) { + EXPECT_EQ(NULL, strrchr16(u"hello", 'z')); + EXPECT_STREQ(u"lo", strrchr16(u"hello", 'l')); + EXPECT_STREQ(u"llo", strchr16(u"hello", 'l')); + EXPECT_STREQ(u"hello", strrchr16(u"hello", 'h')); + EXPECT_STREQ(u"ello", strrchr16(u"hello", 'e')); + EXPECT_STREQ(u"o", strrchr16(u"hello", 'o')); +} + +TEST(strrchr16, simdVectorStuffIsntBroken) { + EXPECT_EQ(NULL, strrchr16(u"--------------------------------", 'x')); + EXPECT_STREQ(u"x", strrchr16(u"-------------------------------x", 'x')); + EXPECT_STREQ(u"x-------------------------------", + strrchr16(u"x-------------------------------", 'x')); + EXPECT_STREQ(u"x" + u"z-------------------------------", + strrchr16(u"x" + u"z-------------------------------", + 'x')); + EXPECT_STREQ(u"x-------------------------------" + u"y-------------------------------", + strrchr16(u"x-------------------------------" + u"y-------------------------------", + 'x')); + EXPECT_STREQ(u"x" + u"z-------------------------------" + u"y-------------------------------", + strrchr16(u"x" + u"z-------------------------------" + u"y-------------------------------", + 'x')); +} + +TEST(wcsrchr, test) { + EXPECT_EQ(NULL, wcsrchr(L"hello", 'z')); + EXPECT_STREQ(L"lo", wcsrchr(L"hello", 'l')); + EXPECT_STREQ(L"llo", wcschr(L"hello", 'l')); + EXPECT_STREQ(L"hello", wcsrchr(L"hello", 'h')); + EXPECT_STREQ(L"ello", wcsrchr(L"hello", 'e')); + EXPECT_STREQ(L"o", wcsrchr(L"hello", 'o')); +} + +TEST(wcsrchr, simdVectorStuffIsntBroken) { + EXPECT_EQ(NULL, wcsrchr(L"--------------------------------", 'x')); + EXPECT_STREQ(L"x", wcsrchr(L"-------------------------------x", 'x')); + EXPECT_STREQ(L"x-------------------------------", + wcsrchr(L"x-------------------------------", 'x')); + EXPECT_STREQ(L"x" + L"z-------------------------------", + wcsrchr(L"x" + L"z-------------------------------", + 'x')); + EXPECT_STREQ(L"x-------------------------------" + L"y-------------------------------", + wcsrchr(L"x-------------------------------" + L"y-------------------------------", + 'x')); + EXPECT_STREQ(L"x" + L"z-------------------------------" + L"y-------------------------------", + wcsrchr(L"x" + L"z-------------------------------" + L"y-------------------------------", + 'x')); +} diff --git a/test/libc/str/strstr_test.c b/test/libc/str/strstr_test.c index c80872c78..273e54e93 100644 --- a/test/libc/str/strstr_test.c +++ b/test/libc/str/strstr_test.c @@ -23,6 +23,8 @@ #include "libc/nexgen32e/x86feature.h" #include "libc/runtime/gc.internal.h" #include "libc/str/internal.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/hyperion.h" #include "libc/testlib/testlib.h" #define MAKESTRING(NAME, VALUE) \ @@ -75,3 +77,11 @@ TEST(strstr, test) { ASSERT_EQ(NULL, strstr("-Wl,--gc-sections", "sanitize")); ASSERT_STREQ("x", strstr("x", "x")); } + +BENCH(strstr, bench) { + EZBENCH2("strstr", donothing, EXPROPRIATE(strstr(kHyperion, "THE END"))); + EZBENCH2("strstr", donothing, + EXPROPRIATE(strstr( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab", + "aaaaaab"))); +} diff --git a/test/libc/str/strrchr_test.inc b/test/libc/x/utf16toutf8_test.c similarity index 55% rename from test/libc/str/strrchr_test.inc rename to test/libc/x/utf16toutf8_test.c index 8f5773a24..476105b82 100644 --- a/test/libc/str/strrchr_test.inc +++ b/test/libc/x/utf16toutf8_test.c @@ -1,7 +1,7 @@ /*-*- 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 │ +│ Copyright 2021 Justine Alexandra Roberts Tunney │ │ │ │ Permission to use, copy, modify, and/or distribute this software for │ │ any purpose with or without fee is hereby granted, provided that the │ @@ -16,31 +16,27 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/mem/mem.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/hyperion.h" +#include "libc/testlib/testlib.h" +#include "libc/x/x.h" -TEST(T(strrchr), test) { - EXPECT_EQ(NULL, strrchr(S("hello"), C('z'))); - EXPECT_STREQ(S("lo"), strrchr(S("hello"), C('l'))); - EXPECT_STREQ(S("llo"), strchr(S("hello"), C('l'))); - EXPECT_STREQ(S("hello"), strrchr(S("hello"), C('h'))); - EXPECT_STREQ(S("ello"), strrchr(S("hello"), C('e'))); - EXPECT_STREQ(S("o"), strrchr(S("hello"), C('o'))); +TEST(utf16toutf8, test) { + EXPECT_STREQ("hello☻♥", gc(utf16toutf8(u"hello☻♥", -1, 0))); + EXPECT_STREQ("hello☻♥hello☻♥h", gc(utf16toutf8(u"hello☻♥hello☻♥h", -1, 0))); + EXPECT_STREQ("hello☻♥hello☻♥hi", gc(utf16toutf8(u"hello☻♥hello☻♥hi", -1, 0))); + EXPECT_STREQ("hello☻♥hello☻♥hello☻♥hello☻♥hello☻♥", + gc(utf16toutf8(u"hello☻♥hello☻♥hello☻♥hello☻♥hello☻♥", -1, 0))); + EXPECT_STREQ("hello--hello--h", gc(utf16toutf8(u"hello--hello--h", -1, 0))); + EXPECT_STREQ("hello--hello--hi", gc(utf16toutf8(u"hello--hello--hi", -1, 0))); + EXPECT_STREQ("hello--hello--hello--hello--hello--", + gc(utf16toutf8(u"hello--hello--hello--hello--hello--", -1, 0))); } -TEST(T(strrchr), simdVectorStuffIsntBroken) { - EXPECT_EQ(NULL, strrchr(S("--------------------------------"), C('x'))); - EXPECT_STREQ(S("x"), strrchr(S("-------------------------------x"), C('x'))); - EXPECT_STREQ(S("x-------------------------------"), - strrchr(S("x-------------------------------"), C('x'))); - EXPECT_STREQ(S("x") S("z-------------------------------"), - strrchr(S("x") S("z-------------------------------"), C('x'))); - EXPECT_STREQ(S("x-------------------------------") - S("y-------------------------------"), - strrchr(S("x-------------------------------") - S("y-------------------------------"), - C('x'))); - EXPECT_STREQ(S("x") S("z-------------------------------") - S("y-------------------------------"), - strrchr(S("x") S("z-------------------------------") - S("y-------------------------------"), - C('x'))); +BENCH(utf16toutf8, bench) { + size_t n; + char16_t *h; + h = utf8toutf16(kHyperion, kHyperionSize, &n); + EZBENCH2("utf16toutf8", donothing, free(utf16toutf8(h, n, 0))); } diff --git a/test/libc/x/utf8toutf16_test.c b/test/libc/x/utf8toutf16_test.c new file mode 100644 index 000000000..d78391416 --- /dev/null +++ b/test/libc/x/utf8toutf16_test.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 2021 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/mem/mem.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/hyperion.h" +#include "libc/testlib/testlib.h" +#include "libc/x/x.h" + +TEST(utf8toutf16, test) { + EXPECT_STREQ(u"hello☻♥", gc(utf8toutf16("hello☻♥", -1, 0))); + EXPECT_STREQ(u"hello☻♥hello☻♥h", gc(utf8toutf16("hello☻♥hello☻♥h", -1, 0))); + EXPECT_STREQ(u"hello☻♥hello☻♥hi", gc(utf8toutf16("hello☻♥hello☻♥hi", -1, 0))); + EXPECT_STREQ(u"hello☻♥hello☻♥hello☻♥hello☻♥hello☻♥", + gc(utf8toutf16("hello☻♥hello☻♥hello☻♥hello☻♥hello☻♥", -1, 0))); + EXPECT_STREQ(u"hello--hello--h", gc(utf8toutf16("hello--hello--h", -1, 0))); + EXPECT_STREQ(u"hello--hello--hi", gc(utf8toutf16("hello--hello--hi", -1, 0))); + EXPECT_STREQ(u"hello--hello--hello--hello--hello--", + gc(utf8toutf16("hello--hello--hello--hello--hello--", -1, 0))); +} + +BENCH(utf8toutf16, bench) { + EZBENCH2("utf8toutf16", donothing, + free(utf8toutf16(kHyperion, kHyperionSize, 0))); +} diff --git a/third_party/chibicc/as.c b/third_party/chibicc/as.c index 88c430b49..a8b8d272c 100644 --- a/third_party/chibicc/as.c +++ b/third_party/chibicc/as.c @@ -136,7 +136,12 @@ #define ISRIP 0x00080000 #define ISREG 0x00100000 -#define APPEND(L) L.p = realloc(L.p, ++L.n * sizeof(*L.p)) +#define APPEND(L) \ + if (++L.n > L.c) { \ + L.c = L.n + 2 + (L.c >> 1); \ + L.p = realloc(L.p, L.c * sizeof(*L.p)); \ + } + #define IS(P, N, S) (N == sizeof(S) - 1 && !strncasecmp(P, S, sizeof(S) - 1)) #define MAX(X, Y) ((Y) < (X) ? (X) : (Y)) #define READ128BE(S) ((unsigned __int128)READ64BE(S) << 64 | READ64BE((S) + 8)) @@ -152,29 +157,29 @@ struct As { bool inhibiterr; bool inhibitwarn; struct Ints { - unsigned long n; + unsigned long n, c; long *p; } ints; struct Floats { - unsigned long n; + unsigned long n, c; long double *p; } floats; struct Slices { - unsigned long n; + unsigned long n, c; struct Slice { - unsigned long n; + unsigned long n, c; char *p; } * p; } slices; struct Sauces { - unsigned long n; + unsigned long n, c; struct Sauce { unsigned path; // strings unsigned line; // 1-indexed } * p; } sauces; struct Things { - unsigned long n; + unsigned long n, c; struct Thing { enum ThingType { TT_INT, @@ -189,7 +194,7 @@ struct As { } * p; } things; struct Sections { - unsigned long n; + unsigned long n, c; struct Section { unsigned name; // strings int flags; @@ -199,7 +204,7 @@ struct As { } * p; } sections; struct Symbols { - unsigned long n; + unsigned long n, c; struct Symbol { bool isused; unsigned char stb; // STB_* @@ -220,7 +225,7 @@ struct As { } * p; } symbolindex; struct Labels { - unsigned long n; + unsigned long n, c; struct Label { unsigned id; unsigned tok; // things @@ -228,7 +233,7 @@ struct As { } * p; } labels; struct Relas { - unsigned long n; + unsigned long n, c; struct Rela { bool isdead; int kind; // R_X86_64_{16,32,64,PC8,PC32,PLT32,GOTPCRELX,...} @@ -239,7 +244,7 @@ struct As { } * p; } relas; struct Exprs { - unsigned long n; + unsigned long n, c; struct Expr { enum ExprKind { EX_INT, // integer @@ -277,11 +282,11 @@ struct As { } * p; } exprs; struct Strings { - unsigned long n; + unsigned long n, c; char **p; } strings, incpaths; struct SectionStack { - unsigned long n; + unsigned long n, c; int *p; } sectionstack; }; @@ -805,8 +810,7 @@ static void Tokenize(struct As *a, int path) { continue; } if (c == '"') { - buf.n = 0; - buf.p = NULL; + memset(&buf, 0, sizeof(buf)); for (i = 1; (c = p[i++]);) { if (c == '"') break; c = ReadCharLiteral(&buf, c, p, &i); diff --git a/tool/build/mkdeps.c b/tool/build/mkdeps.c index 733c9e660..b91da3889 100644 --- a/tool/build/mkdeps.c +++ b/tool/build/mkdeps.c @@ -239,7 +239,11 @@ void LoadRelationships(int argc, char *argv[]) { buf += PAGESIZE; buf[-1] = '\n'; for (i = optind; i < argc; ++i) { - CHECK_NOTNULL((finpaths = fopen(argv[i], "r"))); + if (!(finpaths = fopen(argv[i], "r"))) { + fprintf(stderr, "\n\e[1mERROR: %s FAILED BECAUSE %s CAUSED %m\e[0m\n\n", + argv[0], argv[i]); + exit(1); + } while (getline(&line, &linecap, finpaths) != -1) { src = chomp(line); if (ShouldSkipSource(src)) continue; From 84001a246cbd32a2c1ee0998087da95312ebe98d Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sun, 2 May 2021 11:11:26 -0700 Subject: [PATCH 2/5] Fix redbean date header in daemonize mode --- net/http/encodelatin1.c | 13 ++++++----- net/http/escape.h | 2 +- net/http/hascontrolcodes.c | 28 ++++++++++------------- net/http/isacceptablepath.c | 3 ++- test/net/http/hascontrolcodes_test.c | 34 ++++++++++++++-------------- tool/net/redbean.c | 14 ++++++------ tool/viz/life.c | 6 ++++- 7 files changed, 51 insertions(+), 49 deletions(-) diff --git a/net/http/encodelatin1.c b/net/http/encodelatin1.c index 17d7f35ac..924188c4e 100644 --- a/net/http/encodelatin1.c +++ b/net/http/encodelatin1.c @@ -30,7 +30,7 @@ * @param p is input value * @param n if -1 implies strlen * @param z if non-NULL receives output length - * @param f can kControlC0, kControlC1 to forbid + * @param f can kControlC0, kControlC1, kControlWs to forbid * @return allocated NUL-terminated buffer, or NULL w/ errno * @error EILSEQ means UTF-8 found we can't or won't re-encode * @error ENOMEM means malloc() failed @@ -38,7 +38,12 @@ char *EncodeLatin1(const char *p, size_t n, size_t *z, int f) { int c; size_t i; + char t[256]; char *r, *q; + memset(t, 0, sizeof(t)); + if (f & kControlC0) memset(t + 0x00, 1, 0x20 - 0x00), t[0x7F] = 1; + if (f & kControlC1) memset(t + 0x80, 1, 0xA0 - 0x80); + t['\t'] = t['\r'] = t['\n'] = t['\v'] = !!(f & kControlWs); if (z) *z = 0; if (n == -1) n = p ? strlen(p) : 0; if ((q = r = malloc(n + 1))) { @@ -51,11 +56,7 @@ char *EncodeLatin1(const char *p, size_t n, size_t *z, int f) { goto Invalid; } } - if (((f & kControlC1) && 0x80 <= c && c < 0xA0) || - ((f & kControlC0) && (c < 32 || c == 0x7F) && - !(c == '\t' || c == '\r' || c == '\n' || c == '\v')) || - ((f & kControlWs) && - (c == '\t' || c == '\r' || c == '\n' || c == '\v'))) { + if (t[c]) { goto Invalid; } *q++ = c; diff --git a/net/http/escape.h b/net/http/escape.h index 734082ad6..665ebb0de 100644 --- a/net/http/escape.h +++ b/net/http/escape.h @@ -28,7 +28,7 @@ char *EscapeFragment(const char *, size_t, size_t *); char *EscapeSegment(const char *, size_t, size_t *); char *EscapeJsStringLiteral(const char *, size_t, size_t *); -bool HasControlCodes(const char *, size_t, int); +ssize_t HasControlCodes(const char *, size_t, int); char *Underlong(const char *, size_t, size_t *); char *DecodeLatin1(const char *, size_t, size_t *); char *EncodeLatin1(const char *, size_t, size_t *, int); diff --git a/net/http/hascontrolcodes.c b/net/http/hascontrolcodes.c index 7748bb48e..1ebc0e86e 100644 --- a/net/http/hascontrolcodes.c +++ b/net/http/hascontrolcodes.c @@ -16,11 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ -#include "libc/errno.h" -#include "libc/intrin/pcmpgtb.h" -#include "libc/intrin/pmovmskb.h" -#include "libc/mem/mem.h" -#include "libc/stdio/stdio.h" +#include "libc/bits/likely.h" #include "libc/str/str.h" #include "libc/str/thompike.h" #include "net/http/escape.h" @@ -31,17 +27,21 @@ * @param p is input value * @param n if -1 implies strlen * @param f can have kControlWs, kControlC0, kControlC1 to forbid - * @return true if forbidden characters were found + * @return index of first forbidden character or -1 * @see VisualizeControlCodes() */ -bool HasControlCodes(const char *p, size_t n, int f) { - int c; +ssize_t HasControlCodes(const char *p, size_t n, int f) { + char t[256]; wint_t x, a, b; size_t i, j, m; + memset(t, 0, sizeof(t)); + if (f & kControlC0) memset(t + 0x00, 1, 0x20 - 0x00), t[0x7F] = 1; + if (f & kControlC1) memset(t + 0x80, 1, 0xA0 - 0x80); + t['\t'] = t['\r'] = t['\n'] = t['\v'] = !!(f & kControlWs); if (n == -1) n = p ? strlen(p) : 0; for (i = 0; i < n;) { x = p[i++] & 0xff; - if (x >= 0300) { + if (UNLIKELY(x >= 0300)) { a = ThomPikeByte(x); m = ThomPikeLen(x) - 1; if (i + m <= n) { @@ -57,13 +57,9 @@ bool HasControlCodes(const char *p, size_t n, int f) { } } } - if (((f & kControlC1) && 0x80 <= x && x < 0xA0) || - ((f & kControlC0) && (x < 32 || x == 0x7F) && - !(x == '\t' || x == '\r' || x == '\n' || x == '\v')) || - ((f & kControlWs) && - (x == '\t' || x == '\r' || x == '\n' || x == '\v'))) { - return true; + if (x < 256 && t[x]) { + return i - 1; } } - return false; + return -1; } diff --git a/net/http/isacceptablepath.c b/net/http/isacceptablepath.c index 274074e0a..fa155ce20 100644 --- a/net/http/isacceptablepath.c +++ b/net/http/isacceptablepath.c @@ -16,6 +16,7 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/likely.h" #include "libc/str/str.h" #include "libc/str/thompike.h" #include "net/http/http.h" @@ -45,7 +46,7 @@ bool IsAcceptablePath(const char *data, size_t size) { e = p + size; while (p < e) { x = *p++ & 0xff; - if (x >= 0300) { + if (UNLIKELY(x >= 0300)) { a = ThomPikeByte(x); n = ThomPikeLen(x) - 1; if (p + n <= e) { diff --git a/test/net/http/hascontrolcodes_test.c b/test/net/http/hascontrolcodes_test.c index 26afa3a48..264bf0bd6 100644 --- a/test/net/http/hascontrolcodes_test.c +++ b/test/net/http/hascontrolcodes_test.c @@ -22,30 +22,30 @@ #include "net/http/escape.h" TEST(HasControlCodes, test) { - EXPECT_FALSE( - HasControlCodes(kHyperion, kHyperionSize, kControlC0 | kControlC1)); - EXPECT_TRUE(HasControlCodes("hi\1", -1, kControlC0)); - EXPECT_FALSE(HasControlCodes("hi\1", -1, kControlC1)); - EXPECT_FALSE(HasControlCodes("hi there", -1, 0)); - EXPECT_TRUE(HasControlCodes("hi\tthere", -1, kControlWs)); + EXPECT_EQ(-1, HasControlCodes(kHyperion, kHyperionSize, kControlC0)); + EXPECT_EQ(+2, HasControlCodes("hi\1", -1, kControlC0)); + EXPECT_EQ(-1, HasControlCodes("hi\1", -1, kControlC1)); + EXPECT_EQ(-1, HasControlCodes("hi there", -1, 0)); + EXPECT_NE(-1, HasControlCodes("hi\tthere", -1, kControlWs)); } TEST(HasControlCodes, testDoesUtf8) { - EXPECT_FALSE(HasControlCodes(u8"→", -1, kControlC0 | kControlC1)); - EXPECT_FALSE(HasControlCodes("\304\200", -1, kControlC0 | kControlC1)); - EXPECT_TRUE(HasControlCodes("\300\200", -1, kControlC0 | kControlC1)); - EXPECT_FALSE(HasControlCodes("\300\200", -1, kControlC1)); - EXPECT_TRUE(HasControlCodes("\302\202", -1, kControlC0 | kControlC1)); - EXPECT_TRUE(HasControlCodes("\302\202", -1, kControlC1)); - EXPECT_FALSE(HasControlCodes("\302\202", -1, kControlC0)); + EXPECT_EQ(-1, HasControlCodes(u8"→", -1, kControlC0 | kControlC1)); + EXPECT_EQ(-1, HasControlCodes("\304\200", -1, kControlC0 | kControlC1)); + EXPECT_NE(-1, HasControlCodes("\300\200", -1, kControlC0 | kControlC1)); + EXPECT_EQ(-1, HasControlCodes("\300\200", -1, kControlC1)); + EXPECT_NE(-1, HasControlCodes("\302\202", -1, kControlC0 | kControlC1)); + EXPECT_NE(-1, HasControlCodes("\302\202", -1, kControlC1)); + EXPECT_EQ(-1, HasControlCodes("\302\202", -1, kControlC0)); } TEST(HasControlCodes, testHasLatin1FallbackBehavior) { - EXPECT_TRUE(HasControlCodes("\202", -1, kControlWs | kControlC1)); - EXPECT_FALSE(HasControlCodes("\202", -1, kControlC0)); + EXPECT_NE(-1, HasControlCodes("\202", -1, kControlWs | kControlC1)); + EXPECT_EQ(-1, HasControlCodes("\202", -1, kControlC0)); } BENCH(HasControlCodes, bench) { - EZBENCH2("HasControlCodes", donothing, - HasControlCodes(kHyperion, kHyperionSize, kControlWs)); + EZBENCH2("HasControlCodes small", donothing, HasControlCodes("hello", -1, 0)); + EZBENCH2("HasControlCodes big", donothing, + HasControlCodes(kHyperion, kHyperionSize, kControlC1)); } diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 510bbca7c..0ff6e3d0d 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -1125,7 +1125,7 @@ static void Daemonize(void) { if ((pid = fork()) > 0) _exit(0); umask(0); if (pidpath) { - fd = open(pidpath, O_CREAT | O_EXCL | O_WRONLY, 0644); + fd = open(pidpath, O_CREAT | O_WRONLY, 0644); write(fd, ibuf, uint64toarray_radix10(getpid(), ibuf)); close(fd); } @@ -1695,7 +1695,7 @@ static void AppendLogo(void) { struct Asset *a; if ((a = GetAsset("/redbean.png", 12)) && (p = LoadAsset(a, &n))) { q = EncodeBase64(p, n, &n); - Append("\"[logo]\"\r\n"); free(q); @@ -2951,7 +2951,7 @@ static int LuaSetHeader(lua_State *L) { } switch (h) { case kHttpConnection: - if (evallen != 5 || memcmp(eval, "close", 5)) { + if (evallen != 5 || memcasecmp(eval, "close", 5)) { luaL_argerror(L, 2, "unsupported"); unreachable; } @@ -3162,7 +3162,7 @@ static int LuaHasControlCodes(lua_State *L) { const char *p; p = luaL_checklstring(L, 1, &n); f = LuaCheckControlFlags(L, 2); - lua_pushboolean(L, HasControlCodes(p, n, f)); + lua_pushboolean(L, HasControlCodes(p, n, f) != -1); return 1; } @@ -4530,9 +4530,6 @@ void RedBean(int argc, char *argv[], const char *prog) { xsigaction(SIGALRM, OnAlrm, 0, 0, 0); xsigaction(SIGPIPE, SIG_IGN, 0, 0, 0); /* TODO(jart): SIGXCPU and SIGXFSZ */ - if (setitimer(ITIMER_REAL, &kHeartbeat, NULL) == -1) { - heartless = true; - } server = socket(AF_INET, SOCK_STREAM | SOCK_CLOEXEC, IPPROTO_TCP); CHECK_NE(-1, server); TuneSockets(); @@ -4557,6 +4554,9 @@ void RedBean(int argc, char *argv[], const char *prog) { fflush(stdout); } if (daemonize) Daemonize(); + if (setitimer(ITIMER_REAL, &kHeartbeat, NULL) == -1) { + heartless = true; + } UpdateCurrentDate(nowl()); freelist.c = 8; freelist.p = xcalloc(freelist.c, sizeof(*freelist.p)); diff --git a/tool/viz/life.c b/tool/viz/life.c index 672516b48..2965a67c0 100644 --- a/tool/viz/life.c +++ b/tool/viz/life.c @@ -428,6 +428,7 @@ static void OnUnzoom(long y, long x) { } static void OnMouseLeftDrag(long y, long x) { + int i; if (y == save_y && x == save_x) return; save_y = y; save_x = x; @@ -440,7 +441,10 @@ static void OnMouseLeftDrag(long y, long x) { if (erase) { Unset(y, x); } else { - Set(y, x); + for (i = 0; i < (2 << zoom); ++i) { + Set(y + (rand() % (zoom + 1)) - (rand() % (zoom + 1)), + x + (rand() % (zoom + 1)) - (rand() % (zoom + 1))); + } } } From daa32d27d4c14c508abedf6a2b7e0076db0925f4 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Sun, 2 May 2021 11:50:43 -0700 Subject: [PATCH 3/5] Add live reindexing to redbean when zip changes --- libc/runtime/openexecutable.S | 8 ++- tool/net/redbean.c | 91 +++++++++++++++++++++++++---------- 2 files changed, 68 insertions(+), 31 deletions(-) diff --git a/libc/runtime/openexecutable.S b/libc/runtime/openexecutable.S index 71babb8fd..24750acd6 100644 --- a/libc/runtime/openexecutable.S +++ b/libc/runtime/openexecutable.S @@ -40,9 +40,7 @@ OpenExecutable: pushq MAP_ANONYMOUS(%rip) # -0x28(%rbp) pushq MAP_PRIVATE(%rip) # -0x30(%rbp) pushq MAP_FIXED(%rip) # -0x38(%rbp) - pushq MAP_SHARED(%rip) # -0x40(%rbp) - pushq __NR_mprotect(%rip) # -0x48(%rbp) - pushq __NR_mprotect(%rip) # -0x50(%rbp) + pushq __NR_mprotect(%rip) # -0x40(%rbp) push %rbx # code buffer push %r12 # data buffer push %r14 # filename @@ -98,7 +96,7 @@ OpenExecutable: rep movsb // Change protection. - mov -0x48(%rbp),%eax # __NR_mprotect + mov -0x40(%rbp),%eax # __NR_mprotect mov %rbx,%rdi mov $PAGESIZE,%esi mov $PROT_READ|PROT_EXEC,%edx @@ -133,7 +131,7 @@ OpenExecutable: mov $ape_rom_filesz,%esi mov $PROT_READ|PROT_EXEC,%edx mov -0x38(%rbp),%r10d # MAP_FIXED - or -0x40(%rbp),%r10d # MAP_SHARED + or -0x30(%rbp),%r10d # MAP_PRIVATE mov %r15d,%r8d mov $ape_rom_offset,%r9d push %r9 # openbsd:pad diff --git a/tool/net/redbean.c b/tool/net/redbean.c index 0ff6e3d0d..eac8ce6a2 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -241,6 +241,7 @@ static struct Assets { static struct Shared { int workers; long double nowish; + long double lastreindex; long double lastmeltdown; char currentdate[32]; struct rusage server; @@ -318,6 +319,7 @@ static struct Shared { long readresets; long readtimeouts; long redirects; + long reindexes; long reloads; long rewrites; long serveroptions; @@ -379,13 +381,14 @@ static uint32_t clientaddrsize; static lua_State *L; static size_t zsize; static char *content; -static uint8_t *cdir; static uint8_t *zmap; +static uint8_t *zcdir; static size_t hdrsize; static size_t msgsize; static size_t amtread; static char *extrahdrs; static char *luaheaderp; +static const char *zpath; static const char *brand; static const char *pidpath; static const char *logpath; @@ -408,6 +411,7 @@ static struct Url url; static struct HttpRequest msg; static char slashpath[PATH_MAX]; +static struct stat zst; static long double startread; static long double lastrefresh; static long double startserver; @@ -1415,12 +1419,12 @@ static void IndexAssets(void) { struct Asset *p; uint32_t i, n, m, step, hash; CHECK_GE(HASH_LOAD_FACTOR, 2); - CHECK(READ32LE(cdir) == kZipCdir64HdrMagic || - READ32LE(cdir) == kZipCdirHdrMagic); - n = GetZipCdirRecords(cdir); + CHECK(READ32LE(zcdir) == kZipCdir64HdrMagic || + READ32LE(zcdir) == kZipCdirHdrMagic); + n = GetZipCdirRecords(zcdir); m = roundup2pow(MAX(1, n) * HASH_LOAD_FACTOR); p = xcalloc(m, sizeof(struct Asset)); - for (cf = GetZipCdirOffset(cdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) { + for (cf = GetZipCdirOffset(zcdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) { CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf)); lf = GetZipCfileOffset(zmap + cf); if (!IsCompressionMethodSupported(ZIP_LFILE_COMPRESSIONMETHOD(zmap + lf))) { @@ -1447,17 +1451,17 @@ static void IndexAssets(void) { assets.n = m; } -static void OpenZip(const char *path) { +static void OpenZip(void) { int fd; uint8_t *p; - struct stat st; - CHECK_NE(-1, (fd = open(path, O_RDONLY))); - CHECK_NE(-1, fstat(fd, &st)); - CHECK((zsize = st.st_size)); + if (zmap) munmap(zmap, zsize); + CHECK_NE(-1, (fd = open(zpath, O_RDONLY))); + CHECK_NE(-1, fstat(fd, &zst)); + CHECK((zsize = zst.st_size)); CHECK_NE(MAP_FAILED, (zmap = mmap(NULL, zsize, PROT_READ, MAP_SHARED, fd, 0))); - CHECK_NOTNULL((cdir = GetZipCdir(zmap, zsize))); - if (endswith(path, ".com.dbg") && (p = memmem(zmap, zsize, "MZqFpD", 6))) { + CHECK_NOTNULL((zcdir = GetZipCdir(zmap, zsize))); + if (endswith(zpath, ".com.dbg") && (p = memmem(zmap, zsize, "MZqFpD", 6))) { zsize -= p - zmap; zmap = p; } @@ -2080,11 +2084,11 @@ td { padding-right: 3em; }\r\n\ "
\r\n" "\r\n" "
\r\n",
-         strnlen(GetZipCdirComment(cdir), GetZipCdirCommentSize(cdir)),
-         GetZipCdirComment(cdir));
+         strnlen(GetZipCdirComment(zcdir), GetZipCdirCommentSize(zcdir)),
+         GetZipCdirComment(zcdir));
   memset(w, 0, sizeof(w));
-  n = GetZipCdirRecords(cdir);
-  for (cf = GetZipCdirOffset(cdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
+  n = GetZipCdirRecords(zcdir);
+  for (cf = GetZipCdirOffset(zcdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
     CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf));
     lf = GetZipCfileOffset(zmap + cf);
     path = GetAssetPath(cf, &pathlen);
@@ -2095,8 +2099,8 @@ td { padding-right: 3em; }\r\n\
     }
     free(path);
   }
-  n = GetZipCdirRecords(cdir);
-  for (cf = GetZipCdirOffset(cdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
+  n = GetZipCdirRecords(zcdir);
+  for (cf = GetZipCdirOffset(zcdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
     CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf));
     lf = GetZipCfileOffset(zmap + cf);
     path = GetAssetPath(cf, &pathlen);
@@ -2299,6 +2303,7 @@ static char *ServeStatusz(void) {
   AppendLong1("readresets", shared->readresets);
   AppendLong1("readtimeouts", shared->readtimeouts);
   AppendLong1("redirects", shared->redirects);
+  AppendLong1("reindexes", shared->reindexes);
   AppendLong1("reloads", shared->reloads);
   AppendLong1("rewrites", shared->rewrites);
   AppendLong1("serveroptions", shared->serveroptions);
@@ -2507,6 +2512,13 @@ static char *Route(const char *host, size_t hostlen, const char *path,
   }
 }
 
+static void Reindex(void) {
+  LockInc(&shared->reindexes);
+  LOGF("reindexing");
+  OpenZip();
+  IndexAssets();
+}
+
 static const char *LuaCheckPath(lua_State *L, int idx, size_t *pathlen) {
   const char *path;
   if (lua_isnoneornil(L, idx)) {
@@ -3446,8 +3458,8 @@ static int LuaGetZipPaths(lua_State *L) {
   size_t i, n, pathlen;
   lua_newtable(L);
   i = 0;
-  n = GetZipCdirRecords(cdir);
-  for (cf = GetZipCdirOffset(cdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
+  n = GetZipCdirRecords(zcdir);
+  for (cf = GetZipCdirOffset(zcdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
     CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf));
     path = GetAssetPath(cf, &pathlen);
     lua_pushlstring(L, path, pathlen);
@@ -3847,13 +3859,31 @@ static void LuaReload(void) {
 }
 
 static void HandleReload(void) {
+  LockInc(&shared->reloads);
   LOGF("reloading");
+  Reindex();
   LuaReload();
 }
 
+static bool ZipCdirChanged(void) {
+  struct stat st;
+  if (!IsZipCdir32(zmap, zsize, zcdir - zmap) &&
+      !IsZipCdir64(zmap, zsize, zcdir - zmap)) {
+    return true;
+  }
+  if (stat(zpath, &st) != -1 && st.st_ino != zst.st_ino) {
+    return true;
+  }
+  return false;
+}
+
 static void HandleHeartbeat(void) {
   if (nowl() - lastrefresh > 60 * 60) RefreshTime();
   UpdateCurrentDate(nowl());
+  if (ZipCdirChanged()) {
+    shared->lastreindex = nowl();
+    kill(0, SIGUSR1);
+  }
   getrusage(RUSAGE_SELF, &shared->server);
 #ifndef STATIC
   LuaRun("/.heartbeat.lua");
@@ -4344,6 +4374,10 @@ static void HandleMessages(void) {
         LogClose(DescribeClose());
         return;
       }
+      if (invalidated) {
+        HandleReload();
+        invalidated = false;
+      }
     }
     if (msgsize == amtread) {
       amtread = 0;
@@ -4363,6 +4397,10 @@ static void HandleMessages(void) {
       }
     }
     CollectGarbage();
+    if (invalidated) {
+      HandleReload();
+      invalidated = false;
+    }
   }
 }
 
@@ -4485,7 +4523,7 @@ static void TuneSockets(void) {
   setsockopt(server, SOL_SOCKET, SO_SNDTIMEO, &timeout, sizeof(timeout));
 }
 
-static void RestoreApe(const char *prog) {
+static void RestoreApe(void) {
   char *p;
   size_t n;
   struct Asset *a;
@@ -4493,7 +4531,7 @@ static void RestoreApe(const char *prog) {
   if (IsWindows()) return; /* TODO */
   if (IsOpenbsd()) return; /* TODO */
   if (IsNetbsd()) return;  /* TODO */
-  if (endswith(prog, ".com.dbg")) return;
+  if (endswith(zpath, ".com.dbg")) return;
   close(OpenExecutable());
   if ((a = GetAssetZip("/.ape", 5)) && (p = LoadAsset(a, &n))) {
     mprotect(ape_rom_vaddr, PAGESIZE, PROT_READ | PROT_WRITE);
@@ -4506,7 +4544,7 @@ static void RestoreApe(const char *prog) {
   }
 }
 
-void RedBean(int argc, char *argv[], const char *prog) {
+void RedBean(int argc, char *argv[]) {
   uint32_t addrsize;
   gmtoff = GetGmtOffset((lastrefresh = startserver = nowl()));
   CHECK_GT(CLK_TCK, 0);
@@ -4514,9 +4552,10 @@ void RedBean(int argc, char *argv[], const char *prog) {
            (shared = mmap(NULL, ROUNDUP(sizeof(struct Shared), FRAMESIZE),
                           PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS,
                           -1, 0)));
-  OpenZip(prog);
+  zpath = (const char *)getauxval(AT_EXECFN);
+  OpenZip();
   IndexAssets();
-  RestoreApe(prog);
+  RestoreApe();
   SetDefaults();
   GetOpts(argc, argv);
   LuaInit();
@@ -4603,6 +4642,6 @@ int main(int argc, char *argv[]) {
     setenv("GDB", "", true);
     showcrashreports();
   }
-  RedBean(argc, argv, (const char *)getauxval(AT_EXECFN));
+  RedBean(argc, argv);
   return 0;
 }

From 01e6b3ad8d09a53d18b238dec55918a6ccd7a3f2 Mon Sep 17 00:00:00 2001
From: Justine Tunney 
Date: Mon, 3 May 2021 01:21:50 -0700
Subject: [PATCH 4/5] Reduce number of disk seeks in redbean

---
 libc/calls/setitimer.c       |   2 +
 libc/fmt/kerrornames.S       | 164 +++++++++++++++++++++++++++++++++++
 libc/fmt/strerror_r.c        | 150 ++------------------------------
 libc/runtime/peekall.S       |   1 +
 net/http/gethttpreason.c     |   4 +-
 net/http/hascontrolcodes.c   |   5 +-
 test/tool/net/redbean_test.c |  12 ++-
 tool/net/redbean.c           |  47 +++++-----
 8 files changed, 212 insertions(+), 173 deletions(-)
 create mode 100644 libc/fmt/kerrornames.S

diff --git a/libc/calls/setitimer.c b/libc/calls/setitimer.c
index 205ca9acc..872c545e3 100644
--- a/libc/calls/setitimer.c
+++ b/libc/calls/setitimer.c
@@ -52,6 +52,8 @@
  *
  * Be sure to check for EINTR on your i/o calls, for best low latency.
  *
+ * Timers are not inherited across fork.
+ *
  * @param which can be ITIMER_REAL, ITIMER_VIRTUAL, etc.
  * @param newvalue specifies the interval ({0,0} means one-shot) and
  *     duration ({0,0} means disarm) in microseconds ∈ [0,999999] and
diff --git a/libc/fmt/kerrornames.S b/libc/fmt/kerrornames.S
new file mode 100644
index 000000000..7574a5b9a
--- /dev/null
+++ b/libc/fmt/kerrornames.S
@@ -0,0 +1,164 @@
+/*-*- mode:unix-assembly; indent-tabs-mode:t; tab-width:8; coding:utf-8     -*-│
+│vi: set et ft=asm ts=8 tw=8 fenc=utf-8                                     :vi│
+╞══════════════════════════════════════════════════════════════════════════════╡
+│ Copyright 2021 Justine Alexandra Roberts Tunney                              │
+│                                                                              │
+│ Permission to use, copy, modify, and/or distribute this software for         │
+│ any purpose with or without fee is hereby granted, provided that the         │
+│ above copyright notice and this permission notice appear in all copies.      │
+│                                                                              │
+│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL                │
+│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED                │
+│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE             │
+│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL         │
+│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR        │
+│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER               │
+│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR             │
+│ PERFORMANCE OF THIS SOFTWARE.                                                │
+╚─────────────────────────────────────────────────────────────────────────────*/
+#include "libc/macros.internal.h"
+
+	.macro	.e e
+	.long	\e - kErrorNames
+	.long	1f - kErrorNames
+	.section .rodata.str1.1
+1:	.string	"\e"
+	.previous
+	.endm
+
+	.section .rodata
+	.align 4
+kErrorNames:
+	.e	ENOSYS
+	.e	EPERM
+	.e	ENOENT
+	.e	ESRCH
+	.e	EINTR
+	.e	EIO
+	.e	ENXIO
+	.e	E2BIG
+	.e	ENOEXEC
+	.e	EBADF
+	.e	ECHILD
+	.e	EAGAIN
+	.e	ENOMEM
+	.e	EACCES
+	.e	EFAULT
+	.e	ENOTBLK
+	.e	EBUSY
+	.e	EEXIST
+	.e	EXDEV
+	.e	ENODEV
+	.e	ENOTDIR
+	.e	EISDIR
+	.e	EINVAL
+	.e	ENFILE
+	.e	EMFILE
+	.e	ENOTTY
+	.e	ETXTBSY
+	.e	EFBIG
+	.e	ENOSPC
+	.e	EDQUOT
+	.e	ESPIPE
+	.e	EROFS
+	.e	EMLINK
+	.e	EPIPE
+	.e	EDOM
+	.e	ERANGE
+	.e	EDEADLK
+	.e	ENAMETOOLONG
+	.e	ENOLCK
+	.e	ENOTEMPTY
+	.e	ELOOP
+	.e	ENOMSG
+	.e	EIDRM
+	.e	ETIME
+	.e	EPROTO
+	.e	EOVERFLOW
+	.e	EILSEQ
+	.e	EUSERS
+	.e	ENOTSOCK
+	.e	EDESTADDRREQ
+	.e	EMSGSIZE
+	.e	EPROTOTYPE
+	.e	ENOPROTOOPT
+	.e	EPROTONOSUPPORT
+	.e	ESOCKTNOSUPPORT
+	.e	ENOTSUP
+	.e	EOPNOTSUPP
+	.e	EPFNOSUPPORT
+	.e	EAFNOSUPPORT
+	.e	EADDRINUSE
+	.e	EADDRNOTAVAIL
+	.e	ENETDOWN
+	.e	ENETUNREACH
+	.e	ENETRESET
+	.e	ECONNABORTED
+	.e	ECONNRESET
+	.e	ENOBUFS
+	.e	EISCONN
+	.e	ENOTCONN
+	.e	ESHUTDOWN
+	.e	ETOOMANYREFS
+	.e	ETIMEDOUT
+	.e	ECONNREFUSED
+	.e	EHOSTDOWN
+	.e	EHOSTUNREACH
+	.e	EALREADY
+	.e	EINPROGRESS
+	.e	ESTALE
+	.e	EREMOTE
+	.e	EBADMSG
+	.e	ECANCELED
+	.e	EOWNERDEAD
+	.e	ENOTRECOVERABLE
+	.e	ENONET
+	.e	ERESTART
+	.e	ECHRNG
+	.e	EL2NSYNC
+	.e	EL3HLT
+	.e	EL3RST
+	.e	ELNRNG
+	.e	EUNATCH
+	.e	ENOCSI
+	.e	EL2HLT
+	.e	EBADE
+	.e	EBADR
+	.e	EXFULL
+	.e	ENOANO
+	.e	EBADRQC
+	.e	EBADSLT
+	.e	ENOSTR
+	.e	ENODATA
+	.e	ENOSR
+	.e	ENOPKG
+	.e	ENOLINK
+	.e	EADV
+	.e	ESRMNT
+	.e	ECOMM
+	.e	EMULTIHOP
+	.e	EDOTDOT
+	.e	ENOTUNIQ
+	.e	EBADFD
+	.e	EREMCHG
+	.e	ELIBACC
+	.e	ELIBBAD
+	.e	ELIBSCN
+	.e	ELIBMAX
+	.e	ELIBEXEC
+	.e	ESTRPIPE
+	.e	EUCLEAN
+	.e	ENOTNAM
+	.e	ENAVAIL
+	.e	EISNAM
+	.e	EREMOTEIO
+	.e	ENOMEDIUM
+	.e	EMEDIUMTYPE
+	.e	ENOKEY
+	.e	EKEYEXPIRED
+	.e	EKEYREVOKED
+	.e	EKEYREJECTED
+	.e	ERFKILL
+	.e	EHWPOISON
+	.long	0
+	.endobj	kErrorNames,globl,hidden
diff --git a/libc/fmt/strerror_r.c b/libc/fmt/strerror_r.c
index 0e54cddcb..9a17d23cc 100644
--- a/libc/fmt/strerror_r.c
+++ b/libc/fmt/strerror_r.c
@@ -27,149 +27,17 @@
 #include "libc/nt/runtime.h"
 #include "libc/str/str.h"
 
-const struct Error {
-  const long *x;
-  const char *s;
-} kErrors[] = {
-    {&ENOSYS, "ENOSYS"},
-    {&EPERM, "EPERM"},
-    {&ENOENT, "ENOENT"},
-    {&ESRCH, "ESRCH"},
-    {&EINTR, "EINTR"},
-    {&EIO, "EIO"},
-    {&ENXIO, "ENXIO"},
-    {&E2BIG, "E2BIG"},
-    {&ENOEXEC, "ENOEXEC"},
-    {&EBADF, "EBADF"},
-    {&ECHILD, "ECHILD"},
-    {&EAGAIN, "EAGAIN"},
-    {&ENOMEM, "ENOMEM"},
-    {&EACCES, "EACCES"},
-    {&EFAULT, "EFAULT"},
-    {&ENOTBLK, "ENOTBLK"},
-    {&EBUSY, "EBUSY"},
-    {&EEXIST, "EEXIST"},
-    {&EXDEV, "EXDEV"},
-    {&ENODEV, "ENODEV"},
-    {&ENOTDIR, "ENOTDIR"},
-    {&EISDIR, "EISDIR"},
-    {&EINVAL, "EINVAL"},
-    {&ENFILE, "ENFILE"},
-    {&EMFILE, "EMFILE"},
-    {&ENOTTY, "ENOTTY"},
-    {&ETXTBSY, "ETXTBSY"},
-    {&EFBIG, "EFBIG"},
-    {&ENOSPC, "ENOSPC"},
-    {&EDQUOT, "EDQUOT"},
-    {&ESPIPE, "ESPIPE"},
-    {&EROFS, "EROFS"},
-    {&EMLINK, "EMLINK"},
-    {&EPIPE, "EPIPE"},
-    {&EDOM, "EDOM"},
-    {&ERANGE, "ERANGE"},
-    {&EDEADLK, "EDEADLK"},
-    {&ENAMETOOLONG, "ENAMETOOLONG"},
-    {&ENOLCK, "ENOLCK"},
-    {&ENOTEMPTY, "ENOTEMPTY"},
-    {&ELOOP, "ELOOP"},
-    {&ENOMSG, "ENOMSG"},
-    {&EIDRM, "EIDRM"},
-    {&ETIME, "ETIME"},
-    {&EPROTO, "EPROTO"},
-    {&EOVERFLOW, "EOVERFLOW"},
-    {&EILSEQ, "EILSEQ"},
-    {&EUSERS, "EUSERS"},
-    {&ENOTSOCK, "ENOTSOCK"},
-    {&EDESTADDRREQ, "EDESTADDRREQ"},
-    {&EMSGSIZE, "EMSGSIZE"},
-    {&EPROTOTYPE, "EPROTOTYPE"},
-    {&ENOPROTOOPT, "ENOPROTOOPT"},
-    {&EPROTONOSUPPORT, "EPROTONOSUPPORT"},
-    {&ESOCKTNOSUPPORT, "ESOCKTNOSUPPORT"},
-    {&ENOTSUP, "ENOTSUP"},
-    {&EOPNOTSUPP, "EOPNOTSUPP"},
-    {&EPFNOSUPPORT, "EPFNOSUPPORT"},
-    {&EAFNOSUPPORT, "EAFNOSUPPORT"},
-    {&EADDRINUSE, "EADDRINUSE"},
-    {&EADDRNOTAVAIL, "EADDRNOTAVAIL"},
-    {&ENETDOWN, "ENETDOWN"},
-    {&ENETUNREACH, "ENETUNREACH"},
-    {&ENETRESET, "ENETRESET"},
-    {&ECONNABORTED, "ECONNABORTED"},
-    {&ECONNRESET, "ECONNRESET"},
-    {&ENOBUFS, "ENOBUFS"},
-    {&EISCONN, "EISCONN"},
-    {&ENOTCONN, "ENOTCONN"},
-    {&ESHUTDOWN, "ESHUTDOWN"},
-    {&ETOOMANYREFS, "ETOOMANYREFS"},
-    {&ETIMEDOUT, "ETIMEDOUT"},
-    {&ECONNREFUSED, "ECONNREFUSED"},
-    {&EHOSTDOWN, "EHOSTDOWN"},
-    {&EHOSTUNREACH, "EHOSTUNREACH"},
-    {&EALREADY, "EALREADY"},
-    {&EINPROGRESS, "EINPROGRESS"},
-    {&ESTALE, "ESTALE"},
-    {&EREMOTE, "EREMOTE"},
-    {&EBADMSG, "EBADMSG"},
-    {&ECANCELED, "ECANCELED"},
-    {&EOWNERDEAD, "EOWNERDEAD"},
-    {&ENOTRECOVERABLE, "ENOTRECOVERABLE"},
-    {&ENONET, "ENONET"},
-    {&ERESTART, "ERESTART"},
-    {&ECHRNG, "ECHRNG"},
-    {&EL2NSYNC, "EL2NSYNC"},
-    {&EL3HLT, "EL3HLT"},
-    {&EL3RST, "EL3RST"},
-    {&ELNRNG, "ELNRNG"},
-    {&EUNATCH, "EUNATCH"},
-    {&ENOCSI, "ENOCSI"},
-    {&EL2HLT, "EL2HLT"},
-    {&EBADE, "EBADE"},
-    {&EBADR, "EBADR"},
-    {&EXFULL, "EXFULL"},
-    {&ENOANO, "ENOANO"},
-    {&EBADRQC, "EBADRQC"},
-    {&EBADSLT, "EBADSLT"},
-    {&ENOSTR, "ENOSTR"},
-    {&ENODATA, "ENODATA"},
-    {&ENOSR, "ENOSR"},
-    {&ENOPKG, "ENOPKG"},
-    {&ENOLINK, "ENOLINK"},
-    {&EADV, "EADV"},
-    {&ESRMNT, "ESRMNT"},
-    {&ECOMM, "ECOMM"},
-    {&EMULTIHOP, "EMULTIHOP"},
-    {&EDOTDOT, "EDOTDOT"},
-    {&ENOTUNIQ, "ENOTUNIQ"},
-    {&EBADFD, "EBADFD"},
-    {&EREMCHG, "EREMCHG"},
-    {&ELIBACC, "ELIBACC"},
-    {&ELIBBAD, "ELIBBAD"},
-    {&ELIBSCN, "ELIBSCN"},
-    {&ELIBMAX, "ELIBMAX"},
-    {&ELIBEXEC, "ELIBEXEC"},
-    {&ESTRPIPE, "ESTRPIPE"},
-    {&EUCLEAN, "EUCLEAN"},
-    {&ENOTNAM, "ENOTNAM"},
-    {&ENAVAIL, "ENAVAIL"},
-    {&EISNAM, "EISNAM"},
-    {&EREMOTEIO, "EREMOTEIO"},
-    {&ENOMEDIUM, "ENOMEDIUM"},
-    {&EMEDIUMTYPE, "EMEDIUMTYPE"},
-    {&ENOKEY, "ENOKEY"},
-    {&EKEYEXPIRED, "EKEYEXPIRED"},
-    {&EKEYREVOKED, "EKEYREVOKED"},
-    {&EKEYREJECTED, "EKEYREJECTED"},
-    {&ERFKILL, "ERFKILL"},
-    {&EHWPOISON, "EHWPOISON"},
-};
+extern const struct Error {
+  int x;
+  int s;
+} kErrorNames[];
 
-static const char *geterrname(long x) {
+static const char *GetErrorName(long x) {
   int i;
   if (x) {
-    for (i = 0; i < ARRAYLEN(kErrors); ++i) {
-      if (x == *kErrors[i].x) {
-        return kErrors[i].s;
+    for (i = 0; kErrorNames[i].x; ++i) {
+      if (x == *(const long *)((uintptr_t)kErrorNames + kErrorNames[i].x)) {
+        return (const char *)((uintptr_t)kErrorNames + kErrorNames[i].s);
       }
     }
   }
@@ -184,7 +52,7 @@ int strerror_r(int err, char *buf, size_t size) {
   char *p;
   const char *s;
   err &= 0xFFFF;
-  s = geterrname(err);
+  s = GetErrorName(err);
   p = buf;
   if (strlen(s) + 1 + 5 + 1 + 1 <= size) {
     p = stpcpy(p, s);
diff --git a/libc/runtime/peekall.S b/libc/runtime/peekall.S
index a1f82ebc1..85510fd1f 100644
--- a/libc/runtime/peekall.S
+++ b/libc/runtime/peekall.S
@@ -24,6 +24,7 @@ _peekall:
 	.leafprologue
 	ezlea	_base,si
 	ezlea	_end,cx
+	add	$0x1000,%rsi
 0:	mov	(%rsi),%eax
 	add	$PAGESIZE,%rsi
 	cmp	%rcx,%rsi
diff --git a/net/http/gethttpreason.c b/net/http/gethttpreason.c
index ce10aaf50..c05f46f7e 100644
--- a/net/http/gethttpreason.c
+++ b/net/http/gethttpreason.c
@@ -20,8 +20,8 @@
 #include "libc/macros.internal.h"
 #include "net/http/http.h"
 
-static const struct HttpReason {
-  int code;
+static const struct thatispacked HttpReason {
+  short code;
   const char *name;
 } kHttpReason[] = {
     {100, "Continue"},
diff --git a/net/http/hascontrolcodes.c b/net/http/hascontrolcodes.c
index 1ebc0e86e..c7fba8f22 100644
--- a/net/http/hascontrolcodes.c
+++ b/net/http/hascontrolcodes.c
@@ -33,13 +33,14 @@
 ssize_t HasControlCodes(const char *p, size_t n, int f) {
   char t[256];
   wint_t x, a, b;
-  size_t i, j, m;
+  size_t i, j, m, g;
   memset(t, 0, sizeof(t));
   if (f & kControlC0) memset(t + 0x00, 1, 0x20 - 0x00), t[0x7F] = 1;
   if (f & kControlC1) memset(t + 0x80, 1, 0xA0 - 0x80);
   t['\t'] = t['\r'] = t['\n'] = t['\v'] = !!(f & kControlWs);
   if (n == -1) n = p ? strlen(p) : 0;
   for (i = 0; i < n;) {
+    g = i;
     x = p[i++] & 0xff;
     if (UNLIKELY(x >= 0300)) {
       a = ThomPikeByte(x);
@@ -58,7 +59,7 @@ ssize_t HasControlCodes(const char *p, size_t n, int f) {
       }
     }
     if (x < 256 && t[x]) {
-      return i - 1;
+      return g;
     }
   }
   return -1;
diff --git a/test/tool/net/redbean_test.c b/test/tool/net/redbean_test.c
index 37b309939..38c111de6 100644
--- a/test/tool/net/redbean_test.c
+++ b/test/tool/net/redbean_test.c
@@ -28,16 +28,13 @@
 #include "libc/testlib/testlib.h"
 #include "libc/x/x.h"
 
-/* TODO(jart): Finish this */
-
 STATIC_YOINK("zip_uri_support");
 STATIC_YOINK("o/" MODE "/tool/net/redbean.com");
 char testlib_enable_tmp_setup_teardown;
 
 void SetUp(void) {
-  return;
   ssize_t n;
-  char buf[512];
+  char buf[1024];
   int fdin, fdout;
   ASSERT_NE(-1, mkdir("bin", 0755));
   ASSERT_NE(-1, (fdin = open("zip:o/" MODE "/tool/net/redbean.com", O_RDONLY)));
@@ -45,22 +42,22 @@ void SetUp(void) {
   for (;;) {
     ASSERT_NE(-1, (n = read(fdin, buf, sizeof(buf))));
     if (!n) break;
-    ASSERT_EQ(n, write(fdout, buf, sizeof(buf)));
+    ASSERT_EQ(n, write(fdout, buf, n));
   }
   close(fdout);
   close(fdin);
 }
 
 TEST(redbean, test) {
-  return;
   char portbuf[16];
   int pid, port, pipefds[2];
   sigset_t chldmask, savemask;
   sigaddset(&chldmask, SIGCHLD);
   sigprocmask(SIG_BLOCK, &chldmask, &savemask);
-  ASSERT_NE(-1, pipe2(pipefds, O_CLOEXEC));
+  ASSERT_NE(-1, pipe(pipefds));
   ASSERT_NE(-1, (pid = vfork()));
   if (!pid) {
+    close(pipefds[0]);
     dup2(pipefds[1], 1);
     sigprocmask(SIG_SETMASK, &savemask, NULL);
     execv("bin/redbean.com",
@@ -71,6 +68,7 @@ TEST(redbean, test) {
   EXPECT_NE(-1, read(pipefds[0], portbuf, sizeof(portbuf)));
   port = atoi(portbuf);
   printf("port %d\n", port);
+  fflush(stdout);
   EXPECT_NE(-1, kill(pid, SIGTERM));
   EXPECT_NE(-1, wait(0));
 }
diff --git a/tool/net/redbean.c b/tool/net/redbean.c
index eac8ce6a2..cffbdf1c5 100644
--- a/tool/net/redbean.c
+++ b/tool/net/redbean.c
@@ -44,6 +44,7 @@
 #include "libc/sysv/consts/inaddr.h"
 #include "libc/sysv/consts/ipproto.h"
 #include "libc/sysv/consts/itimer.h"
+#include "libc/sysv/consts/madv.h"
 #include "libc/sysv/consts/map.h"
 #include "libc/sysv/consts/msync.h"
 #include "libc/sysv/consts/o.h"
@@ -1246,7 +1247,8 @@ static void ReportWorkerResources(int pid, struct rusage *ru) {
     AppendResourceReport(ru, "\n");
     if (outbuf.n) {
       if ((s = IndentLines(outbuf.p, outbuf.n - 1, 0, 1))) {
-        LOGF("resource report for pid %d\n%s", pid, s);
+        flogf(kLogInfo, __FILE__, __LINE__, NULL,
+              "resource report for pid %d\n%s", pid, s);
         free(s);
       }
       ClearOutput();
@@ -1415,7 +1417,7 @@ static bool IsCompressionMethodSupported(int method) {
 
 static void IndexAssets(void) {
   int64_t lm;
-  uint64_t cf, lf;
+  uint64_t cf;
   struct Asset *p;
   uint32_t i, n, m, step, hash;
   CHECK_GE(HASH_LOAD_FACTOR, 2);
@@ -1426,10 +1428,9 @@ static void IndexAssets(void) {
   p = xcalloc(m, sizeof(struct Asset));
   for (cf = GetZipCdirOffset(zcdir); n--; cf += ZIP_CFILE_HDRSIZE(zmap + cf)) {
     CHECK_EQ(kZipCfileHdrMagic, ZIP_CFILE_MAGIC(zmap + cf));
-    lf = GetZipCfileOffset(zmap + cf);
-    if (!IsCompressionMethodSupported(ZIP_LFILE_COMPRESSIONMETHOD(zmap + lf))) {
+    if (!IsCompressionMethodSupported(ZIP_CFILE_COMPRESSIONMETHOD(zmap + cf))) {
       LOGF("don't understand zip compression method %d used by %`'.*s",
-           ZIP_LFILE_COMPRESSIONMETHOD(zmap + lf),
+           ZIP_CFILE_COMPRESSIONMETHOD(zmap + cf),
            ZIP_CFILE_NAMESIZE(zmap + cf), ZIP_CFILE_NAME(zmap + cf));
       continue;
     }
@@ -1441,8 +1442,8 @@ static void IndexAssets(void) {
     } while (p[i].hash);
     lm = GetZipCfileLastModified(zmap + cf);
     p[i].hash = hash;
-    p[i].lf = lf;
     p[i].cf = cf;
+    p[i].lf = GetZipCfileOffset(zmap + cf);
     p[i].istext = !!(ZIP_CFILE_INTERNALATTRIBUTES(zmap + cf) & kZipIattrText);
     p[i].lastmodified = lm;
     p[i].lastmodifiedstr = FormatUnixHttpDateTime(xmalloc(30), lm);
@@ -1476,8 +1477,8 @@ static struct Asset *GetAssetZip(const char *path, size_t pathlen) {
     i = (hash + (step * (step + 1)) >> 1) & (assets.n - 1);
     if (!assets.p[i].hash) return NULL;
     if (hash == assets.p[i].hash &&
-        pathlen == ZIP_LFILE_NAMESIZE(zmap + assets.p[i].lf) &&
-        memcmp(path, ZIP_LFILE_NAME(zmap + assets.p[i].lf), pathlen) == 0) {
+        pathlen == ZIP_CFILE_NAMESIZE(zmap + assets.p[i].cf) &&
+        memcmp(path, ZIP_CFILE_NAME(zmap + assets.p[i].cf), pathlen) == 0) {
       return &assets.p[i];
     }
   }
@@ -2034,13 +2035,13 @@ static int GetOctalWidth(int x) {
   return !x ? 1 : x < 8 ? 2 : 1 + bsr(x) / 3;
 }
 
-static const char *DescribeCompressionRatio(char rb[8], uint64_t lf) {
+static const char *DescribeCompressionRatio(char rb[8], uint64_t cf) {
   long percent;
-  if (ZIP_LFILE_COMPRESSIONMETHOD(zmap + lf) == kZipCompressionNone) {
+  if (ZIP_CFILE_COMPRESSIONMETHOD(zmap + cf) == kZipCompressionNone) {
     return "n/a";
   } else {
-    percent = lround(100 - (double)GetZipLfileCompressedSize(zmap + lf) /
-                               GetZipLfileUncompressedSize(zmap + lf) * 100);
+    percent = lround(100 - (double)GetZipCfileCompressedSize(zmap + cf) /
+                               GetZipCfileUncompressedSize(zmap + cf) * 100);
     sprintf(rb, "%ld%%", MIN(999, MAX(-999, percent)));
     return rb;
   }
@@ -2095,7 +2096,7 @@ td { padding-right: 3em; }\r\n\
     if (!IsHiddenPath(path)) {
       w[0] = min(80, max(w[0], strwidth(path, 0) + 2));
       w[1] = max(w[1], GetOctalWidth(GetZipCfileMode(zmap + cf)));
-      w[2] = max(w[2], GetDecimalWidth(GetZipLfileUncompressedSize(zmap + lf)));
+      w[2] = max(w[2], GetDecimalWidth(GetZipCfileUncompressedSize(zmap + cf)));
     }
     free(path);
   }
@@ -2117,17 +2118,17 @@ td { padding-right: 3em; }\r\n\
       localtime_r(&lastmod, &tm);
       strftime(tb, sizeof(tb), "%Y-%m-%d %H:%M:%S %Z", &tm);
       if (IsCompressionMethodSupported(
-              ZIP_LFILE_COMPRESSIONMETHOD(zmap + lf)) &&
+              ZIP_CFILE_COMPRESSIONMETHOD(zmap + cf)) &&
           IsAcceptablePath(path, pathlen)) {
         Append("%-*.*s %s  %0*o %4s  %,*ld  %'s\r\n",
                rn[2], rp[2], w[0], rn[4], rp[4], tb, w[1],
-               GetZipCfileMode(zmap + cf), DescribeCompressionRatio(rb, lf),
-               w[2], GetZipLfileUncompressedSize(zmap + lf), rp[3]);
+               GetZipCfileMode(zmap + cf), DescribeCompressionRatio(rb, cf),
+               w[2], GetZipCfileUncompressedSize(zmap + cf), rp[3]);
       } else {
         Append("%-*.*s %s  %0*o %4s  %,*ld  %'s\r\n", w[0], rn[4], rp[4], tb,
                w[1], GetZipCfileMode(zmap + cf),
-               DescribeCompressionRatio(rb, lf), w[2],
-               GetZipLfileUncompressedSize(zmap + lf), rp[3]);
+               DescribeCompressionRatio(rb, cf), w[2],
+               GetZipCfileUncompressedSize(zmap + cf), rp[3]);
       }
       free(rp[4]);
       free(rp[3]);
@@ -4592,7 +4593,11 @@ void RedBean(int argc, char *argv[]) {
     printf("%d\n", ntohs(serveraddr.sin_port));
     fflush(stdout);
   }
-  if (daemonize) Daemonize();
+  if (daemonize) {
+    Daemonize();
+  } else {
+    setpgid(getpid(), getpid());
+  }
   if (setitimer(ITIMER_REAL, &kHeartbeat, NULL) == -1) {
     heartless = true;
   }
@@ -4602,9 +4607,9 @@ void RedBean(int argc, char *argv[]) {
   unmaplist.c = 1;
   unmaplist.p = xcalloc(unmaplist.c, sizeof(*unmaplist.p));
   hdrbuf.n = 4 * 1024;
-  hdrbuf.p = xvalloc(hdrbuf.n);
+  hdrbuf.p = xmalloc(hdrbuf.n);
   inbuf.n = maxpayloadsize;
-  inbuf.p = xvalloc(inbuf.n);
+  inbuf.p = xmalloc(inbuf.n);
   while (!terminated) {
     if (zombied) {
       ReapZombies();

From af59806a42a4d90fd79d5deecf73e5c254f1e1fc Mon Sep 17 00:00:00 2001
From: Justine Tunney 
Date: Mon, 3 May 2021 01:59:27 -0700
Subject: [PATCH 5/5] Add integration test for redbean

---
 libc/fmt/pflink.h            | 28 ++++++++--------
 libc/testlib/testlib.h       |  3 ++
 libc/testlib/testrunner.c    | 37 +++++++++++++--------
 test/tool/net/redbean_test.c | 63 ++++++++++++++++++++++++++++++++----
 test/tool/net/test.mk        | 16 +++++----
 5 files changed, 106 insertions(+), 41 deletions(-)

diff --git a/libc/fmt/pflink.h b/libc/fmt/pflink.h
index 40f7f2af5..65c503155 100644
--- a/libc/fmt/pflink.h
+++ b/libc/fmt/pflink.h
@@ -18,20 +18,20 @@
  * format strings are constexprs that only contain directives.
  */
 
-#define PFLINK(FMT)                                                     \
-  ({                                                                    \
-    if (___PFLINK(FMT, strpbrk, "faAeEgG")) STATIC_YOINK("__fmt_dtoa"); \
-    if (___PFLINK(FMT, strpbrk, "cmrqs")) {                             \
-      if (___PFLINK(FMT, strchr, '#')) STATIC_YOINK("kCp437");          \
-      if (___PFLINK(FMT, strstr, "%m")) STATIC_YOINK("strerror");       \
-      if (!IsTiny() && (___PFLINK(FMT, strstr, "%*") ||                 \
-                        ___PFLINK(FMT, strpbrk, "0123456789"))) {       \
-        STATIC_YOINK("strnwidth");                                      \
-        STATIC_YOINK("strnwidth16");                                    \
-        STATIC_YOINK("wcsnwidth");                                      \
-      }                                                                 \
-    }                                                                   \
-    FMT;                                                                \
+#define PFLINK(FMT)                                                   \
+  ({                                                                  \
+    if (___PFLINK(FMT, strpbrk, "faAeg")) STATIC_YOINK("__fmt_dtoa"); \
+    if (___PFLINK(FMT, strpbrk, "cmrqs")) {                           \
+      if (___PFLINK(FMT, strchr, '#')) STATIC_YOINK("kCp437");        \
+      if (___PFLINK(FMT, strstr, "%m")) STATIC_YOINK("strerror");     \
+      if (!IsTiny() && (___PFLINK(FMT, strstr, "%*") ||               \
+                        ___PFLINK(FMT, strpbrk, "0123456789"))) {     \
+        STATIC_YOINK("strnwidth");                                    \
+        STATIC_YOINK("strnwidth16");                                  \
+        STATIC_YOINK("wcsnwidth");                                    \
+      }                                                               \
+    }                                                                 \
+    FMT;                                                              \
   })
 
 #define SFLINK(FMT)                    \
diff --git a/libc/testlib/testlib.h b/libc/testlib/testlib.h
index 408541d12..10a6f1f8c 100644
--- a/libc/testlib/testlib.h
+++ b/libc/testlib/testlib.h
@@ -74,6 +74,7 @@ COSMOPOLITAN_C_START_
  * and if the test succeeds it'll be removed along with any contents.
  */
 extern char testlib_enable_tmp_setup_teardown;
+extern char testlib_enable_tmp_setup_teardown_once;
 
 /**
  * User-defined test setup function.
@@ -82,6 +83,7 @@ extern char testlib_enable_tmp_setup_teardown;
  * defined by the linkage.
  */
 void SetUp(void);
+void SetUpOnce(void);
 
 /**
  * User-defined test cleanup function.
@@ -90,6 +92,7 @@ void SetUp(void);
  * defined by the linkage.
  */
 void TearDown(void);
+void TearDownOnce(void);
 
 /*───────────────────────────────────────────────────────────────────────────│─╗
 │ cosmopolitan § testing library » assert or die                           ─╬─│┼
diff --git a/libc/testlib/testrunner.c b/libc/testlib/testrunner.c
index 74f41d78e..50bf8d8aa 100644
--- a/libc/testlib/testrunner.c
+++ b/libc/testlib/testrunner.c
@@ -29,6 +29,10 @@
 #include "libc/testlib/testlib.h"
 #include "libc/x/x.h"
 
+static int x;
+static char cwd[PATH_MAX];
+static char tmp[PATH_MAX];
+
 void testlib_finish(void) {
   if (g_testlib_failed) {
     fprintf(stderr, "%u / %u %s\n", g_testlib_failed, g_testlib_ran,
@@ -42,6 +46,18 @@ wontreturn void testlib_abort(void) {
   unreachable;
 }
 
+static void SetupTmpDir(void) {
+  snprintf(tmp, sizeof(tmp), "o/tmp/%s.%d.%d", program_invocation_short_name,
+           getpid(), x++);
+  CHECK_NE(-1, makedirs(tmp, 0755), "tmp=%s", tmp);
+  CHECK_NE(-1, chdir(tmp), "tmp=%s", tmp);
+}
+
+static void TearDownTmpDir(void) {
+  CHECK_NE(-1, chdir(cwd));
+  CHECK_NE(-1, rmrf(tmp));
+}
+
 /**
  * Runs all test case functions in sorted order.
  */
@@ -62,18 +78,12 @@ testonly void testlib_runtestcases(testfn_t *start, testfn_t *end,
    *
    * @see ape/ape.lds
    */
-  int x;
-  char cwd[PATH_MAX];
-  char tmp[PATH_MAX];
   const testfn_t *fn;
+  CHECK_NOTNULL(getcwd(cwd, sizeof(cwd)));
+  if (weaken(testlib_enable_tmp_setup_teardown_once)) SetupTmpDir();
+  if (weaken(SetUpOnce)) weaken(SetUpOnce)();
   for (x = 0, fn = start; fn != end; ++fn) {
-    if (weaken(testlib_enable_tmp_setup_teardown)) {
-      CHECK_NOTNULL(getcwd(cwd, sizeof(cwd)));
-      snprintf(tmp, sizeof(tmp), "o/tmp/%s.%d.%d",
-               program_invocation_short_name, getpid(), x++);
-      CHECK_NE(-1, makedirs(tmp, 0755), "tmp=%s", tmp);
-      CHECK_NE(-1, chdir(tmp), "tmp=%s", tmp);
-    }
+    if (weaken(testlib_enable_tmp_setup_teardown)) SetupTmpDir();
     if (weaken(SetUp)) weaken(SetUp)();
     errno = 0;
     SetLastError(0);
@@ -83,9 +93,8 @@ testonly void testlib_runtestcases(testfn_t *start, testfn_t *end,
     (*fn)();
     sys_getpid();
     if (weaken(TearDown)) weaken(TearDown)();
-    if (weaken(testlib_enable_tmp_setup_teardown)) {
-      CHECK_NE(-1, chdir(cwd));
-      CHECK_NE(-1, rmrf(tmp));
-    }
+    if (weaken(testlib_enable_tmp_setup_teardown)) TearDownTmpDir();
   }
+  if (weaken(TearDownOnce)) weaken(TearDownOnce)();
+  if (weaken(testlib_enable_tmp_setup_teardown_once)) TearDownTmpDir();
 }
diff --git a/test/tool/net/redbean_test.c b/test/tool/net/redbean_test.c
index 38c111de6..b722e7889 100644
--- a/test/tool/net/redbean_test.c
+++ b/test/tool/net/redbean_test.c
@@ -19,20 +19,30 @@
 #include "libc/calls/calls.h"
 #include "libc/calls/sigbits.h"
 #include "libc/fmt/conv.h"
+#include "libc/log/check.h"
 #include "libc/runtime/gc.internal.h"
 #include "libc/runtime/runtime.h"
+#include "libc/sock/sock.h"
 #include "libc/stdio/stdio.h"
+#include "libc/sysv/consts/af.h"
 #include "libc/sysv/consts/auxv.h"
+#include "libc/sysv/consts/inaddr.h"
+#include "libc/sysv/consts/ipproto.h"
 #include "libc/sysv/consts/o.h"
+#include "libc/sysv/consts/shut.h"
 #include "libc/sysv/consts/sig.h"
+#include "libc/sysv/consts/sock.h"
 #include "libc/testlib/testlib.h"
 #include "libc/x/x.h"
+#include "third_party/regex/regex.h"
 
 STATIC_YOINK("zip_uri_support");
 STATIC_YOINK("o/" MODE "/tool/net/redbean.com");
-char testlib_enable_tmp_setup_teardown;
+char testlib_enable_tmp_setup_teardown_once;
+int port;
 
-void SetUp(void) {
+void SetUpOnce(void) {
+  if (IsWindows()) return;
   ssize_t n;
   char buf[1024];
   int fdin, fdout;
@@ -48,9 +58,41 @@ void SetUp(void) {
   close(fdin);
 }
 
-TEST(redbean, test) {
+char *SendHttpRequest(const char *s) {
+  int fd;
+  char *p;
+  size_t n;
+  ssize_t rc;
+  struct sockaddr_in addr = {AF_INET, htons(port), {htonl(INADDR_LOOPBACK)}};
+  EXPECT_NE(-1, (fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)));
+  EXPECT_NE(-1, connect(fd, &addr, sizeof(addr)));
+  n = strlen(s);
+  EXPECT_EQ(n, write(fd, s, n));
+  shutdown(fd, SHUT_WR);
+  for (p = 0, n = 0;; n += rc) {
+    p = xrealloc(p, n + 512);
+    EXPECT_NE(-1, (rc = read(fd, p + n, 512)));
+    if (rc <= 0) break;
+  }
+  p = xrealloc(p, n + 1);
+  p[n] = 0;
+  close(fd);
+  return p;
+}
+
+bool Matches(const char *regex, const char *str) {
+  bool r;
+  regex_t re;
+  CHECK_EQ(REG_OK, regcomp(&re, regex, 0));
+  r = regexec(&re, str, 0, 0, 0) == REG_OK;
+  regfree(&re);
+  return r;
+}
+
+TEST(redbean, testOptions) {
+  if (IsWindows()) return;
   char portbuf[16];
-  int pid, port, pipefds[2];
+  int pid, pipefds[2];
   sigset_t chldmask, savemask;
   sigaddset(&chldmask, SIGCHLD);
   sigprocmask(SIG_BLOCK, &chldmask, &savemask);
@@ -61,14 +103,21 @@ TEST(redbean, test) {
     dup2(pipefds[1], 1);
     sigprocmask(SIG_SETMASK, &savemask, NULL);
     execv("bin/redbean.com",
-          (char *const[]){"bin/redbean.com", "-zp0", "-l127.0.0.1", 0});
+          (char *const[]){"bin/redbean.com", "-szp0", "-l127.0.0.1", 0});
     _exit(127);
   }
   EXPECT_NE(-1, close(pipefds[1]));
   EXPECT_NE(-1, read(pipefds[0], portbuf, sizeof(portbuf)));
   port = atoi(portbuf);
-  printf("port %d\n", port);
-  fflush(stdout);
+  EXPECT_TRUE(Matches("HTTP/1\\.1 200 OK\r\n"
+                      "Accept: \\*/\\*\r\n"
+                      "Accept-Charset: utf-8,ISO-8859-1;q=0\\.7,\\*;q=0\\.5\r\n"
+                      "Allow: GET, HEAD, POST, PUT, DELETE, OPTIONS\r\n"
+                      "Date: .*\r\n"
+                      "Server: redbean/0\\.4\r\n"
+                      "Content-Length: 0\r\n"
+                      "\r\n",
+                      gc(SendHttpRequest("OPTIONS * HTTP/1.1\n\n"))));
   EXPECT_NE(-1, kill(pid, SIGTERM));
   EXPECT_NE(-1, wait(0));
 }
diff --git a/test/tool/net/test.mk b/test/tool/net/test.mk
index 5dae443a7..d1341da9b 100644
--- a/test/tool/net/test.mk
+++ b/test/tool/net/test.mk
@@ -29,17 +29,21 @@ TEST_TOOL_NET_CHECKS =					\
 
 TEST_TOOL_NET_DIRECTDEPS =				\
 	LIBC_CALLS					\
-	LIBC_STUBS					\
 	LIBC_FMT					\
-	LIBC_STDIO					\
 	LIBC_INTRIN					\
-	LIBC_NEXGEN32E					\
-	LIBC_SYSV					\
+	LIBC_LOG					\
 	LIBC_MEM					\
+	LIBC_NEXGEN32E					\
 	LIBC_RUNTIME					\
-	LIBC_X						\
+	LIBC_SOCK					\
+	LIBC_STDIO					\
+	LIBC_STR					\
+	LIBC_STUBS					\
+	LIBC_SYSV					\
 	LIBC_TESTLIB					\
-	LIBC_ZIPOS
+	LIBC_X						\
+	LIBC_ZIPOS					\
+	THIRD_PARTY_REGEX
 
 TEST_TOOL_NET_DEPS :=					\
 	$(call uniq,$(foreach x,$(TEST_TOOL_NET_DIRECTDEPS),$($(x))))