From e56a9d0e23ff61e5d323ca73931fcefc21a23b2b Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Mon, 3 May 2021 12:09:35 -0700 Subject: [PATCH] Mold the redbean binary to minimize page faults This change brings page faults for precompressed static asset serving down from 27 to 20 (or fewer) after fork. This is more of an art than science. Hopefully Blinkenlights can visualize page faults soon. --- libc/dos.h | 9 + libc/runtime/ftrace.c | 43 +- libc/str/getzipcdirsize.c | 30 + libc/str/memmem.c | 20 +- libc/str/strchr.c | 2 +- libc/str/strstr.c | 10 +- libc/zip.h | 1 + test/libc/nexgen32e/memrchr_test.c | 29 + test/libc/str/memmem_test.c | 27 + test/libc/str/strstr_test.c | 44 +- tool/build/zipobj.c | 6 +- tool/net/counters.inc | 94 ++ tool/net/demo/hello.lua | 1 + tool/net/redbean.c | 1444 ++++++++++++++-------------- 14 files changed, 978 insertions(+), 782 deletions(-) create mode 100644 libc/dos.h create mode 100644 libc/str/getzipcdirsize.c create mode 100644 test/libc/nexgen32e/memrchr_test.c create mode 100644 tool/net/counters.inc diff --git a/libc/dos.h b/libc/dos.h new file mode 100644 index 000000000..61662d02d --- /dev/null +++ b/libc/dos.h @@ -0,0 +1,9 @@ +#ifndef COSMOPOLITAN_LIBC_DOS_H_ +#define COSMOPOLITAN_LIBC_DOS_H_ + +#define DOS_DATE(YEAR, MONTH_IDX1, DAY_IDX1) \ + (((YEAR)-1980) << 9 | (MONTH_IDX1) << 5 | (DAY_IDX1)) +#define DOS_TIME(HOUR, MINUTE, SECOND) \ + ((HOUR) << 11 | (MINUTE) << 5 | (SECOND) >> 1) + +#endif /* COSMOPOLITAN_LIBC_DOS_H_ */ diff --git a/libc/runtime/ftrace.c b/libc/runtime/ftrace.c index 0fa7538fe..3c5d5c68f 100644 --- a/libc/runtime/ftrace.c +++ b/libc/runtime/ftrace.c @@ -23,9 +23,13 @@ #include "libc/calls/internal.h" #include "libc/calls/struct/sigset.h" #include "libc/dce.h" +#include "libc/fmt/itoa.h" #include "libc/intrin/repmovsb.h" #include "libc/macros.internal.h" +#include "libc/nexgen32e/rdtsc.h" +#include "libc/nexgen32e/rdtscp.h" #include "libc/nexgen32e/stackframe.h" +#include "libc/nexgen32e/x86feature.h" #include "libc/nt/files.h" #include "libc/nt/runtime.h" #include "libc/nt/thunk/msabi.h" @@ -49,6 +53,7 @@ void ftrace_hook(void); static int noreentry; +static uint64_t laststamp; static char g_buf[512]; static const char *g_lastsymbol; static struct SymbolTable *g_symbols; @@ -70,11 +75,14 @@ static noasan int GetNestingLevel(struct StackFrame *frame) { * according to the System Five NexGen32e ABI. */ privileged noasan void ftrace(void) { - size_t i, j, nesting; + char *p; + uint64_t stamp; const char *symbol; struct StackFrame *frame; + size_t nesting, symbolsize; if (!cmpxchg(&noreentry, 0, 1)) return; if (g_symbols) { + stamp = rdtsc(); frame = __builtin_frame_address(0); frame = frame->next; symbol = @@ -84,31 +92,30 @@ privileged noasan void ftrace(void) { g_symbols->count, frame->addr - g_symbols->addr_base)] .name_rva]; - if (symbol != g_lastsymbol && - (nesting = GetNestingLevel(frame)) * 2 < ARRAYLEN(g_buf) - 4) { - i = 2; - j = 0; - while (nesting--) { - asm volatile("" : : : "memory"); - g_buf[i++] = ' '; - g_buf[i++] = ' '; + if (symbol != g_lastsymbol) { + symbolsize = strlen(symbol); + nesting = GetNestingLevel(frame); + if (2 + nesting * 2 + symbolsize + 1 + 21 + 2 <= ARRAYLEN(g_buf)) { + p = g_buf; + *p++ = '+'; + *p++ = ' '; + memset(p, ' ', nesting * 2); + p += nesting * 2; + p = mempcpy(p, symbol, symbolsize); + *p++ = ' '; + p += uint64toarray_radix10((stamp - laststamp) / 3.3, p); + *p++ = '\r'; + *p++ = '\n'; + write(2, g_buf, p - g_buf); } - while (i < ARRAYLEN(g_buf) - 2 && symbol[j]) { - asm volatile("" : : : "memory"); - g_buf[i++] = symbol[j++]; - } - g_buf[i++] = '\r'; - g_buf[i++] = '\n'; - write(2, g_buf, i); } g_lastsymbol = symbol; + laststamp = X86_HAVE(RDTSCP) ? rdtscp(0) : rdtsc(); } noreentry = 0; } textstartup void ftrace_install(void) { - g_buf[0] = '+'; - g_buf[1] = ' '; if ((g_symbols = OpenSymbolTable(FindDebugBinary()))) { __hook(ftrace_hook, g_symbols); } else { diff --git a/libc/str/getzipcdirsize.c b/libc/str/getzipcdirsize.c new file mode 100644 index 000000000..67985c214 --- /dev/null +++ b/libc/str/getzipcdirsize.c @@ -0,0 +1,30 @@ +/*-*- 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/zip.h" + +/** + * Returns size of zip central directory. + */ +uint64_t GetZipCdirSize(const uint8_t *eocd) { + if (READ32LE(eocd) == kZipCdir64HdrMagic) { + return ZIP_CDIR64_SIZE(eocd); + } else { + return ZIP_CDIR_SIZE(eocd); + } +} diff --git a/libc/str/memmem.c b/libc/str/memmem.c index fd307ab21..071d5ea3f 100644 --- a/libc/str/memmem.c +++ b/libc/str/memmem.c @@ -30,12 +30,22 @@ void *memmem(const void *haystack, size_t haystacklen, const void *needle, size_t needlelen) { size_t i, j; + const char *p; if (!needlelen) return haystack; - for (i = 0; i < haystacklen; ++i) { - for (j = 0;; ++j) { - if (j == needlelen) return (/*unconst*/ char *)haystack + i; - if (i + j == haystacklen) break; - if (((char *)needle)[j] != ((char *)haystack)[i + j]) break; + if (needlelen <= haystacklen) { + p = memchr(haystack, *(const char *)needle, haystacklen); + if (needlelen == 1) return p; + if (p) { + haystacklen -= p - (const char *)haystack; + haystack = p; + } + /* TODO: make not quadratic */ + for (i = 0; i < haystacklen; ++i) { + for (j = 0;; ++j) { + if (j == needlelen) return (/*unconst*/ char *)haystack + i; + if (i + j == haystacklen) break; + if (((char *)needle)[j] != ((char *)haystack)[i + j]) break; + } } } return NULL; diff --git a/libc/str/strchr.c b/libc/str/strchr.c index 885a4ad8e..ff4187062 100644 --- a/libc/str/strchr.c +++ b/libc/str/strchr.c @@ -20,7 +20,7 @@ #include "libc/bits/bits.h" #include "libc/str/str.h" -noasan static const char *strchr_x64(const char *p, uint64_t c) { +noasan static inline const char *strchr_x64(const char *p, uint64_t c) { unsigned a, b; uint64_t w, x, y; for (c *= 0x0101010101010101;; p += 8) { diff --git a/libc/str/strstr.c b/libc/str/strstr.c index af79548c3..76fd72315 100644 --- a/libc/str/strstr.c +++ b/libc/str/strstr.c @@ -16,7 +16,6 @@ │ 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" /** @@ -30,8 +29,13 @@ */ char *strstr(const char *haystack, const char *needle) { size_t i; - if (!*needle) return haystack; - haystack = firstnonnull(strchr(haystack, *needle), haystack); + const char *p; + if (!needle[0]) return haystack; + if (haystack == needle) return haystack; + p = strchr(haystack, needle[0]); + if (!needle[1]) return p; + if (p) haystack = p; + /* TODO: make not quadratic */ for (;;) { for (i = 0;;) { if (!needle[i]) return (/*unconst*/ char *)haystack; diff --git a/libc/zip.h b/libc/zip.h index cf5c0c7e0..79ae60b90 100644 --- a/libc/zip.h +++ b/libc/zip.h @@ -185,6 +185,7 @@ int GetZipCfileMode(const uint8_t *); uint64_t GetZipCdirOffset(const uint8_t *); uint64_t GetZipCdirRecords(const uint8_t *); void *GetZipCdirComment(const uint8_t *); +uint64_t GetZipCdirSize(const uint8_t *); uint64_t GetZipCdirCommentSize(const uint8_t *); uint64_t GetZipCfileUncompressedSize(const uint8_t *); uint64_t GetZipCfileCompressedSize(const uint8_t *); diff --git a/test/libc/nexgen32e/memrchr_test.c b/test/libc/nexgen32e/memrchr_test.c new file mode 100644 index 000000000..b7f0b6ac1 --- /dev/null +++ b/test/libc/nexgen32e/memrchr_test.c @@ -0,0 +1,29 @@ +/*-*- 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/nexgen32e/nexgen32e.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/testlib.h" + +TEST(memrchr, test) { + EXPECT_STREQ(".there", memrchr("yo.hi.there", '.', 11)); +} + +BENCH(memrchr, bench) { + EZBENCH2("memrchr", donothing, EXPROPRIATE(memrchr("yo.hi.there", '.', 11))); +} diff --git a/test/libc/str/memmem_test.c b/test/libc/str/memmem_test.c index 4b2695df0..87f56ce7c 100644 --- a/test/libc/str/memmem_test.c +++ b/test/libc/str/memmem_test.c @@ -21,6 +21,8 @@ #include "libc/mem/mem.h" #include "libc/str/internal.h" #include "libc/str/str.h" +#include "libc/testlib/ezbench.h" +#include "libc/testlib/hyperion.h" #include "libc/testlib/testlib.h" #define MakeMemory(SL) memcpy(malloc(sizeof(SL) - 1), SL, sizeof(SL) - 1) @@ -60,6 +62,22 @@ TEST(memmem, testEndOfMemory) { free(needle); } +TEST(memmem, testOneNo) { + char *needle = MakeMemory("z"); + char *haystk = MakeMemory("abc123"); + EXPECT_EQ(0, memmem(haystk, 6, needle, 1)); + free(haystk); + free(needle); +} + +TEST(memmem, testOneYes) { + char *needle = MakeMemory("3"); + char *haystk = MakeMemory("abc123"); + EXPECT_EQ(&haystk[5], memmem(haystk, 6, needle, 1)); + free(haystk); + free(needle); +} + TEST(memmem, testCrossesSseRegister) { char *needle = MakeMemory("eeeeeeeeeeeeefffffffffffff"); char *haystk = MakeMemory("eeeeeeeeeeeeeeeeffffffffffffffffrrrrrrrrrrrrrrrr"); @@ -113,3 +131,12 @@ TEST(memmem, testEmptyHaystackAndNeedle_returnsHaystack) { TEST(memmem, testWut) { ASSERT_STREQ("x", memmem("x", 1, "x", 1)); } + +BENCH(memmem, bench) { + EZBENCH2("memmem", donothing, + EXPROPRIATE(memmem(kHyperion, kHyperionSize, "THE END", 7))); + EZBENCH2("memmem", donothing, + EXPROPRIATE(memmem( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab", + 62, "aaaaaab", 7))); +} diff --git a/test/libc/str/strstr_test.c b/test/libc/str/strstr_test.c index 273e54e93..4066424be 100644 --- a/test/libc/str/strstr_test.c +++ b/test/libc/str/strstr_test.c @@ -46,6 +46,12 @@ TEST(strstr, test_notFound) { free(haystack); } +TEST(strstr, test_notFound1) { + MAKESTRING(haystack, "abc123def"); + ASSERT_EQ(NULL, strstr(haystack, gc(strdup("x")))); + free(haystack); +} + TEST(strstr, test_middleOfString) { MAKESTRING(haystack, "abc123def"); ASSERT_STREQ(&haystack[3], strstr(haystack, gc(strdup("123")))); @@ -80,8 +86,40 @@ TEST(strstr, test) { BENCH(strstr, bench) { EZBENCH2("strstr", donothing, EXPROPRIATE(strstr(kHyperion, "THE END"))); - EZBENCH2("strstr", donothing, + EZBENCH2("strstr torture 1", donothing, EXPROPRIATE(strstr( - "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab", - "aaaaaab"))); + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab", + "b"))); + EZBENCH2("strstr torture 2", donothing, + EXPROPRIATE(strstr( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab", + "ab"))); + EZBENCH2("strstr torture 4", donothing, + EXPROPRIATE(strstr( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab", + "aaab"))); + EZBENCH2("strstr torture 8", donothing, + EXPROPRIATE(strstr( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab", + "aaaaaaab"))); + EZBENCH2("strstr torture 16", donothing, + EXPROPRIATE(strstr( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab", + "aaaaaaaaaaaaaaab"))); + EZBENCH2("strstr torture 32", donothing, + EXPROPRIATE(strstr( + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab", + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"))); } diff --git a/tool/build/zipobj.c b/tool/build/zipobj.c index 3b6f21186..52669dfcc 100644 --- a/tool/build/zipobj.c +++ b/tool/build/zipobj.c @@ -22,6 +22,7 @@ #include "libc/calls/calls.h" #include "libc/calls/struct/stat.h" #include "libc/calls/struct/timespec.h" +#include "libc/dos.h" #include "libc/elf/def.h" #include "libc/fmt/conv.h" #include "libc/limits.h" @@ -55,11 +56,6 @@ #define ZIP_LOCALFILE_SECTION ".zip.2." #define ZIP_DIRECTORY_SECTION ".zip.4." -#define DOS_DATE(YEAR, MONTH_IDX1, DAY_IDX1) \ - (((YEAR)-1980) << 9 | (MONTH_IDX1) << 5 | (DAY_IDX1)) -#define DOS_TIME(HOUR, MINUTE, SECOND) \ - ((HOUR) << 11 | (MINUTE) << 5 | (SECOND) >> 1) - char *symbol_; char *outpath_; char *yoink_; diff --git a/tool/net/counters.inc b/tool/net/counters.inc new file mode 100644 index 000000000..2855a3033 --- /dev/null +++ b/tool/net/counters.inc @@ -0,0 +1,94 @@ +C(accepterrors) +C(acceptinterrupts) +C(acceptresets) +C(badlengths) +C(badmessages) +C(badmethods) +C(badranges) +C(closeerrors) +C(compressedresponses) +C(connectionshandled) +C(connectsrefused) +C(continues) +C(decompressedresponses) +C(deflates) +C(dropped) +C(dynamicrequests) +C(emfiles) +C(enetdowns) +C(enfiles) +C(enobufs) +C(enomems) +C(enonets) +C(errors) +C(expectsrefused) +C(failedchildren) +C(forbiddens) +C(forkerrors) +C(frags) +C(fumbles) +C(http09) +C(http10) +C(http11) +C(http12) +C(hugepayloads) +C(identityresponses) +C(inflates) +C(listingrequests) +C(loops) +C(mapfails) +C(maps) +C(meltdowns) +C(messageshandled) +C(missinglengths) +C(netafrinic) +C(netanonymous) +C(netapnic) +C(netapple) +C(netarin) +C(netatt) +C(netcogent) +C(netcomcast) +C(netdod) +C(netford) +C(netlacnic) +C(netloopback) +C(netother) +C(netprivate) +C(netprudential) +C(netripe) +C(nettestnet) +C(netusps) +C(notfounds) +C(notmodifieds) +C(openfails) +C(partialresponses) +C(payloaddisconnects) +C(pipelinedrequests) +C(precompressedresponses) +C(readerrors) +C(readinterrupts) +C(readresets) +C(readtimeouts) +C(redirects) +C(reindexes) +C(reloads) +C(rewrites) +C(serveroptions) +C(shutdowns) +C(slowloris) +C(slurps) +C(statfails) +C(staticrequests) +C(stats) +C(statuszrequests) +C(synchronizationfailures) +C(terminatedchildren) +C(thiscorruption) +C(transfersrefused) +C(urisrefused) +C(verifies) +C(writeerrors) +C(writeinterruputs) +C(writeresets) +C(writetimeouts) diff --git a/tool/net/demo/hello.lua b/tool/net/demo/hello.lua index 602458869..368af761d 100644 --- a/tool/net/demo/hello.lua +++ b/tool/net/demo/hello.lua @@ -1 +1,2 @@ Write('hello world\r\n') +StoreAsset('/hi', 'sup') diff --git a/tool/net/redbean.c b/tool/net/redbean.c index ffcc48987..322427790 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -16,6 +16,8 @@ │ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/bits/bits.h" +#include "libc/bits/likely.h" #include "libc/bits/popcnt.h" #include "libc/bits/safemacros.internal.h" #include "libc/calls/calls.h" @@ -34,7 +36,9 @@ #include "libc/nexgen32e/bsf.h" #include "libc/nexgen32e/bsr.h" #include "libc/nexgen32e/crc32.h" +#include "libc/nt/enum/fileflagandattributes.h" #include "libc/runtime/clktck.h" +#include "libc/runtime/directmap.internal.h" #include "libc/runtime/gc.internal.h" #include "libc/runtime/runtime.h" #include "libc/sock/sock.h" @@ -47,12 +51,14 @@ #include "libc/sysv/consts/inaddr.h" #include "libc/sysv/consts/ipproto.h" #include "libc/sysv/consts/itimer.h" +#include "libc/sysv/consts/lock.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" #include "libc/sysv/consts/prot.h" #include "libc/sysv/consts/rusage.h" +#include "libc/sysv/consts/s.h" #include "libc/sysv/consts/shut.h" #include "libc/sysv/consts/so.h" #include "libc/sysv/consts/sock.h" @@ -77,9 +83,13 @@ #define HASH_LOAD_FACTOR /* 1. / */ 4 #define DEFAULT_PORT 8080 +#define DOS_DATE(YEAR, MONTH_IDX1, DAY_IDX1) \ + (((YEAR)-1980) << 9 | (MONTH_IDX1) << 5 | (DAY_IDX1)) +#define DOS_TIME(HOUR, MINUTE, SECOND) \ + ((HOUR) << 11 | (MINUTE) << 5 | (SECOND) >> 1) + #define read(F, P, N) readv(F, &(struct iovec){P, N}, 1) -#define Hash(P, N) max(1, crc32c(0, P, N)); -#define LockInc(P) asm volatile("lock inc%z0\t%0" : "=m"(*(P))) +#define LockInc(P) asm volatile("lock incq\t%0" : "=m"(*(P))) #define AppendCrlf(P) mempcpy(P, "\r\n", 2) #define HasHeader(H) (!!msg.headers[H].a) #define HeaderData(H) (inbuf.p + msg.headers[H].a) @@ -250,102 +260,19 @@ static struct Shared { char currentdate[32]; struct rusage server; struct rusage children; - long accepterrors; - long acceptinterrupts; - long acceptresets; - long badlengths; - long badmessages; - long badmethods; - long badranges; - long closeerrors; - long compressedresponses; - long connectionshandled; - long connectsrefused; - long continues; - long decompressedresponses; - long deflates; - long dropped; - long dynamicrequests; - long emfiles; - long enetdowns; - long enfiles; - long enobufs; - long enomems; - long enonets; - long errors; - long expectsrefused; - long failedchildren; - long forbiddens; - long forkerrors; - long frags; - long fumbles; - long http09; - long http10; - long http11; - long http12; - long hugepayloads; - long identityresponses; - long inflates; - long listingrequests; - long loops; - long mapfails; - long maps; - long meltdowns; - long messageshandled; - long missinglengths; - long netafrinic; - long netanonymous; - long netapnic; - long netapple; - long netarin; - long netatt; - long netcogent; - long netcomcast; - long netdod; - long netford; - long netlacnic; - long netloopback; - long netother; - long netprivate; - long netprudential; - long netripe; - long nettestnet; - long netusps; - long notfounds; - long notmodifieds; - long openfails; - long partialresponses; - long payloaddisconnects; - long pipelinedrequests; - long precompressedresponses; - long readerrors; - long readinterrupts; - long readresets; - long readtimeouts; - long redirects; - long reindexes; - long reloads; - long rewrites; - long serveroptions; - long shutdowns; - long slowloris; - long slurps; - long statfails; - long staticrequests; - long stats; - long statuszrequests; - long synchronizationfailures; - long terminatedchildren; - long thiscorruption; - long transfersrefused; - long urisrefused; - long verifies; - long writeerrors; - long writeinterruputs; - long writeresets; - long writetimeouts; + struct Counters { +#define C(x) long x; +#include "tool/net/counters.inc" +#undef C + } c; } * shared; +static const char kCounterNames[] = +#define C(x) #x "\0" +#include "tool/net/counters.inc" +#undef C + ; + static bool killed; static bool istext; static bool zombied; @@ -371,6 +298,7 @@ static bool encouragekeepalive; static bool loggednetworkorigin; static bool hasluaglobalhandler; +static int zfd; static int frags; static int gmtoff; static int server; @@ -424,6 +352,13 @@ static long double startconnection; static struct sockaddr_in serveraddr; static struct sockaddr_in clientaddr; +static char *Route(const char *, size_t, const char *, size_t); +static char *RouteHost(const char *, size_t, const char *, size_t); +static char *RoutePath(const char *, size_t); +static char *HandleAsset(struct Asset *, const char *, size_t); +static char *ServeAsset(struct Asset *, const char *, size_t); +static char *SetStatus(unsigned, const char *); + static wontreturn void PrintUsage(FILE *f, int rc) { /* clang-format off */ fprintf(f, "\ @@ -633,11 +568,12 @@ static void OnHup(void) { } } -static bool SlicesEqual(const char *a, size_t n, const char *b, size_t m) { +forceinline bool SlicesEqual(const char *a, size_t n, const char *b, size_t m) { return n == m && !memcmp(a, b, n); } -static bool SlicesEqualCase(const char *a, size_t n, const char *b, size_t m) { +forceinline bool SlicesEqualCase(const char *a, size_t n, const char *b, + size_t m) { return n == m && !memcasecmp(a, b, n); } @@ -815,56 +751,6 @@ static void ProgramRedirectArg(int code, const char *arg) { free(s); } -static int CompareInts(const uint64_t x, uint64_t y) { - return x > y ? 1 : x < y ? -1 : 0; -} - -static const char *BisectContentType(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 NULL; -} - -static const char *FindContentType(const char *path, size_t n) { - size_t i; - uint64_t x; - const char *p, *r; - if ((p = memrchr(path, '.', n))) { - for (x = 0, i = n; i-- > p + 1 - path;) { - x <<= 8; - x |= tolower(path[i] & 255); - } - if ((r = BisectContentType(bswap_64(x)))) { - return r; - } - } - return NULL; -} - -static const char *GetContentType(struct Asset *a, const char *path, size_t n) { - const char *r; - if (a->file && (r = FindContentType(a->file->path, strlen(a->file->path)))) { - return r; - } - return firstnonnull( - FindContentType(path, n), - firstnonnull(FindContentType(ZIP_LFILE_NAME(zmap + a->lf), - ZIP_LFILE_NAMESIZE(zmap + a->lf)), - a->istext ? "text/plain" : "application/octet-stream")); -} - static void DescribeAddress(char buf[32], uint32_t addr, uint16_t port) { char *p; const char *s; @@ -881,17 +767,17 @@ static void DescribeAddress(char buf[32], uint32_t addr, uint16_t port) { *p++ = '\0'; } -static void GetServerAddr(uint32_t *ip, uint16_t *port) { +static inline void GetServerAddr(uint32_t *ip, uint16_t *port) { *ip = ntohl(serveraddr.sin_addr.s_addr); if (port) *port = ntohs(serveraddr.sin_port); } -static void GetClientAddr(uint32_t *ip, uint16_t *port) { +static inline void GetClientAddr(uint32_t *ip, uint16_t *port) { *ip = ntohl(clientaddr.sin_addr.s_addr); if (port) *port = ntohs(clientaddr.sin_port); } -static void GetRemoteAddr(uint32_t *ip, uint16_t *port) { +static inline void GetRemoteAddr(uint32_t *ip, uint16_t *port) { GetClientAddr(ip, port); if (HasHeader(kHttpXForwardedFor) && (IsPrivateIp(*ip) || IsLoopbackIp(*ip))) { @@ -1149,14 +1035,14 @@ static void ReportWorkerExit(int pid, int ws) { --shared->workers; if (WIFEXITED(ws)) { if (WEXITSTATUS(ws)) { - LockInc(&shared->failedchildren); + LockInc(&shared->c.failedchildren); WARNF("%d exited with %d (%,d workers remain)", pid, WEXITSTATUS(ws), shared->workers); } else { DEBUGF("%d exited (%,d workers remain)", pid, shared->workers); } } else { - LockInc(&shared->terminatedchildren); + LockInc(&shared->c.terminatedchildren); WARNF("%d terminated with %s (%,d workers remain)", pid, strsignal(WTERMSIG(ws)), shared->workers); } @@ -1260,7 +1146,7 @@ static void ReportWorkerResources(int pid, struct rusage *ru) { } static void HandleWorkerExit(int pid, int ws, struct rusage *ru) { - ++shared->connectionshandled; + LockInc(&shared->c.connectionshandled); AddRusage(&shared->children, ru); ReportWorkerExit(pid, ws); ReportWorkerResources(pid, ru); @@ -1325,7 +1211,7 @@ static inline ssize_t WritevAll(int fd, struct iovec *iov, int iovlen) { } } while (wrote); } else if (errno == EINTR) { - LockInc(&shared->writeinterruputs); + LockInc(&shared->c.writeinterruputs); if (killed || (meltdown && nowl() - startread > 2)) { return -1; } @@ -1390,16 +1276,16 @@ static int64_t GetZipCfileLastModified(const uint8_t *zcf) { ZIP_CFILE_LASTMODIFIEDTIME(zcf))); } -static bool IsCompressed(struct Asset *a) { +forceinline bool IsCompressed(struct Asset *a) { return !a->file && ZIP_LFILE_COMPRESSIONMETHOD(zmap + a->lf) == kZipCompressionDeflate; } -static int GetMode(struct Asset *a) { +forceinline int GetMode(struct Asset *a) { return a->file ? a->file->st.st_mode : GetZipCfileMode(zmap + a->cf); } -static bool IsNotModified(struct Asset *a) { +forceinline bool IsNotModified(struct Asset *a) { if (msg.version < 10) return false; if (!HasHeader(kHttpIfModifiedSince)) return false; return a->lastmodified >= @@ -1414,10 +1300,19 @@ static char *FormatUnixHttpDateTime(char *s, int64_t t) { return s; } -static bool IsCompressionMethodSupported(int method) { +forceinline bool IsCompressionMethodSupported(int method) { return method == kZipCompressionNone || method == kZipCompressionDeflate; } +static unsigned Hash(const void *p, unsigned long n) { + unsigned h, i; + for (h = i = 0; i < n; i++) { + h += ((unsigned char *)p)[i]; + h *= 0x9e3779b1; + } + return MAX(1, h); +} + static void IndexAssets(void) { int64_t lm; uint64_t cf; @@ -1455,21 +1350,32 @@ static void IndexAssets(void) { assets.n = m; } +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 OpenZip(void) { - int fd; uint8_t *p; if (zmap) munmap(zmap, zsize); - CHECK_NE(-1, (fd = open(zpath, O_RDONLY))); - CHECK_NE(-1, fstat(fd, &zst)); + CHECK_NE(-1, (zfd = open(zpath, O_RDONLY))); + CHECK_NE(-1, fstat(zfd, &zst)); CHECK((zsize = zst.st_size)); CHECK_NE(MAP_FAILED, - (zmap = mmap(NULL, zsize, PROT_READ, MAP_SHARED, fd, 0))); + (zmap = mmap((void *)0x0000300000000000, zsize, PROT_READ, + MAP_FIXED | MAP_SHARED, zfd, 0))); CHECK_NOTNULL((zcdir = GetZipCdir(zmap, zsize))); if (endswith(zpath, ".com.dbg") && (p = memmem(zmap, zsize, "MZqFpD", 6))) { zsize -= p - zmap; zmap = p; } - close(fd); } static struct Asset *GetAssetZip(const char *path, size_t pathlen) { @@ -1494,7 +1400,7 @@ static struct Asset *GetAssetFile(const char *path, size_t pathlen) { a = FreeLater(xcalloc(1, sizeof(struct Asset))); a->file = FreeLater(xmalloc(sizeof(struct File))); for (i = 0; i < stagedirs.n; ++i) { - LockInc(&shared->stats); + LockInc(&shared->c.stats); a->file->path = FreeLater( MergePaths(stagedirs.p[i], strlen(stagedirs.p[i]), path, pathlen, 0)); if (stat(a->file->path, &a->file->st) != -1) { @@ -1503,7 +1409,7 @@ static struct Asset *GetAssetFile(const char *path, size_t pathlen) { (a->lastmodified = a->file->st.st_mtim.tv_sec)); return a; } else { - LockInc(&shared->statfails); + LockInc(&shared->c.statfails); } } } @@ -1525,21 +1431,6 @@ static struct Asset *GetAsset(const char *path, size_t pathlen) { return a; } -static bool MustNotIncludeMessageBody(void) { /* RFC2616 § 4.4 */ - return msg.method == kHttpHead || (100 <= statuscode && statuscode <= 199) || - statuscode == 204 || statuscode == 304; -} - -static char *SetStatus(unsigned code, const char *reason) { - statuscode = code; - stpcpy(hdrbuf.p, "HTTP/1.0 000 "); - hdrbuf.p[7] += msg.version & 1; - hdrbuf.p[9] += code / 100; - hdrbuf.p[10] += code / 10 % 10; - hdrbuf.p[11] += code % 10; - return AppendCrlf(stpcpy(hdrbuf.p + 13, reason)); -} - static char *AppendHeader(char *p, const char *k, const char *v) { if (!v) return p; return AppendCrlf(stpcpy(stpcpy(stpcpy(p, k), ": "), v)); @@ -1578,13 +1469,13 @@ static char *AppendCache(char *p, int64_t seconds) { return AppendExpires(p, (int64_t)shared->nowish + seconds); } -static char *AppendServer(char *p, const char *s) { +static inline char *AppendServer(char *p, const char *s) { p = stpcpy(p, "Server: "); p = stpcpy(p, s); return AppendCrlf(p); } -static char *AppendContentLength(char *p, size_t n) { +static inline char *AppendContentLength(char *p, size_t n) { p = stpcpy(p, "Content-Length: "); p += uint64toarray_radix10(n, p); return AppendCrlf(p); @@ -1606,7 +1497,7 @@ static char *AppendContentRange(char *p, long a, long b, long c) { static bool Inflate(void *dp, size_t dn, const void *sp, size_t sn) { z_stream zs; - LockInc(&shared->inflates); + LockInc(&shared->c.inflates); zs.next_in = sp; zs.avail_in = sn; zs.total_in = sn; @@ -1637,11 +1528,11 @@ static bool Inflate(void *dp, size_t dn, const void *sp, size_t sn) { static bool Verify(void *data, size_t size, uint32_t crc) { uint32_t got; - LockInc(&shared->verifies); + LockInc(&shared->c.verifies); if (crc == (got = crc32_z(0, data, size))) { return true; } else { - LockInc(&shared->thiscorruption); + LockInc(&shared->c.thiscorruption); WARNF("corrupt zip file at %`'.*s had crc 0x%08x but expected 0x%08x", msg.uri.b - msg.uri.a, inbuf.p + msg.uri.a, got, crc); return false; @@ -1651,7 +1542,7 @@ static bool Verify(void *data, size_t size, uint32_t crc) { static void *Deflate(const void *data, size_t size, size_t *out_size) { void *res; z_stream zs; - LockInc(&shared->deflates); + LockInc(&shared->c.deflates); CHECK_EQ(Z_OK, deflateInit2(memset(&zs, 0, sizeof(zs)), 4, Z_DEFLATED, -MAX_WBITS, DEF_MEM_LEVEL, Z_DEFAULT_STRATEGY)); zs.next_in = data; @@ -1677,7 +1568,7 @@ static void *LoadAsset(struct Asset *a, size_t *out_size) { if (size == SIZE_MAX || !(data = malloc(size + 1))) return NULL; if (IsCompressed(a)) { if (!Inflate(data, size, ZIP_LFILE_CONTENT(zmap + a->lf), - GetZipLfileCompressedSize(zmap + a->lf))) { + GetZipCfileCompressedSize(zmap + a->cf))) { free(data); return NULL; } @@ -1692,7 +1583,7 @@ static void *LoadAsset(struct Asset *a, size_t *out_size) { if (out_size) *out_size = size; return data; } else { - LockInc(&shared->slurps); + LockInc(&shared->c.slurps); return xslurp(a->file->path, out_size); } } @@ -1715,13 +1606,13 @@ static inline ssize_t Send(struct iovec *iov, int iovlen) { ssize_t rc; if ((rc = WritevAll(client, iov, iovlen)) == -1) { if (errno == ECONNRESET) { - LockInc(&shared->writeresets); + LockInc(&shared->c.writeresets); DEBUGF("%s write reset", DescribeClient()); } else if (errno == EAGAIN) { - LockInc(&shared->writetimeouts); + LockInc(&shared->c.writetimeouts); WARNF("%s write timeout", DescribeClient()); } else { - LockInc(&shared->writeerrors); + LockInc(&shared->c.writeerrors); WARNF("%s write error %s", DescribeClient(), strerror(errno)); } connectionclose = true; @@ -1778,7 +1669,7 @@ static char *ServeErrorImpl(unsigned code, const char *reason) { size_t n; char *p, *s; struct Asset *a; - LockInc(&shared->errors); + LockInc(&shared->c.errors); ClearOutput(); p = SetStatus(code, reason); s = xasprintf("/%d.html", code); @@ -1787,12 +1678,12 @@ static char *ServeErrorImpl(unsigned code, const char *reason) { if (!a) { return ServeDefaultErrorPage(p, code, reason); } else if (a->file) { - LockInc(&shared->slurps); + LockInc(&shared->c.slurps); content = FreeLater(xslurp(a->file->path, &contentlength)); return AppendContentType(p, "text/html; charset=utf-8"); } else { content = (char *)ZIP_LFILE_CONTENT(zmap + a->lf); - contentlength = GetZipLfileCompressedSize(zmap + a->lf); + contentlength = GetZipCfileCompressedSize(zmap + a->cf); if (IsCompressed(a)) { n = GetZipLfileUncompressedSize(zmap + a->lf); if ((s = FreeLater(malloc(n))) && Inflate(s, n, content, contentlength)) { @@ -1827,7 +1718,7 @@ static char *ServeFailure(unsigned code, const char *reason) { static char *ServeAssetCompressed(struct Asset *a) { uint32_t crc; - LockInc(&shared->compressedresponses); + LockInc(&shared->c.compressedresponses); DEBUGF("ServeAssetCompressed()"); gzipped = true; crc = crc32_z(0, content, contentlength); @@ -1837,33 +1728,42 @@ static char *ServeAssetCompressed(struct Asset *a) { return SetStatus(200, "OK"); } -static char *ServeAssetPrecompressed(struct Asset *a) { +static char *ServeAssetDecompressed(struct Asset *a) { char *buf; size_t size; - uint32_t crc; - crc = ZIP_LFILE_CRC32(zmap + a->lf); - size = GetZipLfileUncompressedSize(zmap + a->lf); - if (ClientAcceptsGzip()) { - LockInc(&shared->precompressedresponses); - DEBUGF("ServeAssetPrecompressed()"); - gzipped = true; - WRITE32LE(gzip_footer + 0, crc); - WRITE32LE(gzip_footer + 4, size); + DEBUGF("ServeAssetDecompressed()"); + size = GetZipCfileUncompressedSize(zmap + a->cf); + LockInc(&shared->c.decompressedresponses); + if ((buf = FreeLater(malloc(size))) && + Inflate(buf, size, content, contentlength) && + Verify(buf, size, ZIP_CFILE_CRC32(zmap + a->cf))) { + content = buf; + contentlength = size; return SetStatus(200, "OK"); } else { - LockInc(&shared->decompressedresponses); - DEBUGF("ServeAssetDecompressed()"); - if ((buf = FreeLater(malloc(size))) && - Inflate(buf, size, content, contentlength) && Verify(buf, size, crc)) { - content = buf; - contentlength = size; - return SetStatus(200, "OK"); - } else { - return ServeError(500, "Internal Server Error"); - } + return ServeError(500, "Internal Server Error"); } } +static inline char *ServeAssetIdentity(struct Asset *a, const char *ct) { + LockInc(&shared->c.identityresponses); + DEBUGF("ServeAssetIdentity(%`'s)", ct); + return SetStatus(200, "OK"); +} + +static inline char *ServeAssetPrecompressed(struct Asset *a) { + size_t size; + uint32_t crc; + DEBUGF("ServeAssetPrecompressed()"); + LockInc(&shared->c.precompressedresponses); + gzipped = true; + crc = ZIP_CFILE_CRC32(zmap + a->cf); + size = GetZipCfileUncompressedSize(zmap + a->cf); + WRITE32LE(gzip_footer + 0, crc); + WRITE32LE(gzip_footer + 4, size); + return SetStatus(200, "OK"); +} + static char *ServeAssetRange(struct Asset *a) { char *p; long rangestart, rangelength; @@ -1872,14 +1772,14 @@ static char *ServeAssetRange(struct Asset *a) { contentlength, &rangestart, &rangelength) && rangestart >= 0 && rangelength >= 0 && rangestart < contentlength && rangestart + rangelength <= contentlength) { - LockInc(&shared->partialresponses); + LockInc(&shared->c.partialresponses); p = SetStatus(206, "Partial Content"); p = AppendContentRange(p, rangestart, rangelength, contentlength); content += rangestart; contentlength = rangelength; return p; } else { - LockInc(&shared->badranges); + LockInc(&shared->c.badranges); LOGF("bad range %`'.*s", HeaderLength(kHttpRange), HeaderData(kHttpRange)); p = SetStatus(416, "Range Not Satisfiable"); p = AppendContentRange(p, -1, -1, contentlength); @@ -1889,87 +1789,6 @@ static char *ServeAssetRange(struct Asset *a) { } } -static char *ServeAsset(struct Asset *a, const char *path, size_t pathlen) { - int fd; - char *p; - void *data; - size_t size; - uint32_t crc; - const char *ct; - ct = GetContentType(a, path, pathlen); - if (IsNotModified(a)) { - LockInc(&shared->notmodifieds); - p = SetStatus(304, "Not Modified"); - } else { - if (!a->file) { - content = (char *)ZIP_LFILE_CONTENT(zmap + a->lf); - contentlength = GetZipLfileCompressedSize(zmap + a->lf); - } else if (a->file->st.st_size) { - size = a->file->st.st_size; - if ((fd = open(a->file->path, O_RDONLY)) != -1) { - data = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0); - if (data != MAP_FAILED) { - LockInc(&shared->maps); - UnmapLater(fd, data, size); - content = data; - contentlength = size; - } else { - LockInc(&shared->mapfails); - WARNF("mmap(%`'s) failed %s", a->file->path, strerror(errno)); - close(fd); - return ServeError(500, "Internal Server Error"); - } - } else { - LockInc(&shared->openfails); - WARNF("open(%`'s) failed %s", a->file->path, strerror(errno)); - if (errno == ENFILE) { - LockInc(&shared->enfiles); - return ServeError(503, "Service Unavailable"); - } else if (errno == EMFILE) { - LockInc(&shared->emfiles); - return ServeError(503, "Service Unavailable"); - } else { - return ServeError(500, "Internal Server Error"); - } - } - } else { - content = ""; - contentlength = 0; - } - if (IsCompressed(a)) { - p = ServeAssetPrecompressed(a); - } else if (msg.version >= 11 && HasHeader(kHttpRange)) { - p = ServeAssetRange(a); - } else if (!a->file) { - LockInc(&shared->identityresponses); - DEBUGF("ServeAssetZipIdentity(%`'s)", ct); - if (Verify(content, contentlength, ZIP_LFILE_CRC32(zmap + a->lf))) { - p = SetStatus(200, "OK"); - } else { - return ServeError(500, "Internal Server Error"); - } - } else if (ClientAcceptsGzip() && - (strlen(ct) >= 5 && !memcasecmp(ct, "text/", 5)) && - contentlength < 1024 * 1024 * 1024) { - p = ServeAssetCompressed(a); - } else { - LockInc(&shared->identityresponses); - DEBUGF("ServeAssetIdentity(%`'s)", ct); - p = SetStatus(200, "OK"); - } - } - p = AppendContentType(p, ct); - p = stpcpy(p, "Vary: Accept-Encoding\r\n"); - p = AppendHeader(p, "Last-Modified", a->lastmodifiedstr); - if (msg.version >= 11) { - p = AppendCache(p, cacheseconds); - if (!IsCompressed(a)) { - p = stpcpy(p, "Accept-Ranges: bytes\r\n"); - } - } - return p; -} - static char *GetAssetPath(uint64_t cf, size_t *out_size) { char *p1, *p2; size_t n1, n2; @@ -2064,7 +1883,7 @@ static void LaunchBrowser(const char *path) { } static char *BadMethod(void) { - LockInc(&shared->badmethods); + LockInc(&shared->c.badmethods); return stpcpy(ServeError(405, "Method Not Allowed"), "Allow: GET, HEAD\r\n"); } @@ -2101,7 +1920,7 @@ static char *ServeListing(void) { char *p, *q, *path; char rb[8], tb[64], *rp[6]; size_t i, n, pathlen, rn[6]; - LockInc(&shared->listingrequests); + LockInc(&shared->c.listingrequests); if (msg.method != kHttpGet && msg.method != kHttpHead) return BadMethod(); Append("\ \r\n\ @@ -2184,7 +2003,7 @@ td { padding-right: 3em; }\r\n\ Append("\r\n"); Append("
\r\n"); Append("/statusz\r\n"); - if (shared->connectionshandled) { + if (shared->c.connectionshandled) { Append("says your redbean
\r\n"); AppendResourceReport(&shared->children, "
\r\n"); } @@ -2208,9 +2027,9 @@ td { padding-right: 3em; }\r\n\ } Append("%s%,ld second%s of operation
\r\n", and, y.rem, y.rem == 1 ? "" : "s"); - x = shared->messageshandled; + x = shared->c.messageshandled; Append("%,ld message%s handled
\r\n", x, x == 1 ? "" : "s"); - x = shared->connectionshandled; + x = shared->c.connectionshandled; Append("%,ld connection%s handled
\r\n", x, x == 1 ? "" : "s"); x = shared->workers; Append("%,ld connection%s active
\r\n", x, x == 1 ? "" : "s"); @@ -2258,9 +2077,18 @@ static void AppendRusage(const char *a, struct rusage *ru) { AppendLong2(a, "ru_nivcsw", ru->ru_nivcsw); } +static void ServeCounters(void) { + const long *c; + const char *s; + for (c = (const long *)&shared->c, s = kCounterNames; *s; + ++c, s += strlen(s) + 1) { + AppendLong1(s, *c); + } +} + static char *ServeStatusz(void) { char *p; - LockInc(&shared->statuszrequests); + LockInc(&shared->c.statuszrequests); if (msg.method != kHttpGet && msg.method != kHttpHead) return BadMethod(); AppendLong1("pid", getpid()); AppendLong1("ppid", getppid()); @@ -2273,100 +2101,7 @@ static char *ServeStatusz(void) { AppendLong1("lastmeltdown", shared->lastmeltdown); AppendLong1("workers", shared->workers); AppendLong1("assets.n", assets.n); - AppendLong1("accepterrors", shared->accepterrors); - AppendLong1("acceptinterrupts", shared->acceptinterrupts); - AppendLong1("acceptresets", shared->acceptresets); - AppendLong1("badlengths", shared->badlengths); - AppendLong1("badmessages", shared->badmessages); - AppendLong1("badmethods", shared->badmethods); - AppendLong1("badranges", shared->badranges); - AppendLong1("closeerrors", shared->closeerrors); - AppendLong1("compressedresponses", shared->compressedresponses); - AppendLong1("connectionshandled", shared->connectionshandled); - AppendLong1("connectsrefused", shared->connectsrefused); - AppendLong1("continues", shared->continues); - AppendLong1("decompressedresponses", shared->decompressedresponses); - AppendLong1("deflates", shared->deflates); - AppendLong1("dropped", shared->dropped); - AppendLong1("dynamicrequests", shared->dynamicrequests); - AppendLong1("emfiles", shared->emfiles); - AppendLong1("enetdowns", shared->enetdowns); - AppendLong1("enfiles", shared->enfiles); - AppendLong1("enobufs", shared->enobufs); - AppendLong1("enomems", shared->enomems); - AppendLong1("enonets", shared->enonets); - AppendLong1("errors", shared->errors); - AppendLong1("expectsrefused", shared->expectsrefused); - AppendLong1("failedchildren", shared->failedchildren); - AppendLong1("forbiddens", shared->forbiddens); - AppendLong1("forkerrors", shared->forkerrors); - AppendLong1("frags", shared->frags); - AppendLong1("fumbles", shared->fumbles); - AppendLong1("http09", shared->http09); - AppendLong1("http10", shared->http10); - AppendLong1("http11", shared->http11); - AppendLong1("http12", shared->http12); - AppendLong1("hugepayloads", shared->hugepayloads); - AppendLong1("identityresponses", shared->identityresponses); - AppendLong1("inflates", shared->inflates); - AppendLong1("listingrequests", shared->listingrequests); - AppendLong1("loops", shared->loops); - AppendLong1("mapfails", shared->mapfails); - AppendLong1("maps", shared->maps); - AppendLong1("meltdowns", shared->meltdowns); - AppendLong1("messageshandled", shared->messageshandled); - AppendLong1("missinglengths", shared->missinglengths); - AppendLong1("netafrinic", shared->netafrinic); - AppendLong1("netanonymous", shared->netanonymous); - AppendLong1("netapnic", shared->netapnic); - AppendLong1("netapple", shared->netapple); - AppendLong1("netarin", shared->netarin); - AppendLong1("netatt", shared->netatt); - AppendLong1("netcogent", shared->netcogent); - AppendLong1("netcomcast", shared->netcomcast); - AppendLong1("netdod", shared->netdod); - AppendLong1("netford", shared->netford); - AppendLong1("netlacnic", shared->netlacnic); - AppendLong1("netloopback", shared->netloopback); - AppendLong1("netother", shared->netother); - AppendLong1("netprivate", shared->netprivate); - AppendLong1("netprudential", shared->netprudential); - AppendLong1("netripe", shared->netripe); - AppendLong1("nettestnet", shared->nettestnet); - AppendLong1("netusps", shared->netusps); - AppendLong1("notfounds", shared->notfounds); - AppendLong1("notmodifieds", shared->notmodifieds); - AppendLong1("openfails", shared->openfails); - AppendLong1("partialresponses", shared->partialresponses); - AppendLong1("payloaddisconnects", shared->payloaddisconnects); - AppendLong1("pipelinedrequests", shared->pipelinedrequests); - AppendLong1("precompressedresponses", shared->precompressedresponses); - AppendLong1("readerrors", shared->readerrors); - AppendLong1("readinterrupts", shared->readinterrupts); - 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); - AppendLong1("shutdowns", shared->shutdowns); - AppendLong1("slowloris", shared->slowloris); - AppendLong1("slurps", shared->slurps); - AppendLong1("statfails", shared->statfails); - AppendLong1("staticrequests", shared->staticrequests); - AppendLong1("stats", shared->stats); - AppendLong1("statuszrequests", shared->statuszrequests); - AppendLong1("synchronizationfailures", shared->synchronizationfailures); - AppendLong1("terminatedchildren", shared->terminatedchildren); - AppendLong1("thiscorruption", shared->thiscorruption); - AppendLong1("transfersrefused", shared->transfersrefused); - AppendLong1("urisrefused", shared->urisrefused); - AppendLong1("verifies", shared->verifies); - AppendLong1("writeerrors", shared->writeerrors); - AppendLong1("writeinterruputs", shared->writeinterruputs); - AppendLong1("writeresets", shared->writeresets); - AppendLong1("writetimeouts", shared->writetimeouts); + ServeCounters(); AppendRusage("server", &shared->server); AppendRusage("children", &shared->children); p = SetStatus(200, "OK"); @@ -2378,7 +2113,7 @@ static char *ServeStatusz(void) { static char *RedirectSlash(void) { size_t n; char *p, *e; - LockInc(&shared->redirects); + LockInc(&shared->c.redirects); p = SetStatus(307, "Temporary Redirect"); p = stpcpy(p, "Location: "); e = EscapePath(url.path.p, url.path.n, &n); @@ -2388,7 +2123,6 @@ static char *RedirectSlash(void) { return p; } -static char *RoutePath(const char *, size_t); static char *ServeIndex(const char *path, size_t pathlen) { size_t i, n; char *p, *q; @@ -2401,14 +2135,6 @@ static char *ServeIndex(const char *path, size_t pathlen) { return p; } -static bool IsLua(struct Asset *a) { - if (a->file) return endswith(a->file->path, ".lua"); - return ZIP_LFILE_NAMESIZE(zmap + a->lf) >= 4 && - !memcmp(ZIP_LFILE_NAME(zmap + a->lf) + - ZIP_LFILE_NAMESIZE(zmap + a->lf) - 4, - ".lua", 4); -} - static char *GetLuaResponse(void) { char *p; if (!(p = luaheaderp)) { @@ -2418,10 +2144,24 @@ static char *GetLuaResponse(void) { return p; } -static char *ServeLua(struct Asset *a, const char *path, size_t pathlen) { +static char *LuaOnHttpRequest(void) { + effectivepath.p = url.path.p; + effectivepath.n = url.path.n; + lua_getglobal(L, "OnHttpRequest"); + if (lua_pcall(L, 0, 0, 0) == LUA_OK) { + return CommitOutput(GetLuaResponse()); + } else { + WARNF("%s", lua_tostring(L, -1)); + lua_pop(L, 1); + return ServeError(500, "Internal Server Error"); + } +} + +static char *ServeLua(struct Asset *a, const char *s, size_t n) { char *code; - effectivepath.p = path; - effectivepath.n = pathlen; + LockInc(&shared->c.dynamicrequests); + effectivepath.p = s; + effectivepath.n = n; if ((code = FreeLater(LoadAsset(a, NULL)))) { if (luaL_dostring(L, code) == LUA_OK) { return CommitOutput(GetLuaResponse()); @@ -2433,39 +2173,23 @@ static char *ServeLua(struct Asset *a, const char *path, size_t pathlen) { return ServeError(500, "Internal Server Error"); } -static char *HandleAsset(struct Asset *a, const char *path, size_t pathlen) { -#ifndef STATIC - if (IsLua(a)) { - LockInc(&shared->dynamicrequests); - return ServeLua(a, path, pathlen); - } -#endif - if (msg.method == kHttpGet || msg.method == kHttpHead) { - LockInc(&shared->staticrequests); - return stpcpy(ServeAsset(a, path, pathlen), - "X-Content-Type-Options: nosniff\r\n"); - } else { - return BadMethod(); - } -} - static char *HandleRedirect(struct Redirect *r) { int code; struct Asset *a; if (!r->code && (a = GetAsset(r->location, strlen(r->location)))) { - LockInc(&shared->rewrites); + LockInc(&shared->c.rewrites); DEBUGF("rewriting to %`'s", r->location); if (!HasString(&loops, r->location)) { AddString(&loops, r->location); return RoutePath(r->location, strlen(r->location)); } else { - LockInc(&shared->loops); + LockInc(&shared->c.loops); return SetStatus(508, "Loop Detected"); } } else if (msg.version < 10) { return ServeError(505, "HTTP Version Not Supported"); } else { - LockInc(&shared->redirects); + LockInc(&shared->c.redirects); code = r->code; if (!code) code = 307; DEBUGF("%d redirecting %`'s", code, r->location); @@ -2483,81 +2207,15 @@ static char *HandleFolder(const char *path, size_t pathlen) { if ((p = ServeIndex(path, pathlen))) { return p; } else { - LockInc(&shared->forbiddens); + LockInc(&shared->c.forbiddens); LOGF("directory %`'.*s lacks index page", pathlen, path); return ServeError(403, "Forbidden"); } } -static char *RoutePath(const char *path, size_t pathlen) { - int m; - long r; - char *p; - struct Asset *a; - DEBUGF("RoutePath(%`'.*s)", pathlen, path); - if ((a = GetAsset(path, pathlen))) { - if ((m = GetMode(a)) & 0004) { - if (!S_ISDIR(m)) { - return HandleAsset(a, path, pathlen); - } else { - return HandleFolder(path, pathlen); - } - } else { - LockInc(&shared->forbiddens); - LOGF("asset %`'.*s %#o isn't readable", pathlen, path, m); - return ServeError(403, "Forbidden"); - } - } else if ((r = FindRedirect(path, pathlen)) != -1) { - return HandleRedirect(redirects.p + r); - } else { - return NULL; - } -} - -static char *RouteHost(const char *host, size_t hostlen, const char *path, - size_t pathlen) { - size_t hn; - char *hp, *p; - hn = 1 + hostlen + url.path.n; - hp = FreeLater(xmalloc(3 + 1 + hn)); - hp[0] = '/'; - mempcpy(mempcpy(hp + 1, host, hostlen), path, pathlen); - if ((p = RoutePath(hp, hn))) return p; - if (ParseIp(host, hostlen) == -1) { - if (hostlen > 4 && !memcmp(host, "www.", 4)) { - mempcpy(mempcpy(hp + 1, host + 4, hostlen - 4), path, pathlen); - if ((p = RoutePath(hp, hn - 4))) return p; - } else { - mempcpy(mempcpy(mempcpy(hp + 1, "www.", 4), host, hostlen), path, - pathlen); - if ((p = RoutePath(hp, hn + 4))) return p; - } - } - return NULL; -} - -static char *Route(const char *host, size_t hostlen, const char *path, - size_t pathlen) { - char *p; - if (hostlen && (p = RouteHost(host, hostlen, path, pathlen))) { - return p; - } - if (SlicesEqual(path, pathlen, "/", 1)) { - if ((p = ServeIndex("/", 1))) return p; - return ServeListing(); - } else if ((p = RoutePath(path, pathlen))) { - return p; - } else if (SlicesEqual(path, pathlen, "/statusz", 8)) { - return ServeStatusz(); - } else { - LockInc(&shared->notfounds); - return ServeError(404, "Not Found"); - } -} - static void Reindex(void) { - LockInc(&shared->reindexes); - LOGF("reindexing"); + LockInc(&shared->c.reindexes); + DEBUGF("reindexing"); OpenZip(); IndexAssets(); } @@ -2709,6 +2367,38 @@ static int LuaLoadAsset(lua_State *L) { return 0; } +static bool IsUtf8(const void *data, size_t size) { + const unsigned char *p, *pe; + for (p = data, pe = p + size; p + 2 <= pe; ++p) { + if (*p >= 0300) { + if (*p >= 0200 && *p < 0300) { + return true; + } else { + return false; + } + } + } + return false; +} + +static bool IsText(const void *data, size_t size) { + const unsigned char *p, *pe; + for (p = data, pe = p + size; p < pe; ++p) { + if (*p <= 3) { + return false; + } + } + return true; +} + +static void GetDosLocalTime(int64_t utcunixts, uint16_t *out_time, + uint16_t *out_date) { + 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 + 1); +} + static int LuaGetDate(lua_State *L) { lua_pushinteger(L, shared->nowish); return 1; @@ -3249,8 +2939,8 @@ static int LuaIsAcceptablePort(lua_State *L) { return LuaIsValid(L, IsAcceptablePort); } -static noinline int LuaCoderImpl(lua_State *L, - char *Coder(const char *, size_t, size_t *)) { +static int LuaCoderImpl(lua_State *L, + char *Coder(const char *, size_t, size_t *)) { void *p; size_t n; p = luaL_checklstring(L, 1, &n); @@ -3260,8 +2950,7 @@ static noinline int LuaCoderImpl(lua_State *L, return 1; } -static noinline int LuaCoder(lua_State *L, - char *Coder(const char *, size_t, size_t *)) { +static int LuaCoder(lua_State *L, char *Coder(const char *, size_t, size_t *)) { return LuaCoderImpl(L, Coder); } @@ -3411,7 +3100,7 @@ static int LuaCrc32c(lua_State *L) { return LuaHash(L, crc32c); } -static noinline int LuaProgramInt(lua_State *L, void Program(long)) { +static int LuaProgramInt(lua_State *L, void Program(long)) { Program(luaL_checkinteger(L, 1)); return 0; } @@ -3901,36 +3590,23 @@ static void LuaReload(void) { #endif } -static void HandleReload(void) { - LockInc(&shared->reloads); - LOGF("reloading"); - Reindex(); - LuaReload(); +static const char *DescribeClose(void) { + if (killed) return "killed"; + if (meltdown) return "meltdown"; + if (terminated) return "terminated"; + if (connectionclose) return "connectionclose"; + return "destroyed"; } -static bool ZipCdirChanged(void) { - struct stat st; - if (!IsZipCdir32(zmap, zsize, zcdir - zmap) && - !IsZipCdir64(zmap, zsize, zcdir - zmap)) { - return true; +static void LogClose(const char *reason) { + if (amtread || meltdown || killed) { + LockInc(&shared->c.fumbles); + LOGF("%s %s with %,ld unprocessed and %,d handled (%,d workers)", + DescribeClient(), reason, amtread, messageshandled, shared->workers); + } else { + DEBUGF("%s %s with %,d requests handled", DescribeClient(), reason, + messageshandled); } - 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"); -#endif } static void LogMessage(const char *d, const char *s, size_t n) { @@ -3994,88 +3670,184 @@ Content-Length: 0\r\n\ \r\n"); } -static void LogClose(const char *reason) { - if (amtread || meltdown || killed) { - LockInc(&shared->fumbles); - LOGF("%s %s with %,ld unprocessed and %,d handled (%,d workers)", - DescribeClient(), reason, amtread, messageshandled, shared->workers); +static void EnterMeltdownMode(void) { + if (shared->lastmeltdown && nowl() - shared->lastmeltdown < 1) return; + WARNF("redbean is melting down (%,d workers)", shared->workers); + LOGIFNEG1(kill(0, SIGUSR2)); + shared->lastmeltdown = nowl(); + ++shared->c.meltdowns; +} + +static void EmergencyClose(int fd) { + struct linger nolinger = {0}; + setsockopt(fd, SOL_SOCKET, SO_LINGER, &nolinger, sizeof(nolinger)); + close(fd); +} + +static char *HandlePayloadDisconnect(void) { + LockInc(&shared->c.payloaddisconnects); + LogClose("payload disconnect"); + return ServeFailure(400, "Bad Request"); /* XXX */ +} + +static char *HandlePayloadDrop(void) { + LockInc(&shared->c.dropped); + LogClose(DescribeClose()); + return ServeFailure(503, "Service Unavailable"); +} + +static char *HandleBadContentLength(void) { + LockInc(&shared->c.badlengths); + return ServeFailure(400, "Bad Content Length"); +} + +static char *HandleLengthRequired(void) { + LockInc(&shared->c.missinglengths); + return ServeFailure(411, "Length Required"); +} + +static char *HandleVersionNotSupported(void) { + LockInc(&shared->c.http12); + return ServeFailure(505, "HTTP Version Not Supported"); +} + +static char *HandleTransferRefused(void) { + LockInc(&shared->c.transfersrefused); + return ServeFailure(501, "Not Implemented"); +} + +static char *HandleConnectRefused(void) { + LockInc(&shared->c.connectsrefused); + return ServeFailure(501, "Not Implemented"); +} + +static char *HandleExpectFailed(void) { + LockInc(&shared->c.expectsrefused); + return ServeFailure(417, "Expectation Failed"); +} + +static char *HandleHugePayload(void) { + LockInc(&shared->c.hugepayloads); + return ServeFailure(413, "Payload Too Large"); +} + +static char *HandlePayloadSlowloris(void) { + LockInc(&shared->c.slowloris); + LogClose("payload slowloris"); + return ServeFailure(408, "Request Timeout"); +} + +static char *HandleMapFailed(struct Asset *a, int fd) { + LockInc(&shared->c.mapfails); + WARNF("mmap(%`'s) failed %s", a->file->path, strerror(errno)); + close(fd); + return ServeError(500, "Internal Server Error"); +} + +static char *HandleOpenFail(struct Asset *a) { + LockInc(&shared->c.openfails); + WARNF("open(%`'s) failed %s", a->file->path, strerror(errno)); + if (errno == ENFILE) { + LockInc(&shared->c.enfiles); + return ServeError(503, "Service Unavailable"); + } else if (errno == EMFILE) { + LockInc(&shared->c.emfiles); + return ServeError(503, "Service Unavailable"); } else { - DEBUGF("%s %s with %,d requests handled", DescribeClient(), reason, - messageshandled); + return ServeError(500, "Internal Server Error"); } } -static const char *DescribeClose(void) { - if (killed) return "killed"; - if (meltdown) return "meltdown"; - if (terminated) return "terminated"; - if (connectionclose) return "connectionclose"; - return "destroyed"; -} - -static void RecordNetworkOrigin(void) { - uint32_t ip; - GetRemoteAddr(&ip, 0); - switch (CategorizeIp(ip)) { - case kIpLoopback: - LockInc(&shared->netloopback); - break; - case kIpPrivate: - LockInc(&shared->netprivate); - break; - case kIpTestnet: - LockInc(&shared->nettestnet); - break; - case kIpAfrinic: - LockInc(&shared->netafrinic); - break; - case kIpLacnic: - LockInc(&shared->netlacnic); - break; - case kIpApnic: - LockInc(&shared->netapnic); - break; - case kIpArin: - LockInc(&shared->netarin); - break; - case kIpRipe: - LockInc(&shared->netripe); - break; - case kIpDod: - LockInc(&shared->netdod); - break; - case kIpAtt: - LockInc(&shared->netatt); - break; - case kIpApple: - LockInc(&shared->netapple); - break; - case kIpFord: - LockInc(&shared->netford); - break; - case kIpCogent: - LockInc(&shared->netcogent); - break; - case kIpPrudential: - LockInc(&shared->netprudential); - break; - case kIpUsps: - LockInc(&shared->netusps); - break; - case kIpComcast: - LockInc(&shared->netcomcast); - break; - case kIpAnonymous: - LockInc(&shared->netanonymous); - break; - default: - LockInc(&shared->netother); - break; +static char *HandlePayloadReadError(void) { + if (errno == ECONNRESET) { + LockInc(&shared->c.readresets); + LogClose("payload reset"); + return ServeFailure(400, "Bad Request"); /* XXX */ + } else if (errno == EAGAIN || errno == EWOULDBLOCK) { + LockInc(&shared->c.readtimeouts); + LogClose("payload read timeout"); + return ServeFailure(408, "Request Timeout"); + } else { + LockInc(&shared->c.readerrors); + LOGF("%s payload read error %s", DescribeClient(), strerror(errno)); + return ServeFailure(500, "Internal Server Error"); } } -char *ServeServerOptions(void) { +static void HandleForkFailure(void) { + FATALF("%s too many processes %s", DescribeServer(), strerror(errno)); + LockInc(&shared->c.forkerrors); + LockInc(&shared->c.dropped); + EnterMeltdownMode(); + SendServiceUnavailable(); + EmergencyClose(client); +} + +static void HandleFrag(size_t got) { + LockInc(&shared->c.frags); + if (++frags == 32) { + SendTimeout(); + LogClose("slowloris"); + LockInc(&shared->c.slowloris); + return; + } else { + DEBUGF("%s fragged msg added %,ld bytes to %,ld byte buffer", + DescribeClient(), amtread, got); + } +} + +static void HandleReload(void) { + LockInc(&shared->c.reloads); + DEBUGF("reloading"); + Reindex(); + LuaReload(); +} + +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"); +#endif +} + +static char *OpenAsset(struct Asset *a) { + int fd; + void *data; + size_t size; + if (a->file->st.st_size) { + size = a->file->st.st_size; + OpenAgain: + if ((fd = open(a->file->path, O_RDONLY)) != -1) { + data = mmap(0, size, PROT_READ, MAP_PRIVATE, fd, 0); + if (data != MAP_FAILED) { + LockInc(&shared->c.maps); + UnmapLater(fd, data, size); + content = data; + contentlength = size; + } else { + return HandleMapFailed(a, fd); + } + } else if (errno == EINTR) { + goto OpenAgain; + } else { + return HandleOpenFail(a); + } + } else { + content = ""; + contentlength = 0; + } + return 0; +} + +static char *ServeServerOptions(void) { char *p; + LockInc(&shared->c.serveroptions); p = SetStatus(200, "OK"); #ifdef STATIC p = stpcpy(p, "Allow: GET, HEAD, OPTIONS\r\n"); @@ -4086,85 +3858,40 @@ char *ServeServerOptions(void) { #endif return p; } -static bool HasAtMostThisElement(int h, const char *s) { - size_t i, n; - struct HttpRequestHeader *x; - if (HasHeader(h)) { - n = strlen(s); - if (!SlicesEqualCase(s, n, inbuf.p + msg.headers[h].a, - msg.headers[h].b - msg.headers[h].a)) { - return false; - } - for (i = 0; i < msg.xheaders.n; ++i) { - x = msg.xheaders.p + i; - if (GetHttpHeader(inbuf.p + x->k.a, x->k.b - x->k.a) == h && - !SlicesEqualCase(inbuf.p + x->v.a, x->v.b - x->v.a, s, n)) { - return false; - } - } - } - return true; -} -static char *SynchronizeStream(void) { +char *SynchronizeStream(void) { size_t got; ssize_t rc; int64_t cl; - if ((cl = ParseContentLength(HeaderData(kHttpContentLength), - HeaderLength(kHttpContentLength))) == -1) { - if (HasHeader(kHttpContentLength)) { - LockInc(&shared->badlengths); - LOGF("bad content length"); - return ServeFailure(400, "Bad Request"); - } else if (msg.method == kHttpPost || msg.method == kHttpPut) { - LockInc(&shared->missinglengths); - return ServeFailure(411, "Length Required"); - } else { - cl = 0; + if (HasHeader(kHttpContentLength)) { + if ((cl = ParseContentLength(HeaderData(kHttpContentLength), + HeaderLength(kHttpContentLength))) == -1) { + return HandleBadContentLength(); } + } else if (msg.method == kHttpPost || msg.method == kHttpPut) { + return HandleLengthRequired(); + } else { + cl = 0; } if (hdrsize + cl > amtread) { - if (hdrsize + cl > inbuf.n) { - LockInc(&shared->hugepayloads); - return ServeFailure(413, "Payload Too Large"); - } + if (hdrsize + cl > inbuf.n) HandleHugePayload(); if (msg.version >= 11 && HeaderEqualCase(kHttpExpect, "100-continue")) { - LockInc(&shared->continues); + LockInc(&shared->c.continues); SendContinue(); } while (amtread < hdrsize + cl) { - LockInc(&shared->frags); - if (++frags == 64) { - LockInc(&shared->slowloris); - LogClose("payload slowloris"); - return ServeFailure(408, "Request Timeout"); - } + LockInc(&shared->c.frags); + if (++frags == 64) HandlePayloadSlowloris(); if ((rc = read(client, inbuf.p + amtread, inbuf.n - amtread)) != -1) { - if (!(got = rc)) { - LockInc(&shared->payloaddisconnects); - LogClose("payload disconnect"); - return ServeFailure(400, "Bad Request"); /* XXX */ - } + if (!(got = rc)) return HandlePayloadDisconnect(); amtread += got; - } else if (errno == ECONNRESET) { - LockInc(&shared->readresets); - LogClose("payload reset"); - return ServeFailure(400, "Bad Request"); /* XXX */ - } else if (errno == EAGAIN || errno == EWOULDBLOCK) { - LockInc(&shared->readtimeouts); - LogClose("payload read timeout"); - return ServeFailure(408, "Request Timeout"); } else if (errno == EINTR) { - LockInc(&shared->readinterrupts); + LockInc(&shared->c.readinterrupts); if (killed || ((meltdown || terminated) && nowl() - startread > 1)) { - LockInc(&shared->dropped); - LogClose(DescribeClose()); - return ServeFailure(503, "Service Unavailable"); + return HandlePayloadDrop(); } } else { - LockInc(&shared->readerrors); - LOGF("%s payload read error %s", DescribeClient(), strerror(errno)); - return ServeFailure(500, "Internal Server Error"); + return HandlePayloadReadError(); } } } @@ -4197,30 +3924,36 @@ static void ParseRequestParameters(void) { FreeLater(url.params.p); } -static char *OnHttpRequest(void) { - effectivepath.p = url.path.p; - effectivepath.n = url.path.n; - lua_getglobal(L, "OnHttpRequest"); - if (lua_pcall(L, 0, 0, 0) == LUA_OK) { - return CommitOutput(GetLuaResponse()); - } else { - WARNF("%s", lua_tostring(L, -1)); - lua_pop(L, 1); - return ServeError(500, "Internal Server Error"); +static bool HasAtMostThisElement(int h, const char *s) { + size_t i, n; + struct HttpRequestHeader *x; + if (HasHeader(h)) { + n = strlen(s); + if (!SlicesEqualCase(s, n, inbuf.p + msg.headers[h].a, + msg.headers[h].b - msg.headers[h].a)) { + return false; + } + for (i = 0; i < msg.xheaders.n; ++i) { + x = msg.xheaders.p + i; + if (GetHttpHeader(inbuf.p + x->k.a, x->k.b - x->k.a) == h && + !SlicesEqualCase(inbuf.p + x->v.a, x->v.b - x->v.a, s, n)) { + return false; + } + } } + return true; } static char *HandleRequest(void) { char *p; - if (msg.version < 10) { - LockInc(&shared->http09); + if (msg.version == 11) { + LockInc(&shared->c.http11); + } else if (msg.version < 10) { + LockInc(&shared->c.http09); } else if (msg.version == 10) { - LockInc(&shared->http10); - } else if (msg.version == 11) { - LockInc(&shared->http11); + LockInc(&shared->c.http10); } else { - LockInc(&shared->http12); - return ServeFailure(505, "HTTP Version Not Supported"); + return HandleVersionNotSupported(); } if ((p = SynchronizeStream())) return p; if (logbodies) LogBody("received", inbuf.p + hdrsize, msgsize - hdrsize); @@ -4229,27 +3962,23 @@ static char *HandleRequest(void) { } if (msg.method == kHttpOptions && SlicesEqual(inbuf.p + msg.uri.a, msg.uri.b - msg.uri.a, "*", 1)) { - LockInc(&shared->serveroptions); return ServeServerOptions(); } if (msg.method == kHttpConnect) { - LockInc(&shared->connectsrefused); - return ServeFailure(501, "Not Implemented"); + return HandleConnectRefused(); } if (!HasAtMostThisElement(kHttpExpect, "100-continue")) { - LockInc(&shared->expectsrefused); - return ServeFailure(417, "Expectation Failed"); + return HandleExpectFailed(); } if (!HasAtMostThisElement(kHttpTransferEncoding, "identity")) { - LockInc(&shared->transfersrefused); - return ServeFailure(501, "Not Implemented"); + return HandleTransferRefused(); } ParseRequestParameters(); if (!url.path.n || url.path.p[0] != '/' || !IsAcceptablePath(url.path.p, url.path.n) || !IsAcceptableHost(url.host.p, url.host.n) || !IsAcceptablePort(url.port.p, url.port.n)) { - LockInc(&shared->urisrefused); + LockInc(&shared->c.urisrefused); return ServeFailure(400, "Bad Request"); } LOGF("RECEIVED %s HTTP%02d %.*s %s %`'.*s %`'.*s", DescribeClient(), @@ -4257,10 +3986,263 @@ static char *HandleRequest(void) { FreeLater(EncodeUrl(&url, 0)), HeaderLength(kHttpReferer), HeaderData(kHttpReferer), HeaderLength(kHttpUserAgent), HeaderData(kHttpUserAgent)); - if (hasluaglobalhandler) return OnHttpRequest(); + if (hasluaglobalhandler) return LuaOnHttpRequest(); return Route(url.host.p, url.host.n, url.path.p, url.path.n); } +static char *Route(const char *host, size_t hostlen, const char *path, + size_t pathlen) { + char *p; + if (hostlen && (p = RouteHost(host, hostlen, path, pathlen))) { + return p; + } + if (SlicesEqual(path, pathlen, "/", 1)) { + if ((p = ServeIndex("/", 1))) return p; + return ServeListing(); + } else if ((p = RoutePath(path, pathlen))) { + return p; + } else if (SlicesEqual(path, pathlen, "/statusz", 8)) { + return ServeStatusz(); + } else { + LockInc(&shared->c.notfounds); + return ServeError(404, "Not Found"); + } +} + +static char *RoutePath(const char *path, size_t pathlen) { + int m; + long r; + char *p; + struct Asset *a; + DEBUGF("RoutePath(%`'.*s)", pathlen, path); + if ((a = GetAsset(path, pathlen))) { + if ((m = GetMode(a)) & 0004) { + if (!S_ISDIR(m)) { + return HandleAsset(a, path, pathlen); + } else { + return HandleFolder(path, pathlen); + } + } else { + LockInc(&shared->c.forbiddens); + LOGF("asset %`'.*s %#o isn't readable", pathlen, path, m); + return ServeError(403, "Forbidden"); + } + } else if ((r = FindRedirect(path, pathlen)) != -1) { + return HandleRedirect(redirects.p + r); + } else { + return NULL; + } +} + +static char *RouteHost(const char *host, size_t hostlen, const char *path, + size_t pathlen) { + size_t hn, hm; + char *hp, *p, b[96]; + if (hostlen) { + hn = 1 + hostlen + url.path.n; + hm = 3 + 1 + hn; + hp = hm <= sizeof(b) ? b : FreeLater(xmalloc(hm)); + hp[0] = '/'; + mempcpy(mempcpy(hp + 1, host, hostlen), path, pathlen); + if ((p = RoutePath(hp, hn))) return p; + if (!isdigit(host[0])) { + if (hostlen > 4 && + READ32LE(host) == ('w' | 'w' << 8 | 'w' << 16 | '.' << 24)) { + mempcpy(mempcpy(hp + 1, host + 4, hostlen - 4), path, pathlen); + if ((p = RoutePath(hp, hn - 4))) return p; + } else { + mempcpy(mempcpy(mempcpy(hp + 1, "www.", 4), host, hostlen), path, + pathlen); + if ((p = RoutePath(hp, hn + 4))) return p; + } + } + } + return NULL; +} + +static inline bool IsLua(struct Asset *a) { + size_t n; + const char *p; + if (a->file) return endswith(a->file->path, ".lua"); + p = ZIP_CFILE_NAME(zmap + a->cf); + n = ZIP_CFILE_NAMESIZE(zmap + a->cf); + return n > 4 && + READ32LE(p + n - 4) == ('.' | 'l' << 8 | 'u' << 16 | 'a' << 24); +} + +static char *HandleAsset(struct Asset *a, const char *path, size_t pathlen) { +#ifndef STATIC + if (IsLua(a)) return ServeLua(a, path, pathlen); +#endif + if (msg.method == kHttpGet || msg.method == kHttpHead) { + LockInc(&shared->c.staticrequests); + return stpcpy(ServeAsset(a, path, pathlen), + "X-Content-Type-Options: nosniff\r\n"); + } else { + return BadMethod(); + } +} + +static inline int CompareInts(const uint64_t x, uint64_t y) { + return x > y ? 1 : x < y ? -1 : 0; +} + +static inline const char *BisectContentType(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 NULL; +} + +static const char *FindContentType(const char *p, size_t n) { + int c; + uint64_t w; + for (w = 0; n--;) { + c = p[n] & 255; + if (c == '.') return BisectContentType(bswap_64(w)); + w <<= 8; + w |= c; + w |= 0100; + } + return NULL; +} + +static const char *GetContentType(struct Asset *a, const char *path, size_t n) { + const char *r; + if (a->file && (r = FindContentType(a->file->path, strlen(a->file->path)))) { + return r; + } + return firstnonnull( + FindContentType(path, n), + firstnonnull(FindContentType(ZIP_CFILE_NAME(zmap + a->cf), + ZIP_CFILE_NAMESIZE(zmap + a->cf)), + a->istext ? "text/plain" : "application/octet-stream")); +} + +static char *ServeAsset(struct Asset *a, const char *path, size_t pathlen) { + char *p; + uint32_t crc; + const char *ct; + ct = GetContentType(a, path, pathlen); + if (IsNotModified(a)) { + LockInc(&shared->c.notmodifieds); + p = SetStatus(304, "Not Modified"); + } else { + if (!a->file) { + content = (char *)ZIP_LFILE_CONTENT(zmap + a->lf); + contentlength = GetZipCfileCompressedSize(zmap + a->cf); + } else if ((p = OpenAsset(a))) { + return p; + } + if (IsCompressed(a)) { + if (ClientAcceptsGzip()) { + p = ServeAssetPrecompressed(a); + } else { + p = ServeAssetDecompressed(a); + } + } else if (msg.version >= 11 && HasHeader(kHttpRange)) { + p = ServeAssetRange(a); + } else if (!a->file) { + LockInc(&shared->c.identityresponses); + DEBUGF("ServeAssetZipIdentity(%`'s)", ct); + if (Verify(content, contentlength, ZIP_LFILE_CRC32(zmap + a->lf))) { + p = SetStatus(200, "OK"); + } else { + return ServeError(500, "Internal Server Error"); + } + } else if (ClientAcceptsGzip() && + (strlen(ct) >= 5 && !memcasecmp(ct, "text/", 5)) && + 100 < contentlength && contentlength < 1024 * 1024 * 1024) { + p = ServeAssetCompressed(a); + } else { + p = ServeAssetIdentity(a, ct); + } + } + p = AppendContentType(p, ct); + p = stpcpy(p, "Vary: Accept-Encoding\r\n"); + p = AppendHeader(p, "Last-Modified", a->lastmodifiedstr); + if (msg.version >= 11) { + p = AppendCache(p, cacheseconds); + if (!IsCompressed(a)) { + p = stpcpy(p, "Accept-Ranges: bytes\r\n"); + } + } + return p; +} + +static char *SetStatus(unsigned code, const char *reason) { + statuscode = code; + stpcpy(hdrbuf.p, "HTTP/1.0 000 "); + hdrbuf.p[7] += msg.version & 1; + hdrbuf.p[9] += code / 100; + hdrbuf.p[10] += code / 10 % 10; + hdrbuf.p[11] += code % 10; + return AppendCrlf(stpcpy(hdrbuf.p + 13, reason)); +} + +static inline bool MustNotIncludeMessageBody(void) { /* RFC2616 § 4.4 */ + return msg.method == kHttpHead || (100 <= statuscode && statuscode <= 199) || + statuscode == 204 || statuscode == 304; +} + +static inline int GetNetworkCounterIndex(int x) { + switch (x) { + case kIpLoopback: + return offsetof(struct Counters, netloopback) / sizeof(long); + case kIpPrivate: + return offsetof(struct Counters, netprivate) / sizeof(long); + case kIpTestnet: + return offsetof(struct Counters, nettestnet) / sizeof(long); + case kIpAfrinic: + return offsetof(struct Counters, netafrinic) / sizeof(long); + case kIpLacnic: + return offsetof(struct Counters, netlacnic) / sizeof(long); + case kIpApnic: + return offsetof(struct Counters, netapnic) / sizeof(long); + case kIpArin: + return offsetof(struct Counters, netarin) / sizeof(long); + case kIpRipe: + return offsetof(struct Counters, netripe) / sizeof(long); + case kIpDod: + return offsetof(struct Counters, netdod) / sizeof(long); + case kIpAtt: + return offsetof(struct Counters, netatt) / sizeof(long); + case kIpApple: + return offsetof(struct Counters, netapple) / sizeof(long); + case kIpFord: + return offsetof(struct Counters, netford) / sizeof(long); + case kIpCogent: + return offsetof(struct Counters, netcogent) / sizeof(long); + case kIpPrudential: + return offsetof(struct Counters, netprudential) / sizeof(long); + case kIpUsps: + return offsetof(struct Counters, netusps) / sizeof(long); + case kIpComcast: + return offsetof(struct Counters, netcomcast) / sizeof(long); + case kIpAnonymous: + return offsetof(struct Counters, netanonymous) / sizeof(long); + default: + return offsetof(struct Counters, netother) / sizeof(long); + } +} + +static inline void RecordNetworkOrigin(void) { + uint32_t ip; + GetRemoteAddr(&ip, 0); + LockInc(((long *)&shared->c) + GetNetworkCounterIndex(CategorizeIp(ip))); +} + static bool HandleMessage(void) { int rc; int iovlen; @@ -4275,7 +4257,7 @@ static bool HandleMessage(void) { RecordNetworkOrigin(); p = HandleRequest(); } else { - LockInc(&shared->badmessages); + LockInc(&shared->c.badmessages); connectionclose = true; LOGF("%s sent garbage %`'s", DescribeClient(), VisualizeControlCodes(inbuf.p, MIN(128, amtread), 0)); @@ -4284,13 +4266,9 @@ static bool HandleMessage(void) { if (!msgsize) { amtread = 0; connectionclose = true; - LockInc(&shared->synchronizationfailures); + LockInc(&shared->c.synchronizationfailures); DEBUGF("could not synchronize message stream"); } - if (0 && connectionclose) { - LockInc(&shared->shutdowns); - shutdown(client, SHUT_RD); - } if (msg.version >= 10) { p = AppendCrlf(stpcpy(stpcpy(p, "Date: "), shared->currentdate)); if (!branded) p = AppendServer(p, serverheader); @@ -4338,7 +4316,7 @@ static bool HandleMessage(void) { (long)((nowl() - startrequest) * 1e6L)); } Send(iov, iovlen); - LockInc(&shared->messageshandled); + LockInc(&shared->c.messageshandled); ++messageshandled; return true; } @@ -4376,16 +4354,7 @@ static void HandleMessages(void) { if (HandleMessage()) { break; } else if (got) { - LockInc(&shared->frags); - if (++frags == 32) { - SendTimeout(); - LogClose("slowloris"); - LockInc(&shared->slowloris); - return; - } else { - DEBUGF("%s fragged msg added %,ld bytes to %,ld byte buffer", - DescribeClient(), amtread, got); - } + HandleFrag(got); } } if (!got) { @@ -4393,25 +4362,25 @@ static void HandleMessages(void) { return; } } else if (errno == EINTR) { - LockInc(&shared->readinterrupts); + LockInc(&shared->c.readinterrupts); } else if (errno == EAGAIN) { - LockInc(&shared->readtimeouts); + LockInc(&shared->c.readtimeouts); if (amtread) SendTimeout(); LogClose("timeout"); return; } else if (errno == ECONNRESET) { - LockInc(&shared->readresets); + LockInc(&shared->c.readresets); LogClose("reset"); return; } else { - LockInc(&shared->readerrors); + LockInc(&shared->c.readerrors); WARNF("%s read failed %s", DescribeClient(), strerror(errno)); return; } if (killed || (terminated && !amtread) || (meltdown && (!amtread || nowl() - startread > 1))) { if (amtread) { - LockInc(&shared->dropped); + LockInc(&shared->c.dropped); SendServiceUnavailable(); } LogClose(DescribeClose()); @@ -4430,7 +4399,7 @@ static void HandleMessages(void) { } } else { CHECK_LT(msgsize, amtread); - LockInc(&shared->pipelinedrequests); + LockInc(&shared->c.pipelinedrequests); DEBUGF("%,ld pipelined bytes", amtread - msgsize); memmove(inbuf.p, inbuf.p + msgsize, amtread - msgsize); amtread -= msgsize; @@ -4447,20 +4416,6 @@ static void HandleMessages(void) { } } -static void EnterMeltdownMode(void) { - if (shared->lastmeltdown && nowl() - shared->lastmeltdown < 1) return; - WARNF("redbean is melting down (%,d workers)", shared->workers); - LOGIFNEG1(kill(0, SIGUSR2)); - shared->lastmeltdown = nowl(); - ++shared->meltdowns; -} - -static void EmergencyClose(int fd) { - struct linger nolinger = {0}; - setsockopt(fd, SOL_SOCKET, SO_LINGER, &nolinger, sizeof(nolinger)); - close(fd); -} - static void HandleConnection(void) { int pid; clientaddrsize = sizeof(clientaddr); @@ -4481,12 +4436,7 @@ static void HandleConnection(void) { } break; case -1: - FATALF("%s too many processes %s", DescribeServer(), strerror(errno)); - ++shared->forkerrors; - LockInc(&shared->dropped); - EnterMeltdownMode(); - SendServiceUnavailable(); - EmergencyClose(client); + HandleForkFailure(); return; default: ++shared->workers; @@ -4500,7 +4450,7 @@ static void HandleConnection(void) { DEBUGF("%s closing after %,ldµs", DescribeClient(), (long)((nowl() - startconnection) * 1e6L)); if (close(client) == -1) { - LockInc(&shared->closeerrors); + LockInc(&shared->c.closeerrors); WARNF("%s close failed", DescribeClient()); } if (!pid) { @@ -4509,37 +4459,37 @@ static void HandleConnection(void) { CollectGarbage(); } } else if (errno == EINTR || errno == EAGAIN) { - ++shared->acceptinterrupts; + LockInc(&shared->c.acceptinterrupts); } else if (errno == ENFILE) { - LockInc(&shared->enfiles); + LockInc(&shared->c.enfiles); WARNF("%s too many open files", DescribeServer()); EnterMeltdownMode(); } else if (errno == EMFILE) { - LockInc(&shared->emfiles); + LockInc(&shared->c.emfiles); WARNF("%s ran out of open file quota", DescribeServer()); EnterMeltdownMode(); } else if (errno == ENOMEM) { - LockInc(&shared->enomems); + LockInc(&shared->c.enomems); WARNF("%s ran out of memory"); EnterMeltdownMode(); } else if (errno == ENOBUFS) { - LockInc(&shared->enobufs); + LockInc(&shared->c.enobufs); WARNF("%s ran out of buffer"); EnterMeltdownMode(); } else if (errno == ENONET) { - ++shared->enonets; + LockInc(&shared->c.enonets); WARNF("%s network gone", DescribeServer()); sleep(1); } else if (errno == ENETDOWN) { - ++shared->enetdowns; + LockInc(&shared->c.enetdowns); WARNF("%s network down", DescribeServer()); sleep(1); } else if (errno == ECONNABORTED) { - ++shared->acceptresets; + LockInc(&shared->c.acceptresets); WARNF("%s connection reset before accept"); } else if (errno == ENETUNREACH || errno == EHOSTUNREACH || errno == EOPNOTSUPP || errno == ENOPROTOOPT || errno == EPROTO) { - ++shared->accepterrors; + LockInc(&shared->c.accepterrors); WARNF("%s ephemeral accept error %s", DescribeServer(), strerror(errno)); } else { FATALF("%s accept error %s", DescribeServer(), strerror(errno));