From 1c8367022993b1200928d88d025ce1dd2af179be Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Fri, 8 Jul 2022 23:06:46 -0700 Subject: [PATCH] Write more redbean unit tests - Fix DescribeSigset() - Introduce new unix.rmrf() API - Fix redbean sigaction() doc example code - Fix unix.sigaction() w/ more than two args - Improve redbean re module API (non-breaking) - Enhance Lua with Python string multiplication - Make third parameter of unix.socket() default to 0 --- examples/ttyaudio.c | 17 +- libc/intrin/describeflags.internal.h | 8 +- libc/intrin/describesigaction.c | 6 +- libc/intrin/describesigset.c | 23 +-- libc/log/showcrashreports.c | 3 +- libc/stdio/dirstream.c | 3 +- test/libc/intrin/describesigset_test.c | 38 +++++ test/tool/net/argon2_test.lua | 28 +++ test/tool/net/lfuncs_test.lua | 46 ++++- test/tool/net/lre_test.lua | 40 +++++ test/tool/net/lua_test.lua | 21 +++ test/tool/net/lunix_test.lua | 163 +++++++++++++++--- third_party/lua/README.cosmo | 2 + third_party/lua/lstrlib.c | 14 +- third_party/lua/lunix.c | 26 ++- third_party/regex/regcomp.c | 76 +++++---- tool/net/help.txt | 227 ++++++++++++++++++++----- tool/net/lfuncs.c | 2 +- tool/net/lre.c | 193 ++++++++++++++------- tool/net/redbean.c | 6 +- 20 files changed, 738 insertions(+), 204 deletions(-) create mode 100644 test/libc/intrin/describesigset_test.c create mode 100644 test/tool/net/argon2_test.lua create mode 100644 test/tool/net/lre_test.lua create mode 100644 test/tool/net/lua_test.lua diff --git a/examples/ttyaudio.c b/examples/ttyaudio.c index 977b00915..2374157ec 100644 --- a/examples/ttyaudio.c +++ b/examples/ttyaudio.c @@ -7,6 +7,7 @@ │ • http://creativecommons.org/publicdomain/zero/1.0/ │ ╚─────────────────────────────────────────────────────────────────*/ #endif +#include "dsp/core/core.h" #include "libc/calls/calls.h" #include "libc/calls/struct/sigaction.h" #include "libc/log/check.h" @@ -17,6 +18,10 @@ #include "libc/sysv/consts/sig.h" #include "libc/time/time.h" +#define CSI "s" +#define SGR1 "?80" +#define SGR2 "?81" + struct Ring { int i; // read index int j; // write index @@ -30,7 +35,7 @@ struct Speaker { struct Ring buf; // audio playback buffer }; -const int maxar = 32; +const int maxar = 31; const int ptime = 20; struct Speaker s; @@ -64,14 +69,14 @@ void OnAlrm(int sig) { int count; int samps = s.rate / (1000 / ptime); for (i = 0; i < samps; i += count) { - printf("\e["); + printf("\e[" SGR2); count = MIN(samps - i, maxar); for (j = 0; j < count; ++j) { - if (j) printf(";"); - printf("%d", s.buf.p[s.buf.i++] & 0xffff); + printf(";%d", s.buf.p[s.buf.i++] & 0xffff); + /* printf(";%d", mulaw(s.buf.p[s.buf.i++])); */ if (s.buf.i == s.buf.n) break; } - printf("p"); + printf(CSI); if (s.buf.i == s.buf.n) break; } fflush(stdout); @@ -87,7 +92,7 @@ int main(int argc, char* argv[]) { s.channels = 1; LoadAudioFile(&s, "/home/jart/Music/numbers.s16"); - printf("\e[%d;%dy", s.rate, s.channels); + printf("\e[" SGR1 "%d;%d;0" CSI, s.rate, s.channels); fflush(stdout); struct sigaction sa = {.sa_handler = OnAlrm}; diff --git a/libc/intrin/describeflags.internal.h b/libc/intrin/describeflags.internal.h index 726797097..40d82bde3 100644 --- a/libc/intrin/describeflags.internal.h +++ b/libc/intrin/describeflags.internal.h @@ -57,9 +57,9 @@ const char *DescribeRlimitName(char[20], int); const char *DescribeSchedParam(char[32], const struct sched_param *); const char *DescribeSchedPolicy(char[48], int); const char *DescribeSeccompOperation(int); -const char *DescribeSigaction(char[128], int, const struct sigaction *); +const char *DescribeSigaction(char[256], int, const struct sigaction *); const char *DescribeSigaltstk(char[128], int, const struct sigaltstack *); -const char *DescribeSigset(char[64], int, const sigset_t *); +const char *DescribeSigset(char[128], int, const sigset_t *); const char *DescribeSockLevel(char[12], int); const char *DescribeSockOptname(char[32], int, int); const char *DescribeSockaddr(char[128], const struct sockaddr *, size_t); @@ -102,9 +102,9 @@ void DescribeIovNt(const struct NtIovec *, uint32_t, ssize_t); #define DescribeRlimitName(rl) DescribeRlimitName(alloca(20), rl) #define DescribeSchedParam(x) DescribeSchedParam(alloca(32), x) #define DescribeSchedPolicy(x) DescribeSchedPolicy(alloca(48), x) -#define DescribeSigaction(rc, sa) DescribeSigaction(alloca(128), rc, sa) +#define DescribeSigaction(rc, sa) DescribeSigaction(alloca(256), rc, sa) #define DescribeSigaltstk(rc, ss) DescribeSigaltstk(alloca(128), rc, ss) -#define DescribeSigset(rc, ss) DescribeSigset(alloca(64), rc, ss) +#define DescribeSigset(rc, ss) DescribeSigset(alloca(128), rc, ss) #define DescribeSockLevel(x) DescribeSockLevel(alloca(12), x) #define DescribeSockOptname(x, y) DescribeSockOptname(alloca(32), x, y) #define DescribeSockaddr(sa, sz) DescribeSockaddr(alloca(128), sa, sz) diff --git a/libc/intrin/describesigaction.c b/libc/intrin/describesigaction.c index f56e2ac1d..f0a0789e0 100644 --- a/libc/intrin/describesigaction.c +++ b/libc/intrin/describesigaction.c @@ -21,15 +21,15 @@ #include "libc/intrin/describeflags.internal.h" #include "libc/intrin/kprintf.h" -const char *(DescribeSigaction)(char buf[128], int rc, +const char *(DescribeSigaction)(char buf[256], int rc, const struct sigaction *sa) { if (rc == -1) return "n/a"; if (!sa) return "NULL"; if ((!IsAsan() && kisdangerous(sa)) || (IsAsan() && !__asan_is_valid(sa, sizeof(*sa)))) { - ksnprintf(buf, 128, "%p", sa); + ksnprintf(buf, 256, "%p", sa); } else { - ksnprintf(buf, 128, "{.sa_handler=%p, .sa_flags=%#lx, .sa_mask=%s}", + ksnprintf(buf, 256, "{.sa_handler=%t, .sa_flags=%#lx, .sa_mask=%s}", sa->sa_handler, sa->sa_flags, DescribeSigset(rc, &sa->sa_mask)); } return buf; diff --git a/libc/intrin/describesigset.c b/libc/intrin/describesigset.c index 4b9530524..62faa59fd 100644 --- a/libc/intrin/describesigset.c +++ b/libc/intrin/describesigset.c @@ -23,40 +23,41 @@ #include "libc/intrin/kprintf.h" #include "libc/str/str.h" -const char *(DescribeSigset)(char buf[64], int rc, const sigset_t *ss) { +#define N 128 + +const char *(DescribeSigset)(char buf[N], int rc, const sigset_t *ss) { bool gotsome; - int i, n, sig; + int i, sig; sigset_t sigset; if (rc == -1) return "n/a"; if (!ss) return "NULL"; if ((!IsAsan() && kisdangerous(ss)) || (IsAsan() && !__asan_is_valid(ss, sizeof(*ss)))) { - ksnprintf(buf, 64, "%p", ss); + ksnprintf(buf, N, "%p", ss); return buf; } i = 0; - n = 64; sigset = *ss; gotsome = false; - if (popcnt(sigset.__bits[0]) + popcnt(sigset.__bits[1]) > 64) { - i += ksnprintf(buf + i, n - i, "~"); + if (popcnt(sigset.__bits[0] & 0xffffffff) > 16) { + i += ksnprintf(buf + i, N - i, "~"); sigset.__bits[0] = ~sigset.__bits[0]; sigset.__bits[1] = ~sigset.__bits[1]; } - i += ksnprintf(buf + i, n - i, "{"); - for (sig = 1; sig < 128; ++sig) { + i += ksnprintf(buf + i, N - i, "{"); + for (sig = 1; sig < 32; ++sig) { if (sigismember(&sigset, sig)) { if (gotsome) { - sig += ksnprintf(buf + sig, n - sig, ", "); + i += ksnprintf(buf + i, N - i, ","); } else { gotsome = true; } - sig += ksnprintf(buf + sig, n - sig, "%s", strsignal(sig)); + i += ksnprintf(buf + i, N - i, "%s", strsignal(sig) + 3); } } - i += ksnprintf(buf + i, n - i, "}"); + i += ksnprintf(buf + i, N - i, "}"); return buf; } diff --git a/libc/log/showcrashreports.c b/libc/log/showcrashreports.c index d757ce568..0351d8ce8 100644 --- a/libc/log/showcrashreports.c +++ b/libc/log/showcrashreports.c @@ -65,6 +65,7 @@ relegated void RestoreDefaultCrashSignalHandlers(void) { int e; size_t i; sigset_t ss; + --__strace; sigemptyset(&ss); sigprocmask(SIG_SETMASK, &ss, NULL); for (i = 0; i < ARRAYLEN(kCrashSigs); ++i) { @@ -74,10 +75,10 @@ relegated void RestoreDefaultCrashSignalHandlers(void) { errno = e; } } + ++__strace; } static void FreeSigAltStack(void *p) { - InstallCrashHandlers(0); sigaltstack(&g_oldsigaltstack, 0); munmap(p, GetStackSize()); } diff --git a/libc/stdio/dirstream.c b/libc/stdio/dirstream.c index ba44952e1..168fef969 100644 --- a/libc/stdio/dirstream.c +++ b/libc/stdio/dirstream.c @@ -266,7 +266,8 @@ DIR *opendir(const char *name) { } } else if (!IsWindows()) { res = 0; - if ((fd = open(name, O_RDONLY | O_DIRECTORY | O_CLOEXEC)) != -1) { + if ((fd = open(name, O_RDONLY | O_NOCTTY | O_DIRECTORY | O_CLOEXEC)) != + -1) { if (!(res = fdopendir(fd))) { close(fd); } diff --git a/test/libc/intrin/describesigset_test.c b/test/libc/intrin/describesigset_test.c new file mode 100644 index 000000000..30167326c --- /dev/null +++ b/test/libc/intrin/describesigset_test.c @@ -0,0 +1,38 @@ +/*-*- 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 2022 Justine Alexandra Roberts Tunney │ +│ │ +│ Permission to use, copy, modify, and/or distribute this software for │ +│ any purpose with or without fee is hereby granted, provided that the │ +│ above copyright notice and this permission notice appear in all copies. │ +│ │ +│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │ +│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │ +│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │ +│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │ +│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │ +│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │ +│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │ +│ PERFORMANCE OF THIS SOFTWARE. │ +╚─────────────────────────────────────────────────────────────────────────────*/ +#include "libc/calls/struct/sigset.h" +#include "libc/intrin/describeflags.internal.h" +#include "libc/sysv/consts/sig.h" +#include "libc/testlib/testlib.h" + +TEST(DescribeSigset, present) { + sigset_t ss; + sigemptyset(&ss); + sigaddset(&ss, SIGINT); + sigaddset(&ss, SIGUSR1); + EXPECT_STREQ("{INT,USR1}", DescribeSigset(0, &ss)); +} + +TEST(DescribeSigset, absent) { + sigset_t ss; + sigfillset(&ss); + sigdelset(&ss, SIGINT); + sigdelset(&ss, SIGUSR1); + EXPECT_STREQ("~{INT,USR1}", DescribeSigset(0, &ss)); +} diff --git a/test/tool/net/argon2_test.lua b/test/tool/net/argon2_test.lua new file mode 100644 index 000000000..4f58910ba --- /dev/null +++ b/test/tool/net/argon2_test.lua @@ -0,0 +1,28 @@ +-- Copyright 2022 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. + +assert(assert(argon2.hash_encoded("password", "somesalt", { + variant = argon2.variants.argon2_i, + m_cost = 65536, + hash_len = 24, + parallelism = 4, + t_cost = 2, + })) == + "$argon2i$v=19$m=65536,t=2,p=4$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG") + +assert(argon2.verify( + "$argon2i$v=19$m=65536,t=2," .. + "p=4$c29tZXNhbHQ$RdescudvJCsgt3ub+b+dWRWJTmaaJObG", + "password")) diff --git a/test/tool/net/lfuncs_test.lua b/test/tool/net/lfuncs_test.lua index ee5d53320..bb0684f67 100644 --- a/test/tool/net/lfuncs_test.lua +++ b/test/tool/net/lfuncs_test.lua @@ -17,6 +17,9 @@ x = Rdtsc() y = Rdtsc() assert(y > x) +assert(Rdrand() ~= Rdrand()) +assert(Rdseed() ~= Rdseed()) + assert(Bsr(1) == 0) assert(Bsr(2) == 1) assert(Bsr(3) == 1) @@ -29,22 +32,54 @@ assert(Bsf(3) == 0) assert(Bsf(4) == 2) assert(Bsf(0x80000001) == 0) +assert(Popcnt(0) == 0) +assert(Popcnt(1) == 1) +assert(Popcnt(2) == 1) +assert(Popcnt(3) == 2) +assert(Popcnt(0b0111101001101001) == 9) + assert(Lemur64() == 0x1940efe9d47ae889) assert(Lemur64() == 0xd4b3103f567f9974) +assert(hex(0x1940efe9d47ae889) == "0x1940efe9d47ae889") +assert(oct(0x1940efe9d47ae889) == "0145007376472436564211") +assert(bin(0x1940efe9d47ae889) == "0b0001100101000000111011111110100111010100011110101110100010001001") + +assert(EscapeHtml("?hello&there<>") == "?hello&there<>") +assert(EscapeParam("?hello&there<>") == "%3Fhello%26there%3C%3E") + +assert(DecodeLatin1("hello\xff\xc0") == "helloÿÀ") +assert(EncodeLatin1("helloÿÀ") == "hello\xff\xc0") + assert(EncodeLua(nil) == "nil") assert(EncodeLua(0) == "0") assert(EncodeLua(3.14) == "3.14") assert(EncodeLua({1, 2}) == "{1, 2}") +x = {1, 2} +x[3] = x +assert(string.match(EncodeLua(x), "{1, 2, \"cyclic@0x%x+\"}")) + +-- TODO(jart): EncodeLua() should sort tables +-- x = {} +-- x.c = 'c' +-- x.a = 'a' +-- x.b = 'b' +-- assert(EncodeLua(x) == '{a="a", b="b", c="c"}') assert(EncodeJson(nil) == "null") assert(EncodeJson(0) == "0") assert(EncodeJson(3.14) == "3.14") assert(EncodeJson({1, 2}) == "[1,2]") -assert(hex(0x1940efe9d47ae889) == "0x1940efe9d47ae889") -assert(oct(0x1940efe9d47ae889) == "0145007376472436564211") -assert(bin(0x1940efe9d47ae889) == "0b0001100101000000111011111110100111010100011110101110100010001001") +url = ParseUrl("https://jart:pass@redbean.dev/2.0.html?x&y=z#frag") +assert(url.scheme == "https") +assert(url.user == "jart") +assert(url.pass == "pass") +assert(url.host == "redbean.dev") +assert(not url.port) +assert(url.path == "/2.0.html") +assert(EncodeLua(url.params) == '{{"x"}, {"y", "z"}}') +assert(url.fragment == "frag") assert(DecodeBase64("abcdefgABCDE") == "\x69\xb7\x1d\x79\xf8\x00\x04\x20\xc4") assert(EncodeBase64("\x69\xb7\x1d\x79\xf8\x00\x04\x20\xc4") == "abcdefgABCDE") @@ -82,6 +117,7 @@ assert(IndentLines("hi\nthere\n") == " hi\n there\n") assert(IndentLines("hi\nthere\n", 2) == " hi\n there\n") assert(ParseHttpDateTime("Fri, 08 Jul 2022 16:17:43 GMT") == 1657297063) +assert(FormatHttpDateTime(1657297063) == "Fri, 08 Jul 2022 16:17:43 GMT") assert(VisualizeControlCodes("hello\x00") == "hello␀") @@ -97,8 +133,8 @@ assert(Sha256("hello") == "\x2c\xf2\x4d\xba\x5f\xb0\xa3\x0e\x26\xe8\x3b\x2a\xc5\ assert(Sha384("hello") == "\x59\xe1\x74\x87\x77\x44\x8c\x69\xde\x6b\x80\x0d\x7a\x33\xbb\xfb\x9f\xf1\xb4\x63\xe4\x43\x54\xc3\x55\x3b\xcd\xb9\xc6\x66\xfa\x90\x12\x5a\x3c\x79\xf9\x03\x97\xbd\xf5\xf6\xa1\x3d\xe8\x28\x68\x4f") assert(Sha512("hello") == "\x9b\x71\xd2\x24\xbd\x62\xf3\x78\x5d\x96\xd4\x6a\xd3\xea\x3d\x73\x31\x9b\xfb\xc2\x89\x0c\xaa\xda\xe2\xdf\xf7\x25\x19\x67\x3c\xa7\x23\x23\xc3\xd9\x9b\xa5\xc1\x1d\x7c\x7a\xcc\x6e\x14\xb8\xc5\xda\x0c\x46\x63\x47\x5c\x2e\x5c\x3a\xde\xf4\x6f\x73\xbc\xde\xc0\x43") -assert(Deflate("hello") == "\xcbH\xcd\xc9\xc9\x07\x00") -assert(Inflate("\xcbH\xcd\xc9\xc9\x07\x00", 5) == "hello") +assert(assert(Deflate("hello")) == "\xcbH\xcd\xc9\xc9\x07\x00") +assert(assert(Inflate("\xcbH\xcd\xc9\xc9\x07\x00", 5)) == "hello") -- deprecated compression api we wish to forget as quickly as possible assert(Uncompress(Compress("hello")) == "hello") diff --git a/test/tool/net/lre_test.lua b/test/tool/net/lre_test.lua new file mode 100644 index 000000000..f05d7b851 --- /dev/null +++ b/test/tool/net/lre_test.lua @@ -0,0 +1,40 @@ +-- Copyright 2022 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. + +m,a,b,c,d = assert(re.search([[^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$]], "127.0.0.1")) +assert(m == "127.0.0.1") +assert(a == "127") +assert(b == "0") +assert(c == "0") +assert(d == "1") + +p = assert(re.compile[[^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$]]) +m,a,b,c,d = assert(p:search("127.0.0.1")) +assert(m == "127.0.0.1") +assert(a == "127") +assert(b == "0") +assert(c == "0") +assert(d == "1") + +m,a,b,c,d = assert(re.search([[\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)\.\([0-9]*\)]], "127.0.0.1", re.BASIC)) +assert(m == "127.0.0.1") +assert(a == "127") +assert(b == "0") +assert(c == "0") +assert(d == "1") + +p,e = re.compile("[{") +assert(e:errno() == re.EBRACK) +assert(e:doc() == "Missing ']'") diff --git a/test/tool/net/lua_test.lua b/test/tool/net/lua_test.lua new file mode 100644 index 000000000..451695a51 --- /dev/null +++ b/test/tool/net/lua_test.lua @@ -0,0 +1,21 @@ +-- Copyright 2022 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. + +-- test redbean lua language extensions +assert(0b100 == 4) +assert(0200 == 128) +assert("\e" == "\x1b") +assert("hi" * 3 == "hihihi") +assert("hello %d" % {123} == "hello 123") diff --git a/test/tool/net/lunix_test.lua b/test/tool/net/lunix_test.lua index d5338800c..40823c22b 100644 --- a/test/tool/net/lunix_test.lua +++ b/test/tool/net/lunix_test.lua @@ -13,40 +13,147 @@ -- TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -- PERFORMANCE OF THIS SOFTWARE. --- dup()+close() -fd = assert(unix.dup(2)) -assert(unix.close(fd)) +gotsigusr1 = false +tmpdir = "o/tmp/lunix_test.%d" % {unix.getpid()} --- dup2()+close() -assert(assert(unix.dup(2, 10)) == 10) -assert(unix.close(10)) - --- fork()+exit() -if assert(unix.fork()) == 0 then - unix.exit(42) +function OnSigUsr1(sig) + gotsigusr1 = true end -pid, ws = assert(unix.wait()) -assert(unix.WIFEXITED(ws)) -assert(unix.WEXITSTATUS(ws) == 42) --- pledge() -if GetHostOs() == "LINUX" then +function UnixTest() + + -- strsignal + assert(unix.strsignal(9) == "SIGKILL") + assert(unix.strsignal(unix.SIGKILL) == "SIGKILL") + + -- gmtime + year,mon,mday,hour,min,sec,gmtoffsec,wday,yday,dst,zone = assert(unix.gmtime(1657297063)) + assert(year == 2022) + assert(mon == 7) + assert(mday == 8) + assert(hour == 16) + assert(min == 17) + assert(sec == 43) + assert(gmtoffsec == 0) + assert(wday == 5) + assert(yday == 188) + assert(dst == 0) + assert(zone == "GMT") + + -- dup + -- 1. duplicate stderr as lowest available fd + -- 1. close the newly assigned file descriptor + fd = assert(unix.dup(2)) + assert(unix.close(fd)) + + -- dup2 + -- 1. duplicate stderr as fd 10 + -- 1. close the new file descriptor + assert(assert(unix.dup(2, 10)) == 10) + assert(unix.close(10)) + + -- fork + -- basic subprocess creation if assert(unix.fork()) == 0 then - assert(unix.pledge("stdio")) - _, err = unix.socket() - assert(err:errno() == unix.EPERM) - unix.exit(0) + unix.pledge("") + unix.exit(42) end pid, ws = assert(unix.wait()) assert(unix.WIFEXITED(ws)) - assert(unix.WEXITSTATUS(ws) == 0) -elseif GetHostOs() == "OPENBSD" then - if assert(unix.fork()) == 0 then - assert(unix.pledge("stdio")) - unix.socket() - unix.exit(1) + assert(unix.WEXITSTATUS(ws) == 42) + + -- pledge + -- 1. fork off a process + -- 2. sandbox the process + -- 3. then violate its security + if GetHostOs() == "LINUX" then + if assert(unix.fork()) == 0 then + assert(unix.pledge("stdio")) + _, err = unix.socket() + assert(err:errno() == unix.EPERM) + unix.exit(0) + end + pid, ws = assert(unix.wait()) + assert(unix.WIFEXITED(ws)) + assert(unix.WEXITSTATUS(ws) == 0) + elseif GetHostOs() == "OPENBSD" then + if assert(unix.fork()) == 0 then + assert(unix.pledge("stdio")) + unix.socket() + unix.exit(1) + end + pid, ws = assert(unix.wait()) + assert(unix.WIFSIGNALED(ws)) + assert(unix.WTERMSIG(ws) == unix.SIGABRT) end - pid, ws = assert(unix.wait()) - assert(unix.WIFSIGNALED(ws)) - assert(unix.WTERMSIG(ws) == unix.SIGABRT) + + -- sigaction + -- 1. install a signal handler for USR1 + -- 2. block USR1 + -- 3. trigger USR1 signal [it gets enqueued] + -- 4. pause() w/ atomic unblocking of USR1 [now it gets delivered!] + -- 5. restore old signal mask + -- 6. restore old sig handler + oldhand, oldflags, oldmask = assert(unix.sigaction(unix.SIGUSR1, OnSigUsr1)) + oldmask = assert(unix.sigprocmask(unix.SIG_BLOCK, unix.Sigset(unix.SIGUSR1))) + assert(unix.raise(unix.SIGUSR1)) + assert(not gotsigusr1) + ok, err = unix.sigsuspend(oldmask) + assert(not ok) + assert(err:errno() == unix.EINTR) + assert(gotsigusr1) + assert(unix.sigprocmask(unix.SIG_SETMASK, oldmask)) + assert(unix.sigaction(unix.SIGUSR1, oldhand, oldflags, oldmask)) + + -- open + -- 1. create file + -- 2. fill it up + -- 3. inspect it + -- 4. mess with it + fd = assert(unix.open("%s/foo" % {tmpdir}, unix.O_RDWR | unix.O_CREAT | unix.O_TRUNC, 0600)) + assert(assert(unix.fstat(fd)):size() == 0) + assert(unix.ftruncate(fd, 8192)) + assert(assert(unix.fstat(fd)):size() == 8192) + assert(unix.write(fd, "hello")) + assert(unix.lseek(fd, 4096)) + assert(unix.write(fd, "poke")) + assert(unix.lseek(fd, 8192-4)) + assert(unix.write(fd, "poke")) + st = assert(unix.fstat(fd)) + assert(st:size() == 8192) + assert(st:blocks() == 8192/512) + assert((st:mode() & 0777) == 0600) + assert(st:uid() == unix.getuid()) + assert(st:gid() == unix.getgid()) + assert(unix.write(fd, "bear", 4)) + assert(unix.read(fd, 10, 0) == "hellbear\x00\x00") + assert(unix.close(fd)) + fd = assert(unix.open("%s/foo" % {tmpdir})) + assert(unix.lseek(fd, 4)) + assert(unix.read(fd, 4) == "bear") + assert(unix.close(fd)) + fd = assert(unix.open("%s/foo" % {tmpdir}, unix.O_RDWR)) + assert(unix.write(fd, "bear")) + assert(unix.close(fd)) + fd = assert(unix.open("%s/foo" % {tmpdir})) + assert(unix.read(fd, 8) == "bearbear") + assert(unix.close(fd)) + + -- getdents + for name, kind, ino, off in assert(unix.opendir(tmpdir)) do + end + end + +function main() + assert(unix.makedirs(tmpdir)) + ok, err = pcall(UnixTest) + if ok then + assert(unix.rmrf(tmpdir)) + else + print(err) + error('UnixTest failed (%s)' % {tmpdir}) + end +end + +main() diff --git a/third_party/lua/README.cosmo b/third_party/lua/README.cosmo index 4ec2c7a03..1d418f797 100644 --- a/third_party/lua/README.cosmo +++ b/third_party/lua/README.cosmo @@ -34,3 +34,5 @@ LOCAL MODIFICATIONS Added luaL_traceback2() for function parameters in traceback. Added Python-like printf modulus operator for strings. + + Added Python-like printf multiply operator for strings. diff --git a/third_party/lua/lstrlib.c b/third_party/lua/lstrlib.c index e49444ea2..f941687d9 100644 --- a/third_party/lua/lstrlib.c +++ b/third_party/lua/lstrlib.c @@ -320,12 +320,22 @@ static int arith_sub (lua_State *L) { } static int arith_mul (lua_State *L) { - return arith(L, LUA_OPMUL, "__mul"); + if (lua_isinteger(L, 2)) { + // [jart] python multiply string operator + lua_pushcfunction(L, str_rep); + lua_pushvalue(L, 1); + lua_pushvalue(L, 2); + lua_call(L, 2, 1); + return 1; + } else { + return arith(L, LUA_OPMUL, "__mul"); + } } static int arith_mod (lua_State *L) { int i, n; - if (lua_istable(L, 2)) { // [jart] python printf operator + if (lua_istable(L, 2)) { + // [jart] python printf operator lua_len(L, 2); n = lua_tointeger(L, -1); lua_pop(L, 1); diff --git a/third_party/lua/lunix.c b/third_party/lua/lunix.c index 3f522756b..b4e68b074 100644 --- a/third_party/lua/lunix.c +++ b/third_party/lua/lunix.c @@ -386,6 +386,14 @@ static int LuaUnixMakedirs(lua_State *L) { makedirs(luaL_checkstring(L, 1), luaL_optinteger(L, 2, 0755))); } +// unix.rmrf(path:str) +// ├─→ true +// └─→ nil, unix.Errno +static int LuaUnixRmrf(lua_State *L) { + int olderr = errno; + return SysretBool(L, "rmrf", olderr, rmrf(luaL_checkstring(L, 1))); +} + // unix.chdir(path:str) // ├─→ true // └─→ nil, unix.Errno @@ -1204,10 +1212,9 @@ static int LuaUnixGetsockopt(lua_State *L) { static int LuaUnixSocket(lua_State *L) { int olderr = errno; int family = luaL_optinteger(L, 1, AF_INET); - return SysretInteger( - L, "socket", olderr, - socket(family, luaL_optinteger(L, 2, SOCK_STREAM), - luaL_optinteger(L, 3, family == AF_INET ? IPPROTO_TCP : 0))); + return SysretInteger(L, "socket", olderr, + socket(family, luaL_optinteger(L, 2, SOCK_STREAM), + luaL_optinteger(L, 3, 0))); } // unix.socketpair([family:int[, type:int[, protocol:int]]]) @@ -1600,11 +1607,17 @@ static int LuaUnixSigaction(lua_State *L) { luaL_argerror(L, 2, "sigaction handler not integer or function"); unreachable; } - sa.sa_flags = luaL_optinteger(L, 3, 0); if (!lua_isnoneornil(L, 4)) { mask = luaL_checkudata(L, 4, "unix.Sigset"); sa.sa_mask.__bits[0] |= mask->__bits[0]; sa.sa_mask.__bits[1] |= mask->__bits[1]; + lua_remove(L, 4); + } + if (lua_isnoneornil(L, 3)) { + sa.sa_flags = 0; + } else { + sa.sa_flags = lua_tointeger(L, 3); + lua_remove(L, 3); } if (!sigaction(sig, saptr, &oldsa)) { lua_getglobal(L, "__signal_handlers"); @@ -2529,7 +2542,6 @@ static const luaL_Reg kLuaUnix[] = { {"getsid", LuaUnixGetsid}, // get session id of pid {"getsockname", LuaUnixGetsockname}, // get address of local end {"getsockopt", LuaUnixGetsockopt}, // get socket tunings - {"tiocgwinsz", LuaUnixTiocgwinsz}, // pseudoteletypewriter dimensions {"getuid", LuaUnixGetuid}, // get real user id of process {"gmtime", LuaUnixGmtime}, // destructure unix timestamp {"isatty", LuaUnixIsatty}, // detects pseudoteletypewriters @@ -2556,6 +2568,7 @@ static const luaL_Reg kLuaUnix[] = { {"recvfrom", LuaUnixRecvfrom}, // receive udp from some address {"rename", LuaUnixRename}, // rename file or directory {"rmdir", LuaUnixRmdir}, // remove empty directory + {"rmrf", LuaUnixRmrf}, // remove file recursively {"send", LuaUnixSend}, // send tcp to some address {"sendto", LuaUnixSendto}, // send udp to some address {"setgid", LuaUnixSetgid}, // set real group id of process @@ -2580,6 +2593,7 @@ static const luaL_Reg kLuaUnix[] = { {"symlink", LuaUnixSymlink}, // create symbolic link {"sync", LuaUnixSync}, // flushes files and disks {"syslog", LuaUnixSyslog}, // logs to system log + {"tiocgwinsz", LuaUnixTiocgwinsz}, // pseudoteletypewriter dimensions {"truncate", LuaUnixTruncate}, // shrink or extend file medium {"umask", LuaUnixUmask}, // set default file mask {"unlink", LuaUnixUnlink}, // remove file diff --git a/third_party/regex/regcomp.c b/third_party/regex/regcomp.c index 1eb2a7262..a2ed7b029 100644 --- a/third_party/regex/regcomp.c +++ b/third_party/regex/regcomp.c @@ -2388,12 +2388,15 @@ static reg_errcode_t tre_ast_to_tnfa(tre_ast_node_t *node, * Compiles regular expression, e.g. * * regex_t rx; - * EXPECT_EQ(REG_OK, regcomp(&rx, "^[A-Za-z]{2}$", REG_EXTENDED)); - * EXPECT_EQ(REG_OK, regexec(&rx, "→A", 0, NULL, 0)); + * CHECK_EQ(REG_OK, regcomp(&rx, "^[A-Za-z]{2}$", REG_EXTENDED)); + * CHECK_EQ(REG_OK, regexec(&rx, "→A", 0, NULL, 0)); * regfree(&rx); * - * @param preg points to state, and needs regfree() afterwards - * @param regex is utf-8 regular expression string + * @param preg points to caller allocated memory that's used to store + * your regular expression. This memory needn't be initialized. If + * this function succeeds, then `preg` must be passed to regfree() + * later on, to free its associated resources + * @param regex is utf-8 regular expression nul-terminated string * @param cflags can have REG_EXTENDED, REG_ICASE, REG_NEWLINE, REG_NOSUB * @return REG_OK, REG_NOMATCH, REG_BADPAT, etc. * @see regexec(), regfree(), regerror() @@ -2579,39 +2582,48 @@ error_exit: /** * Frees any memory allocated by regcomp(). + * + * The same object may be destroyed by regfree() multiple times, in + * which case subsequent calls do nothing. Once a regex is freed, it may + * be passed to regcomp() to reinitialize it. */ void regfree(regex_t *preg) { - tre_tnfa_t *tnfa; unsigned int i; + tre_tnfa_t *tnfa; tre_tnfa_transition_t *trans; - tnfa = (void *)preg->TRE_REGEX_T_FIELD; - if (!tnfa) return; - for (i = 0; i < tnfa->num_transitions; i++) - if (tnfa->transitions[i].state) { - if (tnfa->transitions[i].tags) - free(tnfa->transitions[i].tags), tnfa->transitions[i].tags = NULL; - if (tnfa->transitions[i].neg_classes) - free(tnfa->transitions[i].neg_classes), - tnfa->transitions[i].neg_classes = NULL; + if ((tnfa = preg->TRE_REGEX_T_FIELD)) { + preg->TRE_REGEX_T_FIELD = 0; + for (i = 0; i < tnfa->num_transitions; i++) + if (tnfa->transitions[i].state) { + if (tnfa->transitions[i].tags) { + free(tnfa->transitions[i].tags); + } + if (tnfa->transitions[i].neg_classes) { + free(tnfa->transitions[i].neg_classes); + } + } + if (tnfa->transitions) { + free(tnfa->transitions); } - if (tnfa->transitions) free(tnfa->transitions), tnfa->transitions = NULL; - if (tnfa->initial) { - for (trans = tnfa->initial; trans->state; trans++) { - if (trans->tags) free(trans->tags), trans->tags = NULL; + if (tnfa->initial) { + for (trans = tnfa->initial; trans->state; trans++) { + if (trans->tags) { + free(trans->tags); + } + } + free(tnfa->initial); } - free(tnfa->initial), tnfa->initial = NULL; + if (tnfa->submatch_data) { + for (i = 0; i < tnfa->num_submatches; i++) { + if (tnfa->submatch_data[i].parents) { + free(tnfa->submatch_data[i].parents); + } + } + free(tnfa->submatch_data); + } + if (tnfa->tag_directions) free(tnfa->tag_directions); + if (tnfa->firstpos_chars) free(tnfa->firstpos_chars); + if (tnfa->minimal_tags) free(tnfa->minimal_tags); + free(tnfa); } - if (tnfa->submatch_data) { - for (i = 0; i < tnfa->num_submatches; i++) - if (tnfa->submatch_data[i].parents) - free(tnfa->submatch_data[i].parents), - tnfa->submatch_data[i].parents = NULL; - free(tnfa->submatch_data), tnfa->submatch_data = NULL; - } - if (tnfa->tag_directions) - free(tnfa->tag_directions), tnfa->tag_directions = NULL; - if (tnfa->firstpos_chars) - free(tnfa->firstpos_chars), tnfa->firstpos_chars = NULL; - if (tnfa->minimal_tags) free(tnfa->minimal_tags), tnfa->minimal_tags = NULL; - free(tnfa), tnfa = NULL; } diff --git a/tool/net/help.txt b/tool/net/help.txt index a260de688..a57b2949f 100644 --- a/tool/net/help.txt +++ b/tool/net/help.txt @@ -357,6 +357,9 @@ LUA ENHANCEMENTS example, you can say `"hello %s" % {"world"}` instead of `string.format("hello %s", "world")`. + - redbean supports a string multiply operator, like Python. For + example, you can say `"hi" * 2` instead of `string.rep("hi", 2)`. + - redbean supports octal (base 8) integer literals. For example `0644 == 420` is the case in redbean, whereas in upstream Lua `0644 == 644` would be the case. @@ -1495,6 +1498,7 @@ CONSTANTS Logging anything at this level will result in a backtrace and process exit. + ──────────────────────────────────────────────────────────────────────────────── LSQLITE3 MODULE @@ -1531,6 +1535,7 @@ LSQLITE3 MODULE we provide an APE build of the SQLite shell which you can use to administrate your redbean database. See the sqlite3.com download above. + ──────────────────────────────────────────────────────────────────────────────── RE MODULE @@ -1540,29 +1545,144 @@ RE MODULE # Example IPv4 Address Regular Expression (see also ParseIP) p = re.compile([[^([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$]]) - m,a,b,c,d = p:search(𝑠) + m,a,b,c,d = assert(p:search(𝑠)) if m then print("ok", tonumber(a), tonumber(b), tonumber(c), tonumber(d)) else print("not ok") end - re.search(regex:str,text:str[,flags:int]) → [match[,group_1,...]] - Shortcut for re.compile plus regex_t*:search. + re.search(regex:str, text:str[, flags:int]) + ├─→ match:str[, group1:str, ...] + └─→ nil, re.Errno - re.compile(regex:str[,flags:int]) → regex_t* - Compiles regular expression, using the POSIX extended syntax. This - has an O(2^𝑛) cost, so it's a good idea to do this from your - /.init.lua file. Flags may contain re.BASIC, re.ICASE, re.NOSUB, - and/or re.NEWLINE. See also regcomp() from libc. + Searches for regular expression match in text. - regex_t*:search(text:str[,flags:int]) → [match[,group_1,...]] - Executes regular expression. This has an O(𝑛) cost. This returns - nothing (nil) if the pattern doesn't match anything. Otherwise it - pushes the matched substring and any parenthesis-captured values - too. Flags may contain re.NOTBOL or re.NOTEOL to indicate whether - or not text should be considered at the start and/or end of a - line. + This is a shorthand notation roughly equivalent to: + + preg = re.compile(regex) + patt = preg:search(re, text) + + `flags` defaults to zero and may have any of: + + - `re.BASIC` + - `re.ICASE` + - `re.NEWLINE` + - `re.NOSUB` + - `re.NOTBOL` + - `re.NOTEOL` + + This has exponential complexity. Please use re.compile() to + compile your regular expressions once from `/.init.lua`. This + API exists for convenience. This isn't recommended for prod. + + This uses POSIX extended syntax by default. + + re.compile(regex:str[, flags:int]) → re.Regex + ├─→ preg:re.Regex + └─→ nil, re.Errno + + Compiles regular expression. + + `flags` defaults to zero and may have any of: + + - `re.BASIC` + - `re.ICASE` + - `re.NEWLINE` + - `re.NOSUB` + + This has an O(2^𝑛) cost. Consider compiling regular + expressions once from your `/.init.lua` file. + + If `regex` is an untrusted user value, then `unix.setrlimit` + should be used to impose cpu and memory quotas for security. + + This uses POSIX extended syntax by default. + +──────────────────────────────────────────────────────────────────────────────── + RE REGEX OBJECT + + re.Regex:search(text:str[, flags:int]) + ├─→ match:str[, group1:str, ...] + └─→ nil, re.Errno + + Executes precompiled regular expression. + + Returns nothing (nil) if the pattern doesn't match anything. + Otherwise it pushes the matched substring and any + parenthesis-captured values too. Flags may contain re.NOTBOL + or re.NOTEOL to indicate whether or not text should be + considered at the start and/or end of a line. + + `flags` defaults to zero and may have any of: + + - `re.NOTBOL` + - `re.NOTEOL` + + This has an O(𝑛) cost. + +──────────────────────────────────────────────────────────────────────────────── + RE ERRNO OBJECT + + re.Errno:errno() + └─→ errno:int + + Returns regex error number. + + re.Errno:doc() + └─→ description:str + + Returns English string describing error code. + + re.Errno:__tostring() + └─→ str + + Delegates to re.Errno:doc() + +──────────────────────────────────────────────────────────────────────────────── + RE ERRORS + + re.NOMATCH + No match + + re.BADPAT + Invalid regex + + re.ECOLLATE + Unknown collating element + + re.ECTYPE + Unknown character class name + + re.EESCAPE + Trailing backslash + + re.ESUBREG + Invalid back reference + + re.EBRACK + Missing `]` + + re.EPAREN + Missing `)` + + re.EBRACE + Missing `}` + + re.BADBR + Invalid contents of `{}` + + re.ERANGE + Invalid character range. + + re.ESPACE + Out of memory + + re.BADRPT + Repetition not preceded by valid expression + +──────────────────────────────────────────────────────────────────────────────── + RE FLAGS re.BASIC Use this flag if you prefer the default POSIX regex syntax. We use @@ -1578,27 +1698,29 @@ RE MODULE may only be used with re.compile and re.search. re.NEWLINE - Use this flag to change the handling of NEWLINE (\x0a) characters. - When this flag is set, (1) a NEWLINE shall not be matched by a "." - or any form of a non-matching list, (2) a "^" shall match the - zero-length string immediately after a NEWLINE (regardless of - re.NOTBOL), and (3) a "$" shall match the zero-length string - immediately before a NEWLINE (regardless of re.NOTEOL). + Use this flag to change the handling of NEWLINE (\x0a) + characters. When this flag is set, (1) a NEWLINE shall not be + matched by a "." or any form of a non-matching list, (2) a "^" + shall match the zero-length string immediately after a NEWLINE + (regardless of re.NOTBOL), and (3) a "$" shall match the + zero-length string immediately before a NEWLINE (regardless of + re.NOTEOL). re.NOSUB Causes re.search to only report success and failure. This is - reported via the API by returning empty string for success. This - flag may only be used with re.compile and re.search. + reported via the API by returning empty string for success. + This flag may only be used with re.compile and re.search. re.NOTBOL - The first character of the string pointed to by string is not the - beginning of the line. This flag may only be used with re.search - and regex_t*:search. + The first character of the string pointed to by string is not + the beginning of the line. This flag may only be used with + re.search and re.Regex:search. re.NOTEOL - The last character of the string pointed to by string is not the - end of the line. This flag may only be used with re.search and - regex_t*:search. + The last character of the string pointed to by string is not + the end of the line. This flag may only be used with re.search + and re.Regex:search. + ──────────────────────────────────────────────────────────────────────────────── MAXMIND MODULE @@ -1621,6 +1743,7 @@ MAXMIND MODULE For further details, please see maxmind.lua in redbean-demo.com. + ──────────────────────────────────────────────────────────────────────────────── ARGON2 MODULE @@ -1688,6 +1811,7 @@ ARGON2 MODULE "password") true + ──────────────────────────────────────────────────────────────────────────────── UNIX MODULE @@ -2276,6 +2400,18 @@ UNIX MODULE thereby assisting with simple absolute filename checks in addition to enabling one to exceed the traditional 260 character limit. + unix.rmrf(path:str) + ├─→ true + └─→ nil, unix.Errno + + Recursively removes filesystem path. + + Like unix.makedirs() this function isn't actually a system call but + rather is a Libc convenience wrapper. It's intended to be equivalent + to using the UNIX shell's `rm -rf path` command. + + `path` is the file or directory path you wish to destroy. + unix.fcntl(fd:int, cmd:int, ...) ├─→ ... └─→ nil, unix.Errno @@ -2532,7 +2668,7 @@ UNIX MODULE `whence` can be one of: - - `SEEK_SET`: Sets the file position to `offset` + - `SEEK_SET`: Sets the file position to `offset` [default] - `SEEK_CUR`: Sets the file position to `position + offset` - `SEEK_END`: Sets the file position to `filesize + offset` @@ -2580,14 +2716,14 @@ UNIX MODULE - `SOCK_CLOEXEC` - `SOCK_NONBLOCK` - `protocol` defaults to `IPPROTO_TCP` for AF_INET` and `0` for - `AF_UNIX`. It can also be: + `protocol` may be any of: - - `IPPROTO_IP` - - `IPPROTO_ICMP` + - `0` to let kernel choose [default] - `IPPROTO_TCP` - `IPPROTO_UDP` - `IPPROTO_RAW` + - `IPPROTO_IP` + - `IPPROTO_ICMP` unix.socketpair([family:int[, type:int[, protocol:int]]]) ├─→ fd1:int, fd2:int @@ -2964,18 +3100,19 @@ UNIX MODULE Example: - assert(unix.sigaction(unix.SIGUSR1, function(sig) - gotsigusr1 = true - end)) - gotsigusr1 = false - assert(unix.raise(unix.SIGUSR1)) - ok, err = unix.sigsuspend() - assert(err:errno == unix.EINTR) - if gotsigusr1 - print('hooray the signal was delivered') - else - print('oh no some other signal was handled') + function OnSigUsr1(sig) + gotsigusr1 = true end + gotsigusr1 = false + oldmask = assert(unix.sigprocmask(unix.SIG_BLOCK, unix.Sigset(unix.SIGUSR1))) + assert(unix.sigaction(unix.SIGUSR1, OnSigUsr1)) + assert(unix.raise(unix.SIGUSR1)) + assert(not gotsigusr1) + ok, err = unix.sigsuspend(oldmask) + assert(not ok) + assert(err:errno() == unix.EINTR) + assert(gotsigusr1) + assert(unix.sigprocmask(unix.SIG_SETMASK, oldmask)) It's a good idea to not do too much work in a signal handler. diff --git a/tool/net/lfuncs.c b/tool/net/lfuncs.c index 89ad63112..9f227c4e6 100644 --- a/tool/net/lfuncs.c +++ b/tool/net/lfuncs.c @@ -405,7 +405,7 @@ int LuaResolveIp(lua_State *L) { } static int LuaCheckControlFlags(lua_State *L, int idx) { - int f = luaL_checkinteger(L, idx); + int f = luaL_optinteger(L, idx, 0); if (f & ~(kControlWs | kControlC0 | kControlC1)) { luaL_argerror(L, idx, "invalid control flags"); unreachable; diff --git a/tool/net/lre.c b/tool/net/lre.c index 3f1eb2ddf..4adcb9dcf 100644 --- a/tool/net/lre.c +++ b/tool/net/lre.c @@ -17,13 +17,12 @@ │ PERFORMANCE OF THIS SOFTWARE. │ ╚─────────────────────────────────────────────────────────────────────────────*/ #include "libc/macros.internal.h" -#include "libc/x/x.h" #include "third_party/lua/lauxlib.h" #include "third_party/regex/regex.h" -static const char kRegCode[][8] = { - "OK", "NOMATCH", "BADPAT", "COLLATE", "ECTYPE", "EESCAPE", "ESUBREG", - "EBRACK", "EPAREN", "EBRACE", "BADBR", "ERANGE", "ESPACE", "BADRPT", +struct ReErrno { + int err; + char doc[64]; }; static void LuaSetIntField(lua_State *L, const char *k, lua_Integer v) { @@ -31,40 +30,53 @@ static void LuaSetIntField(lua_State *L, const char *k, lua_Integer v) { lua_setfield(L, -2, k); } +static int LuaReReturnError(lua_State *L, regex_t *r, int rc) { + struct ReErrno *e; + lua_pushnil(L); + e = lua_newuserdatauv(L, sizeof(struct ReErrno), 0); + luaL_setmetatable(L, "re.Errno"); + e->err = rc; + regerror(rc, r, e->doc, sizeof(e->doc)); + return 2; +} + static regex_t *LuaReCompileImpl(lua_State *L, const char *p, int f) { - int e; + int rc; regex_t *r; - r = xmalloc(sizeof(regex_t)); + r = lua_newuserdatauv(L, sizeof(regex_t), 0); + luaL_setmetatable(L, "re.Regex"); f &= REG_EXTENDED | REG_ICASE | REG_NEWLINE | REG_NOSUB; f ^= REG_EXTENDED; - if ((e = regcomp(r, p, f)) != REG_OK) { - free(r); - luaL_error(L, "regcomp(%s) → REG_%s", p, - kRegCode[MAX(0, MIN(ARRAYLEN(kRegCode) - 1, e))]); - unreachable; + if ((rc = regcomp(r, p, f)) == REG_OK) { + return r; + } else { + LuaReReturnError(L, r, rc); + return NULL; } - return r; } static int LuaReSearchImpl(lua_State *L, regex_t *r, const char *s, int f) { - int i, n; + int rc, i, n; regmatch_t *m; - n = r->re_nsub + 1; - m = xcalloc(n, sizeof(regmatch_t)); - if (regexec(r, s, n, m, f >> 8) == REG_OK) { + luaL_Buffer tmp; + n = 1 + r->re_nsub; + m = (regmatch_t *)luaL_buffinitsize(L, &tmp, n * sizeof(regmatch_t)); + if ((rc = regexec(r, s, n, m, f >> 8)) == REG_OK) { for (i = 0; i < n; ++i) { lua_pushlstring(L, s + m[i].rm_so, m[i].rm_eo - m[i].rm_so); } + return n; } else { - n = 0; + return LuaReReturnError(L, r, rc); } - free(m); - return n; } +//////////////////////////////////////////////////////////////////////////////// +// re + static int LuaReSearch(lua_State *L) { + int f; regex_t *r; - int i, e, n, f; const char *p, *s; p = luaL_checkstring(L, 1); s = luaL_checkstring(L, 2); @@ -74,56 +86,51 @@ static int LuaReSearch(lua_State *L) { luaL_argerror(L, 3, "invalid flags"); unreachable; } - r = LuaReCompileImpl(L, p, f); - n = LuaReSearchImpl(L, r, s, f); - regfree(r); - free(r); - return n; + if ((r = LuaReCompileImpl(L, p, f))) { + return LuaReSearchImpl(L, r, s, f); + } else { + return 2; + } } static int LuaReCompile(lua_State *L) { - int f, e; + int f; + regex_t *r; const char *p; - regex_t *r, **u; p = luaL_checkstring(L, 1); f = luaL_optinteger(L, 2, 0); if (f & ~(REG_EXTENDED | REG_ICASE | REG_NEWLINE | REG_NOSUB)) { - luaL_argerror(L, 3, "invalid flags"); + luaL_argerror(L, 2, "invalid flags"); unreachable; } - r = LuaReCompileImpl(L, p, f); - u = lua_newuserdata(L, sizeof(regex_t *)); - luaL_setmetatable(L, "regex_t*"); - *u = r; - return 1; + if ((r = LuaReCompileImpl(L, p, f))) { + return 1; + } else { + return 2; + } } +//////////////////////////////////////////////////////////////////////////////// +// re.Regex + static int LuaReRegexSearch(lua_State *L) { int f; - regex_t **u; + regex_t *r; const char *s; - u = luaL_checkudata(L, 1, "regex_t*"); + r = luaL_checkudata(L, 1, "re.Regex"); s = luaL_checkstring(L, 2); f = luaL_optinteger(L, 3, 0); - if (!*u) { - luaL_argerror(L, 1, "destroyed"); - unreachable; - } if (f & ~(REG_NOTBOL << 8 | REG_NOTEOL << 8)) { luaL_argerror(L, 3, "invalid flags"); unreachable; } - return LuaReSearchImpl(L, *u, s, f); + return LuaReSearchImpl(L, r, s, f); } static int LuaReRegexGc(lua_State *L) { - regex_t **u; - u = luaL_checkudata(L, 1, "regex_t*"); - if (*u) { - regfree(*u); - free(*u); - *u = NULL; - } + regex_t *r; + r = luaL_checkudata(L, 1, "re.Regex"); + regfree(r); return 0; } @@ -143,8 +150,8 @@ static const luaL_Reg kLuaReRegexMeta[] = { {NULL, NULL}, // }; -static void LuaReRegex(lua_State *L) { - luaL_newmetatable(L, "regex_t*"); +static void LuaReRegexObj(lua_State *L) { + luaL_newmetatable(L, "re.Regex"); luaL_setfuncs(L, kLuaReRegexMeta, 0); luaL_newlibtable(L, kLuaReRegexMeth); luaL_setfuncs(L, kLuaReRegexMeth, 0); @@ -152,14 +159,84 @@ static void LuaReRegex(lua_State *L) { lua_pop(L, 1); } -int LuaRe(lua_State *L) { - luaL_newlib(L, kLuaRe); - LuaSetIntField(L, "BASIC", REG_EXTENDED); - LuaSetIntField(L, "ICASE", REG_ICASE); - LuaSetIntField(L, "NEWLINE", REG_NEWLINE); - LuaSetIntField(L, "NOSUB", REG_NOSUB); - LuaSetIntField(L, "NOTBOL", REG_NOTBOL << 8); - LuaSetIntField(L, "NOTEOL", REG_NOTEOL << 8); - LuaReRegex(L); +//////////////////////////////////////////////////////////////////////////////// +// re.Errno + +static struct ReErrno *GetReErrno(lua_State *L) { + return luaL_checkudata(L, 1, "re.Errno"); +} + +// re.Errno:errno() +// └─→ errno:int +static int LuaReErrnoErrno(lua_State *L) { + lua_pushinteger(L, GetReErrno(L)->err); + return 1; +} + +// re.Errno:doc() +// └─→ description:str +static int LuaReErrnoDoc(lua_State *L) { + lua_pushstring(L, GetReErrno(L)->doc); + return 1; +} + +static const luaL_Reg kLuaReErrnoMeth[] = { + {"errno", LuaReErrnoErrno}, // + {"doc", LuaReErrnoDoc}, // + {0}, // +}; + +static const luaL_Reg kLuaReErrnoMeta[] = { + {"__tostring", LuaReErrnoDoc}, // + {0}, // +}; + +static void LuaReErrnoObj(lua_State *L) { + luaL_newmetatable(L, "re.Errno"); + luaL_setfuncs(L, kLuaReErrnoMeta, 0); + luaL_newlibtable(L, kLuaReErrnoMeth); + luaL_setfuncs(L, kLuaReErrnoMeth, 0); + lua_setfield(L, -2, "__index"); + lua_pop(L, 1); +} + +//////////////////////////////////////////////////////////////////////////////// + +_Alignas(1) static const struct thatispacked { + const char s[8]; + char x; +} kReMagnums[] = { + {"BASIC", REG_EXTENDED}, // compile flag + {"ICASE", REG_ICASE}, // compile flag + {"NEWLINE", REG_NEWLINE}, // compile flag + {"NOSUB", REG_NOSUB}, // compile flag + {"NOMATCH", REG_NOMATCH}, // error + {"BADPAT", REG_BADPAT}, // error + {"ECOLLATE", REG_ECOLLATE}, // error + {"ECTYPE", REG_ECTYPE}, // error + {"EESCAPE", REG_EESCAPE}, // error + {"ESUBREG", REG_ESUBREG}, // error + {"EBRACK", REG_EBRACK}, // error + {"EPAREN", REG_EPAREN}, // error + {"EBRACE", REG_EBRACE}, // error + {"BADBR", REG_BADBR}, // error + {"ERANGE", REG_ERANGE}, // error + {"ESPACE", REG_ESPACE}, // error + {"BADRPT", REG_BADRPT}, // error +}; + +int LuaRe(lua_State *L) { + int i; + char buf[9]; + luaL_newlib(L, kLuaRe); + LuaSetIntField(L, "NOTBOL", REG_NOTBOL << 8); // search flag + LuaSetIntField(L, "NOTEOL", REG_NOTEOL << 8); // search flag + for (i = 0; i < ARRAYLEN(kReMagnums); ++i) { + memcpy(buf, kReMagnums[i].s, 8); + buf[8] = 0; + LuaSetIntField(L, buf, kReMagnums[i].x); + } + LuaReRegexObj(L); + LuaReErrnoObj(L); return 1; } diff --git a/tool/net/redbean.c b/tool/net/redbean.c index b9cbcb15a..705880e6f 100644 --- a/tool/net/redbean.c +++ b/tool/net/redbean.c @@ -5398,7 +5398,11 @@ static void LuaInit(void) { lua_State *L = GL; LuaSetArgv(L); if (interpretermode) { - exit(LuaInterpreter(L)); + int rc = LuaInterpreter(L); + if (IsModeDbg()) { + CheckForMemoryLeaks(); + } + exit(rc); } if (LuaRunAsset("/.init.lua", true)) { hasonhttprequest = IsHookDefined("OnHttpRequest");